1
0
Fork 0

Merge branch 'fix/host-network-mode'
Some checks failed
Docker / Test (push) Has been cancelled
Docker / Tests-E2E-Docker (push) Has been cancelled
Docker / Tests-E2E-Kubernetes (push) Has been cancelled
Docker / Tests-E2E-Additional (push) Has been cancelled
Docker / Tests-E2E-Swarm (push) Has been cancelled
Docker / Build (push) Has been cancelled
Docker / Publish-PyPI (push) Has been cancelled
Docker / Helm (push) Has been cancelled
Docker / HelmDeploy (push) Has been cancelled
Docker / Documentation (push) Has been cancelled

This commit is contained in:
badblocks 2026-09-02 13:22:14 -07:00
commit 1302425d09
Signed by: badblocks
SSH key fingerprint: SHA256:hEcM6BP4hKm9F7WsomNuXSBpPn2BSDnFzPSl3y1NerQ
8 changed files with 399 additions and 17 deletions

View file

@ -1,7 +1,10 @@
import ipaddress
import socket
import docker
from functions import ContainerEnv, logger_easyhaproxy
from .interface import ProcessorInterface
@ -12,27 +15,145 @@ class Docker(ProcessorInterface):
super().__init__()
def inspect_network(self):
self.parsed_object = {}
own_container = None
try:
ha_proxy_network_name = next(
iter(self.client.containers.get(socket.gethostname()).attrs["NetworkSettings"]["Networks"]))
own_container = self.client.containers.get(socket.gethostname())
ha_proxy_network_name = next(iter(own_container.attrs["NetworkSettings"]["Networks"]))
except Exception:
# HAProxy is not running in a container, get first container network
if len(self.client.containers.list()) == 0:
own_container = None
ha_proxy_network_name = self._first_attachable_network()
if ha_proxy_network_name is None:
return
ha_proxy_network_name = next(iter(
self.client.containers.get(self.client.containers.list()[0].name).attrs["NetworkSettings"]["Networks"]))
ha_proxy_network = self.client.networks.get(ha_proxy_network_name)
host_address = self._host_gateway_address(own_container, ha_proxy_network, ha_proxy_network_name)
self.parsed_object = {}
for container in self.client.containers.list():
if not any(self.label in key for key in container.labels):
continue
# Issue 32 - Docker container cannot connect to containers in different network.
if ha_proxy_network_name not in container.attrs["NetworkSettings"]["Networks"].keys():
ha_proxy_network.connect(container.name)
container = self.client.containers.get(container.name) # refresh object
ip_address = self._container_address(container, ha_proxy_network, ha_proxy_network_name, host_address)
if ip_address is None:
continue
self._merge_labels(ip_address, container)
ip_address = container.attrs["NetworkSettings"]["Networks"][ha_proxy_network_name]["IPAddress"]
self.parsed_object[ip_address] = container.labels
def _first_attachable_network(self):
# Containers sharing a network namespace have no network of their own to borrow.
for container in self.client.containers.list():
if self._network_mode(container) in ["host", "none"] or self._namespace_owner_id(container) is not None:
continue
networks = container.attrs["NetworkSettings"]["Networks"] or {}
if networks:
return next(iter(networks))
return None
@staticmethod
def _network_mode(container):
return (container.attrs.get("HostConfig") or {}).get("NetworkMode", "")
@staticmethod
def _namespace_owner_id(container):
network_mode = Docker._network_mode(container)
if network_mode.startswith("container:"):
return network_mode.split(":", 1)[1]
return None
def _container_address(self, container, ha_proxy_network, ha_proxy_network_name, host_address, seen=None):
"""
Resolve the address HAProxy must use to reach this container.
Returns None when the container cannot be reached.
"""
# Issue 47 - Containers sharing the host network namespace cannot join any other network.
if self._network_mode(container) == "host":
if host_address is None:
logger_easyhaproxy.warning(
f"Container '{container.name}' uses the host network, but the address of the host could not be "
f"determined. Set EASYHAPROXY_HOST_NETWORK_IP to fix it. Skipping it."
)
return host_address
owner_id = self._namespace_owner_id(container)
if owner_id is not None:
return self._namespace_owner_address(
container, owner_id, ha_proxy_network, ha_proxy_network_name, host_address, seen
)
networks = container.attrs["NetworkSettings"]["Networks"] or {}
# Issue 32 - Docker container cannot connect to containers in different network.
if ha_proxy_network_name not in networks.keys():
try:
ha_proxy_network.connect(container.name)
except docker.errors.APIError as e:
logger_easyhaproxy.warning(
f"Container '{container.name}' could not be connected to the network "
f"'{ha_proxy_network_name}': {e}. Skipping it."
)
return None
container = self.client.containers.get(container.name) # refresh object
networks = container.attrs["NetworkSettings"]["Networks"] or {}
return (networks.get(ha_proxy_network_name) or {}).get("IPAddress") or None
def _namespace_owner_address(self, container, owner_id, ha_proxy_network, ha_proxy_network_name, host_address,
seen=None):
"""
Containers started with `network_mode: container:xxx` are reachable at the address of the
container owning the network namespace.
"""
seen = seen or set()
if container.id in seen:
logger_easyhaproxy.warning(
f"Container '{container.name}' has a circular network namespace reference. Skipping it."
)
return None
seen.add(container.id)
try:
owner = self.client.containers.get(owner_id)
except docker.errors.NotFound:
logger_easyhaproxy.warning(
f"Container '{container.name}' shares the network namespace of '{owner_id}', which was not found. "
f"Skipping it."
)
return None
return self._container_address(owner, ha_proxy_network, ha_proxy_network_name, host_address, seen)
def _host_gateway_address(self, own_container, ha_proxy_network, ha_proxy_network_name):
"""
Address the host is reachable at from inside the HAProxy network. Every container sharing the
host network namespace is served through it.
"""
host_network_ip = ContainerEnv.read()["host_network_ip"]
if host_network_ip:
return host_network_ip
if own_container is not None:
gateway = (own_container.attrs["NetworkSettings"]["Networks"].get(ha_proxy_network_name) or {}).get(
"Gateway")
if gateway:
return gateway
for config in (ha_proxy_network.attrs.get("IPAM") or {}).get("Config") or []:
if config.get("Gateway"):
return config["Gateway"]
try:
return str(next(ipaddress.ip_network(config["Subnet"]).hosts()))
except (KeyError, ValueError, StopIteration):
continue
return None
def _merge_labels(self, ip_address, container):
# Every container sharing the host network namespace resolves to the same address, so the
# labels are merged instead of replaced.
labels = self.parsed_object.setdefault(ip_address, {})
for key, value in container.labels.items():
if key.startswith(self.label) and key in labels and labels[key] != value:
logger_easyhaproxy.warning(
f"Container '{container.name}' redefines '{key}' for '{ip_address}' as '{value}' instead of "
f"'{labels[key]}'. Give each container a unique definition name."
)
labels.update(container.labels)