Шпаргалка по работе с docker-compose

Обновлено Обновлено: Опубликовано Опубликовано:

В данной инструкции мы приведем различные примеры по работе с docker-compose без подробного описания принципа работы. Данный материал можно использовать в качестве шпаргалки.

Базовый шаблон

В первую очередь, предлагаю заготовку для docker-compose файла, на основе которой можно начинать писать свой сценарий для docker.

Создаем файл для работы с контейнерами:

vi docker-compose.yml

services:

  <srv_name>:
    image: <image_name>
    container_name: <container_name>
    hostname: <hostname>
    restart: unless-stopped
    environment:
      TZ: "Europe/Moscow"

* где:

  • services — основной раздел, где мы будем создавать и описывать наши сервисы (контейнеры docker). В данном примере сервис один. Для добавления еще одного добавляем еще строчки <srv_name>.
    • <srv_name> — название для нашего сервиса, на основе которого будет создан контейнер.
    • image — имя образа, который будет использоваться для создания контейнера.
    • container_name — имя, которое получен созданный контейнер.
    • hostname — имя хоста внутри контейнера.
    • restart — поведения контейнера при падении. В нашем примере мы указываем на необходимость автоматической перезагрузки, за исключением случаев, когда мы его сами остановили командой stop.
    • environment — задаем переменные окружения. В нашем примере только одна, которая указывает на часовой пояс (московское время).

Сервисы

В данном разделе рассмотрим примеры настроек сервисов (контейнеров). Синтаксис:

services:
  <имя сервиса>:
    <настройки сервиса>

1. Создание контейнера из готового образа. Образ указывается с помощью директивы image:

    image: nginx

* в данном примере будет взят образ nginx для поднятия контейнера.

При желании, мы можем, явно, указать версию:

    image: nginx:1.18.0

2. Сборка контейнера из Dockerfile. Для этого используем опцию build:

    build:
      context: ./web-server/
      args:
        buildno: 22042001

* где:

  • build — указание на необходимость сборки из Dockerfile. Пример создания последнего читайте в инструкции Создание собственного образа Docker.
  • context — путь, где нужно искать Dockerfile относительно места, где находится docker-compose файл.
  • buildno — номер для сборки.

3. Проброс папок (volumes). Настраивается с помощью опции volumes:

    volumes:
      - /data/mysql:/var/lib/mysql

* в нашем примере мы смонтируем локальный каталог /data/mysql на хосте внутрь контейнера в качестве каталога /var/lib/mysql.

Также мы можем смонтировать папку только на чтение, например:

    volumes:
      - /:/rootfs:ro

4. Работа с портами. Рассмотрим несколько вариантов работы с портами.

а) Ports (внешняя публикация). С помощью данной опции мы можем указывать, на каких портах должен слушать контейнер и на какие порты должны пробрасываться запросы:

    ports:
      - 8080:80

* в данном примере наш контейнер будет слушать запросы на порту 8080 и передавать их внутрь контейнера на порт 80.

Или можно прописать так:

    ports:
      - 80

* в этом примере будет настроен проброс на порт 80. Внешний порт будет выбран docker автоматически.

При необходимости прикрепить проброс с конкретному IP-адресу хостовой машины, используем нотацию:

    ports:
      - 192.168.15.15:80:80

* хост docker будет слушать на порту 80 на адресе 192.168.15.15.

По умолчанию пробрасываются порты по протоколу TCP. Чтобы указать UDP, используем формат:

    ports:
      - 53:53/udp

* в данном примере будет использоваться для прослушивания порт 53/udp.

б) Expose (внутрення публикация). Данная опция задает порт, на котором должно слушать приложение внутри контейнера, но проброса с внешнего адреса не будет — отправить запрос по сети на данный порт можно с другого контейнера:

    expose:
      - 7000

5. Переменные окружения. В нашей универсальной заготовке мы уже использовали одну системную переменную:

    environment:
      TZ: "Europe/Moscow"

