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
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
2. adapted nginx configuration
3. docker daemon must be configured to listen to TCP port
4. existing /tmp/nginx to store proxy files
`docker run --name auto_proxy -d -v /var/run/docker.sock:/var/run/docker.sock -p 80:80 docker_nginx_auto_proxy`
The socket must be handed in so the container can get the events.
## 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
import os
import sys
import http.client
from docker import Client
from docker.errors import APIError
from string import Template
import json
import signal
from string import Template
import os
import logging
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 +34,119 @@ 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)
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:]
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 = ''
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