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

Commit ce53aaa

Browse files
committed
add support for swarm mode
1 parent 1394f45 commit ce53aaa

21 files changed

Lines changed: 320 additions & 146 deletions

README.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ The available version can be found here: https://hub.docker.com/r/dockercloud/ha
1717

1818
You can use `dockercloud/haproxy` in three different ways:
1919

20-
- running in Docker Cloud
21-
- running with Docker legacy links
22-
- running with Docker Compose v2 (new links, compatible with Docker Swarm)
20+
- running in Docker Cloud (Cloud Mode)
21+
- running with Docker legacy links (Legacy Mode)
22+
- running with Docker Compose v2 (Compose Mode, compatible with Docker Swarm)
23+
- running with Docker SwarmMode (Swarm Mode)
2324

24-
### Running in Docker Cloud
25+
### Running in Docker Cloud (Cloud Mode)
2526

2627
1. Launch the service you want to load-balance using Docker Cloud.
2728

@@ -52,7 +53,28 @@ That's it - the haproxy container will start querying Docker Cloud's API for an
5253
roles:
5354
- global
5455

55-
### Running with docker legacy links
56+
### Running with Docker SwarmMode (Swarm Mode)
57+
58+
Docker 1.12 supports SwarmMode natively. `dockercloud/haproxy` will auto config itself to load balance all the services running on the same network:
59+
60+
1. Create a new network using `docker create` command
61+
62+
2. Launch `dockercloud/haproxy` service on that network
63+
64+
3. Launch your application services that need to be load balanced on the same network.
65+
66+
* 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
67+
* This feature is still experimental, please let us know if you find any bugs or have any suggestions.
68+
69+
#### example of docker swarm mode support
70+
71+
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
74+
docker service scale app=2
75+
docker service update --env-add VIRTUAL_HOST=web.org app
76+
77+
### Running with Docker legacy links (Legacy Mode)
5678

5779
Legacy link refers to the link created before docker 1.10, and the link created in default bridge network in docker 1.10 or after.
5880

@@ -79,7 +101,7 @@ Legacy link refers to the link created before docker 1.10, and the link created
79101
**Note**: Any link alias sharing the same prefix and followed by "-/_" with an integer is considered to be from the same service. For example: `web-1` and `web-2` belong to service `web`, `app_1` and `app_2` are from service `app`, but `app1` and `web2` are from different services.
80102

81103

82-
### Running with Docker Compose v2(new links)
104+
### Running with Docker Compose v2 (Compose Mode)
83105

84106
Docker Compose 1.6 supports a new format of the compose file. In the new version(v2), the old link that injects environment variables is deprecated.
85107

haproxy/config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
import re
33

44

5+
class RunningMode():
6+
LegacyMode, ComposeMode, SwarmMode, CloudMode = range(4)
7+
8+
59
def parse_extra_bind_settings(extra_bind_settings):
610
bind_dict = {}
711
if extra_bind_settings:
@@ -43,7 +47,7 @@ def parse_extra_frontend_settings(envvars):
4347
EXTRA_FRONTEND_SETTINGS = parse_extra_frontend_settings(os.environ)
4448
EXTRA_GLOBAL_SETTINGS = os.getenv("EXTRA_GLOBAL_SETTINGS")
4549
EXTRA_SSL_CERT = os.getenv("EXTRA_SSL_CERTS")
46-
EXTRA_ROUTE_SETTINGS=os.getenv("EXTRA_ROUTE_SETTINGS", "")
50+
EXTRA_ROUTE_SETTINGS = os.getenv("EXTRA_ROUTE_SETTINGS", "")
4751
HAPROXY_CONTAINER_URI = os.getenv("DOCKERCLOUD_CONTAINER_API_URI")
4852
HAPROXY_SERVICE_URI = os.getenv("DOCKERCLOUD_SERVICE_API_URI")
4953
HEALTH_CHECK = os.getenv("HEALTH_CHECK", "check inter 2000 rise 2 fall 3")
@@ -63,7 +67,7 @@ def parse_extra_frontend_settings(envvars):
6367
NBPROC = int(os.getenv("NBPROC", 1))
6468

6569
# global
66-
LINK_MODE = ""
70+
RUNNING_MODE = None
6771

6872
# const
6973
CERT_DIR = "/certs/"

haproxy/eventhandler.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import json
22
import logging
3-
import time
43
import os
4+
import time
55

66
import dockercloud
77
from compose.cli.docker_client import docker_client
88
from docker.errors import APIError
99

1010
import config
11-
import helper.cloud_link_helper
11+
import helper.cloud_mode_link_helper
1212
from haproxycfg import add_haproxy_run_task, Haproxy
1313
from utils import get_uuid_from_resource_uri
1414