Чтобы передать несколько переменных, просто их перечисляем:

    environment:
      TZ: "Europe/Moscow"
      MYSQL_ROOT_PASSWORD=password

Для каждого приложения есть свой набор системных переменных, которые оно понимает и интерпретирует. Например, MYSQL_ROOT_PASSWORD поймет СУБД и установит значение в качестве пароля для пользователя root.

Также системные переменные можно передать не в сценарии docker-compose, а в файле .env — просто создадим этот файл в одной директории с файлом docker-compose.yml:

vi .env

MYSQL_ROOT_PASSWORD=secret
MYSQL_PWD=secret

Или мы можем назвать файл с переменными иначе, например env-config. В таком случае, в docker-compose нам нужно указать путь до него:

    env_file:
      - .env
      - env-config

* обратите внимание, что мы указали использовать сразу 2 файла.

6. Зависимости для контейнеров. Мы можем указать с помощью опции depends_on, от какого контейнера зависит сервис:

    depends_on:
      - db

* в конкретном примере, контейнер не запустится, пока не поднимется db.

7. Переопределить команду. С помощью директивы command мы можем переопределить команду для запуска, например:

    command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]

* в данном примере мы запустим redis-server с альтернативным конфигурационным файлом.

Или можно написать так:

    command:
      - redis-server
      - /usr/local/etc/redis/redis.conf

Но если нам понадобиться запустить несколько последовательных команд, рабочий вариант с использованием bash:

    command: bash -c "yarn install && yarn start"

или если в контейнере нет bash:

    command: sh -c "yarn install && yarn start"

Если нам нужно не переопределить команду, но дополнить ее дополнительными аргументами запуска, то можем сделать следующую запись:

    command: >
      --requirepass ${REDIS_PASSWORD}

8. Метки. С помощью опции labels мы можем указывать дополнительную информацию для ориентирования или фильтров при поиске контейнеров:

    labels:
      MAINTAINER: ${MAINTAINER_EMAIL}
      SITE_URL: ${SITE_URL}

* данные могут быть произвольные. Обратите внимание, что в качестве значений мы указали переменные, которые можно просто передать из системного окружения.

9. Пользователь. Директива user позволяет задать конкретного пользователя, от которого будет запускаться и работать контейнер:

    user: root

10. Домашняя директория. Определяет положение по умолчанию, откуда будут выполняться команды.

    working_dir: /var/www/app

11. Лимиты. Дают возможность определить для контейнера ограничения. Приведем несколько примеров таких лимитов.

а) ulimits. Ограничение на число дескрипторов файлов в контейнере:

    ulimits:
      memlock:
        soft: -1
        hard: -1

б) shm_size. Размер устройства /dev/shm, которое является устройством общей памяти и обеспечивает временную файловую систему хранения файлов.

    shm_size: '512m'

Healthcheck и Logs

Отдельно рассмотрим директивы, которые помогают проверять состояние контейнера или вести жерналы.

Healthcheck

Синтаксис:

services:
  <имя сервиса>:
    ...
    healthcheck:
      test: <script>
      interval: <interval>
      timeout: <timeout>
      retries: <retries>

* где:

  • test — наш скрипт, который должен вернуть 0, если все хорошо, или 1 — если все плохо.
  • interval — как часто запускать проверку.
  • timeout — как долго нужно ждать ответа.
  • retries — сколько раз тест должен вернуть отрицательный результат, чтобы контейнер перешел в состояние «unhealthy».

Ниже рассмотрим несколько примеров.

1. Проверка работы веб-портала. Рассмотрим пример, когда сайт при правильной работы должен возвращать слово works:

    healthcheck:
      test: curl -s http://127.0.0.1 | grep works
      interval: 30s
      timeout: 2s
      retries: 10

* в данном примере мы запросим у нашего сервера страницу по http и выведем строку, в которой есть слово works. Если такой строки не найдется, команда вернет код 1.

