learning:diamol
Различия
Показаны различия между двумя версиями страницы.
| learning:diamol [18.05.2023 10:09] – [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 / | ||
| + | </ | ||
| + | |||
