Docker: заметки веб-разработчика. Итерация вторая
Привет, друзья!
Продолжаю делиться с вами заметками о Docker
.
Заметки состоят из 3 частей: первые две теоретические, третья практическая.
Если быть более конкретным:
- первая часть посвящена самому
Docker
,Docker CLI
иDockerfile
; - вторая часть полностью о
Docker Compose
; - в третьей части мы разработаем и «контейнеризуем» приложение, состоящее из клиента, сервера и базы данных, развернем его (задеплоим) и настроим
CI/CD
.
Это часть номер два.
Вот часть номер раз.
Пришел к выводу, что в первой части был излишне многословен, в этой части буду более лаконичным.
Хочу немного дополнить первую часть, а именно: показать парочку примеров Dockerfile
для Node.js-приложений
.
Пример с официального сайта Node.js
FROM node:16
# создание директории приложения
WORKDIR /usr/src/app
# установка зависимостей
# символ астериск ("*") используется для того чтобы по возможности
# скопировать оба файла: `package.json` и `package-lock.json`
COPY package*.json ./
RUN npm install
# для создания сборки для продакшн
# RUN npm ci --only=production
# копируем исходный код
COPY . .
EXPOSE 4000
CMD [ "node", "server.js" ]
Инструкции COPY package*.json ./
и COPY . .
выполняются по отдельности в целях извлечения максимальной выгоды из кеширования слоев. Зависимости проекта меняются на так часто, как файлы, не имеет смысла устанавливать их при каждой сборке образа.
Пример из статьи »10 лучших практик по контейнеризации Node.js-приложений с помощью Docker»
Пример является сокращенным и для продакшна.
FROM node:lts-alpine@sha256:b2da3316acdc2bec442190a1fe10dc094e7ba4121d029cb32075ff59bb27390a
RUN apk add dumb-init
ENV NODE_ENV production
WORKDIR /usr/src/app
COPY --chown=node:node . .
RUN npm ci --only=production
USER node
CMD ['dumb-init', 'node', 'server.js']
Docker Compose
Compose
— это инструмент для определения и запуска Docker-приложений
, состоящих из нескольких контейнеров. Для настройки сервисов приложения используется файл docker-compose.yml
.
Процесс использования Compose
, как правило, состоит из 3 этапов:
- определение среды приложения с помощью
Dockerfile
; - определение сервисов, из которых состоит приложение, в
docker-compose.yml
(для совместного запуска сервисов в изолированной среде); - выполнение команды
docker compose up
для запуска приложения.
Пример файла docker-compose.yml
:
version: "3.9" # опционально
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume: {}
Compose
позволяет делать следующее:
- запускать, останавливать и повторно собирать сервисы;
- получать статус запущенных сервисов;
- получать логи запущенных сервисов;
- выполнять команды в сервисах.
Compose
предоставляет следующие возможности:
- создание нескольких изолированных сред на одном хосте;
- сохранение данных томов при создании контейнеров;
- повторное создание только модифицированных контейнеров;
- передача переменных среды окружения и возможность создания разных сред (для разработки, продакшна и т.д.).
Начало работы с Docker Compose.
docker compose
Команда docker compose
является альтернативой docker-compose CLI
и используется для управления Compose
.
# сигнатура
docker-compose [-f ...] [--profile ...] [options] [COMMAND] [ARGS...]
# основные флаги
-f - путь к docker-compose.yml
-p - название проекта
--project-path - альтернативная рабочая директория (по умолчанию рабочей является директория, содержащая docker-compose.yml)
# основные команды
up - создание и запуск сервисов
down - остановка и удаление контейнеров, сетей, образов и томов
start - запуск сервисов
stop - остановка сервисов
restart - перезапуск сервисов
create - создание сервисов
rm - удаление остановленных контейнеров
run - выполнение одноразовой команды
exec - выполнение команды в запущенном контейнере
Полный список флагов и команд.
docker-compose.yml
Файл Compose
— это файл в формате YAML
, определяющий сервисы, сети и тома. Дефолтным путем этого файла является ./docker-compose.yml
.
Определение сервиса включает в себя установку настроек, которые применяются к каждому контейнеру, запущенному для этого сервиса. Это похоже на передачу аргументов при выполнении команды docker run
. Определения сети и тома аналогично выполнению команд docker network create
и docker volume create
.
Настройки, определенные в Dockerfile
, такие как CMD
, EXPOSE
, VOLUME
и ENV
не нуждаются в дублировании в docker-compose.yml
.
Рассмотрим основные настройки сервисов.
build
Настройки, применяемые во время сборки.
build
может определяться в виде строки — пути к контексту сборки:
version: "3.9"
services:
webapp:
build: ./dir
Или в виде объекта, где context
— путь к контексту, dockerfile
— используемый Dockerfile
и args
— аргументы:
version: "3.9"
services:
webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1
В случае с args
аргументы должны быть определены в Dockerfile
:
# syntax=docker/dockerfile:1
ARG buildno
ARG gitcommithash
RUN echo "Номер сборки: $buildno"
RUN echo "Основано на коммите: $gitcommithash"
build:
context: .
args:
buildno: 1
gitcommithash: cdc3b19
# or
- buildno=1
- gitcommithash=cdc3b19
network
Сеть, к которой подключается контейнер во время сборки (для использования при выполнении команды RUN
):
build:
context: .
network: host
# or
build:
context: .
network: custom_network_1
command
Перезапись дефолтной команды:
command: bundle exec thin -p 3000
depends_on
Зависимость между сервисами. Это означает следующее:
docker compose up
запускает сервисы в определенном порядке. В примере нижеdb
иredis
запускаются передweb
;docker compose up SERVICE
автоматически включает зависимостиSERVICE
. В примереdocker compose up web
также создает и запускаетdb
иredis
;docker compose stop
останавливает сервисы в определенном порядке. В примереweb
останавливается передdb
иredis
.
version: "3.9"
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
restart_policy
Политика перезапуска — определяет, как и когда контейнер должен перезапускаться:
condition
: условие перезапуска —none
,on-failure
илиany
(значение по умолчанию);delay
: время между попытками (по умолчанию равняется5s
);max_attempts
: количество попыток (по умолчанию — бесконечное);window
: время принятия решения об успехе перезапуска (по умолчанию — немедленно).
version: "3.9"
services:
redis:
image: redis:alpine
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
entrypoint
Перезапись дефолтной точки входа:
entrypoint: /code/entrypoint.sh
env_file
Извлечение переменных среды окружения из файла. Может быть единичным значением или списком.
Если файл Compose
определен с помощью docker compose -f FILE
, пути в env_file
будут относительными директории, в которой находится этот файл.
Переменные, определенные в разделе environment
, перезаписывают эти значения.
env_file: .env
# or
env_file:
- ./common.env
- ./apps/web.env
- /opt/runtime_opts.env
expose
Выставление портов без их публикации на хосте — порты будут доступны только связанным (linked) сервисам. Могут определяться только внутренние порты:
expose:
- "3000"
- "8000"
external_links
Подключение к контейнеру, запущенному за пределами docker-compose.yml
или даже за пределами Compose
. Особенно полезно для контейнеров, предоставляющих общие или распределенные сервисы:
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
Обратите внимание: внешние контейнеры должны быть подключены хотя бы к одной сети, к которой подключен сервис.
image
Образ для контейнера. Может быть репозиторием/тегом или частичным идентификатором (partial identifier):
image: redis
image: node:16
image: example-registry.com:4000/postgresql
links
Подключение контейнера к другому сервису. Подключаемый сервис определяется с помощью названия сервиса и синонима ссылки (link alias) ("SERVICE:ALIAS"
) или только названия:
web:
links:
- "db"
- "db:database"
- "redis"
network_mode
Сетевой режим:
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
networks
Сети для подключения:
services:
some-service:
networks:
- some-network
- other-network
ports
Выставление портов.
Короткий синтаксис позволяет делать следующее:
- определять оба порта (
HOST:CONTAINER
); - определять только порт контейнера (для хоста выбирается эфемерный порт);
- определять
IP-адрес
хоста для привязки (bind) и оба порта (значением по умолчанию является0.0.0.0
, что означает все интерфейсы) (IPADDR:HOSTPORT:CONTAINERPORT
).
ports:
- "3000"
- "8000:8000"
- "9090-9091:8080-8081"
- "127.0.0.1:8001:8001"
- "127.0.0.1::5000"
- "6060:6060/udp"
Длинный синтаксис позволяет настраивать дополнительные поля:
target
: порт контейнера;published
: порт хоста (доступный публично);protocol
: протокол порта (tcp
илиudp
);mode
:host
|ingress
.
restart
Определение политики перезапуска. Значением по умолчанию является no
, что означает отключение автоматического перезапуска. always
означает перезапуск в любом случае. on-failure
означает перезапуск только в случае аварийной остановки контейнера. unless-stopped
означает перезапуск контейнера во всех случаев, кроме преднамеренной остановки:
restart: "no"
restart: always
restart: on-failure
restart: unless-stopped
volumes
Монтирование путей хоста (host paths) или именованных томов (named volumes), определенных в виде дополнительных настроек сервиса.
Пути хоста могут монтироваться как часть определения сервиса. Их не нужно указывать в ключе volume
на верхнем уровне.
Однако, если необходимо, чтобы тома использовались несколькими сервисами, они должны быть перечислены в таком volume
.
В следующем примере именованный том mydata
используется сервисом web
, для отдельного сервиса определяется bind mount
(первый путь в volumes
сервиса db
). db
также использует именованный том dbdata
(второй путь), но определяет его с помощью устаревшего строкового формата для монтирования именованных томов. Именованные тома указываются в ключе volume
верхнего уровня:
version: "3.9"
services:
web:
image: nginx:alpine
volumes:
- type: volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind
source: ./static
target: /opt/app/static
db:
image: postgres:latest
volumes:
- "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
- "dbdata:/var/lib/postgresql/data"
volumes:
mydata:
dbdata:
Короткий синтаксис
В этом случае используется формат [SOURCE:]TARGET[:MODE]
, где SOURCE
— это путь хоста или именованный том, TARGET
— это путь монтирования тома в контейнере и MODE
— ro
для доступа только для чтения и rw
для доступа для чтения и записи (дефолтное значение).
Для монтирования могут использоваться относительные пути (путь вычисляется, начиная с директории с файлом Compose
). Относительные пути должны начинаться с .
или ..
.
volumes:
# определяем только путь и делегируем создание тома движку
- /var/lib/mysql
# определяем связывание (mapping) абсолютных путей
- /opt/data:/var/lib/mysql
# путь хоста относительно директории с файлом `Compose`
- ./cache:/tmp/cache
# путь относительно пользователя
- ~/configs:/etc/configs/:ro
# именованный том
- datavolume:/var/lib/mysql
Длинный синтаксис
Длинный синтаксис позволяет настраивать дополнительные поля:
type
: тип монтирования —volume
,bind
,tmpfs
илиnpipe
;source
: источник монтирования, путь хоста дляbind mount
или название тома, определенное в верхнеуровневомvolumes
;target
: путь монтирования тома в контейнере;read_only
: индикатор доступности тома только для чтения;bind
: дополнительные настройки связывания:propagation
: режим распространения, используемый для связывания;
volume
: дополнительные настройки тома:nocopy
: индикатор запрета копирования данных тома.
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- type: volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind
source: ./static
target: /opt/app/static
networks:
webnet:
volumes:
mydata:
Другие настройки, соответствующие настройкам команды docker run
user: postgres
working_dir: /code
domainname: foo.com
hostname: foo
ipc: host
mac_address: 02:42:ac:11:65:43
privileged: true
read_only: true
shm_size: 64M
stdin_open: true
tty: true
Примеры определения продолжительности
2.5s
10s
1m30s
2h32m
5h34m56s
Примеры определения байтовых значений
2b
1024kb
2048k
300m
1gb
Замена переменных
Настройки могут содержать переменные среды окружения. Compose
использует значения переменных из терминала при выполнении команды docker compose
. Например, предположим, что терминал содержит POSTGRES_VERSION=9.3
и применяется такая настройка:
db:
image: "postgres:${POSTGRES_VERSION}"
При выполнении docker compose
значение переменной POSTGRES_VERSION
в настройках заменяется на 9.3
и мы получаем postgres:9.3
.
Если значение переменной не установлено, переменная в настройках заменяется пустой строкой и мы получаем postgres:
.
Дефолтные значения переменных могут быть установлены в файле .env
, который должен находиться в той же директории, что и файл Compose
. Значения переменных в терминале перезаписывают значения, определенные в .env
.
Поддерживается 2 варианта синтаксиса: $VAR
и ${VAR}
. Второй вариант предоставляет такие дополнительные возможности, как:
- определение значений по умолчанию:
${VAR:-default}
: оценивается какdefault
, когдаVAR
не установлена или является пустой;${VAR-default}
: оценивается какdefault
только когдаVAR
не установлена;
- определение обязательных значений:
${VAR:?error}
: ошибка возникает, еслиVAR
не установлена или является пустой;${VAR?error}
: ошибка возникает, только еслиVAR
не установлена.
Расширенные возможности интерполяции переменных типа ${VAR/foo/bar}
в настоящее время не поддерживаются.
Спецификация файла Compose
версии 3.
Это конец второй части.
Благодарю за внимание и happy coding!