2. Проверка СУБД mysql. Проверку можно выполнить с помощью запроса SELECT 1. В композ-файле это выглядит так:

    healthcheck:
      test: ["CMD", "mysql" ,"-h", "mysql", "-P", "3306", "-u", "root", "-e", "SELECT 1", "cache"]
      interval: 30s
      timeout: 2s
      retries: 10

Логирование

Настройка логирования может быть выполнена в конфигурационном файле docker. Однако, если мы хотим для отдельного контейнера выставить определенные значения параметров журналирования, нам поможет директива logging.

Приведем несколько примеров:

services:
  ...
  service_name:
    ...
    logging:
      driver: "json-file"
      options:
        max-size: 100m
        max-file: "5"

* в нашем примере мы указываем, что формат лога должен быть json (json-file); также ограничиваем размер одного файла логов (max-size) и задаем максимальное количество данных файлов (max-file).

Networks

Отдельно рассмотрим варианты сетевых настроек.

Список сетей, созданных для docker можно увидеть командой:

docker network ls

Получить подробную информацию по сети можно командой:

docker network inspect <имя сети>

1. Указать определенную подсеть. Задается с помощью опции subnet в отдельной секции networks. В последней мы создаем конфигурацию для определенной сети (в данном примере, default). Для конкретного сервиса мы также задаем привязку к созданной сети default в подразделе networks:

services:

  <srv_name>:
    ...
    networks:
      - default

networks:
  default:
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16

* где 172.28.0.0/16 — подсеть, в которой будут работать все контейнеры, которые привязаны к созданной сети default.

2. Алиасы. Данная настройка позволит видеть контейнеры по альтернативным именам (по умолчанию, они обнаруживаются по имени контейнера). Настройка указывается в подразделе networks сервиса:

services:

  <srv_name>:
    ...
    networks:
      default:
        aliases:
          - <alternative_name>

* в данном примере наш контейнер будет также резолвиться по имени <alternative_name>.

Такого же эффекта можно добиться с помощью links, но его принцип настройки немного отличается. Предположим, у нас есть сервисы webserver и database. Мы хотим, чтобы webserver видел database по имени db. Тогда настраиваем:

  webserver:
    ...
    links:
      - database:db

  database:
  ...

3. Внешняя сеть. Если необходимо, чтобы наши контейнеры могли видеть по сети другие контейнеры, создаем сеть external:

services:

  <srv_name>:
    ...
    networks:
      - dnet

networks:
  dnet:
    name: dnet
    external: true

4. Статические IP-адреса. По docker идеологии все контейнеры должны получать IP-адреса автоматически. Но все же, метод для указания контейнерам статических адресов предусмотрен. Рассмотрим на примере:

services:

  <srv_name>:
    ...
    networks:
      vpcbr:
        ipv4_address: 172.28.0.2

networks:
  vpcbr:
    driver: bridge
    ipam:
     config:
       - subnet: 172.28.0.0/24
         gateway: 172.28.0.1

* в нашем примере будет создана подсеть 172.28.0.0/24, а контейнеру будет присвоен адрес 172.28.0.2.

5. Добавить сопоставление имя - IP-адрес (на подобие локального файла hosts). С помощью данной настройки мы можем указать отдельно для контейнеров, в какой IP-адрес должен разрешаться определенный хост. Задается с помощью директивы extra_hosts:

services:

  <srv_name>:
    ...
    extra_hosts:
      foo: 1.2.3.4
      bar: 5.6.7.8

* в данном примере имени foo будет соответствовать адрес 1.2.3.4, а bar — 5.6.7.8.

Если нам нужно сделать сопоставление с IP-адресом docker-хоста, существует специальное значение host-gateway:

services:

  <srv_name>:
    ...
    extra_hosts:
      - "dmosk.ru:host-gateway"

* теперь, когда контейнер будет обращаться по адресу dmosk.ru, он будет попадать на хост docker.

6. Связь двух контейнеров из разных compose.

Предположим, нам нужно связать два контейнера, которые запускаются с помощью разных docker-compose. Такие контейнеры будут привязаны к разным сетям и не смогут взаимодействовать.

