Apps: "Docker", "Docker Compose".
Задача: развёртывание в linux-е системы контейнеризации "Docker" вкупе со скриптом управления таковой "Docker Compose". Никаких сложностей, но делается так часто, что проще вынести описание отдельно и ссылаться на него при необходимости.
Одно из клёвейших открытий последних последних лет для меня - "Docker". Это программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации, позволяющее "упаковать" приложение со всем его окружением и зависимостями в контейнер, который может быть применён на любой современной Linux-системе с поддержкой "cgroups" в ядре и изоляцией "пространств имён (namespaces)".
Написан на языке "Go". Изначально использовал возможности LXC, с 2015 года применял собственную библиотеку, абстрагирующую виртуализационные возможности ядра Linux - "libcontainer". С появлением "Open Container Initiative" начался переход от монолитной к модульной архитектуре.
Для экономии дискового пространства проект использует файловую систему "Aufs" с поддержкой технологии каскадно-объединённого монтирования: контейнеры используют образ базовой операционной системы, а изменения записываются в отдельную область. Также поддерживается размещение контейнеров в файловой системе "Btrfs" с включённым режимом копирования при записи.
В состав программных средств входит сервер контроля контейнеров "docker" и сервер запуска контейнеров как таковых "containerd", а также клиентские средства, позволяющие из интерфейса командной строки управлять образами и контейнерами (включая API, позволяющий в стиле REST управлять компонентами программно).
Инструментарием "Docker"-а обеспечивается полная изоляция запускаемых контейнеров на уровне файловой системы (у каждого контейнера собственная корневая файловая система), на уровне процессов (процессы имеют доступ только к собственной файловой системе контейнера, а ресурсы разделены средствами "libcontainer"), на уровне сети (каждый контейнер имеет доступ только к привязанному к нему сетевому "пространству имён" и соответствующим виртуальным сетевым интерфейсам).
Установка системы контейнеризации приложений "Docker".
По состоянию на начало 2021-го несколько поколений "Docker" стали устаревшими и неподдерживаемыми: их дистрибутивы как правило называются "docker", "docker.io" или "docker-engine". Есть смысл профилактически зачистить систему от них:
# apt-get remove docker docker-engine docker.io containerd runc
Данные, по умолчанию располагающиеся в "/var/lib/docker/", могут быть наследованы новыми версиями, возможно с их автоматической конвертацией под следующие мажорные релизы.
Современный "Docker" выпускается в двух вариантах: "Community Edition" (бесплатная) и "Enterprise Edition" (проприетарная, платная). Мы будем работать со свободно распространяемым дистрибутивом, именуемым в APT-системе "docker-ce".
В централизованных репозиториях стабильных версий "Debian/Ubuntu" актуальные пакеты "Docker" отсутствуют - оно и понятно, технология переднего края, непрерывно развивающаяся. Единственный простой способ добычи дистрибутива в подключении репозитория разработчиков и установке оттуда.
Установим утилиты, необходимые для ручного подключения дополнительных APT-репозиториев:
# apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
Перед подключением нового APT-репозитория скачаем и применим PGP-ключ, которым подписано содержимое репозитория:
# curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
Создаём выделенный конфигурационный файл с описанием подключаемого APT-репозитория:
# echo -e "# Official APT-repository Docker-CE\ndeb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" >> /etc/apt/sources.list.d/docker.list
Обновляем сведения о доступном программном обеспечении и устанавливаем базовый набор подсистем "Docker":
# apt-get update && apt-get install docker-ce docker-ce-cli containerd.io
Обращаю внимание на то, что в качестве прослойки абстракции связей контейнеров "Docker-CE" с ядром "Linux" сейчас используется открытая реализация "Open Container Initiative (containerd.io)", сменившая использовавшуюся в предыдущем поколении "Docker" самодельную подсистему "docker-containerd".
Конечно, при остром желании можно установить "Docker" из DEB-пакетов, скачав их из официального хранилища дистрибутивов, потеряв при этом удобства автоматического обновления.
Сразу после установки "Docker" автоматически будут запущены два основных сервиса, о которых далее.
Прослойка изоляции и абстракции ("cgroups" и "namespaces", а также "aufs|btrfs") между ядром "Linux" и docker-контейнерами:
# systemctl status containerd.service
Централизованный сервис управления "виртуальными контейнерами" как таковыми:
# systemctl status docker.service
Система контейнеризации "Docker" вполне управляема непривилегированным пользователем - для этого достаточно включить его в специализированную группу "docker":
# usermod --append --groups docker username
Установка утилиты автоматизации запуска docker-контейнеров "Docker Compose".
Утилита "Docker Compose" написана на языке "Python" и представляет собой единственный файл-скрипт (размером в 16MB), обрабатывающий передаваемый ему конфигурационный YAML-файл и взаимодействующий с "Docker (engine)". Установка тривиальна и заключается в загрузке файла.
Утилита пока не вышла на уровень, когда какой-то из версий можно задать статус "stable" или "latest" - так что первым делом есть смысл сходить на сайт разработчиков, поискать прямую ссылку на последний проверенный релиз и уже его загружать:
# curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
Для совместимости обеспечиваем возможность запуска утилиты из двух типичных месторасположений исполняемых файлов:
# ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
Лучше сразу проверить, запускается ли утилита (попутно убедимся, что интерпретатор "Python" присутствует в операционной системе):
$ docker-compose --version
Автоматизация представления как файлов журналов docker-контейнеров.
Как правило, docker-образы принято собирать таким образом, чтобы журналы событий контейнеризированного приложения перенаправлялись в терминальные потоки STDOUT и STDERR, которые автоматически, посредством "logging driver", оказываются доступны для просмотра снаружи docker-контейнера через команду "docker logs".
Когда docker-контейнеров немного, их журналы событий можно выводить в текстовые файлы сопутствующими командами в скриптах запуска, например таким образом:
# docker logs --follow example-app > /var/log/docker/output-example-app.log 2>&1 &
Весьма скоро добираться до журналов событий docker-контейнеров путём запуска дополнительной команды с подобранными аргументами надоедает - хочется простоты и эмуляции привычного набора текстовых файлов, характерных для nix-систем. Для этого я написал простенький bash-скрипт, который прослушивает канал потока событий docker-подсистемы и отлавливает моменты запуска docker-контейнеров, сразу после этого подцепляя к ним считыватель журнала событий, выводящий таковой в текстовый файл:
# vi /usr/local/bin/docker-logs-collector.sh && chmod +x /usr/local/bin/docker-logs-collector.sh
#!/bin/bash
# Exit if another instance of this script is running
for PID in $(pidof -x `basename $0`) ; do [[ "${PID}" != "$$" ]] && exit 1 ; done
# Endless waiting accessibility "Docker Engine"
while true ; do
# Monitor containers startup and capture event logs output to a files
DOCKER_LOGS="/var/log/docker" ; [ -d "${DOCKER_LOGS}" ] || mkdir -p "${DOCKER_LOGS}"
docker events --filter 'event=start' --format '{{.Actor.Attributes.name}}' | \
while read NAME ; do
docker logs --follow "${NAME}" > "${DOCKER_LOGS}/docker-output-${NAME}.log" 2>&1 &
done
sleep 10
done
# Exit if another instance of this script is running
for PID in $(pidof -x `basename $0`) ; do [[ "${PID}" != "$$" ]] && exit 1 ; done
# Endless waiting accessibility "Docker Engine"
while true ; do
# Monitor containers startup and capture event logs output to a files
DOCKER_LOGS="/var/log/docker" ; [ -d "${DOCKER_LOGS}" ] || mkdir -p "${DOCKER_LOGS}"
docker events --filter 'event=start' --format '{{.Actor.Attributes.name}}' | \
while read NAME ; do
docker logs --follow "${NAME}" > "${DOCKER_LOGS}/docker-output-${NAME}.log" 2>&1 &
done
sleep 10
done
Запуск скрипта вручную, с отрывом его от текущей терминальной сессии:
# /usr/local/bin/docker-logs-collector.sh &
Нужно сделать так, чтобы скрипт автоматизации сбора журналов запускался автоматически после старта операционной системы и подсистемы контейнеризации "Docker". Ранее это делалось через скрипты "/etc/rc.local" подсистемы "System-V", но ныне принято решать такие задачи через регистрацию короткоживущей службы "Systemd".
# vi /etc/systemd/system/docker-logs-collector.service
[Unit]
Description=Service script of collecting logs and presenting them as files
Requires=docker.service containerd.service
After=docker.service
[Service]
ExecStart=/usr/local/bin/docker-logs-collector.sh &
ExecReload=/usr/local/bin/docker-logs-collector.sh &
Restart=/usr/local/bin/docker-logs-collector.sh &
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=default.target
Description=Service script of collecting logs and presenting them as files
Requires=docker.service containerd.service
After=docker.service
[Service]
ExecStart=/usr/local/bin/docker-logs-collector.sh &
ExecReload=/usr/local/bin/docker-logs-collector.sh &
Restart=/usr/local/bin/docker-logs-collector.sh &
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=default.target
Указываем "Systemd" перечитать и принять новую конфигурацию, а потом явно активируем и запускаем новый сервис:
# systemctl daemon-reload
# systemctl enable docker-logs-collector.service
# systemctl start docker-logs-collector
# systemctl enable docker-logs-collector.service
# systemctl start docker-logs-collector
Специально заниматься ротацией полученных от docker-контейнеров файлов журналов не вижу смысла, так как обычно они не разрастаются до катастрофических размеров, зачищаясь автоматически при пересоздании docker-контейнера - мы не приращиваем наши текстовые файлы журналов, а лишь воспроизводим в удобном виде те данные, что уже имеются в журнале docker-подсистемы.