в docker

Docker Swarm: stack deploy и env-переменные


Познакомившись с docker, рано или поздно вы начнете использовать оркестраторы Fleet/Nomad/Kubernetes/Aurora/Docker Swarm и т.д. На мой взгляд, самый простой из них — Docker Swarm, который «из коробки» дает возможность развернуть отдельный сервис или целый стек.

Однако при использовании команды docker stack deploy для развертывания стека сервисов из файла docker-compose.yml переменные окружения заменяются пустыми значениями, хотя при использовании команды docker-compose up -d все работает, как ожидается. Давайте разберемся!

В данной статье не рассматривается установка и настройка полноценного кластера Docker Swarm — работаем только с одной нодой и решаем только вопрос проброса переменных окружения при деплое стека.

Считаем, что у вас уже установлен docker и docker-compose необходимых версий и включен режим роя (swarm). В данном примере используются:

docker info
Containers: 16
 Running: 16
 Paused: 0
 Stopped: 0
Images: 181
Server Version: 17.06.0-ce
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 280
 Dirperm1 Supported: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins: 
 Volume: local
 Network: bridge host ipvlan macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: active
 NodeID: udow4edogefqxcfs6gzkkgh6w
 Is Manager: true
 ClusterID: ut8uvfxdk1ldgguazc7snuc6u
 Managers: 1
 Nodes: 1
 Orchestration:
  Task History Retention Limit: 5
 Raft:
  Snapshot Interval: 10000
  Number of Old Snapshots to Retain: 0
  Heartbeat Tick: 1
  Election Tick: 3
 Dispatcher:
  Heartbeat Period: 5 seconds
 CA Configuration:
  Expiry Duration: 3 months
  Force Rotate: 0
 Root Rotation In Progress: false
 Node Address: 192.168.0.34
 Manager Addresses:
  192.168.0.34:2377
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: cfb82a876ecc11b5ca0977d1733adbe58599088a
runc version: 2d41c047c83e09a6d61d464906feb2a2f3c52aa4
init version: 949e6fa
Security Options:
 apparmor
 seccomp
  Profile: default
Kernel Version: 4.4.0-91-generic
Operating System: Ubuntu 16.04.2 LTS
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 7.67GiB
Name: e-lebed.lc
ID: 7VNW:YUXN:EOH2:3JRQ:V7AX:O5OQ:EYJ3:UT7H:JNM3:SGTK:BRIP:DHKB
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Username: ealebed
Registry: https://index.docker.io/v1/
Experimental: true
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

WARNING: No swap limit support
docker-compose version
docker-compose version 1.14.0, build c7bdf9e
docker-py version: 2.3.0
CPython version: 2.7.13
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

После инициализации режима роя следует переписать файл docker-compose.yml минимум на 3-ю версию для развертывания сервисов через команду deploy. К примеру, ранее мы использовали файл второй версии:

version: '2'
services:
### Applications Code Container #############################
    applications:
        container_name: application
        image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
### PHP-FPM Container #######################################
    php-fpm:
        container_name: php-fpm
        image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
        volumes_from:
            - applications
        expose:
            - "9000"
        logging:
          driver: gelf
          options:
            gelf-address: "udp://${GRAYLOG_ADDR}:12201"
            tag: "php-fpm"
### Nginx Server Container ##################################
    nginx:
        container_name: nginx
        image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:latest
        volumes_from:
            - applications
        depends_on:
            - "php-fpm"
        ports:
            - "80:80"
            - "443:443"
### Redis Container #########################################
    redis:
        container_name: redis
        image: registry.gitlab.lc:5000/develop/ed/redis-ed-sq:latest
        volumes:
            - redis:/data
        ports:
            - "${REDIS_PORT}:6379"
        logging:
          driver: gelf
          options:
            gelf-address: "udp://${GRAYLOG_ADDR}:12201"
            tag: "redis"
### Memcached Container #####################################
    memcached:
        container_name: memcached
        image: registry.gitlab.lc:5000/develop/ed/memcached-ed-sq:latest
        volumes:
            - memcached:/var/lib/memcached
        ports:
            - "${MEMCACHED_PORT}:11211"
        logging:
          driver: gelf
          options:
            gelf-address: "udp://${GRAYLOG_ADDR}:12201"
            tag: "memcached"
