diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0418745 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM debian:8 + +MAINTAINER Joachim Lusiardi + +RUN apt-get update; +RUN apt-get install -y python3-pip +RUN apt-get install -y nginx + + +ADD nginx.conf /etc/nginx.conf +ADD start.sh /start.sh +RUN chmod +x /start.sh + +ADD nginx_proxy.py /nginx_proxy.py +RUN chmod +x /nginx_proxy.py +RUN pip3 install docker-py + +VOLUME ["/keys"] +EXPOSE 80 +ENTRYPOINT /start.sh + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..18a021f --- /dev/null +++ b/nginx.conf @@ -0,0 +1,18 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + sendfile on; + keepalive_timeout 65; + log_format https_combined '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" HTTPS'; + log_format http_combined '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" HTTP'; + + include /tmp/nginx/*; +} + +daemon on; +error_log /dev/stdout info; + diff --git a/nginx_proxy.py b/nginx_proxy.py index 611094f..aaa3bb1 100755 --- a/nginx_proxy.py +++ b/nginx_proxy.py @@ -1,27 +1,32 @@ #!/usr/bin/python3.4 -import os -import sys -import http.client -import json -import signal +from docker import Client +from docker.errors import APIError from string import Template +import json +import datetime +import signal +import os +import logging +import sys -target_path="/tmp/nginx" -pid_file="/run/nginx.pid" -non_location_template = """server { +target_path="/tmp/nginx/" +pid_file="/var/run/nginx.pid" +non_location_template = """# proxy for container '$containername' +server { listen $listen; server_name $name; location / { proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Forwarded-For $$remote_addr; proxy_set_header Host $$host; - proxy_pass http://$ip:$port/; + proxy_pass http://$ip:$port/; } } """ -location_template="""server { +location_template="""# proxy for container '$containername' +server { listen $listen; server_name $name; location / { @@ -31,116 +36,124 @@ location_template="""server { proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Forwarded-For $$remote_addr; proxy_set_header Host $$host; - proxy_pass http://$ip:$port/; + proxy_pass http://$ip:$port/; } } """ -def get_pid(): - with open(pid_file, 'r') as file: - return int(file.read()) +def print_json(data): + """Prints the given value in JSON to stdout. Use this for debugging only""" + print(json.dumps(data, sort_keys=True, indent=4)) -def get_container_data(): - conn = http.client.HTTPConnection("localhost:2375") - conn.request("GET", "/containers/json") - response = conn.getresponse() - data = response.read().decode("UTF-8") - data = json.loads(data) -# print(json.dumps(data, sort_keys=True, indent=4)) - conn.close() - return data - -def inspect_container(container_id): - conn = http.client.HTTPConnection("localhost:2375") - conn.request("GET", "/containers/"+str(container_id)+"/json") - response = conn.getresponse() - data = response.read().decode("UTF-8") - data = json.loads(data) -# print(json.dumps(data, sort_keys=True, indent=4)) - conn.close() - return data - -def analyse_env_vars(data): +def analyse_env_vars(inspect_data): + """Extracts the environment variables from the given result of an 'inspect + container' call.""" env_data = {} - for env_var in data: + for env_var in inspect_data['Config']['Env']: t = env_var.split("=") env_data[t[0]] = t[1] return env_data def analyse_proxy_data(data): + """Extracts the data for the proxy configuration (envrionment variable + 'PROXY_DATA' and converts it to a dictionary.""" proxy_data = {} - for proxy_var in data.split(','): + for proxy_var in data['PROXY_DATA'].split(','): t = proxy_var.split(":",1) proxy_data[t[0]] = t[1] return proxy_data -def handle_event(event): - files = os.listdir(target_path) - print('deleting files') - for file in files: - if file.startswith('proxy_'): - os.remove(target_path+'/'+file) - event_data = json.loads(event) - containers_data = get_container_data() - for container_data in containers_data: - inspect_data = inspect_container(container_data['Id']) - env_data = analyse_env_vars(inspect_data['Config']['Env']) - if 'PROXY_DATA' in env_data: - container_id = container_data['Id'] - ip = inspect_data['NetworkSettings']['IPAddress'] - proxy_data = analyse_proxy_data(env_data['PROXY_DATA']) +def extract_ip(inspect_data): + """extracts the container's ip from the given inspect data""" + return inspect_data['NetworkSettings']['IPAddress'] - server_name = '' - if 'server_name' in proxy_data: - server_name = proxy_data['server_name'] - - port = 0 - if 'port' in proxy_data: - port = proxy_data['port'] +def extract_name(inspect_data): + """extracts the container's name from the given inspect data""" + return inspect_data['Name'] - location = '' - if 'location' in proxy_data: - location = proxy_data['location'] +def get_if_available(dict, key, defValue): + if key in dict: + return dict[key] + else: + return defValue - listen ='*:80' - if 'ip' in proxy_data: - listen = proxy_data['ip']+':80' +def handle_container(id): + """This function take a container's id and collects all data required + to create a proper proxy configuration. The configuration is then + written to the directory of temporary nginx files""" + inspect_data = client.inspect_container(id) + env_vars = analyse_env_vars(inspect_data) + if 'PROXY_DATA' in env_vars: + proxy_data = analyse_proxy_data(env_vars) + substitutes = { + 'containername': extract_name(inspect_data), + 'ip': extract_ip(inspect_data), + 'location': get_if_available(proxy_data, 'location', ''), + 'name': get_if_available(proxy_data, 'server_name', ''), + 'port': get_if_available(proxy_data, 'port', 80), + 'listen': get_if_available(proxy_data, 'ip', '*') + ':80' + } + logging.info('writing to %sproxy_%s', target_path, id) + with open(target_path + '/proxy_'+id, 'w') as file: + if substitutes['location'] == '': + del substitutes['location'] + file.write(Template(non_location_template).substitute(substitutes)) + else: + file.write(Template(location_template).substitute(substitutes)) - print('writing /tmp/nginx/proxy_'+container_id) - with open('/tmp/nginx/proxy_'+container_id, 'w') as file: - if location == '': - s = Template(non_location_template) - file.write(s.substitute(name=server_name,ip=ip,port=port,listen=listen)) - else: - s = Template(location_template) - file.write(s.substitute(name=server_name,ip=ip,port=port,listen=listen,location=location)) - print('HUPing nginx') - os.kill(pid, signal.SIGHUP) +def reload_nginx_configuration(): + logging.info('HUPing nginx') + os.kill(pid, signal.SIGHUP) -if not os.path.exists(target_path): - os.mkdir(target_path) +def get_pid(): + """This function reads the process id from the given file.""" + with open(pid_file, 'r') as file: + return int(file.read()) -pid = get_pid() +def setup_logging(): + logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', level=logging.INFO) -conn = http.client.HTTPConnection("localhost:2375") -try: - conn.request("GET", "/events") -except ConnectionRefusedError: - print('Docker does not expose events on its REST-API. Perhaps it is not running?') - sys.exit(-1) +if __name__ == '__main__': + setup_logging() -response = conn.getresponse() + # prepare required stuff + pid = get_pid() + logging.info('nginx pid: %s', str(pid)) + if not os.path.exists(target_path): + logging.info('creating target path: %s', target_path) + os.mkdir(target_path) + + client = Client(base_url='unix://var/run/docker.sock', version='1.15') + + # handle all running containers existing at startup of this container + container_ids = client.containers(quiet=True) + for container_id in container_ids: + handle_container(container_id['Id']) + reload_nginx_configuration() + + # hook to the events + for line in client.events(): + line_str = line.decode("utf-8") + event = json.loads(line_str) + + container_id = event['id'] + try: + inspect_data = client.inspect_container(container_id) + ip = extract_ip(inspect_data) + except APIError: + ip = '' + +# logging.info(event['status'] + " " + ip) + if ip == '': + logging.info('removing %sproxy_%s', target_path, container_id) + if os.path.exists(target_path + 'proxy_' + container_id): + os.remove(target_path + 'proxy_' + container_id) + else: + handle_container(container_id) + + reload_nginx_configuration() + +# event['timestamp'] = datetime.datetime.fromtimestamp(event['time']).strftime('%Y-%m-%d %H:%M:%S'); +# print_json(event) -events = "" -while not response.closed: - data = response.read(80) - if len(data) != 0: - events += data.decode("UTF-8") - if events.find("{") != -1: - open_pos = events.index("{") - if events.find("}", open_pos) != -1: - close_pos = events.index("}", open_pos)+1 - handle_event(events[open_pos:close_pos]) - events = events[close_pos:] - diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..6967b63 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +nginx -c /etc/nginx.conf +/nginx_proxy.py