Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
Merged

v1.3 #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
FROM ubuntu:trusty
FROM alpine:edge
MAINTAINER Feng Honglin <hfeng@tutum.co>

# Install pip and haproxy
RUN echo 'deb http://ppa.launchpad.net/vbernat/haproxy-1.5/ubuntu trusty main' >> /etc/apt/sources.list && \
echo 'deb-src http://ppa.launchpad.net/vbernat/haproxy-1.5/ubuntu trusty main' >> /etc/apt/sources.list && \
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 505D97A41C61B9CD && \
apt-get update && \
apt-get install -y --no-install-recommends haproxy python-pip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install tini, haproxy, pip and the dockercloud-haproxy python package:
RUN apk --no-cache add \
tini \
haproxy \
py-pip \
&& apk --no-cache add --virtual deps git \
&& pip install --upgrade \
pip \
&& apk del deps \
# Clean up obsolete files:
&& rm -rf \
# Clean up any temporary files:
/tmp/* \
# Clean up the pip cache:
/root/.cache \
# Remove any compiled python files (compile on demand):
`find / -regex '.*\.py[co]'`

COPY reload.sh /reload.sh
COPY . haproxy-src/
RUN cd /haproxy-src/ && \
pip install .
pip install . \
# Clean up obsolete files:
&& rm -rf \
# Clean up any temporary files:
/tmp/* \
# Clean up the pip cache:
/root/.cache \
# Remove any compiled python files (compile on demand):
`find / -regex '.*\.py[co]'`

ENV RSYSLOG_DESTINATION=127.0.0.1 \
MODE=http \
Expand All @@ -28,4 +45,5 @@ ENV RSYSLOG_DESTINATION=127.0.0.1 \
HEALTH_CHECK="check"

EXPOSE 80 443 1936
ENTRYPOINT ["tini", "--"]
CMD ["dockercloud-haproxy"]
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Settings in this part is immutable, you have to redeploy HAProxy service to make
|EXTRA_BIND_SETTINGS|<empty>|comma-separated string(`<port>:<setting>`) of extra settings, and each part will be appended to the related port bind section in the configuration file. To escape comma, use `\,`. Possible value: `443:accept-proxy, 80:name http`|
|EXTRA_DEFAULT_SETTINGS|<empty>|comma-separated string of extra settings, and each part will be appended to DEFAULT section in the configuration file. To escape comma, use `\,`|
|EXTRA_GLOBAL_SETTINGS|<empty>|comma-separated string of extra settings, and each part will be appended to GLOBAL section in the configuration file. To escape comma, use `\,`. Possible value: `tune.ssl.cachesize 20000, tune.ssl.default-dh-param 2048`|
|EXTRA_SSL_CERTS||List of extra certificate names separated by comma, eg. `CERT1, CERT2, CERT3`. You also need to specify each certificate as separate env variables like so: `CERT1="<cert-body1>"`, `CERT2="<cert-body2>"`, `CERT3="<cert-body3>"`|
|EXTRA_SSL_CERTS||list of extra certificate names separated by comma, eg. `CERT1, CERT2, CERT3`. You also need to specify each certificate as separate env variables like so: `CERT1="<cert-body1>"`, `CERT2="<cert-body2>"`, `CERT3="<cert-body3>"`|
|HEALTH_CHECK|check|set health check on each backend route, possible value: "check inter 2000 rise 2 fall 3". See:[HAProxy:check](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.2-check)|
|HTTP_BASIC_AUTH|<empty>|a comma-separated list of credentials(`<user>:<pass>`) for HTTP basic auth, which applies to all the backend routes. To escape comma, use `\,`. *Attention:* DO NOT rely on this for authentication in production|
|MAXCONN|4096|sets the maximum per-process number of concurrent connections.|
Expand All @@ -152,16 +152,19 @@ Settings in this part is immutable, you have to redeploy HAProxy service to make
|STATS_AUTH|stats:stats|username and password required to access the Haproxy stats.|
|STATS_PORT|1936|port for the HAProxy stats section. If this port is published, stats can be accessed at `http://<host-ip>:<STATS_PORT>/`
|TIMEOUT|connect 5000, client 50000, server 50000|comma-separated list of HAProxy `timeout` entries to the `default` section.|
|ADDITIONAL_SERVICES||list of additional services to balance (es: `prj1:web,prj2:sql`). Discovery will be based on `com.docker.compose.[project|service]` container labels. This environment variable only works on compose v2, and the referenced services must be on a network accessible to this containers.|
|CA_CERT_FILE|the path of a ca-cert file. This allows you to mount your ca-cert file directly from a volume instead of from envvar. If set, `CA_CERT` envvar will be ignored. Possible value: `/cacerts/cert0.pem`|
|CERT_FOLDER|the path of certificates. This allows you to mount your certificate files directly from a volume instead of from envvars. If set, `DEFAULT_SSL_CERT` and `SSL_CERT` from linked services are ignored. Possible value:`/certs/`|

### Settings in linked application services###

Settings here can overwrite the settings in HAProxy, which are only applied to the linked services. If run in Docker Cloud, when the service redeploys, joins or leaves HAProxy service, HAProxy service will automatically update itself to apply the changes

|env var|description|
|:-----:|:----------|
|APPSESSION|sticky session option. possible value `JSESSIONID len 52 timeout 3h`. See:[HAProxy:appsession](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-appsession)|
|APPSESSION|sticky session option, possible value `JSESSIONID len 52 timeout 3h`. See:[HAProxy:appsession](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-appsession)|
|BALANCE|load balancing algorithm to use. Possible values include: `roundrobin`, `static-rr`, `source`, `leastconn`. See:[HAProxy:balance](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-balance)|
|COOKIE|sticky session option. possible value `SRV insert indirect nocache`. See:[HAProxy:cookie](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-cookie)|
|COOKIE|sticky session option. Possible value `SRV insert indirect nocache`. See:[HAProxy:cookie](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-cookie)|
|DEFAULT_SSL_CERT|similar to SSL_CERT, but stores the pem file at `/certs/cert0.pem` as the default ssl certs. If multiple `DEFAULT_SSL_CERT` are specified in linked services and HAProxy, the behavior is undefined|
|EXCLUDE_PORTS|comma separated port numbers(e.g. 3306, 3307). By default, HAProxy will add all the ports exposed by the application services to the backend routes. You can exclude the ports that you don't want to be routed, like database port|
|EXTRA_SETTINGS|comma-separated string of extra settings, and each part will be appended to either related backend section or listen session in the configuration file. To escape comma, use `\,`. Possible value: `balance source`|
Expand Down Expand Up @@ -344,6 +347,13 @@ Use the following:
docker run -d -e FORCE_SSL=yes -e SSL_CERT="YOUR_CERT_TEXT" --name webapp dockercloud/hello-world
docker run -d --link webapp:webapp -p 443:443 dockercloud/haproxy

#### I want to load my SSL certificate from volume instead of passing it through environment variable

You can use `CERT_FOLDER` envvar to specify which folder the certificates are mounted in the container, using the following:

docker run -d --name webapp dockercloud/hello-world
docker run -d --link webapp:webapp -e CERT_FOLDER="/certs/" -v $(pwd)/cert1.pem:/certs/cert1.pem -p 443:443 dockercloud/haproxy

#### I want to set up virtual host routing by domain

Virtual hosts can be configured by the proxy reading linked container environment variables (`VIRTUAL_HOST`). Here is an example:
Expand Down Expand Up @@ -426,5 +436,3 @@ In most cases, `dockercloud/haproxy` will configure itself automatically when th

* `docker exec <haproxy_id> /reload.sh`, if you are on the node where dockercloud/haproxy deploys
* `docker-cloud exec <haproxy_uuid> /reload.sh`, if you use docker-cloud cli

Note: when `reload.sh` is invoked, it doesn't necessarily mean that HAProxy will be restarted. In fact, `dockercloud/haproxy` will try to get the current information of the the service and calculate a new configuration. HAProxy will only be restarted when the newly generated configuration differs from the current one.
3 changes: 3 additions & 0 deletions haproxy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def parse_extra_bind_settings(extra_bind_settings):
HAPROXY_CONTAINER_URI = os.getenv("DOCKERCLOUD_CONTAINER_API_URI")
HAPROXY_SERVICE_URI = os.getenv("DOCKERCLOUD_SERVICE_API_URI")
API_AUTH = os.getenv("DOCKERCLOUD_AUTH")
CA_CERT_FILE = os.getenv("CA_CERT_FILE")
CERT_FOLDER = os.getenv("CERT_FOLDER")
ADDITIONAL_SERVICES = os.environ.get("ADDITIONAL_SERVICES")
DEBUG = os.getenv("DEBUG", False)
LINK_MODE = ""

Expand Down
6 changes: 5 additions & 1 deletion haproxy/eventhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ def on_websocket_close():


def on_user_reload(signum, frame):
run_haproxy("User reload")
Haproxy.cls_cfg = None
if config.LINK_MODE == "legacy":
logger.info("User reload is not supported in legacy link mode")
else:
run_haproxy("User reload")


def listen_dockercloud_events():
Expand Down
27 changes: 23 additions & 4 deletions haproxy/haproxycfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Haproxy(object):
cls_cfg = None
cls_process = None
cls_certs = []
cls_ca_certs = []

def __init__(self, link_mode="", msg=""):
logger.info("==========BEGIN==========")
Expand Down Expand Up @@ -80,6 +81,16 @@ def _init_new_links():
logger.info("Docker API error, regressing to legacy links mode: ", e)
return None
links, Haproxy.cls_linked_services = NewLinkHelper.get_new_links(docker, haproxy_container)

try:
if ADDITIONAL_SERVICES:
additional_services = ADDITIONAL_SERVICES.split(",")
NewLinkHelper.get_additional_links(docker, additional_services, haproxy_container,
links, Haproxy.cls_linked_services)
except Exception as e:
logger.info("Error loading ADDITIONAL_SERVICES: %s" % str(e))
return None

logger.info("Linked service: %s", ", ".join(NewLinkHelper.get_service_links_str(links)))
logger.info("Linked container: %s", ", ".join(NewLinkHelper.get_container_links_str(links)))
return links
Expand Down Expand Up @@ -121,8 +132,16 @@ def _update_haproxy(self, cfg):

def _config_ssl(self):
ssl_bind_string = ""
ssl_bind_string += self._config_ssl_certs()
ssl_bind_string += self._config_ssl_cacerts()
if CERT_FOLDER:
ssl_bind_string += "ssl crt %s" % CERT_FOLDER
else:
ssl_bind_string += self._config_ssl_certs()

if CA_CERT_FILE:
ssl_bind_string += " ca-file %s verify required" % CA_CERT_FILE
else:
ssl_bind_string += self._config_ssl_cacerts()

if ssl_bind_string:
self.ssl_bind_string = ssl_bind_string

Expand All @@ -149,8 +168,8 @@ def _config_ssl_cacerts(self):
if DEFAULT_CA_CERT:
cacerts.append(DEFAULT_CA_CERT)
if cacerts:
if set(cacerts) != set(Haproxy.cls_certs):
Haproxy.cls_certs = copy.copy(cacerts)
if set(cacerts) != set(Haproxy.cls_ca_certs):
Haproxy.cls_ca_certs = copy.copy(cacerts)
self.ssl_updated = True
SslHelper.save_certs(CACERT_DIR, cacerts)
logger.info("SSL CA certificates are updated")
Expand Down
41 changes: 40 additions & 1 deletion haproxy/helper/new_link_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,43 @@ def get_new_links(docker, haproxy_container):
return links, ["%s_%s" % (project, service) for service in linked_compose_services]


def get_additional_links(docker, additional_services, haproxy_container, links, linked_services):
networks_data = docker.networks()
haproxy_networks_ids = _find_container_networks_ids(haproxy_container, networks_data)
for _container in docker.containers():
container_id = _container.get("Id", "")
container = docker.inspect_container(container_id)
compose_project = container.get("Config", {}).get("Labels", {}).get("com.docker.compose.project", "")
compose_service = container.get("Config", {}).get("Labels", {}).get("com.docker.compose.service", "")
for _service in additional_services:
terms = _service.strip().split(":")
if len(terms) == 2:
if terms[0].strip() == compose_project and terms[1].strip() == compose_service:
container_networks_ids = _find_container_networks_ids(container, networks_data)
if set(container_networks_ids).intersection(haproxy_networks_ids):
if _service not in linked_services:
linked_services.append(_service)
container_name = container.get("Name").lstrip("/")
container_evvvars = _get_container_envvars(container)
endpoints = _get_container_endpoints(container, container_name)
links[container_id] = {"service_name": _service,
"container_envvars": container_evvvars,
"container_name": container_name,
"endpoints": endpoints,
"compose_service": compose_service,
"compose_project": compose_project}
else:
logger.info("Ignoring container '%s': no shared network with haproxy")


def _find_container_networks_ids(container, networks_data):
ids = []
for network in networks_data:
if container['Id'] in network['Containers'].keys():
ids.append(network['Id'])
return ids


def _calc_links(docker, linked_compose_services, project):
links = {}
for _container in docker.containers():
Expand Down Expand Up @@ -76,7 +113,9 @@ def _get_linked_compose_services(networks, project):

haproxy_links = []
for network in networks.itervalues():
haproxy_links.extend(network.get("Links", []))
network_links = network.get("Links", [])
if network_links:
haproxy_links.extend(network_links)

linked_services = []
for link in haproxy_links:
Expand Down
4 changes: 2 additions & 2 deletions test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ pip install -r test-requirements.txt
nosetests -v --with-coverage --cover-package haproxy

if [ "$(uname -s)" != "Darwin" ]; then
echo ==================== Integration Test =======================
tests/integration_test.sh
echo ==================== Integration Test on Legacy links =======================
tests/test_legacy_links.sh
fi
Loading