Ещё раз о Docker

от автора

Часть 1. Введение

В данной статье хотел бы обобщить знания и опыт, которые накопились за время использования Docker.

И так, без долгих предисловий, в путь по океану наполненному синими китами!

Важно понять, контейнер - это НЕ виртуальная машина. Это лишь обёртка для одного процесса, который может порождать другие процессы. Он использует ядро и память той системы на которой запущен. Он "эфeмерен" и должен создаваться и уничтожаться без боязни и жалости. Контейнер занимает меньше места и ресурсов, чем виртуальная машина, но не может хранить состояния (по умолчанию)

Где Docker поможет, а где нет.

Docker имеет много преимуществ по сравнению со стандартным способом использования ПО.

  • Принятие решения об используемой архитектуре в проекте становится проще, так как приложению теперь без разницы где запускаться;

  • Все приложения упаковываются по стандарту Open Container Initiative;

  • В одном образе находятся нужные файловые системы, зависимости, директории, код;

  • ПО отделяется от “железа”;

  • Накладные расходы за использование технологии невелики.

Но не стоит думать, что Docker станет “серебряной пулей”, которая решит все проблемы и задачи. Docker не заменит и не старается заменить:

  • Виртуальные машины;

  • Облака;

  • ПО для развёртынвания хостов или конфигураций;

  • Среды разработки.

Терминология

Для успешного освоения docker нужно понимать как он устроен, а значит важно не путаться в терминологии.

  1. Клиент Docker — это то, с помощью чего мы можем управлять сервером Docker, вызывается командой docker

  2. Сервер Docker — это команда dockerd для запуска серверного процесса, который собирает и запускает контейнеры через клиент.

  3. Образы Docker — это любые образы, соответствующие стандарту OSI. Они состоят из нескольких слоёв файловой системы и метаданных. В них собраны все файлы, необходимые для выполнения приложения в контейнере.

  4. Контейнер Docker — это контейнер созданный из образа Docker. Один контейнер может существовать только один раз, но из одного образа может быть созданно любое количество контейнеров. Сам термин Docker-контейнер не является точным, так как Docker просто использует функционал операционной системы

  5. Минимальный хост — небольшой образ ОС, который поддерживает хостинг контейнеров.

Среда выполнения

Основной средой выполнения с сертификацией OSI является containerd — высокоуровневая среда по умолчанию в Docker и Kubernetes.

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

  • runc — используется чаще всего;

  • crun — написана на С;

  • Kata Containers — от Intel, Hyper и OpenStack. Может работать и с контейнерами, и с виртуальными машинами;

  • gVisor — изолированная среда выполнения, реализованная в пространстве пользователя;

  • Nabla Containers — ещё одна изолированная среда выполнения.

Архитектура Docker

Docker очень сложно устроен. Очень. Но нам, обычным пользователям, бояться этого не стоит, так как вся сложность спрятана под капотом, а мы видим лишь стандартную модель “клиент-сервер”.

Под капотом используются:

  • Различные механизмы ядра ОС, включая iptables;

  • Виртуальные сетевые мосты;

  • Cgroups;

  • Capabilities (привелегии);

  • Secure compututing mode (безопасный режим вычислений);

  • и многое другое.

Что видит пользователь:

  • Клиент;

  • Сервер;

  • Registry (хранит образы и их метаданные).

Сетевые порты

По умолчанию Docker использует три порта:

  • 2375 TCP — для нешифрованного трафика;

  • 2376 TCP — для зашифрованный SSL-соединений;

  • 2377 для режима Swarm.

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

Сокет Unix может располагаться по разным путям, но обычно он находится в /var/run/docker.sock.

В более новых версиях docker.sock создаётся в корневом каталоге пользователя, внутри .docker/run/, а на /var/run/docker.sock делается ссылка.

Сеть для контейнеров

Все контейнеры по умлочанию создаются с сетью в режиме bridge, где шлюзом является хостовая машина. Сетевым трафиком в основном упарвляет библиотека vpnkit.

Docker выделяет частные подсети из неиспользуемого блока согласно RFC 1918. Сеть соединяется мостом с локальной сетью хоста через интерфейс docker0.

