From 428f477ba3622a31d978135593688e45e0d599d0 Mon Sep 17 00:00:00 2001 From: Matthew Baggett Date: Thu, 5 May 2022 16:42:53 +0200 Subject: [PATCH] Added support for swarm mode. --- bouncer/Dockerfile | 11 +++- bouncer/Makefile | 37 +++++++++++ bouncer/bouncer | 124 +++++++++++++++++++++++++++++-------- bouncer/bouncer.runit | 2 +- bouncer/docker-compose.yml | 48 ++++++++------ 5 files changed, 172 insertions(+), 50 deletions(-) create mode 100644 bouncer/Makefile diff --git a/bouncer/Dockerfile b/bouncer/Dockerfile index f3cd208..5ae9e77 100644 --- a/bouncer/Dockerfile +++ b/bouncer/Dockerfile @@ -1,4 +1,4 @@ -FROM benzine/php:cli-8.0 +FROM benzine/php:cli-8.0 as bouncer LABEL maintainer="Matthew Baggett " \ org.label-schema.vcs-url="https://github.com/benzine-framework/docker" \ org.opencontainers.image.source="https://github.com/benzine-framework/docker" @@ -47,4 +47,11 @@ COPY bouncer /app COPY composer.* /app/ RUN composer install && \ chmod +x /app/bouncer && \ - mkdir -p /var/log/bouncer \ No newline at end of file + mkdir -p /var/log/bouncer + +FROM benzine/php:nginx-8.0 as test-app-a +COPY ./test/public-web-a /app/public + +FROM benzine/php:nginx-8.0 as test-app-b +COPY ./test/public-web-b /app/public + diff --git a/bouncer/Makefile b/bouncer/Makefile new file mode 100644 index 0000000..6458bcc --- /dev/null +++ b/bouncer/Makefile @@ -0,0 +1,37 @@ +test-as-service: clean + docker build -t bouncer --target bouncer . + docker build -t test-app-a --target test-app-a . + docker build -t test-app-b --target test-app-b . + -docker network create --driver overlay bouncer-test + $(MAKE) start_bouncer + $(MAKE) start_test_a + $(MAKE) start_test_b + docker service logs -f bouncer +start_test_a: + docker service create \ + --network bouncer-test \ + --name test-app-a \ + --env BOUNCER_DOMAIN=test-a.local \ + --env BOUNCER_ALLOW_NON_SSL=yes \ + --publish 8081:80 \ + test-app-a +start_test_b: + docker service create \ + --network bouncer-test \ + --name test-app-b \ + --env BOUNCER_DOMAIN=test-b.local \ + --env BOUNCER_ALLOW_NON_SSL=yes \ + --publish 8082:80 \ + test-app-b +start_bouncer: + docker service create \ + --network bouncer-test \ + --name bouncer \ + --publish 8080:80 \ + --mount type=bind,destination=/var/run/docker.sock,source=/var/run/docker.sock \ + bouncer + +clean: + -docker service rm bouncer test-app-a test-app-b + #-docker network rm bouncer-test + #-docker image rm test-app-a test-app-b bouncer \ No newline at end of file diff --git a/bouncer/bouncer b/bouncer/bouncer index a2609a3..3d4d242 100755 --- a/bouncer/bouncer +++ b/bouncer/bouncer @@ -162,6 +162,25 @@ class Bouncer private Logger $logger; private string $instanceStateHash = ''; private array $fileHashes; + private bool $swarmMode = false; + + /** + * @return bool + */ + public function isSwarmMode(): bool + { + return $this->swarmMode; + } + + /** + * @param bool $swarmMode + * @return Bouncer + */ + public function setSwarmMode(bool $swarmMode): Bouncer + { + $this->swarmMode = $swarmMode; + return $this; + } public function __construct() { @@ -220,9 +239,10 @@ class Bouncer * * @return BouncerTarget[] */ - public function findContainers(): array + public function findContainersContainerMode(): array { $bouncerTargets = []; + $containers = json_decode($this->client->request('GET', 'containers/json')->getBody()->getContents(), true); foreach ($containers as $container) { $envs = []; @@ -241,31 +261,7 @@ class Bouncer $bouncerTarget = (new BouncerTarget()) ->setId($inspect['Id']) ; - foreach ($envs as $eKey => $eVal) { - switch ($eKey) { - case 'BOUNCER_DOMAIN': - $domains = explode(',', $eVal); - array_walk($domains, function (&$domain, $key): void { $domain = trim($domain); }); - $bouncerTarget->setDomains($domains); - - break; - - case 'BOUNCER_LETSENCRYPT': - $bouncerTarget->setLetsEncrypt(in_array(strtolower($eVal), ['yes', 'true'], true)); - - break; - - case 'BOUNCER_TARGET_PORT': - $bouncerTarget->setPort($eVal); - - break; - - case 'BOUNCER_ALLOW_NON_SSL': - $bouncerTarget->setAllowNonSSL(in_array(strtolower($eVal), ['yes', 'true'], true)); - - break; - } - } + $bouncerTarget = $this->parseContainerEnvironmentVariables($envs, $bouncerTarget); if (isset($inspect['NetworkSettings']['IPAddress']) && !empty($inspect['NetworkSettings']['IPAddress'])) { // As per docker service @@ -282,6 +278,38 @@ class Bouncer $bouncerTargets[] = $bouncerTarget; } } + return $bouncerTargets; + } + public function findContainersSwarmMode(): array + { + $bouncerTargets = []; + $services = json_decode($this->client->request('GET', 'services')->getBody()->getContents(), true); + + if(isset($services['message'])){ + $this->logger->debug(sprintf('Something happened while interrogating services.. This node is not a swarm node, cannot have services: %s', $services['message'])); + }else{ + foreach($services as $service){ + $envs = []; + foreach($service['Spec']['TaskTemplate']['ContainerSpec']['Env'] as $env){ + list($eKey, $eVal) = explode("=", $env,2); + $envs[$eKey] = $eVal; + } + if(isset($envs['BOUNCER_DOMAIN'])) { + $bouncerTarget = (new BouncerTarget()) + ->setId($service['ID']); + + $bouncerTarget = $this->parseContainerEnvironmentVariables($envs, $bouncerTarget); + + $bouncerTarget->setIp("172.17.0.1"); + $bouncerTarget->setPort($service['Endpoint']['Ports'][0]['PublishedPort']); + $bouncerTarget->setTargetPath(sprintf('http://%s:%d/', $bouncerTarget->getIp(), $bouncerTarget->getPort())); + + $this->logger->debug(sprintf('Decided that %s has the target path %s', $bouncerTarget->getName(), $bouncerTarget->getTargetPath())); + + $bouncerTargets[] = $bouncerTarget; + } + } + } return $bouncerTargets; } @@ -335,7 +363,11 @@ class Bouncer if ($this->s3Enabled()) { $this->getCertificatesFromS3(); } - $targets = $this->findContainers(); + $determineSwarmMode = json_decode($this->client->request('GET', 'swarm')->getBody()->getContents(), true); + $this->setSwarmMode(!isset($determineSwarmMode['message'])); + $this->logger->info(sprintf("Swarm mode is %s.", $this->isSwarmMode() ? 'enabled' : 'disabled')); + $targets = $this->isSwarmMode() ? $this->findContainersSwarmMode() : $this->findContainersContainerMode(); + $this->logger->info(sprintf('%s Found %d services with BOUNCER_DOMAIN set', Emoji::CHARACTER_MAGNIFYING_GLASS_TILTED_LEFT, count($targets))); foreach ($targets as $target) { $this->generateNginxConfig($target); @@ -494,6 +526,44 @@ class Bouncer $this->logger->info(sprintf('%s Restarting nginx', Emoji::CHARACTER_TIMER_CLOCK)); $shell->run($command); } + + /** + * @param array $envs + * @param BouncerTarget $bouncerTarget + * @return BouncerTarget + */ + public function parseContainerEnvironmentVariables(array $envs, BouncerTarget $bouncerTarget): BouncerTarget + { + foreach ($envs as $eKey => $eVal) { + switch ($eKey) { + case 'BOUNCER_DOMAIN': + $domains = explode(',', $eVal); + array_walk($domains, function (&$domain, $key): void { + $domain = trim($domain); + }); + $bouncerTarget->setDomains($domains); + + break; + + case 'BOUNCER_LETSENCRYPT': + $bouncerTarget->setLetsEncrypt(in_array(strtolower($eVal), ['yes', 'true'], true)); + + break; + + case 'BOUNCER_TARGET_PORT': + $bouncerTarget->setPort($eVal); + + break; + + case 'BOUNCER_ALLOW_NON_SSL': + $bouncerTarget->setAllowNonSSL(in_array(strtolower($eVal), ['yes', 'true'], true)); + + break; + + } + } + return $bouncerTarget; + } } (new Bouncer())->run(); diff --git a/bouncer/bouncer.runit b/bouncer/bouncer.runit index dfe70c5..4c67c32 100755 --- a/bouncer/bouncer.runit +++ b/bouncer/bouncer.runit @@ -1,4 +1,4 @@ #!/usr/bin/env bash echo "Starting Bouncer" /app/bouncer -sleep 60; \ No newline at end of file +sleep 30; \ No newline at end of file diff --git a/bouncer/docker-compose.yml b/bouncer/docker-compose.yml index 3ec4ed3..974e9c2 100644 --- a/bouncer/docker-compose.yml +++ b/bouncer/docker-compose.yml @@ -2,36 +2,44 @@ version: "3.4" services: bouncer: - image: benzine/bouncer - build: . + image: bouncer + build: + context: . + target: bouncer volumes: - /var/run/docker.sock:/var/run/docker.sock - ./:/app - environment: - - BOUNCER_LETSENCRYPT_MODE=staging - - BOUNCER_LETSENCRYPT_EMAIL=matthew@baggett.me - - BOUNCER_S3_ENDPOINT=http://grey.ooo:9000 - - BOUNCER_S3_KEY_ID=geusebio - - BOUNCER_S3_KEY_SECRET=changeme - - BOUNCER_S3_BUCKET=bouncer-certificates - - BOUNCER_S3_USE_PATH_STYLE_ENDPOINT="yes" +# environment: +# - BOUNCER_LETSENCRYPT_MODE=staging +# - BOUNCER_LETSENCRYPT_EMAIL=matthew@baggett.me +# - BOUNCER_S3_ENDPOINT=http://grey.ooo:9000 +# - BOUNCER_S3_KEY_ID=geusebio +# - BOUNCER_S3_KEY_SECRET=changeme +# - BOUNCER_S3_BUCKET=bouncer-certificates +# - BOUNCER_S3_USE_PATH_STYLE_ENDPOINT="yes" ports: - 127.0.99.100:80:80 - 127.0.99.100:443:443 web-a: - image: benzine/php:nginx + image: test-app-a + build: + context: . + target: test-app-a volumes: - ./test/public-web-a:/app/public environment: - BOUNCER_DOMAIN=a.web.grey.ooo - - BOUNCER_LETSENCRYPT=true +# - BOUNCER_LETSENCRYPT=true + + web-b: + image: test-app-b + build: + context: . + target: test-app-b + volumes: + - ./test/public-web-b:/app/public + environment: + - BOUNCER_DOMAIN=b.web.grey.ooo +# - BOUNCER_LETSENCRYPT=true -# web-b: -# image: benzine/php:nginx -# volumes: -# - ./test/public-web-b:/app/public -# environment: -# - BOUNCER_DOMAIN=b.web.grey.ooo -# -# \ No newline at end of file