### Volumes Setup ###########################################
volumes:
    memcached:
        driver: "local"
    redis:
        driver: "local"
То теперь файл третьей версии будет выглядеть так:

version: '3.1'
services:
### Code from branch develop #############################################
  applications:
    image: registry.gitlab.lc:5000/develop/ed/develop.sources:latest
    volumes:
      - developcode:/var/www/develop
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
        delay: 5s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
### PHP-FPM ##############################################################
  php-fpm:
    image: registry.gitlab.lc:5000/develop/ed/php-fpm-ed-sq:latest
    volumes:
      - developcode:/var/www/develop
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 5s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "php-fpm"
### Nginx ################################################################
  nginx:
    image: registry.gitlab.lc:5000/develop/ed/nginx-ed-sq:staging
    volumes:
      - developcode:/var/www/develop
    ports:
      - "80:80"
      - "443:443"
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 5s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
### Redis ################################################################
  redis:
    image: registry.gitlab.lc:5000/develop/ed/redis-ed-sq:latest
    volumes:
      - redis:/data
    ports:
      - "${REDIS_PORT}:6379"
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 5s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "redis"
### Memcached ############################################################
  memcached:
    image: registry.gitlab.lc:5000/develop/ed/memcached-ed-sq:latest
    volumes:
      - memcached:/var/lib/memcached
    ports:
      - "${MEMCACHED_PORT}:11211"
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 5s
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]
    logging:
      driver: gelf
      options:
        gelf-address: "udp://${GRAYLOG_ADDR}:12201"
        tag: "memcached"
### Volumes Setup ########################################################
volumes:
  developcode:
  memcached:
    driver: "local"
  redis:
    driver: "local"

Из самого важного — мы больше не можем использовать конструкцию volumes_from для монтирования данных из Data Only контейнеров, приходится выкручиваться с именованными томами (named volumes), об этом отдельная подробная статья. Также в режиме роя больше не нужны имена контейнеров (они генерируются автоматически и выглядят примерно так: test-stack_nginx.1.kut1gjutr9zdbo1wdf1biuvti), не нужно делать expose для портов, доступных только между контейнерами и появилась большая и гибкая секция deploy.

В каталоге с вышеприведенным файлом docker-compose.yml находится также файл с переменными окружения .env с таким содержимым:

### Graylog ##############################################################
GRAYLOG_ADDR=graylog.lc
### Redis ################################################################
REDIS_PORT=6379
### Memcached ############################################################
MEMCACHED_PORT=11211

При использовании команды docker-compose up -d переменные читаются из файла и подставляются при старте контейнеров — можно в этом убедиться выполнив docker inspect <имя_контейнера>, например (вывод сокращен):

...
        "HostConfig": {
            "Binds": [],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "gelf",
                "Config": {
                    "gelf-address": "udp://graylog.lc:12201",
                    "tag": "redis"
...
            "Env": [
                "REDIS_PORT=6379",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "REDIS_VERSION=3.2.8",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.8.tar.gz",
                "REDIS_DOWNLOAD_SHA1=6780d1abb66f33a97aad0edbe020403d0a15b67f"
            ],
...

Однако, если мы попробуем развернуть данный стек сервисов с помощью команды deploy, например:

docker stack deploy --with-registry-auth --compose-file docker-compose.yml test-stack

то вместо ожидаемых значений переменных из файла .env мы увидим пустоту:

...
        "HostConfig": {
            "Binds": [],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "gelf",
                "Config": {
                    "gelf-address": "udp://:12201",
                    "tag": "redis"
...
            "Env": [
                "REDIS_PORT=",
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "REDIS_VERSION=3.2.8",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.8.tar.gz",
                "REDIS_DOWNLOAD_SHA1=6780d1abb66f33a97aad0edbe020403d0a15b67f"
            ],
...

Точно такой же результат будет если передавать значения в контейнер с помощью секции environment, о чем пишут здесь.

Один из вариантов решения — не использовать переменные и сразу захардкодить все в docker-compose.yml, но иногда вследствие архитектурных решений обойтись без переменных нельзя. В таком случае предлагаю использовать следующий «велосипед» (надежный и проверенный):

env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy --with-registry-auth --compose-file docker-compose.yml test-stack

Добавить комментарий