Created
February 25, 2023 15:21
-
-
Save wusuopu/356684834b204362270048809ce68b92 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# encoding: utf-8 | |
# traefik provider 配置: | |
# providers: | |
# http: | |
# endpoint: "http://<host>:<port>/api/config" | |
# pollInterval: "10s" | |
# pollTimeout: "5s" | |
import subprocess | |
import json | |
import os | |
import io | |
import yaml | |
import http.server | |
import socketserver | |
PORT = int(os.environ.get('HTTP_PORT', 17890)) | |
PODMAN_BIN = 'podman' | |
# 缓存的数据 | |
CACHED_CONTAINER_IDS = [] | |
CACHED_CONFIG = None | |
CACHED_CONFIG_YML = b'' | |
# =============================================================== | |
class MyHandler(http.server.BaseHTTPRequestHandler): | |
def do_GET(self): | |
self.send_response(200) | |
self.send_header("Content-type", "text/yaml") | |
self.end_headers() | |
self.wfile.write(dump_config_string()) | |
def run_server(): | |
with socketserver.TCPServer(("", PORT), MyHandler) as httpd: | |
print("serving at port", PORT) | |
httpd.serve_forever() | |
# =============================================================== | |
def get_path(obj, path, defaultValue=None): | |
fields = path.split('.') | |
ret = obj | |
i = 1 | |
for p in fields: | |
ret = ret.get(p) | |
if i < len(fields) and not isinstance(ret, dict): | |
ret = {} | |
i += 1 | |
if ret is None: | |
ret = defaultValue | |
return ret | |
def set_path(obj, path, value): | |
fields = path.split('.') | |
ret = obj | |
i = 1 | |
for p in fields: | |
if i == len(fields): | |
ret[p] = value | |
continue | |
if p not in ret: | |
ret[p] = {} | |
ret = ret[p] | |
i += 1 | |
def load_config(filename): | |
data = '' | |
try: | |
with open(filename) as fp: | |
data = fp.read() | |
except Exception: | |
pass | |
return data | |
def run_podman(cmd): | |
args = "%s %s" % (PODMAN_BIN, cmd) | |
print('run: ', args) | |
p = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE) | |
p.wait() | |
if p.returncode != 0: | |
return '' | |
return p.stdout.read().decode('utf8') | |
def parse_ip(obj): | |
ip = get_path(obj, 'NetworkSettings.IPAddress') | |
if ip: | |
return ip | |
networks = get_path(obj, 'NetworkSettings.Networks', {}) | |
for n in networks: | |
ip = networks[n].get('IPAddress') | |
if ip: | |
return ip | |
def parse_labels(labels, ip): | |
config = {} | |
for l in labels: | |
if not l.startswith('traefik.'): | |
continue | |
set_path(config, l.lower(), labels[l]) | |
if not get_path(config, 'traefik.http.routers') or not get_path(config, 'traefik.http.services'): | |
return {} | |
routers = config['traefik']['http']['routers'] | |
services = config['traefik']['http']['services'] | |
for r in routers: | |
if not routers[r].get('service'): | |
routers[r]['service'] = r | |
port = get_path(services[r], 'loadbalancer.server.port', '80') | |
set_path(services[r], 'loadbalancer.server.url', 'http://%s:%s' % (ip, port)) | |
return config['traefik']['http'] | |
def generate_config(): | |
global CACHED_CONTAINER_IDS | |
global CACHED_CONFIG | |
global CACHED_CONFIG_YML | |
containers = [] | |
for line in run_podman('ps').splitlines()[1:]: | |
containers.append(line.split()[0]) | |
has_changed = False | |
if len(containers) != len(CACHED_CONTAINER_IDS): | |
has_changed = True | |
else: | |
for c in containers: | |
if c not in CACHED_CONTAINER_IDS: | |
# 有新的容器 | |
has_changed = True | |
break | |
if not has_changed and CACHED_CONFIG is not None: | |
# 容器没有变化,则返回缓存的结果 | |
return [CACHED_CONFIG, False] | |
CACHED_CONTAINER_IDS = containers | |
data = [] | |
for container in containers: | |
info = run_podman("inspect %s" % (container)) | |
data = data + json.loads(info) | |
servers = { 'routers': {}, 'services': {}, } | |
for item in data: | |
labels = get_path(item, 'Config.Labels', {}) | |
if labels.get('traefik.enable') != 'true': | |
continue | |
ip = parse_ip(item) | |
if not ip: | |
continue | |
config = parse_labels(labels, ip) | |
if not get_path(config, 'routers') or not get_path(config, 'services'): | |
continue | |
for r in config['routers']: | |
if r not in servers['routers']: | |
servers['routers'][r] = {} | |
servers['routers'][r].update(config['routers'][r]) | |
for s in config['services']: | |
if s not in servers['services']: | |
servers['services'][s] = {'loadbalancer': {'servers': []}} | |
urls = get_path(servers['services'][s], 'loadbalancer.servers') | |
url = get_path(config['services'][s], 'loadbalancer.server.url') | |
urls.append({'url': url}) | |
CACHED_CONFIG = {'http': servers} | |
return [CACHED_CONFIG, True] | |
def dump_config_file(): | |
""" 将配置生成到文件 """ | |
filename = os.path.join(os.path.dirname(__file__), 'etc_traefik/dynamic_conf.yml') | |
old_config = load_config(filename) | |
config, new = generate_config() | |
with open(filename, 'w') as fp: | |
yaml.dump(config, fp) | |
new_config = load_config(filename) | |
if old_config != new_config: | |
run_podman('stop traefik') | |
run_podman('start traefik') | |
def dump_config_string(): | |
global CACHED_CONFIG_YML | |
config, new = generate_config() | |
if new: | |
output = io.StringIO() | |
yaml.dump(config, output) | |
CACHED_CONFIG_YML = output.getvalue().encode('utf8') | |
output.close() | |
return CACHED_CONFIG_YML | |
def main(): | |
run_server() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment