Support containers sharing a network namespace (issue #47)
This commit is contained in:
parent
8bd9f06f12
commit
463e2023c8
8 changed files with 404 additions and 16 deletions
|
|
@ -23,6 +23,7 @@ _CONTAINER_ENV_VARS = [
|
|||
"HAPROXY_CUSTOMERRORS",
|
||||
"EASYHAPROXY_SSL_MODE",
|
||||
"EASYHAPROXY_LABEL_PREFIX",
|
||||
"EASYHAPROXY_HOST_NETWORK_IP",
|
||||
"EASYHAPROXY_LOG_LEVEL",
|
||||
"HAPROXY_LOG_LEVEL",
|
||||
"CERTBOT_LOG_LEVEL",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ def test_container_env_empty():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -42,6 +43,7 @@ def test_container_env_customerrors():
|
|||
"customerrors": True,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -76,6 +78,7 @@ def test_container_env_sslmode():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "strict",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -111,6 +114,7 @@ def test_container_env_stats():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -146,6 +150,7 @@ def test_container_env_stats_password():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"stats": {
|
||||
"username": "admin",
|
||||
"password": "xyz",
|
||||
|
|
@ -188,6 +193,7 @@ def test_container_env_stats_password_2():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"stats": {
|
||||
"username": "abc",
|
||||
"password": "xyz",
|
||||
|
|
@ -230,6 +236,7 @@ def test_container_env_certbot_email():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -272,6 +279,7 @@ def test_container_env_certbot_full():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.DEBUG,
|
||||
"haproxy": Functions.INFO,
|
||||
|
|
@ -316,6 +324,7 @@ def test_container_log_level():
|
|||
"customerrors": False,
|
||||
"ssl_mode": "default",
|
||||
"lookup_label": "easyhaproxy",
|
||||
"host_network_ip": "",
|
||||
"logLevel": {
|
||||
"easyhaproxy": Functions.ERROR,
|
||||
"haproxy": Functions.FATAL,
|
||||
|
|
|
|||
|
|
@ -105,4 +105,234 @@ def test_processor_docker():
|
|||
container2.stop()
|
||||
|
||||
|
||||
def _skip_unless_docker_is_idle():
|
||||
try:
|
||||
client = docker.from_env()
|
||||
except docker.errors.DockerException:
|
||||
pytest.skip("There is no docker environment")
|
||||
|
||||
if len(client.containers.list()) > 0:
|
||||
pytest.skip("I cannot run this test with other containers running.")
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def _bridge_gateway(client):
|
||||
return client.networks.get("bridge").attrs["IPAM"]["Config"][0]["Gateway"]
|
||||
|
||||
|
||||
def test_processor_docker_host_network():
|
||||
client = _skip_unless_docker_is_idle()
|
||||
|
||||
container = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_host",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
network_mode="host",
|
||||
labels={
|
||||
"easyhaproxy.hostmode.port": "80",
|
||||
"easyhaproxy.hostmode.localport": "8080",
|
||||
"easyhaproxy.hostmode.host": "hostmode.local",
|
||||
})
|
||||
container2 = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_bridge",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
labels={
|
||||
"easyhaproxy.bridged.port": "80",
|
||||
"easyhaproxy.bridged.localport": "8080",
|
||||
"easyhaproxy.bridged.host": "bridged.local",
|
||||
})
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
||||
|
||||
assert {
|
||||
'easyhaproxy.hostmode.host': 'hostmode.local',
|
||||
'easyhaproxy.hostmode.localport': '8080',
|
||||
'easyhaproxy.hostmode.port': '80',
|
||||
} == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.hostmode")
|
||||
|
||||
# The container shares the host network namespace, so it is served through the gateway.
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == _bridge_gateway(client)
|
||||
|
||||
# The container on the bridge network keeps being served through its own address.
|
||||
bridged_ip = client.containers.get(container2.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.bridged") == bridged_ip
|
||||
|
||||
static.get_haproxy_conf()
|
||||
assert static.get_hosts() == [
|
||||
'bridged.local:80',
|
||||
'hostmode.local:80'
|
||||
]
|
||||
finally:
|
||||
container.stop()
|
||||
container2.stop()
|
||||
|
||||
|
||||
def test_processor_docker_host_network_merges_labels():
|
||||
client = _skip_unless_docker_is_idle()
|
||||
|
||||
container = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_host1",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
network_mode="host",
|
||||
labels={
|
||||
"easyhaproxy.first.port": "80",
|
||||
"easyhaproxy.first.localport": "8080",
|
||||
"easyhaproxy.first.host": "first.local",
|
||||
})
|
||||
container2 = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_host2",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
network_mode="host",
|
||||
environment={"PORT": "9000", "TLS_PORT": "9443"},
|
||||
labels={
|
||||
"easyhaproxy.second.port": "80",
|
||||
"easyhaproxy.second.localport": "9000",
|
||||
"easyhaproxy.second.host": "second.local",
|
||||
})
|
||||
container3 = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_bridge_merge",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
labels={
|
||||
"easyhaproxy.bridged.port": "80",
|
||||
"easyhaproxy.bridged.localport": "8080",
|
||||
"easyhaproxy.bridged.host": "bridged.local",
|
||||
})
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
||||
gateway = _bridge_gateway(client)
|
||||
|
||||
# Both containers resolve to the same address, so their labels are merged, not replaced.
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.first") == gateway
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.second") == gateway
|
||||
merged = {key: value for key, value in static.get_parsed_object()[gateway].items()
|
||||
if key.startswith("easyhaproxy.")}
|
||||
assert {
|
||||
'easyhaproxy.first.host': 'first.local',
|
||||
'easyhaproxy.first.localport': '8080',
|
||||
'easyhaproxy.first.port': '80',
|
||||
'easyhaproxy.second.host': 'second.local',
|
||||
'easyhaproxy.second.localport': '9000',
|
||||
'easyhaproxy.second.port': '80',
|
||||
} == merged
|
||||
|
||||
haproxy_cfg = static.get_haproxy_conf()
|
||||
assert f"server srv-0 {gateway}:8080" in haproxy_cfg
|
||||
assert f"server srv-0 {gateway}:9000" in haproxy_cfg
|
||||
finally:
|
||||
container.stop()
|
||||
container2.stop()
|
||||
container3.stop()
|
||||
|
||||
|
||||
def test_processor_docker_host_network_ip_override():
|
||||
client = _skip_unless_docker_is_idle()
|
||||
|
||||
container = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_host_override",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
network_mode="host",
|
||||
labels={
|
||||
"easyhaproxy.hostmode.port": "80",
|
||||
"easyhaproxy.hostmode.localport": "8080",
|
||||
"easyhaproxy.hostmode.host": "hostmode.local",
|
||||
})
|
||||
container2 = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_bridge_override",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
labels={
|
||||
"easyhaproxy.bridged.port": "80",
|
||||
"easyhaproxy.bridged.localport": "8080",
|
||||
"easyhaproxy.bridged.host": "bridged.local",
|
||||
})
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
os.environ['EASYHAPROXY_HOST_NETWORK_IP'] = '10.20.30.40'
|
||||
|
||||
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
||||
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == '10.20.30.40'
|
||||
finally:
|
||||
del os.environ['EASYHAPROXY_HOST_NETWORK_IP']
|
||||
container.stop()
|
||||
container2.stop()
|
||||
|
||||
|
||||
def test_processor_docker_shared_namespace():
|
||||
client = _skip_unless_docker_is_idle()
|
||||
|
||||
owner = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_owner",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True)
|
||||
time.sleep(1)
|
||||
container = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_sidecar",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
environment={"PORT": "9000", "TLS_PORT": "9443"},
|
||||
network_mode="container:test_processor_docker_owner",
|
||||
labels={
|
||||
"easyhaproxy.sidecar.port": "80",
|
||||
"easyhaproxy.sidecar.localport": "9000",
|
||||
"easyhaproxy.sidecar.host": "sidecar.local",
|
||||
})
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
||||
|
||||
# The sidecar is reachable at the address of the container owning the network namespace.
|
||||
owner_ip = client.containers.get(owner.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
|
||||
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.sidecar") == owner_ip
|
||||
finally:
|
||||
container.stop()
|
||||
owner.stop()
|
||||
|
||||
|
||||
def test_processor_docker_host_network_only():
|
||||
client = _skip_unless_docker_is_idle()
|
||||
|
||||
container = client.containers.run("byjg/static-httpserver",
|
||||
name="test_processor_docker_host_only",
|
||||
detach=True,
|
||||
auto_remove=True,
|
||||
remove=True,
|
||||
network_mode="host",
|
||||
labels={
|
||||
"easyhaproxy.hostmode.port": "80",
|
||||
"easyhaproxy.hostmode.localport": "8080",
|
||||
"easyhaproxy.hostmode.host": "hostmode.local",
|
||||
})
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
# There is no network to borrow, but the discovery must not fail.
|
||||
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
||||
assert static.get_parsed_object() == {}
|
||||
assert static.get_haproxy_conf() != ""
|
||||
finally:
|
||||
container.stop()
|
||||
|
||||
|
||||
# test_processor_docker()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue