From 74910509d35d1ab6c6a730c1f83502f54696a475 Mon Sep 17 00:00:00 2001 From: Jascen Date: Fri, 20 Oct 2017 13:50:30 -0400 Subject: [PATCH] Added "Services" env var for explicit declaration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: I don't really know Python. I hacked my way around, so I apologize if anything just looks plain dumb. - You can now indicate your Service(s) to be load balanced ("SERVICES" Environment Variable on HAProxy) - Note: Services env var only only apply to services within the same project/namespace. - Format is: :,;... - Ex: SERVICES=nginxapp1:443,8080;nginxapp2:80,nginxapp3 - If any services are specified, HAProxy will only pay attention to the defined services in this variable.  - Compose: must Link the service to the HAProxy - If any ports are specified, they will be the only ports used for that service.   - Note: If no ports are specified: - Swarm: it will check the Service for the Env Var "SERVICE_PORTS" (original behavior) - Compose: it will add all exposed ports (original behavior) - If a specified service is not on the same network as HAProxy, it will be ignored (original behavior) --- haproxy/config.py | 1 + haproxy/eventhandler.py | 2 +- haproxy/haproxycfg.py | 5 +-- haproxy/helper/compose_mode_link_helper.py | 42 +++++++++++++++++----- haproxy/helper/swarm_mode_link_helper.py | 40 ++++++++++++++++----- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/haproxy/config.py b/haproxy/config.py index 6986997..aa32eed 100644 --- a/haproxy/config.py +++ b/haproxy/config.py @@ -103,6 +103,7 @@ def parse_additional_backend_settings(envvars): HEALTH_CHECK = os.getenv("HEALTH_CHECK", "check inter 2000 rise 2 fall 3") HTTP_BASIC_AUTH = os.getenv("HTTP_BASIC_AUTH") HTTP_BASIC_AUTH_SECURE = os.getenv("HTTP_BASIC_AUTH_SECURE") +LINKED_SERVICES = os.getenv("SERVICES") MAXCONN = os.getenv("MAXCONN", "4096") MODE = os.getenv("MODE", "http") MONITOR_PORT = os.getenv("MONITOR_PORT") diff --git a/haproxy/eventhandler.py b/haproxy/eventhandler.py index 946d23c..5b5d6b8 100644 --- a/haproxy/eventhandler.py +++ b/haproxy/eventhandler.py @@ -110,7 +110,7 @@ def polling_service_status_swarm_mode(): services = docker.services() tasks = docker.tasks(filters={"desired-state": "running"}) _, linked_tasks = SwarmModeLinkHelper.get_task_links(tasks, services, Haproxy.cls_service_id, - Haproxy.cls_nets) + Haproxy.cls_nets, Haproxy.cls_namespace) if cmp(Haproxy.cls_linked_tasks, linked_tasks) != 0: add_haproxy_run_task("Tasks are updated") except APIError as e: diff --git a/haproxy/haproxycfg.py b/haproxy/haproxycfg.py index efc3af8..540fa4a 100644 --- a/haproxy/haproxycfg.py +++ b/haproxy/haproxycfg.py @@ -58,6 +58,7 @@ class Haproxy(object): cls_ca_certs = [] cls_nets = set() cls_service_id = "" + cls_namespace = "" def __init__(self, running_mode=RunningMode.LegacyMode): logger.info("==========BEGIN==========") @@ -117,10 +118,10 @@ def _init_swarm_mode_links(): logger.info("Docker API error, regressing to legacy links mode: %s" % e) return None haproxy_container_id = os.environ.get("HOSTNAME", "") - Haproxy.cls_service_id, Haproxy.cls_nets = SwarmModeLinkHelper.get_swarm_mode_haproxy_id_nets(docker, + Haproxy.cls_service_id, Haproxy.cls_nets, Haproxy.cls_namespace = SwarmModeLinkHelper.get_swarm_mode_haproxy_id_nets(docker, haproxy_container_id) links, Haproxy.cls_linked_tasks = SwarmModeLinkHelper.get_swarm_mode_links(docker, Haproxy.cls_service_id, - Haproxy.cls_nets) + Haproxy.cls_nets, Haproxy.cls_namespace) logger.info("Linked service: %s", ", ".join(SwarmModeLinkHelper.get_service_links_str(links))) logger.info("Linked container: %s", ", ".join(SwarmModeLinkHelper.get_container_links_str(links))) return links diff --git a/haproxy/helper/compose_mode_link_helper.py b/haproxy/helper/compose_mode_link_helper.py index 1eb27b3..e7f61ba 100644 --- a/haproxy/helper/compose_mode_link_helper.py +++ b/haproxy/helper/compose_mode_link_helper.py @@ -1,4 +1,5 @@ import logging +from haproxy.config import LINKED_SERVICES logger = logging.getLogger("haproxy") @@ -49,12 +50,25 @@ def _calc_links(docker, linked_compose_services, project): compose_labels = container.get("Config", {}).get("Labels", {}) compose_project = compose_labels.get("com.docker.compose.project", "") compose_service = compose_labels.get("com.docker.compose.service", "") - + + linked_service_names = {} + for x in str(LINKED_SERVICES).strip().split(";"): + if not x.strip(): + break + + service_values = x.strip().split(":") + service_name = "%s" % service_values[0] + if len(service_values) == 2: + linked_service_names[service_name] = {y: {} for y in service_values[1].strip().split(",") if y} + else: + linked_service_names[service_name] = {} + if compose_project == project and compose_service in linked_compose_services: service_name = "%s_%s" % (compose_project, compose_service) container_name = container.get("Name").lstrip("/") container_evvvars = get_container_envvars(container) - endpoints = get_container_endpoints(container, container_name) + explicit_endpoints = linked_service_names.get(compose_service, {}) + endpoints = get_container_endpoints(container, container_name, explicit_endpoints) links[container_id] = {"service_name": service_name, "container_envvars": container_evvvars, "container_name": container_name, @@ -64,10 +78,12 @@ def _calc_links(docker, linked_compose_services, project): return links -def get_container_endpoints(container, container_name): +def get_container_endpoints(container, container_name, explicit_endpoints): endpoints = {} - container_endpoints = container.get("Config", {}).get("ExposedPorts", {}) - for k, v in container_endpoints.iteritems(): + if not explicit_endpoints: + explicit_endpoints = container.get("Config", {}).get("ExposedPorts", {}) + + for k, v in explicit_endpoints.iteritems(): if k: terms = k.split("/", 1) port = terms[0] @@ -80,7 +96,6 @@ def get_container_endpoints(container, container_name): endpoints[k] = v return endpoints - def get_container_envvars(container): container_evvvars = [] envvars = container.get("Config", {}).get("Env", []) @@ -105,14 +120,25 @@ def _get_linked_compose_services(networks, project): if network_links: haproxy_links.extend(network_links) + linked_service_names = [] + if LINKED_SERVICES: + for x in str(LINKED_SERVICES).strip().split(";"): + if not x.strip(): + break + service_values = x.strip().split(":") + if service_values: + linked_service_names.append("%s" % (service_values[0])) + linked_services = [] for link in haproxy_links: terms = link.strip().split(":") - service = terms[0].strip() + service = terms[0].strip() if service and service.startswith(prefix): last = service.rfind("_") linked_service = service[prefix_len:last] - if linked_service not in linked_services: + if linked_service_names and linked_service not in linked_service_names: + continue + if linked_service not in linked_services: linked_services.append(linked_service) return linked_services diff --git a/haproxy/helper/swarm_mode_link_helper.py b/haproxy/helper/swarm_mode_link_helper.py index b04b3ab..ef6a3c2 100644 --- a/haproxy/helper/swarm_mode_link_helper.py +++ b/haproxy/helper/swarm_mode_link_helper.py @@ -1,7 +1,7 @@ import logging import compose_mode_link_helper -from haproxy.config import SERVICE_PORTS_ENVVAR_NAME, LABEL_SWARM_MODE_DEACTIVATE +from haproxy.config import SERVICE_PORTS_ENVVAR_NAME, LABEL_SWARM_MODE_DEACTIVATE, LINKED_SERVICES logger = logging.getLogger("haproxy") @@ -13,6 +13,7 @@ def get_swarm_mode_haproxy_id_nets(docker, haproxy_container_short_id): logger.info("Docker API error, regressing to legacy links mode: %s" % e) return "", set() labels = haproxy_container.get("Config", {}).get("Labels", {}) + haproxy_namespace = labels.get("com.docker.stack.namespace", "") haproxy_service_id = labels.get("com.docker.swarm.service.id", "") if not haproxy_service_id: logger.info("Dockercloud haproxy is not running in a service in SwarmMode") @@ -22,18 +23,32 @@ def get_swarm_mode_haproxy_id_nets(docker, haproxy_container_short_id): haproxy_container.get("NetworkSettings", {}).get("Networks", {}).iteritems() if name != "ingress"]) - return haproxy_service_id, haproxy_nets + return haproxy_service_id, haproxy_nets, haproxy_namespace -def get_swarm_mode_links(docker, haproxy_service_id, haproxy_nets): +def get_swarm_mode_links(docker, haproxy_service_id, haproxy_nets, haproxy_namespace): services = docker.services() tasks = docker.tasks(filters={"desired-state": "running"}) - return get_task_links(tasks, services, haproxy_service_id, haproxy_nets) + return get_task_links(tasks, services, haproxy_service_id, haproxy_nets, haproxy_namespace) -def get_task_links(tasks, services, haproxy_service_id, haproxy_nets): +def get_task_links(tasks, services, haproxy_service_id, haproxy_nets, haproxy_namespace): services_id_name = {s.get("ID"): s.get("Spec", {}).get("Name", "") for s in services} services_id_labels = {s.get("ID"): s.get("Spec", {}).get("Labels", {}) for s in services} + + linked_service_names = {} + if LINKED_SERVICES: + for x in str(LINKED_SERVICES).strip().split(";"): + if not x.strip(): + break + + service_values = x.strip().split(":") + service_name = "%s_%s" % (haproxy_namespace, service_values[0]) + if len(service_values) == 2: + linked_service_names[service_name] = service_values[1] + else: + linked_service_names[service_name] = "" + links = {} linked_tasks = {} for task in tasks: @@ -45,8 +60,11 @@ def get_task_links(tasks, services, haproxy_service_id, haproxy_nets): task_slot = "%d" % task.get("Slot", 0) task_service_id = task.get("ServiceID", "") task_service_name = services_id_name.get(task_service_id, "") - task_labels = services_id_labels.get(task_service_id, {}) + if LINKED_SERVICES and task_service_name not in linked_service_names: + continue + + task_labels = services_id_labels.get(task_service_id, {}) if task_labels.get(LABEL_SWARM_MODE_DEACTIVATE, "").lower() == "true": continue @@ -54,9 +72,13 @@ def get_task_links(tasks, services, haproxy_service_id, haproxy_nets): task_envvars = get_task_envvars(task.get("Spec", {}).get("ContainerSpec", {}).get("Env", [])) service_ports = "" - for task_envvar in task_envvars: - if task_envvar["key"] == SERVICE_PORTS_ENVVAR_NAME: - service_ports = task_envvar["value"] + if LINKED_SERVICES and linked_service_names.get(task_service_name, ""): + service_ports = linked_service_names[task_service_name] + else: + for task_envvar in task_envvars: + if task_envvar["key"] == SERVICE_PORTS_ENVVAR_NAME: + service_ports = task_envvar["value"] + task_ports = [x.strip() for x in service_ports.strip().split(",") if x.strip()] task_ips = []