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

Commit dc0c9ca

Browse files
committed
swarm_mode_by_polling_manager
1 parent ce53aaa commit dc0c9ca

8 files changed

Lines changed: 1067 additions & 78 deletions

File tree

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,26 @@ That's it - the haproxy container will start querying Docker Cloud's API for an
5757

5858
Docker 1.12 supports SwarmMode natively. `dockercloud/haproxy` will auto config itself to load balance all the services running on the same network:
5959

60-
1. Create a new network using `docker create` command
60+
1. Create a new network using `docker network create -d overlay <name>` command
6161

62-
2. Launch `dockercloud/haproxy` service on that network
62+
2. Launch `dockercloud/haproxy` service on that network on manager nodes.
6363

6464
3. Launch your application services that need to be load balanced on the same network.
6565

66+
**Note**
67+
- You **HAVE TO** set the environment variable `SERVICE_PORTS=<port1>, <port2>` in your application service, which are the ports you would like to expose.
68+
- For `dockercloud/haproxy` service:
69+
If you mount `/var/run/docker.sock`, it can only be run on swarm manager nodes.
70+
If you want the haproxy service to run on worker nodes, you need to setup DOCKER_HOST envvar that points to the manager address.
71+
6672
* If your application services need to access other services(database, for example), you can attach your application services to two different network, one is for database and the other one for the proxy
6773
* This feature is still experimental, please let us know if you find any bugs or have any suggestions.
6874

6975
#### example of docker swarm mode support
7076

7177
docker network create -d overlay proxy
72-
docker service create --name haproxy --network proxy --mount target=/var/run/docker.sock,source=/var/run/docker.sock,type=bind -p 80:80 dockercloud/haproxy
73-
docker service create --name app --network proxy dockercloud/hello-world
78+
docker service create --name haproxy --network proxy --mount target=/var/run/docker.sock,source=/var/run/docker.sock,type=bind -p 80:80 --constraint "node.role == manager" dockercloud/haproxy
79+
docker service create -e SERVICE_PORTS="80" --name app --network proxy --constraint "node.role != manager" dockercloud/hello-world
7480
docker service scale app=2
7581
docker service update --env-add VIRTUAL_HOST=web.org app
7682

@@ -243,6 +249,7 @@ Settings here can overwrite the settings in HAProxy, which are only applied to t
243249
|TCP_PORTS|comma separated ports(e.g. 9000, 9001, 2222/ssl). The port listed in `TCP_PORTS` will be load-balanced in TCP mode. Port ends with `/ssl` indicates that port needs SSL termination.
244250
|VIRTUAL_HOST_WEIGHT|an integer of the weight of an virtual host, used together with `VIRTUAL_HOST`, default:0. It affects the order of acl rules of the virtual hosts. The higher weight one virtual host has, the more priority that acl rules applies.|
245251
|VIRTUAL_HOST|specify virtual host and virtual path. Format: `[scheme://]domain[:port][/path], ...`. wildcard `*` can be used in `domain` and `path` part|
252+
|SERVICE_PORTS|comma separated ports(e.g. 80, 8080), which are the ports you would like to expose in your application service. This envvar is swarm mode only, and it is **MUST** be set in swarm mode|
246253

247254
Check [the HAProxy configuration manual](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html) for more information on the above.
248255

haproxy/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def parse_extra_frontend_settings(envvars):
6565
STATS_PORT = os.getenv("STATS_PORT", "1936")
6666
TIMEOUT = os.getenv("TIMEOUT", "connect 5000, client 50000, server 50000")
6767
NBPROC = int(os.getenv("NBPROC", 1))
68+
SWARM_MODE_POLLING_INTERVAL = int(os.getenv("SWARM_MODE_POLLING_INTERVAL", 5))
6869

6970
# global
7071
RUNNING_MODE = None
@@ -76,6 +77,7 @@ def parse_extra_frontend_settings(envvars):
7677
HAPROXY_RUN_COMMAND = ['/usr/sbin/haproxy', '-f', HAPROXY_CONFIG_FILE, '-db', '-q']
7778
API_RETRY = 10 # seconds
7879
PID_FILE = "/tmp/dockercloud-haproxy.pid"
80+
SERVICE_PORTS_ENVVAR_NAME = "SERVICE_PORTS"
7981