Для решения проблемы мы создадим внешнию подсеть и привяжем ее к контейнеру. Это делается в обоих docker-compose файлах:

services:
    ...
    networks:
      - default
      - net_split1
...
networks:
  net_split1:
    name: net_split1
    driver: bridge
    external: true

* где:

  • services - networks — описываем сети, которые будут доступны контейнеру.
  • networks - net_split1 — создаем внешнюю (external) сеть с именем net_split1.

Примеры полезных команд docker-compose

Рассмотрим различные примеры выполнения команды docker-compose.

1. Посмотреть версию:

docker-compose --version

2. Создание и запуск контейнеров:

docker-compose up -d

* напомним, что в текущем каталоге находится созданный файл с названием docker-compose.yml.

С указанием альтернатнативного файла docker-compose.

docker-compose -f /foo/bar/other-docker-compose.yml up -d

Мы можем не указывать каждый раз файл docker-compose, если зададим путь к нему в файле .env:

COMPOSE_FILE=other-docker-compose.yml

3. Перечитать файл docker-compose:

docker-compose up -d

* на самом деле, команда такая же, как для запуска. Система если определит, что есть изменения, пересоздаст контейнер.

Перечитать и пересобрать контейнеры, если есть инструкция build:

docker-compose up -d --build

4. Остановить контейнеры.

Только остановить контейнеры:

docker-compose down

Остановить контейнеры с удаление данных (в volumes):

docker-compose down --volumes

5. Перезапуск контейнеров.

Перезагрузить все контейнеры для docker-compose файла:

docker-compose restart

Перезагрузить определенный контейнер:

docker-compose restart app

* в данном примере будет перезапущен контейнер app.

6. Запуск команды внутри контейнера (exec).

Запуск команды:

docker-compose exec app npm run build

* в данном примере в контейнере app будет запущена команда npm run build.

Провалиться в shell контейнера:

docker-compose exec -it app sh

Разное

Рассмотрим несколько примеров, которые пока сложно классифицировать и вывести в отдельную группу.

1. Якоря.

Позволяют делать ссылку на определенный блок кода, что позволит не повторять его. Важно отметить, что это возможность не docker-compose, а yml.

Чтобы создать якорь, указываем тег с & в начале:

    logging: &logging
      driver: "json-file"
      options:
        max-size: 100m
        max-file: "5"

* в данном примере мы создали якорь с названием logging. Данный якорь ведет на все настройки блока logging.

Чтобы применить якорь:

    logging:
      <<: *logging

Возможные ошибки

Network <net-name>_default  Error

При попытке запустить compose получаем ошибку:

 Network <net-name>_default  Error
failed to create network <net-name>_default: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

Причина: при запуске композа автоматически создается сеть с префиксом _default. Данная ошибка означает, что такую сеть не удалось создать, так как существует лимит на пулы IP-адресов и данный лимит был исчерпан.

Решение: быстрее всего выполнить чистку docker от неиспользуемых сетей. Для этого выполняем команду:

docker network prune

toomanyrequests

Ошибка появляется при попытке загрузить образ с репозитория docker (docker pull или docker-compose pull). Более полный текст:

toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading

Причина: с ноября 2020 года docker ввел ограничение на количество обращений к их репозиторию анонимных пользователей. Либо вы обратились к данному репозиторию много раз, либо адрес находится за NAT и кто-то другой использовал лимит.

Решение: есть 2 решения:

1. Подождать, пока не закончится действие лимита. Может потребоваться до 6 часов.

2. Залогиниться под своей учетной записью docker hub с помощью команды:

docker login

Читайте также

Другие инструкции, связанные с Docker:

1. Установка Docker на Linux.

2. Создание собственного образа Docker.

3. Настройка локального репозитория для образов Docker и работа с ним.

4. Docker-compose для создания nginx entrypoint.

# DevOps # Виртуализация
Дмитрий Моск — частный мастер
Была ли полезна вам эта инструкция?

Да            Нет