diff --git a/docs/docker.md b/docs/docker.md index e8d85e4..f9248d5 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -4,8 +4,12 @@ This method will use a docker standalone installation to discover the containers and configure the HAProxy. -The only requirement is that containers and EasyHAProxy must be in the same docker network. -If not, EasyHAProxy will connect the container with the EasyHAProxy network. +You cannot mix docker containers with swarm containers. + +The only request is that containers and EasyHAProxy must be in the same docker network. +If you don't add to your services the same network EasyHAProxy is connected to, EasyHAProxy will attach it network to your container. + +Also, it is highly recommended you create a network external to EasyHAProxy. e.g.: @@ -47,13 +51,12 @@ Once the container is running, EasyHAProxy will detect automatically and start t You don't need to expose any port in your container. -Please follow the [docker label configuration](container-labels.md) to see other configurations available. +Please follow the [docker label configuration](container-labels.md) to see other configurations available. ## Setup the EasyHAProxy container You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables, please follow the [docker container environment](docker-environment.md) - ## Setup certificates with Letsencrypt Follow [this link](letsencrypt.md) diff --git a/docs/kubernetes.md b/docs/kubernetes.md index 358be77..cf2d47f 100644 --- a/docs/kubernetes.md +++ b/docs/kubernetes.md @@ -71,7 +71,7 @@ Once the container is running, EasyHAProxy will detect automatically and start t You don't need to expose any port in your container. -Caveats: +Notes: - At this point, the implementation doesn't support all ingress properties or wildcard domains. - The ingress will publish the ports 80 and 443, plus 1936 if stats are enabled. diff --git a/docs/swarm.md b/docs/swarm.md index fc671d1..9ab29c4 100644 --- a/docs/swarm.md +++ b/docs/swarm.md @@ -5,13 +5,17 @@ This method will use a docker swarm installation to discover the containers and configure the HAProxy. The advantage of this method is that you can discover containers in other nodes from the cluster. -The only requirement is that containers and EasyHAProxy must be in the same docker swarm network. -If not, EasyHAProxy will connect the service with the EasyHAProxy service network. +You cannot mix docker containers with swarm containers. + +The only request is that containers and EasyHAProxy must be in the same docker swarm network. +If you don't add to your services the same network EasyHAProxy is connected to, EasyHAProxy will attach it network to your container. + +Also, it is highly recommended you create a network external to EasyHAProxy. e.g.: ```bash -docker network create -d overlay easyhaproxy +docker network create -d overlay --attachable easyhaproxy ``` And then deploy the EasyHAProxy stack: @@ -79,13 +83,11 @@ networks: external: true ``` -Note: The services to be discovered **don't need** to be in the same network as EasyHAProxy is. - Once the container is running, EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container. You don't need to expose any port in your container. -Please follow the [docker label configuration](container-labels.md) to see other configurations available. +Please follow the [docker label configuration](container-labels.md) to see other configurations available. ## Setup the EasyHAProxy container diff --git a/docs/volumes.md b/docs/volumes.md index 50f6065..4413095 100644 --- a/docs/volumes.md +++ b/docs/volumes.md @@ -2,13 +2,13 @@ You can map the following volumes: -| Volume | Description | -|------|------| -| /etc/haproxy/static/ | The folder that will contain the [config.yml](static.md) file for static configuration | -| /certs/haproxy/ | The folder that will contain the certificates (`PEM`) for the [SSL](ssl.md) | -| /certs/letsencrypt/ | The folder that will contain the certificates (`PEM`) for the SSL. Use this volume to cache the [letsencrypt](letsencrypt.md) certificate and avoid re-issue certificates between restarts. | -| /etc/haproxy/conf.d/ | The folder that will contain the [custom configuration](other.md) files. | -| /etc/haproxy/errors-custom/ | The folder that will contain the [custom error](other.md) files. | +| Volume | Description | +|-----------------------------|----------------------------------------------------------------------------------------| +| /etc/haproxy/static/ | The folder that will contain the [config.yml](static.md) file for static configuration | +| /certs/haproxy/ | The folder that will contain the certificates (`PEM`) for the [SSL](ssl.md) | +| /certs/letsencrypt/ | The folder that will contain the certificates (`PEM`) for the SSL. Use this volume to cache the [letsencrypt](letsencrypt.md) certificate and avoid re-issue certificates between restarts. | +| /etc/haproxy/conf.d/ | The folder that will contain the [custom configuration](other.md) files. | +| /etc/haproxy/errors-custom/ | The folder that will contain the [custom error](other.md) html files. | ---- -[Open source ByJG](http://opensource.byjg.com) \ No newline at end of file +[Open source ByJG](http://opensource.byjg.com) diff --git a/examples/swarm/certs/host1.local.pem b/examples/swarm/certs/host1.local.pem new file mode 100644 index 0000000..0d7eb39 --- /dev/null +++ b/examples/swarm/certs/host1.local.pem @@ -0,0 +1,82 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgIURi+w1ZVgeedTlNIAwqQBMJv6dXswDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLaG9zdDEubG9jYWwwHhcNMjEwODEwMTg0OTA2WhcNMzEw +ODA4MTg0OTA2WjAWMRQwEgYDVQQDDAtob3N0MS5sb2NhbDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAMBDAhLAygJuaW6w6ffigzTAAGXpmEz0tIxn1k4Z +x5wN5rpv/qu0QMYz+Av2u1eOKEKZeaFRVpT0r93dX7IvbEZHt25GPiBvlLGqhjKR +PnSk/7U8XmsnttUAV7rVEK1UrdFw8/IwriQC+dhr0mnYfSDMkvBoMFpdhVNTrbAZ +1TB6rQjE7Ar0Mt8my96XJmwrcjK2Tj+E2rgPIUz1e5cekFYIDSBatmw+3+vr+T5x +FNFkJ2o30W5o8ZflCJJzrVaihqQics6ZKDgpf7iqXMFiwWIlhdQpGvx5Gf/KFTK9 +UaOnRZz/X+2CebAFaTHR3k/PYppWTgBBBuRvlpCw+wdnkmteC0SQRF91QWVr7ejo +7KaOlGI5VtvMUsWvTeAZmpaymIaATETuOJaY0JU11OmLeD9DOj5E2SQ7qIX/pFcp +xpzG5j4c+MlgvxP2VAkNTeAXCaYiPBQH5ZZg0HE2WnB1KhLRFlHd4iHQD2GJ5yN/ +6fCFBfZfKSeK8JauwxgWkra53OcDq/mKd+DA/dK+/ruG7tqwVgIa04HOplzM7LYR +GB0Irs9+lr5/PJbQZmU073Mdn6cXAg3p+6wvwFlDkS5v13gBDYNHtF62bc551edF +Z6kGzJ7wmGRo84aBP7MuRZeReLOrSS67a1wLdzZsMnP1TJ7x9Lfr9MKl2uDnQdnY +ex8DAgMBAAGjUzBRMB0GA1UdDgQWBBSQ/mtZd6h8en9YQVH6HO1PlWWiqzAfBgNV +HSMEGDAWgBSQ/mtZd6h8en9YQVH6HO1PlWWiqzAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQChQYNuah3+mTpIBDYxGrjTJNuOTIMaWzMyi1tkf+L0 +sEGwpbmAO2mWWQYF7WVLsi98PULh3adjt2jiud9VlaaC6gnwn5Zo1+Pilo9sNLLW +6ij0+rN4kwIm/pNqi+jDuu2cvAuHIwZWeh8bEe/5UCxo4ihmWFQN8eJ6TUKCphRC +6Eor/SSZZBQHgPl0BchzHOkwu7R3LCndRqxjhAoVb9yQOV+ZsmTeJXulwNzJ1uLt +T8OIgIiDpmBo7HSN2H0k3chx00AsjUyJ9mmAWPejFe/KXLRPcVZR17jhzgfIBEzs +M5WtWFm1aHDjVv6M6iteVm61E9T+k/M11ru1e2YwsxTDvb6x04mcrNu9soqddBbr +VfpluuoQ/hEAbXtFNPoTySpz0cwOwcHCowVOLmdKgvImszZiMyHHG8VGGmPh88n7 +wVxb0gV0P4RMrcMLdeTdn55YQr1CqBr34eB6ol6AsbTm3VzBHRVmFNksl1o5JB5t +tXLgF/G8/rzJ/4m1PaVuxrB7DxUmIk8EPbSIVkvZvd7LBzKwQ6IfVaucewHfEajQ +VIiexSMiFc7lw3KnxjOHZjf6FM9VYg3No++GdC99s7LkIuJwAMLNqTQ7Hvhn7YvP +4FlSIgc6xj0YkGZEQlb5o/5nauEqQU0ABgw6jtI4NxrNLT6cp7CO4M0xIDEg/3YD +aA== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +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 +YkU14WzQKleFkiBJI9lVvnfgGnOlgg0= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/examples/swarm/host2.local.pem b/examples/swarm/certs/host2.local.pem similarity index 100% rename from examples/swarm/host2.local.pem rename to examples/swarm/certs/host2.local.pem diff --git a/examples/swarm/easyhaproxy.yml b/examples/swarm/easyhaproxy.yml new file mode 100644 index 0000000..3d088ef --- /dev/null +++ b/examples/swarm/easyhaproxy.yml @@ -0,0 +1,40 @@ +# To Install +# docker network create --driver overlay --attachable easyhaproxy +# docker stack deploy -c easyhaproxy.yml easyhaproxy + + +version: "3" + +services: + haproxy: + image: byjg/easy-haproxy:4.3.1-rc2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./certs:/certs/haproxy + - certs_letsencrypt:/certs/letsencrypt + deploy: + replicas: 1 + environment: + EASYHAPROXY_DISCOVER: swarm + EASYHAPROXY_SSL_MODE: "loose" + EASYHAPROXY_LETSENCRYPT_EMAIL: changeme@example.org + HAPROXY_CUSTOMERRORS: "true" + HAPROXY_USERNAME: admin + HAPROXY_PASSWORD: password + HAPROXY_STATS_PORT: 1936 + ports: + - "80:80/tcp" + - "443:443/tcp" + - "1936:1936/tcp" + networks: + - easyhaproxy + +networks: + easyhaproxy: + external: true + +volumes: + certs_letsencrypt: + # external: true + # certs_haproxy: + # external: true \ No newline at end of file diff --git a/examples/swarm/portainer.yml b/examples/swarm/portainer.yml new file mode 100644 index 0000000..b438b58 --- /dev/null +++ b/examples/swarm/portainer.yml @@ -0,0 +1,28 @@ +# To install: +# docker stack deploy -c portainer.yml portainer + +version: "3" + +services: + portainer: + image: portainer/portainer-ce:latest + volumes: + - portainer_data:/data portainer + - /var/run/docker.sock:/var/run/docker.sock + deploy: + replicas: 1 + labels: + # easyhaproxy.http.redirect_ssl: true + # easyhaproxy.http.letsencrypt: true + easyhaproxy.http.host: portainer.local + easyhaproxy.http.port: 80 + easyhaproxy.http.localport: 9000 + +volumes: + certs_letsencrypt: + external: true + # certs_haproxy: + # external: true + portainer_data: + # external: true + diff --git a/examples/swarm/docker-compose.yml b/examples/swarm/services.yml similarity index 94% rename from examples/swarm/docker-compose.yml rename to examples/swarm/services.yml index d5fa394..ad16ce2 100644 --- a/examples/swarm/docker-compose.yml +++ b/examples/swarm/services.yml @@ -1,3 +1,6 @@ +# To install: +# docker stack deploy -c services.yml services +# # To test: # curl -k -H "Host: host1.local" https://127.0.0.1/ # curl -k -H "Host: host2.local" https://127.0.0.1/ @@ -18,25 +21,6 @@ version: "3" services: - haproxy: - image: byjg/easy-haproxy - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./host2.local.pem:/certs/haproxy/host2.local.pem - deploy: - replicas: 1 - environment: - EASYHAPROXY_DISCOVER: swarm - EASYHAPROXY_SSL_MODE: "loose" - HAPROXY_CUSTOMERRORS: "true" - HAPROXY_USERNAME: admin - HAPROXY_PASSWORD: password - HAPROXY_STATS_PORT: 1936 - ports: - - "80:80/tcp" - - "443:443/tcp" - - "1936:1936/tcp" - container: image: byjg/static-httpserver deploy: diff --git a/src/processor/__init__.py b/src/processor/__init__.py index fa50232..98f9835 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -40,6 +40,7 @@ class ProcessorInterface: def __init__(self, filename = None): self.filename = filename + self.label = ContainerEnv.read()['lookup_label'] self.refresh() @staticmethod @@ -156,26 +157,40 @@ class Swarm(ProcessorInterface): def inspect_network(self): ha_proxy_service_name = self.client.containers.get(socket.gethostname()).name.split('.')[0] - for endpoint in self.client.services.get(ha_proxy_service_name).attrs['Endpoint']["VirtualIPs"]: - ha_proxy_network_id = endpoint["NetworkID"] - if self.client.networks.get(ha_proxy_network_id).name != 'ingress': + ha_proxy_network_id = None + swarm_ingress_id = None + + # Get the HAProxy network and the ingress network + for endpoint in self.client.services.get(ha_proxy_service_name).attrs['Endpoint']["VirtualIPs"]: + network_name = self.client.networks.get(endpoint["NetworkID"]).name + if swarm_ingress_id is None and network_name == 'ingress': + swarm_ingress_id = endpoint["NetworkID"] + if ha_proxy_network_id is None and network_name != 'ingress': + ha_proxy_network_id = endpoint["NetworkID"] + if ha_proxy_network_id is not None and swarm_ingress_id is not None: break + # Check if the service is attached to the HAProxy network self.parsed_object = {} for service in self.client.services.list(): + if not any(self.label in key for key in service.attrs["Spec"]["Labels"]): + continue + ip_address = None network_list = [] for endpoint in service.attrs["Endpoint"]["VirtualIPs"]: if ha_proxy_network_id == endpoint["NetworkID"]: ip_address = endpoint["Addr"].split("/")[0] break - network_list.append(endpoint["NetworkID"]) - + elif swarm_ingress_id != endpoint["NetworkID"]: + network_list.append(endpoint["NetworkID"]) + + # Attach the service to the HAProxy network if ip_address is None: network_list.append(ha_proxy_network_id) service.update(networks = network_list) continue # skip to the next service to give time to update the network - + self.parsed_object[ip_address] = service.attrs["Spec"]["Labels"]