8082
# regular expressions
8183
SERVICE_NAME_MATCH = re.compile(r"(.+)_\d+$")

haproxy/eventhandler.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,30 +97,26 @@ def listen_docker_events_compose_mode():
9797
add_haproxy_run_task("Reconnect docker events")
9898

9999

100-
def listen_docker_events_swarm_mode():
100+
def polling_service_status_swarm_mode():
101101
while True:
102+
time.sleep(config.SWARM_MODE_POLLING_INTERVAL)
102103
try:
103104
try:
104105
docker = docker_client()
105106
except:
106107
docker = docker_client(os.environ)
107108

108-
docker.ping()
109-
for event in docker.events(decode=True):
110-
logger.debug(event)
111-
action = event.get("Action", "")
112-
type = event.get("Type", "")
113-
container = event.get("Actor", {}).get("Attributes", {}).get("container", "")
114-
network = event.get("Actor", {}).get("Attributes", {}).get("name")
115-
id = event.get("Actor", {}).get("ID", "")
116-
if type == "network" and action in ["connect", "disconnect"] and id in Haproxy.cls_swarm_networks:
117-
if action == "connect":
118-
msg = "Docker event: container %s %s to network %s" % (container, action, network)
119-
else:
120-
msg = "Docker event: container %s %s from network %s" % (container, action, network)
121-
add_haproxy_run_task(msg)
109+
tasks = docker.tasks(filters={"desired-state": "running"})
110+
linked_tasks = set()
111+
for task in tasks:
112+
task_nets = [network.get("Network", {}).get("ID", "") for network in
113+
task.get("NetworksAttachments", [])]
114+
task_service_id = task.get("ServiceID", "")
115+
if task_service_id != Haproxy.cls_service_id and Haproxy.cls_nets.intersection(set(task_nets)):
116+
task_id = task.get("ID", "")
117+
linked_tasks.add(task_id)
118+
119+
if Haproxy.cls_linked_tasks != linked_tasks:
120+
add_haproxy_run_task("Tasks are updated")
122121
except APIError as e:
123122
logger.info("Docker API error: %s" % e)
124-
125-
time.sleep(1)
126-
add_haproxy_run_task("Reconnect docker events")

haproxy/haproxycfg.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727

2828
def add_haproxy_run_task(msg=None):
29-
logger.info("=> Add task: %s", msg)
29+
if msg:
30+
logger.info("=> Add task: %s", msg)
3031
gevent.spawn(tasks.put, (config.RUNNING_MODE, msg))
3132

3233

@@ -38,7 +39,8 @@ def run_haproxy():
3839
while not tasks.empty():
3940
if mode != RunningMode.CloudMode:
4041
delay = 0.1
41-
logger.info("=> Task accumulated, skip: %s", msg)
42+
if msg:
43+
logger.info("=> Task accumulated, skip: %s", msg)
4244
mode, msg = tasks.get()
4345
time.sleep(delay)
4446
continue
@@ -50,11 +52,13 @@ def run_haproxy():
5052

5153
class Haproxy(object):
5254
cls_linked_services = set()
53-
cls_swarm_networks = []
55+
cls_linked_tasks = set()
5456
cls_cfg = None
5557
cls_process = None
5658
cls_certs = []
5759
cls_ca_certs = []
60+
cls_nets = set()
61+
cls_service_id = ""
5862

