Merge branch 'master' of code.nerd2nerd.org:n0ob/docker_nginx_auto_proxy

This commit is contained in:
Joachim Lusiardi 2015-02-01 08:40:01 +01:00
commit b37c8ab7de
5 changed files with 161 additions and 101 deletions

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM debian8_python3
MAINTAINER Joachim Lusiardi
RUN apt-get update; \
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
EXPOSE 80
ENTRYPOINT /start.sh

View File

@ -1,8 +1,22 @@
# Automated Nginx reverse Proxy for Docker Webservices # Automated Nginx reverse Proxy for Docker Webservices
This image attaches to the docker event queue and creates/removes proxy settings in the contained nginx.
## Requirements ## Starting the container
The container is started as:
1. installed nginx `docker run --name auto_proxy -d -v /var/run/docker.sock:/var/run/docker.sock -p 80:80 docker_nginx_auto_proxy`
2. adapted nginx configuration
3. docker daemon must be configured to listen to TCP port The socket must be handed in so the container can get the events.
4. existing /tmp/nginx to store proxy files
## How it works
Containers that should be proxied neet meta information in the environment variable *PROXY_DATA* available.
This variable must be of the following format:
`PROXY_DATA=server_name:test.com,port:80`
The following options are possible:
* **server_name**(required) the name of the virtual host
* **port**(optional, defaults to 80) the port on the target container
* **ip**(optional, defaults to listen on all IPs) the IP on which the proxy should listen.
* **location**(optional) if the proxied web application is not running on the /-path

18
nginx.conf Normal file
View File

@ -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;

View File

@ -1,27 +1,30 @@
#!/usr/bin/python3.4 #!/usr/bin/python3.4
import os from docker import Client
import sys from docker.errors import APIError
import http.client from string import Template
import json import json
import signal import signal
from string import Template import os
import logging
target_path="/tmp/nginx" target_path="/tmp/nginx/"
pid_file="/run/nginx.pid" pid_file="/var/run/nginx.pid"
non_location_template = """server { non_location_template = """# proxy for container '$containername'
server {
listen $listen; listen $listen;
server_name $name; server_name $name;
location / { location / {
proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$remote_addr; proxy_set_header X-Forwarded-For $$remote_addr;
proxy_set_header Host $$host; 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; listen $listen;
server_name $name; server_name $name;
location / { location / {
@ -31,116 +34,119 @@ location_template="""server {
proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$remote_addr; proxy_set_header X-Forwarded-For $$remote_addr;
proxy_set_header Host $$host; proxy_set_header Host $$host;
proxy_pass http://$ip:$port/; proxy_pass http://$ip:$port/;
} }
} }
""" """
def get_pid(): def print_json(data):
with open(pid_file, 'r') as file: """Prints the given value in JSON to stdout. Use this for debugging only"""
return int(file.read()) print(json.dumps(data, sort_keys=True, indent=4))
def get_container_data(): def analyse_env_vars(inspect_data):
conn = http.client.HTTPConnection("localhost:2375") """Extracts the environment variables from the given result of an 'inspect
conn.request("GET", "/containers/json") container' call."""
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):
env_data = {} env_data = {}
for env_var in data: for env_var in inspect_data['Config']['Env']:
t = env_var.split("=") t = env_var.split("=")
env_data[t[0]] = t[1] env_data[t[0]] = t[1]
return env_data return env_data
def analyse_proxy_data(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 = {} proxy_data = {}
for proxy_var in data.split(','): for proxy_var in data['PROXY_DATA'].split(','):
t = proxy_var.split(":",1) t = proxy_var.split(":",1)
proxy_data[t[0]] = t[1] proxy_data[t[0]] = t[1]
return proxy_data return proxy_data
def handle_event(event): def extract_ip(inspect_data):
files = os.listdir(target_path) """extracts the container's ip from the given inspect data"""
print('deleting files') return inspect_data['NetworkSettings']['IPAddress']
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'])
server_name = '' def extract_name(inspect_data):
if 'server_name' in proxy_data: """extracts the container's name from the given inspect data"""
server_name = proxy_data['server_name'] return inspect_data['Name']
port = 0
if 'port' in proxy_data:
port = proxy_data['port']
location = '' def get_if_available(dict, key, defValue):
if 'location' in proxy_data: if key in dict:
location = proxy_data['location'] return dict[key]
else:
return defValue
listen ='*:80' def handle_container(id):
if 'ip' in proxy_data: """This function take a container's id and collects all data required
listen = proxy_data['ip']+':80' 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) def reload_nginx_configuration():
with open('/tmp/nginx/proxy_'+container_id, 'w') as file: logging.info('HUPing nginx')
if location == '': os.kill(pid, signal.SIGHUP)
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)
if not os.path.exists(target_path): def get_pid():
os.mkdir(target_path) """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") if __name__ == '__main__':
try: setup_logging()
conn.request("GET", "/events")
except ConnectionRefusedError:
print('Docker does not expose events on its REST-API. Perhaps it is not running?')
sys.exit(-1)
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)
events = "" client = Client(base_url='unix://var/run/docker.sock', version='1.15')
while not response.closed:
data = response.read(80) # handle all running containers existing at startup of this container
if len(data) != 0: container_ids = client.containers(quiet=True)
events += data.decode("UTF-8") for container_id in container_ids:
if events.find("{") != -1: handle_container(container_id['Id'])
open_pos = events.index("{") reload_nginx_configuration()
if events.find("}", open_pos) != -1:
close_pos = events.index("}", open_pos)+1 # hook to the events
handle_event(events[open_pos:close_pos]) for line in client.events():
events = events[close_pos:] 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 = ''
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()

4
start.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
nginx -c /etc/nginx.conf
/nginx_proxy.py