learning:diamol
Различия
Показаны различия между двумя версиями страницы.
learning:diamol [18.05.2023 18:52] – [17. Оптимизация образов по размеру, скорости и безопасности] viacheslav | learning:diamol [30.07.2024 19:21] (текущий) – внешнее изменение 127.0.0.1 | ||
---|---|---|---|
Строка 1: | Строка 1: | ||
+ | ====== Docker in a month ====== | ||
+ | <code bash> | ||
+ | # Клон учебного репозитория | ||
+ | git clone https:// | ||
+ | # грохнуть все контейнеры, | ||
+ | docker container rm -f $(docker container ls -aq) | ||
+ | # грохнуть все образы diamol | ||
+ | docker image rm -f $(docker image ls -f reference=' | ||
+ | </ | ||
+ | ===== 2. Начало ===== | ||
+ | <code bash> | ||
+ | # Интерактивно зайти внутрь контейнера (-it - interactive, | ||
+ | docker container run -it diamol/base | ||
+ | |||
+ | # Список запущенных контейнеров. ID контейнера = его hostname | ||
+ | docker ps | ||
+ | CONTAINER ID | ||
+ | aa9454c9f05e | ||
+ | |||
+ | # Список процессов внутри контейнера (aa - начало ID контейнера) | ||
+ | docker container top aa | ||
+ | UID | ||
+ | root 3138 3112 0 | ||
+ | |||
+ | # Логи контейнера - здесь то, что было введено в командной строке интерактивно | ||
+ | docker logs 9c | ||
+ | / # hostname | ||
+ | 9cfdb08bc3c8 | ||
+ | / # date | ||
+ | Mon Apr 10 11:34:58 UTC 2023 | ||
+ | |||
+ | # Подробная информация о контейнере | ||
+ | docker container inspect 9c | ||
+ | [ | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | ... | ||
+ | |||
+ | # Список работающих контейнеров | ||
+ | docker ps # или | ||
+ | docker container ls | ||
+ | # Список всех контейнеров | ||
+ | docker ps -a # или | ||
+ | docker container ls -a | ||
+ | |||
+ | # Контейнеры не исчезают после того, как они выполнили задачу и остановились. Можно снова их запустить, | ||
+ | # Докер не удаляет контейнеры, | ||
+ | # Запуск контейнера с постоянно работающим процессом в фоне ('' | ||
+ | docker run -d -p 8088:80 diamol/ | ||
+ | |||
+ | # Потребление контейнером CPU/ | ||
+ | docker container stats 9c | ||
+ | |||
+ | # Удалить контейнер (--force, -f для запущенных контейнеров) | ||
+ | docker container rm -f 9c | ||
+ | # Удалить все контейнеры | ||
+ | docker container rm --force $(docker container ls --all --quiet) | ||
+ | </ | ||
+ | |||
+ | ===== 3. Создание собственных образов ===== | ||
+ | <code bash> | ||
+ | # Скачать образ из реестра по умолчанию (default registry) | ||
+ | docker image pull diamol/ | ||
+ | Using default tag: latest | ||
+ | latest: Pulling from diamol/ | ||
+ | e7c96db7181b: | ||
+ | bbec46749066: | ||
+ | 89e5cf82282d: | ||
+ | 5de6895db72f: | ||
+ | f5cca017994f: | ||
+ | 78b9b6c949f8: | ||
+ | Digest: sha256: | ||
+ | Status: Downloaded newer image for diamol/ | ||
+ | docker.io/ | ||
+ | |||
+ | # Запустить, | ||
+ | docker run -d --name web-ping diamol/ | ||
+ | |||
+ | # В логах: | ||
+ | docker container logs web-ping |less | ||
+ | ** web-ping ** Pinging: blog.sixeyed.com; | ||
+ | Making request number: 1; at 1681130396655 | ||
+ | Got response status: 200 at 1681130397047; | ||
+ | Making request number: 2; at 1681130399657 | ||
+ | Got response status: 200 at 1681130400349; | ||
+ | </ | ||
+ | |||
+ | У контейнера могут быть некие стандартные значения параметров, | ||
+ | У контейнера свои переменные, | ||
+ | <code bash> | ||
+ | # Запуск с переменной окружения, | ||
+ | docker run --name web-ping --env TARGET=bva.dyndns.info diamol/ | ||
+ | ** web-ping ** Pinging: bva.dyndns.info; | ||
+ | Making request number: 1; at 1681131611538 | ||
+ | Got response status: 200 at 1681131612215; | ||
+ | Making request number: 2; at 1681131614540 | ||
+ | Got response status: 200 at 1681131615097; | ||
+ | </ | ||
+ | |||
+ | ==== Dockerfile ==== | ||
+ | Dockerfile - это скрипт для запаковки приложения в контейнер. | ||
+ | <code bash> | ||
+ | # Базовый образ | ||
+ | FROM diamol/node | ||
+ | |||
+ | # Значения по умолчанию для переменных окружения | ||
+ | ENV TARGET=" | ||
+ | ENV METHOD=" | ||
+ | ENV INTERVAL=" | ||
+ | |||
+ | # Рабочий каталог - если его нет, то он создаётся | ||
+ | WORKDIR /web-ping | ||
+ | # Копирование файлов с хоста внутрь образа, | ||
+ | COPY app.js . | ||
+ | |||
+ | # Команда, | ||
+ | CMD [" | ||
+ | </ | ||
+ | |||
+ | Сборка и использование образа | ||
+ | <code bash> | ||
+ | # --tag - имя образа, | ||
+ | docker image build --tag web-ping . | ||
+ | |||
+ | # вывести список образов на w | ||
+ | docker image ls ' | ||
+ | REPOSITORY | ||
+ | web-ping | ||
+ | |||
+ | # Запустить собранный образ | ||
+ | docker container run -e TARGET=bva.dyndns.info -e INTERVAL=5000 web-ping | ||
+ | ** web-ping ** Pinging: bva.dyndns.info; | ||
+ | Making request number: 1; at 1681137797563 | ||
+ | Got response status: 200 at 1681137798217; | ||
+ | Making request number: 2; at 1681137802567 | ||
+ | Got response status: 200 at 1681137803136; | ||
+ | </ | ||
+ | ==== Слои ==== | ||
+ | <code bash> | ||
+ | # История создания образа послойно | ||
+ | docker image history web-ping | ||
+ | # В списке образов можно увидеть их размер | ||
+ | docker image ls | ||
+ | </ | ||
+ | Образ - это коллеция слоёв. Слои - это файлы, хранимые в кэше Докера. Слои могут быть общими у разных образов и контейнеров: | ||
+ | |||
+ | Размер в списке образов - логический, | ||
+ | <code bash> | ||
+ | # Сколько всего места занимает Докер (реальный размер) | ||
+ | docker system df | ||
+ | TYPE TOTAL | ||
+ | Images | ||
+ | Containers | ||
+ | Local Volumes | ||
+ | Build Cache | ||
+ | </ | ||
+ | |||
+ | Если слой используется в нескольких образах/ | ||
+ | |||
+ | ==== Оптимизация Докерфайлов через использование кэша слоёв ==== | ||
+ | В примере с web-ping есть файл приложения. Если его изменить и пересоздать образ, то получится новый слой. Так как слои идут последовательно, | ||
+ | <code bash> | ||
+ | docker image build -t web-ping:v2 . | ||
+ | </ | ||
+ | Для каждого слоя генерится контрольная сумма, если она не меняется - Докер берёт слой из кэша, если меняется - собирается новый слой и все последующие, | ||
+ | |||
+ | В примере выше можно оптимизировать Докерфайл, | ||
+ | <code bash> | ||
+ | FROM diamol/node | ||
+ | |||
+ | CMD [" | ||
+ | |||
+ | ENV TARGET=" | ||
+ | METHOD=" | ||
+ | INTERVAL=" | ||
+ | |||
+ | WORKDIR /web-ping | ||
+ | |||
+ | COPY app.js . | ||
+ | </ | ||
+ | |||
+ | Создание образа без использования Dockerfile, пример. | ||
+ | <code bash> | ||
+ | # Запуск контейнера с именем ch03-lab из образа diamol/ | ||
+ | docker container run -it --name ch03-lab diamol/ | ||
+ | # Изменение файла ch03.txt | ||
+ | echo " | ||
+ | # Выход из контейнера, | ||
+ | exit | ||
+ | # Сделать образ ch03-lab-image-new из контейнера ch03-lab | ||
+ | docker container commit ch03-lab ch03-lab-image-new | ||
+ | # Создать контейнер из образа ch03-lab-image-new и вывести содержимое файла ch03.txt | ||
+ | docker container run ch03-lab-image-new cat ch03.txt | ||
+ | </ | ||
+ | |||
+ | ===== 4. Из исходного кода - в образ ===== | ||
+ | Внутри докерфайла можно запускать команды. Команды выполняются во время сборки, | ||
+ | |||
+ | ==== multi-stage ==== | ||
+ | Это " | ||
+ | <code bash> | ||
+ | # AS - название этапа (необязательно) | ||
+ | FROM diamol/base AS build-stage | ||
+ | RUN echo ' | ||
+ | |||
+ | FROM diamol/base AS test-stage | ||
+ | # COPY с аргументом --from предписывает копировать файлы с предыдущего этапа, а не с хоста | ||
+ | COPY --from=build-stage /build.txt /build.txt | ||
+ | # Запись файла, вывод сохраняется как слой. Вызываемые команды должны присутствовать в образе из FROM | ||
+ | RUN echo ' | ||
+ | |||
+ | FROM diamol/base | ||
+ | COPY --from=test-stage /build.txt /build.txt | ||
+ | CMD cat /build.txt | ||
+ | </ | ||
+ | Что тут происходит: | ||
+ | |||
+ | Каждый этап изолирован. Можно использовать разные базовые образы с разным набором инструментов и запускать какие угодно команды. Если на каком-то этапе возникает ошибка, | ||
+ | |||
+ | ==== Java app with Maven ==== | ||
+ | |||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | |||
+ | WORKDIR / | ||
+ | COPY pom.xml . | ||
+ | RUN mvn -B dependency: | ||
+ | |||
+ | COPY . . | ||
+ | RUN mvn package | ||
+ | |||
+ | # app | ||
+ | FROM diamol/ | ||
+ | |||
+ | WORKDIR /app | ||
+ | COPY --from=builder / | ||
+ | |||
+ | EXPOSE 80 | ||
+ | ENTRYPOINT [" | ||
+ | </ | ||
+ | Здесь первый этап (builder) использует образ diamol/ | ||
+ | |||
+ | Во время сборки будет обширный вывод, и одна из строк будет | ||
+ | <code bash> | ||
+ | Step 9/11 : COPY --from=builder / | ||
+ | </ | ||
+ | , что свидетельствует о копировании готового файла из этапа сборки. | ||
+ | |||
+ | Контейнеры получают доступ друг к другу через виртуальную сеть по виртуальным IP-адресам, | ||
+ | <code bash> | ||
+ | # Создать сеть | ||
+ | docker network create nat | ||
+ | </ | ||
+ | Теперь можно при запуске контейнера указывать, | ||
+ | <code bash> | ||
+ | docker container run --name iotd -d -p 800:80 --network nat image-of-the-day | ||
+ | </ | ||
+ | Ещё раз - при таком подходе необходим Докер, не нужно заморачиваться со всем остальным. Ещё нужно обратить внимание, | ||
+ | |||
+ | ==== node.js ==== | ||
+ | Это другой подход к сборке, | ||
+ | <code bash> | ||
+ | FROM diamol/node AS builder | ||
+ | |||
+ | WORKDIR /src | ||
+ | COPY src/ | ||
+ | |||
+ | RUN npm install | ||
+ | |||
+ | # app | ||
+ | FROM diamol/node | ||
+ | |||
+ | EXPOSE 80 | ||
+ | CMD [" | ||
+ | |||
+ | WORKDIR /app | ||
+ | COPY --from=builder / | ||
+ | COPY src/ . | ||
+ | </ | ||
+ | Выполнить: | ||
+ | <code bash> | ||
+ | cd ch04/ | ||
+ | docker image build -t access-log . | ||
+ | docker container run --name accesslog -d -p 801:80 --network nat access-log | ||
+ | </ | ||
+ | |||
+ | ==== Go ==== | ||
+ | Go компилируется в исполняемый бинарник, | ||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | |||
+ | COPY main.go . | ||
+ | RUN go build -o /server | ||
+ | RUN chmod +x /server | ||
+ | |||
+ | # app | ||
+ | FROM diamol/base | ||
+ | |||
+ | EXPOSE 80 | ||
+ | ENV IMAGE_API_URL=" | ||
+ | ACCESS_API_URL=" | ||
+ | CMD ["/ | ||
+ | |||
+ | WORKDIR web | ||
+ | COPY --from=builder /server . | ||
+ | COPY index.html . | ||
+ | </ | ||
+ | |||
+ | <code bash> | ||
+ | cd ch04/ | ||
+ | docker image build -t image-gallery . | ||
+ | docker container run -d -p 802:80 --network nat image-gallery | ||
+ | </ | ||
+ | |||
+ | Если посмотреть на размеры образов, | ||
+ | <code bash> | ||
+ | docker image ls -f reference=diamol/ | ||
+ | REPOSITORY | ||
+ | image-gallery | ||
+ | diamol/ | ||
+ | </ | ||
+ | Поэтапный докерфайл делает проект полностью портативным. Вне зависимости от того, какой CI-сервис используется - Jenkins или какой-то облачный - везде это будет работать одинаково. | ||
+ | |||
+ | Итак, ключевые особенности такого подхода: | ||
+ | - Стандартизация, | ||
+ | - Производительность: | ||
+ | - Управляемость: | ||
+ | |||
+ | ===== 5. Доступ к образам: | ||
+ | От сборки и запуска образов переходим к выкладыванию их в общий доступ. По умолчанию в Докере прописан реестр Dockerhub. Формат пути к образу: | ||
+ | <code bash> | ||
+ | # docker.io - реестр | ||
+ | # diamol - аккаунт в реестре | ||
+ | # golang - репозиторий (имя образа) | ||
+ | # v2 - Тэг (по умолчанию - latest) | ||
+ | docker.io/ | ||
+ | </ | ||
+ | Тэг - самая важная часть, которая используется для различения версий/ | ||
+ | <code bash> | ||
+ | openjdk:13 # свежий релиз | ||
+ | openjdk: | ||
+ | openjdl: | ||
+ | </ | ||
+ | Если при сборке не указать тэг, то он будет latest. Тем не менее, это может не отражать сути - latest может быть не самой свежей версией. Поэтому при загрузке образа в реестр тэг нужно всегда указывать. | ||
+ | ==== Загрузка образа в реестр ==== | ||
+ | <code bash> | ||
+ | dockerId=" | ||
+ | # вход в реестр | ||
+ | docker login --username $dockerId | ||
+ | # Установить метку (reference) на образ | ||
+ | # Если потом вывести список образов, | ||
+ | docker image tag image-gallery $dockerId/ | ||
+ | # Загрузить образ в реестр | ||
+ | docker image push $dockerId/ | ||
+ | </ | ||
+ | Загрузка образа происходит послойно, | ||
+ | |||
+ | ==== Собственный реестр ==== | ||
+ | У Докера есть [[https:// | ||
+ | <code bash> | ||
+ | # --restart=always для того, чтобы контейнер запускался после перезагрузки самого Докера, | ||
+ | docker run -d -p 5000:5000 --restart=always -e REGISTRY_STORAGE_DELETE_ENABLED=true --name registry registry:2 | ||
+ | |||
+ | # Прописать псевдоним registry.logal для наглядности | ||
+ | echo $' | ||
+ | |||
+ | # Пометить образы | ||
+ | docker image tag lab4:v2 registry.local: | ||
+ | docker image tag image-gallery registry.local: | ||
+ | docker image tag access-log registry.local: | ||
+ | docker image tag image-of-the-day: | ||
+ | # Загрузить образы | ||
+ | docker image push registry.local: | ||
+ | docker image push registry.local: | ||
+ | docker image push registry.local: | ||
+ | docker image push registry.local: | ||
+ | |||
+ | # Если тэгов у образа несколько, | ||
+ | docker image push -a registry.local: | ||
+ | </ | ||
+ | https:// | ||
+ | |||
+ | Факультативно может понадобиться прописать этот реестр в конфигурацию Докера, | ||
+ | <file json / | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Проверить - '' | ||
+ | Список образов: | ||
+ | |||
+ | ==== Эффективный выбор тэгов ==== | ||
+ | Основная идея - использовать '' | ||
+ | * major - новые функции | ||
+ | * minor - добавление, | ||
+ | * patch - исправление ошибок | ||
+ | |||
+ | Это даёт пользователю возможность выбора, | ||
+ | <code bash> | ||
+ | registry.local: | ||
+ | registry.local: | ||
+ | registry.local: | ||
+ | registry.local: | ||
+ | </ | ||
+ | |||
+ | ==== " | ||
+ | Проблема доверия к образам, | ||
+ | * Проверенных издателей - компании вроде Microsoft, IBM и т. п., которым присваивается такой статус. | ||
+ | * Официальных образов - обычно опенсорс-образы, | ||
+ | |||
+ | " | ||
+ | <code bash> | ||
+ | FROM mcr.microsoft.com/ | ||
+ | |||
+ | LABEL framework=" | ||
+ | LABEL version=" | ||
+ | LABEL description=" | ||
+ | LABEL owner=" | ||
+ | |||
+ | WORKDIR src | ||
+ | COPY global.json . | ||
+ | </ | ||
+ | Сборка золотых образов: | ||
+ | <code bash> | ||
+ | docker image build -t golden/ | ||
+ | docker image build -t golden/ | ||
+ | </ | ||
+ | |||
+ | Пример сборки с использованием золотого образа: | ||
+ | <code bash> | ||
+ | FROM golden/ | ||
+ | COPY . . | ||
+ | RUN dotnet publish -o /out/app app.csproj | ||
+ | |||
+ | FROM golden/ | ||
+ | COPY --from=builder /out /app | ||
+ | CMD [" | ||
+ | </ | ||
+ | Здесь используется обычная поэтапная сборка, | ||
+ | |||
+ | ==== HTTP API v2 ==== | ||
+ | <code bash> | ||
+ | # Список репозиториев | ||
+ | http:// | ||
+ | # Вывести тэги репозитория gallery/ui | ||
+ | http:// | ||
+ | # Манифест для gallery/ | ||
+ | http:// | ||
+ | # Дайджест для gallery/ | ||
+ | curl -s --head http:// | ||
+ | # Удалить | ||
+ | registry=' | ||
+ | repo=' | ||
+ | tag=' | ||
+ | digest=`curl -s --head $registry/ | ||
+ | curl -X DELETE $registry/ | ||
+ | </ | ||
+ | https:// | ||
+ | <WRAP round tip 60%> | ||
+ | <code bash> | ||
+ | curl -X DELETE $registry/ | ||
+ | curl: (3) URL using bad/illegal format or missing URL | ||
+ | </ | ||
+ | You get a \r (carriage return). You can remove it with '' | ||
+ | https:// | ||
+ | </ | ||
+ | |||
+ | ===== 6. Постоянное хранение данных - тома (volumes) и точки монтирования (mounts) ===== | ||
+ | Каждый контейнер имеет свою изолированную файловую систему, | ||
+ | <code bash> | ||
+ | docker container cp < | ||
+ | </ | ||
+ | Файловая система контейнера состоит из слоёв, которые доступны только на чтение, | ||
+ | |||
+ | Если требуется сохранять данные и после удаления контейнера, | ||
+ | ==== Тома ==== | ||
+ | Можно вручную создавать тома и подключать их к контейнерам, | ||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | WORKDIR /app | ||
+ | ENTRYPOINT [" | ||
+ | |||
+ | VOLUME /data | ||
+ | COPY --from=builder /out/ . | ||
+ | </ | ||
+ | |||
+ | Приложение to-do, которое будет хранить свои данные на томе | ||
+ | <code bash> | ||
+ | docker container run --name todo1 -d -p 8010:80 diamol/ | ||
+ | # Показать ID тома, путь на хосте и путь в контейнере | ||
+ | docker container inspect --format ' | ||
+ | [{volume 48d9319e1601b951e8f578b77bf03b38f414119bd0509d16fa7b31eab8565433 / | ||
+ | # список томов | ||
+ | docker volume ls | ||
+ | </ | ||
+ | Тома, которые прописаны в образах, | ||
+ | <code bash> | ||
+ | # подключить контейнеру app2 том app1 | ||
+ | docker container run -d --name t3 --volumes-from todo1 diamol/ | ||
+ | # проверить каталог, | ||
+ | docker exec t3 ls /data | ||
+ | </ | ||
+ | Тем не менее, просто так цеплять нескольким контейнерам один том чаще всего плохая идея, т. к. данные могут быть повреждены, | ||
+ | <code bash> | ||
+ | target='/ | ||
+ | docker volume create todo-list | ||
+ | docker container run -d -p 8011:80 -v todo-list: | ||
+ | # ... добавляется какая-то информация в http:// | ||
+ | # удаление контейнера | ||
+ | docker container rm -f todo-v1 | ||
+ | # создание нового с прикручиванием старого тома | ||
+ | docker container run -d -p 8011:80 -v todo-list: | ||
+ | </ | ||
+ | Команда VOLUME в докерфайле и '' | ||
+ | |||
+ | Команда '' | ||
+ | |||
+ | |||
+ | ==== Точки монтирования ==== | ||
+ | Bind mounts - прямое использование файловой системы хоста: на нём создаётся каталог, | ||
+ | <code bash> | ||
+ | source=" | ||
+ | mkdir $source | ||
+ | docker container run --mount type=bind, | ||
+ | curl http:// | ||
+ | ls $source # проверить наличие файла | ||
+ | </ | ||
+ | |||
+ | Bind mount двунаправленный - можно создавать файлы в контейнере и редактировать их на хосте и наоборот. Так как контейнеры должны запускаться от непривилегированной учётки, | ||
+ | Если писать файлы не нужно, можно смонтировать каталог на хосте только для чтения контейнером - это один из вариантов брать конфигурацию для контейнера с хоста без переделывания образа. | ||
+ | <code bash> | ||
+ | docker run --name todo-configured --mount type=bind, | ||
+ | </ | ||
+ | В общем, монтировать можно всё, к чему имеет доступ хост - это могут быть отказоустойчивые или распределённые хранилища, | ||
+ | - Что, если целевой каталог контейнера (target) уже существует и там уже есть файлы? При монтировании source полностью заменяет target - файлы, которые там были, будут недоступны.< | ||
+ | - Что если монтируется один файл в существующий каталог в контейнере? | ||
+ | - При использовании распределённых хранилищ (SMB, Azure, S3 и т. д.) надо быть готовым к тому, что система может не заработать, | ||
+ | |||
+ | ==== Строение файловой системы контейнера ==== | ||
+ | Каждый контейнер содержит виртуальный диск, собранный из нескольких источников, | ||
+ | |||
+ | * Слой записи - хранит любые изменения за время существования контейнера. Удаляется вместе с контейнером. | ||
+ | * Локальные точки монтирования - общий доступ между хостом и контейнером. Удобен тем, что можно редактировать файлы на хосте и изменения будут сразу отражаться в контейнере без необходимости пересборки образа. | ||
+ | * Распределённые точки монтирования - общий доступ между сетевым хранилищем и контейнером. Полезная опция, но имеет свои недостатки, | ||
+ | * Тома - общие данные между контейнером и объектом хранения под управлением Докера. Используются для постоянного хранения данных приложения. При обновлении приложения данные берутся с этого тома и используются в дальнейшем. | ||
+ | * Слои образа - базовая файловая система контейнера, | ||
+ | |||
+ | Пример с точкой монтирования, | ||
+ | <code bash> | ||
+ | docker volume create todo6 | ||
+ | docker run --name todo6 -dp 8003:80 -v todo6:/ | ||
+ | --mount type=bind, | ||
+ | diamol/ | ||
+ | </ | ||
+ | ===== 7. Multi-container apps (Docker Compose) ===== | ||
+ | Возможность указать желаемую конфигурацию в едином файле. | ||
+ | |||
+ | Пример секции services в docker-compose.yml: | ||
+ | <code yaml> | ||
+ | accesslog: | ||
+ | image: diamol/ | ||
+ | |||
+ | iotd: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - " | ||
+ | |||
+ | image-gallery: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - " | ||
+ | depends_on: | ||
+ | - accesslog | ||
+ | - iotd | ||
+ | </ | ||
+ | iotd - публикуется порт 80 контейнера на любой порт хоста, а image-gallery запустится только после accesslog и iotd. | ||
+ | |||
+ | Размножить iotd до 3 экз., просмотреть логи, где при обновлении страниц в браузере видно, что обращения идут на разные экземпляры контейнеров iotd. | ||
+ | <code bash> | ||
+ | docker-compose up -d --scale iotd=3 | ||
+ | # browse to http:// | ||
+ | docker-compose logs --tail=1 iotd # показать последнюю запись из каждого экземпляра iotd | ||
+ | </ | ||
+ | |||
+ | Остановить, | ||
+ | <code bash> | ||
+ | docker-compose stop # Показываются контейнеры | ||
+ | Stopping image-of-the-day_iotd_3 | ||
+ | Stopping image-of-the-day_iotd_2 | ||
+ | Stopping image-of-the-day_image-gallery_1 ... done | ||
+ | Stopping image-of-the-day_accesslog_1 | ||
+ | Stopping image-of-the-day_iotd_1 | ||
+ | docker-compose start # Показываются сервисы (запускаются в заданном порядке) | ||
+ | Starting accesslog | ||
+ | Starting iotd ... done | ||
+ | Starting image-gallery ... done | ||
+ | docker container ls # Если остановленные контейнеры существуют, | ||
+ | CONTAINER ID | ||
+ | 732d36e8af7d | ||
+ | 81b762117f5a | ||
+ | abce83ac3a53 | ||
+ | 01cff95179c8 | ||
+ | c523d48d79de | ||
+ | |||
+ | |||
+ | |||
+ | </ | ||
+ | |||
+ | В примере выше, когда контейнер запускался в 3-х экз., это не было отражено в самом файле docker-compose, | ||
+ | <code bash> | ||
+ | # down - удалить приложение, | ||
+ | docker-compose down | ||
+ | docker-compose up -d | ||
+ | docker container ls | ||
+ | </ | ||
+ | |||
+ | ==== Как контейнеры взаимодействуют ==== | ||
+ | В докере есть свой DNS, и контейнеры в пределах виртуальной сети видят друг друга по имени. Если контейнер запрашивает имя, которого не существует в виртуальной сети, Докер запрашивает внешний DNS. Это можно проверить, | ||
+ | |||
+ | Если грохнуть контейнер через Docker CLI мимо docker-compose, | ||
+ | <code bash> | ||
+ | docker rm -f image-of-the-day_accesslog_1 | ||
+ | image-of-the-day_accesslog_1 | ||
+ | |||
+ | docker-compose up -d --scale iotd=3 | ||
+ | image-of-the-day_iotd_1 is up-to-date | ||
+ | image-of-the-day_iotd_2 is up-to-date | ||
+ | image-of-the-day_iotd_3 is up-to-date | ||
+ | Creating image-of-the-day_accesslog_1 ... done | ||
+ | image-of-the-day_image-gallery_1 is up-to-date | ||
+ | </ | ||
+ | ==== Пример конфигурации docker-compose ==== | ||
+ | <code yaml> | ||
+ | services: | ||
+ | |||
+ | todo-db: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - " | ||
+ | networks: | ||
+ | - app-net | ||
+ | |||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - " | ||
+ | environment: | ||
+ | - Database: | ||
+ | depends_on: | ||
+ | - todo-db | ||
+ | networks: | ||
+ | - app-net | ||
+ | secrets: | ||
+ | - source: postgres-connection | ||
+ | target: / | ||
+ | </ | ||
+ | '' | ||
+ | |||
+ | Secrets обычно нужны при кластеризации типа Docker Swarm или K8s. Они хранятся в БД кластера и могут быть зашифрованы, | ||
+ | <code yaml> | ||
+ | secrets: | ||
+ | postgres-connection: | ||
+ | file: ./ | ||
+ | </ | ||
+ | В какой-то степени чтение секретов из файлов напоминает bind mounts, потому что файлы хоста используются в контейнере. Тем не менее, использование опции секрета позволяет впоследствии легче мигрировать с кластер. | ||
+ | |||
+ | Файлы docker-compose.yml делают проще настройку для нескольких окружений, | ||
+ | |||
+ | <code bash> | ||
+ | # Показать только контейнеры, | ||
+ | docker-compose ps | ||
+ | </ | ||
+ | Разделение упаковки приложения и настроек - ключевая функция Докера. Приложение может быть собрано через CI-пайплайн, | ||
+ | |||
+ | |||
+ | ==== Какие проблемы решает docker-compose ==== | ||
+ | Исчезает разница между документацией и конфигурацией. Файл docker-compose.yml фактически является описанием конфигурации системы, | ||
+ | |||
+ | Позволяет создавать мультиконтейнерные сервисы в рамках одного хоста. Тем не менее, он не следит за состоянием контейнеров, | ||
+ | |||
+ | ===== 8. Проверка здоровья и зависимостей ===== | ||
+ | В продуктивной среде используется Swarm или K8s, где есть функционал сабжа. При создании контейнеров нужно указать информацию, | ||
+ | ==== Проверки здоровья в образах ==== | ||
+ | Докер на базовом уровне проверяет каждый контейнер при запуске. Контейнер запускает какой-либо свой основной процесс, | ||
+ | |||
+ | Для того, чтобы Докер был в курсе, в Dockerfile есть команда HEALTHCHECK, | ||
+ | <code bash> | ||
+ | # --fail - код возврата, | ||
+ | HEALTHCHECK --interval=10s \ | ||
+ | CMD curl --fail http:// | ||
+ | </ | ||
+ | '' | ||
+ | |||
+ | Если вывести простыню о состоянии контейнера, | ||
+ | <code bash> | ||
+ | docker container inspect $(docker container ls --last 1 --format ' | ||
+ | </ | ||
+ | Теперь управляющий софт будет в курсе проблемы и сможет предпринять соответствующие действия. На одиночном сервере, | ||
+ | |||
+ | ==== Запуск с проверкой зависимостей ==== | ||
+ | В отличие от docker-compose, | ||
+ | |||
+ | В Докерфайле: | ||
+ | <code bash> | ||
+ | # Если API отвечает по ссылке, | ||
+ | CMD curl --fail http:// | ||
+ | dotnet Numbers.Web.dll | ||
+ | </ | ||
+ | |||
+ | ==== Написание собственных инструментов для проверки ==== | ||
+ | Curl - хорошая вещь для проверок, | ||
+ | |||
+ | Ещё одно преимущество написания собственных проверок - образ становится портативным, | ||
+ | |||
+ | ==== Проверки здоровья и зависимостей в Docker Compose ==== | ||
+ | В Docker Compose у healthcheck есть тонкие настройки (в данном случае используется проверка, | ||
+ | <code yaml> | ||
+ | numbers-api: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - " | ||
+ | healthcheck: | ||
+ | interval: 5s # интервал между проверками | ||
+ | timeout: 1s # время ожидания выполнения проверки (после истечения проверка считается неуспешной) | ||
+ | retries: 2 # кол-во неуспешных проверок для признания контейнера нездоровым | ||
+ | start_period: | ||
+ | networks: | ||
+ | - app-net | ||
+ | </ | ||
+ | Эти опции регулируются в зависимости от ситуации. Необходимо также учитывать, | ||
+ | |||
+ | Если проверка не строена в образ, можно задать её в '' | ||
+ | <code yaml> | ||
+ | healthcheck: | ||
+ | test: [" | ||
+ | interval: 5s | ||
+ | timeout: 1s | ||
+ | retries: 2 | ||
+ | start_period: | ||
+ | # тут по желанию можно добавить автоперезапуск | ||
+ | restart: on-failure | ||
+ | # и зависимости | ||
+ | depends_on: [serviceName] | ||
+ | </ | ||
+ | Если не задать зависимости, | ||
+ | |||
+ | Зачем указывать зависимости внутри образа, | ||
+ | |||
+ | ==== От проверок к самовосстановлению ==== | ||
+ | Запуск приложения как распределённой системы из мелких компонентов повышает гибкость и быстродействие, | ||
+ | |||
+ | Здесь как раз могут помочь проверки здоровья и зависимостей. Лучше дать платформе запускать контейнеры как ей нужно - максимум, | ||
+ | |||
+ | С проверками не надо переусердствовать.\\ | ||
+ | Проверки здоровья запускаются периодически, | ||
+ | Проверки зависимостей запускаются только во время старта контейнера и экономия вычислительных ресурсов здесь неактуальна, | ||
+ | |||
+ | ===== 9. Мониторинг ===== | ||
+ | Контроль и возможность обзора работы приложений очень важен, без него нельзя переводить приложение в продуктивную среду. Ниже будут рассмотрены Prometheus (сборщик метрик) и Grafana (визуализация), | ||
+ | ==== Структура мониторинга для контейнерных приложений ==== | ||
+ | Традиционный мониторинг обычно подразумевает какую-то панель с отображением списка серверов и их дисковое пространство, | ||
+ | |||
+ | Прометей предоставляет единый подход к мониторингу различных приложений - одни и те же типы метрик подходят для приложений, | ||
+ | <code bash> | ||
+ | sudo nano / | ||
+ | # добавить перед последней закрывающей } | ||
+ | # если файла нет, то нужно его создать | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | # Потом нужно дать права группе docker на этот файл, если он был создан | ||
+ | sudo chown user:docker / | ||
+ | # перезапустить Докер | ||
+ | sudo systemctl restart docker | ||
+ | </ | ||
+ | После этого метрики будут доступны по адресу http:// | ||
+ | |||
+ | После этого можно ставить самого Прометея | ||
+ | <code bash> | ||
+ | # так в книжке, | ||
+ | # здесь переменная указывает на хост, чтобы брать с него метрики | ||
+ | docker run -e DOCKER_HOST=192.168.1.10 -dp 9090:9090 diamol/ | ||
+ | </ | ||
+ | https:// | ||
+ | |||
+ | После выполнения запуска Прометея веб-интерфейс доступен по адресу http:// | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Докер выдаёт кучу метрик, | ||
+ | |||
+ | ==== Метрики приложения ==== | ||
+ | У Прометея есть клиентские библиотеки для всех основных языков программирования, | ||
+ | |||
+ | Помимо метрик самого Докера и метрик окружения (runtime), должны быть ещё и метрики самого приложения, | ||
+ | |||
+ | У Прометея есть несколько типов метрик, | ||
+ | |||
+ | Полезно мониторить в метриках: | ||
+ | * При обращении ко внешним системам - как долго длится запрос и успешен ли ответ. Будет видно, тормозит ли внешняя система и не нарушает ли она работу вашей системы. | ||
+ | * Все те события, | ||
+ | * Подробности о приложении и поведении пользователей - то, о чём бизнес желает получать отчёты. Метрики дадут картину в реальном времени, | ||
+ | |||
+ | ==== Запуск Прометея в контейнере для сбора метрик ==== | ||
+ | Прометей использует pull-сбор, | ||
+ | |||
+ | Вот пример конфигурации, | ||
+ | <code yaml> | ||
+ | # Общая настройка - 10 сек между проверками | ||
+ | global: | ||
+ | scrape_interval: | ||
+ | |||
+ | # job для каждого компонента | ||
+ | scrape_configs: | ||
+ | - job_name: " | ||
+ | metrics_path: | ||
+ | static_configs: | ||
+ | - targets: [" | ||
+ | | ||
+ | - job_name: " | ||
+ | metrics_path: | ||
+ | static_configs: | ||
+ | - targets: [" | ||
+ | |||
+ | - job_name: " | ||
+ | metrics_path: | ||
+ | # использовать Докер-DNS для автопоиска | ||
+ | dns_sd_configs: | ||
+ | - names: | ||
+ | - accesslog | ||
+ | type: A | ||
+ | port: 80 | ||
+ | </ | ||
+ | Здесь контейнеры будут обнаруживаться автоматически, | ||
+ | |||
+ | Одна из самых мощных функций Прометея - запись расширенной информации в метриках, | ||
+ | < | ||
+ | access_log_total{hostname=" | ||
+ | access_log_total{hostname=" | ||
+ | access_log_total{hostname=" | ||
+ | </ | ||
+ | Но можно получить общее число, выполнив суммирование и фильтр ярлыков (labels): | ||
+ | <code bash> | ||
+ | sum(access_log_total) without(hostname, | ||
+ | |||
+ | {job=" | ||
+ | # На соседней вкладке graph график тоже будет суммированным | ||
+ | </ | ||
+ | Запрос //sum()// написан на прометеевском языке //PromQL.// Это мощный язык запросов, | ||
+ | |||
+ | Интерфейс Прометея хорош для проверки конфигурации, | ||
+ | |||
+ | ==== Визуализация метрик с помощью Grafana ==== | ||
+ | Гугл в книжке [[https:// | ||
+ | |||
+ | Пример настройки панели: | ||
+ | |||
+ | * HTTP 200 Responses - сколько запросов успешно обработано.< | ||
+ | sum(image_gallery_requests_total{code=" | ||
+ | * In-flight requests - число одновременных запросов, | ||
+ | sum(image_gallery_in_flight_requests) without(instance)</ | ||
+ | * Memory in use - сколько памяти съело приложение. Это хороший индикатор насыщенности.< | ||
+ | * Active Goroutines - сколько запущено Goroutines. Показывает активность Go-приложения, | ||
+ | |||
+ | Остальные показатели используют примерно такие же несложные запросы. Не нужно сильно заморачиваться с PromQL - достаточно выбирать правильные метрики и подходящее их отображение. | ||
+ | |||
+ | В мониторинге актуальные значения менее ценны, чем отображение тенденций (трендов), | ||
+ | |||
+ | Добавить панель в Графану можно с помощью кнопки с плюсиком вверху. В том же ряду есть кнопка Share dashboard, и там есть складка Export, где выгружается json, с помощью которого можно сделать свой образ, в котором сразу будет всё настроено, | ||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | |||
+ | COPY datasource-prometheus.yaml ${GF_PATHS_PROVISIONING}/ | ||
+ | COPY dashboard-provider.yaml ${GF_PATHS_PROVISIONING}/ | ||
+ | COPY dashboard.json / | ||
+ | </ | ||
+ | Таким же образом можно добавлять и пользователей, | ||
+ | |||
+ | ==== Уровни наблюдения ==== | ||
+ | Обозримость (observability) - важнейшее требования для продуктивной системы. Необходимо несколько панелей наблюдения - инфраструктурная (загрузка серверов - проц, память, | ||
+ | |||
+ | ==== Лабораторная ==== | ||
+ | Надо написать конфиг для Прометея и собрать образ: | ||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | <file yaml prometheus.yml> | ||
+ | global: | ||
+ | scrape_interval: | ||
+ | |||
+ | scrape_configs: | ||
+ | - job_name: ' | ||
+ | metrics_path: | ||
+ | static_configs: | ||
+ | - targets: [' | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | <file bash Dockerfile> | ||
+ | FROM diamol/ | ||
+ | COPY prometheus.yml / | ||
+ | </ | ||
+ | <code bash> | ||
+ | # Сборка | ||
+ | docker image build -t my-prometheus -f prometheus/ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Потом пишется docker-compose.yml с образом '' | ||
+ | <file bash Dockerfile> | ||
+ | FROM diamol/ | ||
+ | COPY dashboard.json / | ||
+ | </ | ||
+ | <code bash> | ||
+ | # Сборка | ||
+ | docker image build -t my-grafana -f grafana/ | ||
+ | </ | ||
+ | |||
+ | Потом в '' | ||
+ | <file yaml docker-compose.yml> | ||
+ | version: ' | ||
+ | services: | ||
+ | todo: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - 8080:80 | ||
+ | prometheus: | ||
+ | image: my-prometheus | ||
+ | ports: | ||
+ | - 9090:9090 | ||
+ | grafana: | ||
+ | image: my-grafana | ||
+ | ports: | ||
+ | - 3000:3000 | ||
+ | </ | ||
+ | ===== 10. Запуск нескольких окружений с помощью docker-compose ===== | ||
+ | Нужно, когда одна версия продуктивная, | ||
+ | <code bash> | ||
+ | $ docker-compose -f numbers/ | ||
+ | Creating network " | ||
+ | Creating numbers_numbers-api_1 ... done | ||
+ | Creating numbers_numbers-web_1 ... done | ||
+ | $ docker-compose -f todo-list/ | ||
+ | Creating network " | ||
+ | Creating todo-list_todo-web_1 ... done | ||
+ | $ docker-compose -f todo-list/ | ||
+ | todo-list_todo-web_1 is up-to-date | ||
+ | </ | ||
+ | |||
+ | Докер-композ использует понятие // | ||
+ | |||
+ | Но имя проекта можно переопределить, | ||
+ | <code bash> | ||
+ | $ docker-compose -f todo-list/ | ||
+ | Creating network " | ||
+ | Creating todo-test_todo-web_1 ... done | ||
+ | </ | ||
+ | |||
+ | Нюанс в том, что для каждого экземпляра нужно выяснять порт, к которому нужно подключаться извне, чтобы попасть в контейнер, | ||
+ | <code bash> | ||
+ | docker-compose -f ./ | ||
+ | docker ps | ||
+ | docker container port todo-test_todo-web_1 80 # Выяснить порт | ||
+ | 0.0.0.0: | ||
+ | :::49157 | ||
+ | </ | ||
+ | Чтобы сделать ситуацию более управляемой, | ||
+ | |||
+ | ==== Docker Compose override files ==== | ||
+ | При простом дублировании и редактировании файлов их содержимое будет практически одинаковым, | ||
+ | Докер-композ позволяет создавать перекрывающие файлы, где будут отражены только изменения базовой конфигурации. К примеру, | ||
+ | |||
+ | В этом случае для изменения какой-то среды нужно редактировать только один файл, а если нужно изменить что-то для всех окружений - отредактировать основной файл. | ||
+ | <code yaml> | ||
+ | # from docker-compose.yml - the core app specification: | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - 80 | ||
+ | environment: | ||
+ | - Database: | ||
+ | networks: | ||
+ | - app-net | ||
+ | # and from docker-compose-v2.yml - the version override file: | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | </ | ||
+ | В данном случае, | ||
+ | |||
+ | Чтобы запустить основной файл с перекрывающим, | ||
+ | <code bash> | ||
+ | # config в конце - проверка конфигурации без реального запуска, | ||
+ | # config сортирует вывод по алфавиту, | ||
+ | docker-compose -f ./ | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | Основной файл: описывает только сервисы без портов и сетей. | ||
+ | <file yaml numbers/ | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | image: diamol/ | ||
+ | networks: | ||
+ | - app-net | ||
+ | |||
+ | numbers-web: | ||
+ | image: diamol/ | ||
+ | environment: | ||
+ | - RngApi__Url=http:// | ||
+ | networks: | ||
+ | - app-net | ||
+ | |||
+ | networks: | ||
+ | app-net: | ||
+ | </ | ||
+ | |||
+ | Среда разработки: | ||
+ | <file yaml numbers/ | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | ports: | ||
+ | - " | ||
+ | healthcheck: | ||
+ | disable: true | ||
+ | |||
+ | numbers-web: | ||
+ | entrypoint: | ||
+ | - dotnet | ||
+ | - Numbers.Web.dll | ||
+ | ports: | ||
+ | - " | ||
+ | |||
+ | networks: | ||
+ | app-net: | ||
+ | name: numbers-dev | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | Тест: сеть, проверки. Сервис API остаётся внутренним и не публикуется. | ||
+ | <file yaml numbers/ | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | healthcheck: | ||
+ | interval: 20s | ||
+ | start_period: | ||
+ | retries: 4 | ||
+ | |||
+ | numbers-web: | ||
+ | ports: | ||
+ | - " | ||
+ | restart: on-failure | ||
+ | healthcheck: | ||
+ | test: [" | ||
+ | interval: 20s | ||
+ | timeout: 10s | ||
+ | retries: 4 | ||
+ | start_period: | ||
+ | |||
+ | networks: | ||
+ | app-net: | ||
+ | name: numbers-test | ||
+ | </ | ||
+ | |||
+ | Тест на юзерах: | ||
+ | <file yaml numbers/ | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | healthcheck: | ||
+ | interval: 10s | ||
+ | retries: 2 | ||
+ | restart: always | ||
+ | ports: | ||
+ | - " | ||
+ | |||
+ | numbers-web: | ||
+ | restart: always | ||
+ | ports: | ||
+ | - " | ||
+ | healthcheck: | ||
+ | interval: 10s | ||
+ | retries: 2 | ||
+ | |||
+ | networks: | ||
+ | app-net: | ||
+ | name: numbers-uat | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <code bash> | ||
+ | # запуск трёх сред - разработка, | ||
+ | docker-compose -f ./ | ||
+ | docker-compose -f ./ | ||
+ | docker-compose -f ./ | ||
+ | |||
+ | docker ps | ||
+ | CONTAINER ID | ||
+ | b4f10fb1eeac | ||
+ | 7e2e8739eb06 | ||
+ | daef59fda817 | ||
+ | d19477e16c43 | ||
+ | f86e7079c986 | ||
+ | a16bb1a08e64 | ||
+ | </ | ||
+ | Теперь мы имеем три изолированные среды на одном сервере, | ||
+ | |||
+ | Для того, чтобы удалить среду, недостаточно просто команды '' | ||
+ | <code bash> | ||
+ | # Не удалит ничего | ||
+ | docker-compose down | ||
+ | |||
+ | # Это работает, | ||
+ | # Если имя проекта было указано, | ||
+ | # т. к. контейнеры с префиксом по умолчанию он не найдёт (префикс другой - app-test), | ||
+ | # а сеть не может быть удалена, | ||
+ | docker-compose -f ./ | ||
+ | Removing network numbers-test | ||
+ | ERROR: error while removing network: network numbers-test id 8193d858fd0f2c9716d750478c253d032251571586d12f61648987baadc75315 has active endpoints | ||
+ | # Если имя проекта было указано, | ||
+ | docker-compose -f ./ | ||
+ | Stopping numbers-test_numbers-api_1 ... done | ||
+ | Stopping numbers-test_numbers-web_1 ... done | ||
+ | Removing numbers-test_numbers-api_1 ... done | ||
+ | Removing numbers-test_numbers-web_1 ... done | ||
+ | Removing network numbers-test | ||
+ | </ | ||
+ | |||
+ | ==== Добавление в конфигурацию переменных и секретов ==== | ||
+ | Помимо изолирования приложения с помощью сетей и настройки разницы между окружениями с помощью перекрывающих файлов, | ||
+ | |||
+ | Возьмём приложение to-do, где нужно настраивать 3 параметра: | ||
+ | - Уровень логирования: | ||
+ | - Тип базы данных: | ||
+ | - Строка подключения к БД: учётные данные для подключения, | ||
+ | |||
+ | Конфигурация приложения в виде секрета. | ||
+ | <code yaml> | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | secrets: | ||
+ | # Название секрета, | ||
+ | - source: todo-db-connection | ||
+ | # Целевой файл внутри контейнера | ||
+ | target: / | ||
+ | </ | ||
+ | Базовый конфиг выше неполноценен, | ||
+ | <code yaml> | ||
+ | services: | ||
+ | todo-web: | ||
+ | ports: | ||
+ | - 8089:80 | ||
+ | # Environment добавляет переменную внутрь контейнера, | ||
+ | # Это самый простой путь задавать параметры, | ||
+ | environment: | ||
+ | - Database: | ||
+ | # env_file - путь к текстовому файлу, где заданы переменные в формате name=value | ||
+ | # Удобно для задания переменных для нескольких компонентов сразу, чтобы не дублировать их в конфиге | ||
+ | env_file: | ||
+ | - ./ | ||
+ | |||
+ | # Secrets - секция верхнего уровня, | ||
+ | # Указывает, | ||
+ | secrets: | ||
+ | todo-db-connection: | ||
+ | file: ./ | ||
+ | </ | ||
+ | |||
+ | Помимо задания переменных внутри контейнера, | ||
+ | <code yaml> | ||
+ | todo-web: | ||
+ | ports: | ||
+ | - " | ||
+ | environment: | ||
+ | - Database: | ||
+ | env_file: | ||
+ | - ./ | ||
+ | networks: | ||
+ | - app-net | ||
+ | </ | ||
+ | |||
+ | Если Докер-композ обнаруживает файл .env в текущем каталоге, | ||
+ | <code bash> | ||
+ | # Порты | ||
+ | TODO_WEB_PORT=8877 | ||
+ | TODO_DB_PORT=5432 | ||
+ | # Файлы и имя проекта: | ||
+ | COMPOSE_PATH_SEPARATOR=; | ||
+ | COMPOSE_FILE=docker-compose.yml; | ||
+ | COMPOSE_PROJECT_NAME=todo_ch10 | ||
+ | </ | ||
+ | Собственно говоря, | ||
+ | |||
+ | Резюме: | ||
+ | * **Свойство environment** - самый простой вариант, | ||
+ | * **environment_file** - удобно, | ||
+ | * **secret** - наиболее гибкий подход: | ||
+ | * **.env** - задание параметров окружения по умолчанию. | ||
+ | |||
+ | [[https:// | ||
+ | [[https:// | ||
+ | |||
+ | ==== Поля расширения (extension fields) - устранение дублирования текста конфигурации ==== | ||
+ | Поле расширения - это блок файла YAML, на который можно ссылаться несколько раз в композ-файле. Поля расширения должны определяться на верхнем уровне вне прочих блоков - сервисов, | ||
+ | <code yaml> | ||
+ | x-labels: & | ||
+ | logging: | ||
+ | options: | ||
+ | max-size: ' | ||
+ | max-file: ' | ||
+ | | ||
+ | x-labels: &labels | ||
+ | app-name: image-gallery | ||
+ | </ | ||
+ | Здесь в случае блока logging задаются настройки логирования, | ||
+ | Вызываются поля расширения с помощью конструкции '' | ||
+ | <code yaml> | ||
+ | services: | ||
+ | iotd: | ||
+ | ports: | ||
+ | - 8080:80 | ||
+ | <<: *logging | ||
+ | labels: | ||
+ | <<: *labels | ||
+ | public: api | ||
+ | </ | ||
+ | |||
+ | <code yaml> | ||
+ | # config покажет результат вместе с подстановкой полей расширений | ||
+ | docker-compose -f docker-compose.yml -f docker-compose-prod.yml config | ||
+ | </ | ||
+ | Фрагмент вывода: | ||
+ | <code yaml> | ||
+ | image-gallery: | ||
+ | image: diamol/ | ||
+ | labels: | ||
+ | app-name: image-gallery | ||
+ | public: web | ||
+ | logging: | ||
+ | options: | ||
+ | max-file: ' | ||
+ | max-size: 100m | ||
+ | networks: | ||
+ | app-net: {} | ||
+ | ports: | ||
+ | - published: 80 | ||
+ | target: 80 | ||
+ | </ | ||
+ | Поля расширения помогают стандартизировать сервисы. Тем не менее, есть ограничение - поля расширения не применяются к нескольким файлам, | ||
+ | |||
+ | ==== Процесс конфигурации с помощью Докера ==== | ||
+ | Очень хорошо иметь всю конфигурацию для деплоя в гите - это позволяет развернуть любую версию приложения, | ||
+ | |||
+ | Между окружениями всегда существуют различия, | ||
+ | - **Композиция приложения: | ||
+ | - **Конфигурация контейнеров: | ||
+ | - **Конфигурация приложения: | ||
+ | |||
+ | Важно, что для всех окружений используется одни и те же образы, | ||
+ | |||
+ | ==== Лабораторная ==== | ||
+ | Создать два окружения: | ||
+ | - dev: локальная база (файл), публикация на порту 8089, образ v2. Запуск должен быть по команде без лишних ключей, | ||
+ | - test: БД в отдельном контейнере и с томом для хранения данных, | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | <file yaml docker-compose.yml> | ||
+ | version: ' | ||
+ | |||
+ | services: | ||
+ | todo-web: | ||
+ | image: ${IMAGE_WEB} | ||
+ | ports: | ||
+ | - ${PORT_WEB}: | ||
+ | networks: | ||
+ | - network | ||
+ | |||
+ | networks: | ||
+ | network: | ||
+ | name: ${NETWORK} | ||
+ | </ | ||
+ | <file bash .env> | ||
+ | IMAGE_WEB=' | ||
+ | PORT_WEB=8089 | ||
+ | COMPOSE_PROJECT_NAME=todo-dev | ||
+ | NETWORK=todo-dev | ||
+ | Database: | ||
+ | </ | ||
+ | <code bash> | ||
+ | # Запуск | ||
+ | docker-compose up -d | ||
+ | Creating network " | ||
+ | Pulling todo-web (diamol/ | ||
+ | v2: Pulling from diamol/ | ||
+ | 68ced04f60ab: | ||
+ | e936bd534ffb: | ||
+ | caf64655bcbb: | ||
+ | d1927dbcbcab: | ||
+ | 641667054481: | ||
+ | 9d301c563cc9: | ||
+ | 92dc1ae7fce7: | ||
+ | 12c9a1dda02c: | ||
+ | Digest: sha256: | ||
+ | Status: Downloaded newer image for diamol/ | ||
+ | Creating todo-dev_todo-web_1 ... done | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | <file yaml docker-compose-test.yml> | ||
+ | services: | ||
+ | todo-web: | ||
+ | depends_on: | ||
+ | - todo-db | ||
+ | todo-db: | ||
+ | image: ${IMAGE_DB} | ||
+ | networks: | ||
+ | - network | ||
+ | volumes: | ||
+ | - todo-db: | ||
+ | volumes: | ||
+ | todo-db: | ||
+ | </ | ||
+ | <file bash test.env> | ||
+ | IMAGE_WEB=' | ||
+ | IMAGE_DB=' | ||
+ | PORT_WEB=8080 | ||
+ | COMPOSE_PROJECT_NAME=todo-test | ||
+ | NETWORK=todo-test | ||
+ | Database: | ||
+ | PGDATA=/ | ||
+ | </ | ||
+ | <code bash> | ||
+ | # Запуск | ||
+ | docker-compose -f docker-compose.yml -f docker-compose-test.yml --env-file test.env up -d | ||
+ | Creating volume " | ||
+ | Creating todo-test_todo-db_1 ... done | ||
+ | Creating todo-test_todo-web_1 ... done | ||
+ | </ | ||
+ | Имя тома можно хардкодить в композ-файле - при наличии имени проекта оно ставится спереди и получается '' | ||
+ | |||
+ | [[https:// | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Результат | ||
+ | <code bash> | ||
+ | docker ps | ||
+ | CONTAINER ID | ||
+ | 91946dd92dd4 | ||
+ | 4d834b9ae0a9 | ||
+ | 69af7592fc08 | ||
+ | </ | ||
+ | |||
+ | ===== 11. Сборка и тестирование приложений ===== | ||
+ | Докер унифицирует CI-процесс, | ||
+ | |||
+ | Суть в том, что в контейнер с Дженкинсом ставится Docker CLI, который подключается к Docker API хоста для выполнения задач сборки. | ||
+ | <code yaml> | ||
+ | services: | ||
+ | jenkins: | ||
+ | volumes: | ||
+ | - type: bind | ||
+ | - source: / | ||
+ | - target: / | ||
+ | </ | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | Основной файл. Тут применяется замена переменной, | ||
+ | <file yaml docker-compose.yaml> | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | image: ${REGISTRY: | ||
+ | networks: | ||
+ | - app-net | ||
+ | |||
+ | numbers-web: | ||
+ | image: ${REGISTRY: | ||
+ | environment: | ||
+ | - RngApi__Url=http:// | ||
+ | depends_on: | ||
+ | - numbers-api | ||
+ | networks: | ||
+ | - app-net | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | Перекрывающий файл: где искать докерфайлы. | ||
+ | <file yaml docker-compose-build.yaml> | ||
+ | x-args: &args | ||
+ | args: | ||
+ | BUILD_NUMBER: | ||
+ | BUILD_TAG: ${BUILD_TAG: | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | build: | ||
+ | context: numbers | ||
+ | dockerfile: numbers-api/ | ||
+ | <<: *args | ||
+ | | ||
+ | numbers-web: | ||
+ | build: | ||
+ | context: numbers | ||
+ | dockerfile: numbers-web/ | ||
+ | <<: *args | ||
+ | | ||
+ | networks: | ||
+ | app-net: | ||
+ | </ | ||
+ | Context - относительный путь к рабочему каталогу сборки, | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <code bash> | ||
+ | # Сборка через докер-композ - соберутся все компоненты, | ||
+ | docker-compose -f docker-compose.yml -f docker-compose-build.yml build | ||
+ | # Проверка ярлыков образа api | ||
+ | docker image inspect -f ' | ||
+ | map[build_number: | ||
+ | </ | ||
+ | |||
+ | Собирать через Композ - хорошая практика, | ||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | |||
+ | ARG BUILD_NUMBER=0 | ||
+ | ARG BUILD_TAG=local | ||
+ | |||
+ | LABEL version=" | ||
+ | LABEL build_number=${BUILD_NUMBER} | ||
+ | LABEL build_tag=${BUILD_TAG} | ||
+ | |||
+ | ENTRYPOINT [" | ||
+ | </ | ||
+ | '' | ||
+ | |||
+ | Значения по умолчанию в нескольких местах здесь нужны, чтобы обеспечить корректную сборку и через CI, и локально. Сборка через обычный Докер: | ||
+ | <code bash> | ||
+ | # С указанием докерфайла и аргумента сборки (здесь --build-arg BUILD_TAG перекроет ARG в докерфайле) | ||
+ | docker image build -f numbers-api/ | ||
+ | # Проверка ярлыков | ||
+ | docker image inspect -f ' | ||
+ | map[build_number: | ||
+ | </ | ||
+ | Тэги очень полезны: | ||
+ | ==== Создание задач CI без каких-либо зависимостей, | ||
+ | //Jenkins в репозитории устарел и сломан.// | ||
+ | |||
+ | ===== 12. Оркестраторы - Docker Swarm и k8s ===== | ||
+ | <code bash> | ||
+ | # Включить режим Swarm | ||
+ | docker swarm init | ||
+ | Swarm initialized: | ||
+ | To add a worker to this swarm, run the following command: | ||
+ | docker swarm join --token SWMTKN-1-5hlq0y8b4z34bsuuopr3uovtwliehdoiwhew 192.168.0.2: | ||
+ | To add a manager to this swarm, run ' | ||
+ | | ||
+ | # Показать ссылку для добавления рабочего | ||
+ | docker swarm join-token worker | ||
+ | |||
+ | # Показать ссылку для добавления менеджера | ||
+ | docker swarm join-token manager | ||
+ | |||
+ | # Список узлов кластера | ||
+ | docker node ls | ||
+ | ID HOSTNAME | ||
+ | ti5tg544h7k6dsw0mnj6uepot * | ||
+ | </ | ||
+ | |||
+ | В кластере вы не запускаете контейнеры - вы разворачиваете сервисы, | ||
+ | <code bash> | ||
+ | # Развернуть сервис | ||
+ | $ docker service create --name timecheck --replicas 1 diamol/ | ||
+ | v02pz59txdtirqyqzr18s4w71 | ||
+ | overall progress: 1 out of 1 tasks | ||
+ | 1/1: running | ||
+ | verify: Service converged | ||
+ | |||
+ | # Вывести список сервисов | ||
+ | $ docker service ls | ||
+ | ID | ||
+ | v02pz59txdti | ||
+ | |||
+ | # Обновить образ у сервиса timecheck | ||
+ | docker service update --image diamol/ | ||
+ | |||
+ | # Вывести все реплики сервиса timecheck | ||
+ | docker service ps timecheck | ||
+ | |||
+ | # Откатиться к предыдущему состоянию | ||
+ | docker service update --rollback timecheck | ||
+ | |||
+ | # Логи всех реплик за последние 30 сек | ||
+ | docker service logs --since 30s timecheck | ||
+ | |||
+ | # Удалить сервис | ||
+ | docker service rm timecheck | ||
+ | </ | ||
+ | |||
+ | ==== Управление сетью в кластере ==== | ||
+ | Чтобы контейнеры могли обмениваться трафиком, | ||
+ | |||
+ | <code bash> | ||
+ | $ docker network create --driver overlay iotd-net | ||
+ | gpl2688gwl76xtmo67u1g4xvg | ||
+ | |||
+ | $ docker service create --detach --replicas 3 --network iotd-net --name iotd diamol/ | ||
+ | if4bv9e96evlqm0d3hj35jx7c | ||
+ | |||
+ | $ docker service create --detach --replicas 2 --network iotd-net --name accesslog diamol/ | ||
+ | d7pq960ki6gqj1da0f8e34byf | ||
+ | |||
+ | $ docker service ls | ||
+ | ID | ||
+ | d7pq960ki6gq | ||
+ | if4bv9e96evl | ||
+ | </ | ||
+ | Балансировка запросов между узлами кластера, | ||
+ | <code bash> | ||
+ | # Запуск веб-части image gallery | ||
+ | $ docker service create --detach --replicas 2 --network iotd-net --name image-gallery --publish 8010:80 diamol/ | ||
+ | l9oju3hy9qi5qx4hz1ubinp4f | ||
+ | |||
+ | $ docker service ls | ||
+ | ID | ||
+ | d7pq960ki6gq | ||
+ | l9oju3hy9qi5 | ||
+ | if4bv9e96evl | ||
+ | </ | ||
+ | Здесь видно один порт на несколько контейнеров, | ||
+ | |||
+ | ==== Выбор между Swarm и k8s ==== | ||
+ | В датацентре удобнее и проще Сворм, в облаке - Кубер. Сворм в целом проще, хотя не имеет настолько больших возможностей, | ||
+ | |||
+ | ==== Лабораторная ==== | ||
+ | Создать в Сворме приложение Numbers | ||
+ | <code bash> | ||
+ | docker network create --driver overlay numbers-net | ||
+ | docker service create --detach --replicas 2 --network numbers-net --name numbers-api diamol/ | ||
+ | docker service create --detach --replicas 2 --network numbers-net --name numbers-web --publish 8010:80 diamol/ | ||
+ | |||
+ | docker service ls | ||
+ | ID | ||
+ | vr1psfnkhxs8 | ||
+ | wtruhsgcjrhb | ||
+ | |||
+ | docker service rm numbers* | ||
+ | docker network rm numbers-net | ||
+ | </ | ||
+ | |||
+ | ===== 13. Развёртывание приложений в кластере Docker Swarm ===== | ||
+ | До этого команды запуска были императивными, | ||
+ | |||
+ | ==== Композ для развёртывания в проде ==== | ||
+ | К примеру, | ||
+ | <file yaml v1.yml> | ||
+ | version: " | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - 8080:80 | ||
+ | </ | ||
+ | Если развернуть его с помощью Композа на одном сервере, | ||
+ | |||
+ | <code bash> | ||
+ | # Развернуть стэк | ||
+ | docker stack deploy -c ./ | ||
+ | Creating network todo_default | ||
+ | Creating service todo_todo-web | ||
+ | # Список стэков | ||
+ | docker stack ls | ||
+ | NAME SERVICES | ||
+ | todo 1 Swarm | ||
+ | # Список развёрнутых сервисов | ||
+ | docker service ls | ||
+ | ID | ||
+ | pb9y6a0dyxbf | ||
+ | </ | ||
+ | В этом примере используется стандартный композ-файл без каких-либо добавок, | ||
+ | |||
+ | Специфичные для Сворма настройки размещаются в разделе deploy сервиса. Здесь указаны 2 реплики и ограничение в пол-ядра и 100 МБ памяти для каждой. Лимиты применяются во время запуска контейнера, | ||
+ | <code yaml> | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - 8080:80 | ||
+ | deploy: | ||
+ | replicas: 2 | ||
+ | resources: | ||
+ | limits: | ||
+ | cpus: " | ||
+ | memory: 100M | ||
+ | </ | ||
+ | Стэк - это организационная единица для управления приложением в кластере, | ||
+ | <code bash> | ||
+ | # список всех сервисов | ||
+ | docker stack services todo | ||
+ | # список всех реплик всех сервисов в стэке | ||
+ | docker stack ps todo | ||
+ | # удалить стэк | ||
+ | docker stack rm todo | ||
+ | </ | ||
+ | |||
+ | ==== Управление конфигурацией приложения с помощью конфигурационных объектов ==== | ||
+ | Ранее рассматривалась настройка приложения как с помощью настроек по умолчанию в среде разработки, | ||
+ | <code bash> | ||
+ | # Создание конфига из локального json-файла | ||
+ | $ docker config create todo-list-config ./ | ||
+ | xyc2p4bngbacp4zib7a5qo3ah | ||
+ | # Список конфигов | ||
+ | $ docker config ls | ||
+ | ID NAME | ||
+ | xyc2p4bngbacp4zib7a5qo3ah | ||
+ | </ | ||
+ | В примере файл JSON, но это может быть и XML, и бинарник. Сворм пробрасывает объект конфигурации в контейнер, | ||
+ | <code bash> | ||
+ | $ docker config inspect | ||
+ | ID: | ||
+ | Name: | ||
+ | Created at: | ||
+ | Updated at: | ||
+ | Data: | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | Чтобы сервис использовал конфиг, | ||
+ | <code yaml> | ||
+ | services: | ||
+ | todo-web: | ||
+ | image: diamol/ | ||
+ | ports: | ||
+ | - 8080:80 | ||
+ | configs: | ||
+ | - source: todo-list-config | ||
+ | target: / | ||
+ | | ||
+ | #... | ||
+ | |||
+ | configs: | ||
+ | todo-list-config: | ||
+ | external: true | ||
+ | </ | ||
+ | Обновить стэк можно, задеплоив новую версию с тем же именем, | ||
+ | <code bash> | ||
+ | docker stack deploy -c ./ | ||
+ | </ | ||
+ | Но работать это не будет, т. к. в конфиге нет логина и пароля для подключения к базе, которые нужно передать в секрете, | ||
+ | |||
+ | ==== Управление конфиденциальной информацией с помощью секретов ==== | ||
+ | Секреты очень похожи на конфиги, | ||
+ | <code bash> | ||
+ | $ docker secret create todo-list-secret ./ | ||
+ | 1hc6k5nt0hy8a6pru6qedonkk | ||
+ | |||
+ | $ docker secret inspect --pretty todo-list-secret | ||
+ | ID: 1hc6k5nt0hy8a6pru6qedonkk | ||
+ | Name: todo-list-secret | ||
+ | Driver: | ||
+ | Created at: 2023-04-25 11: | ||
+ | Updated at: 2023-04-25 11: | ||
+ | </ | ||
+ | |||
+ | Секрет в композ-файле: | ||
+ | <code yaml> | ||
+ | secrets: | ||
+ | - source: todo-list-secret | ||
+ | target: / | ||
+ | |||
+ | #... | ||
+ | |||
+ | secrets: | ||
+ | todo-list-secret: | ||
+ | external: true | ||
+ | </ | ||
+ | Обновить стэк: | ||
+ | <code bash> | ||
+ | docker stack deploy -c ./ | ||
+ | </ | ||
+ | Теперь приложение работает. | ||
+ | |||
+ | Ни конфиги, | ||
+ | - Создаётся новый конфиг/ | ||
+ | - В композ-файле прописывается новый секрет | ||
+ | - Стэк обновляется, | ||
+ | |||
+ | Кубер позволяет обновлять конфиги/ | ||
+ | |||
+ | ==== Хранение постоянных данных в Сворме (volumes) ==== | ||
+ | В кластере контейнер может запуститься на разных нодах, поэтому локальный том имеет ограниченное применение. Самый простой случай, | ||
+ | <code bash> | ||
+ | # Вывести идентификатор узла и прописать его как ярлык | ||
+ | docker node update --label-add storage=raid $(docker node ls -q) | ||
+ | </ | ||
+ | Привязка в композ-файле: | ||
+ | <code yaml> | ||
+ | services: | ||
+ | todo-db: | ||
+ | image: diamol/ | ||
+ | volumes: | ||
+ | - todo-db-data:/ | ||
+ | deploy: | ||
+ | placement: | ||
+ | constraints: | ||
+ | - node.labels.storage == raid | ||
+ | |||
+ | #... | ||
+ | |||
+ | volumes: | ||
+ | todo-db-data: | ||
+ | </ | ||
+ | Это самый простой пример, | ||
+ | |||
+ | ==== Как кластер управляет стэками ==== | ||
+ | Стэк - это группа ресурсов в кластере, | ||
+ | * **Тома** могут создаваться и удаляться Свормом. Стэк создаст том по умолчанию, | ||
+ | * **Секреты/ | ||
+ | * **Сети** могут управляться отдельно от приложений, | ||
+ | * **Сервисы** создаются и удаляются во время развёртывания стэка. Когда они работают, | ||
+ | |||
+ | Между сервисами нельзя создать зависимость, | ||
+ | |||
+ | ===== 14. Автоматизация релизов - обновление и откат ===== | ||
+ | Докер не поддерживает развёртывание стэка из нескольких (перекрывающих) композ-файлов, | ||
+ | <code bash> | ||
+ | # Слепить файлы, заодно проверить конфиг | ||
+ | docker-compose -f ./ | ||
+ | # Развернуть | ||
+ | docker stack deploy -c stack.yml numbers | ||
+ | # Список сервисов стэка | ||
+ | docker stack services numbers | ||
+ | ID | ||
+ | htqpdr0tfvjw | ||
+ | w2veemvl0qxf | ||
+ | </ | ||
+ | Режим global значит, | ||
+ | <code yaml> | ||
+ | numbers-web: | ||
+ | ports: | ||
+ | - target: 80 | ||
+ | published: 80 | ||
+ | mode: host | ||
+ | deploy: | ||
+ | mode: global | ||
+ | </ | ||
+ | '' | ||
+ | |||
+ | Обновим стэк, добавив проверки и заменив образ на v2: | ||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | <file yaml docker-compose.yaml> | ||
+ | version: " | ||
+ | |||
+ | services: | ||
+ | numbers-api: | ||
+ | image: diamol/ | ||
+ | networks: | ||
+ | - app-net | ||
+ | |||
+ | numbers-web: | ||
+ | image: diamol/ | ||
+ | environment: | ||
+ | - RngApi__Url=http:// | ||
+ | networks: | ||
+ | - app-net | ||
+ | </ | ||
+ | <file yaml prod.yaml> | ||
+ | services: | ||
+ | numbers-api: | ||
+ | deploy: | ||
+ | replicas: 6 | ||
+ | resources: | ||
+ | limits: | ||
+ | cpus: " | ||
+ | memory: 75M | ||
+ | |||
+ | numbers-web: | ||
+ | ports: | ||
+ | - target: 80 | ||
+ | published: 80 | ||
+ | mode: host | ||
+ | deploy: | ||
+ | mode: global | ||
+ | resources: | ||
+ | limits: | ||
+ | cpus: " | ||
+ | memory: 150M | ||
+ | |||
+ | networks: | ||
+ | app-net: | ||
+ | name: numbers-prod | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | <file yaml prod-healthcheck.yaml> | ||
+ | services: | ||
+ | numbers-api: | ||
+ | healthcheck: | ||
+ | test: [" | ||
+ | interval: 2s | ||
+ | timeout: 3s | ||
+ | retries: 2 | ||
+ | start_period: | ||
+ | |||
+ | numbers-web: | ||
+ | healthcheck: | ||
+ | interval: 20s | ||
+ | timeout: 10s | ||
+ | retries: 3 | ||
+ | start_period: | ||
+ | </ | ||
+ | <file yaml v2.yaml> | ||
+ | services: | ||
+ | numbers-api: | ||
+ | image: diamol/ | ||
+ | |||
+ | numbers-web: | ||
+ | image: diamol/ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | Обновить стэк: | ||
+ | <code bash> | ||
+ | docker-compose -f ./ | ||
+ | docker stack deploy -c stack.yml numbers | ||
+ | </ | ||
+ | При таком обновлении контейнеры сначала останавливаются, | ||
+ | |||
+ | <code yaml> | ||
+ | services: | ||
+ | numbers-api: | ||
+ | deploy: | ||
+ | update_config: | ||
+ | parallelism: | ||
+ | monitor: 60s # Период слежения за новыми репликами перед продолжением обновления | ||
+ | failure_action: | ||
+ | order: start-first # Порядок замены реплик, | ||
+ | </ | ||
+ | |||
+ | Есть команда, | ||
+ | <code bash> | ||
+ | # Сервис именуется {stack-name}_{service-name} | ||
+ | docker service inspect --pretty numbers_numbers-api | ||
+ | |||
+ | ID: | ||
+ | Name: | ||
+ | Labels: | ||
+ | | ||
+ | | ||
+ | Service Mode: | ||
+ | | ||
+ | UpdateStatus: | ||
+ | | ||
+ | | ||
+ | | ||
+ | Placement: | ||
+ | UpdateConfig: | ||
+ | | ||
+ | On failure: | ||
+ | | ||
+ | Max failure ratio: 0 | ||
+ | | ||
+ | RollbackConfig: | ||
+ | | ||
+ | On failure: | ||
+ | | ||
+ | Max failure ratio: 0 | ||
+ | | ||
+ | ContainerSpec: | ||
+ | | ||
+ | Resources: | ||
+ | | ||
+ | CPU: 0.5 | ||
+ | Memory: | ||
+ | Networks: numbers-prod | ||
+ | Endpoint Mode: vip | ||
+ | | ||
+ | Interval = 2s | ||
+ | Retries = 2 | ||
+ | StartPeriod = 5s | ||
+ | Timeout = 3s | ||
+ | Tests: | ||
+ | Test = CMD | ||
+ | Test = dotnet | ||
+ | Test = Utilities.HttpCheck.dll | ||
+ | Test = -u | ||
+ | Test = http:// | ||
+ | Test = -t | ||
+ | Test = 500 | ||
+ | </ | ||
+ | |||
+ | Нужно помнить, | ||
+ | |||
+ | ==== Откат обновления ==== | ||
+ | Команды, | ||
+ | |||
+ | Развёртывания всё равно являются основной причиной простоя, | ||
+ | |||
+ | Агрессивная политика отката обновления | ||
+ | <code yaml> | ||
+ | services: | ||
+ | numbers-api: | ||
+ | deploy: | ||
+ | rollback_config: | ||
+ | parallelism: | ||
+ | monitor: 0s # Note: Setting to 0 will use the default 5s. | ||
+ | failure_action: | ||
+ | order: start-first # Сначала запустить старую версию, | ||
+ | </ | ||
+ | https:// | ||
+ | |||
+ | {{: | ||
+ | |||
+ | ==== Обслуживание узлов кластера ==== | ||
+ | Чтобы выгнать контейнеры с узла для его обновления или обслуживания, | ||
+ | <code bash> | ||
+ | docker node update --availability drain node5 | ||
+ | </ | ||
+ | Управляющие узлы в дрейн-моде всё равно выполняют свои функции, | ||
+ | |||
+ | <code bash> | ||
+ | # Повысить рабочий узел до менеджера | ||
+ | docker node promote node6 | ||
+ | # Список узлов | ||
+ | docker node ls | ||
+ | </ | ||
+ | |||
+ | Есть 2 способа для ноды выйти из кластера: | ||
+ | - С менеджера командой '' | ||
+ | - С самой ноды командой '' | ||
+ | |||
+ | Понизить менеджера до рабочего: | ||
+ | |||
+ | Рассмотрим некоторые ситуации: | ||
+ | * Все менеджеры недоступны - всё будет работать, | ||
+ | * Только один менеджер доступен, | ||
+ | * Перераспределение существующих реплик по узлам - реплики автоматически не распространяются на новые добавленные узлы кластера, | ||
+ | |||
+ | ==== Отказоустойчивость в кластерах Сворма ==== | ||
+ | Есть несколько слоёв внедрения приложения с точки зрения отказоустойчивости, | ||
+ | * Проверки здоровья, | ||
+ | * Несколько рабочих узлов (перераспределение контейнеров); | ||
+ | * Несколько менеджеров (передача обязанностей мониторинга и планировщика). | ||
+ | |||
+ | Остался вопрос отказоустойчивости самого датацентра. Вариант разнесения датацентров кластера по разным геолокациям хорош с точки зрения простоты управления, | ||
+ | |||
+ | Лучше иметь несколько кластеров, | ||
+ | |||
+ | ===== 15. Удалённый доступ и CI/CD ===== | ||
+ | Стандартно Docker API открыт только для локальной машины, | ||
+ | |||
+ | ==== Незащищённый доступ ==== | ||
+ | FIXME Незащищённый доступ использовать в реальной жизни крайне не рекомендуется! | ||
+ | <file yaml / | ||
+ | { | ||
+ | " | ||
+ | # enable remote access on port 2375: | ||
+ | " | ||
+ | # and keep listening on the local channel - Windows pipe: | ||
+ | " | ||
+ | # OR Linux socket: | ||
+ | " | ||
+ | ], | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | } | ||
+ | </ | ||
+ | //Это не работает, | ||
+ | <code bash> | ||
+ | docker --host tcp:// | ||
+ | Cannot connect to the Docker daemon at tcp:// | ||
+ | </ | ||
+ | |||
+ | ==== Защищённый доступ, | ||
+ | 2 способа: | ||
+ | |||
+ | Чтобы в каждой команде не указывать сервер, | ||
+ | <code bash> | ||
+ | # создание | ||
+ | docker context create remote --docker " | ||
+ | # список контекстов | ||
+ | docker context ls | ||
+ | NAME DESCRIPTION | ||
+ | remote | ||
+ | default * | ||
+ | </ | ||
+ | Контекст можно указать временно для терминальной сессии, | ||
+ | <code bash> | ||
+ | # Временно | ||
+ | export DOCKER_CONTEXT=' | ||
+ | # Постоянно | ||
+ | docker context use remote | ||
+ | </ | ||
+ | :!: Переменная '' | ||
+ | |||
+ | ==== Модель доступа в Докере ==== | ||
+ | Защита доступа состоит из 2-х частей: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | |||
+ | ===== 16. Сборка образов для разных архитектур (Linux, Windows, Intel, Arm) ===== | ||
+ | |||
+ | ===== 17. Оптимизация образов по размеру, | ||
+ | ✔ Докер не удаляет старые слои после скачивания новых - это нужно делать руками. Хорошей практикой является регулярная очистка. | ||
+ | <code bash> | ||
+ | # Оценка занимаемого места | ||
+ | docker system df | ||
+ | # Очистка старых слоёв и кэша | ||
+ | docker system prune | ||
+ | </ | ||
+ | |||
+ | ✔ В образ нужно копировать только то, что необходимо для запуска приложения. Например, | ||
+ | |||
+ | ✔ Помимо избирательного копирования, | ||
+ | <code bash> | ||
+ | # Запуск без .dockerignore | ||
+ | docker image build -t diamol/ | ||
+ | Sending build context to Docker daemon | ||
+ | # После добавления .dockerignore с содержимым docs/ | ||
+ | docker image build -t diamol/ | ||
+ | Sending build context to Docker daemon | ||
+ | </ | ||
+ | |||
+ | ✔ Надо выбирать базовый образ с минимальным набором необходимых функций, | ||
+ | |||
+ | ✔ Контроль совместно устанавливаемых " | ||
+ | <code bash> | ||
+ | FROM debian: | ||
+ | RUN apt-get update | ||
+ | RUN apt-get install -y curl=7.52.1-5+deb9u9 | ||
+ | RUN apt-get install -y socat=1.7.3.1-2+deb9u1 | ||
+ | |||
+ | # -20 МБ | ||
+ | FROM debian: | ||
+ | RUN apt-get update \ | ||
+ | && apt-get install -y --no-install-recommends \ | ||
+ | curl=7.52.1-5+deb9u9 \ | ||
+ | socat=1.7.3.1-2+deb9u1 \ | ||
+ | && rm -rf / | ||
+ | </ | ||
+ | |||
+ | ✔ Распаковка только тех файлов из внешних архивов, | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | Большой размер образа | ||
+ | <code bash> | ||
+ | FROM diamol/base | ||
+ | |||
+ | ARG DATASET_URL=https:// | ||
+ | |||
+ | WORKDIR /dataset | ||
+ | |||
+ | RUN wget -O dataset.tar.gz ${DATASET_URL} && \ | ||
+ | tar xvzf dataset.tar.gz | ||
+ | |||
+ | WORKDIR / | ||
+ | RUN cp Day1.svm Day1.bak && \ | ||
+ | rm -f *.svm && \ | ||
+ | mv Day1.bak Day1.svm | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | Маленький размер образа | ||
+ | <code bash> | ||
+ | FROM diamol/base | ||
+ | |||
+ | ARG DATASET_URL=https:// | ||
+ | |||
+ | WORKDIR /dataset | ||
+ | |||
+ | RUN wget -O dataset.tar.gz ${DATASET_URL} && \ | ||
+ | tar -xf dataset.tar.gz url_svmlight/ | ||
+ | rm -f dataset.tar.gz | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Есть более удобный подход, | ||
+ | <code bash> | ||
+ | FROM diamol/base AS download | ||
+ | ARG DATASET_URL=https:// | ||
+ | RUN wget -O dataset.tar.gz ${DATASET_URL} | ||
+ | |||
+ | FROM diamol/base AS expand | ||
+ | COPY --from=download dataset.tar.gz . | ||
+ | RUN tar xvzf dataset.tar.gz | ||
+ | |||
+ | FROM diamol/base | ||
+ | WORKDIR / | ||
+ | COPY --from=expand url_svmlight/ | ||
+ | </ | ||
+ | |||
+ | Поэтапный докерфайл можно останавливать на любом этапе: | ||
+ | <code bash> | ||
+ | docker image build -t diamol/ | ||
+ | docker image build -t diamol/ | ||
+ | docker image build -t diamol/ | ||
+ | |||
+ | # Размеры образов | ||
+ | docker image ls -f reference=diamol/ | ||
+ | </ | ||
+ | Это даёт возможность запустить контейнер из образа нужного этапа и исправлять ошибки, | ||
+ | |||
+ | Поэтапный докерфайл в этом случае - лучший выбор, т. к. на выходе имеется оптимизированный образ и используется более простой синтаксис - не надо, как во втором варианте, | ||
+ | |||
+ | ✔ Сокращение времени сборки. В поэтапном докерфайле самые редко меняющиеся части надо помещать в начало (опубликованные порты, переменные окружения, | ||
+ | |||
+ | ===== 18. Управление конфигурацией приложения в контейнерах ===== | ||
+ | |||
+ | ==== Многоуровневый подход ==== | ||
+ | |||
+ | 3 типа конфигурации: | ||
+ | - По релизу: | ||
+ | - По окружению: | ||
+ | - По функциям: | ||
+ | |||
+ | К примеру, | ||
+ | Разрабы запускают базовый образ без метрик Прометея для экономии ресурсов, | ||
+ | <code bash> | ||
+ | # default config | ||
+ | docker container run -d -p 8080:80 diamol/ | ||
+ | # loading a local config file override | ||
+ | docker container run -d -p 8081:80 -v " | ||
+ | # override file + environment variable, которая должна быть в формате JSON (здесь: | ||
+ | docker container run -d -p 8082:80 -v " | ||
+ | |||
+ | # check the config APIs in each container: | ||
+ | curl http:// | ||
+ | {" | ||
+ | curl http:// | ||
+ | {" | ||
+ | curl http:// | ||
+ | {" | ||
+ | </ | ||
+ | Это базовый подход, | ||
+ | |||
+ | ==== Помещение всех конфигураций в образ ==== | ||
+ | Можно зашивать в образ конфиги cразу для всех окружений, | ||
+ | <code bash> | ||
+ | # Стандартно appsettings.json сливается с appsettings.Development.json (проверка - http:// | ||
+ | docker container run -d -p 8083:80 diamol/ | ||
+ | # А здесь appsettings.json сливается с appsettings.Test.json (проверка - http:// | ||
+ | docker container run -d -p 8084:80 -e DOTNET_ENVIRONMENT=Test diamol/ | ||
+ | # А здесь appsettings.json сливается с appsettings.Production.json, | ||
+ | # local.json указывает использовать локальный файл БД вместо сервера БД для диагностики ошибок | ||
+ | docker container run -d -p 8085:80 -e DOTNET_ENVIRONMENT=Production -v " | ||
+ | # + имя релиза (проверка - http:// | ||
+ | docker container run -d -p 8086:80 -e DOTNET_ENVIRONMENT=Production -e release=CUSTOM -v " | ||
+ | </ | ||
+ | Тем не менее, зашивать всю конфигурацию невозможно, | ||
+ | |||
+ | ==== Загрузка конфигурации из среды выполнения ==== | ||
+ | Подобно .NET Core libraries или node-config, | ||
+ | |||
+ | TOML, зашитый в образ | ||
+ | <code yaml> | ||
+ | release = " | ||
+ | environment = " | ||
+ | |||
+ | [metrics] | ||
+ | enabled = true | ||
+ | |||
+ | [apis] | ||
+ | | ||
+ | [apis.image] | ||
+ | url = " | ||
+ | | ||
+ | [apis.access] | ||
+ | url = " | ||
+ | </ | ||
+ | |||
+ | Монтируемый TOML | ||
+ | <code yaml> | ||
+ | environment = " | ||
+ | |||
+ | [metrics] | ||
+ | enabled = false | ||
+ | </ | ||
+ | |||
+ | <code bash> | ||
+ | # Значения по умолчанию, | ||
+ | docker container run -d -p 8086:80 diamol/ | ||
+ | {" | ||
+ | # Монтирование доп. конфига (проверка - http:// | ||
+ | docker container run -d -p 8087:80 -v " | ||
+ | {" | ||
+ | </ | ||
+ | |||
+ | ==== Настройка старых приложений ==== | ||
+ | Чтобы заставить старое приложение, | ||
+ | - Прочесть из специального файла перекрывающие настройки | ||
+ | - Прочитать перекрывающие настройки из переменных окружения | ||
+ | - Слить полученные перекрывающие настройки воедино с приоритетом переменных | ||
+ | - Результат записать в файл в контейнере | ||
+ | |||
+ | <code bash> | ||
+ | # Настройки по умолчанию | ||
+ | docker container run -d -p 8089:80 diamol/ | ||
+ | {" | ||
+ | # Монтируется файл настройки и переменной указывается его расположение в контейнере | ||
+ | docker container run -d -p 8090:80 -v " | ||
+ | {" | ||
+ | </ | ||
+ | В это время в Докерфайле задаётся переменная с путём: | ||
+ | <code bash> | ||
+ | FROM diamol/ | ||
+ | # ... | ||
+ | RUN mvn package | ||
+ | # config util | ||
+ | FROM diamol/ | ||
+ | WORKDIR / | ||
+ | COPY ./ | ||
+ | RUN javac ConfigLoader.java | ||
+ | |||
+ | # app | ||
+ | FROM diamol/ | ||
+ | |||
+ | ENV CONFIG_SOURCE_PATH="" | ||
+ | CONFIG_TARGET_PATH="/ | ||
+ | |||
+ | CMD java ConfigLoader && \ | ||
+ | java -jar / | ||
+ | |||
+ | WORKDIR /app | ||
+ | COPY --from=utility-builder / | ||
+ | COPY --from=builder / | ||
+ | </ | ||
+ | |||