5963
def __init__(self, running_mode=RunningMode.LegacyMode):
6064
logger.info("==========BEGIN==========")
@@ -114,8 +118,10 @@ def _init_swarm_mode_links():
114118
logger.info("Docker API error, regressing to legacy links mode: %s" % e)
115119
return None
116120
haproxy_container_id = os.environ.get("HOSTNAME", "")
117-
links, Haproxy.cls_linked_services, Haproxy.cls_swarm_networks = SwarmModeLinkHelper.get_swarm_mode_links(
118-
docker, haproxy_container_id)
121+
Haproxy.cls_service_id, Haproxy.cls_nets = SwarmModeLinkHelper.get_swarm_mode_haproxy_id_nets(docker,
122+
haproxy_container_id)
123+
links, Haproxy.cls_linked_tasks = SwarmModeLinkHelper.get_swarm_mode_links(docker, Haproxy.cls_service_id,
124+
Haproxy.cls_nets)
119125
logger.info("Linked service: %s", ", ".join(SwarmModeLinkHelper.get_service_links_str(links)))
120126
logger.info("Linked container: %s", ", ".join(SwarmModeLinkHelper.get_container_links_str(links)))
121127
return links

haproxy/helper/swarm_mode_link_helper.py

Lines changed: 66 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,88 @@
11
import logging
2+
23
import compose_mode_link_helper
4+
from haproxy.config import SERVICE_PORTS_ENVVAR_NAME
35

46
logger = logging.getLogger("haproxy")
57

68

7-
def get_swarm_mode_links(docker, haproxy_container_short_id):
9+
def get_swarm_mode_haproxy_id_nets(docker, haproxy_container_short_id):
810
try:
911
haproxy_container = docker.inspect_container(haproxy_container_short_id)
1012
except Exception as e:
1113
logger.info("Docker API error, regressing to legacy links mode: %s" % e)
12-
return {}, set()
14+
return "", set()
1315
labels = haproxy_container.get("Config", {}).get("Labels", {})
14-
service_id = labels.get("com.docker.swarm.node.id", "")
15-
service_name = labels.get("com.docker.swarm.service.name", "")
16-
task_id = labels.get("com.docker.swarm.task.id", "")
17-
task_name = labels.get("com.docker.swarm.task.name", "")
18-
if not (service_id and service_name and task_id and task_name):
16+
haproxy_service_id = labels.get("com.docker.swarm.service.id", "")
17+
if not haproxy_service_id:
1918
logger.info("Dockercloud haproxy is not running in a service in SwarmMode")
20-
return {}, set()
19+
return "", set()
2120

22-
nets = haproxy_container.get("NetworkSettings", {}).get("Networks", {})
23-
net_ids = [network.get("NetworkID", "") for network in nets.values()]
21+
haproxy_nets = set([network.get("NetworkID", "") for network in
22+
haproxy_container.get("NetworkSettings", {}).get("Networks", {}).values()])
2423

25-
linked_containers_ids = []
26-
for net_id in net_ids:
27-
network_inspect = docker.inspect_network(net_id)
28-
linked_containers_ids.extend(network_inspect.get("Containers", {}).keys())
24+
return haproxy_service_id, haproxy_nets
2925

30-
linked_containers = {}
31-
for linked_containers_id in linked_containers_ids:
32-
try:
33-
linked_container = docker.inspect_container(linked_containers_id)
34-
except Exception as e:
35-
logger.info("Docker API error: %s" % e)
36-
continue
37-
if linked_container.get("Config", {}).get("Labels", {}).get("com.docker.swarm.service.name",
38-
"") != service_name:
39-
linked_containers[linked_containers_id] = linked_container
4026

41-
links, services = _calc_links(linked_containers)
42-
return links, services, net_ids
27+
def get_swarm_mode_links(docker, haproxy_service_id, haproxy_nets):
28+
services = docker.services()
29+
tasks = docker.tasks(filters={"desired-state": "running"})
30+
links, linked_tasks = get_task_links(tasks, services, haproxy_service_id, haproxy_nets)
31+
return links, linked_tasks
4332

4433