@@ -38,7 +38,7 @@ def on_cloud_event(message):
3838

3939

4040
def on_websocket_open():
41-
helper.cloud_link_helper.LINKED_CONTAINER_CACHE.clear()
41+
helper.cloud_mode_link_helper.LINKED_CONTAINER_CACHE.clear()
4242
add_haproxy_run_task("Websocket open")
4343

4444

@@ -70,7 +70,7 @@ def listen_dockercloud_events():
7070
time.sleep(3600)
7171

7272

73-
def listen_docker_events():
73+
def listen_docker_events_compose_mode():
7474
while True:
7575
try:
7676
try:
@@ -81,7 +81,7 @@ def listen_docker_events():
8181
docker.ping()
8282
for event in docker.events(decode=True):
8383
logger.debug(event)
84-
attr = event.get("Actor", {}).get("Attributes")
84+
attr = event.get("Actor", {}).get("Attributes", {})
8585
compose_project = attr.get("com.docker.compose.project", "")
8686
compose_service = attr.get("com.docker.compose.service", "")
8787
container_name = attr.get("name", "")
@@ -95,3 +95,32 @@ def listen_docker_events():
9595

9696
time.sleep(1)
9797
add_haproxy_run_task("Reconnect docker events")
98+
99+
100+
def listen_docker_events_swarm_mode():
101+
while True:
102+
try:
103+
try:
104+
docker = docker_client()
105+
except:
106+
docker = docker_client(os.environ)
107+
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)
122+
except APIError as e:
123+
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: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88

99
import config
1010
import helper.backend_helper as BackendHelper
11-
import helper.cloud_link_helper as CloudLinkHelper
11+
import helper.cloud_mode_link_helper as CloudModeLinkHelper
12+
import helper.compose_mode_link_helper as ComposeModeLinkHelper
1213
import helper.config_helper as ConfigHelper
1314
import helper.frontend_helper as FrontendHelper
14-
import helper.new_link_helper as NewLinkHelper
1515
import helper.ssl_helper as SslHelper
16+
import helper.swarm_mode_link_helper as SwarmModeLinkHelper
1617
import helper.tcp_helper as TcpHelper
1718
import helper.update_helper as UpdateHelper
1819
from haproxy.config import *
19-
from haproxy.parser import LegacyLinkSpecs, NewLinkSpecs
20+
from haproxy.parser import LegacySpecs, NewSpecs
2021
from utils import fetch_remote_obj, prettify, save_to_file, get_service_attribute, get_bind_string
2122

2223
logger = logging.getLogger("haproxy")
@@ -26,7 +27,7 @@
2627

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

3132

3233
def run_haproxy():
@@ -35,89 +36,118 @@ def run_haproxy():
3536
mode, msg = tasks.get()
3637
time.sleep(delay)
3738
while not tasks.empty():
38-
if mode != "cloud":
39+
if mode != RunningMode.CloudMode:
3940
delay = 0.1
4041
logger.info("=> Task accumulated, skip: %s", msg)
4142
mode, msg = tasks.get()
4243
time.sleep(delay)
4344
continue
4445
logger.info("=> Executing task: %s", msg)
4546

46-
haproxy = Haproxy(config.LINK_MODE)
47+
haproxy = Haproxy(config.RUNNING_MODE)
4748
haproxy.update()
4849

4950

5051
class Haproxy(object):
5152
cls_linked_services = set()
53+
cls_swarm_networks = []
5254
cls_cfg = None
5355
cls_process = None
5456
cls_certs = []
5557
cls_ca_certs = []
5658

57-
def __init__(self, link_mode=""):
59+
def __init__(self, running_mode=RunningMode.LegacyMode):
5860
logger.info("==========BEGIN==========")
59-
60-
self.link_mode = link_mode
61+
self.running_mode = running_mode
6162
self.ssl_bind_string = None
6263
self.ssl_updated = False
6364
self.routes_added = []
6465
self.require_default_route = False
6566
self.specs = None
6667
self.tcp_ports = set()
67-
68-
self.specs = self._initialize(self.link_mode)
68+
self.specs = self._initialize(self.running_mode)
6969

7070
@staticmethod
71-
def _initialize(link_mode):
72-
if link_mode == "cloud":
71+
def _initialize(running_mode):
72+
if running_mode == RunningMode.CloudMode:
7373
links = Haproxy._init_cloud_links()
74-
specs = NewLinkSpecs(links)
75-
elif link_mode == "new":
76-
links = Haproxy._init_new_links()
74+
specs = NewSpecs(links)
75+
elif running_mode == RunningMode.ComposeMode:
76+
links = Haproxy._init_compose_mode_links()
77+
if links is None:
78+
specs = LegacySpecs()
79+
else:
80+
specs = NewSpecs(links)
81+
elif running_mode == RunningMode.SwarmMode:
82+
links = Haproxy._init_swarm_mode_links()
7783
if links is None:
78-
specs = LegacyLinkSpecs()
84+
specs = LegacySpecs()
7985
else:
80-
specs = NewLinkSpecs(links)
86+
specs = NewSpecs(links)
8187
else:
82-
specs = LegacyLinkSpecs()
88+
specs = LegacySpecs()
8389
return specs
8490