Можно настроить контейнер так, чтобы он работал с сетью напрямую, не используя bridge-режим, для этого нужно при запуске контейнера указать --net=host. А можно и совсем отключить сеть, указав --net=none.

Контроль изменений

Из коробки у Docker есть два способа контроля изменений. Один отслеживает слои файловой системы, второй — теги этих образов.

Каждый слой в контейнере обладает уникальным хэшем для идентификации и все наборы изменений накладываются поверх предыдущих.

Теги же выбираются для образа в ходе стандартного процесса сборки. В начале работы с Docker удобно использовать тег latest, которым обычно помечается последняя доступная версия. Для продакшена же этот путь не подойдёт и там нужно укзывать конкретную версию приложения.

Плюсы и минусы

Ограниченная изоляция

По умолчанию контейнеры изолированы друг от друга, но не стоит думать, что эта изоляция полная. Так как процессы в контейнере это процессы основной операционной системы и отображаются в ps, то и ресуры ЦП и ОЗУ будут использовать общие, а также будут работать на том же ядре, что и основная ОС и другие контейнеры. Поэтому нужно дважды подумать, прежде чем давать контейнеру ресуррсов больше, чем ему требуется. Более того, многие контейнеры используют UID 0 для запуска процессов и это может привести к компрометации всей системы.

Контейнеры легковесны

Контейнер по своей сути это просто ссылка на многослойный образ файловой системы и включает в себя немного метаданных о конфигурации.

Приложения без сохранения состояния

Контейнеры не хранят в себе никакие данные. При удалении или пересоздании контейнера все наработки будут уничтожены. Во многих случаях даже состояние конфигурации выносится в переменные среды и не встраивается в контейнер, а применяется при развёртывании.

Сохранение состояния всё-таки возможно

Если же данные нужно сохранять, то для этого всегда можно использовать Volumes.

Часть 2. В которой рассказывается о работе с Docker образами

Вообще второй частью должна была идти инструкция по установке Docker на различные ОС, но так как эта тема довольно скучная да и может меняться время от времени, так что я просто оставлю это здесь.

Мы же двинемся дальше и посмотрим как работать с образами Docker.

Структура Dockerfile

Для создания образа Docker с помощью инструментов по умолчанию нужен dockerfile. Типичный dockerfile может содержать следующие параметры

