Skip to content

Instantly share code, notes, and snippets.

@wusuopu
Created February 25, 2023 15:21
Show Gist options
  • Save wusuopu/356684834b204362270048809ce68b92 to your computer and use it in GitHub Desktop.
Save wusuopu/356684834b204362270048809ce68b92 to your computer and use it in GitHub Desktop.
#!/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