8591
@staticmethod
8692
def _init_cloud_links():
8793
haproxy_container = fetch_remote_obj(HAPROXY_CONTAINER_URI)
8894
if haproxy_container:
89-
links = CloudLinkHelper.get_cloud_links(haproxy_container)
90-
Haproxy.cls_linked_services = CloudLinkHelper.get_linked_services(links)
91-
logger.info("Linked service: %s", ", ".join(CloudLinkHelper.get_service_links_str(links)))
92-
logger.info("Linked container: %s", ", ".join(CloudLinkHelper.get_container_links_str(links)))
95+
links = CloudModeLinkHelper.get_cloud_mode_links(haproxy_container)
96+
Haproxy.cls_linked_services = CloudModeLinkHelper.get_linked_services(links)
97+
logger.info("Linked service: %s", ", ".join(CloudModeLinkHelper.get_service_links_str(links)))
98+
logger.info("Linked container: %s", ", ".join(CloudModeLinkHelper.get_container_links_str(links)))
9399
return links
94100
else:
95101
return {}
96102

97103
@staticmethod
98-
def _init_new_links():
104+
def _init_swarm_mode_links():
99105
try:
100106
try:
101107
docker = docker_client()
102108
except:
103109
docker = docker_client(os.environ)
104110

105111
docker.ping()
112+
113+
except Exception as e:
114+
logger.info("Docker API error, regressing to legacy links mode: %s" % e)
115+
return None
116+
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)
119+
logger.info("Linked service: %s", ", ".join(SwarmModeLinkHelper.get_service_links_str(links)))
120+
logger.info("Linked container: %s", ", ".join(SwarmModeLinkHelper.get_container_links_str(links)))
121+
return links
122+
123+
@staticmethod
124+
def _init_compose_mode_links():
125+
try:
126+
try:
127+
docker = docker_client()
128+
except:
129+
docker = docker_client(os.environ)
130+
docker.ping()
106131
container_id = os.environ.get("HOSTNAME", "")
107132
haproxy_container = docker.inspect_container(container_id)
108133
except Exception as e:
109134
logger.info("Docker API error, regressing to legacy links mode: %s" % e)
110135
return None
111-
links, Haproxy.cls_linked_services = NewLinkHelper.get_new_links(docker, haproxy_container)
136+
try:
137+
links, Haproxy.cls_linked_services = ComposeModeLinkHelper.get_compose_mode_links(docker, haproxy_container)
138+
except Exception as e:
139+
logger.info("Docker API error, regressing to legacy links mode: %s" % e)
140+
return None
112141

113142
if ADDITIONAL_SERVICES:
114-
additional_links, additional_services = NewLinkHelper.get_additional_links(docker, ADDITIONAL_SERVICES)
143+
additional_links, additional_services = ComposeModeLinkHelper.get_additional_links(docker,
144+
ADDITIONAL_SERVICES)
115145
if additional_links and additional_services:
116146
links.update(additional_links)
117147
Haproxy.cls_linked_services.update(additional_services)
118148

119-
logger.info("Linked service: %s", ", ".join(NewLinkHelper.get_service_links_str(links)))
120-
logger.info("Linked container: %s", ", ".join(NewLinkHelper.get_container_links_str(links)))
149+
logger.info("Linked service: %s", ", ".join(ComposeModeLinkHelper.get_service_links_str(links)))
150+
logger.info("Linked container: %s", ", ".join(ComposeModeLinkHelper.get_container_links_str(links)))
121151
return links
122152

123153
def update(self):
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
pool = Pool(size=5)
99

1010

11-
def get_cloud_links(haproxy_container):
11+
def get_cloud_mode_links(haproxy_container):
1212
links = _init_links(haproxy_container.linked_to_container)
1313
new_added_container_uris = _get_new_added_link_uri(LINKED_CONTAINER_CACHE, links)
1414
new_added_containers = _get_container_object_from_uri(new_added_container_uris)
@@ -69,7 +69,7 @@ def _get_linked_containers(cache, container_links):
6969
def get_linked_services(haproxy_links):
7070
linked_services = set()
7171
for link in haproxy_links.itervalues():
72-
linked_services.add(link["service_uri"])
72+
linked_services.add(link["service_uri"])
7373
return set(linked_services)
7474

7575

0 commit comments

Comments
 (0)