FROM ubuntu:22.04.3ARG email="test@test.com"LABEL "maintainer"=$emailUSER rootENV AP /data/appENV SCPATH /etc/supervisor/conf.dRUN apt-get -y updateRUN apt-get install supervisorRUN mkdir -p /var/log/supervisorCOPY ./supervisord/conf.d/* $SCPATH/WORKDIR #AP RUN npm installCMD ["supervisord", "-n"]

FROM указывает на базовый образ ОС, который будет взят за основу. Базовым образом обычно выбирают Alpine linux, так как он занимает очень мало места, но есть одно но. Данный образ основан на библиотеке musl, а не на стандартной GNU C. Это может повлиять на приложения на основе Java и разрешение имён DNS. Плюс используется bin/sh, вместо /bin/bash. При необходимости нужные библиотеки можно доустановить.

ARG позволяет задавать переменные, которые доступны только во время сборки образа.

LABEL добавляет метки с помощью пар “ключ — значение”, которые затем можно использовать для поиска и идентификации контейнеров. Посмотреть метки образа можно с помощью команды docker image inspect.

USER позволяет менять пользователя в контейнере, так как по умолчанию Docker работает от рута.

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

RUN выполняет необходимые команды.

В данном примере указана команда apt-get -y update, но в реальном образе её использование не рекомендуется, так как образ перестанет быть воспроизводимым, так как версии пакетов в репозиториях со временем меняются.

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

ADD может копировать не только файлы, но и архивы. И даже из сети по ссылкам.

WORKDIR меняет рабочий каталог в образе для остальных инструкций в сборке.

CMD определяет команду, запускающую процесс, который будет выполняться в контейнере.

Порядок команд в Dockerfile влияет на время сборки. Изменения, которые происходят чаще всего должны располагаться как можно ближе к концу файла, чтобы при сборке пересобиралось как можно меньше слоёв.
Каждое использование COPY и RUN создаёт новый слой в образе, так что для ускорения сборки и уменьшения размера образа будет разумно располагать данные команды на одной строке.
Рекомендуется запускать в контейнере только один процесс. Эта рекомендация происходит из идеи, что контейнер должен выполнять одну функцию.

Сборка образа

Рядом с файлом dockerfile можно расположить файл .dockerignore, в котором можно указать файлы и каталоги, которые не должны попасть на хост Docker при сборке образа, например, туда можно добавить .git.

При сборке Docker полагает, что dockerfile находится в текущем каталоге. Если это не так, то путь можно указать с помощью аргумента -f.

Также Docker при сборке использует кэш, но бывают случаи, когда необходимо этого не делать, тогда нужно использовать ключ --no-cache.

Сборку образа можно запустить следующими командами:

docker image build -t example/docker-hello:latest .илиdocker build -t example/docker-hello:latest .

Точка в конце build обозначает текущий каталог.

Запуск образа

Запустить собранный образ можно командой

docker container run --rm -d -p 8080:8080 example/docker-hello:latest

где --rm указывает удалить контейнер после выполнения работы

-d запускает его в фоновом режиме

-p 8080:8080 указывает с какого порта хоста на какой порт контейнера сделать проброс.

После запуска определить IP-адрес контейнера можно, посмотрев вывод команды docker context list или при сборке задать переменную DOCKER_HOST. Также если был задан UNIX-сокет для DOCKER_ENDPOINT, то адрес будет 127.0.0.1.

Аргументы, которые мы задавали при сборке, можно переопределить при запуске с помощью аргумента --build-arg, например

docker image build --build-arg email=test2@test1.org

Также можно передавать значения переменных, в уже готовые контейнеры при запуске, но с помощью аргумента --env

docker container run --env WHO="blablabla"

Хранение образов

Есть несколько мест для хранения образов — публичные реестры и частные реестры.

Самым популярным публичным является Docker Hub.

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

Чтобы залогиниться в реестре нужно ввести команду docker login, потом свои логин и пароль, а Docker сохранит их и будет в будущем использовать при необходимости.

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

Для отправки образов в хаб необходимо использовать команду docker image build -t docker.io/USER_NAME/docker-test:latest, где docker.io — имя реестра, а USER_NAME — логин.

Если мы хоти заменить теги для созданного ранее образа, то можно использовать команду docker image tag.

Для поиска доступных образов используется команда docker search NEED_NAME.

Оптимизация образа

Для того чтобы знать что оптимизировать нужно уметь смотреть что есть внутри имеющегося контейнера. Для этого есть два способа.

Первый, можно экспортировать содержимое контейнера в архив и потом его посмотреть — docker container export CONTAINER_ID -0 test.tar.

Второй, подключиться напрямую к серверу Docker:

  1. Определяем где хранятся файлы образа — docker image inspect ubuntu:latest

  2. Подключаемся — docker container run --rm -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh

Многоэтапные сборки

Суть многоэтапной сборки проста. Например, нам нужно получить контейнер в котором будет какое-нибудь приложение на go. Для этого в dockerfile мы сначала запустим один контейнер, где будет проходить вся сборка, но будет много ненужного софта для работы самого приложения, а потом перенесём полученный артефакт в другой контейнер, в котором будет минимальное окружение, а значит и минимально занимаемый объём.

Пример:

# Этап 1: Сборка приложенияFROM golang:1.22-alpine AS builder# Устанавливаем рабочую директориюWORKDIR /app# Копируем go.mod и go.sum для предварительной загрузки зависимостейCOPY go.mod go.sum ./RUN go mod download# Копируем исходный кодCOPY . .# Компилируем приложениеRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \    go build -o /myapp .# Этап 2: Финальный образFROM alpine:latest# Устанавливаем рабочую директориюWORKDIR /# Копируем скомпилированный бинарь из этапа сборкиCOPY --from=builder /myapp .# Указываем команду запускаCMD ["./myapp"]

Здесь нужно обратить внимание на строки FROM golang:1.22-alpine AS builder и FROM alpine:latest. Именно в них происходит магия многоэтапной сборки.

Суммирование слоёв

В Docker слои всегда суммируются. Соответственно для уменьшения образа нужно удалять всё что не нужно для работы образа на том же слое, что и добавлять, располагая команды на одной строке. Например, удаление кэша при установке нужных пакетов можно производить командой

RUN dnf install -y httpd && dnf clean all

Нужно помнить, что слои формируются командами FROM, RUN, COPY и ADD.

Мультиархитектурные сборки

Для сборок под различные архитектуры был разработан плагин buildx и обычно он уже установлен в системе. Проверить его наличие можно с помощью команды:

docker buildx version

Чтобы собрать образы для разных платформ нужно ввести подобную команду:

docker buidx build --platform linux/amd64, linux/arm64 --tag docker.io/USERNAME/test:latest .

Часть 3. В которой мы знакомимся с контейнерами.

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

Вот такое определение приводится в libcontainer. От него мы и будем отталкиваться.

Одним из главных преимуществ контейнеров является эффективность по ресурсам, так как нам не нужен целый экземпляр ОС для выполнения приложения. Контейнер использует ядро хостовой ОС и память хостовой ОС, а следовательно нет накладных расходов на поддержание дополнительных слоёв и абстракций.

При использовании контейнеров мы можем выполнять только процессы совместимые с ядром, то есть нельзя запустить приложение для Windows напрямую на хосте Linux. И наоборот данное правило тоже работает.

Запуск контейнера

Запустить контейнер можно командой docker container run. На самом деле это команда включает в себя два шага:

docker container create - создаёт контейнер из образаиdocker container start - выполняет контейнер

Эти команды можно выполнять и по отдельности, применяя к ним те же ключи, что и к run.

Базовая конфигурация

Имя контейнера

По умолчанию контейнер получает имя состоящее из прилагательного и имени какого-нибудь известного человека, но это действие можно легко изменить, с помощью аргумента --name

docker container create --name="test-service" ubuntu:latest sleep 120

В пределах хоста у контейнеров должны быть уникальные имена.

Метки

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

docker container run --rm -d --name test-service -l deployer=Tzintch ubuntu:latest sleep 120

Это даст нам возможность потом при поиске фильтровать метаданные

docker container ls -a -f label=deployer=Tzintch

Выше были использованы несколько аргументов значение которых необходимо пояснить. rm — даёт команду удалить контейнер после выполнения задачи d — запускает контейнер фоном l — вешает лейбл

Также можно использовать аргументы t — для выделения псевдо-TTY i — указатель, что сеанс будет интерактивным и мы хотим оставить поток STDIN открытым

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

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

... hostname="test-pc" ...

DNS

По умолчанию контейнер внутри себя делает точную копию файла /etc/resolve.conf с хостовой машины, но и это поведение можно переопределить с помощью аргументов --dns и --dns-search. Если Вы не хотите указывать поисковый домен, то можно просто указать --dns-search=.

MAC-адрес

Мы имеем возможность задать нужный MAC-адрес для нашего контейнера. Делается это с помощью аргумента --mac-address. В основном это никогда не нужно, но может пригодиться, если возникнет необходимость зарезервировать определённые адреса для Docker и избежать конфликтов с другими службами, которые также могут использовать частные диапазоны адресов.

Тома хранилища

Если возникает необходимость сохранять часть настроек контейнера, то можно подключить внешнюю директорию к внутренней директории контейнера, делается это с помощью команды --mount или -v

docker container run --rm -ti --mount type=bind,target=/mnt/session_data,source=/data ubuntu:latest /bin/bashилиdocker container run --rm -ti -v /mnt/session_data:/data ubuntu:latest /bin/bash

По умолчанию директории монтируются в режиме чтения/записи. Если нужно примонтировать только в режиме чтения, то используется дополнение readonly для --mount и :ro для -v

docker container run --rm -ti --mount readonly  type=bind,target=/mnt/session_data,source=/data ubuntu:latest /bin/bashилиdocker container run --rm -ti -v /mnt/session_data:/data:ro ubuntu:latest /bin/bash

Если точки монтирования не созданы заранее, то они будут созданы автоматически.

Если на хосте включен SELinux, то могут возникнуть проблемы из-за отсутствия прав доступа, решается это с помощью опции -z

... -v /mnt/session_data:/data:z ...

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

Квоты

Docker может ограничивать контейнеры в ресурсах благодаря использованию cgroups, но эти возможности должны быть включены в ядре. Если cgroups работают, то ограничивать можно ЦП, память и SWAP.

ЦП

Доля процессорного времени

В Docker пул процессорного времени делится на 1024. Если мы хотим отдать контейнеру половину вычислительной мощности, то можно выделить ему 512 долей --cpu-shares=512 Это не значит, что другие ресурсы не смогут выполнять свои действия на данных мощностях, но будет ограничено время их выполнения, в то время как контейнер получит свои стандартные 100 мкс.

Привязка к процессору

Мы можем привязать контейнер к одному или нескольким ядрам ЦП --cpuset-cpus=0 Данный аргумент задаёт начало индекса и берёт первое ядро. Данный вариант можно комбинировать с выделением долей процессорного времени.

Упрощение квот на ресурсы ЦП

В современных версиях Docker выставлять ограничения на использование ЦП стало проще. Используется команда -cpus со значением от 0.01 до количества ядер в системе.

... -cpus=".25"

Команда docker container update позволяет динамически корректировать ограничения ресурсов.

ОЗУ

В отличие от ограничений ЦП, ограничения на ОЗУ действуют жёстко и контейнер не получит больше памяти, чем ему было выделено и если выйдет за лимиты, то начнёт использовать файл подкачки.

Задаются ограничения аргументом --memory

... --memory 512m ...

Данное ограничение выделит контейнеру 512 МБ ОЗУ и столько же файла подкачки. Если же задать дополнительно параметр --memory-swap -1, то размер используемого свопа будет неограничен.

Если контейнер упрётся в ограничения памяти, то к нему может прийти OOM Killer и убить его. Чтобы отключить его для данного контейнера можно использовать аргументы --oom-kill-disable и --oom-score-adj, но лучше этого не делать.

Блочный ввод-вывод

При желании или необходимости можно также ограничить скорости чтения и записи на диск. Механизм будет действовать подобно ограничениям на ЦП.

--device-read-bps - ограничения скорости чтения с устройства (байт в секунду)--device-read-iops - ограничения скорости чтения с устройства (операций в секунду)--device-write-bps - ограничения скорости записи на устройство (байт в секунду)--device-write-iops - ограничения скорости записи на устройство (операций в секунду)

Запуск контейнера

Запустить контейнер можно командой docker container start, но чтобы знать какой запускать нужно для начала узнать или его имя, или хэш, или часть хэша, главное, чтобы эта часть была уникальной, сделать это можно с помощью команды

docker container ls -aилиdocker ps -a

Если контейнеров много, то можно использовать фильтр

docker container ls -a --filter ancestor=redis:2.8

Автоматический перезапуск контейнера

Часто возникает необходимость, что контейнер должен автоматически перезапускаться после завершения работы. Данное поведение управляется с помощью аргумента --restart, которое может принимать четыре значения

no - никогдаalways - всегдаon-failure - при сбое (количетсво попыток можно ограничить on-failure:3)unless-stopped - пока не остановят

Остановка контейнера

Остановить контейнер можно командой docker container stop. Данная команда отправляет контейнеру сигнал SIGTERM контейнер будет выключен. Если же этого не произошло и процесс зависает, то можно использовать аргумент -t.

docker container stop -t 25

В этом случае будет отправлен сигнал SIGTERM и если он не отработает, то через 25 секунд (по умолчанию — 10), то будет отправлен сигнал SIGKILL и завершит процесс принудительно. После этого его можно опять запустить при необходимости с той же конфигурацией. Мы можем повторно запускать контейнер без создания новых до тех пор пока не удалим его. Можно использовать сразу принудительно завершение docker container kill

Приостановка контейнера

Есть возможность приостановить работу контейнера, сохранив за ним выделенные ресурсы и оставить записи в таблице процессов — docker container pause. Это отправит сигнал SIGSTOP и поставит контейнер на паузу. Запустить снова можно с помощью docker container unpause.

Удаление контейнера

Удалить контейнер можно с помощью команды docker container rm, но сделать это можно только после остановки контейнера. Если же останавливать контейнер желания и необходимости нет, то можно сделать удаление с помощью аргумента -f или --force.

Далее можно удалить и образы, если они больше не нужны. Список всех образов выводится командой docker image ls, а потом нужный удаляется с помощью docker image rm. Образы можно удалять только тогда, когда их не используют контейнеры

Удаление всего, что не используется

Если нужно удалить все контейнеры, образы и сети, которые не связаны ни с чем, то можно применить команду docker system prune. Если нужно удалить всё, а не только несвязанное, то можно добавить аргумент -a.

Удалить все контейнеры можно командой

docker container rm $(docker container ls -a -q)

Удалить все образы можно командой

docker image rm $(docker image -q)

Часть 4. Собираем информацию в Docker

Мало развернуть контейнер, нужно за ним ещё и наблюдать. Способов для этого много и мы рассмотрим их ниже.

В Docker есть команды, которые позволяют облегчить различные задачи, например:

  • Просмотр версии Docker;

  • Просмотр информации о сервере;

  • Загрузка обновлений образов;

  • Изучение контейнеров;

  • Вход в работающий контейнер;

  • Возврат результатов;

  • Просмотр журналов;

  • Мониторинг статистики и т.д.

Просмотр версии Docker

docker version

Версии компонентов могут различаться

Информация о сервере

docker system info

Выведется информация об образах, контейнерах, плагинах и т.д. По умолчанию файл конфигурации Docker содержится в файле /etc/docker/daemon.json. Для большинства аргументов, которые передаются в dockerd здесь можно задать постоянные значения.

Загрузка обновлений образов

docker image pull ubuntu:latest 

Docker не будет автоматически обновлять локальный образ.

Изучение контейнера

docker container inspect

Выдаёт подробный вывод о контейнере.

Изучение командной оболочки

docker container run -it ubuntu:latest /bin/bash

Если после запуска мы посмотрим на запущенные процессы, то увидим только bash. Это происходит потому, что в контейнере ничего не запускается автоматически.

Возврат результата

docker container run ubuntu:latest /bin/cat /etc/passwd

В ответ мы получим содержимое файла /etc/passwd. Символ | в контейнер не передаётся. Если нужно выполнить пайп именно в контейнере, то нужно передавать строку для выполнения совместно с /bin/bash.

Что происходит в контейнере

Exec

docker contanter exec -ti ... /bin/bash

Данной командой мы откроем TTY в контейнере и перейдём в него.

Volume

docker volume ls

Позволяет посмотреть список томов, которые были созданы самим Docker через docker volume create my-data.

docker volume rm

Позволяет удалить том, если он больше не нужен.

Журналирование

docker container logs

Позволяет вывести логи из контейнера. Файлы из этого журнала хранятся в /var/lib/docker/containers/id/. Чтобы не смотреть логи локально можно настроить их отправку во внешние системы с помощью настроек файла daemon.json.

Мониторинг

stats

docker container stats

Выведет динамический срез информации о работе контейнера с информацией:

  • Имя контейнера;

  • Потребление ресурсов ЦПУ, где 100% — одно ядро;

  • ОЗУ;

  • Сеть, Docker перенаправляет трафик на порты контейнера даже если они ещё не запущены. Также данную информацию можно получить через API /stats

events

docker system events

Показывает все события, которые происходят в Docker.

cAdvisor

Инструмент от компании Google, который сам ставится через Docker или обычным бинарником и позволяет следить за контейнерами. Также метрики с него можно отгружать в Prometheus

Часть 5. Учимся отлаживать контейнеры

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

Самым простым способом заглянуть внутрь контейнера является команда

docker container top

В ответ мы получим список процессов внутри контейнера, упорядоченный по PID. Стоит учесть, что имя пользователя для некоторых UID может отличаться в контейнере и в хостовой машине, а где-то его может и не быть совсем.

Получить более детальную информамцию можно другими способами. Можно использовать стандартные утилиты Linux, например strace, например:

sudo straсe -p PROCESS_NUMBER

Подобным образом можно использовать и lsof

sudo lsof -p PROCESS_NUMBER

Все пути в выводе будут указаны относительно вида на файловую систему изнутри контейнера.

Всегда есть возможность завершить в контейнере отдельную службу с помощью kill и контейнер продожит работать, только если таким способом не убить процесс верхнего уровня (PID 1). Но лучше так не делать, в конце концов, проще создать новый контейнер, чтобы не произошло каких-то неожиданных эффектов.

Отладка сетей

Посмотреть существующие сети можно командой docker network ls. Чтобы посмотреть, какие контейнеры подключены к определённой сети, можно подать команду docker network inspect.

История образа

С помощью команды docker image history можно отследить происхождение и базовый образ. Данная команда показывает все слои, их размеры и команды с помощью которых образ собирался. Данная команда поможет определить почему образ получился большего объёма, чем ожидалось и поможет понять как можно его улучшить.

Изучение контейнера

На диске есть каталог, который выделяется для контейнера, обычно это /var/lib/docker/containers и в нём лежат очень длинные хэши. Можно ввести команду docker container ls и взять короткий хэш с помощью которого и найти нужный длинный. В каталоге будут файлы, которые монтируются к контейнеру.

Также можно проверить записывает ли контейнер что-либо в свою внутреннюю файловую систему. Сделать это можно с помощью команды docker container diff, например:

docker container diff nginx-fs C /run

В ответе будут выданы строки с литерами A и C в начале. A означает added, то есть было добавлено, а C — changed, то есть было изменено.

Часть 6. Знакомимся с Docker Compose

Изначально Docker Compose был отдельным приложением написанным на Python и запускался через docker-compose. Теперь это считается первой версией. Сейчас используется docker compose, это вторая версия, которая полностью переписана на go и является плагином для Docker. Docker Compose обычно использует один декларативный YAML-файл для каждого проекта — docker-compose.yaml. Этот файл легко читается и будет работать одинаково у любого пользователя. В данном файле указываются все важные требования для каждого сервиса, а также их взаиможействие друг с другом. Также происходит валидация и проверка логики.

Содержимое файла

version: '3.8'services:  test-agent:    image: 'test-agent:1.3.1'    restart: unless-stopped    container_name: test-agent    hostname: test-agent    environment:      - USERNAME=login      - PASSWORD=password      - NGINX_UPSTREAM_CONFIG=true      - UPSTREAM_SERVER=192.0.2.101:80    sysctls:    kernel.sem: "32000 1024000000 500 32000"    ports:      - "80:80/tcp"    volumes:      - ./logs:/var/log/nginx    networks:      botnetnetworks:  botnet:    driver: bridge

Первая строка указывает версию языка конфигурации Compose. Оставшийся код разделён на две части: services и networks.

Services

Самая важная часть конфигурации. Здесь указывается какие приложения ы хотим запускать и с какими параметрами. Файл docker-compose.yaml может ссылаться на переменные среды в формате ${ИМЯ ПЕРЕМЕННОЙ}, чтобы извлекать секреты, не сохраняя их в файле.

Networks

Данный раздел позволяет определить именованую сеть с указанием её типа.

Запуск сервисов

После заполнения compose-файла можно проверить его правильность

docker compose config

Если всё хорошо, то команда выведет наш файл. Если возникнут проблемы, то выдаст ошибку с дополнительными сведениями.

Собрать любые нужные контейнеры можно с помощью

docker compose build

Для запуска контейнера в фоновом режиме используем

docker compose up -d

Использование .env

Если нам необходимо передать в контейнер при его запуске несколько значений переменных, то проще всего это сделать с помощью .env файла. В него необходимо поместить нужное содержимое в виде пар ключ-значение, например:

PASSWORD=pswd01

Далее запускаем контейнер.

Если файл находится в той же директории, что и docker-compose.yaml, то он будет считан автоматически.

Сначала Docker Compose считывает все значения по умолчанию из файла docker-compose, затем перезаписывает их данными из файла .env, а уже потом берёт значения из переменной среды пользователя. А это значит, что в файле .env должны быть только общие для всех пользователей данные.

Заключение

В данной статье я постарался охватить самое необходимое для использования Docker, что бывает нужно часто и без чего очень сложно. Здесь не упомянуты утилиты для управления контейнерами, такие как LazyDocker или Portainer, и тем более не затронута тема оркестрации. Но это уже совсем отдельные вопросы, разбираться с которыми нужно отдельно и обстоятельно.

ссылка на оригинал статьи https://habr.com/ru/articles/1029710/