45-
def _calc_links(containers):
34+
def get_task_links(tasks, services, haproxy_service_id, haproxy_nets):
35+
services_id_name = {s.get("ID"): s.get("Spec", {}).get("Name", "") for s in services}
4636
links = {}
47-
services = set()
48-
for container_id, container in containers.items():
49-
container_name = container.get("Name").lstrip("/")
50-
service_name = container.get("Config", {}).get("Labels", {}).get("com.docker.swarm.service.name", "")
51-
container_evvvars = get_container_envvars(container)
52-
endpoints = get_container_endpoints(container, container_name)
53-
links[container_id] = {"service_name": service_name,
54-
"container_envvars": container_evvvars,
55-
"container_name": container_name,
56-
"endpoints": endpoints,
57-
}
58-
services.add(service_name)
59-
return links, services
60-
61-
62-
def get_container_endpoints(container, container_name):
63-
return compose_mode_link_helper.get_container_endpoints(container, container_name)
64-
65-
66-
def get_container_envvars(container):
67-
return compose_mode_link_helper.get_container_envvars(container)
37+
linked_tasks = set()
38+
for task in tasks:
39+
task_nets = [network.get("Network", {}).get("ID", "") for network in task.get("NetworksAttachments", [])]
40+
task_service_id = task.get("ServiceID", "")
41+
task_nets_attached = haproxy_nets.intersection(set(task_nets))
42+
if task_service_id != haproxy_service_id and task_nets_attached:
43+
task_id = task.get("ID", "")
44+
task_slot = "%d" % task.get("Slot", 0)
45+
task_service_id = task.get("ServiceID", "")
46+
task_service_name = services_id_name.get(task_service_id, "")
47+
container_name = ".".join([task_service_name, task_slot, task_id])
48+
task_envvars = get_task_envvars(task.get("Spec", {}).get("ContainerSpec", {}).get("Env", []))
49+
50+
service_ports = ""
51+
for task_envvar in task_envvars:
52+
if task_envvar["key"] == SERVICE_PORTS_ENVVAR_NAME:
53+
service_ports = task_envvar["value"]
54+
task_ports = [x.strip() for x in service_ports.strip().split(",") if x.strip()]
55+
56+
task_ips = []
57+
for network_attachment in task.get("NetworksAttachments", []):
58+
if network_attachment.get("Network", {}).get("ID", "") in task_nets_attached:
59+
task_ips = network_attachment.get("Addresses", [])
60+
break
61+
62+
if task_ips:
63+
task_ip = task_ips[0].split("/")[0]
64+
else:
65+
task_ip = container_name
66+
67+
task_endpoints = {"%s/tcp" % port: "tcp://%s:%s" % (task_ip, port) for port in task_ports}
68+
69+
links[task_id] = {"endpoints": task_endpoints, "container_name": container_name,
70+
"service_name": task_service_name, "container_envvars": task_envvars}
71+
linked_tasks.add(task_id)
72+
return links, linked_tasks
73+
74+
75+
def get_task_envvars(envvars):
76+
new_envvars = []
77+
for _envvar in envvars:
78+
terms = _envvar.split("=", 1)
79+
envvar = {"key": terms[0]}
80+
if len(terms) == 2:
81+
envvar["value"] = terms[1]
82+
else:
83+
envvar["value"] = ""
84+
new_envvars.append(envvar)
85+
return new_envvars
6886

6987

7088
def get_service_links_str(links):

haproxy/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import config
1616
from config import DEBUG, PID_FILE, HAPROXY_CONTAINER_URI, HAPROXY_SERVICE_URI, API_AUTH
1717
from eventhandler import on_user_reload, listen_docker_events_compose_mode, listen_dockercloud_events, \
18-
listen_docker_events_swarm_mode
18+
polling_service_status_swarm_mode
1919
from haproxy import __version__
2020
import haproxycfg
2121
from haproxycfg import add_haproxy_run_task, run_haproxy, Haproxy
@@ -58,7 +58,7 @@ def main():
5858
gevent.spawn(listen_docker_events_compose_mode)
5959
elif config.RUNNING_MODE == RunningMode.SwarmMode:
6060
add_haproxy_run_task("Initial start - Swarm Mode")
61-
gevent.spawn(listen_docker_events_swarm_mode)
61+
gevent.spawn(polling_service_status_swarm_mode)
6262
elif config.RUNNING_MODE == RunningMode.LegacyMode:
6363
add_haproxy_run_task("Initial start - Legacy Mode")
6464

0 commit comments

Comments
 (0)