Загрузил Michail S.

Docker: Вводный курс. Руководство по контейнеризации

THIRDEDmON
Docker: Up & Running
Shipping ReliaЫe Containers in Production
Sean Р. Капе
with Karl Matthias
Вeijing • Вoston • Famham • Sebastopol • Tokyo
o·REILLY'
Книги для программистов: https://clcks.ru/books-for-it
Шон П. Кейн, Карл Мапиас
Docker
Вводный курс
3-е изда.,.ие
Астана
«АЛИСТ»
2024
Книги для программистов: https://clcks.ru/books-for-it
УДК 004.2
ББК 32.973-018.2
юз
КЗЗ
КейнШ.П.
Docker. Вводный курс. - 3-е изд.: Пер. с анrл. / Ш. П. Кейн, К. Матrиас. Астана: АЛИСТ, 2024. - 352 с.: ил.
ISBN 978-601-09-7541-5
Книга подробно описывает инструментарий Docker и возможности использова­
ния контейнеров для развертывания программного обеспечения. Рассказано об ин­
теграции Docker и контейнеров Linux с облачными сервисами и Kubemetes. Описа­
ны методы сборки образов Open Container lnitiative (OCI), развертывания и адми­
нистрирования образов с использованием командной строки. Показано, как образы
OCI упрощают управление зависимостями и ускоряют процесс развертывания при­
ложений. Даны практические рекомендации по настройке и тестированию контей­
неров, подробно рассмотрены инструменты оркестрации, обеспечения безопасности
и конфигурирования Docker. В третьем издании особое внимание уделено инстру­
менту BuildKit, поддержке мультиархитектурных образов, а также контейнеров
в режиме rootless.
Для разработчиков, системных администраторов, архитекторов
и технических специалистов
УДКОО4.2
ББК 32.973-018.2
© 2024 ALIST LLP
Authori.7.ed Russian translation ofthe English edition of Docker: Up & Running, ЗЕ,
ISBN 9781098131821 © 2023 Sean Р. Кале and Karl Matthias.
This translation is puЫished and sold Ьу perrnission of O'Reilly Media, !пс., which owns or controls all rights to puЫish
and sell the same.
Авторизованный перевод с английского языка на русский издания Docker: Up & Running, ЗЕ,
ISBN 9781098131821 © 2023 Sean Р. Капе, Karl Matthias.
Перевод опубликован и продается с разрешения компании-правообладателя O'Reilly Media, !пс.
ISBN 978-1-098-13182-1 (англ.)
ISBN 978-601-09-7541-5 (каз.)
О Sean Р. Kane, Кarl Matthias, 2023
О Издание на русском язьrке. ТОО "АЛИСТ", 2024
Книги для программистов: https://clcks.ru/books-for-it
Оглавление
Отзывы о книге ............................................................................................................. 13
Вступительное слово ..................................................................................................... 15
Предисловие ................................................................................................................... 17
Кому следует прочесть эту книгу ................................................................................................17
Зачем читать эту книrу..................................................................................................................17
Содержание книги .........................................................................................................................18
Обозначения ...................................................................................................................................19
Использование примеров кода .....................................................................................................20
Онлайн-обучение O'Reilly ............................................................................................................ 20
Контактная информация ............................................................................................................... 21
Благодарности ................................................................................................................................ 21
Глава 1. Введение .......................................................................................................... 23
Ожидания от Docker ......................................................................................................................23
Преимущества рабочего процесса Docker .............................................................................25
В чем Docker не поможет..............................................................................................................27
Важная терминология ...................................................................................................................28
Заключение.....................................................................................................................................29
Глава 2. Общие сведения о Docker ............................................................................. 30
Упрощение рабочих процессов ....................................................................................................30
Широкая поддержка и внедрение ................................................................................................33
Архитектура ...................................................................................................................................35
Клиент-серверная архитектура ...............................................................................................35
Сетевые порты и сокеты Unix.................................................................................................36
Надежные инструменты ..........................................................................................................37
Интерфейс командной строки Docker ....................................................................................37
Docker Engine API ....................................................................................................................38
Сеть для контейнеров ..............................................................................................................38
Как использовать весь поте1ЩИал Docker....................................................................................40
Контейнеры - это не виртуальные машины ........................................................................40
Ограниченная изоляция........... : ...............................................................................................41
Контейнеры легковесны ..........................................................................................................42
Сдвиг в сторону неизменяемой инфраструктуры .................................................................42
Приложения без сохранения состояния .................................................................................43
Хранение состояния во внешнем хранилище ........................................................................44
Рабочий процесс Docker................................................................................................................44
Контроль изменений ................................................................................................................45
Слои файловой системы...............................................................................................45
Теги образов ..................................................................................................................45
Книги для программистов: https://clcks.ru/books-for-it
6
Оглавление
Сборка ....................................................................................................................................... 46
Тестирование ............................................................................................................................47
Упаковка ...................................................................................................................................48
Развертывание ..........................................................................................................................48
Экосистема Docker ...................................................................................................................49
Оркестрация ..................................................................................................................49
Неизменяемые атомарные хосты ................................................................................49
Дополнительные инструменты ....................................................................................50
Закточение.....................................................................................................................................51
Глава 3. Установка Doker ............................................................................................ 52
Клиент Docker ................................................................................................................................52
Linux..........................................................................................................................................53
UЬuntu Linux 22.04 (64-разрядная) ..............................................................................53
Fedora Linux 36 (64-разрядная) ....................................................................................54
macOS, Мае OS Х.....................................................................................................................55
Установщик с графическим интерфейсом..................................................................55
Установка Homebrew....................................................................................................55
Microsoft Windows 11 ..............................................................................................................56
Вкточение режима контейнеров Linux для Windows ...............................................56
Установка Chocolatey ...................................................................................................56
Сервер Docker ................................................................................................................................57
Linux с systemd .........................................................................................................................58
Сервер на базе виртуальной машины без Linux ....................................................................58
Vagrant ...........................................................................................................................58
Тестирование системы ..................................................................................................................63
UЬuntu .......................................................................................................................................63
Fedora ........................................................................................................................................63
Alpine Linux ..............................................................................................................................64
Сервер Docker ................................................................................................................................64
Закточение.....................................................................................................................................66
Глава 4. Работа с образами Docker............................................................................. 67
Структура Dockerfile .....................................................................................................................68
Сборка образа.................................................................................................................................70
Запуск образа .................................................................................................................................73
Аргументы сборки ...................................................................................................................74
Переменные среды для конфигурации...................................................................................74
Пользовательские базовые образы ...............................................................................................75
Хранение образов ..........................................................................................................................76
Публичные реестры .................................................................................................................76
Частные реестры ......................................................................................................................77
Аутентификация для входа в реестр ...................................................................................... 77
Создание учетной записи Docker Hub ........................................................................ 78
Вход в реестр.................................................................................................................78
Отправка образов в репозиторий ................................................................................80
Поиск образов в Docker Hub ........................................................................................81
Управление частным реестром ...............................................................................................82
Тестирование частного реестра ...................................................................................84
Книги для программистов: https://clcks.ru/books-for-it
Оглавление
7
Оmимизация образов ....................................................................................................................85
Контроль размера образов.......................................................................................................85
Многоэтапные сборки ..................................................................................................90
Суммирование слоев................................................................................................................92
Использование кеша слоев ......................................................................................................94
Кеширование каталога.............................................................................................................99
Решение проблем со сборками ...................................................................................................103
Отладка образов без BuildКit ................................................................................................103
Отладка образов с BuildКit ...................................................................................................105
Мулыиархитектурные сборки ...................................................................................................107
Заюпочение...................................................................................................................................111
Глава 5. Работа с контейнерами ............................................................................... 112
Что такое контейнер? ..................................................................................................................112
История контейнеров .............................................................................................................113
Создание контейнера...................................................................................................................114
Базовая конфигурация ...........................................................................................................115
Имя контейнера...........................................................................................................115
Метки ...........................................................................................................................115
Имя хоста.....................................................................................................................116
DNS ..............................................................................................................................118
МАС-адрес ..................................................................................................................118
Тома хранилиша .....................................................................................................................119
Квоты на ресурсы ...................................................................................................................121
Доля процессорного времени ....................................................................................122
Привязка к процессору...............................................................................................124
Упрощение квот на ресурсы процессора..................................................................125
Память .........................................................................................................................126
Блочный ввод-вывод .......................................... : .......................................................128
u/imit.............................................................................................................................129
Запуск контейнера .......................................................................................................................130
Автоматический перезапуск контейнера...................................................................................131
Остановка контейнера.................................................................................................................131
Принудительное завершение контейнера .................................................................................133
Приостановка и возобновление контейнера .............................................................................133
Очистка контейнеров и образов .................................................................................................134
Контейнеры Windows ..................................................................................................................136
Заюпочение...................................................................................................................................140
Глава 6. Сбор информации в Docker ....................................................................... 141
Просмотр версии Docker.............................................................................................................141
Информация о сервере ................................................................................................................143
Загрузка обновлений образов .....................................................................................................144
Изучение контейнера ..................................................................................................................145
Изучение командной оболочки ..................................................................................................147
Возврат результата ......................................................................................................................148
Что происходит в контейнере .....................................................................................................149
docker container ехес ...............'...............................................................................................149
docker vo/ume ..........................................................................................................................150
Журналирование..........................................................................................................................152
docker container /ogs...............................................................................................................152
Расширенные возможности журналирования .....................................................................154
Книги для программистов: https://clcks.ru/books-for-it
В
Оглавление
Мониторинг в Docker ..................................................................................................................156
Статистика контейнера ..........................................................................................................156
Статистика в командной строке ................................................................................156
Эндпоинт API stats .....................................................................................................158
Проверки работоспособности контейнеров.........................................................................160
docker system events ................................................................................................................163
cAdvisor ...................................................................................................................................164
Мониторинг с помощью Prometheus ..........................................................................................166
Самостоятельная работа .............................................................................................................170
Заключение...................................................................................................................................170
Глава 7. Отладка контейнеров ................................................................................. 171
Вьmод процесса ...........................................................................................................................171
Изучение процесса ......................................................................................................................176
Контроль процессов ....................................................................................................................178
Изучение сети ..............................................................................................................................180
История образа.............................................................................................................................183
Изучение контейнера ..................................................................................................................184
Изучение файловой системы ......................................................................................................185
Заключение................................................................................................................................... 186
Глава 8. Принципы работы Docker Compose ......................................................... 187
Настройка Docker Compose ........................................................................................................188
Запуск сервисов ........................................................................................................................... 196
Принципы работы Rocket.Chat ...................................................................................................198
Применение Docker Compose .....................................................................................................205
Управление конфигурацией .......................................................................................................207
Значения по умолчанию ........................................................................................................207
Обязательные значения .........................................................................................................209
Файл dotenv .................... :.......................................................................................................210
Заключение...................................................................................................................................211
Глава 9. Переход на контейнеры в рабочей среде ................................................. 212
,Подготовка к развертыванию .....................................................................................................212
Docker в рабочих средах .............................................................................................................213
Управление заданиями ..........................................................................................................214
Ограничения ресурсов ...........................................................................................................215
Сети .........................................................................................................................................216
Конфигурация ........................................................................................................................216
Упаковка и поставка ..............................................................................................................217
Журналирование ....................................................................................................................217
Мониторинг ............................................................................................................................217
Планирование .........................................................................................................................218
Распределенные планировщики ................................................................................218
Оркестрация ................................................................................................................219
Обнаружение сервисов ..........................................................................................................220
Заключение по рабочей среде ...............................................................................................222
Docker в конвейере DevOps ........................................................................................................222
Краткий обзор ........................................................................................................................222
Внешние зависимости ...........................................................................................................225
Заключение...................................................................................................................................226
Книги для программистов: https://clcks.ru/books-for-it
Оглавление
1
9
Глава 1 О. Масштабирование контейнеров ............................................................. 227
Режим Docker Swarm ...................................................................................................................228
Kubernetes .....................................................................................................................................238
Инструмент Minjkube ............................................................................................................239
Общие сведения о Minikube.......................................................................................239
Установка Minikube ....................................................................................................239
Запуск Kubernetes .......................................................................................................242
Команды minikube .......................................................................................................244
Kubernetes Dashboard ..................................................................................................244
Контейнеры и поды Kubernetes .................................................................................245
Практический пример ................................................................................................247
Развертывание реалистичного стека .........................................................................249
Определение сервиса ..................................................................................................250
Определение PersistentVo/umeC/aim .........................................................................251
Определение Dep/oyment............................................................................................252
Развертывание приложения .......................................................................................253
Увеличение масштаба ................................................................................................255
kubectl API ...................................................................................................................256
Kubernetes, интегрированный в Docker Desktop .................................................................258
Kind .........................................................................................................................................259
Amazon ECS и Fargate .................................................................................................................261
Базовая настройка АWS ........................................................................................................262
Настройка ролей IАМ ............................................................................................................262
Настройка А WS CLI ..............................................................................................................262
Установка ....................................................................................................................263
Конфигурация .............................................................................................................263
Экземпляры контейнеров ......................................................................................................264
Задачи......................................................................................................................................265
Тестирование задачи ..............................................................................................................273
Остановка задачи ...................................................................................................................273
Заключение...................................................................................................................................275
Глава 11. Расширенные концепции ......................................................................... 276
Контейнеры в деталях .................................................................................................................276
cgroups ....................................................................................................................................277
Файловая система /sys ................................................................................................ 278
Пространства имен ................................................................................................................281
Изучение пространств имен ......................................................................................283
Безопасность ................................................................................................................................284
U/D О ....................................................................................................................•.....•............285
Режим root/ess ........................................................................................................................288
Привилегированные контейнеры .........................................................................................292
Secure Computing Mode .........................................................................................................295
SELinux и AppArmor ..............................................................................................................299
Демон Docker ..........................................................................................................................300
Расширенная конфигурация .......................................................................................................301
Сети .........................................................................................................................................302
Сеть хоста ....................................................................................................................304
Конфигурирование сетей ...........................................................................................306
Хранилище ................................................................................................................................... 308
Книги для программистов: https://clcks.ru/books-for-it
10
1
Оглавление
nsenter ...........................................................................................................................................312
Отладка контейнеров без командной оболочки ..................................................................313
Структура Docker ........................................................................................................................315
Альтернативные среды выполнения ..........................................................................................320
gVisor ......................................................................................................................................320
Заюпочение...................................................................................................................................323
Глава 12. Развитие экосистемы ................................................................................ 324
Клиентские инструменты ...........................................................................................................324
nerdctl ......................................................................................................................................324
podman и buildah ....................................................................................................................325
Универсальные инструменты разработчика .............................................................................327
Rancher Desktop ......................................................................................................................327
Podman Desktop ......................................................................................................................328
Заюпочение...................................................................................................................................329
Глава 13. Устройство контейнерной платформы ................................................. 330
Приложение двенадцати факторов ............................................................................................331
Кодовая база ...........................................................................................................................331
Зависимости............................................................................................................................332
Конфигурация ........................................................................................................................333
Сторонние службы .................................................................................................................334
Сборка, выпуск, выполнение ................................................................................................335
Процессы ................................................................................................................................335
Привязка портов .....................................................................................................................336
Параллелизм ...........................................................................................................................336
Утилизируемость .................... :..............................................................................................337
Паритет среды разработки и рабочей среды .......................................................................337
Журналы .................................................................................................................................338
Администрирование ..............................................................................................................338
Заюпочение по 12 факторам....................................................•.............................................339
Манифест реактивных систем .................................................................................................... 339
Быстрый отклик .....................................................................................................................339
Отказоустойчивость...............................................................................................................339
Масштабируемость ................................................................................................................ 339
Ориентация на события .........................................................................................................340
Заюпочение...................................................................................................................................340
Глава 14. Заключение ................................................................................................. 341
Дальнейшее развитие ..................................................................................................................341
Зачем нужен Docker ....................................................................................................................342
Рабочий процесс Docker..............................................................................................................343
Меньше программных пакетов для развертывания..................................................................343
Оптимизация хранения и получения .........................................................................................344
Результат ......................................................................................................................................345
Итоги.............................................................................................................................................345
Предметный указатель............................................................................................... 346
Об авторах..................................................................................................................... 349
Об изображении на обложке ...................................................................................... 350
Книги для программистов: https://clcks.ru/books-for-it
Моей жене и детям, благодаря которым в моей жизни столько смысла.
Моим родителям, которые научwzи меня слушать свой разум и сердце.
Моей сестре, которая вдохновляет меня смотреть на мир
глазами других людей.
-ШонП. Кейн
Моей маме, которая npuвwza мне любовь к чтению,
и моему папе, который читал мне.
Моей жене и детям, которые всегда поддерживают меня.
- Карл Маттиас
Книги для программистов: https://clcks.ru/books-for-it
Отзывы о книге
В книге "Docker. Вводный курс" описываются не только теоретические
преимущества Docker, но и практические аспекты использования контейнеров
в рабочей среде.
- Келси Хайтауэр (Kelsey Нightower),
руководитель по работе с разработчиками,
Google Cloud Platform
Книга "Docker. Вводный курс" подробно описывает базовые принцшn,1
и практические аспекты работы с Docker на основе многолетнего опыта.
- Лиз Райс (Liz Rice),
руководитель по опенсорс-проектам с eBPF в Isovalent
Книга "Docker. Вводный курс" поможет вам строить современные, надежные
и высокодоступные системы.
-Михай Тодор (Mihai Todor),
главный инженер, TLCP
Несколько лет назад мне пришлось перейти с виртуальных машин на контейнеры.
Мне нравится сначала изучать новые технологии с точки зрения пользователя
и только потом углубляться в технические детали. Благодаря книге
"Docker. Вводный курс" я на практике познакомился с Docker и контейнерами,
и довольно быстро стал применять эти навыки в работе.
- Фабиана Фиденчио (Fablano Fidencio),
инженер по облачной оркестрации, Intel Corporation
Книги для программистов: https://clcks.ru/books-for-it
Вступительное слово
Контейнеры сегодня используются везде - от локальных сред разработки до кон­
вейеров непрерывной интеграции и масштабных рабочих нагрузок. В чем секрет их
популярности, что ждет нас дальше и какая нам от этого польза?
В прошлом многие технологии обещали возможность переносить приложения
между платформами без переписывания кода, но мы не всегда получали желаемое,
и нам в любом случае нужна была среда выполнения (и дополнительные зависимо­
сти) для запуска приложения. С контейнерами мы можем собрать приложение один
раз, а затем запускать где угодно. С их помощью мы упаковываем приложения, не­
обходимую среду выполнения, файлы конфигурации и все зависимости в один про­
граммный пакет. Если на целевом компьютере есть среда выполнения контейнеров,
приложение будет работать. Больше не нужно переживать о том, что приложение
корректно работало на компьютере разработчика, но запускается с проблемами
в другой среде.
Мы управляем жизненным циклом контейнера и приложениями, упакованными
в него, с помощью стандартного API. Этот API предоставляет единый интерфейс
для разнородных компонентов развертывания, так что специалистам по сопровож­
дению не приходится разбираться в тонкостях развертывания и запуска приложе­
ний, а значит, они могут сосредоточиться на своих основных обязанностях управлении инфраструктурой, обеспечении безопасности, соблюдении требований
и поддержании непрерывной работы.
Кроме того, интерфейс открывает простор для инноваций. Оркестраторы контейне­
ров, вроде Kubemetes и Nomad, с помощью этой плоскости управления повышают
уровень абстракции, упрощая управление контейнеризированными рабочими на­
грузками в большом масштабе. Технологии service mesh, например lstio, дополняют
оркестраторы, решая такие задачи, как обнаружение сервисов и обеспечение безо­
пасности в стеке приложений.
Стандартный API заметно упрощает жизнь и разработчикам, ведь с помощью од­
ной команды можно создать целую среду разработки. В конвейере непрерывной
интеграции мы можем запускать контейнеры с базами данных, очередями или дру­
гими зависимостями, которые нужны приложению, для интеграционного, "дымово­
го" (smoke) и сквозного тестирования. Наконец, благодаря переносимости контей­
неров разработчики несут ответственность за свою работу в рабочей среде, под­
держивая принципы DevOps.
Книги для программистов: https://clcks.ru/books-for-it
16
1
Вступительное слово
Версии сред выполнения регулярно обновляются, в одном проекте может сочетать­
ся несколько языков программирования, практики DevOps, вроде сине-зеленого
развертывания и канареечных релизов, стали нормой, а приложения достигают не­
мыслимых масштабов, и в этих условиях разработчики по всему миру используют
контейнеры для сборки и развертывания своих приложений. Контейнерами больше
никого не удивишь. Более того, они стали стандартом для упаковки и развертыва­
ния приложений.
Работать с контейнерами не так просто. Я знаю, что говорю, ведь я применяю их
почти десять лет и вдоба�;юк учу этому людей по всему миру.
Опираясь на богатый опыт, Шон и Карл написали понятное и подробное руко­
водство по использованию контейнеров с Docker. В этой книге вы найдете все, что
понадобится для начала и продуктивного продолжения работы с Docker - от уста­
новки до осознанной практики и сборки образов, работы с контейнерами, проверки
сборок и среды выполнения, а также развертывания контейнеров в рабочей среде.
Более того, Шон и Карл вдаются в мельчайшие детали, рассказывая о таких базо­
вых компонентах, как контрольные группы и пространства имен в Linux, благодаря
которым возможна работа контейнеров. Экосистема Docker непрерывно развивает­
ся и расширяется, и книга не обходит эту тему стороной.
В предисловии ко второму изданию "Docker. Вводный курс" Лора Тако (Laura
Tacho) подметила, что при использовании облачных технологий, вроде виртуаль­
ных машин и контейнеров, мы не обязаны ограничиваться чем-то одним - они
прекрасно сочетаются друг с другом. С этим нельзя не согласиться, ведь сегодня
появляются технологии на основе легких виртуальных машин, такие как Kata
Containers, которые позволяют нам реализовать главные преимущества обеих тех­
нологий - изолированность виртуальных машин и переносимость контейнеров.
Контейнеры сегодня присутствуют везде, и рано или поздно придется с чего-то на­
чать долгий и трудный путь их освоения. Если в качестве первого шага вы взяли
эту книгу, вы сделали правильный выбор. Два опытных специалиста покажут вам
дорогу, и я желаю вам удачи на этом пути, даже если с этой книгой вы сможете
обойтись без нее.
Счастливой контейнеризации!
- Раджу Ганди (Raju Gandhi), основатель DejМacro Software, LLC,
и автор книг "Head First Software Architecture", "Head First Git и JavaScript Next"
@looselytyped Колумбус, Огайо
Апрель 2023 года
Книги для программистов: https://clcks.ru/books-for-it
Предисловие
Эта книга предназначена для всех, кто хочет с практической точки зрения разо­
браться в контейнерах Linux и понять, как с их помощью улучшить разработку и
сопровождение приложений. В большинстве современных рабочих систем и про­
цессов интеграции разработчики и специалисты по сопровождению должны четко
понимать, как работают контейнеры Linux и как с их помощью можно повысить
воспроизводимость и предсказуемость системы. Вы также узнаете, как собирать,
тестировать, развертывать и отлаживать контейнеры Linux в экосистеме Docker.
Кроме того, мы рассмотрим некоторые популярные инструменты оркестрации для
контейнеров Linux. Наконец, мы обсудим рекомендации по безопасности и органи­
зации контейнерной среды.
Кому следует прочесть эту книгу
Эта книга будет полезна тем, кто хочет наладить сложный рабочий процесс разра­
ботки и развертывания программного обеспечения в большом масштабе. Если вас
интересуют контейнеры Linux, Docker, Kubernetes, DevOps и большие масштаби­
руемые инфраструктуры, эта книга для вас.
Зачем читать эту книгу
Сегодня в Интернете можно найти много обсуждений, статей и выступлений
о Docker, и в некоторых даже предсказывается закат Docker.
Зачем же тратить драгоценные часы на чтение этой книги?
Да, сегодня есть альтернативы, но именно с Docker началось широкое распростра­
нение контейнеров Linux. Пока разработчики Docker не создали формат образов
контейнеров и не поспособствовали созданию множества базовых библиотек
для систем контейнеризации, работать с контейнерами Linux было очень сложно.
В основном они использовались крупными облачными хостинrами для масштаби­
рования и защиты систем от недоверенноrо пользовательского кода.
Docker все изменил.
Вы можете найти много информации о Docker и контейнерах Linux, но экосистема
постоянно развивается, и лучшие практики меняются. Если вы вычитали какой-то
прием в статье четырехлетней давности, он уже может утратить свою актуальность,
Книги для программистов: https://clcks.ru/books-for-it
18
Предисловие
даже если все еще будет работать. Пока мы писали первое издание книги, компания
Docker, lnc. успела выпустить четыре версии Docker и несколько важных инстру­
ментов. За семь лет между первым и третьим изданиями книги многое изменилось.
Docker стал стабильнее, появилось немало дополнительных инструментов с похо­
жими функциями. Теперь нам доступны неплохие варианты почти для каждого
аспекта рабочего процесса DevOps. Потребуется немало времени и сил, чтобы изу­
чить возможности контейнеров Linux и Docker, понять, как они впишутся в ваш
рабочий процесс, и правильно интегрировать новые инструменты в свои системы.
За девять лет мы успели помочь нескольким компаниям в создании и сопровожде­
нии комбинации платформ для контейнеров Linux, включая Docker, Mesos и
Kubernetes. Впервые мы реализовали Docker в рабочей среде всего через несколько
месяцев после его выхода и хотим поделиться с вами опытом, который накопили
за все это время. Надеемся, что наши открытия помогут вам избежать ошибок,
с которыми в свое время сталкивались мы. Конечно, в документации к Docker
(https://docs.docker.com/) вы найдете много полезного, но мы попробуем описать
более полную картину и поделиться советами, опираясь на свой опыт.
После прочтения книги вы будете хорошо понимать, что собой представляют кон­
тейнеры Linux, какие возможности предлагает Docker и как с помощью этих инст­
рументов можно оптимизировать процессы - от локальной разработки до рабочей
(production) среды. Мы рассмотрим несколько интересных технологий и даже
опробуем их на практике.
Содержание книги
Материал книги распределен следующим образом:
♦ В главах 1 и 2 мы в общих чертах знакомимся с Docker и вариантами его приме­
нения.
♦ В главе 3 приводятся инструкции по установке Docker.
♦ В главах 4-6 мы рассматриваем клиент Docker, образы и контейнеры, а также
методы работы с ними.
♦ В главе 7 описывается отладка образов и контейнеров.
♦ Глава 8 посвящена Docker Compose. Вы узнаете, как с его помощью значительно
упростить развертывание сложных сервисов на основе контейнеров.
♦ В главе 9 мы обсудим, как обеспечить плавный переход в рабочую среду.
♦ В главе 1 О мы поговорим о масштабном развертывании контейнеров в публич­
ном и частном облаках.
♦ В главе 11 мы подробно рассмотрим расширенные концепции, которые потре­
буют более глубокого понимания Docker и помогут вам начать реализацию
Docker в рабочей среде.
♦ Глава 12 посвящена альтернативным инструментам, которые пригодятся в кон­
тейнеризированных средах Linux.
Книги для программистов: https://clcks.ru/books-for-it
Предисловие
1
19
♦ В главе 13 описываются общие принципы проектирования контейнерных плат­
форм нового поколения.
♦ В главе 14 мы подводим итоги и делаем выводы, в том числе вспоминаем, какие
темьi обсуждали и как эти знания помогут вам разрабатывать и масштабировать
сервисы.
Мы понимаем, что мало кто читает техническую литературу от корки до корки,
и предисловие обычно пропускают, но если вы все же его читаете, вот несколько
советов по изучению книги:
♦ Если вы еще не работали с контейнерами Linux, читайте книгу с самого начала.
В первых двух главах вы познакомитесь с общими принципами Docker и кон­
тейнеров Linux и узнаете, что они собой представляют, как работают _и какую
пользу приносят.
♦ Если хотите начать с установки и запуска Docker на компьютере, сразу перейди­
те к главам 3 и 4, в которых приводятся инструкции по установке Docker, созда­
нию и загрузке образов, запуску контейнеров и так далее.
♦ Если вы знакомы с основами Docker и хотите узнать больше о том, как исполь­
зовать его для разработки, изучите главы 5-8, в которых мы даем рекомендации
по работе с Docker и рассматриваем Docker Compose.
♦ Если вы уже используете Docker для разработки, а теперь хотите реализовать
его преимущества в рабочей среде, начните с главы 9 и читайте до главы 12.
В этих главах мы говорим о развертывании контейнеров, использовании кон­
тейнерных платформ и других углубленных вопросах.
♦ Архитекторов программного обеспечения и платформ может заинтересовать
глава 13, где мы рассматриваем современные контейнеризированные приложе­
ния и горизонтально масштабируемые архитектуры.
Обозначения
В этой книге приняты следующие обозначения:
♦ Курсив - новые термины.
♦ Полужирный шрифт - URL-aдpeca, адреса электронной почты.
♦ моноширинный шрифт - листинги, а также элементы кода, например имена перемен­
ных и функций, базы данных, типы данных, переменные окружения, инструкции
и ключевые слова.
♦ Моноширинный шриФ'1' полужирю.�й - команды или другой текст, который нужно вво­
дить без изменений.
♦ <Моноширинный шрифт в угловых скобках> - текст, который нужно заменить на значе­
ния пользователя или значения, определяемые контекстом.
; Эrот "ачо, обо"ачает соает или подскащ,
.
Книги для программистов: https://clcks.ru/books-for-it
20
Предисловие
&
� Этот зна,о, обозна,ает приме,ание.
Этот зна,о, обозна,ает предупреждение или предостережение.
Использование примеров кода
Дополнительный материал (примеры кода, упражнения и т. д.) можно загрузить по
адресу https://github.com/Ыuewhalebook/docker-up-and-running-3rd-edition.
Это практическое руководство поможет вам в работе. Как правило, если к книге
прилагается код, вы можете использовать его в своих программах и документации.
Вам не требуется наше разрешение, если вы не воспроизводите значительную часть
кода. Например, вы можете не спрашивать разрешение, чтобы вставить несколько
фрагментов кода из книги в свою программу. Если вы продаете или распространяе­
те несколько примеров из книг O'Reilly, необходимо получить разрешение. Если
вы отвечаете на вопрос, цитируя эту книгу или приводя примеры кода, это можно
делать без разрешения. Если вы включаете значительную часть примеров кода из
этой книги в документацию к продукту, спросите разрешение.
Мы будем благодарны, если вы сошлетесь на источник, но это не обязательно.
В ссылке на источник обычно указывается название, автор, издательство и ISBN
книги. Например: Docker: Up & Running, 3е, Ьу Sean Р. Капе with Karl Matthias
(O'Reilly). Copyright 2023 Sean Р. Капе and Karl Matthias, 978-1-098-13182-1.
Если вы хотите использовать примеры кода из этой книги и вам кажется, что для
этого требуется разрешение, напишите нам по адресу permissions@oreilly.com.
Онлайн-обучение O'Reilly
Уже более 40 лет O'Reilly Media (https://oreilly.com/) предлагает обучение, знания
и аналитику в сфере технологий и бизнеса, помогая компаниям развиваться и про­
цветать.
Наша уникальная сеть экспертов и новаторов делится своими знаниями и умениями
в книгах, статьях и на платформе онлайн-обучения. На платформе онлайн-обучения
O'Reilly вам доступны онлайн-курсы, обучающие программы, интерактивные
среды программирования и большая коллекция текстовых и видеоматериалов
от O'Reilly и более чем 200 других издателей. Узнайте больше на сайте https://
oreilly.com.
Книги для программистов: https://clcks.ru/books-for-it
Предисловие
1
21
Контактная информация
Адрес для вопросов и отзывов об этой книге:
O'Reilly Media, lnc.
1005 Gravenstein Highway North
Sebastopol, СА 95472
800-998-9938 (США и Канада)
707-829-0515 (международный и местный)
707-829-0104 (факс).
Список ошибок, примеры и дополнительную информацию по этой книге
вы найдете на веб-странице https://oreil.ly/docker-up-and-running-Зe.
Комментарии и вопросы по этой книге можно присылать по адресу
bookquestions@oreilly.com.
Новости и информация о книгах и курсах: https://oreilly.com.
Liлkedin: https:/Лinkedin.com/company/oreilly-media.
Twitter: https://twitter.com/oreillymedia 1 •
УouTube: https://youtube.com/oreillymedia2 .
Благодарности
Мы хотим поблагодарить всех тех, кто помогал нам с каждым изданием этой книги:
♦ Ник Бендерс (Nic Benders), Бьорн Фриман-Бенсон (Bjorn Freeman-Benson) и Да­
на Лоусон (Dana Lawson) из New Relic - за то, что оказали нам неоценимую
помощь с первым изданием и помогли найти время для работы над книгой.
♦ Роланд Трич (Roland Tritsch) и Nitro Software - за поддержку Карла в работе
над вторым изданием.
♦ Лорел Рума (Laurel Ruma) из O'Reilly - за то, что предложила нам написать
книгу о Docker, и Майк Лукидс (Mike Loukides) - за то, что все контролировал.
♦ Отдельная благодарность редактору первого издания, Брайану Андерсону (Brian
Aлderson), который объяснил, какая работа нам предстоит, и помогал нам на
каждом этапе.
♦ Никки Макдональд (Nikki McDonald) и Вирджиния Уилсон (Virginia Wilson)­
зa то, что помогали нам в работе над актуальным вторым изданием книги.
♦ Джон Девинс (John Devins), Мишель Кронин (Michele Cronin) и Элизабет Ферм
(Elizabeth Faerm) - за те усилия, которые они приложили к выпуску третьего
издания.
1 Организация запрещена в РФ. -Ред.
2 Организация запрещена в РФ. - Ред.
Книги для программистов: https://clcks.ru/books-for-it
22
1
Предисловие
♦ Евгений (Джим) Брикман (Yevgeniy (Jim) Brikman), автор превосходной кни­
ги "Teгraform: Up & Running", - за то, что разрешил создать сайт https://
dockerupandrunning.com на основе его дизайна.
♦ Требуется особый талант, чтобы без лишних слов познакомить новую аудито­
рию с новой технологией. Мы очень благодарны Ларсу Херману (Lars Herr­
mann), Лоре Франк Тако (Laura Frank Tacho) и Раджу Ганди (Raju Ghandi) за на­
писание предисловий к изданиям книги.
♦ Редакторы черновиков, которые следили, чтобы мы не сбились с пути при напи­
сании книги: Ксения Бурлаченко (Ksenia Burlachenko), за первую рецензию и
полную техническую рецензию; Эндрю Т. Бейкер (Andrew Т. Baker), Себастьен
Гоасген (Sebastien Goasguen), Генри Гомес (Henri Gomez), Челси Франк (Chelsey
Frank), Рашид Заруали (Rachid Zarouali), Вернер Дижкерман (Werner Dijkerrnan),
Предраг Кнежевич (Predrag Кnezevic) с Вишвеш Рави Шримали (Vishwesh Ravi
Shrimali).
♦ Отдельная благодарность Элис Голдфас (Alice Goldfuss) и Тому Офферману
(Тот Offermann) за подробную и полезную обратную связь при написании пер­
вого издания, и Михаю Тодору (Mihai Todor) за поддержку, техническую рецен­
зию и полную обратную связь по второму изданию.
♦ Джилиан Макгарви (Gillian McGarvey), Мелани Ярбро (Melanie Yarbrough),
Джастин Биллинг (Justin Billing), Рейчел Монаган (Rachel Monaghan) и Соня
Саруба (Sonia Saruba)- за литературное редактирование рукописи, благодаря
которому книга выглядит так, будто мы умеем грамотно писать. Уже добавлено
517 запятых, и это не предел.
♦ Сью Клефстад (Sue Кlefstad) - за помощь в подготовке третьего издания, а
также Венди Каталано (Wendy Catalano) и Эллен Траутман (Ellen Troutman)­
зa работу над индексацией предыдущих изданий.
♦ Особая благодарность Нику Адамсу (Nick Adams) и всем сотрудникам O'Reilly
Media, благодаря которым книга прилично выглядит во всех форматах.
♦ Нашим коллегам в New Relic и Nitro, которые вместе с нами познавали Docker,
чтобы мы смогли поделиться с вами нашим общим опытом.
♦ Grains of Wrath Brewery, World Cup Coffee, McMenamins Ringlers Pub, Old Town
Pizza, А Beer at а Тime!, Taylor's Three Rock и другим заведениям, которые раз­
решали нам сидеть за столиками и подключаться к розеткам, даже когда все
давно было съедено.
♦ Нашим семьям - за !]Оддержку и возможность поработать в тишине.
♦ И, наконец, всем, кто поддерживал и вдохновлял нас на протяжении всего пути.
Также издательство АЛИСТ выражает искреннюю благодарность научному редак­
тору Ксении Кириловой за помощь в подготовке издания этой книги на русском
языке.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 1
Введение
Впервые мир неожиданно услышал о Docker 15 марта 2013 года, когда Соломон
Хайкс, основатель и генеральный директор компании dotCloud, рассказал о нем
в пятиминутном блиц-докладе на конференции разработчиков на Python (https://
us.pycon.org/) в Санта-Кларе, штат Калифорния. На тот момент испытать Docker
успели всего 40 человек за пределами dotCloud.
Уже через несколько недель о Docker заговорили многие. Исходный код бьш опуб­
ликован в щкрытом доступе на GitHub (https://github.com/moby/moby). В сле­
дующие несколько месяцев в отрасли все чаще высказывалось мнение, что Docker
перевернет подход к разработке, поставке и выполнению программного обеспе­
чения. Всего через год о Docker знал каждый разработчик, но мало кто понимал,
зачем нужен этот инструмент и почему вокруг него столько шума.
Docker обещает возможность легко инкапсулировать процесс создания распростра­
няемого программного пакета для любого приложения, чтобы можно было развер­
тывать его в произвольном масштабе в любой среде, а также оптимизировать рабо­
чие процессы и скорость работы команд, использующих методологию Agile.
Ожидания от Docker
Поначалу многие люди, не знакомые с Docker, воспринимали его как платформу
виртуализации, когда на самом деле это бьш первый широкодоступный инструмент
для более новой технологии - контейнеризации. Docker и контейнеры Linux по­
влияли на самые разные сегменты отрасли, использующие такие инструменты
и технологии, как Vagrant, KVM, OpenStack, Mesos, Capistrano, AnsiЫe, Chef, Puppet
и т. д. Заметили, что общего между решениями, которые потеряли свои позиции на
рынке после появления Docker? Да, все эти инструменты имеют разные примене­
ния, но Docker навGеrда изменил соответствующие рабочие процессы. В основном
это связано с тем, как Docker повлиял на представления о конвейере непрерывной
интеграции и поставки (CI/CD)- специалисты уже не хотят тратить время на про­
движение конвейера DevOps вручную и ждут, что этот процесс будет полностью
автоматизирован и переходы с этапа на этап не потребуют участия человека. Пере­
численные технологии предназначены для повышения продуктивности, и как раз
это преимущество привлекло столько внимания к Docker. Docker вбирает в себя
лучшее из самых эффективных технологий последнего десятилетия и позволяет
заметно усовершенствовать почти каждый этап кон,вейееа.
Книги для программистов: https://clcks.ru/books-for-it
24
1
Глава 1
Если детально сравнить Docker и лучший инструмент в каждой сфере (например,
управление конфигурациями), скорее всего, Docker вас не поразит. В чем-то он
лучше, в чем-то отстает, но его сильная сторона - набор возможностей для реше­
ния широкого ряда задач, ведь он сочетает в себе возможности для удобного тести­
рования и развертывания приложений, как в Vagrant и Capistrano, простой подход
к администрированию систем виртуализации, а также интерфейсы, упрощающие
автоматизацию и оркестрацию рабочих процессов.
Новые технологии появляются и исчезают постоянно, и мы привыкли восприни­
мать их со здоровой долей скептицизма. Когда Docker только появился, он мог бы
стать для нас очередной технологией, решающей конкретный ряд задач для разра­
ботчиков или команды сопровождения. Если воспринимать Docker только ·как
технологию псевдовиртуализации или развертывания, он вряд ли сможет приятно
удивить. Но Docker умеет гораздо больше.
Налаживать коммуникации и процессы между командами разработчиков сложно и
дорого даже в небольших организациях. При этом мы живем в мире, где обмен ин­
формацией между разработчиками все чаще становится залогом успеха. Инстру­
мент, который упрощает коммуникации и при этом помогает разрабатывать более
надежное программное обеспечение, - это настоящий подарок. Именно поэтому
стоит рассмотреть использование Docker. Это не панацея, и для эффективной реа­
лизации Docker в организации потребуется продуманная стратегия, но Docker и
контейнеры Linux предлагают эффективный подход к решению реальных проблем
и помогают компаниям быстрее поставлять на рынок качественное программное
обеспечение. Хорошо отлаженный рабочий процесс для контейнеров Linux упро­
щает работу техническим специалистам и позволяет бизнесу ощутимо сэкономить.
Где у компаний самое слабое место? Нелегко поставлять качественное программ­
ное обеспечение с той скоростью, какую ожидают от компаний. Если раньше
в компании работали два разработчика, а потом штат разросся до нескольких ко­
манд, становится по-настоящему сложно управлять коммуникациями при выпуске
новых версий. Программисты должны разбираться в тонкостях среды, в которую
они поставляют свой продукт, а специалисты по сопровождению должны понимать
принципы работы приложения, которое они обслуживают. Обычно эти навыки сле­
дует развивать, потому что они помогают видеть целостную картину и, следова­
тельно, разрабатывать более надежное программное обеспечение. С другой сторо­
ны, эти навыки сложно эффективно масштабировать при расширении организации.
Когда несколько команд работают с одной средой, они активно взаимодействуют
друг с другом, хотя этот процесс часто не приносит пользы напрямую. Например,
когда разработчикам приходится просить у инженеров по сопровождению вы­
пуск 1.2. l определенной библиотеки, это замедляет работу и не добавляет прямой
бизнес-ценности. Если бы разработчики могли сами обновлять используемую биб­
лиотеку, писать код, тестировать его с новой версией и поставлять, весь процесс
занимал бы гораздо меньше времени, а развертывание изменения стало менее рис­
кованным. Если бы инженеры по сопровождению могли обновлять версию про­
граммного обеспечения в хост-системе, не координируя усилия с несколькими
Книги для программистов: https://clcks.ru/books-for-it
Введение
1
25
командами разработчиков, они работали бы быстрее. Docker помогает создать слой
изоляции, который сокращает потребность в коммуникациях между людьми.
Кроме того, Docker требует определенную программную архитектуру, побуждая
разработчиков лучше продумывать приложения. ПодХод Docker - использовать
мельчайшие одноразовые контейнеры. При развертывании вся среда выполнения
старого приложения выбрасывается вместе с ним. Ни один компонент среды при­
ложения не переживет само приложение. Эта простая идея многое меняет, ведь
теперь приложения не смогут случайно использовать программные пакеты, остав­
шиеся от предыдущего релиза. Это значит, что временные изменения при отладке
имеют меньше шансов остаться в будущих выпусках, которые переняли их из ло­
кальной файловой системы. Получается, что приложения можно легко переносить
между серверами, ведь их состояние напрямую включается в программный пакет
развертывания и не изменяется. Или отправляется во внешнюю зависимость, на­
пример базу данных, кеш или файловый сервер.
В результате приложение становится не только более масштабируемым, но и более
надежным. Экземпляры контейнера приложения появляются и исчезают, мало влияя
на стабильность работы фронтенда. Эти разумные принципы доказали свою со­
стоятельность и для приложений, не задействующих Docker, но Docker требует
придерживаться этого подхода для контейнерных приложений. И это хорошо.
Преимущества рабочего процесса Docker
Сложно классифицировать все преимущества Docker. При правильной реализации
он приносит много пользы организациям, командам, разработчикам и инженерам
сопровождения. Он упрощает принятие решений об архитектуре, ведь все прило­
жения, по сути, выглядят одинаково снаружи, с точки зрения хост-системы. Он по­
могает создавать инструменты и применять их в разных приложениях. Обычно за
все хорошее нужно расплачиваться, но с Docker идти на такие компромиссы почти
не приходится. Вот еще несколько преимуществ, которые мы получаем от Docker и
контейнеров Linux:
♦ Упаковка программного обеспечения знакомым разработчику способом. У мно­
гих компаний есть отдельные инженеры по выпуску и сборке, которые обладают
специальными навыками и управляют всеми инструментами, чтобы создавать
пакеты программного обеспечения для поддерживаемых платформ. Инструмен­
ты Linux, например rpm, mock, dpkg и pbuilder, могут быть сложны для исполь­
зования, и их и нужно изучать отдельно. Docker объединяет все требования
в один формат упаковки - стандарт Open Container Initiative (OCI) (https://
opencontainers.org/).
♦ Объединение прwюжения и файловых систем в единый стандартизированный
формат образа. Раньше приходилось упаковывать не только приложение, но и
множество его зависимостей, включая библиотеки и демоны, но невозможно
было гарантировать, что среда выполнения будет идентичной. При нативной
компиляции это значит, что система сборки должна содержать точно такие же
версии общих библиотек, что и рабочая среда. Из-за этого было сложно освоить
Книги для программистов: https://clcks.ru/books-for-it
26
1
Глава 1
упаковку приложений, и у многих компаний этот процесс не был налажен.
Пользователи Scientific Linux (https://scientificlinux.org/), например, часто пы­
тались развернуть пакет от сообщества, протестированный на Red Hat Enterprise
Linux (https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux),
в надежде, что различия будут минимальными. С помощью Docker мы развер­
тываем приложение вместе со всеми файлами, которые ему понадобятся. Мно­
гослойные образы Docker обеспечивают эффективный подход, который гаран­
тирует, что приложение будет выполняться в ожидаемой среде.
♦ Использование программных пакетов для тестирования и поставки одинаковых
артефактов сборки во все системы и среды. Когда разработчик отправляет из­
менения в систему контроля версий, собирается новый образ Docker, который
проходит через весь процесс тестирования и развертывается в рабочей среде,
при этом ни на одном этапе не требуется повторная компиляция или упаковка,
если только мы сами этого не захотим.
♦ Разделение программного обеспечения от оборудования без потери ресурсов.
Традиционные корпоративные решения для виртуализации, такие как VMware,
обычно используются как уровень абстракции между оборудованием и выпол­
няемыми на нем приложениями, при этом потребляя много ресурсов. Гиперви­
зоры, которые управляют виртуальными машинами и их ядрами, тоже потреб­
ляют аппаратные ресурсы, забирая их у размещенных приложений. Контейнер
же - это просто очередной процесс, который обычно взаимодействует напря­
мую с ядром Linux и потому может задействовать больше ресурсов, пока не из­
расходует квоту или все ресурсы системы.
Когда Docker только появился, контейнеры Linux применялись уже несколько
лет - как и некоторые другие технологии, на которых он построен. Однако Docker
представляет собой уникальную комбинацию вариантов архитектуры и рабочих
процессов, в которой целое становится гораздо сильнее, чем сумма его частей. Бла­
годаря Docker контейнеры Linux, которые находились в свободном доступе
с 2008 года, стали понятными и применимыми для всех компьютерных инженеров.
Docker помогает относительно легко вписать контейнеры в существующий рабочий
про·цесс и операции реальных компаний. Проблемы, о которых мы говорили ранее,
волновали многих, поэтому интерес к Docker возрастал гораздо быстрее, чем мож­
но было ожидать.
Docker ускоренно развивался с момента своего первого появления в 2013 году и
теперь обладает огромным набором возможностей и используется в огромном
количестве рабочих проектов по всему миру. Он стал основной для многих совре­
менных распределенных систем и вдохновил многих инженеров на развитие этого
подхода. Теперь многие компании благодаря Docker и контейнерам Linux упроща­
ют сложные процессы поставки приложений.
Книги для программистов: https://clcks.ru/books-for-it
Введение
27
В чем Docker не поможет
Docker решает широкий ряд задач, для которых традиционно применялись другие
инструменты. Однако широта иногда идет в ущерб глубине. Например, некоторые
организации полностью отказываются от своего инструмента управления конфигу­
рациями, когда переходят на Docker. Да, частично Docker заменяет традиционные
инструменты, но главное его преимущество в том, что он хорошо сочетается с ни­
ми и даже работает лучше в комбинации. Далее мы рассмотрим категории инстру­
ментов, которые Docker не заменяет напрямую, но можно получить превосходные
результаты, если использовать их совместно:
♦ Корпоративная платформа виртуализации (VMware, КУМ и т. д.). Контейнер не
является виртуальной машиной в традиционном смысле. Виртуальные машины
содержат полноценную операционную систему, которая запускается поверх
гипервизора, управляемого операционной системой хоста. Гипервизоры создают
уровни виртуального аппаратного обеспечения, на которых можно запускать
дополнительные операционные системы на одном физическом компьютере. При
таком подходе можно легко использовать много виртуальных машин с самыми
разными операционными системами на одном хаете. Контейнеры, с другой сто­
роны, делят одно ядро с хостом. Следовательно, контейнеры потребляют мень­
ше системных ресурсов, но должны выполняться на той же операционной сис­
теме (например, Linux).
♦ Облачная платформа (OpenStack, CloudStack и т. д.). В этой категории контейне­
ры тоже в общих чертах похожи на более традиционные облачные платформы.
Оба варианта служат для горизонтального масштабирования в ответ на измене­
ние спроса. Однако Docker - это не облачная платформа. Он предназначен
только для развертывания, выполнения и администрирования контейнеров на
уже имеющихся хостах Docker. Он не позволяет создавать новые хает-системы
(экземпляры), хранилище объектов, блочное хранилище и другие ресурсы, кото­
рые часто используются на облачной платформе. Правда, когда вы начнете рас­
ширять инструментарий Docker, вы будете замечать все больше и больше пре­
имуществ, которые обычно ожидают от облака.
♦ Управление конфигурацией (Puppet, Chef и т. д.). Docker помогает организациям
управлять приложениями и их зависимостями, но не полностью заменяет более
традиционный подход к управлению конфигурацией. В Dockerfile определено,
как контейнер должен выглядеть на момент сборки, но эти файлы не управляют
состоянием контейнера и не предназначены для управления хает-системой
Docker. Правда, при использовании Docker нам и не требуется сложный код
управления конфигурацией. Когда все больше серверов становятся хостами
Docker, база кода для управления конфигурацией в компании заметно сокраща­
ется, и Docker служит для поставки более сложных приложений внутри стандар­
тизированных образов OCI.
♦ Фреймворк развертывания (Capistrano, Fabric и т. д.). Docker упрощает разные
аспекты развертывания благодаря образам контейнеров, которые инкапсулиру­
ют все зависимости приложения и позволяют развертывать его в любых средах
Книги для программистов: https://clcks.ru/books-for-it
28
Глава 1
без изменений. Правда, сам по себе Docker невозможно использовать для авто­
матизации сложного процесса развертывания - для связывания всех частей
крупного рабочего процесса потребуются дополнительные инструменты. С дру­
гой стороны, Docker и другие инструменты для работы с контейнерами Linux,
например Kubemetes (k8s), предоставляют хорошо определенный интерфейс для
развертывания, так что метод развертывания контейнера будет одинаков на всех
хостах и одного поставленного процесса развертывания хватит для большинства
или даже для всех приложений на основе Docker.
♦ Среда разработки (Vagrant и т. д.). Vagrant представляет собой инструмент
управления виртуальными машинами для разработчиков. Он часто используется
для симуляции серверных стеков, очень похожих на реальную рабочую среду,
в которой будет развернуто приложение. Кроме того, Vagrant позволяет легко
запускать программное обеспечение Linux на рабочих станциях macOS и
Windows. Виртуальные машины, управляемые с помощью таких инструментов,
как Vagrant, помогают разработчикам избежать типичной ситуации, когда про­
граммное обеспечение прекрасно выполняется на компьютере разработчика, но
не в других средах. Правда, как и в предыдуших примерах, когда вы начнете по­
стоянно работать с Docker, вам придется все реже имитировать различные рабо­
чие системы при разработке, поскольку обычно это будут одинаковые серверы
с контейнерами Linux, которые можно легко воспроизводить локально.
♦ Инструмент управления рабочими нагрузками (Mesos, Kubemetes, Swarm и
т. д.). Уровень оркестрации (включая встроенный режим Swarm) помогает коор­
динировать работу в пуле хостов с контейнерами Linux, отслеживать текущее
состояние всех хостов и их ресурсов и вести список запущенных контейнеров.
Эти системы позволяют автоматизировать выполнение регулярных задач, чтобы
поддерживать работоспособность рабочего кластера. Кроме того, они предос­
тавляют инструменты, которые упрощают людям работу с динамичными кон­
тейнеризированными рабочими нагрузками.
В каждой из этих категорий мы видим, какую важную роль сыграли Docker и кон­
тейнеры Linux. Контейнеры Linux позволяют выполнять программное обеспечение
в контролируемой изолированной среде, а простой в использовании интерфейс ко­
мандной строки (Command Line Interface, или CLI) и стандарт образов контейнера,
представленный Docker, заметно упростили работу с контейнерами и предложили
воспроизводимый способ собирать программное обеспечение для любых сред.
Важная терминология
Вот несколько терминов, которыми мы будем пользоваться в этой книге:
♦ Клиент Docker - это команда docker, используемая для управления большей
частью рабочего процесса Docker и взаимодействия с удаленными серверами
Docker.
♦ Сервер Docker - это команда dockerd для запуска серверного процесса Docker,
который собирает и запускает контейнеры через клиент.
Книги для программистов: https://clcks.ru/books-for-it
Введение
1
29
♦ Образы Docker или OCI - образы Docker и OCI состоят из нескольких слоев
файловой системы и важных метаданных. Они представляют все файлы, необ­
ходимые для выполнения контейнерного приложения. Один образ можно ско­
пировать на несколько хостов. У образа обычно есть адрес репозитория, имя
и тег. Тег служит для идентификации выпуска образа (например, docker.io/
superorbltal/wordchain:vl.0.1). Образ Docker - это любой образ, совместимый
с инструментами Docker, а образ OCI- это образ, соответствующий стандарту
Open Container Initiative, который гарантированно будет работать с любым инст­
рументом, соответствующим требованиям OCI.
♦ Контейнер Linux - это контейнер, созданный из образа Docker или OCI. Опре­
деленный контейнер может существовать всего один раз, но из одного образа
можно создать несколько контейнеров. Термин контейнер Docker неточен, т. к.
Docker просто использует функционал контейнеров операционной системы.
♦ Минимальный, или неизменяемый, хает - это небольшой, тщательно настроен­
ный образ ОС, например Fedora CoreOS (https://getfedora.org/en/coreos), кото­
рый поддерживает хостинг контейнеров и атомарные обновления.
Заключение
Без определенной подготовки сложно сразу понять всю философию Docker. В сле­
дующей главе мы в общих чертах рассмотрим, что такое Docker, как с ним нужно
работать и какие преимущества он дает при правильном применении.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 2
Общие сведения о Docker
Прежде чем перейти к конфигурированию и установке,. мы поговорим о том; что
представляет собой Docker и кюще преимущества он дает. Это мощная, но не
слишком сложная технология. В данной главе мы рассмотрим общие · принципы
работы Docker и ,контейнеров Linux, их главные достоинства и варианты примене­
ния. Если вы читаете эту книгу, скорее всего, у вас есть причины использовать кон­
тейнеры, но 'дополнительное понимание никогда не помешает.
Не волнуйтесь, эта глава не будет длинной. Уже в следующей гл.аве вы узнаете, как
установить Docker и запустить его в своей системе.
Упрощение рабочих процессов
'
Поскольку Docker - это программное обеспечение, не все понимают, что при правильной реализации он может улучшить бизнес-процерсы и коммуникации между
командами специалистов. Давайте копнем глубже и узнаем, как Docker и контейне­
ры Linux упрощают рабочие процессы и взаимодействия. Обычно все начинается
с развертывания. Традиционно цикл поставки приложения· в рабочую среду выгля­
дит так, как показано на рис. 2.1:
1. Разработчики приложения запрашивают ресурсы у инженеров сопровождения.
2. Инженеры сопровождения выделяют ресурсы и передают их разработчикам.
3. Разработчики пишут скрипт для развертьmания.
4. Инженеры сопровождения и разработчики несколько раз вносят корректировки.
5. Разработчики обнаруживают дополнительные зависимости приложения.
6. Инженеры сопровождения устанавливают требуемые зависимости.
7. Этапы с 4-го по 6-й повторяются п раз.
8. Приложение развертывается.
Как показывает опыт, развертывание нового приложения в рабочую среду тради­
ционным способом может занять большую часть недели, если речь идет о сложной
новой системе. Это не самый продуктивный подход, и хотя практики DevOps зна­
чительно упрощают задачу, все-таки разработчики прикладывают много усилий и
активно взаимодействуют друг с другом. Это технически сложный и дорогой про­
цесс. Хуже того, он ограничивает инновации, которые •команды разработчиков мог­
ли бы внедрить в будушем. Если развертывание программного обеспечения отниКниги для программистов: https://clcks.ru/books-for-it
Общие сведения о Oocker 1
31
мает много сил и времени и требует привлечения другой команды специалистов,
разработчики пытаются встраивать новые функции в существующее приложение,
чтобы лишний раз не запускать сложный процесс развертывания. Или еще хуже­
они могут просто избегать проблем, которые требуют написания нового программ­
ного кода.
Команда разработки
Команда сопровождения
Запрос ресурсов (1)
Подготовка ресурсов (2)
Скрипт развертывания (3)
Приложение развернуто (8)
Рис. 2.1. Традиционный процесс развертывания (без Docker)
Автоматизированные системы развертывания, вроде Heroku (https://www.
heroku.com/), работающие по модели push-to-deploy, показывают разработчикам,
как легко нам всем работалось бы, если бы мы могли контролировать свое прило­
жение и большую часть зависимостей. Разработчики часто говорят, насколько
Heroku и подобные платформы облегчают им жизнь. Если вы занимаетесь сопро­
вождением программного обеспечения, скорее всего, вы слышали жалобы на то,
как медленно работают внутренние системы по сравнению с автоматизированными
решениями, подобными Heroku, которые используют технологию контейнеров
Linux и с помощью которых развернуть приложение можно путем нескольких на­
жатий кнопок.
Heroku - это полноценная среда, не просто движок для контейнеров. Docker не
пытается воспроизвести все функции, доступные в Heroku, зато предоставляет чет­
кое разделение обязанностей и инкапсуляцию зависимостей, помогая повысить
производительность. Docker даже дает более детализированный контроль, чем
Heroku, потому что разработчики могут управлять почти всем, включая точные
версии файлов и пакетов, которые поставляются с приложением. Некоторые инст­
рументы и оркестраторы, построенные на Docker (например, Kubemetes, Docker
Swarm и Mesos), стремятся воспроизвести простоту, предоставляемую системами
вроде Heroku. Эти платформы расширяют возможности Docker, поддерживая более
функциональные и сложные среды, но при использовании одного только Docker мы
Книги для программистов: https://clcks.ru/books-for-it
32
Глава 2
получаем все основные преимущества без дополнительных сложностей, связанных
с большими системами.
Продукты Docker предоставляются по принципу "батарейки в комплекте, но можно
заменить". Это значит, что у них есть все функции, которые могут понадобиться
для работы, но их можно легко заменять для создания собственных решений.
Docker использует репозиторий образов для передачи ответственности, чтобы зада­
чи по сборке образа приложения можно было отделить от задач по развертыванию
и сопровождению контейнера. На практике это означает, что разработчики могут
сами собирать приложение с нужными зависимостями, запускать его в средах раз­
работки и тестирования, а потом просто поставлять в точности такой же пакет со
всеми зависимостями в рабочую среду. Поскольку снаружи все эти пакеты выгля­
дят одинаково, инженеры по сопровождению могут собирать или устанавливать
стандартные инструменты для развертывания и запуска приложений. В этом случае
цикл, показанный на рис. 2.1, выглядит как на рис. 2.2:
1. Разработчики собирают образ Docker и отправляют его в реестр.
2. Инженеры по сопровождению предоставляют детали конфигурации для контей­
нера и выделяют ресурсы.
3. Разработчики запускают развертывание.
Команда разработки
Команда сопровождения
Сборка образа
Предоставление данных конфиrурации
Развертывание
Рис. 2.2. Процесс развертывания с Docker
Это возможно, потому что Docker позволяет обнаруживать все проблемы с зависи­
мостями еще на этапах разработки и тестирования. К тому времени, как приложе­
ние окажется готово к первому развертыванию, вся эта работа уже будет продела­
на, причем без активных коммуникаций между разработчиками и инженерами
сопровождения. В хорошо продуманном конвейере созданием и развертыванием
нового сервиса не придется заниматься никому, кроме самих разработчиков. Это
гораздо проще и быстрее. Более того, это позволяет повысить надежность про­
граммного обеспечения, поскольку еще до выпуска оно будет протестировано
в среде развертывания.
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker
1
33
Широкая поддержка и внедрение
Docker широко поддерживается коммерческими решениями, в том числе почти
всеми крупными провайдерами публичных облаков. Например, Docker и контейне­
ры Linux применяются в Amazon Web Services (АWS) для разных продуктов, вклю­
чая Amazon Elastic Container Service (Amazon ECS), Amazon Elastic Kubernetes
Service (Amazon EKS), Amazon Fargate и Amazon Elastic Beanstalk. Контейнеры
Linux можно использовать в Google Арр Engine (GAE), Google Kubernetes Engine,
Red Hat OpenShift, ШМ Cloud, Microsoft Azure и других платформах. На конферен­
ции DockerCon 2014 Эрик Брюэр из Google объявил, что Google будет использовать
Docker как основной формат внутренних контейнеров. Для компаний такие объяв­
ления были хорошим маркетингом, а для сообщества Docker это означало, что
в поддержание стабильности и развития платформы будет вкладываться много
средств.
Более того, формат образов Docker для контейнеров Linux стал единым стандартом
среди облачных провайдеров, что позволяет переносить облачные приложения ме­
жду платформами без изменений кода. Когда Docker выпустил библиотеку разра­
ботки libswarm, один инженер из Orchard продемонстрировал, как можно развер­
нуть контейнер Linux в разнородной среде из нескольких облаков одновременно.
Раньше это было бы слишком сложно, потому что у каждого облачного провайдера
был свой API или набор инструментов для управления инстансами. Инстансы
обычно были мельчайшим элементом, которым можно управлять с помощью API.
Если в 2014 году мы только мечтали об этом, сейчас это тренд, и крупнейшие ком­
пании продолжают инвестировать в Docker, поддержку платформы и инструменты.
Поскольку большинство провайдеров так или иначе предлагают оркестрацию
Docker и контейнеров Linux, а также саму среду выполнения контейнеров, сейчас
Docker поддерживается почти для любых рабочих нагрузок в стандартных рабочих
средах. Если вся система построена вокруг Docker и контейнеров Linux, приложе­
ния можно развертывать одинаково независимо от облачной платформы. Раньше
о такой гибкости можно было только мечтать.
В 2017 году Docker передал среду выполнения containerd (https://thenewstack.io/
docker-donate-container-runtime-containerd-cloud-native-computing-foundation)
фонду Cloud Native Computing Foundation (CNCF), и в 2019 году проект был завер­
шен и получил статус graduated.
Сегодня контейнеры Linux все чаще применяются в разработке, поставке и рабочей
среде. В 2022 году Docker стал уступать свою долю на рынке более новым версиям
Kubemetes, которым не требовался демон Docker, но даже эти версии Kubernetes
активно задействуют среду выполнения containerd, изначально разработанную
Docker. Docker по-прежнему используется во многих рабочих процессах разработ­
ки и конвейерах CI/CD.
Какие ОС совместимы с Docker? Клиент Docker работает напрямую на большинст­
ве главных операционных систем, а сервер может работать на Linux или Win­
dows Server. Большая часть экосистемы построена на серверах Linux, но развивается
поддержка и для других платформ. Скорее всего, мы и дальше будем чаще всего
использовать контейнеры и серверы Linux.
Книги для программистов: https://clcks.ru/books-for-it
34
1
Глава 2
Мы можем запускать контейнеры Windows нативно (без виртуальной машины) на
64-разрядных версиях Windows Server 2016 и выше. Правда, для 64-разрядных
версий Windows 10+ Professional все еще нужен Hyper-V, который предоставляет
ядро Windows Server для контейнеров Windows. Подробнее об этом мы поговорим
в главе 5 в разделе "Контейнеры Windows".
Стоит отметить, что на Windows можно запускать контейнеры Linux без виртуаль­
ной машины, если использовать WSL 2 (Windows Subsystem for Linux, версия 2).
Чтобы поддержать растущий спрос на инструменты Docker в средах разработки,
Docker выпустил простые для использования реализации для macOS и Windows.
Кажется, что все работает нативно, но на самом деле реализована небольшая вир­
туальная машина Linux, которая предоставляет сервер Docker и ядро Linux. Docker
традиционно разрабатывается на дистрибутиве Ubuntu Linux, но сейчас по возмож­
ности поддерживается большинство дистрибутивов Linux и другие популярные
операционные системы. Red Hat, например, полностью перешли на контейнеры,
и все их платформы превосходно поддерживают Docker. Поскольку в мире Linux
сейчас почти все работает на контейнерах, появляются такие дистрибутивы, как
Red Hat Fedora CoreOS, созданные специально для контейнерных рабочих нагрузок
Linux.
В первые годы после выхода Docker некоторые конкуренты и поставщики услуг
выражали беспокойство по поводу проприетарного формата образов Docker. У кон­
тейнеров на Linux не было стандартного формата образов, поэтому в Docker, lnc.
создали свой вариант в соответствии с потребностями своего бизнеса.
Поставщики услуг и вендоры коммерческих решений не хотели создавать плат­
формы, завися от прихотей конкурента, и в этот период в сторону Docker раздава­
лись публичные обвинения. Чтобы улучшить свою репутацию и расширить распро­
странение на рынке, в июне 2015 года в Docker, Inc. решили поддержать инициати­
ву Open Container lnitiative (OCI) (https://www.opencontainers.org/). Первая полная
спецификация была выпущена в июле 2017 года, и она по большей части повторяла
вторую версию формата образов Docker. Теперь можно пройти сертификацию OCI
для образов контейнеров и сред выполнения контейнеров.
Основная среда выполнения с сертификацией OCI - это containerd (https://
containerd.io/) - высокоуровневая среда по умолчанию в современных версиях
Docker и Kubemetes.
Перечисленные далее низкоуровневые среды выполнения, сертифицированные OCJ,
могут использоваться средой containerd для администрирования и создания контей­
неров:
♦ runc (https://github.com/opencontainers/runc) - часто используется как низко­
уровневая среда по умолчанию в containerd.
♦ crun (https://github.com/containers/crun) - написана на С; она быстрая и по­
требляет мало памяти.
♦ Kata Containers (https://katacontainers.io/) от lntel, Hyper и OpenStack Foun­
dation - виртуализированная среда выполнения, в которой может работать
смесь контейнеров и виртуальных машин.
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
35
♦ gVisor (https://github.com/google/gvisor) от Google - изолированная среда вы­
полнения, полностью реализованная в пространстве пользователя.
♦ NаЫа Containers (https://naЫa-containers.github.io/) - еще одна изолированная
среда выполнения, которая помогает значительно сократить поверхность атаки
для контейнеров Linux.
Также продолжают развиваться решения для развертывания контейнеров и оркест­
рирования целых систем контейнеров. Многие из них поставляются с открытым
исходным кодом и доступны как для локальной, так и для облачной среды или по
модели SaaS (Software as а Service - программное обеспечение как услуга) от раз­
ных поставщиков в публичных или частных облаках. Учитывая огромные инвести­
ции в развитие контейнеров Linux, скорее всего, Docker продолжит играть важную
роль в современном Интернете.
Архитектура
Docker - эффективная и функциональная технология, а потому связанные с ним
инструменты и процессы не отличаются простотой. Docker действительно довольно
сложно устроен, но со стороны пользователя представляет собой понятную мо­
цель "клиент-сервер". За Docker API находятся несколько компонентов, включая
containerd и runc, но базовое взаимодействие осуществляется между клиентом и
сервером через API. За кажущейся простотой скрывается использование различных
механизмов ядра, включая iptaЫes, виртуальные мосты, контрольные группы Linux
(cgroups), пространства имен Linux, capabilities (привилегии) Linux, secure
computing mode (безопасный режим вычислений), драйверы различных файловых
систем и многое другое. О некоторых из этих аспектов мы поговорим в главе 11.
А пока давайте узнаем, как работают клиент и сервер, и кратко рассмотрим сетевой
уровень, находящийся под контейнерами Linux в Docker.
Клиент-серверная архитектура
Проще всего представить Docker в виде двух частей: клиент и сервер/демон
(рис. 2.3). К ним может добавляться третий компонент- реестр (registry), который
к.ранит образы Docker и их метаданные. Сервер отвечает за сборку, выполнение и
СсрвРр IJo<:kf'I
Hd хосте или lJM L111LJX
Клиент Docker
на локальной рабочей станции
Рис. 2.3. Клиент-серверная архитектура Docker
Книги для программистов: https://clcks.ru/books-for-it
36
1
Глава 2
администрирование контейнеров, а с помощью клиента мы отдаем серверу коман­
ды. Демон (https://en.wikipedia.org/wiki/Daemon_(computing)) Docker может вы­
полняться на любом числе серверов в инфраструктуре, и один клиент может обра­
щаться к любому числу серверов. Клиенты управляют всеми коммуникациями, но
серверы Docker могут напрямую взаимодействовать с реестрами образов, если так
велит клиент. Клиенты отдают серверам команды, а серверы отвечают за размеще­
ние контейнерных приложений и управление ими.
По структуре Docker немного отличается от другого клиент-северного программно­
го обеспечения. У него есть клиент docker и сервер dockerd, но сервер не является
монолитным и оркестрирует еще несколько компонентов от имени клиента, вклю­
чая containerd-shim-runc-v2, который используется для взаимодействия с runc и
containerd. Docker аккуратно прячет все сложности за простым серверным API, так
что в большинстве случаев мы видим только взаимодействие клиента и сервера.
У каждого хоста Docker обычно есть один сервер Docker, который может управлять
любым числом контейнеров. С сервером можно общаться с помощью интерфейса
командной строки Docker - с самого сервера или, если приняты надлежащие меры
безопасности, с удаленного клиента. Чуть позже мы поговорим об этом подробнее.
Сетевые порты и сокеты Unix
Интерфейс командной строки docker и демон dockerd взаимодействуют через сокеты
Unix и сетевые порты. Компания Docker, Inc. зарегистрировала в IANA (lnternet
Assigned Numbers Authority - Администрация адресного пространства Интернета,
https://www.iana.org/) три порта для демона и клиента Docker: ТСР-порт 2375 для
незашифрованного трафика, порт 2376 для зашифрованных SSL-соединений и
порт 2377 для режима Docker Swarm. При желании можно легко настроить другие
порты. По умолчанию установщик Docker осуществляет коммуникацию с локаль­
ным демоном Docker только через сокет Unix. Это самые безопасные системные
настройки по умолчанию, но их легко можно менять. Правда, настоятельно реко­
мендуется не использовать сетевые порты с Docker, потому что у демона нет меха­
низмов аутентификации пользователей и управления доступом на основе ролей.
Сокет Unix может располагаться по разным путям в разных операционных систе­
мах, но обычно находится здесь: /var/run/docker.sock. При желании можно выбрать
другое расположение, указав его при установке или изменив конфигурацию серве­
ра позже, а затем перезагрузив демон. В принципе, параметры по умолчанию под­
ходят для большинства сценариев. Как и с другими решениями, выбор параметров
по умолчанию позволяет сэкономить много времени и сил.
Недавние версии Docker Desktop моrут создавать файл docker.sock в корневом
каталоге пользователя внутри .docker/run/, а потом просто создавать ссылку
/var/run/docker.sock к этому местоположению.
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
37
Надежные инструменты
Одна из причин популярности Docker - простые и надежные инструменты. С мо­
мента выпуска возможности Docker постоянно расширяются благодаря усилиям
сообщества. Инструменты, которые поставляются с Docker, поддерживают сборку
образов Docker, базовое развертывание в индивидуальные демоны Docker, распре­
деленный режим Swarm и все функции, необходимые для управления удаленным
сервером Docker. Кроме режима Swarm сообщество работает над решениями для
управления целыми кластерами серверов Docker, а также для планирования и орке­
страции развертываний контейнеров.
Когда мы говорим о Docker Swarm или режиме Swarm (https://docs.docker.com/
engine/swarm) в этой книге, мы имеем в виду встроенный функционал Swarm
в клиенте и сервере Docker, который использует базовую библиотеку SwarmKit.
� В Интернете могут попадаться упоминания более старой, отдельной версии Docker
Swarm, которая сегодня обычно называется классическим Docker Swarm (https://
github.com/docker-archive/classicswarm).
Docker также запустил свой набор инструментов для оркестрации, включая
Compose (https://github.com/docker/compose), Docker Desktop (https://www.
docker.com/products/docker-desktop) и режим Swarm, который обеспечивает связ­
ный процесс развертывания для разработчиков. Продукты Docker для оркестрации
в рабочей среде обычно заметно уступают Kubemetes от Google, хотя не забывайте,
что Kubemetes активно использовал Docker до выпуска версии 1.24 в начале
2022 года (https://kubernetes.io/Ыog/2020/12/02/dockershim-faq). Инструменты
оркестрации Docker все же имеют свое применение. Например, Compose очень
удобен для локальной разработки.
Поскольку у Docker есть как инструмент командной строки, так и удаленный
REST API, к нему можно добавлять инструменты на любом языке. При помощи
инструмента командной строки мы пишем shеll-скрипты, и все, что способен де­
лать клиент, можно запрограммировать с использованием REST API. Docker CLI
так широко известен, что многие другие интерфейсы командной строки для кон­
тейнеров Linux, например podman (https://podman.io/) и nerdctl (https://github.com/
containerd/nerdctl), используют его аргументы для совместимости и простоты вне­
дрения.
Интерфейс командной строки Docker
CLI Docker - это главный интерфейс, через который большинство людей взаимо­
действуют с Docker. Клиент Docker - это программа на Go (https://golang.org/),
которая компилируется и выполняется на всех популярных архитектурах и опера­
ционных системах. Интерфейс командной строки поставляется в комплекте
с Docker на разных платформах и напрямую компилируется из кода Go. Вот непол­
ный список задач, которые можно выполнять с помощью Docker CLI:
♦ сборка образа контейнера;
♦ извлечение образов из реестра в демон Docker и обратно;
Книги для программистов: https://clcks.ru/books-for-it
38
Глава 2
♦ запуск контейнера на сервере Docker на переднем плане или в фоновом режиме;
♦ извлечение журналов Docker с удаленного сервера;
♦ интерактивное выполнение команды в работающем контейнере на удаленном
сервере;
♦ мониторинг статистики о контейнере;
♦ просмотр процессов, запущенных в контейнере.
Наверное, вы уже представляете себе, как из этих задач можно составить рабочий
процесс сборки, развертывания и сопровождения приложений. Интерфейс команд­
ной строки Docker - не единственный способ взаимодействия с Docker, и даже не
всегда самый эффективный.
Docker Engine API
У демона Docker, как и у остальных современных программ, есть API. Именно че­
рез его интерфейс командной строки Docker взаимодействует с демоном. Посколь­
ку API задокументирован и общедоступен, внешние инструменты часто использу­
ют его напрямую. Это удобный механизм, с помощью которого можно в любом
инструменте создавать, проверять и администрировать все образы и контейнеры,
находящиеся под управлением демона Docker. Вряд ли новички сразу будут обра­
щаться к Docker АРТ напрямую, но это очень удобный способ, который стоит иметь
в виду. По мере внедрения Docker в организации вы будете все чаще замечать, как
удобно работать с инструментами через API.
На сайте Docker можно найти подробную документацию для API (https://dockr.ly/
2wxCHnx). По мере развития экосистемы появились хорошие реализации библио­
тек Docker API для всех популярных языков. Компания Docker предлагает SDK для
Python и Go (https://dockr.ly/2w_xCHnx), а у третьих сторон есть неплохие библио­
теки для других языков. Например, мы несколько лет используем эти библиотеки
для Go (https://github.com/fsouza/go-dockerclient) и Ruby (https://github.com/
upserve/docker-api), и они не только хорошо продуманы, но и своевременно
обновляются с выпусками новых версий Docker.
API позволяет выполнять почти те же задачи, что и интерфейс командной строки
Docker. Правда, есть пара исключений - конечные точки, которым требуется по­
токовая передача или терминальный доступ: использование удаленных командных
оболочек (shell) или выполнение контейнера в интерактивном режиме. В этих слу­
чаях обычно проще задействовать одну из упомянутых эффективных клиентских
библиотек или интерфейс командной строки.
Сеть для контейнеров
Контейнеры Linux в основном состоят из процессов, выполняемых прямо в хост­
системе, но на сетевом уровне они ведут себя совсем не так, как другие процессы.
Изначально Docker поддерживал только одну сетевую модель, а сейчас доступны
различные конфигурации, которые подойдут для большинства приложений. ОбычКниги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
39
но контейнеры запускаются в конфигурации по умолчанию - в режиме моста.
Давайте посмотрим, как это работает.
Для этого попробуйте представить, что каждый контейнер Linux ведет себя как
хост в частной сети. Сервер Docker в этом случае выступает как виртуальный мост,
а контейнеры - клиенты, которые находятся за ним. Мост - это просто сетевое
устройство, передающее трафик с одной стороны на другую. Представьте себе вир­
туальную мини-сеть, в которой каждый контейнер похож на хост, подключенный
к этой сети. Фактически у каждого контейнера есть виртуальный интерфейс
Ethemet, подключенный к мосту Docker, и IР-адрес, назначенный виртуальному
интерфейсу (рис. 2.4). Docker позволяет привязывать отдельные порты и группы
портов на хосте к контейнерам, чтобы можно было обращаться к ним извне. Тра­
фиком в основном управляет библиотека vpnkit (https://github.com/moby/vpnkit).
Виртуальная сеть
Сервер Docker
Входящий запрос
к открытому
порту ТСР 10520
elhO
10.0.0.7
NAT
dockerO
172.16.23.7
1111
• • •'
"ii
172.16.23.1
1
1 Исходящий запрос
• от контейнера
Рис. 2.4. Сеть на типичном сервере Docker
Docker выделяет частные подсети из неиспользуемого блока частных подсетей
(RFC 1918, https://www.rfc-editor.org/rfc/rfc1918). Он определяет, какие блоки не
используются на хосте, и назначает один из них виртуальной сети. Она соединяется
мостом с локальной сетью хоста через интерфейс на сервере, который называется
ctockerO. Это означает, что по умолчанию все контейнеры находятся в сети и могут
взаимодействовать друг с другом напрямую, но для обращения к хосту или внеш­
нему миру им требуется интерфейс виртуального моста ctockerO.
Существует много способов настроить сетевой уровень Docker: от назначения сво­
их сетевых блоков до настройки пользовательского интерфейса моста. Многие вы­
бирают механизмы по умолчанию, но иногда приложению требуется более сложная
или специализированная конфигурация. Больше сведений о сетях Docker можно
найти в документации (https://dockr.Iy/2otp461). Подробнее об этом мы поговорим
в главе 11.
Книги для программистов: https://clcks.ru/books-for-it
40
1
Глава 2
При создании рабочего процесса Docker советуем начать с сети по умолчанию.
В дальнейшем может оказаться, что виртуальная сеть по умолчанию вам не под­
ходит. Можно настроить сеть отдельно для каждого контейнера или полностью
� отключить виртуальную сеть для контейнера, указав параметр --net=host для
docker container run. В этом режиме контейнеры Linux используют сетевые уст­
ройства и адреса хоста, обходясь без виртуальных интерфейсов или мостов. При
работе с сетью хоста необходимо помнить о безопасности. Возможны и другие
топологии сети. Мы поговорим о них в главе 11.
Как использовать весь потенциал Docker
Как и большинство инструментов, Docker прекрасно подходит для одних ситуаций
и не годится для других. Например, стеклянную банку можно открыть молотком,
но это не всегда удобно. Лучше заранее изучить, как эффективнее всего применять
инструмент, или хотя бы определить, подходит ли он для ваших целей.
В первую очередь Docker предназначен для приложений, которые не хранят со­
стояние (stateless) или у которых состояние хранится отдельно, в базе данных или
кеше. Такие приложения проще всего контейнеризовать. Docker заставляет при­
держиваться разумных принципов разработки подобных приложений, и позже мы
поговорим о том, почему это хорошо. Правда, если мы попытаемся поместить
в Docker СУБД, то будем, образно выражаясь, плыть против течения. Нельзя ска­
зать, что это невозможно или запрещено - просто это не самый подходящий сце­
нарий для Docker, и если вы начнете с этого, скорее всего, почти сразу разочаруе­
тесь. Развертывать базы данных на Docker можно, но потребуются усилия. Знаком­
ство с Docker лучше всего начать с веб-фронтендов, бэкенд API и коротких задач,
например скриптов сопровождения, для которых обычно можно использовать cron.
Если сначала вы попробуете разобраться с контейнеризацией приложений без
сохранения состояния или с внешним состоянием, то у вас будет фундамент, на ко­
тором потом можно экспериментировать с другими вариантами применения. Мы
рекомендуем начать с приложений без сохранения состояния, и только набравшись
опыта, переходить к другим сценариям. Сообщество постоянно работает над под­
держкой приложений с сохранением состояния в Docker, и, скорее всего, нас ждут
подвижки в этой области.
Контейнеры - это не виртуальные машины
Чтобы лучше понять Docker, попробуйте представить себе контейнеры Linux не как
виртуальные машины, а как очень легкие обертки для одного процесса Unix. При
реализации этот процесс может порождать другие процессы, но внутри него может
находиться только статически компилируемый двоичный код (см. раздел "Виешние
зависимости" главы 9). Контейнеры "эфемерны" - они появляются и исчезают
гораздо быстрее и проще, чем традиционная виртуальная машина.
Виртуальные машины функционируют как замена реального оборудования, кото­
рое можно установить в стойку и оставить там на несколько лет. Они работают
вместо реального сервера, а потому могут существовать долго. Даже в облаке, где
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
41
виртуальные машины активируются и отключаются по запросу, обычно они рабо­
тают по несколько дней. Контейнер может существовать месяцами, а может созда­
ваться для минутной задачи, а затем уничтожаться. Это нормально, но виртуальные
машины обычно используются совсем не так.
Еще один пример: если мы запускаем Docker на mac или Windows, то используем
виртуальную машину Linux для выполнения dockerd, сервера Docker. А на Linux
можно запускать dockerd нативно, т. е. виртуальная машина ни для чего не понадо­
бится (рис. 2.5).
Dщ:ker с виртуальной машиной
(Windows, macOS и т. д.)
Нативный Docker
(Linux)
Рис. 2.5. Типичные варианты установки Docker
Ограниченная изоляция
Контейнеры изолированы друг от друга, но изоляция не такая полная, как можно
ожидать. Мы можем установить ограничения на ресурсы, но по умолчанию кон­
тейнеры будут разделять одни и те же ресурсы ЦП и памяти в хост-системе, как и
следует процессам Unix. Получается, что без дополнительных ограничений кон­
тейнеры будут состязаться за ресурсы. Возможно, вас это устраивает, но нужно
будет продумать некоторые аспекты. В Docker рекомендуется ограничивать
потребление ЦП и памяти, но в большинстве случаев не существует лимитов по
умолчанию, как у виртуальных машин.
Часто много контейнеров используют одни и те же уровни файловой системы. Это
одно из преимуществ Docker, но при обновлении общего образа придется пере­
строить и заново развернуть контейнеры, задействующие старый образ.
Контейнеризованные процессы - это процессы на самом сервере Docker. Они вы­
полняются на том же экземпляре ядра Linux, что и операционная система хоста.
Все процессы контейнера отображаются в обычных выходных данных ps на сервере
Docker. Этот подход существенно отличается от гипервизора, в котором изоляция
процессов предполагает отдельный экземпляр ядра ОС на каждую виртуальную
машину.
Из-за слабой изоляции может возникнуть искушение предоставить больше ресур­
сов хоста, например общие файловые системы для хранения состояния. Дважды
подумайте, прежде чем предоставлять ресурсы хоста контейнеру, - они должны
Книги для программистов: https://clcks.ru/books-for-it
42
Глава 2
использоваться только этим контейнером. Мы поговорим о безопасности контейне­
ров позже, но, как правило, можно усилить изоляцию с помощью политик Security­
Enhanced Linux (SELinux) (https://www.redhat.com/en/topics/linux/what-is-selinux)
или AppArmor (https://apparmor.net/), не нарушая существующие барьеры.
По умолчанию многие контейнеры используют UID О дпя запуска процессов. По­
скольку контейнер ограничен, может показаться, что это безопасно, но на самом
деле это не совсем так. Все выполняется на одном ядре, поэтому из-за многих ти­
пов уязвимостей или просто ошибок конфигурации пользователь root контейнера
· может получить несанкционированный доступ к системным ресурсам, файлам и
процессам хоста. Подробнее о решении этих проблем мы поговорим в разделе
"Безопасность" главы 11.
Контейнеры легковесны
Чуть позже мы обсудим, как это работает, а пока просто упомянем, что создание
контейнера может занимать очень мало места на диске. Простой тест покажет:
только что созданный из существующего образа контейнер занимает всего 12 КБ
дискового пространства. Это очень мало. Виртуальная машина, созданная из. эта­
лонного образа (golden image), может весить сотни и тысячи мегабайт, потому что
ей как минимум требуется полноценная операционная система. Новый контейнер
занимает так мало места, потому что представляет собой просто ссылку на много­
слойный образ файловой системы и включает немного метаданных о конфигура­
ции. По умолчанию для контейнера не создаются никакие копии данных. Контей­
нер - это процесс в существующей системе, который, возможно, просто будет
считывать информацию с диска, и ему может не потребоваться собственная копия
данных, пока не придет время записать данные, уникальные для конкретного
экземпляра контейнера.
Благодаря такой легкости контейнеры целесообразны в ситуациях, в которых соз­
давать виртуальную машину было бы слишком затратно или известно, что процесс
просуществует очень короткое время. Вряд ли кто-то станет запускать целую вир­
туальную машину, чтобы выполнить команду curl на сайте из удаленного располо­
жения. Зато для этой цели очень удобен контейнер.
Сдвиг в сторону неизменяемой инфраструктуры
Если вы развертываете большую часть приложений в контейнерах, можно упро­
стить управление конфигурацией, перейдя на неизменяемую инфраструктуру,
в которой компоненты заменяются полностью, а не правятся на месте. Идея неиз­
меняемой инфраструктуры набирает популярность, потому что в реальности очень
сложно поддерживать кодовую базу для управления действительно идемпотентной
конфигурацией. По мере разрастания кодовая база может выйти из-под контроля,
как крупные монолитные приложения.
Легкий сервер Docker почти не требует управления конфигурацией, а иногда и не
требует совсем. Мы управляем приложением, просто развертывая нужные контей­
неры на сервере. Если серверу требуется важное обновление, например для демона
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
43
Docker или ядра Linux, мы просто поднимаем новый сервер с изменениями, развер­
тываем на нем контейнер, а старый сервер останавливаем или переустанавливаем.
Дистрибутивы Linux на базе контейнеров, например Red Hat Fedora CoreOS
(https://getfedora.org/en/coreos), построены как раз на этом принципе, но нам не
приходится выводить экземпляр из эксплуатации, потому что Fedora CoreOS пол­
ностью обновляется и переходит на обновленную ОС. Конфигурация и рабочая
нагрузка остаются в контейнерах, и нам почти не требуется настраивать ОС.
Благодаря четкому разделению между развертыванием и конфигурацией серверов
многие используют для рабочей среды такие инструменты, как HashiCorp Packer
(https://www.packer.io/intro/index.html), чтобы собирать образы облачных вирту­
альных серверов, а затем при помощи Docker полностью или почти полностью
обходятся без систем управления конфигурацией.
Приложения без сохранения состояния
Веб-приложение, которое храни1:_ состояние в базе данных, - идеальный кандидат
на контейнеризацию. Приложения без сохранения состояния должны сразу отве­
чать на отдельный запрос и не отслеживают информацию между запросами от
одного или нескольких клиентов. Кроме того, в контейнерах можно запускать эфе­
мерные экземпляры Memcached (https://memcached.org/). Скорее всего, ваше веб­
приложение хотя бы отчасти хранит состояние локально, например файлы конфи­
гурации. Может показаться, что это немного, но если придется встраивать конфи­
гурацию в образы, то мы не сможем без ограничений повторно использовать образ
и будет сложнее развертывать его в разных средах, не храня несколько образов для
различных целевых платформ развертывания.
Во многих случаях в процессе контейнеризации мы переносим состояние конфигу­
рации приложения в переменные среды, которые можно передавать приложению
во время выполнения. Таким образом мы не встраиваем конфигурацию в контей­
нер, а применяем ее при развертывании. Благодаря этому можно задействовать
один и тот же контейнер в рабочей или промежуточной среде. В большинстве ком­
паний для этих сред потребовалось бы много разных параметров конфигурации,
например URL-aдpeca для подключения к внешним службам, используемым при­
ложением.
При реализации контейнеров вы заметите, что размер приложения постоянно со­
кращается, потому что вы стараетесь оптимизировать его, убрав все лишнее. Кста­
ти, когда приходится продумывать все, что может потребоваться для распределен­
ного запуска в виде контейнера, можно прийти к очень интересным проектным
решениям. Например, если сервис собирает данные, обрабатывает их и возвращает
результат, можно настроить контейнеры с этой задачей на множестве серверов, а
затем объединять ответ в другом контейнере.
Книги для программистов: https://clcks.ru/books-for-it
44
1
Глава 2
Хранение состояния во внешнем хранилище
Если Docker лучше всего подходит для приложений без сохранения состояния, как
все-таки хранить состояние? Конфигурация обычно передается в переменных сре­
ды. Docker нативно поддерживает переменные среды, и они хранятся в метадан­
ных, которые составляют конфигурацию контейнера. Это значит, что при переза­
пуске контейнера приложению каждый раз будет передаваться одна и та же конфи­
гурация. За конфигурацией контейнера теперь легко наблюдать, пока он запущен,
а это заметно упрощает отладку, хотя передавать секреты в переменных среды
небезопасно. Мы можем хранить конфигурацию приложения во внешнем храни­
лище, например в Consul (https://www.consul.io/) или PostgreSQL (https://www.
postgresql.org/).
Масштабируемые приложения часто хранят состояние в базе данных, и ничего
в Docker не мешает контейнерным приложениям поступать так же. Правда, если
приложение должно хранить файлы, возникнут проблемы. При хранении данных в
файловой системе контейнера производительность окажется низкой, пространства
будет мало, а при пересоздании контейнера состояние не сохранится. Когда сервис
хранит состояние и внешнее хранилище не используется, мы целиком теряем со­
стояние при повторном развертывании этого сервиса. Если приложению нужно
хранить состояние файловой системы, следует тщательно все продумать, прежде
чем реализовывать его с помощью Docker. Если вы думаете, что контейнеры Linux
вам подходят, создайте решение, в которой состояние будет храниться в централи­
зованном расположении вне зависимости от того, на каком хосте запущен контей­
нер. В некоторых случаях для этого потребуется сервис, вроде Amazon Simple
Storage Service (Amazon S3), OpenStack Swift или локальное блочное хранилище,
или придется даже подключать тома EBS или диски iSCSI внутри контейнера. Так­
же можно использовать плаrины томов Docker (https://docs.docker.com/engine/
extend/plugins_volume), и мы поговорим об этом в главе 11.
Хотя мы можем хранить состояние в локальной файловой системе хоста, обычно
новичкам это не рекомендуется. Лучше начать с приложений, которым не требует­
ся хранить состояние. Для этого есть много причин, но почти во всех случаях это
связано с появлением зависимостей между контейнером и хостом, а это мешает
использовать Docker как по-настоящему динамичный сервис поставки приложений
с горизонтальным масштабированием. Если контейнер хранит состояние в локаль­
ной файловой системе· хоста, его можно развертывать только там, где есть эта
локальная файловая система. Удаленные тома, которые можно подключать дина­
мически, могут исправить ситуацию, но это тоже считается усложненным случаем.
Рабочий процесс Docker
Как и многие инструменты, Docker требует придерживаться определенного рабоче­
го процесса, который хорошо впишется во многие компании, хотя, скорее всего,
сейчас ваша команда работает чуть иначе. Мы адаптировали рабочие процессы на­
шей организации к подходу Docker и можем с уверенностью сказать, что это изме­
нение положительно повлияет на многие наши команды разработчиков. Если проКниги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker
1
45
цесс правильно реализован, он поможет сократить издержки на коммуникацию
между специалистами.
Контроль изменений
Docker «из коробки» предлагает два готовых способа контроля изменений: один
отслеживает слои файловой системы, из которых состоит каждый образ Docker, а
второй - теги для этих образов.
Слои файловой системы
Контейнеры Linux состоят из наложенных друг на друга слоев файловой системы,
каждый с уникальным хешем для идентификации, и все наборы изменений, вне­
сенных в процессе сборки, накладываются поверх предыдущих изменений. Это
удобно, потому что при новой сборке требуется пересобрать только слои, следую­
щие за внесенными изменениями. Таким образом мы экономим время и трафик,
поскольку контейнеры поставляются в виде слоев, и уже сохраненные на сервере
слои отправлять не нужно. Если раньше вы применяли много классических инст­
рументов развертывания, то знаете, что в итоге вы снова и снова отправляете на
сервер сотни мегабайтов одинаковых данных с каждым развертыванием. Это очень
неэффективно и, хуже того, невозможно узнать точно, что изменилось между раз­
вертываниями. Благодаря наличию слоев и тому факту, что контейнеры Linux
включают все зависимости приложения, Docker позволяет лучше видеть изменения,
'-----­
поставляемые в рабочую среду.
Если сформулировать проще, образ Docker содержит все необходимое для выпол­
нения приложения. Меняя одну строку кода, мы определенно не хотим тратить
время на пересборку каждой необходимой зависимости в новый образ. Вместо
этого Docker использует кеш сборки, чтобы пересобирать только слои, затронутые
изменением.
Теги образов
Второй тип контроля изменений в Docker помогает ответить на важный вопрос:
какая версия приложения была развернута последней? Ответ найти не всегда легко.
Для приложений, не использующих контейнеры, существуют разные решения - от
тегов Git для каждого выпуска до журналов развертываний, сборок с тегами и т. д.
Если координацию развертывания вы осуществляете, например, с помощью
Capistrano (https://capistranorb.com/), этот инструмент будет хранить на сервере
заданное число предыдущих выпусков, и с помощью символьных ссылок помечать
один из них как текущий.
В большой рабочей среде у разных приложений будут различные способы контроля
версий развертывания. Более того, в средах с разными языками у приложений мо­
гут быть различные инструменты развертывания. На вопрос о предыдущей развер­
нутой версии может быть много ответов - зависит от того, у кого мы спраш�ваем
и какое приложение имеем в виду. У Docker есть встроенный механизм для кон­
троля версий: выбор тега образа в ходе стандартного процесса сборки. На сервере
Книги для программистов: https://clcks.ru/books-for-it
46
1
Глава 2
может быть несколько версий приложения, чтобы упростить откат к предьщущей.
Это понятный функционал, доступный и в других инструментах развертывания, но,
имея образы контейнеров, процесс можно стандартизировать для всех приложений
и команд. Такой подход упрощает коммуникации между командами специалистов
и подготовку развертывания, потому что у нас будет единый источник истины для
выпусков приложений.
Во многих примерах в Интернете и в этой книге вы встретите тег latest (послед­
ний). Он удобен, когда вы начинаете работать с Docker или пишете примеры, по­
тому что всегда выбирается самая недавняя сборка образа. Правда latest не при­
вязан к конкретным образам, так что его лучше не использовать в рабочих средах� мы не сможем контролировать обновление зависимостей и откатывать
приложение до образа latest, ведь у старых версий этого тега уже не будет. Кроме
того, будет сложно проверить, выполняется ли на разных серверах один и тот же
образ. Поэтому тег latest не рекомендуется для рабочей среды или для исходных
образов.
В конвейере CI/CD лучше использовать теги, уникально определяющие конкретный
коммит исходного кода. В рабочем процессе git это может быть хеш git, связанный
с коммитом. Когда будете готовы выпустить образ, примените семантическое вер­
сионирование (https://semver.org/) и пометьте образ тегами: 1.4.3, 2.0.0 и т. д.
Управление тегами требует дополнительной работы, зато избавляет от многих
неприятных сюрпризов во время сборки и развертывания.
Сборка
Во многих организациях сборка приложений считается темным искусством - мало
кто знает, что нужно повернуть и куда нажать, чтобы получить жизнеспособный
программный пакет. Правильная сборка - это затратная часть процесса разверты­
вания приложения. Docker не решает всех проблем, зато предоставляет стандар­
тизированную конфигурацию и набор инструментов для сборок. С их помощью
гораздо проще научиться собирать приложения и делать это правильно.
В интерфейсе командной строки Docker есть флаг build, который позволяет на
основе Docker.file создать образ Docker. Каждая команда в Dockerfile создает новый
слой в образе, так что по содержимому Dockerfile легко понять, что собой пред­
ставляет сборка. Преимущества стандартизации в том, что любой инженер, у кото­
рого есть опыт работы с Dockerfile, может легко изменить сборку любого приложе­
ния. Поскольку образ Docker представляет собой стандартизированный артефакт,
подготовка к сборке будет одинаковой независимо от языка программирования,
базового образа или количества слоев. Dockerfile обычно отправляется в систему
контроля версий, что упрощает отслеживание изменений в сборке. В современных
многоэтапных сборках в Docker можно определить среду сборки отдельно от ито­
гового образа программного пакета. В результате мы получаем для среды сборки
такие же широкие возможности конфигурации, как для контейнера в рабочей среде.
Часто сборка Docker выполняется одним вызовом команды docker irnage build, при
котором создается один артефакт - образ контейнера. Поскольку вся логика, отно­
сящаяся к сборке, обычно полностью содержится в Dockerfile, мы можем легко созКниги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker
1
47
давать стандартные задания сборки, которые любая команда разработчиков сможет
использовать в системах сборки, вроде Jenkins (https://jenkins-ci.org/). Многие
компании, например еВау, применяют стандартизированные контейнеры Linux для
сборки образов из Dockerfile. Решения по сборке, предлагаемые по модели "про­
граммное обеспечение как услуга" (Software as а Service, SaaS), например Travis CI
(https://travis-ci.com/) или CodeShip (https://codeship.com/), также превосходно
поддерживают сборки Docker.
Мы можем автоматизировать создание нескольких образов, поддерживающих раз­
ные базовые архитектуры, например х86 или АRМ, благодаря недавно представ­
ленной поддержке BuildKit (https://github.com/moby/buildkit) в Docker.
Тестирование
В самом Docker нет встроенного фреймворка для тестирования, но способ сборки
контейнеров упрощает тестирование в контейнерах Linux.
Тестировать приложения можно разными способами - от модульных тестов до
полноценного интеграционного тестирования в среде, близкой к рабочей. Docker
поддерживает эффективное тестирование, поскольку гарантирует, что программ­
ный пакет, прошедший тестирование, в том же самом виде поступит в рабочую
среду. Это возможно благодаря тому, что мы можем использовать Docker SНА для
контейнера или пользовательский тег, чтобы поставлять точную версию прило­
жения.
Контейнеры по определению включают все нужные зависимости, так что тестиро­
вание дает надежный результат. Если фреймворк модульного тестирования под­
тверждает, что тест образа контейнера выполнен успешно, мы можем быть увере­
ны, что при развертывании у нас не возникнет проблем с имеющейся версией биб­
лиотеки. В случае других технологий достичь этого сложнее, и даже файлы Java
WAR (Java Web application ARchive), например, не включают тестирование сервера
приложений. Приложение Java, развернутое в контейнере Linux, обычно также
включает сервер приложений, например Tomcat, так что для всего стека можно
провести "дымовое" (smoke) тестирование перед отправкой в рабочую среду.
Дополнительное преимущество поставки приложений в контейнерах Linux заклю­
чается в том, что при наличии нескольких приложений, которые взаимодействуют
друг с другом удаленно через API или подобный механизм, разработчики одного
приложения могут опираться на ту версию другого приложения, которая в данный
момент помечена как соответствующая нужной среде, например рабочей или про­
межуточной (staging). В команде разработчиков одного приложения не нужен спе­
циалист, который разбирался бы в работе и развертывании другого приложения.
Если представить, что действие происходит в сервис-ориентированной архитектуре
с бессчетным числом микросервисов, станет очевидно, насколько контейнеры
Linux упрощают задачи по разработке или контролю качества в условиях огромно­
го количества вызовов АР[ между микросервисами.
Обычно в организациях, которые используют контейнеры Linux в рабочей среде,
применяются автоматизированные интеграционные тесты для развертывания верКниги для программистов: https://clcks.ru/books-for-it
48
Глава 2
сионированного набора контейнеров Linux для разных сервисов в соответствии
с текущими развернутыми версиями. Интеграционные тесты для нового сервиса
задействуют именно те версии, которые будуr нужны сервису после развертыва­
ния. Раньше в среде с разными языками для этого потребовалась бы длительная
подготовка, но благодаря стандартизации, предоставляемой контейнерами Liлux,
это стало гораздо проще.
Упаковка
Сборки Docker создают образ, который можно считать единым артефактом сборки,
хотя технически он может состоять из нескольких слоев файловой системы. Неза­
висимо от языка, на котором написано приложение, или используемого дистрибу­
тива Linux в результате сборки мы получаем многослойный образ Docker. При этом
весь процесс сборки выполняется с помощью инструментов Docker. Образ сборки
похож на метафорический грузовой контейнер, от которого Docker и получил свое
название (docker в переводе с английского- "портовый погрузчик"). Это цельная
транспортабельная единица, с которой можно работать независимо от ее содержи­
мого. Docker также имеет дело с одной стандартной «емкостью» - образом
Docker. Это очень удобно, потому что одни и те же инструменты подойдуr нам для
разных приложений, и готовые инструменты для контейнеров, созданные кем-то
другим, будуr хорошо сочетаться с нашими образами сборки.
С помощью Docker мы можем легко переносить приложения, которым раньше тра­
диционно требовалась трудоемкая конфигурация перед развертыванием на новом
хаете или в новой системе разработки. После сборки контейнер можно без усилий
развернуть в любой системе с помощью сервера Docker с той же архитектурой.
Развертывание
Для развертывания существует столько инструментов, что перечислить их все было
бы невозможно. Это могут быть shеll-скрипты, Capistrano (https://capistranorb.com/),
Fabric (https://www.fabfile.org/), АлsiЫе (https://www.ansiЫe.com/) или собствен­
ные инструменты компании. В больших организациях в каждой команде обычно
есть один или два человека, которые умеют волшебным образом осуществлять раз­
вертывания. Если что-то идет не так, команда ждет, пока они все исправят. Навер­
ное, вы уже догадываетесь, что Docker меняет ситуацию к лучшему. Встроенные
инструменты поддерживают простую и линейную стратегию развертывания, с по­
мощью которой можно запустить сборку на хаете. Стандартный клиент Docker раз­
вертывает приложение только на один хост за раз, но есть много инструментов,
с помощью которых можно выполнять развертывание в кластере хостов Docker или
других систем, совместимых с контейнерами Linux. Благодаря предоставляемой
Docker стандартизации сборку можно развернуть в любой из этих систем без лиш­
них усилий со стороны команды разработчиков.
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
49
Экосистема Docker
За несколько лет развилось обширное сообщество Docker, куда входят разработчи­
ки и системные администраторы. Как и философия DevOps, это движение создает
инструменты, позволяющие решать задачи сопровождения с помощью программ­
ного кода. Если сам Docker не предлагает необходимые средства, другие компании
и отдельные разработчики заполняют этот пробел. Многие инструменты поставля­
ются с открытым кодом, т. е. любая компания может адаптировать их под свои по­
требности.
Docker - это коммерческая организация, которая передала сообществу большую
часть базового кода Docker. Другие компании могут присоединиться к сообществу
и внести свой вклад в развитие проекта. Если вы ищете поддерживаемые версии
базовых инструментов Docker, узнайте больше на сайте Docker (https://www.
docker.com/support).
Оркестрация
Первая важная категория инструментов, которые расширяют функционал базовой
версии Docker с контейнерами Linux, используется для оркестрации и массового
развертывания. Первые инструменты развертывания, вроде Centurion от New Relic
(https://github.com/newrelic/centurion), Helios от Spotify (https://github.com/
spotify/helios) и AnsiЫe Docker (https://oreil.lyN8X_t) 1 , все еще придерживаются
традиционного подхода к развертыванию, но в качестве распространяемого про­
граммного пакета используют контейнер. В них применяется простой и понятный
подход, и мы получаем множество преимуществ Docker без лишних сложностей.
Сейчас эти инструменты уступают более надежным и гибким решениям, например
Kubemetes.
Полностью автоматический планировщик, например KuЬemetes (https://kuЬernetes.io/)
или Apache Mesos с Marathon (https://mesos.apache.org/), предлагают больше воз­
можностей и почти полностью берут на себя контроль над пулом хостов, освобож­
дая вас от этой задачи. Доступно множество коммерческих решений, например
Nomad от HashiCorp (https://www.nomadproject.io/), Mesosphere DC/OS (Datacenter
Operating System) (https://dcos.io/) и Rancher (https://rancher.com/)2 • Экосистемы
с бесплатными и коммерческими вариантами продолжают активно развиваться.
Неизменяемые атомарные хосты
Еще один вариант использования Docker - неизменяемые атомарные хосты.
Обычно серверы и виртуальные машины - это системы, которые организация
тщательно собирает, настраивает и обслуживает, чтобы предоставлять широкий
набор возможностей для разных вариантов применения. Обновления часто прихо­
дится устанавливать с помощью неатомарных операций, и можно представить раз1 Полный URL: https://docs.aosiЫe.com/ansiЫe/latest/collectioos/commuoity/dockeг/docsite/
sce oaгio_guide.html#aosible-collectioos-community-dockeг-docsite-sceoaгio-guide.
1 У некоторых из этих коммерческих решений есть бесплатные версии.
Книги для программистов: https://clcks.ru/books-for-it
50
Глава 2
ные варианты, когда в конфигурациях хоста возникают отклонения, вызывая
непредвиденное поведение. Для большинства современных систем обновления и
патчи устанавливаются на «коленке», при этом в разработке чаще всего заново раз­
вертывается целая копия нового приложения, а не отдельные обновления. Одно из
преимуществ контейнеров состоит в том, что они повышают атомарность прило­
жений по сравнению с традиционными моделями развертывания.
Представьте, что контейнерный подход можно было бы применить даже к опера­
ционной системе. Не пришлось бы управлять конфигурациями, пытаясь устанавли­
вать обновления и объединять изменения компонентов операционной системы, зато
можно просто развернуть новый тонкий образ ОС и перезагрузить сервер. А если
что-то сломается, можно так же легко откатиться до точного образа, который
использовался раньше.
Эта идея лежит в основе атомарных хостов на базе Linux, например Red Hat Fedora
CoreOS, Bottlerocket OS (https://github.com/bottlerocket-os/bottlerocket) и др. Вы
сможете легко сносить и повторно развертывать не только приложения - эту фи­
лософию можно применить ко всему программному стеку, поддерживая высокий
уровень согласованности и устойчивости.
Неизменяемый, или атомарный, хает (https://gist.github.com/jzЬ/0f336c6t23a0ba
145Ь0а) обычно занимает минимум места и спроектирован таким образом, чтобы
поддерживать контейнеры Linux и Docker, а также атомарные обновления и откаты
операционной системы, которыми можно легко управлять с помощью инструмен­
тов оркестрации для нескольких хостов на "голом железе" (bare metal - без ОС)
и распространенных платформах виртуализации.
В главе 3 мы рассмотрим, как использовать неизменяемые хосты в процессе развер­
тывания. При этом становится возможным создать удивительно идентичные стеки
в среде разработки и рабочей среде.
Дополнительные инструменты
Docker не представляет собой единое решение - скорее, это большой набор воз­
можностей. Часто возникают ситуации, когда стандартного набора не хватает,
и тогда мы можем воспользоваться обширной экосистемой инструментов, улучшаю­
щих или дополняющих функционал Docker. Хорошие инструменты для рабочей
среды часто используют Docker API, например Prometheus (https://prometheus.io/)
для мониторинга и AnsiЫe (https://www.ansiЫe.com/) для простой оркестрации.
Другие опираются на поддержку плагинов - исполняемых программ, которые на
основе определенной спецификации получают данные из Docker и отправляют их
обратно.
Многие плагины Docker считаются устаревшими, им на смену приходят новые ре­
шения. Тщательно все изучите, прежде чем выбрать плагин, - убедитесь, что он
подходит для вашего случая и не устареет в ближайшее время.
Есть много других хороших инструментов, которые взаимодействуют с API или
выполняются как плагины. Многие из них появились, чтобы облегчить работу
Книги для программистов: https://clcks.ru/books-for-it
Общие сведения о Docker 1
51
с Docker в разных облаках и позволяют бесшовно интегрировать Docker в облако.
Сообщество продолжает придумывать инновации, экосистема непрерывно расши­
ряется, появляются все новые решения и инструменты. Если вы не можете решить
какую-то задачу в своей среде, попробуйте найти подходящее решение в эко­
системе.
Заключение
Мы кратко рассмотрели, что собой представляет Docker. Мы вернемся к этому
обсуждению позже и подробнее рассмотрим архитектуру Docker, примеры исполь­
зования инструментов сообщества, а также аспекты проектирования надежных
контейнерных платформ. Скорее всего, вам не терпится перейти к практике, поэто­
му в следующей главе мы установим и запустим Docker.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 3
Установка Doker
Сейчас вы уже примерно представляете себе, что такое Docker, что он умеет и чего
не умеет. Давайте теперь установим его, чтобы перейти к практике. Действия по
установке Docker зависят о платформы разработки и дистрибутива Linux, с по­
мощью которого вы размещаете приложения в рабочей среде.
В этой главе мы рассмотрим, как создать работоспособную среду разработки
Docker на большинстве современных настольных операционных систем. Сначала
мы установим клиент Docker на нативной платформе разработки, а затем поднимем
сервер Docker на Linux. Наконец, мы протестируем установку и убедимся, что все
работает нормально.
Клиент Docker для управления сервером Docker может работать на Windows и
macOS, но контейнеры Linux можно собирать и запускать только в Linux. В других
системах потребуется виртуальная машина или удаленный сервер для размещения
сервера Docker на базе Linux. Docker Community Edition, Docker Desktop и Vagrant,
о которых мы поговорим чуть позже, предлагают некоторые варианты решения
этой проблемы. Мы также можем запускать контейнеры Windows в системах
Windows, и об этом мы поговорим в разделе "Контейнеры Windows" главы 5, но
в основном в этой книге мы будем рассматривать контейнеры Linux.
С развитием технологий экосистема Docker очень быстро меняется, становится
более надежной и учится решать все больше задач. Некоторые функции, рассмат­
риваемые в этой книге и в других источниках, могут уже устареть. Узнать, какие
� функции устарели и будут удалены, можно из документации на https://docs.
docker.com/engine/deprecated.
В большинстве примеров кода в этой книге предполагается, что у вас традицион­
ная командная оболочка Unix. Вы можете работать с PowerShell, но в некоторые
команды нужно будет внести изменения.
Если вам необходимо использовать прокси, убедитесь, что он правильно настроен
для Docker.
Клиент Docker
Клиент Docker нативно поддерживает 64-разрядные версии Linux, Windows и
macOS.
Большинство популярных дистрибутивов Linux произошли от DeЬian или Red Hat.
Системы DeЬian используют формат пакета deb и Advanced Package Tool (apt)
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker
1
53
(https://wiki.deblan.org/AptCLI) для установки пакетов программного обеспече­
ния. В системах Red Hat для этой цели предусмотрены файлы RPM Package
Manager (rpm) и Yellowdog Updater, Modified (yum) (https://en.wikipedia.org/
wiki/Yum_(software)) или Dandified yum (dnf) (https://goo.gl!ГdkGRS). В Alpine
Linux, который часто встречается в средах, где операционная система должна
занимать очень мало места, для этого применяется Alpine Package Keeper (apk)
(https://wiki.alpinelinux.org/wiki/Package_management).
В macOS и Microsoft Windows устанавливать пакеты программного обеспечения и
управлять ими проще всего с помощью встроенных установщиков с графическим
интерфейсом. Homebrew for macOS (https://brew.sh/) и Chocolatey for Windows
(https://chocolatey.org/) тоже очень популярны среди технически подкованных
пользователей.
&
В этом разделе мы рассмотрим несколько подходов к установке Docker. Выберите
первый, который вам подойдет. Если используется несколько версий, нужно уметь
переключаться между ними, иначе могут возникнуть проблемы.
Варианты на выбор: Docker Desktop, Docker Community Edition, менеджер пакетов
операционной системы и Vagrant.
Актуальную документацию по установке см. на сайте Docker (https://docs.
docker.com/get-docker).
Linux
Рекомендуется запускать Docker на свежей версии предпочитаемого дистрибутива
Linux. Более старые выпуски тоже подойдут, но могут возникнуть проблемы со
стабильностью. Обычно требуется ядро 3.8 или более поздней верСИ'1, и мы реко­
мендуем последнюю стабильную версию выбранного дистрибутива. В следующих
инструкциях предполагается, что вы используете последний стабильный выпуск
дистрибутива Ubuntu или Fedora Linux.
Мы не будем рассматривать его здесь, но можно использовать Docker Desktop for
Linux (https://docs.docker.com/desktop/linux/install), если демон Docker должен
работать на локальной виртуальной машине, а не напрямую в системе.
Ubuntu Linux 22.04 (64-разрядная)
Начнем с установки Docker на 64-разрядной версии Ubuntu Linux 22.04.
Актуальные инструкции и описание других версий Ubuntu см. на странице Docker
� Community Edition for Ubuntu (https://dockr.ly/2NwNbuw).
�
Первые две команды удаляют старые версии Docker, если те установлены. Пакеты
несколько раз переименовывались, так что нужно указать несколько возможньrх
вариантов:
Книги для программистов: https://clcks.ru/books-for-it
54
Глава 3
$ sudo apt-get remove docker docker.io containerd runc
$ sudo apt-get remove docker-engine
lJ!N
�
Можно игнорировать ошибки apt-get UпаЫе to locate package (Не удалось найти
пакет) или Package is not installed (Пакет не установлен).
Далее мы добавим необходимые зависимости и репозиторий apt для Docker
Community Edition. Таким образом мы сможем получить и установить пакеты для
Docker и убедиться, что они подписаны:
$ sudo apt-get update
$ sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
$ sudo mkdir -р /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/uЬuntu/gpg 1\
sudo gpg --dearmor -о /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
$ echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/uЬuntu \
$(lsb_release -cs) staЬle" 1\
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Создав и настроив репозиторий, выполним следующие команды для установки
Docker:
$ sudo apt-get update
$ sudo apt-get install \
docker-ce \
docker-ce-cli \
containerd.io \
docker-compose-plugin
Если никаких ошибок не появилось, Docker установлен.
Fedora Linux 36 (64-разрядная)
Теперь установим Docker на 64-разрядной версии Fedora Linux 36.
lJ!N
�
Актуальные инструкции и описание других версий Fedora см. на странице Docker
Community Edition fог Fedora (https://dockr.ly/2NwNdTa).
Первая команда удаляет старые версии Docker, если они имеются. Как и в системах
Ubuntu, пакет был несколько раз переименован, так что нужно указать несколько
вариантов:
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker 1
55
$ sudo dnf remove -у\
docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
Далее мы добавим необходимые зависимости и репозиторий dnf для Docker
Community Edition:
$ sudo dnf -у install dnf-plugins-core
$ sudo dnf config-manager \
--add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
Теперь можно установить текущую версию Docker Community Edition:
$ sudo dnf install -у\
docker-ce \
docker-ce-cli \
containerd.io \
docker-coпpose-plugin
macOS, Мае 05 Х
Для установки Docker в macOS используйте официальный установщик Docker
DesktQp.
Установщик с графическим интерфейсом
Загрузите последюою версию установщика Docker Desktop for Мае (https://dockr.ly/
2wyTpCO) и дважды щелкните по значку загруженной программы. Следуйте инст­
рукциям в установщике. Docker Desktop for macOS использует проект xhyve
(https://github.com/machyve/xhyve) и гипервизор {https://developer.apple.com/
documentation/hypervisor) Apple, чтобы предоставлять легкий нативный слой вир­
туализации для компонента сервера Linux. Он необходим для запуска виртуальных
машин Linux, на которых будут собираться образы Docker и запускаться контей­
неры.
Установка Homebrew
Вы также можете установить инструменты Docker CLI с помощью популярной сис­
темы управления пакетами Homebrew (https://docs.brew.sh/lnstallation) для macOS.
Если вы выберете этот подход, установите Vagrant для создания и администрироКниги для программистов: https://clcks.ru/books-for-it
56
Глава З
вания виртуальной машины Linux. Мы поговорим об этом далее в этой главе,
в разделе "Сервер на базе виртуальной машины без Linux".
Microsoft Windows 11
Вот как установить Docker Desktop в Windows 11.
Рекомендуется настроить подсистему Windows для Linux (WSL2) (https:/ldocs.
microsoft.com/en-us/windows/wsl/install) до установки Docker Desktop, а затем
выбрать из доступных вариантов в установщике Docker Desktop, чтобы установить
WSL2.
1
Docker Desktop for Windows может использовать Hyper-V для предоставления на­
тивноrо слоя виртуализации для серверных компонентов Linux, но WSL2 предлага­
ет самый удобный подход для работы с контейнерами Linux.
Загрузите последнюю версию установщика Docker Desktop for Windows (https://
dockr.ly/2C0n7H0) и дважды щелкните по значку загруженной программы. Сле­
дуйте инструкциям в установщике.
Включение режима контейнеров Linux для Windows
По умолчанию установка Docker Desktop на Windows должна быть настроена для
контейнеров Linux, но если вы получите сообщение "по matching manifest for
windows/amd64" (нет подходящего манифеста для windows/amd64) или что-то по­
добное, скорее всего, Docker Desktop настроен для контейнеров Windows.
Контейнеры Linux остаются самым популярным типом контейнеров, и они потре­
буются вам для выполнения практических упражнений в этой книге. Чтобы изме­
нить настройку Windows, нажмите правой кнопкой мыши на значке Docker на
панели задач Windows и выберите пункт Switch to Linux containers (Переключить­
ся на контейнеры Linux), как показано на рис. 3.1 и 3.2.
При необходимости мы можем легко переключаться между контейнерами Linux и
Windows.
Установка Chocolatey
Вы также можете установить инструменты Docker CLI с помощью популярной сис­
темы управления пакетами Chocolatey (https://docs.chocolatey.org/en-us/choco/
setup) для Windows. Если вы выберете этот подход, установите Vagrant для созда­
ния и администрирования виртуальной машины Linux. Мы поговорим об этом
далее, в разделе "Сервер на базе виртуальной машины без Linux".
На сайте Docker есть инструкции по установке дополнительных сред
�
� (https:/ldocs.docker.com/engine/install).
1
Полный URL: https:/Лeaгn.micгosoft.com/en-us/viгtualization/hypeг-v-on-windows/about.
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker
8
ii
�
57
Docker Deslctop is runnIng
Dashboard
►
spkane
Settings
Check for Updates
Ct,·t�Comma
TrouЫeshoot
Switch to Linux containers...
About Docker Desktop
Documentation
Quick Start Gu,de
Docker Hub
•
Extensions
11
Pause
t'>
(.!)
►
Restart
Qt1it Docker Desktop
�
=
о
тJi
11
л е ОФ>)
12:09 РМ
2/3/2023
Рис. 3.1. Переключение на контейнеры Linux
х
Switch to Linux containers
You are about to switch to Linux containers. Existing containers will continue to run, but you
will not Ье аЫе to manage them until you switch back to windows containers. No data will
Ье lost otherwise.
Do you want to continue?
О Don't show this message again
Switch
1/
Сапсеl
Рис. 3.2. Переключение на контейнеры Linux - подтверждение
Сервер Docker
Сервер Docker представляет собой отдельный от dосkеr-клиента исполняемый
файл. С помощью сервера мы управляем большей частью задач, для которых обыч­
но используется Docker. Далее мы поговорим о самых распространенных способах
управления сервером Docker.
Книги для программистов: https://clcks.ru/books-for-it
58
1
Глава 3
Docker Desktop и Docker Community Edition уже настроили за нас сервер, так что
если вы выбрали этот вариант, не нужно больше ничего делать. Можно только
убедиться, что сервер (dockerd) запущен. В Windows и macOS для этого достаточно
� запустить приложение Docker. В Linux потребуется выполнить следующие команды
systemctl, чтобы запустить сервер.
Linux с systemd
Текущие выпуски Fedora и Ubuntu управляют процессами в системе с помощью
Поскольку мы уже
установили Docker, можно выполнить следующую команду, чтобы сервер запус­
кался каждый раз при загрузке системы:
systerro (https://www.freedesktop.org/wiki/Software/systemd).
$ sudo systemctl enaЬle docker
Эта команда указывает systemd добавить сервис docker в автозапуск при старте сис­
темы или при переключении на уровень выполнения по умолчанию (default
runlevel). Для запуска сервера Docker введите следующую команду:
$ sudo systemctl start docker
Сервер на базе виртуальной машины без Linux
Если вы работаете с Docker в Microsoft Windows или macOS, вам потребуется вир­
туальная машина, чтобы поднять сервер Docker для тестирования. Docker Desktop
сам создает эту виртуальную машину, если вы используете нативную технологию
виртуализации на этих платформах. Если у вас старая версия Windows или вы не
можете использовать Docker Desktop по другим причинам, возможно, Vagrant
(https://www.vagrantup.com/) поможет вам в создании и администрировании вир­
туальной машины Linux для севера Docker.
Кроме Vagrant есть и другие инструменты виртуализации, например Lima на
macOS (https://github.com/lima-vm/lima), или стандартный гипервизор, которые
позволят создать локальный сервер Docker в зависимости от конкретных предпоч­
тений и потребностей.
Vagrant
Vagrant предоставляет поддержку для нескольких гипервизоров и позволяет ими­
тировать даже самые сложные среды.
Vagrant часто используется при разработке с Docker для поддержки тестирования
образов, которые соответствуют рабочей среде. Vagrant поддерживает любые дист­
рибутивы - от самых общих, вроде Red Hat Enterprise Linux (https://www.
redhat.com/en/technologies/linux-platforms/enterprise-linux) и Ubuntu (https://
ubuntu.com/), до специализированных версий для атомарных хостов, например
Fedora CoreOS (https://getfedora.org/en/coreos).
Vagrant можно легко установить на большинстве платформ, загрузив автономный
(self-contained) пакет (https://www.vagrantup.com/downloads.html).
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker
1
59
Рассмотренный далее пример с Vagrant не считается безопасны� и не рекоменду­
_
ется. Это просто демонстрация базовых требовании для настроики и использова­
ния виртуальной машины для удаленного сервера Docker. Не забудьте позабо­
титься о безопасности сервера, это очень важно.
По возможности используйте для разработки Docker Desktop.
Вам потребуется установить в системе один из следующих rипервизоров:
♦ VirtualВox
• Предоставляется бесплатно
• Поддерживает разные платформы на большинстве архитектур
♦ VMware Workstation Pro/Fusion2
• Коммерческое решение
• Поддерживает разные платформы на большинстве архитектур
♦ HyperV3
• Коммерческое решение
• Поддерживает Windows на большинстве архитектур
♦ КУМ
• Предоставляется бесплатно
• Поддерживает Linux на большинстве архитектур.
По умолчанию Vagrant предполагает, что мы используем rипервизор VirtualВox,
но этот параметр можно поменять с помощью флага --provider (https:/Лearn.
hashicorp.com/tutorials/vagrant/getting-started-providers) в команде vagrant.
В следующем примере мы создадим хост Docker на базе Ubuntu с демоном Docker.
Затем мы создадим на хосте каталог с именем docker-host и перейдем в него:
$ mkdir docker-host
$ cd docker-host
Дr�я использования Vagrant потребуется найти Vagrant Вох (образ виртуальной
машины https://app.vagrantup.com/boxes/search), совместимый с вашим решением
и архитектурой. В этом примере мы используем Vagrant Вох для rипервизора
Virtual Вох.
l.JN
�
Virtual Вох работает только на системах lntel/AMD х86(64), а применяемый нами
Vagrant Вох специально создан для систем AMD64.
2 Полный URL: https://,V\V\v.vm,vare.com/products/,vorkstation-pro.html.
3 Полный URL: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enaЫe-hyper-v.
Книги для программистов: https://clcks.ru/books-for-it
60
Глава 3
Создайте новый файл с именем Vagrantfile со следующим содержимым:
puts («-ЕОГ)
[WARNING] This exposes an unencrypted Docker ТСР port on the VМ! !
This is NОГ secure and may expose your system to significant risk
if left running and exposed to the broader network4 •
ЕОГ
$script = <<-SCRIPТ
echo \'{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}\'
sudo tee /etc/docker/daemon.json
sudo mkdir -р /etc/systemd/system/docker.service.d
echo -е \"[Service]\nExecStart=\nExecStart=/usr/Ьin/dockerd\" 1 \
sudo tee /etc/systemd/system/docker.service.d/docker.conf
sudo systemctl daemon-reload sudo systemctl restart docker SCRIPТ
Vagrant.configure(2) do lconfigl
# Выбираем совместимый Vagrant Вох
config.vm.box = 'bento/uЬuntu-20.04'
# Устанавливаем Docker, если его нет в образе виртуальной машины
config.vm.provision :docker
# Docker будет ожидать данных на незашифрованном локальном порту
config. vm.provision "shell", inline: $script,
run: "always"
1
\
# Проброс порта Docker на
# 12375 (или другой открытый порт) на хосте
config.vm.network "forwarded_port", guest: 2375,
host: 12375, protocol: "tcp", auto correct: true
end
Полную копию файла можно извлечь следующей командой:
$ git clone https://githuЬ.com/Ьluewhalebook/\
docker-up-and-running-3rd-edition.git --config core.autocrlf=input
$ cd docker-up-and-running-3rd-edition/chapter_03/vagrant
$ 1s Vagrantfile
Возможно, потребуется удалить символ "\" в команде git clone и собрать URL
в одну строку. Просто команда слишком длинная для стандартной страницы выво­
да. В стандартной командной оболочке Unix эта команда будет работать, если
в строках нет начальных и конечных пробелов.
Убедитесь, что находитесь в каталоге с файлом Vagrantfile, а затем выполните сле­
дующую команду, чтобы запустить виртуальную машину Vagrant.
4 ПРЕДУПРЕЖДЕНИЕ. На виртуальной машине будет открыт незашифрованный порт ТСР Docker. Это
НЕБЕЗОПАСНО, система может поверrатъся значительному риску при связи с сетью.
Книги для программистов: https://clcks.ru/books-for-it
&
Установка Doker
1
61
Эта установка представлена в качестве простого примера. Она небезопасна и ее
не следует использовать, не закрыв доступ к серверу из общедоступной сети.
У Docker есть документация по обеспечению безопасности эндпоинта Docker
с помощью клиентских сертификатов SSH или TLS (https://docs.docker.com/engine/
security/protect-access), а также дополнительная информация о поверхности атаки
демона Docker (https://docs.docker.com/engine/security/#docker-daemon-attack­
surface).
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider ...
=> default: Importing base Ьох 'bento/uЬuntu-20.04'.. .
==> default: Matching МАС address for NAT networking...
==> default: Checking if Ьох 'bento/uЬuntu-20.04' version '...' is up to date...
=> default: А newer version of the Ьох 'bento/uЬuntu-20.04' for provider...
==> default: availaЬle' You currently have version '...'. The latest is version
==> default: '202206.03.0'. Run ·vagrant Ьох update' to update.
==> default: Setting the name of the VМ: vagrant_default_1654970697417_18732
==> default: Clearing any previously set network interfaces...
==> default: Running provisioner: docker... default: Installing Docker onto machine...
==> default: Running provisioner: shell... default: Running: inline script
default: {"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"])
default: [Service]
default: ExecStart=
default: ExecStart=/usr/bin/dockerd
В macOS может появиться следующая ошибка: VBoxManage: error: Details: code
NS_ERROR_FAILURE (Ох80004005), component MachineWrap, interface /Machine.
Это связано с функциями безопасности в macOS. В статье по адресу https://
scriptcrunch.com/solved-vboxmanage-error-component-machinewrap описывается, как исправить проблему.
После запуска виртуальной машины мы сможем подключиться к серверу Docker,
выполнив следующую команду и сообщив клиенту Docker, куда следует подклю­
чаться, с помощью аргумента -н:
$ docker -Н 127.0.0.1:12375 version
Client:
Cloud integration: vl.0.24
Version: 20.10.14
API version: 1.41
Server: Docker Engine - Community
Engine:
Version:
20.10.17
API version: 1.41 (minimurn version 1.12)
Книги для программистов: https://clcks.ru/books-for-it
62
Глава З
В выводе мы види,:м информацию о версии различных компонентов, из которых со­
стоят клиент и сервер Docker.
Передавать IР-адрес и порт при каждом выполнении команды Docker неудобно, но,
к счастью, с помощью команды docker context мы можем настроить Docker таким
образом, чтобы он знал о нескольких серверах Docker. Для начала давайте посмот­
рим, какой контекст сейчас используется. Обратите внимание на строчку со звез­
дочкой ( *), которая обозначает текущий контёкст:
$ docker context list
NАМЕ ТУРЕ ... DOCКER ENDPOINT...
default * moby ... unix:///var/run/docker.sock
Мы можем создать новый ко'нтекст для виртуальный машины, а затем активировать
его с помощью следующей последовательности команд:
$ docker context create vagrant --docкer host=tcp://127.0.0.1:12375 vagrant
Successfully created context "vagrant"
$ docker context use vagrant vagrant
Если снова вывести все контексты, мы увидим примерно следующее:
$ docker context list
NАМЕ
ТУРЕ
default
moby
vagrant * moby
DOCKER ENDPOINT ...
unix:///var/run/docker. sock...
tcp://127.0.0.1:12375...
Теперь, когда для текущего контекста задано vagrant, при выполнении команды
docker version без аргумента -н мы сможем подключиться к нужному серверу Docker
и получить ту же информацию, что и раньше.
Чтобы подключиться к командцой оболочке виртуальной машины на базе Vagrant,
выполните следующую команду:
$ vagrant ss\).
Welcome to UЬuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic х86_64)
vaqrant@vaqrant:�$ exit
Пока вы не обеспечите безопасность этой системы, лучше остановить виртуальную
машину и вернуть исходное значение для контекста:
$ vagrant halt
=> default: Atteпpting graceful shutdown of VМ...
$ docker version
Cannot connect to ... daemon at tcp://127.0.0.1:12375. Is the ... daemon running?
$ docker context use default
default
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker 1
63
Для macOS доступна среда выполнения Colima (https://github.com/ablosoft/
colima), с помощью которой можно легко запустить гибко настраиваемую вирту­
альную машину Docker или Kubernetes и управлять ею.
Тестирование системы
Итак, у нас есть функционирующий клиент и сервер, и мы готовы протестировать,
что все работает. Мы можем выполнить в локальной системе любую из следующих
команд, чтобы демон Docker загрузил последний официальный контейнер для этого
дистрибутива и запустил его с командной оболочкой Unix.
Данный шаг позволяет убедиться, что все компоненты правильно установлены и
могут взаимодействовать друг с другом. Это проявление одного из преимуществ
Docker: мы можем запускать контейнеры на базе любого дистрибутива Linux.
Далее мы запустим контейнеры Linux на Ubuntu, Fedora и Alpine Linux. Не обяза­
тельно пробовать их все, достаточно одного варианта.
Если вы используете клиент Docker в системе Linux, каждая команда docker долж­
на начинаться с sudo, потому что по умолчанию только у пользователя root может
быть доступ к Docker.
Обычно создается группа docker для управления пользователями, у которых есть доступ
к сокету Unix dockerd. Мы можем добавить пользователя в эту группу, чтобы не приходилось
каждый раз указывать sudo (https://man7.org/linux/man-pages/man8/sudo.8.html).
Ubuntu
Давайте попробуем запустить контейнер с использованием последнего базового
образа Ubuntu Linux:
$ docker container run --rm -ti docker.io/uЬuntu:latest /bin/bash
root@aa9b72aelfea:/#
1W
�]
С функциональной точки зрения docker container run работает так же, как docker
run.
Fedora
В этом примере мы запустим контейнер с использованием последнего базового
образа Fedora Linux:
$ docker container run --rm -ti docker.io/fedora:latest /bin/bash
[root@Sc9720le827b /]# exit
Книги для программистов: https://clcks.ru/books-for-it
64
Глава 3
Alpine Linux
Наконец, запустим контейнер с использованием последнего базового образа Alpine
Linux:
$ docker container run --rm -ti docker.io/alpine:latest /bin/sh
/ # exit
Объекты docker.io/uЬuntu:latest, docker. io/fedora:latest и docker. io/alpine:latest
строятся по одной схеме: репозиторий образов Docker, имя образа и тег образа.
Сервер Docker
Сервер Docker часто устанавливается, вюпочается и запускается автоматически,
но будет полезно увидеть, как можно легко запустить демон Docker вручную на
системе Linux (https://docs.docker.com/engine/reference/commandline/dockerd),
выполнив простую команду:
$ sudo dockerd -Н unix:///var/run/docker.sock \
--config-file /etc/docker/daemon.json
В этом разделе предполагается, что вы используете сервер Linux или виртуальную
машину с демоном Docker. Если у вас Docker Desktop в Windows или Мае, то вы не
сможете напрямую взаимодействовать с исполняемым файлом dockerd, потому
что он намеренно скрыт от конечного пользователя, но мы посмотрим, как решить
эту проблему.
Эта команда запускает демон Docker, создает и начинает прослушивать unix-coкeт
(-н unix:///var/run/docker.sock), а затем считывает остальную конфигурацию из
/etc/docker/daemon.json. Вряд ли вам придется запускать сервер Docker самостоя­
тельно, но полезно знать, что при этом происходит. Если вы работаете не в Linux,
то, скорее всего, будете использовать виртуальную машину на базе Linux, на кото­
рой размещен сервер Docker. Docker Desktop сам создает эту виртуальную машину.
J-Ж
�
Если Docker уже запущен, снова �ыполнить демон не получится, потому что он не
может прослушивать один сетевои порт дважды.
В большинстве случаев мы можем легко подюпочиться по SSH к серверу Docker
и посмотреть, что там происходит, но Docker Desktop так хорошо интегрируется
с системами, .отличными от Linux, что мы не замечаем локальную виртуальную
машину, на которой выполняется демон Docker. Поскольку виртуальная машина
Docker Desktop очень маленькая и стабильная, у нее нет демона SSH и к ней слож­
но подобраться.
Если у вас будет необходимость подюпочиться к нижележащей виртуальной маши­
не или вам просто любопытно, это возможно, но потребуются углубленные знания.
Книги для программистов: https://clcks.ru/books-for-it
Установка Doker
1
65
Мы подробно поговорим о команде nsenter в соответствующем разделе в главе 11.
А пока что, если вы хотите посмотреть на виртуальную машину (или базовый хост)
сейчас, выполните следующие команды:
$ docker container run --rm -it --privileged --pid=host debian \
nsenter -t 1 -m -u -n -i sh
/ # cat /etc/os-release PRETTY_NAМE="Docker Desktop"
/ # ps I grep dockerd
1540 root 1:05 /usr/local/bin/dockerd
--containerd /var/run/desktop-containerd/containerd.sock
--pidfile /run/desktop/docker.pid
--swarm-default-advertise-addr=ethO
--host-gateway-ip 192.168.65.2
/ # exit
Эти команды используют привилегированный контейнер DeЬian, который содер­
жит команду nsenter для управления пространствами имен ядра Linux, чтобы можно
было перемещаться по файловой системе нижележащих виртуальной машины или
хоста.
Этот контейнер имеет достаточно прав, чтобы разрешить нам перейти к базовому
_
хосту, но не стоит использовать привилегированные контеинеры просто так, если
вместо этого можно добавить отдельные привилегии (capability) или возможности
системного вызова. Подробнее мы поговорим об этом в разделе "Безопасность•
главы 11.
Если вы можете задействовать эндпоинт сервера Docker, эта команда даст вам
доступ к базовому хосту.
Конфигурация демона Docker обычно хранится в файле /etc/docker/daemon.json, но
расположение на виртуальной машине Docker Desktop может быть другим, напри­
мер /containers/services/docker/rootfs/etc/docker/daemon.json. Docker задает разумные
параметры по умолчанию, поэтому файл может быть очень маленьким, а может и
Docker Engine
-• General
:.!.:;
..
IC!J Resources
Configure the Docker daemon Ьу typing а jsc
Docker Engine
€) Kubernetes
••••
v20.10.22
0 Software updates
Extensions
••• Features in development
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enaЫed": true
},
"experimental": false
Рис. 3.3. Конфигурация сервера Docker Desktop
Книги для программистов: https://clcks.ru/books-for-it
66
1
Глава 3
вовсе отсутствовать. Если вы работаете с Docker Desktop, файл можно открыть для
редактирования - щелкните на значок Docker и выберите пункты Preferences...
(Параметры) - Docker Engine, как показано на рис. 3.3.
Заключение
Итак, мы установили и запустили Docker. Вы можете самостоятельно рассмотреть
более сложные варианты установки. В следующей главе мы узнаем, как собирать
образы Docker и управлять ими. Это базовые действия для каждого контейнера,
который мы запускаем в Docker.
Если далее в командной строке вы увидите команду docker, помните, что вам нуж­
на корректная конфигурация в виде контекста Docker, переменных среды или фла­
га командной строки -н, чтобы клиент docker понимал, как подключиться к серверу
dockerd.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА4
Работа с образами Docker
Любой контейнер Linux основан на образе. Образ содержит определение того, что
будет работать в запущенном контейнере, - примерно как виртуальный диск ста­
новится виртуальной машиной при запуске. Образы Docker или Open Container
Initiative, OCI предоставляют основу для всего, что мы можем развернуть и запус­
тить в Docker. Для запуска контейнера необходимо загрузить общедоступный образ
или создать собственный. Образ можно рассматривать как единый ресурс, пред­
ставляющий файловую систему для контейнера. Каждый образ состоит из одного
или нескольких связанных слоев файловой системы, обычно по одному на каждый
этап сборки, в ходе которой создавался образ.
Поскольку образы создаются из отдельных слоев, они предъявляют особые требо­
вания к ядру Linux, которое должно предоставлять драйверы, необходимые Docker
для управления хранилищем на бэкенде. Docker использует это хранилище для
управления образами, и оно взаимодействует с базовой файловой системой Linux,
чтобы собирать и контролировать многочисленные слои, соединяющиеся в один
готовый образ. Поддерживаются следующие хранилища:
♦ Overlay2 (https://oreil.ly/r4JНY)1 •
♦ B-Tree File System (Btrfs) (https://Ьtrfs.wiki.kernel.org/index.php/Мain_Page).
♦ Device Mapper (https://www.sourceware.org/dm).
Каждый из этих вариантов предоставляет быструю систему копирования при запи­
си (Copy-on-Write, CoW) для управления образами. Подробнее о различных храни­
лищах мы поговорим в главе 11. А пока используем бэкенд по умолчанию и по­
смотрим, как работают образы, поскольку они составляют основу почти всех задач,
которые мы будем выполнять с помощью Docker. Мы рассмотрим следующие
темы:
♦ Сборка образов.
♦ Отправка (push) образов в реестр.
♦ Загрузка (pull) образов из реестра.
♦ Создание и запуск контейнеров из образа.
1 Полный URL: https://github.com/torvalds/linux/commit/e9be9d5e76e34872f0c37d72e25bc27fe9e2c54c.
Книги для программистов: https://clcks.ru/books-for-it
68
Глава 4
Структура Dockerfile
Для создания пользовательского образа Docker с помощью инструментов по умол­
чанию потребуется Dockerfile. В этом файле описаны все шаги, с помощью кото­
рых создается образ, и обычно он хранится в корневом каталоге репозитория
исходного кода приложения.
Далее показано, как может выглядеть типичный Dockerfile. В этом примере созда­
ется контейнер для приложения на Node.js:
FRCМ node:18.13.0
ARG email="anna@example.com"
LAВEL "maintainer"=$email
LAВEL "rating"="Five Stars" "class"="First Class"
USER root
ENV АР /data/app
ENV SCPATH /etc/supervisor/conf.d
RIJN apt-get -у update
# Демоны
RIJN apt-get -у install supervisor
RIJN mkdir -р /var/log/supervisor
# Конфигурация супервайзера
СОРУ ./supervisord/conf.d/* $SCPATH/
# Код приложения
СОРУ * .js* $АР/
'IIORIФIR $АР
RIJN npm install
СМ) ["supervisord", "-n"]
Если подробно рассмотреть этот Dockerfile, можно получить представление об ин­
струкциях, с помощью которых собирается образ. Каждая строка в Dockerfile соз­
дает новый слой образа, который хранится Docker. Этот сло.й включает все измене­
ния, которые были внесены этой командой. Получается, что при сборке новых об­
разов Docker будет собирать только слои, которые отличаются от предыдущих
сборок - слои без изменений можно использовать повторно.
Mw можем собрать экземпляр Node из простого базового образа Linux или найти
официальные образы для Node в Docker Hub (https://registry.hub.docker.com/).
Сообщество Node.js предлагает серию образов Docker (https://registry.hub.
docker.com/_/node) и теги, чтобы можно бьшо быстро определить доступные вер­
сии. Если мы хотим привязать образ к определенному релизу Node, можно, напри­
мер, добавить тег node:18.13.0. В следующем примере мы получаем образ Ubuntu
Linux для Node 11.11.х:
FRCМ docker.io/node:18.13.0
Параметр ARG предоставляет способ задавать переменные и их значения по умолча­
нию, которые доступны только во время сборки образа:
ARG email ="anna@exa.mple . сот"
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
69
Применение меток к образам и контейнерам позволяет добавлять метаданные с по­
мощью пар "ключ-значение", которые затем можно использовать для поиска и
идентификации контейнеров и образов Docker. Просмотреть метки для образа
можно с помощью команды docker image inspect. Для метки maintainer (ответствен­
ный по сопровождению) мы задаем значение аргумента сборки email, которое было
определено в предыдущей строке Dockerfile. Это значит, что метку можно изме­
нить при сборке образа:
LAВEL "maintainer"=$email
LAВEL "rating"="Five Stars" "class"="First Class"
По умолчанию Docker выполняет все процессы с правами root в контейнере, но мы
можем изменить этот параметр с помощью инструкции USER:
USER root
Хотя контейнеры предоставляют некоторую изоляцию от базовой операционной
системы, они все же выполняются на ядре хоста. В связи с потенциальными рис­
ками для безопасности в рабочей среде контейнеры почти всегда должны выпол­
няться в контексте непривилегированного пользователя.
В отличие от инструкции ARG, инструкция ENV позволяет задавать переменные ко­
мандной оболочки, которые приложение может использовать для конфигурации во
время работы, а также в процессе сборки. Инструкции ENV и ARG упрощают
Dockerfile и помогают соблюдать принцип DRY (Don't Repeat Уourself - не повто­
ряйся):
ENV АР /data/app
ENV SCPATH /etc/supervisor/conf.d
В следующем фрагменте кода мы с помощью набора инструкций RUN создаем необ­
ходимую структуру файла и устанавливаем обязательные зависимости:
RIJN apt-get -у update
# Демоны
RIJN apt-get -у install supervisor
RIJN mkdir -р /var/log/supervisor
&�
Для простоты здесь мы включили команды вроде apt-get -у update или dnf -у
� 1 update, но обычно их не рекомендуют в Dockerfile приложения, потому что потребу­
ется проходить по индексу репозитория при каждом запуске сборки, т. е. сборка не
будет воспроизводимой, ведь версии пакетов могут меняться между сборками.
Образ приложения может базироваться на другом образе, в котором уже применены обновления и версии находятся в известном состоянии. Так будет быстрее,
и образ можно будет легко воспроизвести.
Инструкция СОРУ служит для копирования файлов из локальной файловой системы
в наш образ. Чаще всего в них входит код приложения и необходимые дополни­
тельные файлы. Поскольку СОРУ копирует файлы в образ, для их использования нам
не придется обращаться к файловой системе после сборки образа. Мы будем при­
менять переменные сборки, определенные в предыдущем разделе, чтобы упростить
себе работу и не допустить опечаток:
Книги для программистов: https://clcks.ru/books-for-it
70
Глава 4
# Конфигурация супервайзера
СОРУ ./supervisord/conf.d/* $SCPATH/
# Код приложения
СОРУ * .js* $АР/
Каждая инструкция создает новый слой образа Docker, поэтому разумно объеди­
нить несколько логически связанных команд на одной строке. Мы даже можем
совместить инструкции СОРУ и RUN, чтобы скопировать в образ сложный скрипт, а
затем выполнять этот скрипт всего двумя командами в Dockerfile.
С помощью инструкции WORКDIR мы меняем рабочий каталог в образе для остальных
инструкций сборки и процесса по умолчанию, который запускается с получивши­
мися контейнерами:
tIORIФIR $АР
RIJN npm install
Порядок команд в Dockerfile заметно влияет на время сборки. Располагайте ко­
манды в таком порядке, чтобы изменения находились ближе к нижней части. Это
значит, что добавлять код и выполнять аналогичные действия следует в конце.
При повторной сборке образа будет пересобираться каждый слой, который идет
после первого найденного изменения.
В конце мы пишем инструкцию смо, чтобы определить команду, запускающую про­
цесс, который будет выполняться в контейнере:
а,ю [ "supervisord", "-n"]
Это не строгое правило, но обычно рекомендуется запускать в контейнере только
один процесс. Идея в том, что контейнер должен выполнять одну функцию, и мы
могли бы легко горизонтально масштабировать отдельные функции в архитектуре.
В этом примере мы используем supervisord для управления процессами, чтобы
повысить отказоустойчивость приложения в контейнере и обеспечить его стабильную работу. Кроме того, таким образом мы сможем искать и устранять неисправ­
ности в приложении во время развертывания, чтобы перезапускать сервис, не пе­
резапуская целый контейнер.
Подобного эффекта можно достичь с помощью аргумента командной строки --init
для docker container run, о котором мы поговорим в разделе "Контроль процес­
сов" главы 7.
Сборка образа
Для сборки первого образа склонируйте репозиторий Git, который содержит при­
мер приложения docker-node-hello, как показано здесь2:
$ git clone https://githuЬ.com/spkane/docker-node-hello.git \
--config core.autocrlf=input
Cloning into 'docker-node-hello' ...
2
Этот код взят с GitНub (https://github.com/enokd/docker-node-hello).
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker 1
71
remote: Counting objects: 41, done.
remote: Total 41 (delta 0), reused О (delta О), pack-reused 41
Unpacking objects: 100% (41/41), done.
$ cd docker-node-hello
Git обычно уже установлен в Linux и macOS, но если у вас его нет, загрузите про­
стой установщик с сайта https://git-scm.com/downloads.
Опция --config core.autocrlf=input помогает защититься от случайного изменения
окончания строк, поскольку ожидается соответствие стандарту Linux.
В итоге мы загрузим работающий Dockerfile и соответствующие файлы с исходным
кодом в каталог docker-node-hello. Если мы посмотрим на содержимое с игнориро­
ванием каталога в репозитории Git, то увидим следующее:
$ tree -а -I .git
гГ
.dockerignore
.gitignore
Dockerfile
index.js
package.json
L. supervisord
conf.d
node.conf
L. supervisord.conf
Г
г
г
гг
Давайте рассмотрим самые важные файлы в этом репозитории.
Dockerfile должен быть таким же, как мы только что видели.
В файле .dockerignore мы указываем файлы и каталоги, которые не хотим отправ­
лять на хост Docker при сборке образа. В этом примере файл .dockerignore содер­
жит следующую строку:
.git
Это значит, что команда docker image build будет исключать из сборки каталог .git,
который содержит целый репозиторий исходного кода. Остальные файлы отражают
текущее состояние исходного кода в выгруженной ветви. Нам не потребуется
содержимое каталога .git для сборки образа Docker, и поскольку со временем он
может разрастись до огромных размеров, мы не хотим тратить время на то, чтобы
копировать его при каждой сборке. В файле package.json определяется приложение
Node.js и его зависимости. Файл index.js содержит главный исходный код прило­
жения.
Каталог supervisord содержит файлы конфигурации для supervisord, которые пона­
добятся нам для запуска и мониторинга приложения.
Использовать supervisord (http://supervisord.org/) в этом примере для мониторинга
приложений - это чересчур, но так будет понятнее, какие методы в контейнере
позволяют получить больше контроля над приложением и его состоянием.
Книги для программистов: https://clcks.ru/books-for-it
72
Глава 4
Как мы узнали в главе 3, мы сможем собрать образ Docker, если у нас есть запу­
щенный сервер Docker и правильно настроенный клиент для взаимодействия с ним.
Если все работает, мы сможем запустить новую сборку следующей командой, ко­
торая соберет и пометит тегом образ на основе файлов в текущем каталоге.
Каждый шаг, определенный в следующих выходных данных, напрямую соотносит­
ся со строкой в Dockerfile, и на каждом шаге создается новый слой образа на основе
предыдущего шага. На первую сборку уйдет несколько минут, потому что нужно
будет загрузить базовый образ node. Дальше сборки будут вьmолняться гораздо бы­
стрее, если только не появится новая версия тега базового образа.
Вывод, приведенный далее, взят из нового BuildКit, включенного в Docker. Если вы
получили совсем другой вывод, скорее всего, у вас более старый код сборки обра­
зов. Включите BuildКit в своей среде, задав для переменной среды DOCKER_BUILDKIT
значение 1. Подробнее об этом можно узнать на сайте Docker (https://docs.
docker.com/bui ld/bui ldkit).
В конце команды build стоит точка. Она относится к контексту сборки и указывает
Docker, какие файлы нужно загрузить на сервер, чтобы можно было собрать образ.
Чаще всего в конце команды build будет стоять точка, обозначающая текущий ката­
лог. По контексту сборки файл .dockerignore отфильтровывает содержимое, чтобы
мы не загрузили больше, чем нужно.
"f!'l
--
Docker предполагает, что Dockerfile находится в текущем каталоге, но если он хра­
нится где-то в другом месте, мы можем указать его с помощью аргумента -f.
Давайте выполним сборку:
$ docker image build -t example/docker-node-hello:latest
=>
[internal] load build definition from Dockerfile
=> => transferring dockerfile: 37В
[internal] load .dockerignore
34В
=> [internal] load metadata for docker.io/library/node:18.13.0
=> CACHED (1/8] FROМ docker.io/library/node:18.13.0@19a9713dЬaf3a3899ad...
=> [internal] load build context
=> => transferring context: 233В
=> (2/8) RUN apt-get -у update
=> (3/8] RUN apt-get -у install supervisor
=> (4/8] RUN mkdir -р /var/log/supervisor
=> (5/8] СОРУ ./supervisord/conf.d/* /etc/supervisor/conf.d/
=> (6/8] СОРУ *.js* /data/app/
=> (7/8] WORКDIR /data/app
=> (8/8] RUN npm install
=> exporting to image
=> => exporting layers
=>
=> => transferring context:
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
73
=> => writing image sha256:99184427lca5b984939aЬ49d8lb24d4d53137f04alЬd...
=> => naming to docker.io/exaпple/docker-node-hello:latest
Чтобы ускорить сборку, Docker использует локальный кеш, если считает, что это
безопасно. Иногда это приводит к неожиданным проблемам, потому что можно не
заметить изменения на нижнем слое. В предыдущем выводе есть строки вроде
=> [2/8] RUN apt-get -у update. Если вместо этого вы увидите => CACHED [2/8] RUN
apt-get -у update, это значит, Docker решил задействовать кеш. Чтобы отключить
кеш для сборки, укажите аргумент --no-cache с командой docker image build.
Если вы собираете образы Docker в системе, в которой одновременно выполняются
другие процессы, можно ограничить ресурсы, доступные сборкам, с помощью
методов cgroup, которые мы рассмотрим в главе 5. Подробнее об аргументах коман­
ды docker image build можно узнать из официальной документации (bttps://docs.
docker .com/engine/reference/commandline/image_build).
С функциональной точки зрения команда docker image build работает так же, как
� docker build.
Если сборка работает некорректно, перейдите к разделу "Многоэтапные сборки"
или "Решение проблем со сборками" далее в этой главе.
Запуск образа
После успешной сборки образа его можно запустить на хосте Docker с помощью
следующей команды:
$ docker container run --rm -d -р 8080:8080 \
example/docker-node-hello:latest
Эта команда указывает Docker, что нужно создать в фоновом режиме запущенный
контейнер из образа с тегом exaпple/docker-node-hello: latest, а затем пробросить порт
8080 в контейнере с портом 8080 на хосте Docker. Если все получится, в контейнере
на хосте запустится приложение Node.js. Это можно проверить командой docker
container ls. Чтобы увидеть запущенное приложение в действии, в браузере перей­
дите к порту 8080 на хосте Docker.
Чтобы определить IР-адрес хоста Docker, просмотрите запись docker context list со
звездочкой или проверьте значение переменной среды DOCKER_нosт, если оно задано.
Если для DOCKER ENDPOINT задан unix-coкeт, скорее всего, IР-адрес будет 127.о.о.1:
$ docker context list
NАМЕ
ТУРЕ ... DOCKER ENDPOINT
default * moby ... unix:///var/run/docker.sock
Введите IР-адрес и порт (http://127.0.0.l:8080/ или другой) в адресной строке брау­
зера или в инструменте командной строки, например curl. Вы увидите примерно
следующий текст:
Hello World. Wish you were here.
Книги для программистов: https://clcks.ru/books-for-it
74
Глава 4
Аргументы сборки
Если мы посмотрим на собранный образ, то увидим, что для метки maintainer было
задано anna@example.com:
$ docker image inspect \
example/docker-node-hello:latest I grep maintainer
"maintainer": "anna@example.com",
Чтобы изменить метку maintainer, можно просто снова запустить сборку, предоста­
вив новое значение для email ARG с помощью аргумента командной строки --Ьuild­
arg, например:
$ docker image build --build-arg email=me@example.com \
-t example/docker-node-hello:latest .
=> => naming to docker.io/example/docker-node-hello:latest
После выполнения сборки мы можем проверить результаты, посмотрев новый
образ:
$ docker image inspect \
example/docker-node-hello:latest I grep maintainer
"maintainer": "me@exarrple.com",
Инструкции ARG и ENV позволяют очень гибко использовать Dockerfile и избегать по­
вторяющихся значений, которые сложно постоянно обновлять.
Переменные среды для конфигурации
В файле index.js вы заметите, что часть файла относится к переменной $wно, с по­
мощью которой приложение определяет, кому скажет Hello:
var DEFAULT_WHO = "World";
var WHO = process.env.WHO 11 DEFAULT_WHO;
арр.get ( '/', function ( req, res) {
res.send ( 'Hello ' + WHO + ' . Wish you were here.\n') ;
}) ;
Давайте коротко рассмотрим, как настроить это приложение, передавая перемен­
ные среды при запуске. Сначала нужно остановить существующий контейнер дву­
мя командами. Первая команда выдаст идентификатор контейнера, который пона­
добится для второй команды:
$ docker container 1s
CONTAINER ID IМAGE
STATUS
b7145e06083f example/centos-node-hello:latest Up 4 minutes ...
Вывод docker container 1s можно отформатировать с помощью шаблона Go
(https://developer.hashicorp.com/nomad/tutorials/templates/go-template�syntax),
чтобы отображалась только нужная информация. В предыдущем примере можно
выполнить команду docker container 1s --format "tаЫе {{.ID}}\t{{.Image}}
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker 1
75
\t{ { .Status} }", чтобы просмотреть только три поля. Если мы выполним docker con
tainer ls --quiet без опций форматирования, то получим только идентификатор
контейнера.
Затем, указав идентификатор контейнера из вывода предьщущей команды, можно
остановить выполняющийся контейнер командой:
$ docker container stop b7145e06083f
Ь7145е0608Зf
Команда docker container ls - это функциональный эквивалент docker
container list, docker container ps И docker ps.
А docker container stop - это функциональный эквивалент docker stop.
Затем мы можем добавить один экземпляр аргумента --env в предыдущую команду
docker container run и перезапустить контейнер:
$ docker container run --rrn -d \
--puЫish mode=ingress,puЫished=8080,target=8080 \
--env WHO="Sean and Karl" \
example/docker-node-hello:latest
Если обновить страницу браузера, мы увидим новый текст:
Hello Sean and Karl . Wish you were here.
Предыдущую команду docker при желании можно сократить:
$ docker container run --rrn -d -р 8080:8080 \
-е
WHO="Sean and Karl" \
�
example/docker-node-hello:latest
Теперь можно остановить контейнер командой docker container stop, передав иден­
тификатор контейнера.
Пользовательские базовые образы
Базовые образы - это образы нижнего уровня, на которых строятся остальные об­
разы Docker. Чаще всего они основаны на минимальных установках дистрибутивов
Linux, например Ubuntu, Fedora или Alpine Linux, но могут быть значительно
меньше и содержать, например, один статически скомпилированный файл. Боль­
шинство людей предпочитают использовать официальные базовые образы для
любимого дистрибутива или инструмента.
Правда, иногда лучше создать собственный базовый образ. Например, чтобы под­
держивать единый образ операционной системы для всех методов развертывания
для физического оборудования, виртуальных машин и контейнеров. Еще одна при­
чина - сокращение размера образа. Не нужно передавать дистрибутив Ubuntu це­
ликом, например, если мы развертываем статически собранное приложение на С
или Go. Нам понадобятся только стандартные инструменты для отладки и еще
Книги для программистов: https://clcks.ru/books-for-it
76
1
Глава 4
несколько двоичных файлов и shеll-команд. Стоит потратить время на создание
такого образа, чтобы ускорить развертывание и упростить распространение прило­
жения.
Обычно в обоих случаях собирают образы на основе Alpine Linux, потому что обра­
зы получаются очень маленькими, и это отличный вариант для Docker. Alpine Linux
основан на легкой и современной стандартной библиотеке musl (https://
musl.libc.org/), а не традиционной GNU С Library (glibc) (https://www.gnu.org/
softwareЛibc). Обычно в связи с этим не возникает проблем, поскольку многие
пакеты поддерживают musl, но об этом лучше помнить. Особенно это влияет на
приложения на основе Java и разрешение DNS-имен. Из-за небольшого размера
образа такой вариант часто применяется в рабочей среде. Alpine Linux экономит
пространство, поэтому по умолчанию поставляется с /Ьin/sh, а не /Ьin/Ьash. При не­
обходимости мы можем установить glibc и bash в Alpine Linux, и многие так делают
для контейнеров на виртуальных машинах Java.
В официальной документации Docker можно найти полезную информацию о том,
как собирать базовые образы на разных дистрибутивах Linux (https://dockr.ly/
2NlFZcU).
Хранение образов
Итак, мы создали нужный образ Docker, а теперь хотим сохранить его там, где он
будет легкодоступен для любого хоста Docker, на котором мы захотим его развер­
нуть. На этапе сохранения образов для будущего развертывания часто происходит
передача ответственности. Обычно мы не собираем образы в рабочей среде, чтобы
сразу запустить их там. Мы описывали этот процесс, когда рассматривали взаимо­
действие между командами при развертывании приложения. Как правило, при раз­
вертывании образ извлекается из репозитория и запускается на одном или несколь­
ких серверах Linux. Существуют несколько способов сохранения образов в цен­
тральном репозитории, откуда их будет легко извлекать.
Публичные реестры
Docker предоставляет реестр образов (https://registry.hub.docker.com/), где хра­
нятся общедостутrnые образы от сообщества, в том числе официальные образы для
дистрибутивов Linux, готовые контейнеры WordPress и многое другое.
Если мы хотим опубликовать образ в Интернете, лучше всего выбрать публичный
реестр, например Docker Hub (https://hub.docker.com/). Правда, есть и другие
варианты. Когда основные инструменты Docker только набирали популярность,
Docker Hub еще не существовал. Чтобы заполнить этот пробел, был создан Quay.io
(https://quay.io/). Quay.io несколько раз менял владельца и сейчас принадлежит Red
Hat. Поставщики облачных сервисов, вроде Google, и компании, предоставляющие
услуги SaaS, например GitHub, предлагают свои реестры. Здесь мы будем рассмат­
ривать только два варианта.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
77
Docker Hub и Quay.io предлагают централизованные реестры образов Docker, дос­
тупные при подключении к Интернету, и позволяют хранить не только общедос­
тупные, но и частные образы. У обоих реестров есть удобные интерфейсы и воз­
можность разделять разрешения на доступ и управлять пользователями. У обоих
реестров есть хорошие платные варианты для частного хостинга образов - похо­
жим образом GitHub продает частные реестры в своих системах. Это неплохой
начальный вариант, если вы планируете всерьез работать с Docker, но у вас пока
недостаточно кода, чтобы вам понадобилось решение для внутреннего хостинга.
Главный минус этих реестров для компаний, которые активно используют
Docker, - они не находятся в сети, где развертывается приложение. Это значит,
что каждый слой каждого развертывания придется передать через Интернет. За­
держки при передаче замедляют развертывание программного обеспечения, а пере­
бои с доступом к реестрам могут нарушить график развертывания. Огчасти эти
проблемы решаются с помощью тщательно продуманной структуры образов, ведь
тонкие слои гораздо проще передавать через Интернет.
Частные реестры
Еще один вариант, который выбирают многие компании, - собственный реестр
образов Docker, который взаимодействует с клиентом Docker и поддерживает
отправку, извлечение и поиск образов. Проект Distribution (https://github.com/
distribution/distribution) с открытым кодом предоставляет базовый функционал, на
котором строится большинство остальных реестров.
Также неплохими вариантами частных реестров считаются Harbor (https://
goharbor.io/) и Red Hat Quay (https://www.redhat.com/en/technologies/cloud­
computing/quay). Помимо базового функционала реестров Docker эти решения
предлагают удобные графические пользовательские интерфейсы с множеством до­
полнительных функций, таких как верификация образов.
Аутентификация для входа в реестр
Взаимодействие с реестром, который хранит образы контейнеров, - это часть по­
вседневной работы с Docker. Многие реестры требуют аутентификации для доступа
к образам. Docker пытается упростить автоматизацию этих задач - он сохраняет
данные о входе и использует их при необходимости, например когда вы хотите
извлечь частный образ. Docker считает реестром по умолчанию Docker Hub, пуб­
личный репозиторий от Docker, Inc.
Потребуются дополнительные усилия, но вы можете настроить демон Docker
3
таким образом, чтобы использовать пользовательское зеркало реестра или кеш
4
образов в режиме pull-through .
3
4
Полный URL: https://docs.docker.com/registry/recipes/mirror/#configure-the-docker-daemon.
Полный URL: https://docs.docker.com/registry/recipes/mirror/#run-a-registry-as-a-pull-through-cache.
Книги для программистов: https://clcks.ru/books-for-it
78
1
Глава 4
Создание учетной записи Docker Hub
В этих примерах мы создадим учетную запись на Docker Hub. Для загрузки обще­
доступных образов учетная запись не нужна, но нам нужно будет зарегистриро­
ваться, чтобы избежать ограничений по скорости и отправлять в реестр свои кон­
тейнеры.
Для создания учетной записи перейдите на сайте Docker Hub (https://hub.
docker.com/) в mобом браузере.
Войдите в существующую учетную запись или создайте новую, указав адрес элек­
тронной почты. При создании учетной записи Docker Hub отправит электронное
письмо с подтверждением на указанный адрес. Откройте письмо и перейдите по
ссылке подтверждения, чтобы завершить процесс проверки.
Теперь у нас есть общедоступный реестр, куда можно отправлять новые образы.
В настройках учетной записи (https://hub.docker.com/settings/default-privacy) под
фотографией профиля есть раздел Default Privacy (Параметры конфиденциально­
сти по умолчанию), где можно при необходимости изменить видимость реестра на
private (частный).
Для дополнительной безопасности при создании учетной записи и входа исполь­
зуйте персональный токен доступа с ограниченными привилегиями (https://docs.
docker.com/go/access-tokens).
Вход в реестр
Давайте войдем в реестр Docker Hub с помощью своей учетной записи:
$ docker login
Login with your Docker ID to push and pull images from Docker НuЬ. If you don't have
а Docker ID, head over to https://huЬ.docker.com to create one.
Username: <имя пользователя>
Password: <пароль/токен>
Logiп Succeeded
r.:l
�
Команда docker login - это функциональный эквивалент команды docker login
docker.io.
Если сервер отправил подтверждение Login succeeded (Вход выполнен успешно), мы
можем извлекать из реестра образы. Как это произошло? Docker записал dotfile
в домашнем каталоге, чтобы кешировать эту информацию. Установлены разреше­
ния 0600, чтобы другие пользователи не могли прочитать наши учетные данные.
В файле мы увидим примерно такое содержимое:
$ 1s -la ${HOME)/.docker/config.json
-rw-------@ 1 ... 158 Dec 24 10:37 /Users/someuser/.docker/config.json
$ cat ${HOME)/.docker/config.json
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker 1
79
В Linux мы увидим следующие строки:
"auths": {
"https://index.docker.io/vl/ 11 :
"auth":"cmVsaEXamPLЗhElRmFCOUE=",
"email":"someuser@example.com"
Docker непрерывно развивается и добавляет поддержку для нативных систем
управления секретами разных операционных систем, например macOS Keychain
или Диспетчер учетных данных Windows. Ваш файл config.json может выглядеть
совсем не так, как в рассмотренном примере. Существуют различные менеджеры
учетных данных (https://github.com/docker/docker-credential-helpers) для разных
платформ, которые упростят вам жизнь.
Заачение auth , файле ,онфщрации ,nиента Oocker имеет rодиров,у base64. Оно
не зашифровано. Обычно это представляет проблему только в системах Linux
с несколькими пользователями, поскольку в системе нет менеджера учетных дан­
� ных по умолчанию, и другие привилегированные пользователи смоrут читать файл
конфигурации клиента Docker и хранящиеся в нем секреты. Мы можем настроить
gpg или pass для шифрования этих файлов на Linux.
Здесь мы видим, что файл ${HOМE}/.docker/config.json содержит учетные данные
docker.io для пользователя someuser@example.com в JSON. Этот файл конфигура­
ции поддерживает хранение учетных данных для нескольких реестров. Пока у нас
будет только одна запись, для Docker Hub, но при необходимости их можно добав­
лять. Если теперь реестр потребует аутентификацию, Docker будет искать учетные
данные в файле ${HOМE}/.docker/config.json. Если они там есть для этого имени
хоста, он их предоставит. Вы могли заметить, что у нас отсутствует одно значение:
метка времени. Учетные данные хранятся неопределенное время или до тех пор,
пока вы не удалите их сами.
Если мы больше не хотим хранить учетные данные в кеше, то можем выйти из рее­
стра:
$ docker logout
Removing login credentials for https://index.docker.io/vl/
$ cat ${HOМE}/.docker/config.json
{
"auths": {
Таким образом мы удаляем кешированные учетные данные, и Docker больше не
будет их хранить. Некоторые версии Docker даже удаляют этот файл, если в нем
ничего нет. Если мы попытаемся войти в другой реестр, кроме Docker Hub, то
можем указать имя хоста в командной строке:
$ docker login someregistry.example.com
Книги для программистов: https://clcks.ru/books-for-it
80
Глава 4
В файл ${HOМE}/.docker/con:fig.json будет добавлена еще одна запись об аутенти­
фикации.
Отправка образов в репозиторий
На первом этапе отправки образа мы должны убедиться, что вошли в нужный репо­
зиторий Docker. В этом примере мы будем использовать Docker Hub, так что вой­
дите в Docker Hub, указав свои учетные данные:
$ docker login
Login with your Docker ID to push and pull images from Docker НuЬ. If you don't have
а Docker ID, head over to https://huЬ.docker.com to create one.
Username: <имя пользователя>
Password: <пароль/токен>
Login Succeeded
Logging in with your password grants your terminal coЩ)lete access to your account.
Войдя в реестр, отправьте образ. Раньше мы использовали команду ctocker image
build -t example/docker-node-hello: latest . для сборки образа docker-node-hello.
В реальности клиент Docker (и по соображениям совместимости многие другие ин­
струменты для работы с контейнерами) интерпретирует example/docker-node­
hello: latest как docker.io/example/docker-node-hello:latest. Здесь docker. io означает имя
хоста реестра образов, а example/docker-node-hello - это репозиторий внутри реестра,
который содержит нужные образы.
При локальной сборке образа можно указать любое имя реестра и репозитория.
Если мы будем отправлять образ в реальный реестр, имя должно совпадать с логи­
ном.
Если мы хотим изменить теги для уже созданного образа, можно выполнить сле­
дующую команду, заменив $/<myuser>J на свое имя пользователя Docker Hub:
$ docker image tag example/docker-node-hello:latest \
docker.io/$(<myuser>}/docker-node-hello:latest
Если нужно заново собрать образ с новым соглашением об именовании или мы
просто хотим поэкспериментировать, можно выполнить следующую команду в ра­
бочем каталоге docker-no de-hello, созданном при операции git checkout чуть раньше
в этой главе:
$ docker image build -t docker.io/${<myuser>}/docker-node-hello:latest .
�
В следующих примерах вместо $(<myuser>} указывайте свое имя пользователя на
Docker Hub. Если вы используете другой реестр, заменяйте docker.io на имя хоста
вашего реестра.
Первая сборка займет некоторое время. Повторная сборка будет выполняться го­
раздо быстрее. Это связано с тем, что почти все или даже все слои уже существуют
на сервере Docker после предыдущей сборки. Мы можем быстро проверить, нахоКниги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
81
1
дится ли образ на сервере, выполнив команду docker image 1s $ ( <myuser>}/docker-node­
hello:
$ docker image 1s $(<myuser>}/docker-node-hello
REPOSITORY
TAG
IМAGE ID
latest
f683df27f02d
myuser/docker-node-hello
CREATED
дЬоut an hour ago
SIZE
649МВ
Мы можем отформатировать вывод команды docker image 1s, чтобы просматри­
вать только нужные данные, с помощью арrумента --format: docker image 1s
--format="taЫe ((.ID}}\t((.Repository}}".
Сейчас мы можем отправить образ в репозиторий Docker с помощью команды
dockerimage push:
$ docker image push $(<myuser>}/docker-node-hello:latest
Using default tag: latest
The push refers to repository [docker.io/myuser/docker-node-hello]
5f3ee7afc69c: Pushed
5bb0785f2eee: Mounted from library/node
latest: digest: sha256:f5ceb032aec36fcacaЬ7le468eaf0ba8a832cfc8244fЬc784d0...
Если этот образ отправлен в публичный репозиторий, кто угодно может загрузить
его с помощью команды docker image pull.
$ docker image pull ${<myuser>}/docker-node-hello:latest Using default tag: latest
latest: Pulling from myuser/docker-node-hello
Digest: sha256:f5ceb032aec36fcacaЬ7le468eaf0ba8a832cfc8244fЬc784d040872be04lcd5
Status: Image is up to date for myuser/docker-node-hello:latest docker.io/myuser/
docker-node-hello:latest
Если вы загрузили образ в частный репозиторий, пользователь должен войти
в него под своими учетными данными с помощью команды docker login, чтобы
скачать образ в локальную систему.
Поиск образов в Docker Hub
Мы
можем
искать
доступные
образы
не
только
на
сайте
(https://hub.docker.com/), но и с помощью команды docker search.
Docker
Hub
Например, команда docker search node найдет образы со словом node в имени или
описании:
$ docker search node
NАМЕ
DESCRIPТION
STARS OFFICIAL АUТОМАТЕD
node
Node.js is а JavaScript-ba... 12267 [ОК]
mongo-express
Web-based MongoDB admin in...
nodered/node-red
Low-code prograпming for е...
nodered/node-red-docker
Deprecated - older Node-RE...
circleci/node
Node.js is а JavaScript-ba...
1274
544
356
130
[ОК]
[ОК]
Книги для программистов: https://clcks.ru/books-for-it
82
Глава 4
kindest/node
Ьitnami/node
cimg/node
opendronemap/nodeodm
Ьitnami/node-exporter
appdynamics/nodejs-agent
wallarm/node
sigs.k8s.io/kind node imag...
Bitnami Node.js Docker Ima...
The CircleCI Node.js Docke...
Automated build for NodeOD...
Bitnami Node Exporter Dock...
Agent for monitoring Node....
Wallarm: end-to-end API se...
78
69
14
10
9
5
5
[ОК]
[ОК]
[ОК]
[ОК]
Заголовок OFFICIAL указывает, что это официальный рекомендованный образ
(https://docs.docker.com/docker-huЬ/official_images) с Docker Hub. Обычно это
означает, что образ принадлежит компании или официальному сообществу разра­
ботчиков, которое занимается сопровождением приложения. Заголовок AUTOМATED
указывает, что образ собран и отправлен автоматически в ходе процесса CVCD,
который запускается через коммиты в базовом репозитории исходного кода. Офи­
циальные образы всегда имеют этот заголовок.
Управление частным реестром
Docker поддерживает философию программного обеспечения с открытым исход­
ным кодом и рекомендует сообществу делиться образами Docker через Docker Hub
по умолчанию. Иногда это невозможно по коммерческим или юридическим причи­
нам, из-за требований к хранению образов или проблем с надежностью.
В таких случаях имеет _смысл поднять внутренний частный реестр. Создать базо­
вый реестр несложно, но если вы будете использовать его в рабочей среде, следует
изучить все доступные варианты конфигурации для Docker Registry с открытым
исходным кодом (https://docs.docker.com/registry).
В этом примере мы создадим очень простой защищенный реестр с поддержкой SSL
и базовой НТТР-аутентификацией.
Сначала создадим на сервере Docker несколько каталогов и файлов. Если вы ис­
пользуете виртуальную машину или облако для сервера Docker, подключитесь
к нему через SSH, чтобы выполнить несколько команд. Если вы применяете Docker
Desktop или Community Edition, их можно выполнить в локальной системе.
Пользователям Windows нужно будет загрузить дополнительные инструменты,
наnри�ер htpassw�, или скорректировать команды, чтобы выполнить те же задачи
в своеи локальнои системе.
Склонируем репозиторий Git, который содержит базовые файлы для создания про­
стого реестра Docker с аутентификацией:
$ git clone https://githuЬ.com/spkane/basic-registry \
--config core.autocrlf=input Cloning into 'basic-registry' ...
remote: Counting objects: 10, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 10 (delta О), reused 10 (delta 0), pack-reused О
Unpacking objects: 100% .(10/10), done.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами !)ocker
1
83
Загрузив файлы ло�ально, мы можем перейти в соответствующий каталог и про­
смотреть, что мы только что загр_узили:
$ ed basie-registry
$ 1s
Doekerfile
eonfig.yaml.sample
registry.ert.sample
README.md
registry.key.sample
htpasswd.sample
Dockerfile, на основе базового образа с Docker Hub, копирует локальные файлы
конфигурации и сопутствующие файлы в новый образ.
Файлы с примерами пригодны для тестирования, но не для рабочей среды.
Если сервер Docker дОС'I)'ПеН через loealhost (127.0.0.1), просто скопируйте файлы
без изменений:
$ ер eonfig.yaml.sample eonfig.yaml
$ ер registry. key.sample registry.key
$ ер registry.ert.sample registry.ert
$ ер htpasswd.sample htpasswd
Если сервер Docker находится на удаленном IР-адресе, потребуется чуть больше
усилий.
Сначала скопируйте config.yaml.sample в config.yaml:
$ ер eonfig.yaml.sample eonfig.yaml
Затем в config.yaml замените 127.о.о.1 на IР-адрес сервера Docker. Вместо:
http:
host: https://127.0.0.1:5000
у вас получится что-то вроде:
http:
host: https://172.17.42.10:5000
Мы можем легко создать реестр с помощью полного доменного имени (Fully
Qualified Domain Name, FQDN), например my-registry. example. сот, но в этом при­
мере проще работать с IР-адресами, потому что DNS не требуется.
Затем нужно будет создать пару ключей SSL для IР-адреса реестра. Это можно сде­
лать с помощью следующей команды OpenSSL:
$ openssl req -х509 -nodes -sha256 -newkey rsa:4096 \
-keyout registry.key -out registry.ert \
-days 14 -suЬj '{/CN=l72.17.42.10}'
IР-адрес в части /CN=l 72.17.42.10 команды должен соответствовать IР-адресу сервера
Docker.
Наконец, можно скопировать файл примера htpasswd:
$ ер htpasswd.sample htpasswd
Книги для программистов: https://clcks.ru/books-for-it
84
Глава 4
Или создайте свое имя пользователя и пароль для аутентификации с помощью сле­
дующей команды, заменив $!<имя_пользователя>} и ${<пароль>}своими значениями:
$ docker coпtainer run --rm --entrypoint htpasswd g \
-Bbn ${<имя_пользователя>} ${<пароль>}> htpasswd
Снова проверив содержимое каталога, вы увидите примерно следующее:
$ ls
Dockerfile
README.md
config.yaml
config.yaml.sample
htpasswd
htpasswd.sample
registry.crt
registry.crt.sample
registry.key
registry.key.sample
Если какие-то из этих файлов отсутствуют, убедитесь, что вы ничего не пропустили
на предыдущих этапах.
Если все нормально, вы готовы собрать и запустить реестр:
$ docker image build -t my-registry .
$ docker container run --rm -d -р 5000:5000 --name registry my-registry
$ docker container logs registry
Если возникли ошибки, например "docker: Еггог response from daemon: Conflict. The
container name "/registry" is already in use" (Сообщение об ошибке от демона: кон­
фликт: имя контейнера /registry уже используется), измените имя предыдущего
контейнера или удалите существующий контейнер с этим именем. Удалить контей­
нер можно командой docker container rm registry.
Тестирование частного реестра
Реестр запущен, можно приступать к тестированию. В первую очередь, нужно
пройти аутентификацию и войти в него. Убедитесь, что IР-адрес, указанный в ctocker
login, соответствует IР-адресу сервера Docker, где запущен реестр.
Имя пользователя по умолчанию - myuser, пароль по умолчанию - myuser-pw ! .
Если у вас свой htpasswd, укажите значение из него.
� $ docker login 127.0.0.1:5000
Username: <имя_пользователя_реестра>
Password: <пароль_реестра>
Login Succeeded
У этого контейнера реестра есть встроенный ключ SSL, и он не использует внеш­
нее хранилище, а значит, он содержит секрет, и при удалении выполняющегося
контейнера будут удалены все образы. Так и задумано.
В рабочей среде контейнеры должны получать секреты из системы управления
секретами и иметь резервное внешнее хранилище, например хранилище объектов.
Если вы хотите сохранять образы реестра в процессе разработки при удалении
контейнеров, укажите --mount type=Ьind, source=/tmp/registry- data, target=/var/
liЬ/registry в команде docker container run, чтобы сохранить данные реестра на
сервере Docker.
Давайте теперь попробуем отправить только что собранный образ в локальный
частный реестр.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker 1
85
; Во всех эп,х командах должен быть у,азан ,оррепны• IР-адрес вашего реестра.
$ docker image tag my-registry 127.0.0.1:5000/my-registry
$ docker image push 127.0.0.1:5000/my-registry Using default tag: latest
The push refers to repository [127.0.0.1:5000/my-registry] f09a0346302c: Pushed
4fc242d58285: Pushed
latest: digest: sha256:c374b0a72lal2c4ld5b298930dlle658fЬd37f22dc2a0fac7dбa2...
Попробуйте извлечь тот же образ из репозитория:
$ docker image pull 127.0.0.1:5000/my-registry
Using default tag: latest
latest: Pulling from my-registry
Digest: sha256:c374b0a72lal2c4ld5b298930dlle658fЬd37f22dc2a0fac7dбa2ecdc0ba5490
Status: Image is up to date for 127.0.0.1:5000/my-registry:latest 127.0.0.1:5000/
my-registry:latest
Помните, что Docker Hub и Docker Distribution предоставляют АРl-эндnоинт, к кото­
рому можно отправлять запросы для получения информации. Больше об API
читайте в официальной документации
(https://github.com/distribution/distribution/ЬloЬ/main/docs/spec/api.md).
Если ошибок не возникло, у нас теперь есть рабочий реестр для разработки, и на
его основе мы можем создать реестр для рабочей среды. Пока реестр можно оста­
новить, выполнив команду:
$ docker container stop registry
Познакомившись с Docker Distribution, вы можете изучить оnенсорс-nроект Cloud
Native Computing Foundation (CNCF) - НагЬог (https://goharbor.io/), который до­
полняет Docker Distribution множеством функций, связанных с безопасностью и
надежностью.
Оптимизация образов
Начав работать в Docker, вы быстро заметите, что маленькие образы и быстрая
сборка помогают ускорить развертывание новых версий программного обеспечения
в рабочей среде. В этом разделе мы рассмотрим, о чем нужно помнить при проек­
тировании образов и какими методами достичь этих целей.
Контроль размера образов
В большинстве современных компаний люди, не задумываясь, загружают через
Интернет файлы размером 1 ГБ. Некоторые программы проще скачать, запустить
один раз, удалить, а при необходимости скачать снова, чтобы не хранить локаль­
ную копию. Это нормально, если нам нужна одна копия на одном сервере, но предКниги для программистов: https://clcks.ru/books-for-it
86
1
Глава 4
ставьте, что у нас больше ста узлов и мы развертываем новые выпуски несколько
раз в день. Большой объем трафика перегружает сеть и замедляет развертывание, и
это скоро станет заметно в рабочей среде.
Для удобства большое число контейнеров Linux наследуют от базового образа, ко­
торый содержит минимальный дистрибутив Linux. Это удобно для начала работы,
но не обязательно. Контейнеры должны содержать только файлы, которые нужны
для запуска приложения на ядре хоста, и ничего лишнего. Лучший способ это по­
нять - рассмотреть минимальный контейнер.
Go - это компилируемый язык программирования, который позволяет легко соз­
давать статически скомпонованные двоичные файлы. В этом примере мы возьмем
очень маленькое веб-приложение на Go с GitHub (https://github.com/spkane/
scratch-helloworld).
Давайте посмотрим, что делает· это приложение. Выполним следующую команду,
откроем браузер и подключимся к хосту Docker на порте 8080 (например,
Ьttp://127.0.0.1:8080 для Docker Desktop и Community Edition):
$ docker container run --rm -d -р 8080:8080 spkane/scratch-helloworld
Если все получилось, мы увидим в браузере следующее сообщение: Hello World
from Go in minimal Linux container (Hello World от Go в минимальном контейнере
Linux). Давайте посмотрим, какие файлы содержит контейнер. Можно предполо­
жить, что как минимум там будет среда Linux и все файлы для компиляции про­
грамм Go, но мы увидим, что это не так.
Не останавливая контейнер, выполним следующую команду, чтобы узнать его
идентификатор. Эта команда возвращает информацию для последнего созданного
контейнера:
$ docker container 1s -1
CREATED
СОММАND
CONTAINER ID IМAGE
ddc3fбlf311b spkane/scratch-helloworld "/helloworld" 4 minutes ago
Узнав идентификатор контейнера, мы можем экспортировать файлы в Т АR-файл,
чтобы изучить их:
$ docker container export ddc3fбlf311b -о web-app.tar
С помощью команды tar мы можем посмотреть содержимое контейнера на момент
экспорта:
$ tar -tvf web-app.tar
о о
о
о
-rwxr-xr-x
Jan 7 15:54 .dockerenv
drwxr-xr-x
Jan 7 15:54 dev/
о о
о
о
Jan 7 15:54 dev/console
о о
о
о
-rwxr-xr-x
о о
о
о
drwxr-xr-x
Jan 7 15:54 dev/pts/
Jan 7 15:54 dev/shm/
о о
о
о
drwxr-xr-x
Jan 7 15:54 etc/
drwxr-xr-x
о о
о
о
Jan 7 15:54 etc/hostname
о о
о
о
-rwxr-xr-x
Jan 7 15:54 etc/hosts
о о
о
о
-rwxr-xr-x
Jan 7 15:54 etc/mtaЬ -> /proc/mounts
lrwxrwxrwx
о
о
о о
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
-rwxr-xr-x
-rwxr-xr-x
drwxr-xr-x
drwxr-xr-x
о о
о о
о о
,О о
о
о
о
о
о
3604416
о
о
Jan
Jul
Jan
Jan
7 15:54
2 2014
7 15:54
7 15:54
1
87
etc/resolv.conf
helloworld
proc/
sys/
Первое, что бросается в глаза, - в контейнере очень мало файлов, и почти все
имеют размер О байтов. Все файлы нулевой длины обязательно должнь1 находиться
в каждом контейнере Linux и автоматически монтируются методом Ьind mount
(https://unix.stackexchange.com/questions/198590/what-is-a-Ьind-mount) с хоста
в контейнер при его создании. Все эти файлы, кроме .dockerenv, необходимы для
корректной работы. Единственный файл с действительным содержимым относится
к нашему приложению - это статически скомпилированный двоичный файл
helloworld.
Как видите, контейнеры обязательно должны содержать только то, что необходимо
для запуска на ядре. Все остальное можно не включать. Для диагностики и устра­
нения неполадок полезно иметь доступ к командной оболочке контейнера, поэтому
люди часто идут на компромисс и собирают образы на основе легких дистрибути­
вов Linux, вроде Alpine Linux.
Если вы часто изучаете файлы образов, попробуйте инструмент dive (https://
github.com/wagoodman/dive), который предоставляет удобный интерфейс CLI для
просмотра содержимого образов.
Давайте заглянем глубже: вернемся к тому же контейнеру и рассмотрим базовую
файловую систему, чтобы сравнить ее с популярным базовым образом alpine.
Мы могли бы легко заглянуть в образ alpine с помощью команды docker container
run -ti alpine:latest /Ьin/sh, но для образа spkane/scratch-helloworld такой способ не
подходит, пот_ому что в -нем нет командной оболочки или SSH. Получается, мы не
можем изучить его с помощью ssh, nsenter или docker container ехес, хотя есть один
сложный прием, который мы· рассмотрим в разделе "Отладка контейнеров без ко­
мандной-оболочки II главы 11. Ранее мы с помощью команды docker container export
создали файл .tar с копией всех файлов в контейнере, �о на этот раз мы изучим
файловую систему кшrrейцера, напрямую подключившись к серверу Docker. Для
этого нужно найти_ файлы образа на диске сервера.
Чтобы определить, где име,нно на сервере хранятся файлы, выполним команду
docker image insp.ect для образ.а alpine: latest:
$ docker image inspect alpine:latest .[
"Id": "sha25?:3fd...353",
"RepoTags": [
],
"alpine:latest"
"RepoDigests": [ "alpine@sha256: 7Ь8 ... f8b"
],
Книги для программистов: https://clcks.ru/books-for-it
88
Глава 4
11GraphDriver 11 :
11 Data 11 :
{
''МergedDir 11 :
11
/var/liЬ/docker/overlay2/ea8...13a/merged11
,
11UpperDir 11 :
11
/var/liЬ/docker/overlay2/ea8... 13a/diff 11
,
11WorkDir 11 :
11
/var/liЬ/docker/overlay2/ea8...13a/work 11
},
11Name 11 : 1 overlay2 11
1
А затем для образа spkane/scratch-helloworld:latest:
$ docker image inspect spkane/scratch-helloworld:latest
11 Id11 :
11
sha256:4fa... Обd11
11 RepoTags 11 :
1
1
],
spkane/scratch-helloworld:latest 11
11 RepoDigests 11 :
11
],
,
[
[
spkane/scratch-helloworld@sha256: 46d...ald11
11 Gr aphDriver 11 :
11 Data 11 :
{
11LowerDir 11 :
/var/liЬ/docker/overlay2/37a...84d/diff:
/var/liЬ/docker/overlay2/28d...ef4/diff",
11
''МergedDir 11 :
1
1
/var/liЬ/docker/overlay2/fc9... c91/merged",
11UpperDir 11 :
11
/var/liЬ/docker/overlay2/fc9.. .c91/diff",
11WorkDir 11 :
11
},
/var/liЬ/docker/overlay2/fc9...c91/work"
11Name 11 :
11
overlaу2"
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
�
1
89
В этом примере мы используем Docker Desktop на macOS, -но такой подход будет
работать на большинстве серверов Docker. Подключайтесь к серверу Docker лю­
бым удобным для вас способом.
Поскольку мы используем Docker Desktop, нам потребуется nsenter для входа в вир­
туальную машину без SSH:
$ docker container run --rm -it --privileged --pid=host debian \
nsenter -t 1 -m -u -n -i sh
/ #
Войдя в виртуальную машину, мы можем изучать различные каталоги из секции
GraphDriver в выводе команды docker image inspect.
В этом примере мы посмотрим на первую запись для образа alpine. Мы увидим, что
она помечена как мergedDir и указывает на папку /var/liЬ/docker/overlay2/ea86408b
2Ьl5d33ee27d78ff44f8210470528622lf055bal33lb58673f4b3lЗa/merged. Если мы
попробуем вывести содержимое этого каталога, то получим ошибку, но, заглянув
в родительский каталог, мы увидим, что на самом деле нам нужен каталог diff:
/ # 1s -lFa /var/liЬ/docker/overlay2/ea ...3a/merged
1s: /var/liЬ/docker/overlay2/ea ..Зa/merged: No such file or directory
/ # 1s -lF /var/liЬ/docker/overlay2/ea...3a/
total В
4096 Mar 15 19:27 diff/
18 root
root
drwxr-xr-x
26 Mar 15 19:27 link
root
1 root
-rw-r--r-/ # 1s -lF /var/liЬ/docker/overlay2/ea...3a/diff
total 64
Jan
9 19:37
2
root
4096
root
drwxr-xr-x
9 19:37
2
root
4096
Jan
root
drwxr-xr-x
9 19:37
4096
Jan
root
root
drwxr-xr-x
15
9 19:37
4096
Jan
root
drwxr-xr-x
2
root
4096
Jan
9 19:37
drwxr-xr-x
5
root
root
9 19:37
4096
root
root
Jan
drwxr-xr-x
5
2
root
root
9 19:37
4096
Jan
drwxr-xr-x
2
4096
root
root
Jan
dr-xr-xr-x
9 19:37
drwx-----2
root
root
4096
9 19:37
Jan
root
drwxr-xr-x
2
root
4096
9 19:37
Jan
drwxr-xr-x
2
root
root
4096
Jan
9 19:37
2
drwxr-xr-x
root
4096
Jan
root
9 19:37
2
drwxr-xr-x
root
4096
root
9 19:37
Jan
2
drwxrwxrwt
root
root
4096
9 19:37
Jan
7
drwxr-xr-x
root
root
4096
Jan
9 19:37
drwxr-xr-x
11
root
root
4096
9 19:37
Jan
Ьin/
dev/
etc/
home/
liЬ/
media/
mnt/
proc/
root/
run/
sЬin/
srv/
sys/
trnp/
usr/
var/
/ # du -sh /var/lib/docker/overlay2/ea ...3a/diff 4.5М
/var/liЬ/docker/overlay2/ea ... Зa/diff
alpine - очень маленький образ, он весит всего 4,5 МБ и идеально подходит как
база под контейнеры. Однако мы видим, что еще до начала сборки в контейнере
по-прежнему много всего.
Книги для программистов: https://clcks.ru/books-for-it
90 1
Глава 4
Давайте посмотрим 'файлы в образе spkane/scratch-helloworld. В этом случае нас ин­
тересует первый каталог в записи LowerDir в выводе docker image inspect, и в итоге мы
снова попадаем в каталог с именем diff:
/ # ls -lFh /var/liЬ/docker/overlay2/37 ...4d/diff_
total 3520
-rwxr-xr-x
root
З.4М Jul 2 2014 helloworld*
l root
/ # exit
Как видите, в каталоге находится всего один файл размером 3,4 МБ. Двоичный
файл helloworld - единственный в этом контейнере, и он меньше, чем начальный
размер образа alpine до того, как в него бьmи добавлены другие файлы приложения.
Мы можем запустить приложение helloworld из этого каталога прямо на сервере
Docker, потому что ему не нужны никакие другие файлы. Но это следует делать
только в среде разработки, чтобы лучше понять, в чем могут быть преимущства
статически компилируемых приложений.
Многоэтапные сборки
Во многих случаях мы можем еще больше ограничить размер с помощью много­
этапных сборок (multistage build). Это рекомендованный подход для большинства
контейнеров в рабочей среде. В результате нам не приходится волноваться о при­
влечении дополнительных ресурсов для сборки приложения, и контейнер остается
очень легким. Контейнеры с многоэтапной сборкой поддерживают сборки
в Docker, а это обеспечивает высокую степень воспроизводимости в системе сборки.
Как написал автор исходного приложения scratch-helloworld (https://medium.com/
%40adriaandejonge/simplify-the-smallest-possiЬle-docker-image-62c0e0d342ef), под­
держка выпуска многоэтапных сборок в самом Docker значительно упростила цро­
цесс создания небольших контейнеров. Многоэтапные сборки позволяют .легко
сделать то, для чего раньше пришлось бы собрать один образ, который компилиру­
ет код, извлечь получившийся двоичный файл, а затем собрать второй образ без
зависимостей и вставить в него двоичный файл. Это был сложный процесс, кото­
рый еще нужно бьmо адаптировать к стандартным конвейерам развертывания.
Сейчас мы можем достичь подобных результатов с помощью простого Dockerfi\e:
# Сборка контейнера
FROМ docker.io/golang:alpine as builder
RUN apk update && \
apk add git && \
CGO_ENAВLED=0 go install -а -ldflags '-s' \
githuЬ.com/spkane/scratch-helloworld@latest
# Рабочий контейнер
FROМ scratch
СОРУ --from=builder /go/bin/scratch-helloworld /helloworld
EXPOSE 8080
CMD [ "/helloworld"]
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
91
Первое, что бросается в глаза, - этот файл выглядит так, будто состоит из двух
Dockerfile. Это действительно так, но не все так просто. Команда FROM расширена,
чтобы можно было присвоить образу имя на этапе сборки. В этом примере первая
строка ( FROM docker.io/golang as builder) означает, что сборка должна быть основана
на образе golang, и этот образ и шаг сборки мы будем называть builder.
В четвертой строке мы снова видим FROM - до введения многоэтапных сборок такой
синтаксис был запрещен. В этой строке FROM указано специальное имя образа,
scratch, чтобы Docker начал с пустого образа без дополнительных файлов. Следую­
щая строка, СОРУ --from=builder /go/Ьin/scratch-helloworld /helloworld, позволяет ско­
пировать двоичный файл, собранный в образе builder, напрямую в текущий образ.
В результате мы получим контейнер минимального размера.
Строка EXPOSE 8080 уведомляет пользователей, через какие порты и протоколы (по
умолчанию - ТСР) сервис получает данные.
Давайте выполним сборку и посмотрим, что получится. Сначала создадим каталог
для работы, а затем вставим в любом текстовом редакторе содержимое из преды­
дущего примера в файл с именем Dockerfile:
$ mkdir /trnp/multi-build
$ cd /tmp/multi-build
$ vi Dockerfile
; Мож,о за,рузи;s ,опию этою Dockenile ,а G;tнuь'
Мы можем запустить многоэтапную сборку:
$ docker image build .
[+) Building 9.7s (7/7) FINISHED
=> [internal) load build definition from Dockerfile
=> => transferring dockerfile: 37В
=> [internal) load .dockerignore
=> => transferring context: 2В
=> [internal) load metadata for docker.io/library/golang:alpine
=> CACHED [builder 1/2] FROM docker.io/library/golang:alpine@sha256:7cc6257.. .
=> [builder 2/2) RUN apk update && apk add git && CGO_ENABLED=0 go install .. .
=> [stage-1 1/1) СОРУ --from=builder /go/Ьin/scratch-helloworld /helloworld
=> exporting to image
=> => exporting layers
=> => writing image sha256:bb853f23418161927498b9631f54692cflld84dбbde3af2d...
Выходные данные будут похожи на большинство остальных сборок. В конце мы
получаем сообщение об успешном создании итогового минимального образа.
5 Полный URL: https://githu b.com/Ыuewhalebook/docker-up-and-running-Зrd-edition/ЫoЫmain/chapter_04/multi
stage/Dockerfile.
Книги для программистов: https://clcks.ru/books-for-it
92
Глава 4
Если вы компилируете двоичные файлы в локальной системе, и они используют
общие библиотеки, убедитесь, что процессу в контейнере доступны правильные
версии этих библиотек.
Эrапов может быть больше двух, и они могут быть никак не связаны друг с другом.
Эrапы выполняются по порядку. Например, у нас может быть этап, основанный на
общедоступном образе Go, где мы собираем приложение Go в качестве API, и этап
на основе контейнера Angular для сборки пользовательского интерфейса веб­
приложения. На последнем этапе можно объединить выходные данные из первых
двух.
Приступая к сборке более сложных образов, вы заметите, как сложно придержи­
ваться одного контекста сборки. Плагин docker-buildx, о котором мы поговорим
ближе к концу этой главы, поддерживает несколько контекстов сборки (https://
www.docker.com/Ыog/dockerfiles-now-support-multiple-build-contexts), поэтому
его можно использовать в очень сложных рабочих процессах.
Суммирование слоев
На первый взгляд это не очевидно, но слои файловой системы, составляющие обра­
зы, всегда только суммируются. Мы можем скрывать или маскировать файлы на
предыдущих слоях, но удалить их невозможно. На практике это означает, что мы
не можем уменьшить образ, удалив файлы, созданные на предыдущих шагах.
lSl
Если включить на сервере Docker экспериментальные функции, можно вместить
в один слой несколько слоев с помощью команды docker irnage build --squash. При
этом все файлы, удаленные на промежуточных слоях, не войдут в итоговый образ
и не будут занимать лишнее место. Зато системе придется загружать весь слой
целиком, даже если обновлена всего одна строка кода, так что у этого подхода
есть свои ощутимые недостатки.
Самый простой способ объяснить суммирование слоев - на практических приме­
рах. В новом каталоге загрузите (https://github.com/Ыuewhalebook/docker-up-and­
running-3rd-edition/ЫoЬ/main/chapter_04/additive) или создайте следующий файл,
который сформирует образ, запускающий веб-сервер Apache на Fedora Linux:
FRCМ docker.io/fedora
RUN dnf install -у httpd
СИ) ["/usr/sЬin/httpd", "-DFOREGROUND"]
Выполните сборку:
$ docker irnage build
[+] Building 63.5s (6/6) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 130В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:latest
=> (1/2) FROМ docker.io/library/fedora
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
93
=> [2/2) RUN dnf install -у httpd
=> exporting to image
=> => exporting layers
=> => writing image sha256:543dбlc956778b8ea3b32fle09a9354a864467772eб...
Давайте пометим тегом получившийся образ, чтобы ссылаться на него в после­
дующих командах:
$ docker image tag sha256:543dбlc956778b8ea3b32fle09a9354a864467772e6... sizel
Теперь посмотрим на наш образ с помощью команды docker image history. Эта
команда позволит больше узнать о слоях файловой системы и шагах сборки нашего
образа:
$ docker image history sizel
IМAGE
CREATED
AЬout а minute ago
543dбlc95677
<missing>
AЬout а minute ago
6 weeks ago
<missing>
<missing>
6 weeks ago
<missing>
3 months ago
15 months ago
<missing>
CREATED ВУ
CMD ["/usr/sЫn/httpd" "-DFOREGROU... "]
RUN /Ыn/sh -с dnf install -у httpd
/Ыn/sh -с #(nop) CMD ["/Ьin/bash"] ...
/Ыn/sh -с #(nop) ADD file:58865512с...
/Ьin/sh -с #(nop) ENV DISTTAG=f36co...
/Ыn/sh -с #(nop) LAВEL maintainer=...
SIZE ...
ов
273МВ
ов
lбЗМВ
ов
ов
Как видите, три слоя не повлияли на размер итогового образа, а другие два - уве­
личили его значительно. Слой размером 163 МБ можно легко объяснить - это
базовый образ Fedora, который включает минимальный дистрибутив Linux. А что
насчет слоя размером 273 МБ? Веб-сервер Apache не должен быть таким большим.
Если вы работали с диспетчерами пакетов, например apk, apt, dnf или yum, то знае­
те, что почти все они используют большой кеш, который содержит информацию
обо всех пакетах, доступных для установки на той или иной платформе. Кеш зани­
мает много места и не приносит никакой пользы после установки нужных пакетов.
Разумеется, на следующем этапе мы хотим удалить этот кеш. В системах Fedora
для этого достаточно отредактировать Dockerfile:
FRCМ docker.io/fedora
RlJN dnf install -у httpd RlJN dnf clean all
СМ) ["/usr/sЬin/httpd", "-DFOREGROUND"]
Затем мы соберем, пометим тегами и изучим получившийся образ:
$ docker image build .
[+] Building 0.5s (7/7) FINISHED
=> => writing image sha256:bбbf99cбe7a69a1229ef63fc086836ada20265a793cb8f2d...
$ docker image tag sha256:bбbf99cбe7a69al229ef63fc086836ada20265a793cb8f2dl7...
IМAGE
CREATED
SIZE ...
CREATED ВУ
Ь6Ьf99сбе7аб AЬout а minute ago CMD ["/usr/sЬin/httpd" "-DFOREGROU..."]
ов
<missing>
AЬout а minute ago RUN /Ьin/sh -с dnf clean all # build...
71.ВkВ
<missing>
10 minutes ago
RUN /Ьin/sh -с dnf install -у httpd ...
273МВ
<missing>
6 weeks ago
/Ыn/sh -с #(nop) CMD ["/Ьin/bash"] ...
ов
<missing>
6 weeks ago
/Ыn/sh -с #(nop) ADD file:58865512c...
lбЗМВ
Книги для программистов: https://clcks.ru/books-for-it
94
1
Глава 4
<rnissing>
<rnissing>
3 months ago
15 months ago
ов
ов
/Ьin/sh -с. #(nop) ENV DISTTAG=fЗбco...
/Ьin/sh -с #(nop) LAВEL maintainer=.. .
В ВЫХОДНЫХ данных команды docker image history МЫ увtщим, ЧТО создали · НОВЫЙ
слой, добавивший в образ 71,8 КБ, но при э.том проблемный слой нисколько не
уменьшился. Что же произошло?
Важно понимать, .-что слои образа всегда_ суммируются. Мы не J\.fожем ничего уда­
лить ИЗ уже СОЗДаННОГО СЛОЯ. Д� если На следующих: СЛОЯХ МЫ Уда.JJИМ файлы,
предыдущие слои не станут меньше. Когда мы удащiем или редактируем файлы на
следующих слоях, мы просто маскируем более старые, версии. Получается, что мы
можем уменьшить слой, только если удалим файлы до сохранения.
Самый простой способ это сделать - написать J<Омандьi на одной строке в Docker­
file с помощью оператора &&. Этот оператор высl)'Пае.т как AND и выполняет сле­
дующую �омандУ, только если предыдущая выполнена успешно. Кроме того, с помощью оператора \ можно указать, что команда продолжается после перехода на
новую строку. Это упростит чтение длинных команд.
Итак, мы можем переписать Dockerfile:'
•
FIOf docker.io/fedora
RUN dnf install �У httpd && \
dnf clean all
СИ) ["/usr/sЬin/httpd", "-DFOREGROUND"]
1
.
.
'
.
'
Давайте пересоберем образ и посмотрим, как это изменение повлияло на размер
слоя, который вкmочает �емон http:
$ docker image build .
[+] Building 0.5s (7/7) FINISHED
=> => writing image sha256:14fe7924bb0b64lddflle08d3dd56f40aff427lcad7a42lfe..•
$ docker image tag sha256:14fe7924bb0b64lddflle08d3dd56f40aff427lcad7a42lfe9b...
IМAGE
CREATED
CREATED ВУ
(
14fe7924bb0b
AЬout а minute ago
CMD ["/usr/sЬin/httpd" "-DFOREGROUN."] ...
RUN /Ьin/sh -с drif inst_all --у httpd &•••
<rnissing>
AЬout а minute ago
6 weeks ago
<rnissing>
/Ьin/sh -с #(nop) CMD ["/Ьin/bash"] ...
6 weeks ago
<rnissing>
/Ьin/sh -с #(nop) ADD ·file:58865512са...·
<rnissing>
3 months·ago
/Ьin/sh -с #(nop) ENV DISTTAG=f36con...
<rnissing>
15 months ago
/Ьin/sh -с #(nop) LAВEL maintainer=C .. ;
SIZE
ов
44.ВМВ
ов
iбЗМВ
ов
ов
В первых двух примерах размер. слоя составлял 273 МБ, но мы удалили лишние
файлы и уменьшили его до 44,8 МБ. Это позволит существенно сэкономить трафик,
когда много серверов будут извлекать образ во время развертывания.
Использование кеша слоев
Последний метод сборки, который мы здесь рассмотрим, связан с ускорением
сборки. Философия DevOps подразумевает максимально короткий циI<Л обратной
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
95
связи. Чем раньше мы найдем проблему, тем быстрее приступим к ее исправлению,
пока мы еще помним этот код и не перешли к совершенно не связанным с ним
задачам.
В стандартном процессе сборки Docker использует кеширование слоев, чтобы не
пересобирать слои образа, которые уже были собраны и не содержат заметных из­
менений. Благодаря этому кешу порядок выполнения операций в Dockerfile заметно
влияет на время выполнения сборки.
Для начала возьмем Dockerfile из предыдущего примера и немного его изменим.
�l
�
Эти файлы, как и остальные, вы найдете на GitHub (https://github.com/
Ыuewhalebook/docker-up-and-running-Зrd-edition/ЫoЬ/main/chapter_04/cache).
FRCМ docker.io/fedora
RUN dnf install -у httpd && \
dnf clean all
RUN mkdir -р /var/www && \
mkdir -р /var/www/html
ADD index.html /var/www/html
СМ) ["/usr/sЬin/httpd", "-DFOREGROUND"]
Теперь в том же каталоге создадим файл index.html:
<html>
<head>
<title>My custom Web Site</title>
</head>
<Ьоdу>
<p>Welcome to my custom Web Site</p>
</Ьоdу>
</html>
В первом тесте измерим время сборки без использования кеша Docker:
$ time docker image build --no-cache
time docker image build --no-cache
[+] Building 48.Зs (9/9) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 238В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:latest
=> CACHED [1/4] FROM docker.io/library/fedora
=> [internal] load build context
=> => transferring context: 32В
=> [2/4] RUN dnf install -у httpd &&
dnf clean all
=> [3/4] RUN mkdir -р /var/www &&
mkdir -р /var/www/html
=> [4/4] ADD index.html /var/www/html
Книги для программистов: https://clcks.ru/books-for-it
96
Глава 4
=> exporting to image
=> => exporting layers
=> => writing image sha256:7f94d0d6492f2d2c0b8576f0f492e03334eбa535cac85576c...
real lm21.645s
user Om0.428s
sys Om0.323s
Пользователи Windows могут выполнить эту команду в сеансе WSL2 или исполь­
зовать функцию PowerShell Measure-Command6, чтобы заменить команду Unix time
в этих примерах.
В выводе команды time мы видим, что сборка без кеша заняла 1 мин 21 с, и мы из­
влекли из кеша только базовый образ. Если сразу пересобрать образ и на этот раз
задействовать кеш, то получится гораздо быстрее:
$ time docker image build .
[+] Building 0.1s (9/9) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:latest
=> [1/4] FROМ docker.io/library/fedora
=> [internal] load build context
=> => transferring context: 32В
=> CAC1�ED [2/4] RUN dnf install -у httpd &&
dnf clean all
=> CACHED [3/4] RUN mkdir -р /var/www &&
mkdir -р /var/www/html
=> CACHED [4/4] ADD index.html /var/www/html
=> exporting to image
=> => exporting layers
=> => writing image sha256:0d3aeeeeebd09606d99719e0c5197clf3e59a843c4d7a2laf...
real Om0.416s
user Om0.120s
sys Om0.087s
Поскольку ни один из слоев не изменился и все четыре этапа можно взять из кеша,
понадобилось меньше секунды. Давайте внесем небольшое изменение в файл
index.html:
<html>
<head>
<title>My custom Web Site</title>
</head>
6
Полный URL: bttps://learn.microsoft.com/en-us/powersbell/module/microsoft.powershell.utility/
measure-command? view=powershell-7.3.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
97
<Ьоdу>
<div align= "center">
<p>Welcome to ту custom Web Site! ! !</р>
</div>
</Ьоdу>
</html>
Снова пересоберем образ:
$ time docker image build .
[+] Building 0.1s (9/9) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:latest
=> [internal] load build context
=> => transferring context: 214В
=> [1/4] FROМ docker.io/library/fedora
dnf clean all
=> CACHED [2/4] RUN dnf install -у httpd &&
mkdir -р /var/www/html
=> CACHED [3/4] RUN mkdir -р /var/www &&
=> [4/4] ADD index.html /var/www/html
=> ADD index.html /var/www/html
=> exporting to image
=> => exporting layers
=> => writing image sha256:daf792dalbбa0ae7cfЬ2673b29f98ef2123d666b8dl4e0Ь74 ...
real 0m0.456s
user 0m0.120s
sys 0m0.068s
В выводе видно, что для большей части сборки использовался кеш. Только на ша­
ге 4/4 Docker извлек index.html, для которого копия в кеше бьша отменена, и слои
нужно было создавать заново. Поскольку большую часть сборки мы извлекли из
кеша, сборка снова заняла не больше секунды.
Давайте посмотрим, что будет, если мы изменим порядок команд в Dockerfile:
FRCМ docker.io/fedora
RUN mkdir -р /var/www && \
mkdir -р /var/www/html
ADD index.html /var/www/html
RUN dnf install -у httpd && \
dnf clean all
СМ) ["/usr/sЬin/httpd", "-DFOREGROUND"]
Измерим время этой сборки без кеша:
$ time docker image build --no-cache
[+] Building 51.5s (9/9) FINISHED
=> => writing image sha256:lcc5f2c5e4a4dlcf384fбfb3a34fd4d00e7f5e7a7308d5flf...
Книги для программистов: https://clcks.ru/books-for-it
98
Глава 4
real Om51.859s
user Om0.237s
Om0.159s
sys
На этот раз сборка заняла 51 с: указав аргумент --no-cache, мы знаем, что кроме
базового образа мы ничего не загружали из кеша. Разница во времени по сравне­
нию с первым тестом связана с изменениями скорости сети, а не с изменениями
в Dockerfile.
Давайте снова внесем изменения в файл index.html:
<hЬal>
<head>
<title>My custom Web Site</title>
</head>
<Ьоdу>
<div align="center" style="font-size:180%">
<p>Welcome to my custom Web Site</p>
</div>
</Ьоdу>
</hЬal>
На этот раз пересоберем образ, используя кеш:
$ time docker image build .
[+] Building 43.4s (9/9) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:latest
=> (1/4] FROМ docker.io/library/fedora
=> [internal] load build context
=> => transferring context: 233В
mkdir -р /var/www/html
=> CACHED (2/4] RUN mkdir -р /var/www &&
= > [3/4] ADD index.html /var/www/html
=> (4/4] RUN dnf install -у httpd &&
dnf clean all
= > exporting to image
=> => exporting layers
=> => writing image sha256:9a05b2d0lb5870649e0adld7ad68858e0667f402c8087f0b4...
real Om43.695s
user OmO.211s
sys Om0.133s
Когда мы в первый раз собирали образ снова после изменения файла index.html, это
заняло всего 0,456 с, а сейчас понадобилось 43,695 с - почти столько же, сколько
ушло на сборку всего образа без кеша.
Это связано с тем, как мы изменили Dockerfile, - теперь файл index.html копирует­
ся в начале процесса. Проблема этого подхода связана с тем, что файл index.html
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
99
часто меняется и отменяет содержимое в кеше. Еще один минус- файл почему-то
помещен перед очень долгим шагом в Dockerfile: установкой веб-сервера Apache.
Как видите, порядок операций в Dockerfile очень важен, поэтому старайтесь распо­
лагать их таким образом, чтобы самые стабильные и долгие части сборки выполня­
лись в первую очередь, а код добавляйте как можно позже.
Если в проекте необходимо устанавливать зависимости на основе кода с помощью
таких инструментов, как npm и bundle, тщательно изучите дополнительную информа­
цию, чтобы оптимизировать сборки Docker для этих платформ. Для этого часто
необходимо фиксировать версии зависимостей и хранить их вместе с кодом, чтобы
не загружать при каждой сборке.
Кеширование каталога
BuildКit расширяет наши возможности при сборке образов, и одна из удобных
функций- кеширование каталога. Этот метод ускоряет сборку, не заставляя нас
сохранять в образе лишние файлы для среды выполнения. По сути, мы можем со­
хранить содержимое каталога в образе на специальном слое, который можно мон­
тировать методом Ьind mount во время сборки, а затем удалять до создания момен­
тального снимка образа. Этот метод часто используется для работы с каталогами,
в которых установщики Linux (apt, apk, dnf и т. д.) и менеджеры зависимостей для
разных языков (npm, bundler, pip и т. д.) загружают свои базы данных и файлы архи­
вов.
Если вы не знаете, как работает Ьind mount, изучите этот раздел в документации
Docker (https://docs.docker.com/storage/blnd-mounts).
Чтобы реализовать кеширование каталога, потребуется включить BuildKit. Обычно
он уже включен, но мы можем включить его принудительно со стороны клиента,
задав для переменной среды DOCKER_вur1oкrт значение 1:
$ export DOCKER_BUILDKIT=l
Давайте извлечем следующий репозиторий Git и посмотрим, как кеширование
каталога повышает эффективность последовательных сборок и позволяет контро­
лировать размер образов:
$ git clone htt ps://githuЬ .com/spkane/open-mastermind .git \
--config core.autocrlf=input
$ cd open-mastermind
$ cat Dockerfile
� python:3.9.15-slim-bullseye
RIJN mkdir /арр WORКDIR /арр
СОРУ . /арр
RIJN pip install -r requirements.txt
'IIORI<DIR /app/mastermind
Q,I) [ " python", "mastermind.p y"]
Книги для программистов: https://clcks.ru/books-for-it
100
Глава 4
В этом коде мы отправляем в репозиторий обычный Dockerfile. Давайте посмот­
рим, сколько времени уйдет на сборку этого образа с кешем и без кеша и каким
в итоге получится образ:
$ tirne docker build --no-cache -t docker.io/spkane/open-mastermind:latest
[+] Building 67.5s (12/12) FINISHED
=> =>
real
user
sys
naming to docker.io/spkane/open-mastermind:latest
0m28.934s
0m0.222s
0m0.248s
0.0s
$ docker image 1s --format "{{ .Size }}" spkane/open-mastermind:latest 293МВ
$ tirne docker build -t docker.io/spkane/open-mastermind:latest
[+] Building 1.5s (12/12} FINISHED
=> => naming to docker.io/spkane/open-mastermind:latest
0ml.083s
real
0m0.098s
user
0m0.095s
sys
0.0s
В выводе мы видим, что сборка образа без кеша слоев занимает меньше 29 с,
а с кешем - меньше 2 с. Итоговый образ имеет размер 293 МБ.
BuildKit позволяет изменить или полностью отключить цвета для выходных дан­
ных (https:1/github.com/moby/buildkit#color-output-controls). Это особенно удоб­
но при работе в терминале с темным фоном. Цвета можно настроить с помощью
команды (например, export BUILDKIT_COLORS=run=green:warning=yellow:error=red:
cancel=cyan) или отключить: expcrt NO_COLOR=true.
Версии BuildKit в различных компонентах docker и сторонних инструментах обнов­
ляются, так что этот подход может работать не всегда.
Для тестирования сборки можно выполнить команду:
$ docker container run -ti --rm docker.io/spkane/open-mastermind:latest
Запустится опенсорс-версия игры Masteпnind в терминале (https://github.com/
philshem/open-mastermind). На экране появятся инструкции к игре, при необходи­
мости из нее можно выйти, нажав клавиши <Ctrl>+<C>.
Если это приложение Python, оно указывает все нужные библиотеки в файле
requirements.txt, а затем для установки зависимостей используется приложение pip
в Dockerfile.
Мы устанавливаем необязательные зависимости, чтобы продемонстрировать пре­
� имущества кеширования каталога.
�
.
.
Оrкроем файл requirements.txt и добавим строку log- symЬols:
colorama
# Необязательно - используется дпя демонстрации pandas
flask
log-symЬols
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker 1
101
Выполним сборку еще раз:
$ time docker build -t docker.io/spkane/open-mastermind:latest \
--progress=plain .
11 [internal] load build definition from Dockerfile
19 [5/6] RUN pip install -r requirements.txt
19 sha256:82dЬcl0flbb9fa476d93cc0d8104b76f46afBece7991eb55393d6d72a230919e
19 1.954 Collecting colorama
19 2.058
Downloading colorama-0.4.5-py2.py3-none-any.whl (16 kВ)
real
user
sys
Om16.379s
Om0.112s
Om0.082s
В выводе для шага 5/6 видно, что все зависимости загружаются снова, хотя pip
кеширует большую часть из них в /root/.cache. builder видит, что мы внесли измене­
ние, влияющее на слой, и воссоздает весь слой заново, поэтому мы теряем кеш,
который был сохранен для этого слоя образа.
Давайте исправим ситуацию. Для этого используем кеш каталога BuildКit
(https://github.com/moby/Ьuildkit/ЫoЬ/master/frontend/dockerflle/docs/reference.m
d#run--mounttypecache), но сначала внесем несколько изменений в Dockerfile:
# syntax=docker/dockerfile:1
FRCМ python:3.9.15-slim-bullseye
RlJN mkdir /арр
'WORКDIR / арр
СОРУ . /арр
RlJN --mount=type=cache,target=/root/.cache pip install -r requirements.txt
'WORКDIR /app/mastermind
(1,f) ["python", "mastermind.py"]
Здесь мы видим два важных изменения. Во-первых, мы добавили следующую
строку:
# syntax=docker/dockerfile:1
Таким образом мы указываем Docker, что будем использовать более новую версию
фронтенда Dockerfile (https://hub.docker.com/r/docker/dockerfile), которая предос­
тавляет доступ к новым возможностям BuildКit.
Затем мы отредактировали строку RUN:
RUN --mount=type=cache,target=/root/.cache pip install \
-r requirements.txt
В результате BuildKit монтирует слой кеширования в контейнер по пути /root/.cache
для этого этапа сборки. Эго позволит нам достичь двух целей: содержимое каталога
не войдет в итоговый образ, но он будет подключен к pip и доступен в последую­
щих сборках.
Книги для программистов: https://clcks.ru/books-for-it
102
Глава 4
Давайте полностью пересоберем образ с учетом этих изменений и создадим
начальное содержимое кешируемого каталога. В выводе мы увидим, что pip загру­
жает все зависимости, как и раньше:
$ time docker build --no-cache -t docker.io/spkane/open-rnastermind:latest
[+] Building 15.2s (15/15) FINISHED
=> => naming to docker.io/spkane/open-rnastermind:latest
real
user
sys
0.0s
0ml5.493s
0m0.137s
0m0.096s
Откроем файл requirements.txt и добавим строку py-events:
colorarna
# Необязательно - используется для демонстрации
pandas
flask
log-symЬols py-events
Здесь мы заметим результат внесеннь1х изменений. Если мы пересоберем образ
сейчас, то увидим, что загружены только py-events и его зависимости. Все остальное
использует существующий кеш из предыдущей сборки, который примонтирован
к образу для этого шага сборки:
$ time docker build -t docker.io/spkane/open-rnastermind:latest \
--progress=plain .
#1 [internal] load build definition from Dockerfile
#14 [stage-0 5/6] RUN --mount=type=cache,target=/root/.cache pip install
#14 sha256:9bc7244lfdf2ec5f5803d4d5df43dЬe7bc6eeef88ebee98edl8d8dЬb478270ba
#14 1.711 Collecting colorarna
Using cached colorarna-0.4.5-py2.py3-none-any.whl (16 kB)
#14 1.714
#14 2.236 Collecting py-events
#14 2.356
Downloading py_events-0.l.2-py3-none-any.whl (5.8 kВ)
#16 DONE 1.4s
real
user
sys
0ml2.624s
0m0.180s
0m0.112s
$ docker irnage 1s --forrnat "{{ .Size }}" spkane/open-rnastermind:latest 261МВ
Время сборки сократилось, потому что мы не загружаем все заново, и образ стал на
32 МБ меньше, хотя мы добавили новые зависимости. Это связано с тем, что ката­
лог кеша больше не хранится напрямую в образе, содержащем приложение.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
103
BuildKit и новые инструменты для работы с Dockerfile дают больше возможностей
при сборке образов. Мы советуем прочесть руководство https://github.com/moby/
buildkit/ЫoЬ/master/frontend/dockerfile/docs/reference.md и изучить все доступ­
ные функции.
Решение проблем со сборками
В идеале проблем возникать не должно, особенно если мы сами написали сценарий
сборки, но в реальном мире что угодно может пойти не так. Давайте выясним, как
решать проблемы со сборками в Docker. В этом разделе мы рассмотрим два вариан­
та: в одном мы соберем образ так, как это делали до появления BuildKit, а в другом
воспользуемся BuildКit.
В этом примере мы опять обратимся к репозиторию docker-hello-node. При необхо­
димости склонируйте его снова:
$ git clone https://githuЬ.com/spkane/docker-node-hello.git \
--config core.autocrlf=input Cloning into 'docker-node-hello'...
remote: Counting objects: 41, done.
remote: Total 41 (delta 0), reused О (delta О), pack-reused 41
Unpacking objects: 100% (41/41), done.
$ cd docker-node-hello
Отладка образов без BuildKit
Для следующих упражнений нам нужна проблемная сборка. Давайте создадим ее.
Для этого внесем изменения в Dockerfile в строку:
RUN apt-get -у update
Теперь она будет выглядеть так:
RUN apt-get -у update-all
_
Если вы используете PowerShell в Windows, скорее всего, потребуется задать
переменную среды, которая отключает BuildКit, прежде чем выполнять команду
docker image build, а затем сбросить изменения:
PS С:\> $env:DOCKER_BUILDKIT = О
PS С:\> docker image build'
-t example/docker-node-hello:latest
--no-cache .
PS С:\> $env:DOCKER_BUILDKIT = 1
Если мы попробуем собрать образ сейчас, то увидим следующую ошибку:
$ DOCKER_BUILDKIT=O docker image build -t example/docker-node-hello:latest \
--no-cache .
Sending build context to Docker daemon 9.21бkВ Step 1/14
---> 9ff38e3aбd9d
FROМ docker.io/node:18.13.0
Книги для программистов: https://clcks.ru/books-for-it
104
Глава 4
Step 6/14 : ENV SCPATH /etc/supervisor/conf.d
---> Running in е903367еаеЬ8
Removing intermediate container е903367еаеЬ8
---> 2a236efc3f06
Step 7/14 : RUN apt-get -у update-all
---> Running in c7cd72f7d9bf Е: Invalid operation update-all
The coпrnand '/bin/sh -с apt-get -у update-all' returned а non-zero code: 100
Как устранить проблему, особенно если мы работаем не в Linux? Помните, что
почти все образы Docker являются слоями поверх других образов Docker, и мы мо­
жем запустить контейнер на основе любого из них. Поначалу смысл не очевиден,
но давайте посмотрим на вывод на шаге 6:
Step 6/14 : ENV SCPATH /etc/supervisor/conf.d
---> Running in е903367еаеЬ8
Removing intermediate container е903367еаеЬ8
---> 2a236efc3f06
В первой строке Running in е903367еаеЬ8 указано, что процесс сборки запустил новый
контейнер на основе образа, созданного на шаге 5. В следующей строке - Removing
intermediate container е903367еаеЬ8 - мы видим, что Docker удаляет контейнер после
изменения на основе инструкции на шаге 6. На этом этапе добавилась перемен­
ная среды по умолчанию: ENV SCPATH /etc/supervisor/conf.d. Последняя строка
(---> 2a236efc3f06) - самая важная, потому что мы получаем идентификатор образа,
созданного на шаге б. Он понадобится для устранения неполадок со сборкой, пото­
му что это образ с последнего успешного этапа в сборке.
Имея эту информацию, мы сможем запустить интерактивный контейнер, чтобы вы­
яснить, почему сборка не работает. Каждый образ контейнера основан на преды­
дущих слоях. Одно из главных преимуществ такого подхода заключается в том, что
мы можем запустить нижний слой как отдельный контейнер и осмотреться там
с помощью командной оболочки:
$ docker container run --rm -ti 2a236efc3f06 /bin/bash
root@Ь83048106ЬOf:/1
Внутри контейнера можно вьmолнять любые команды, чтобы определить причину
проблемы в Dockerfile и понять, как исправить ошибку:
root@Ь83048106ЬOf:/I apt-get -у update-all Е: Invalid operation update-all
root@Ь83048106ЬOf:/I apt-get --help apt 1.4.9 (amd64)
Most used commands:
update - Retrieve new lists of packages
root@Ь83048106ЬOf:/1 apt-get -у update
Get:1 http://security.debian.org/debian-security stretch/updates ... [53.0 kB]
Reading package lists... Done
root@Ь83048106ЬOf:/1 exit
exit
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
105
Раз мы нашли причину, то можем исправить Dockerfile, заменив строку RUN apt-get
-у update-all на RUN apt-get -у update. После пересборки образа все должно завер­
шиться успешно:
$ DOCКER_BUILDKIT=0 docker image build -t exarnple/docker-node-hello:latest
Sending build context to Docker daemon 15.87kВ
Successfully built 69f5e83bb86e
Successfully tagged exarnple/docker-node-hello:latest
Отладка образов с BuildKit
При использовании BuildKit мы изменим подход к поискам причины сбоя, потому
что промежуточные слои сборки не экспортируются из контейнера в демон Docker.
Возможности отладки с BuildKit практически наверняка будут развиваться, а пока
мы рассмотрим подход, который работает уже сейчас.
Давайте вернем Dockerfile в исходное состояние и изменим строку
RlJN npm install
на:
RlJN npm installer
Попробуем собрать этот образ.
;
Не забудьте включ,ть вu;ldKt.
$ docker image build -t exarnple/docker-node-hello:debug --no-cache
[+] Building 51.7s (13/13) FINISHED
0.0s
=> [internal] load build definition from Dockerfile
=> [7/8] WORКDIR /data/app
=> ERROR [8/8] RUN npm installer
0.0s
0.4s
> [8/8] RUN npm installer:
#13 0.399
#13 0.399 Usage: npm <command>
#13 0 .402 Did you mean one of these?
#13 0.402
install
#13 0.402
install-test
#13 0.402
uninstall
executor failed running [/bin/sh -с npm installer]: exit code: 1
Как и ожидалось, мы видим ошибку. Как получить доступ к слою, чтобы устранить
проблему?
Книги для программистов: https://clcks.ru/books-for-it
106
Глава 4
Один из способов - использовать многоэтапные сборки и аргумент --target в docker
image build.
Давайте внесем два изменения в Dockerfile. Изменим строку:
FRCМ docker.io/node:18.13.0
на:
FRCМ docker.io/node:18.13.0 as deploy
И прямо перед проблемной строкой добавим новую строку FRoм:
FRCМ deploy
RIJN npm installer
Таким образом мы создаем многоэтапную сборку: на первом этапе находятся все
шаги, которые точно работают, а второй этап начинается с проблемного шага.
Если мы попробуем пересобрать образ с помощью той же команды, ничего не по­
лучится:
$ docker image build -t example/docker-node-hello:debug
[+] Building 51.7s (13/13) FINISHED
executor failed running [/bin/sh -с npm installer]: exit code: 1
Вместо этого укажем Docker, что хотим собрать только первый образ в нашем мно­
гоэтапном Dockerfile:
$ docker image build -t example/docker-node-hello:debug --target deploy .
[+] Building 0.8s (12/12) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37В
0.0s
0.0s
0.1s
0.1s
=> => writing image sha256:a42dfЬcfc7Ь18ee3d30ace944ad4134ea2239a2cO 0.0s
0.0s
=> => naming to docker.io/example/docker-node-hello:debug
=> exporting to image
=> => exporting layers
Теперь можно создать из этого образа контейнер и выполнить тесты:
$ docker container run --rm -ti docker.io/example/docker-node-hello:debug \
/Ьin/bash
root@l780799717бe:/data/appt 1s index.js package.json
root@l780799717бe:/data/appt npm install
added 18 packages from 16 contributors and audited 18 packages in l.248s
root@l780799717бe:/data/appt exit exit
Найдя ошибку в Dockerfile, отменим изменения и исправим строку npm, чтобы все
работало правильно.
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
107
Мультиархитектурные сборки
С момента запуска Docker архитектура АМD64/х86_64 была и остается основной
платформой, для которой развертывалось большинство контейнеров. Сейчас мно­
гое изменилось. Все больше разработчиков используют системы на основе
АRМ64/AArch64, а облачные компании начинают предоставлять виртуальные
машины на базе АRМ, потому что эти платформы обеспечивают более дешевые
вычисления.
В результате нам приходится собирать и сопровождать образы, предназначенные
для нескольких архитектур. Как обслуживать единую оптимизированную кодовую
базу и конвейер сборки и при этом поддерживать различные целевые платформы?
К счастью, Docker выпустил плагин для Docker CLI - buildx, который помогает
упростить этот процесс. Обычно docker-buildx уже установлен в системе. Мы можем
это проверить:
$ docker buildx version
githuЬ.com/docker/buildx v0 .9.1 ed00243a0ce2a0aee75311b06e32d33b44729689
Если нужно установить плагин, следуйте инструкциям из репозитория GitHub
� (https://github.com/docker/buildx#installing).
-
По умолчанию docker-buildx использует виртуализацию на базе QEМU (https://
www.qemu.org/) и Ьinfint_misc (https://docs.kernel.org/admin-guide/Ьinfmt-misc.html)
для поддержки архитектур, которые отличаются от базовой системы. Скорее всего,
в Linux уже есть все необходимое, но на всякий случай выполним следующую
команду при создании сервера Docker, чтобы убедиться, что файлы QEМU зареги­
стрированы корректно и актуальны:
$ docker container run --rm --privileged multiarch/qemu-user-static \
--reset -р yes
Setting /usr/bin/qemu-alpha-static as binfmt interpreter for alpha Setting /usr/bin/
qemu-arm-static as binfmt interpreter for arm Setting /usr/bin/qemu-armeb-static as binfmt
interpreter for armeb
Setting /usr/bin/qemu-aarch64-static as binfmt interpreter for aarch64 Setting
/usr/bin/qemu-aarch64_be-static as binfmt interpreter for aarch64_be
В отличие от встроенного функционала сборок Docker, который выполняется
напрямую на сервере, BuildКit использует при сборке образов контейнер сборки,
который предоставляет дополнительную гибкость. Далее мы создадим контейнер
buildx по умолчанию с именем builder.
�
._
Если у вас уже есть контейнер buildx с этим именем, удалите его командой docker
buildx rm builder или измените имя в последующей команде docker buildx create.
Книги для программистов: https://clcks.ru/books-for-it
108
Глава 4
Следующие две команды создают контейнер сборки, задают его по умолчанию,
и запускают:
$ docker buildx create --narne builder --driver docker-container --use builder
$ docker buildx inspect --bootstrap [+] Building 9.6s (1/1) FINISHED
=> [internal] booting buildkit
9.6s
=> => pulling image moby/buildkit:buildx-staЬle-1
8.6s
=> => creating container buildx_buildkit_builder0
0.9s
Narne:
builder
Driver:
docker-container
Nodes:
builder0
Narne:
Endpoint: unix:///var/run/docker.sock
running
Status:
Buildkit: v0.10.5
Platforms: linux/amd64, linux/amd64/v2, linux/arm64, linux/riscv64,
linux/ppc64le, linux/s390x, linux/386, linux/mips64le,
linux/mips64, linux/arm/v7, linux/arm/vб
В этом примере давайте загрузим репозиторий Git wordchain, который содержит
полезный инструмент для генерации случайных: и детерминированных: последова­
тельностей слов для динамического именования:
$ git clone https://githuЬ.com/spkane/wordchain.git \
--config core.autocrlf=input
$ cd wordchain
Если мы внимательнее посмотрим на Dockerfile, то увидим вполне обычный много­
этапный Dockerfile, в котором нет ничего, что связано с архитектурой платформы:
FRQf golang:1.18-alpineЗ.15
AS build
RIJN apk --no-cache add \
bash \
gcc \
musl-dev \
openssl
� CGO ENAВLED=0
СОРУ . /build
1ЮRIO>IR /build
RIJN go install githuЬ.com/markЬates/pkger/cmd/pkger@latest && \
pkger -include /data/words.json && \
go build .
FRQf alpine:3.15 AS deploy
1ЮRIO>IR /
СОРУ --from=build /build/wordchain /
USER 500
EXPOSE 8080
Dl'l'RYIOINТ ["/wordchain"]
а,ю ["listen"]
Книги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
109
На первом шаге мы собираем статически компилируемый двоичный файл Go, а на
втором - упаковываем его в небольшой образ развертывания.
Инструкция ENTRYPOINT в Dockerfile позволяет отделить процесс по умолчанию, вы­
полняемый контейнером, (ENTRYPOINT) от аргументов командной строки, которые
передаются этому процессу (смо). Если ENTRYPOINT отсутствует в Dockerfile, инст­
� рукция смо должна содержать и процесс, и все необходимые аргументы командной
строки.
Давайте соберем этот образ и загрузим его в локальный сервер Docker с помощью
следующей команды:
$ docker buildx build --tag wordchain:test --load
[+] Building 2.4s (16/16) FINISHED
=> [internal] load .dockerignore
=> => transferring context: 93В
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 461В
0.0s
0.0s
0.0s
0.0s
0.3s
=> exporting to oci image format
0.0s
=> => exporting layers
=> => exporting manifest sha256:4bdl971f2ed820b4f64ffda97707c27aac3e8eb7
0.0s
=> => exporting config sha256:ce8f8564bf53b283d486bddeb8cbb074ff9a9d4ce9
0.0s
0.2s
=> => sending tarball
0.0s
=> importing to docker
Протестируем образ с помощью следующих команд:
$ docker container run wordchain:test random
witty-stack
$ docker container run wordchain:test random -1 3 -d .
odd.goo
$ docker container run wordchain:test --help
wordchain is an application that can generate а readaЫe chain
of customizaЫe words for naming things like
containers, clusters, and other objects.
Если вы получили случайные пары слов после выполнения первых двух команд,
все работает правильно.
Чтобы собрать этот образ для нескольких архитектур, нужно просто добавить
к сборке аргумент --platform.
В обычной ситуации мы также заменили бы --load на --push, чтобы все получив­
шиеся образы были отправлены в указанный репозиторий, но в данном случае мы
просто удалим --load, потому что сервер Docker не может загружать образы для
нескольких платформ одновременно и у нас нет репозитория, куда можно было бы
Книги для программистов: https://clcks.ru/books-for-it
110
Глава 4
отправлять эти образы. Если бы репозиторий был и мы корректно пометили обра­
зы, то могли бы легко собрать и отправить итоговые образы за один шаг примерно
такой командой:
docker buildx build --platfoпn linux/amd64,linux/aпn64 --tag docker.io/spkane/
wordchain:latest --push.
Этот образ можно собрать для платформ linux/amd64 и linux/arm64:
$ docker buildx build --platfoпn linux/amd64,linux/aпn64 \
--tag wordchain:test .
[+] Building 114.9s (23/23) FINISHED
=> [linux/апn64 internal] load metadata for docker.io/library/alpine:3.1 2.7s
=> [linux/amd64 internal] load metadata for docker.io/library/alpine:3.1 2.7s
=> [linux/aпn64 internal] load metadata for docker.io/library/golang:1.1 3.0s
=> [linux/amd64 internal] load metadata for docker.io/library/golang:1.1 2.8s
=> CACHED [linux/amd64 build 5/5] RUN go install githuЬ.com/markЬates/pk 0.0s
=> CACHED [linux/amd64 deploy 2/3] СОРУ --from=build /build/wordchain / 0.0s
=> [linux/aпn64 build 5/5) RUN go install githuЬ.com/markЬates/pkger/c 111.7s
=> [linux/aпn64 deploy 2/3] СОРУ --from=build /build/wordchain /
0.0s
WARNING: No output specified with docker-container driver. Build result will
oniy remain in the build cache. То push result image into registry
use --push or to load image into docker use --load
�
При сборке образов для других архитектур требуется эмуляция, поэтому некото­
рые шаги выполняются дольше обычного. Это нормально, учитывая дополнитель­
ные издержки на эмуляцию.
Мы можем настроить Docker таким образом, чтобы он собирал каждый образ на
рабочем узле с соответствующей архитектурой, - во многих ситуациях это значи­
тельно ускорит процесс. Подробнее об этом читайте в этой статье из блога
о Docker (https://www.docker.com/Ыog/speed-up-building-with-docker-buildx-and­
graviton2-ec2).
В выводе сборки мы увидим строки, начинающиеся с => [linux/amd64 *J или
=> [linux/aпn64 *J. Каждая из этих строк представляет процесс сборки на этом шаге
для соответствующей платформы. Многие из этих шагов будут выполняться парал­
лельно, и из-за кеширования и других факторов каждая сборка будет выполняться
со своей скоростью.
Поскольку мы не добавили в сборку --push, в конце мы увидим предупреждение.
Дело в том, что драйвер docker-container, который используется в ходе сборки, про­
сто оставил все в кеше сборки - мы не можем запустить получившиеся образы
и остается только надеяться, что все работает.
Существует несколько аргументов build (https://docs.docker.com/engine/
reference/builder/#automatic-platform-args-in-the-global-scope), которые автома­
тически задаются Docker и могут использоваться в Dockerfile при сборках для
разных архитектур. Например, TARGETARCH позволяет убедиться, что определенКниги для программистов: https://clcks.ru/books-for-it
Работа с образами Docker
1
111
ный шаг сборки загружает корректный подготовленный двоичный файл для
текущей платформы образа.
Когда мы отправляем образ в репозиторий, как Docker узнает, какой образ следует
использовать для локальной платформы? Эта информация указывается на сервере
Docker с помощью манифеста образа. Мы можем изучить манифест для
docker.io/spkane/workdchain, выполнив следующую команду:
$ docker manifest inspect docker.io/spkane/wordchain:latest
{
"schemaVersion": 2,
"media'l'ype": 1 application/vnd.docker. distribution.manifest.list.v2+json11
"manifests": [
1
,
"media'l'ype": application/vnd.docker.distribution.manifest.v2+json",
739,
"diqest": "sha256: 4Ьdl ...ЬfсО",
"platform": {
"architecture": "amd64",
linuxtt
"0s 11
11
"size":
:
11
),
"platform":
"architecture":
11
arm6411
,
"os": "linux"
),
В выводе мы видим блоки, в которых определен образ, необходимый для каж­
дой поддерживаемой платформы. Для этого используются отдельные записи
digest (https://github.com/opencontainers/image-spec/ЫoЬ/main/descriptor.md#digests),
объединяемые с блоком platform. Этот файл манифеста загружается сервером, когда
ему требуется образ. Сервер ищет в манифесте, какой образ требуется для локаль­
ной платформы, и загружает его. Поэтому наш Dockerfile вообще работает. Каждая
строка FROМ соответствует базовому образу, а сервер Docker обращается к файлу ма­
нифеста, чтобы определить нужный образ для каждой платформы, для которой
предназначена сборка.
Заключение
Мы подробно рассмотрели создание образов для Docker и теперь хорошо представ­
ляем себе, какие инструменты и возможности помогут нам оптимизировать конвей­
ер сборки. В следующей главе мы поговорим о том, как с помощью образов созда­
вать контейнеризированные процессы для проектов.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 5
Работа с контейнерами
В предыдущей главе мы узнали, как создать образ Docker и как запустить из него
контейнер. В этой главе мы сначала рассмотрим историю появления контейнеров, а
затем перейдем к подробностям работы контейнеров и изучим команды Docker для
управления конфигурацией, ресурсами и привилегиями контейнера.
Что такое контейнер?
Скорее всего, вы знакомы с системами виртуализации, вроде VMware или КУМ,
с помощью которых мы можем запускать полноценное ядро Linux и операционную
систему поверх слоя виртуализации - гипервизора. Такой подход обеспечивает
хорошую изоляцию рабочих нагрузок, поскольку у каждой виртуальной машины
есть свое ядро операционной системы, которое находится в отдельном пространст­
ве памяти поверх слоя аппаратной виртуализации.
Контейнеры основаны на других механизмах, ведь они используют одно общее
ядро, в котором и реализуется изоляция между рабочими нагрузками. Это называ­
ется виртуализацией операционной системы.
В libcontainer READМE приводится емкое определение контейнера (https://
github.com/opencontainers/runc/ЬloЬ/main/libcontainer/READМE.md).
Контей­
нер - это самодостаточная среда выполнения, которая использует ядро хост­
системы и может работать изолированно от других контейнеров в системе.
Одно из главных преимуществ контейнеров - эффективность по ресурсам, потому
что нам не требуется целый экземпляр операционной системы для каждой изолиро­
ванной рабочей нагрузки. Ядро общее, а значит, между задачей и оборудованием
находится меньше слоев. Когда процесс выполняется внутри контейнера, нам нуж­
но совсем немного кода в ядре, чтобы управлять этим контейнером. Сравните этот
подход с виртуальной машиной, где требуется дополнительный слой. В виртуаль­
ной машине вызовы от процесса к оборудованию или гипервизору требуют дважды
переходить в привилегированный режим процессора, поэтому многие вызовы вы­
полняются значительно медленнее.
Libcontainer - это библиотека Go, которая предоставляет стандартный интерфейс
для управления контейнерами Linux из приложений
(https://github.com/opencontainers/runc/tree/main/libcontainer).
При использовании контейнеров мы можем выполнять только процессы, совмести­
мые с ядром. Например, в отличие от аппаратной виртуализации через VMware,
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
1
113
КУМ и подобные технологии, приложения Windows не могут нативно вьmолняться
в контейнере Linux на хаете Linux. Зато приложения Windows могут выполняться
в контейнерах Windows на хаете Windows. Контейнер зависит от операционной
системы, и мы можем запускать в нем только приложения или демоны, совмести­
мые с ядром сервера. При работе с контейнерами постарайтесь забыть все, что вы
знали о виртуальных машинах. Представьте себе контейнер в виде оболочки вокруг
обычного процесса, выполняющегося на сервере.
Контейнеры можно выполнять на виртуальных машинах, а виртуальные машины
можно легко запускать в контейнерах. При таком подходе мы можем выполнять
приложение Windows на виртуальной машине Windows, запущенной внутри кон­
тейнера Linux.
История контейнеров
Нередко революционная технология на самом деле оказывается не такой уж и но­
вой - просто ее время наконец пришло. Технологии циклично возвращаются,
и некоторые идеи из 1960-х годов сегодня снова в моде. Например, Docker появил­
ся недавно и сразу стал хитом благодаря своей простоте, но он возник не в вакууме.
Многие механизмы, на которых работает Docker, развивались в течение 30 лет си­
лами специалистов в нескольких областях. Мы легко можем проследить концепту­
альную эволюцию контейнеров от простого системного вызова, добавленного
в ядро Unix в конце 1970-х, до современных инструментов работы с контейнерами,
применяемых такими технологическими гигантами, как Google, Twitter и Meta.
Стоит отдельно изучить развитие технологий, которое привело к созданию
Docker, - это поможет нам вписать его в контекст других платформ и инструмен­
тов, с которыми вы уже знакомы.
Сама по себе идея контейнеров не нова, ведь это просто способ изолировать и ин­
капсулировать часть работающей системы. Самая старая технология в этой сфере
включает первые системы пакетной обработки. В первых компьютерах система вы­
полняла по одной программе за раз и переходила к следующей программе, когда
завершалась предыдущая или истекал заданный период времени. Такой подход
подразумевал вынужденную изоляцию: программы не делили ресурсы, потому что
всегда выполнялись по одной. Современные компьютеры тоже переключаются
между задачами, но это происходит очень быстро и совершенно не заметно для
большинства пользователей.
Можно сказать, что семена современных контейнеров были посажены еще
в 1979 году, когда в Unix версии 7 добавили системный вызов chroot, который по­
зволяет ограничить доступную процессу часть дерева базовой файловой системы.
Системный вызов chroot защищает операционную систему от недоверенных сер­
верных процессов, таких как FTP, BIND и Sendrnail, которые открыты для общего
доступа и уязвимы для компрометации.
В 1980-1990-х бьmи созданы различные варианты Unix с мандатным контролем
доступа из соображений безопасности 1• Это означало, что на одном ядре Unix вы1
Современная реализация - SELinux.
Книги для программистов: https://clcks.ru/books-for-it
114
1
Глава 5
полнялось несколько строго контролируемых доменов. Процессы в каждом домене
имели ограниченный доступ к системе и не могли взаимодействовать с процессами
в других доменах. В популярном коммерческом дистрибутиве Unix был реализован
межсетевой экран Sidewinder поверх BSDI Unix, но большинство распространен­
ных реализаций Unix не поддерживало такой подход.
Все изменилось в 2000 году с выпуском FreeBSD 4.0, где была представлена новая
команда jail. Она позволяла хостинг-провайдерам безопасно разделять в общей
среде свои процессы и процессы каждого клиента. Команда FreeBSD jail дополня­
ла возможности chroot и также ограничивала все, что мог делать процесс с базовой
системой и другими изолированными процессами.
В 2004 году Sun выпустили раннюю сборку Solaris 1 О, которая включала контейне­
ры Solaris, позже реализованные в виде технологии Solaris Zones. Это была первая
крупная коммерческая реализация технологии контейнеризации, и она по­
прежнему используется. В 2005 году компания Virtuozzo представила OpenVZ for
Linux, а в 2007 году появилось решение НР Secure Resource Partitions for НР-UХ,
позже переименованное в НР-UХ Containers.
Компании вроде Google, которые масштабируют приложения для всего Интернета
и предоставляют хостинг для недоверенного пользовательского кода, поддержива­
ли развитие технологии контейнеризации с начала 2000-х, чтобы надежно и безо­
пасно распределять приложения по центрам обработки данных по всему миру. Не­
которые компании дорабатывали ядра Linux, реализуя поддержку контейнеров для
внутреннего использования, но когда потребность в этих функциях стала очевид­
ной внутри сообщества Linux, Google предложил свои вспомогательные контейне­
ры для мейнлайнового ядра Linux, и в 2008 году были выпущены контейнеры Linux
(Linux Containers, LXC) в версии 2.6.24 ядра Linux. Невероятный рост популярно­
сти контейнеров Linux в сообществе начался только в 2013 году, когда в версию 3.8
ядра Linux были включены пользовательские пространства имен, а месяц спустя
вышел Docker.
Сегодня контейнеры распространены повсеместно. Образы Docker и OCI предос­
тавляют формат упаковки для огромного множества программ, поставляемых в ра­
бочие среды, а также предлагают основу для таких систем, как Kubernetes и бессер­
верные облачные технологии.
Так называемые бессерверные технологии на самом деле работают на серве­
рах - просто не на ваших, а на чьих-то других, так что вам, как владельцу прило­
жения, не приходится управлять оборудованием и операционной системой.
Создание контейнера
До этого мы запускали контейнеры с помощью удобной команды docker container
run. На самом деле docker container run объединяет два шага, и на первом мы создаем
контейнер из базового образа. Эту задачу можно выполнить отдельно командой
docker container create. Вторая задача docker container run - выполнение контейнера,
и это тоже можно сделать отдельно командой docker container start.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
1
115
Команды docker container create и docker container start поддерживают любые аргу­
менты, относящиеся к первой настройке контейнера. В главе 4 мы видели, что
в команде docker container run можно сопоставлять сетевые порты в контейнере
с хостом с помощью аргумента -p/--puЫish, а -e/--env позволяет передавать в кон­
тейнер переменные среды.
И это лишь малая часть возможностей для конфигурации контейнера при первом
создании. Давайте рассмотрим некоторые возможности, доступные с docker.
Базовая конфигурация
Давайте узнаем, как сообщить Docker желаемую конфигурацию контейнера при
создании.
Имя контейнера
Контейнер создается из базового образа, но на итоговую конфигурацию можно
повлиять с помощью различных аргументов командной строки. Параметры
в Dockerfile всегда задаются как значения по умолчанию, но мы можем переопре­
делить их при создании.
По умолчанию Docker присваивает контейнеру случайное имя (https://github.com/
moby/moby/ЬloЬ/master/pkg/namesgenerator/names-generator.go), состоящее из
прилагательного и имени какого-нибудь известного человека. Например, ecstatic­
babbage (восторженный Бэббидж) или serene-albattani (безмятежный Аль-Батани).
Присвоить контейнеру другое имя можно с помощью аргумента --name:
$ docker container create --name= "awesome-service" uЬuntu:latest \
sleep 120
После создания контейнер можно запустить командой docker container start awesome­
service. Он автоматически завершится через 120 секунд, но мы можем остановить
его принудительно: docker container stop awesome-service. Позже в этой главе мы рас­
смотрим эти команды.
В пределах хоста Docker у контейнеров должны быть уникальные имена. Если мы
запустим предыдущую команду два раза подряд, получим ошибку. Нужно сначала
удалить предыдущий контейнер командой docker container rm или переименовать
новый контейнер.
Метки
Как было сказано в главе 4, метки представляют собой пары "ключ-значение", ко­
торые можно добавлять к образам и контейнерам Docker в качестве метаданных.
При создании новые контейнеры Linux автоматически наследуют все метки из
родительского образа.
Мы также можем добавлять в контейнеры новые метки, чтобы применять метадан­
ные, относящиеся к одному контейнеру:
Книги для программистов: https://clcks.ru/books-for-it
Глава 5
116
$ docker container run --rm -d --name has-some-labels \
-1 deployer=Ahmed -1 tester=Asako \
uЬuntu:latest sleep 1000
Мы можем искать и фильтровать контейнеры по этим метаданным с помощью
таких команд, как docker container 1s:
$ docker container 1s -а -f label=deployer=Ahmed
CONTAINER ID IМAGE
СОММАND
NAМES
845731631Ьа4 uЬuntu:latest "sleep 1000" ... has-some-laЬels
Команда docker container inspect выводит все метки контейнера:
$ docker container inspect has-some-laЬels
11
LaЬels 11 :
{
"deployer": "Ahmed",
"tester": "Asako"
),
Этот контейнер выполняет команду sleep 1000, так что через 1 ООО секунд он остано­
вится.
Имя хоста
По умолчанию при запуске контейнера Docker копирует некоторые системные
файлы на хосте, включая /etc/hostname, в каталог конфигурации контейнера на хос­
те2, а затем при помощи binct mount связывает эту копию файла с контейнером. Мы
можем запустить контейнер по умолчанию без специальной конфигурации:
$ docker container run --rm -ti uЬuntu:latest /bin/bash
Здесь команда docker container run запускает docker container create И docker container
start в фоновом режиме. Нам нужна возможность взаимодействовать с контейне­
ром, который мы создадим для демонстрации, поэтому мы передадим несколько
полезных аргументов. Аргумент --rm указывает Docker удалить контейнер при за­
вершении работы, аргумент -t велит выделить псевдо-ТТУ, а аргумент -i означает,
что сеанс будет интерактивным и мы хотим оставить поток STDIN открытым. Если
в образе не определена точка входа ENTRYPOINT, то последним аргументом в команде
будет исполняемый файл и аргументы командной строки, которые должны выпол­
няться в контейнере. В нашем случае это будет неизменный /Ьin/bash. Если в образе
определена ENTRYPOINT, финальный аргумент передается в процесс ENTRYPOINT в виде
списка аргументов командной строки для этой команды.
Возможно, вы заметили, что мы говорим об аргументах -i и -t, а в команде указан
аргумент -ti. Это связано с историей Unix. Если интересно, прочтите короткую
справку (https://nullprogram.com/Ыog/2020/08/01 ).
2
Обычно по пуrи /var/liЬ/docker/coпtaiпers.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
117
Если мы сейчас выполним команду mount в итоговом контейнере, то увидим что-то
подобное:
root@eЬc8cf2d8523:/1 mount
overlay оп/ type overlay (rw,relatime,lowerdir= ...,upperdir= ...,workdir ...)
proc оп/proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs оп/dev type tmpfs (rw,nosuid,mode=755)
shm оп/dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=б5536k)
mqueue оп/dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
devpts оп/dev/pts type devpts (rw,nosuid,noexec,relatime, ...,ptmxmode=ббб)
sysfs оп/sys type sysfs (ro,nosuid,nodev,noexec,relatime)
/dev/sda9 оп/etc/resolv.conf type ext4 (rw,relatime,data=ordered)
/dev/sda9 оп/etc/hostname type ext4 (rw,relatime,data=ordered)
/dev/sda9 оп/etc/hosts type ext4 (rw,relatime,data=ordered)
devpts оп/dev/console type devpts (rw,nosuid,noexec,relatime, ...,ptmxmode=OOO)
proc оп/proc/sys type proc (ro,nosuid,nodev,noexec,relatime)
proc оп /proc/sysrq-trigger type proc (ro,nosuid,nodev,noexec,relatime)
proc оп/proc/irq type proc (ro,nosuid,nodev,noexec,relatime)
proc оп/proc/bus type proc (ro,nosuid,nodev,noexec,relatime)
tmpfs оп/proc/kcore type tmpfs (rw,nosuid,mode=755)
root@eЬc8cf2d8523:/I
Если в строке с примером вы видите что-то вроде root@hashID, команда выполня­
ется в контейнере, а не на локальном хосте.
�
Иногда для контейнера настроено другое имя хоста (например, с помощью аргу­
мента --name в CLI), но по умолчанию это хеш идентификатора (ID hash) контей­
нера.
Также мы можем изменить пользователя в контейнере с помощью --user, но по
умолчанию это будет root.
В контейнере несколько привязок Ьind mount, но мы обратим внимание на следую­
щую строку:
/dev/sda9 оп/etc/hostname type ext4 (rw,relatime,data=ordered)
Номера устройства для контейнеров будут различаться, но нас интересует точка
монтирования - /etc/hostname. В контейнере /etc/hostname связывается с файлом
имени хоста, который Docker подготовил для этого контейнера. По умолчанию он
содержит идентификатор контейнера и не имеет полного доменного имени.
Мы можем проверить это в контейнере, выполнив следующую команду:
root@eЬc8cf2d8523:/1 hostname -f
ebc8cf2d8523
root@eЬc8cf2d8523:/# exit
Когда закончите, не забудьте выйти (exit) из командной оболочки контейнера, что­
� бы вернуться на локальный хост.
�
Книги для программистов: https://clcks.ru/books-for-it
118
Глава 5
Чтобы задать имя хоста, можно указать аргумент --hostname и передать нужное зна­
чение:
$ docker container run --rm -ti --hostname= "mycontainer.example.com" \
uЬuntu:latest /Ыn/bash
В контейнере мы увидим, что полное имя хоста определено, как мы запросили:
root@mycontainer:/# hostname -f
mycontainer.example.com
root@mycontainer:/# exit
DNS
Как и /etc/hostname, файл r esolv.conf, настраивающий разрешение доменных имен
(Domain Name Syste m, DNS), управляется через Ыnd mount между хостом и контей­
нером:
/dev/sda9 on /etc/resolv.conf type ext4 (rw,relatime,data= ordered)
�
�
Подробнее о файле resolv.conf можно почитать здесь:
https://sslhow.com/understanding-etc-resolv-conf-file-in-linux.
По умолчанию это точная копия файла resolv.conf хоста Docker. Если вас это не
устраивает, с помощью аргументов --dns и --dns-search переопределите это поведе­
ние в контейнере:
$ docker container run --rm -ti --dns=8.8.8.8 --dns=8.8.4.4 \
--dns-search=exaшplel.com --dns-search=example2.com \
uЬuntu:latest /Ьin/bash
� Есл, вы не ,а,,.е нас;раива;s аоис,овый домен, у,аж"е --dns-seшh•.
В контейнере останется привязка Ыnd mount, но содержимое файла больше не будет
повторять файл resolv.confxocт a, а станет выглядеть следующим образом:
root@Of887071000a:/# more /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4
search examplel.com example2.com
root@Of887071000a:/# exit
МАС-адрес
Мы можем настроить для контейнера МАС-адрес.
Если значение не задано, контейнер получит вычисленный МАС-адрес, который
начинается с префикса 02: 42: ас: 11.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
1
119
При желании -мы можем задать это значение:
$ docker container run --rm -ti --mac-address="a2:11:aa:22:bb:33" \
uЬuntu:latest /Ыn/bash
Как правило, в этом нет необходимости, но иногда мы хотим зарезервировать на­
бор МАС-адресов для контейнеров, чтобы избежать конфликтов с другими слоями
виртуализации, которые используют тот же блок частных адресов, что и Docker.
Будьте осторожны при настройке МАС-адресов. Если две системы объявят один
МАС-адрес, возникнет конфликт, связанный с ARP. Если задать адрес все же не­
обходимо, постарайтесь сделать так, чтобы ваши локальные диапазоны находи­
лись в пределах официальных, например х2-хх-хх-хх-хх-хх, хб-хх-хх-хх-хх- хх, хАхх-хх-хх-хх-хх и хЕ-хх-хх-хх-хх-хх (где х - любой допустимый шестнадцатерич­
ный символ).
Тома хранилища
Иногда нам недостаточно дискового пространства, выделенного контейнеру по
умолчанию, или контейнер не должен быть "эфемерным", и тогда потребуется хра­
нилище, чтобы данные сохранялись между развертываниями.
Обычно не рекомендуется подключать хранилище с хоста Docker, потому что кон­
тейнер будет привязан к определенному хосту, на котором хранится его состояние.
Но для временных фi:iйлов кеша или полуэфемерных состояний этот вариант мо­
жет подойти.
В таких случаях используйте команду --mount/-v для подкmочения каталогов и от­
дельных файлов с сервера хоста к контейнеру. В аргументе --mount/-v необходимо
указать полный путь. В следующем примере /mnt/session_data подключается к /data
в контейнере:
$ docker container run --rm -ti \
--mount type=Ыnd,target=/mnt/session_data,source=/data \
uЬuntu:latest /Ьin/bash
root@Of887071000a:/t mount I grep data
/dev/sda9 on /data type ext4 (rw,relatime,data=ordered)
root@Of887071000a:/t exit
Для Ыnd mount можно использовать аргумент -v, чтобы сократить команду. При
этом вы заметите, что исходные и целевые файлы и каталоги разделены двоето­
чием(:).
Важно ртметить, что тома по умолчанию монтируются в режиме чтения и записи.
Мы можем указать doaker, что файл или каталог нужно примотировать только для
чтения, добавив readonl у после аргументов --mount или указав : ro после аргумен­
тов -v:
$ docker container run --rm -ti \
-v /mnt/session data:/data:ro \
uЬuntu:latest /Ыn/bash
Книги для программистов: https://clcks.ru/books-for-it
120
Глава 5
Точки монтирования на хосте и в контейнере не обязательно создавать заранее.
Если точка монтирования на хосте еще не существует, она будет создана в виде
каталога. Если мы пытаемся указать на файл, а не каталог, могут возникнуть про­
блемы.
В параметрах монтирования мы видим, что файловая система успешно подключена
для чтения и записи по пути /data, как и ожидалось.
SELinux и подключение томов
Если на хосте Docker включен SELinux, то при попытке примонтировать том к контейне­
ру может возникнуть ошибка из-за отсутствия прав доступа. Чтобы решить проблему,
укажите z в команде Docker для подключения томов:
• Опция z (в нижнем регистре) указывает, что содержимое bind mount является общим
для нескольких контейнеров.
• Опция z (в верхнем регистре) указывает, что содержимое Ьind mount доступно только
этому контейнеру.
Если том будет общим для контейнеров, укажите z при подключении контей­
нера:
$ docker container run --rm -v /app/dhcpd/etc:/etc/dhcpd:z dhcpd
т
Лучше, конечно, указать в команде подключения тома параметр z, чтобы задать для
директории ту же метку MCS (например, chcon ... -1 s0:cl,c2}, которую будет использо­
вать контейнер. Это позволит обеспечить самый высокий уровень безопасности, потому
что к тому будет доступ только у одного контейнера:
$ docker container run --rm -v /app/dhcpd/etc:/etc/dhcpd:Z dhcpd
Будьте крайне осторожны с параметрами z/Z. Если мы попытаемся с помощью
Ьind mount примонтировать системный каталог, например /etc или /var, указав па­
раметр z, скорее всего, мы не сможем дальше работать с системой, и придется
использовать инструменты SELinux, чтобы вручную сменить метки на хост-машине
(https://www.thegeekdiary.com/understanding-selinux-file-labelling-and-selinuxcontext).
Если контейнерное приложение будет записывать данные в каталог /data, то эти
данные будут видны в файловой системе хоста в /mлt/session_data, и они будут дос­
тупны, даже когда этот контейнер остановится и запустится новый с тем же под­
ключенным томом.
Мы можем указать Docker, что корневой том контейнера должен подключаться
в режиме "только для чтения", чтобы процессы в контейнере ничего не могли запи­
сывать в корневую файловую систему. В этом случае файлы журнала и другие дан­
ные, о которых разработчик может не знать, не будут заполнять выделенное кон­
тейнеру место на диске в рабочей среде. При таком подходе мы сможем гарантиро­
вать, что данные будут записываться только в ожидаемые расположения.
В предыдущем примере для этого достаточно было добавить к команде аргумент
--read-only=true:
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
121
$ docker container run --rm -ti --read-only=true -v /mnt/session data:/data \
uЬuntu:latest /bin/bash
root@df542767Ьc17:/# mount I grep " /"
overlay on / type overlay (ro,relatime,lowerdir=...,upperdir=...,workdir=... )
root@df542767Ьc17:/# mount I grep data
/dev/sda9 on /data type ext4 (rw,relatime,data=ordered)
root@df542767Ьc17:/# exit
Если внимательно изучить параметры подключения для корневого каталога, можно
заметить параметр ro, который задает доступ только для чтения. Однако папка
/session_data все еще подключена с параметром rw, так что приложение будет запи­
сывать данные в тот том, запись в который разрешена.
Иногда мы хотим записывать данные в каталог /tmp или подобный, даже если ос­
тальное предоставляется только для чтения. В этом случае можно добавить аргу­
мент --mount type=tmpfs в команду docker container run, чтобы подключить файловую
систему tmpfs к контейнеру. Файловые системы tmpfs целиком находятся в памяти.
Они очень быстрые, но временные (т. е. содержимое не сохраняется при переза­
грузке), и потребляют дополнительную системную память. При остановке контей­
нера все данные в каталогах tmpfs будут также утеряны. В следующем примере по­
казан контейнер с файловой системой tmpfs на 256 МБ, подключенный к каталогу
/tmp:
$ docker container run --rm -ti --read-only=true \
--mount type=tmpfs,destination=/tmp,tmpfs-size= 256M \
uЬuntu:latest /bin/bash
rооt@25Ь4f3632ЬЬс:/# df -h /tmp
Used Avail Use%
Mounted оп
Filesystem
Size
256М
0%
256М
О
/tmp
tmpfs
rооt@25Ь4f3632ЬЬс:/# grep /tmp /etc/mtaЬ
tmpfs /tmp tmpfs rw,nosuid,nodev,noexec,relatime,size=262144k О О
rооt@25Ь4f3632ЬЬс:/# exit
�
По возможнос,и ,онтейнеры стоит ороекп,роватъ та,им образом, чтобы они не
сохраняли состояние. Необходимость в хранилище приводит к нежелательным
зависимостям и существенно усложняет сценарии развертывания.
Квоты на ресурсы
"Шумный сосед" (noisy neighbor) - одна из главных проблем, которые возникают
при работе в облаке. "Шумным соседом" называют приложение, которое выполня­
ется в той же физической системе, что и ваше приложение, и мешает его работе,
захватывая много ресурсов.
При использовании виртуальных машин мы строго контролируем выделение памя­
ти, ЦП и других ресурсов. С Docker приходится задействовать функционал cgroup
в ядре Linux, чтобы ограничивать ресурсы, доступные контейнеру Linux. Команды
docker container create и docker container run позволяют при создании контейнера наКниги для программистов: https://clcks.ru/books-for-it
122
Глава 5
прямую настраивать лимиты по ЦП, памяти, подкачке (swap) и вводу-выводу хра­
нилища.
Если нужно будет изменить эти параметры после создания контейнера, можно вы­
полнить команду docker container update или развернуть новый контейнер с новы­
ми значениями.
Правда, нужно помнить, что хотя Docker и поддерживает лимиты на ресурсы, эти
возможности должны быть включены в ядре. Возможно, необходимо будет доба­
вить их как параметры командной строки к ядру при запуске. Чтобы узнать, под­
держивает ли ваше ядро эти лимиты, выполним команду docker system info. Если
что-то не поддерживается, мы увидим предупреждение:
WARNING: No swap limit support
(ПРЕДУПРЕЖДЕНИЕ: лимиты на подкачку не поддерживаются)
lЖ
�
Настройка поддержки возможностей в cgroup для ядра зависит от дистрибутива.
3
Подробности см. в документации Docker .
Доля процессорного времени
В Docker есть несколько способов ограничить потребление процессорных ресурсов
для приложений в контейнерах. Давний и все еще популярный метод - доли про­
цессорного времени (CPU shares). Мы рассмотрим и другие варианты.
Вычислительная мощность всех процессорных ядер в системе представляет собой
весь пул долей. В Docker пул делится на 1024 доли. Настраивая доли процессорно­
го времени контейнера, мы можем указывать, сколько времени следует выделить
контейнеру. Если мы хотим отдать контейнеру половину вычислительной мощно­
сти системы, можно выделить 512 долей. Мы не запрещаем другим процессам ис­
пользовать эти доли - если мы назначим все 1024 доли одному контейнеру, это не
значит, что остальные контейнеры не смогут выполняться. Мы скорее указываем
планировщику, сколько времени каждый контейнер может выполняться при каж­
дом запуске. Если мы назначили одному контейнеру 1024 доли (по умолчанию)
и еще двум - по 512, все они будут запланированы одинаковое число раз. Но если
обычное процессорное время для каждого процесса составляет 100 мкс, контейне­
ры, которым мы выделили по 512 долей, будут выполняться по 50 мкс, а контейнер
с 1024 долями получит стандартные l 00 мкс.
Давайте посмотрим, как это будет работать на практике. Для следующих примеров
мы создадим новый образ Docker, который содержит команду stress (https://
Iinux.die.net/man/1/stress), чтобы задействовать все ресурсы системы.
3 Полный URL: https://docs.docker.com/engine/instalVlinux-postinstalV#your-kernel-does-not-support­
cgroup-swap-limit-capabllities.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
123
Если выполнить stress без ограничений cgroup, то будет потребляться столько ре­
сурсов, сколько мы укажем. Следующая команда создает среднюю нагрузку (load
average) около пяти: два процесса, ограниченных процессорным временем, один
процесс, ограниченный вводом-выводом, и два процесса, ограниченных памятью.
Во всех следующих примерах мы работаем в системе с двумя ЦП.
Здесь весь код после имени образа контейнера относится к команде stress, а не
docker:
$ docker container run --rm -ti spkane/train-os \
stress -v --cpu 2 --io 1 --vrn 2 --vrn-bytes 128М --timeout 120s
Эта команда будет адекватно выполняться на любом современном компьютере, но
нагрузка на хост-систему будет очень большой. Не выполняйте команду, если сис­
тема не справится с дополнительной нагрузкой или из-за нехватки ресурсов воз­
никнет сбой.
Если выполнить команду top или htop на хосте Docker, то ближе к концу двухми­
нутного выполнения вы заметите, как влияет на систему нагрузка, созданная про­
граммой stress:
$ top -bnl I head -n 15
top - 20:56:36 up 3 min, 2 users, load average: 5.03, 2.02, 0.75
Tasks: 88 total,
5 running, 83 sleeping,
О stopped,
О zomЬie
%Cpu(s): 29.8 us, 35.2 sy, О.О ni, 32.0 id, 0.8 wa, 1.6 hi, 0.6 si, О.О st
42716 buffers
KiB Mem:
1021856 total,
270148 used,
751708 free,
83764 cached Mem
KiB Swap:
О total,
О used,
О free.
PID USER
810 root
813 root
812 root
814 root
811 root
1 root
2 root
3 root
PR
20
20
20
20
20
20
20
20
NI
о
о
о
о
о
о
о
о
VIRT
7316
7316
138392
138392
7316
110024
о
о
RES
96
96
46936
22360
96
4916
о
о
SHR S
О R
О R
996 R
996 R
О D
3632 s
оs
оs
%CPU
44.3
44.3
31.7
31.7
25.3
о.о
о.о
о.о
%МЕМ
о.о
о.о
4.6
2.2
о.о
0.5
о.о
о.о
TIME+ COMМAND
О:49.63 stress
0:49.18 stress
0:46.42 stress
0:46.89 stress
0:21.34 stress
0:07.32 systemd
0:00.04 kthreadd
О:00.11 ksoftir
Пользователи Docker Desktop, которые работают не в Linux, заметят, что Docker
настраивает доступ к файловой системе только для чтения и не предлагает много
полезных инструментов для мониторинга виртуальной системы. Если вы хотите
� отслеживать потребление ресурсов различными процессами, попробуйте обойти
эту проблему так:
$ docker container run --rm -it --pid=host alpine sh
/ # apk update
/ # apk add htop
/ # htop -р $ (pgrep stress I tr '\n' ', ')
/ # exit
Книги для программистов: https://clcks.ru/books-for-it
124
Глава 5
Предыдущая команда htop выдаст ошибку, если при ее запуске не выполнятся
stress, потому что команда pgrep не вернет никакие процессы.
Необходимо выходить и запускать htop снова каждый раз при запуске нового эк­
земпляра stress.
Если вы хотите снова выполнить ту же команду stress, но уменьшив доступное
время ЦП вдвое, это возможно:
$ docker container run --rm -ti --cpu-shares 512 spkane/train-os \
stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М --timeout 120s
Флаг --cpu-shares 512 указывает, что этому контейнеру нужно выделить 512 долей
процессорного времени. Если система не очень загружена, мы можем не заметить
действие этого аргумента - контейнер, выполняющий задачи, будет Шiанировать­
ся на тот же отрезок времени, когда в системе достаточно ресурсов. В нашем слу­
чае результаты команды top в хост-системе будут выглядеть точно так же, если
только мы не запустим еще несколько контейнеров, которые тоже будут потреблять
процессорное время.
В отличие от виртуальных машин, в Docker ограничения по долям процессорного
времени на основе cgroup могут иметь неожиданные последствия. Это не жесткие
лимиты, а относительные, примерно как команда nice. В рассмотренном примере
запускается контейнер, которому выделили только половину процессорного времени, но система не слишком загружена. Поскольку ЦП не занят, лимит по времени
будет иметь ограниченный эффект - больше никто не претендует на ресурсы
в пуле планировщика. Если развернуть в той же системе второй контейнер, кото­
рому требуется много процессорного времени, мы сразу заметим, как действуют
ограничения для первого контейнера. Учитывайте эти факторы при распределении
и ограничении ресурсов.
Привязка к процессору
Мы можем привязать контейнер к одному или нескольким ядрам ЦП (CPU pinning),
и тогда он будет Шiанироваться только на этих ядрах. Это удобно, если мы хотим
зафиксировать разделение ЦП между приложениями или приложение по каким-то
причинам должно выполняться на определенном процессоре, например для эффек­
тивного использования кеша.
В следующем примере мы запускаем с утилитой stress контейнер, привязанный
к первым двум ЦП, выделив ему 512 долей процессорного времени:
$ docker container run --rm -ti \
--cpu-shares 512 --cpuset-cpus= O spkane/train-os \
stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М --timeout 120s
т
Аргумент --cpuset-cpus задает начало с индекса О, так что первым ядром будет
ядро О. Если мы укажем Docker использовать ядро ЦП, которого нет в хост­
системе, получим ошибку "Саппоt start container'' (Невозможно запустить контей­
нер). На хосте с двумя ядрами, это можно проверить, используя --cpuset-cpus=0-2.
Если снова выполнить top, мы заметим, что процент процессорного времени, по­
траченного в пространстве пользователя (user space, us), ниже, чем раньше, потому
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
125
что мы предоставили один ЦП двум процессам, требующим много процессорного
времени:
%Cpu(s): 18.5 us, 22.0 sy, О.О ni, 57.6 id, 0.5 wa, 1.0 hi, 0.3 si, О.О st
Когда мы привязываем контейнер к ЦП, дополнительные ограничения по процес­
сорному времени будут учитываться, только если на том же наборе ядер выпол­
няются другие контейнеры.
При использовании планировщика Completely Fair Scheduler (CFS) в ядре Linux
можно изменить квоту по процессорному времени для заданного контейнера, уста­
новив для флага --cpu-quota соответствующее значение при запуске контейнера
командой docker container run.
Упрощение квот на ресурсы процессора
Изначально для управления лимитами на ресурсы процессора в Docker использова­
лись доли процессорного времени, но с тех пор многое изменилось, и сейчас суще­
ствует гораздо более удобный способ. Вместо того чтобы самостоятельно задавать
доли и квоты, мы можем указать Docker, сколько ресурсов процессора следует пре­
доставить контейнеру, и система сама рассчитает значения, которые нужно задать
ДЛЯ cgroup.
Для команды -cpus можно задать число с плавающей запятой от 0.01 до количества
ядер ЦП на сервере Docker:
$ docker container run -rm -ti -cpus= ".25" spkane/train-os \
stress -v -cpu 2 -io 1 -vm 2 -vm-bytes 128М -timeout 60s
Если задать слишком большое значение, мы получим сообщение об ошибке от
самого Docker (не от утилиты stress), в котором будет указано доступное число
ядер процессора:
$ docker container run -rm -ti -cpus="40.25" spkane/train-os \
stress -v -cpu 2 -io 1 -vm 2 -vm-bytes 128М -timeout 60s
docker: Error response from daemon: Range of CPUs is from
0.01 to 4.00, as there are only 4 CPUs availaЫe.
See 'docker container run -help'.
Команда docker container update позволяет динамически корректировать ограниче­
ния ресурсов в одном или нескольких контейнерах. Мы можем изменить выделение
процессорных ресурсов сразу для двух контейнеров:
$ docker container update -cpus= "l.5" 092c5dc85044 92Ь797f12afl
Docker воспринимает процессоры так же, как их воспринимает Liпux. Гиперпоточ­
ность и ядра процессора обрабатываются в Liпux, и информация о них экспорти­
руется в специальный файл /proc/cpuinfo. При вызове команды --cpus в Docker мы
указываем, к какому числу записей в этом файле у контейнера будет доступ, и это
могут быть стандартные или гиперпоточные ядра.
Книги для программистов: https://clcks.ru/books-for-it
126
1
Глава 5
Память
Мы можем ограничить потребление памяти контейнером так же, как ограничивали
ресурсы процессора. Правда, есть важное отличие: ограничение процессорного
времени влияет только на приоритет при выполнении приложения, а лимиты памя­
ти задаются жестко. Даже в системе без ограничений, rде свободно 96 ГБ памяти,
мы можем выделить контейнеру 24 ГБ, и он не _получит больше, сколько бы сво­
бодной памяти ни было в системе. Система виртуальной памяти в Linux работает
таким образом, что мы можем выделить контейнеру· больше памяти, 'чем имеет
фактическое ОЗУ системы. В этом случае контейнер будет использовать файл под­
качки, как любой обычный процесс Linux.
Давайте запустим контейнер с ограничением памяти, передав параметр --rnemory
в команде docker container run:
$ docker container run --rrn -ti --rnemory 512m spk�ne/train-os \
stress -v --ери 2 --io 1 --vrn 2 --vrn-bytes 128М --tirneout 10s
'
'
Если мы укажем только параметр --rnemory, то зададим для контейнера ограничение
по ОЗУ и объему подкачки. Если мы указали --rnernory s12m, контейнер получит
512 МБ ОЗУ и 512 МБ пространства подкачки. Docker· поддерживает обозначения ь,
k, rn и g для байтов, килобайтов, мегабайтов и гигабайтов соотвщтвенно. Если ·
в вашей системе с Linux и Docker память измеряется терабайтами,, к сожалению,
вам все равно придется оперировать гигабайтами.
Если объем подкачки нужно задать отдельно или совсем отключить, у нас есть
параметр --rnemory-swap. Он определяет общий объем памяти и подкачки, доступный
контейнеру. Давайте изменим предыдущую команду:
$ docker container run --rrn -ti --rnernory 512m --rnernory-swap=768rn ,.
spkane/train-os stress -v --cpu 2 --io 1 --vrn 2 --vrn-bytes 128М \
--tirneout 10s
Мы указали ядру, что этому контейнеру будет доступно 512 МБ памяти и 256 МБ
подкачки. Если задать для параметра --rnemory-swap значение -1, то контейнер сможет
использовать неограниченное пространство подкачки в базовой системе, а если для
--rnemory-swap и --rnemory задано одинаковое положительное значение, то у контейнера
не будет доступа к пространству подкачки.
Опять же, в отличие от процессорного времени, ограничения на память задаются
жестко. Это хорошо, потому что при развертывании в системе очередного контей­
нера уже развернутые контейнеры не пострадают. С другой стороны, нужно тща­
тельно рассчитывать лимиты в соответствии с потребностями контейнера, потому
что пространства для маневра не 'останется. Если у контейнера заканчивается память, ядро ведет себя так, будто память закончилась всi' всей системе, - оно по­
пытается найти процесс, который можно завершить, чтобы освободить место. Это
частая причина сбоя в системах, где для контейнеров выделяется слишком мало
памяти. На проблему указывает код завершения контейнера 137 и сообщения ядра
о нехватке памяти (Out-Of-Memoгy, ООМ) в выводе drnesg сервера Docker.
Что будет, если контейнер достигнет лимита памяти? Давайте попробуем изменить
одну из предыдущих команд, выделив гораздо меньше памяти:
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
127
$ docker container run --rm -ti --memory 100m spkane/train-os \
stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М --timeout 10s
Предыдущие запуски контейнера stress завершались строками об успешном вы­
полнении:
stress: info: [17] successful run completed in 10s
Теперь запуск быстро завершается сбоем:
stress: FAIL: [1] (451) failed run completed in 0s
Это связано с тем, что контейнер пытается выделить больше памяти, чем разреше­
но, и вызывается защитный механизм Linux ООМ Killer, который начинает завер­
шать процессы в cgroup, чтобы освободить память. В этом контейнере выполняется
один родительский процесс, породивший несколько дочерних, и когда ООМ Killer
завершает один из дочерних процессов, родительский процесс· очищает остальное
и завершает работу с ошибкой.
В Docker можно настроить и отключить Linux ООМ Killer с помощью аргументов
--oom-kill-disaЬle и --oom-score-adj для команды docker container run, но в по­
давляющем большинстве случаев лучше так не делать.
Если обратиться к серверу Docker, можно прочесть сообщение от ядра, связанное
с этим событием, с помощью ctmesg. Мы получим примерно следующий вывод:
4210.403984] stress invoked oom-killer: gfp_mask=0x24000c0
[ 4210.404899] stress cpuset=5bfa65084931efaЬda59d9a70fa8e88
[ 4210.405951] CPU: 3 PID: 3429 Comm: stress Not tainted 4.9
[ 4210.406624] Hardware name: BHYVE, BIOS 1.00 03/14/2014
4210.408978] Call Trace:
[ 4210.409182] [ <ffffffff94438115>] ? dump_stack+0x5a/0x6f
[ 4210.414139] [<ffffffff947f9cf8>] ? page_fault+0x28/0x30
[ 4210.414619] Task in /docker-ce/docker/5... 3
killed as а result of limit of /docker-ce/docker/5...3
4210.416640] memory: usage 102380kB, limit 102400kB, failc ...
4210.417236] memory+swap: usage 204800kB, limit 204800kB, ...
4210.417855] kmem: usage 1180kB, limit 9007199254740988kB, ...
4210.418485] Memory cgroup stats for /docker-ce/docker/5...3:
cache:0KB rss:101200KB rss_huge:0KB mapped_file:0KB dirty:0KB
writeback:11472KB swap:102420KB inactive_anon:50728KB
active-anon:50472KB inactive-file:0KB active-file:0KB unevictaЬle:0KB
[ 4210.426783] Memory cgroup out of memory: Kill process 3429...
[ 4210.427544] Killed process 3429 (stress) total-vm:138388kB,
anon-rss:44028kB, file-rss:900kВ, shmem-rss:0kB
[ 4210.442492] oom_reaper: reaped process 3429 (stress), now
anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Книги для программистов: https://clcks.ru/books-for-it
128
1
Глава 5
Это событие ООМ также будет записано в Docker, и его можно посмотреть с по­
мощью команды docker systern events:
$ docker systern events
2018-01-28Т15:56:19.972142371-08:ОО container oorn \
d0d803ce32c4e86d0aa6453512a9084a156e96860e916ffc2856fc63ad9cf88b \
(irnage=spkane/train-os, narne=loving_franklin)
Блочный ввод-вывод
Многие контейнеры представляют собой приложения без сохранения состояния,
и им не нужны ограничения на блочный ввод-вывод. Docker все же поддерживает
эти ограничения несколькими способами с помощью механизмов cgroup.
Первый способ - настройка приоритетов при использовании контейнером ввода­
вывода блочного устройства. Для этого необходимо изменить параметр по умолча­
нию атрибута Ыkio.weight cgroup. У атрибута может быть значение О (отключено)
или число от 10 до 1000. По умолчанию - 500. Это ограничение работает пример­
но так же, как доли процессорного времени, потому что система делит весь доступ­
ный ввод-вывод для всех процессов в cgroup на 1 ООО, и выбранное значение указы­
вает, сколько ресурсов ввода-вывода доступно каждому процессу.
Чтобы задать это значение для контейнера, мы передаем --Ыkio-weight команде
docker container run с допустимым значением. Мы можем указать значение для кон­
кретного устройства с помощью параметра --Ьlkio-weight-device.
Как и при настройке долей процессорного времени, здесь сложно подобрать пра­
вильное значение, но можно упростить эту задачу, с помощью cgroup ограничив
максимальное число байтов или операций в секунду, доступных контейнеру. Пара­
метры для контроля этих значений:
♦ --device-read-bps - ограничение скорости чтения (байт в секунду) с устройства;
♦ --device-read-iops - ограничение скорости чтения (операций в секунду) с уст­
ройства;
♦ --device-write-bps - ограничение скорости записи (байт в секунду) на устройство;
♦ --device-write-iops - ограничение скорости записи (операций в секунду) на устройство.
Мы можем проверить, как эти настройки влияют на производительность контейне­
ра, выполнив следующие команды, использующие утилиту тестирования Linux
bonnie (https://www.coker.com.au/bonnie):
$ tirne docker container run --rrn -ti spkane/train-os:latest bonnie++ \
-u 500:500 -d /tmp -r 1024 -s 2048 -х 1
real 0rn27.715s
user 0rn0.027s
sys
0rn0.030s
$ tirne docker container run -ti --rrn --device-write-iops /dev/vda:256 \
spkane/train-os:latest bonnie++ -u 500:500 -d /tmp -r 1024 -s 2048 -х 1
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
129
real 0m58.765s
user 0m0.028s
sys
0m0.029s
$ time docker container run -ti --rm --device-write-bps /dev/vda:5mЬ \
spkane/train-os:latest bonnie++ -и 500:500 -d /tmp -r 1024 -s 2048 -х 1
Пользователи PowerShell могут использовать функцию Measure-Command (https://
learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/
measure-command?view=powershell-7.3) вместо команды Unix time.
Как показывает опыт, аргументы --device-read-iops и --device-write-iops - это самый
эффективный способ задавать ограничения для блочного ввода-вывода.
ulimit
До Linux cgroup был другой способ ограничивать ресурсы, досrупные процессу:
применение пользовательских лимитов с помощью команды ulimit. Этот способ все
еще работает для всех случаев, в которых он использовался раньше (https://www.
linuxhowtos.orgffips%20and%20Tricks/ulimit.htm).
В следующем фрагменте кода перечисляются типы системных ресурсов, которые
можно ограничить с помощью мягких и жестких лимитов командой ulimit:
$ ulimit -а
core file size (Ьlocks, -с) О
data seg size (kЬytes, -d) unlimited
scheduling priority (-е) О
file size (Ьlocks, -f) unlimited
pending signals (-i) 5835
max locked memory (kЬytes, -1) 64
max memory size (kЬytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -р) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) О
stack size (kЬytes, -s) 10240
ери time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kЬytes, -v) unlimited
file locks (-х) unlimited
Мы можем настроить демон Docker с пользовательскими лимитами по умолчанию,
которые будут применяться к каждому контейнеру. Следующая команда указывает
демону Docker, что нужно запустить все контейнеры с мягким (soft) ограничением
в 50 открытых файлов и жестким (hard) ограничением в 150 открытых файлов:
$ sudo dockerd --default-ulimit nofile=50:150
Книги для программистов: https://clcks.ru/books-for-it
130
Глава 5
Мы можем переопределить эти ограничения для конкретного контейнера, передав
значения с помощью аргумента --ulimit:
$ docker container run --rm -d --ulimit nofile=lS0:300 nginx
При создании контейнеров можно выполнять и более сложные команды, но приве­
денных здесь будет достаточно для самых распространенных вариантов использо­
вания. В документации по клиенту Docker (https://dockr.ly/2ME0ygi) перечислены
все доступные варианты. Изменения вносятся с каждым выпуском Docker.
Запуск контейнера
Перед тем как углубиться в тему ограничения ресурсов, мы создали контейнер ко­
. мандой docker container create. Пока контейнер ничего не делает. У него есть конфи­
гурация, но никакие процессы не выполняются. Когда мы будем готовы запустить
контейнер, мы выполним команду docker container start.
Допустим, мы хотим запустить копию Redis, стандартного хранилища ключей
и значений. Мы ничего не будем делать с этим контейнером Redis - это просто
легкий и долгосрочный процесс, на примере которого мы рассмотрим, какие воз­
можности нам доступны в реальной среде. Сначала можно создать контейнер:
$ docker container create -р 6379:6379 redis:2.8
UnaЫe to find image 'redis:7.0' locally
7.0: Pulling from library/redis
3f4ca6laafcd: Pull complete
20bfl5ad3c24: Pull complete
Digest: sha256:8184cfe57f205ab34c62bd0e9552dffeb885d2a7f82ce4295c0df344cb6f0007
Status: Downloaded newer image for redis:7.0
092c5dc850446324e4387485df7b7625Bfdf9ed0aedcd53a37299d35fc67a042
В последней строке вывода этой команды приводится полный хеш, сгенерирован­
ный для контейнера. Мы можем использовать эту строку для запуска контейнера,
но если мы ее не записали, можно вывести список всех контейнеров в системе,
даже не запущенных, следующей командой:
$ docker container 1s -а --filter ancestor=redis:2.8
СОММАND
CONTAINER ID IМAGE
... NAМES
CREATED
092c5dc85044 redis:7.0 "docker-entrypoint.s... " 46 seconds ago elegant_wright
Чтобы найти нужный контейнер, отфильтруем результаты по использованному об­
разу и посмотрим на время создания. Запустим контейнер следующей командой:
$ docker container start 092c5dc85044
Большинство команд Docker работают с именем контейнера, полным хешем, ко­
ротким хешем или даже частью хеша, если она уникальна. В предыдущем примере
полный хеш контейнера - 092c5dc850446324e...a37299d35fc67a042, а короткий, кото­
� рый будет отображаться в выходных данных большинства команд, - 092c5dc85044.
Короткий хеш включает первые 12 символов полного. В рассмотренном примере
достаточно было указать docker container start 092.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
1
131
Контейнер должен был запуститься, но поскольку он работает на фоне, мы могли
бы и не заметить сбой. Мы можем проверить, все ли в порядке:
$ docker container ls
CONTAINER ID IМAGE
СОММАND
092c5dc85 044 redis:7.0 "docker-entrypoint.s ..."
STATUS
Up 2 minutes
Все работает, как и ожидалось: мь; видим статус Up и время работы контейнера.
Автоматический перезапуск контейнера
Часто мы хотим, чтобы контейнеры перезапускались после завершения работы.
Некоторые контейнеры живут очень недолго, постоянно запускаясь и отключаясь.
Если мы запустили приложение в рабочей среде, мы ожидаем, что оно будет рабо­
тать непрерывно. В сложной системе нам помогает планировщик, но в этом про­
стом примере мы поручим Docker управлять перезапуском с помощью аргумента
--restart для команды docker container run. Он принимает четыре значения: no, always,
on-failure и unless-stopped. Если для restart задано no (нет), контейнер никогда не пе­
резапускается после завершения работы. Значение always (всегда) означает, что кон­
тейнер будет запускаться каждый раз, независимо от кода выхода. Когда для restart
указано on-failure (при сбое), Docker попробует перезапустить контейнер, если код
выхода отличен от О. Если установить on-failure:З, Docker попробует перезапустить
контейнер три раза, а затем прекратит попытки. Значение unless-stopped (пока не
остановят)- самый популярный вариант, при котором контейнер перезапускается,
пока мы его не остановим, например командой docker container stop.
Мы можем проверить эти значения на практике, стартовав последний контейнер
с ограничением по памяти без аргумента --rm, зато с аргументом --restart:
$ docker container run -ti --restart=on-failure:3 --memory 10 0m \
spkane/train-os stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М \
--timeout 120s
В этом примере мы видим на консоли выходные данные первого выполнения, пре­
жде чем контейнер завершается. Если сразу после этого выполнить docker container
ls, скорее всего, мы увидим, что Docker перезапустил контейнер:
$ docker container ls
... IМAGE
... STATUS
... spkane/train-os ... Up Less than а second
Перезапуск снова завершится сбоем, потому что мы не предоставили контейнеру
достаточно памяти для работы. После трех попыток Docker перестанет запускать
контейнер, и он исчезнет из выходных данных docker container ls.
Остановка контейнера
Мы можем запускать и останавливать контейнеры по желанию. Это похоже на
приостановку и возобновление обычного процесса, но есть отличия. При остановке
процесс не ставится на паузу- он завершается. Если мы остановим контейнер, он
Книги для программистов: https://clcks.ru/books-for-it
132
Глава 5
не будет отображаться в выводе docker container 1s. При перезагрузке Docker попы­
тается запустить все контейнеры, которые функционировали при завершении рабо­
ты. Если контейнер не должен выполнять никаких задач, но при этом мы не хотим
останавливать процесс, контейнер Linux можно приостановить, выполнив команду
docker container pause, а затем запустить снова с помощью unpause. Позже мы погово­
рим об этом подробнее. Давайте остановим контейнер Redis, который запустили
чуть раньше:
$ docker container stop 092c5dc85044
$ docker container 1s
CONTAINER ID IМAGE СОММАND CREATED STATUS PORTS NAМES
Список контейнеров пуст. Можем запустить контейнер снова по идентификатору,
но запоминать идентификаторы неудобно. К счастью, у команды docker container 1s
есть дополнительный параметр (-а), с помощью которого можно просмотреть все
контейнеры, а не только запущеннь1е:
$ docker container 1s -а
CONTAINER ID IМAGE
STATUS
092c5dc85044 redis:7.0 Exited (0) 2 minutes ago
В поле sтдтus мы видим, что контейнер завершил работу с кодом выхода О, т. е. без
ошибок. Мы можем запустить его снова с той же конфигурацией, что и раньше:
$ docker container start 092c5dc85044
092c5dc85044
$ docker container 1s -а
CONTAINER ID IМAGE
STATUS
092c5dc85044 redis:7.0 Up 14 seconds
Контейнер снова работает, и с теми же параметрами.
Контейнеры существуют в системе Docker в виде ВLОВ-объекта с конфиrурацией,
даже если они не запущены. Следовательно, пока мы не удалим контейнер, его
можно запускать снова, не создавая повторно. Хотя память и содержимое времен­
� ных файлов (tmpfs) будут утеряны, остальное содержимое файловой системы и
метаданные, включая переменные среды и привязки портов, сохранятся при пере­
запуске.
Скорее всего, вы уже понимаете, что контейнеры представляют собой дерево про­
цессов, которые взаимодействуют с системой практически так же, как любые дру­
гие процессы на сервере. Эго означает, что мы можем отправлять сигналы Unix
процессам в контейнерах, и они будут отвечать на сигналы. В предыдущем приме­
ре с помощью команды docker container stop мы отправляем контейнеру сигнал
SIGТERМ и ждем, пока он корректно завершит работу. Контейнеры подчиняются тем
же правилам распространения сигналов по группе процессов, что и остальные
группы процессов в Linux.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
1
133
Команда docker container stop отправляет процессу сигнал SIGГERМ. Если мы хотим
принудительно завершить процесс, который не остановился через определенный
период, можно указать аргумент -t:
$ docker container stop -t 25 092c5dc85044
Docker сначала отправит сигнал SIGTERМ, как и раньше, и если контейнер не остано­
вится через 25 секунд (по умолчанию- 10), Docker отправит сигнал SIGKILL, чтобы
завершить процесс принудительно.
Лучший способ останавливать контейнеры- команда stop, но иногда она не сраба­
тывает, и нужно остановить контейнер принудительно, как это бывает с любым
другим процессом.
Принудительное завершение контейнера
Если процесс работает неправильно, команда docker container stop может на него не
подействовать. Если мы хотим завершить контейнер немедленно, можно подать
команду docker container kill, которая очень похожа на docker container stop:
$ docker container start 092c5dc85044
092c5dc85044
$ docker container kill 092c5dc85044
092c5dc85044
Команда docker container ls показывает, что контейнер больше не выполняется:
$ docker container ls
CONTAINER ID IМAGE СОММАND CREATED STATUS PORTS NAМES
Если контейнер завершен принудительно, его все равно можно снова запустить
обычной командой docker container start. Кроме stop и kill доступны и другие сиг­
налы. Как и команда Linux kill, docker container kill поддерживает все сигналы
Unix. Допустим, мы хотим послать контейнеру сигнал usю, чтобы он повторно под­
ключился к удаленному сеансу журналирования. Это можно сделать следующим
образом:
$ docker container start 092c5dc85044
092c5dc85044
$ docker container kill --signal=USRl 092c5dc85044
092c5dc85044
Если процесс контейнера знает, как реагировать на сигнал usю, он отреагирует
соответствующим образом. Таким способом можно послать контейнеру любой
стандартный сигнал Unix.
Приостановка и возобновление контейнера
У нас могут быть разные причины не останавливать контейнер полностью. Напри­
мер, мы хотим приостановить его, сохранить за ним выделенные ресурсы и оста­
вить записи в таблице процессов. Возможно, мы хотим сделать моментальный сииКниги для программистов: https://clcks.ru/books-for-it
134
Глава 5
мок файловой системы, чтобы создать новый образ, или нам нужно ненадолго
получить дополнительное процессорное время на хосте. Если вы умеете работать
с обычными процессами Unix, то представляете себе, как это действует, ведь кон­
тейнеризированные процессы, по сути, мало чем отличаются от обычных.
Когда мы приостанавливаем процесс, cgroup freezer (https://www.kernel.org/doc/
Documentation/cgroup-vl/freezer-subsystem.txt) запрещает планировать его, пока
мы не запустим его заново. Контейнер не делает ничего, только поддерживает свое
состояние, включая содержимое памяти. В отличие от остановки контейнера сигна­
лом SIGSTOP, при которой процессы знают, что они остановлены, в случае приоста­
новки контейнер не получает информации об изменении состояния. Это важное
различие. Некоторые команды Docker используют приостановку и возобновление.
Вот как можно приостановить контейнер:
$ docker container start 092c5dc85044
092c5dc85044
$ docker container pause 092c5dc85044
092c5dc85044
l,ж
�
Для приостановки и возобновления контейнеров в Windows необходимо использо­
вать базовую технологию виртуализации, например Нурег-V или WSL2.
В списке запущенных контейнеров мы теперь видим, что контейнер Redis имеет
статус (Paused) :
$ docker container 1s
CONTAINER ID IМAGE
092c5dc85044 redis:7.0
STATUS
Up 25 seconds (Paused)
Мы не сможем использовать контейнер, поставленный на паузу. Он присутствует,
но ничего не делает. Давайте возобновим контейнер командой docker container
unpause:
$ docker container unpause 092c5dc85044
092c5dc85044
$ docker container 1s
CONТAINER ID IМAGE
092c5dc85044 redis:7.0
STATUS
Up 55 seconds
Контейнер снова выполняется, и это отражено в выводе docker container 1s. Мы
видим время выполнения - Up 55 seconds, - потому что Docker считает, что кон­
тейнер выполняется, даже когда он приостановлен.
Очистка контейнеров и образов
Когда мы выполним все команды, чтобы собрать образы, создать контейнеры и
запустить их, в системе накопится много слоев образов и папок контейнеров.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами 1
135
Мы можем вывести перечень всех контейнеров в системе с помощью команды
docker container 1s -а, а затем удалить контейнеры из списка. Прежде чем удалить
сам образ, нужно остановить все контейнеры, которые его используют. Удалить
контейнер можно командой docker container rm:
$ docker container stop 092c5dc85044
092c5dc850441s
$ docker container rm 092c5dc85044
092c5dc85044
Мы мо:кем удалить апущенный контейнер с помощью флага -f или --force с ко­
�
� мандои docker contaшer
пn.
�
Затем можно вывести все образы в системе:
$ docker image 1s
REPOSITORY
uЬuntu
redis
spkane/train-os
TAG
latest
7.0
latest
IМAGE ID
5Ьа9dаЬ47459
0256c63af7dЬ
78fЬ082a4d65
CREATED
3 weeks ago
2 weeks ago
4 months ago
SIZE
188,ЗМВ
117МВ
254МВ
Теперь можно удалить образ и все слои файловой системы:
$ docker image rm 0256c63af7dЬ
Если мы попробуем удалить образ, используемый контейнером, то получим ошиб­
ку о невозможности уд�ления: "Conflict, cannot delete". Сначала необходимо оста­
новить и удалить контеинер.
Иногда, особенно во время разработки, необходимо удалить из системы все образы
или контейнеры. Самый простой способ - выполнить команду docker system prune.
При этом мы получим предупреждение, что будут удалены все остановленные кон­
тейнеры, все сети, не используемые ни одним контейнером, все несвязанные обра­
зы без тегов и весь кеш сборки:
$ docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used Ьу at least one container
- all dangling images
- all build cache
Are you sure you want to continue? [y/N] у
Deleted Containers:
cbbc42acfeбcc7c2d5eбc3361003e077478c58bb062dd57a230d3lbcd01f6190
Deleted Images:
deleted: sha256:becбec29elбa409aflc556Ьf9e6Ь2ec584c7fb5ffЬfd7c46ec00b30bf
untagged: spkane/squid@sha256:64fbc44666405fdla02f0ec73le35881465fac395e7
Total reclaimed space: 1,385ГБ
Книги для программистов: https://clcks.ru/books-for-it
136
Глава 5
Чтобы удалить все неиспользуемые образы, а не только несвязанные (dangling),
� выполните команду docker system prune -а.
-
Мы можем достичь тех же целей, задав более конкретные команды. Например, уда­
ляем все контейнеры на хостах Docker:
$ docker container rm $(docker container 1s -а -q)
Удаляем все образы на хосте Docker:
$ docker image rm $(docker images -q)
Команды docker container 1s и docker images поддерживают аргумент filter, который
позволяет удалять конкретные контейнеры и образы.
Например, с помощью фильтра можно удалить все контейнеры с кодом выхода,
отличным от нуля:
$ docker container rm $(docker container 1s -а -q --filter 'exited!=O')
Также мы можем удалить все образы без тегов:
$ docker image rm $(docker images -q -f "dangling=true")
�
Подробнее о фильтрах см. в официальной документации Docker (https:1/docs.
docker.com/engine/reference/commandline/ps/#filtering). Пока фильтров не так
много, но со временем их станет больше.
Мы можем создавать очень сложные фильтры, объединяя команды с помощью
вертикальной черты ( 1) и применяя другие подобные методы.
В рабочих системах, где постоянно происходят развертывания, накапливается мно­
го старых и неиспользуемых образов, занимающих место на диске. Напишите
скриm, чтобы команда docker system prune выполнялась по расписанию (например,
через cron или с помощью таймера systemd).
Контейнеры Windows
До этого мы рассматривали исключительно команды Docker для контейнеров
Linux, потому что это самый распространенный сценарий, который работает на
всех платформах Docker. Однако с 2016 года Microsoft Windows поддерживает кон­
тейнеры Windows с нативными приложениями Windows, и ими можно управлять
с помощью обычных команд Docker.
В этой книге мы не будем подробно останавливаться на контейнерах Windows, по­
скольку они составляют лишь малую часть всех контейнеров в рабочих системах и
не полностью интегрируются в экосистему Docker из-за специфических образов.
Однако они набирают популярность, так что мы кратко рассмотрим их. По сути, не
считая содержимого контейнеров, остальное работает так же, как в контейнерах
Linux. В этом разделе мы рассмотрим небольшой пример того, как запустить кон­
тейнер Windows на Windows 1 О и выше с Hyper-V и Docker.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
137
Вам потребуется Docker Desktop на совместимом 64-разрядном выпуске Windows 10 или более поздней версии.
�
•
Сначала нужно переключить Docker с контейнеров Linux на контейнеры Windows.
Для этого щелкните правой кнопкой мыши по значку Docker с изображением кита
на панели задач и выберите пункт Switch to Windows Containers... (Переключиться
на контейнеры Windows), а затем подтвердите переход (рис. 5.1 и 5.2).
8
Docker Desktop is running
ii
Dashboard
..:.
spkane
Settings
Ctrl+Comma
Check for Updates
TrouЫeshoot
Switch to Windo,vs containers...
AЬout Docker Desktop
Documentation
Quick Start Guide
Docker Hub
•
Extensions
11
Pause
►
л е С?Ф>)
Рис. 5.1. Переключение на контейнеры Windows
х
А Switch to Windows containers
Switch to Windows containers
You are about to switch to Windows containers. Existing containers will continue to run, but
you will not Ье аЫе to manage them until you switch back to linux containers. No data will
Ье lost otherwise.
Do you want to continue?
О Don't show this message again
Switch
J
i
Cancel
J
Рис. 5.2. Переключение на контейнеры Windows - подтверждение
Книги для программистов: https://clcks.ru/books-for-it
138
Глава 5
Процесс может занять некоторое время, хотя обычно это происходит почти момен­
тально. К сожалеюnо, уведомление о переходе мы не получим. Если мы еще раз
щелкнем по значку Docker, то увидим теперь вариант Switch to Linux Containers...
(Переключиться на контейнеры Linux).
Если в первый раз при нажатии на значок Docker отображалась надпись Switch to
Linux Containers... (Переключиться на контейнеры Linux), значит, Docker уже был
готов к работе с контейнерами Windows.
Мы можем протестировать простой контейнер Windows, открыв PowerShell4 и по­
пытавшись выполнить следующую команду:
PS С:\> docker container run --rm -it mcr.microsoft.com/ powershell '
pwsh -command '
'Write-Host "Hello World from Windows '($IsWindows') "'
Hello World from Windows (True)
При этом загрузится и запустится базовый контейнер для PowerShell
(https://hub.docker.com/_/microsoft-powershell), а затем скрипт выведет на экран
Hello World from Windows (True).
Если вы видите Hello World from Windows (false), значит, вы не перешли на кон­
� тейнеры Windows или выполняете команду не на платформе Windows.
�
Чтобы собрать образ контейнера Windows, который будет выполнять примерно ту
же задачу, можно создать следующий Dockerfile:
# escape= '
FIO-f mcr.microsoft.com/powershell
SНELL [ "p wsh", "-command"]
RUN Add-Content C:\helloworld.psl '
'Write-Host "Hello World from Windows"'
СИ> ["pwsh", "C:\\helloworld.psl"]
При сборке этого Dockerfile образ будет основан на mcr.microsoft.com/powershell.
Docker создаст небольшой скрипт PowerShell, а затем настроит образ так, чтобы он
выполнял скрипт при запуске контейнера из образа.
Возможно, вы заметили, что пришлось экранировать обратную косую черту (\) та­
ким же символом в строке смо в файле Dockerfile. Дело в том, что Docker основан
на Unix, а в командных оболочках Unix у этого символа есть собственное значение.
И хотя мы изменили символ экранирования (https://docs.docker.com/engine/
reference/builder/#escape) для Dockerfile, чтобы он соответствовал параметрам
PowerShell по умолчанию (задается с помощью директивы SHELL - https://docs.
docker.com/engine/reference/builder/#shell-form-entrypoint-example), нам все же
нужно экранировать обратную косую черту, чтобы Docker правильно восприни­
мал ее.
4
Полный URL: https:/Лeaгn.micгosoft.com/en-us/powersbelVscгipting/oveгview?view=poweгshell7 .З&viewFallback Fгom=poweгshell-6.
Книги для программистов: https://clcks.ru/books-for-it
Работа с контейнерами
139
Если мы соберем этот Dockerfile, то получим примерно следующее:
PS С:\> docker image build -t windows-helloworld:latest
Sending build context to Docker daemon 2.04ВkВ
Step 1/4 : FROМ mcr.microsoft.com/powershell
---> 7d8f82lc04eb
Step 2/4 : SHELL ["pwsh", "-coпroand"]
---> Using cache
---> 1987fЬ489a3d
Step 3/4 : RUN Add-Content C:\helloworld.psl
'Write-Host "Hello World from Windows"'
---> Using cache
---> 37df47d57bfl
Step 4/4 : CMD ["pwsh", "C:\\helloworld.psl"]
---> Using cache
---> 03046ff62Be4
Successfully built 03046ff62Be4
Successfully tagged windows-helloworld:latest
Если мы выполним получившийся образ, то увидим следующее:
PS С:\> docker container run --rm -ti windows-helloworld:latest
Hello World from Windows
Microsoft предлагает хорошую документацию по контейнерам Windows5, где можно
найти пример сборки контейнера, выполняющего приложение .NЕГ.
На платформе Windows мы можем достичь улучшенной изоляции для контейнера,
если запустим его внутри специальной и очень легкой виртуальной машины Нурег-V.
Это легко можно сделать, добавив параметр --isolation=hyperv к командам docker
container create и docker container run. В результате немного изменится произво­
дительность и потребление ресурсов, зато изоляция контейнеров серьезно улучшится. Подробнее читайте в документации (https://learn.microsoft.com/en-us/
virtualization/windowscontainers/manage-containers/hyperv-container).
Даже если вы планируете в основном работать с контейнерами Windows, для изу­
чения примеров в оставшейся части книги переключитесь обратно на контейнеры
Linнx, чтобы все работало правильно. Когда закончите читать книгу и будете гото­
вы к сборке собственных контейнеров, вы всегда сможете переключиться обратно.
Чтобы перейти на контейнеры Linux, щелкните правой кнопкой мыши на значке
Docker и выберите пункт Switch to Linux Containers... (Переключиться на ·контей­
неры Linux).
5 Полнъ1й URL: https:/Лearn.microsoft.com/en-us/virtualization/windowscontainers/about.
6 Полный URL: https:/Лearn.microsoft.com/en-us/virtualization/windowscontainers/quick-start/Ьuilding­
sample-app.
Книги для программистов: https://clcks.ru/books-for-it
140 1
Глава 5
Заключение
В следующей главе мы продолжим рассматривать возможности Docker. Рекомен­
дуем вам поэкспериментировать самостоятельно - попробуйте выполнить коман­
ды, которые мы рассмотрели, чтобы изучить параметры командной строки и при­
выкнуть к синтаксису. Попробуйте создать и собрать небольшой образ, а потом
запустить его в виде контейнера. Когда будете готовы к дальнейшему изучению,
переходите к главе 6.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА&
Сбор информации в Docker
У вас уже есть опыт работы с контейнерами и образами. Давайте теперь познако­
мимся с другими возможностями Docker. В этой главе мы снова будем с помощью
инструмента командной строки Docker отправлять команды активному серверу
dockerd, а также рассмотрим другие базовые команды.
Docker предлагает команды для упрощения разных задач:
♦ просмотр версии Docker;
♦ просмотр информации о сервере;
♦ загрузка обновлений образов;
♦ изучение контейнеров;
♦ вход в работающий контейнер;
♦ возврат результата;
♦ просмотр журналов;
♦ мониторинг статистики и т. д.
Давайте выясним, как выполнять эти задачи, а также использовать другие инстру­
менты от сообщества, которые расширяют встроенные возможности Docker.
Просмотр версии Docker
Если вы выполняли практическую часть в предыдущей главе, значит, уже работали
с демоном Docker на сервере Linux или виртуальной машине и запускали базовый
контейнер, чтобы убедиться, что все работает. Если вы пока не настроили систему
и хотите работать с примерами в оставшейся части книги, выполните действия по
установке из главы 3.
Самое простое, что можно сделать в Docker, - посмотреть версии различных ком­
понентов. Может показаться, что это не слишком важная информация, но Docker
состоит из множества компонентов, версии которых определяют доступные нам
возможности. Кроме того, нам нужно знать точную версию, чтобы решать некото­
рые проблемы с подключением между клиентом и сервером. Например, если кли­
ент Docker выдает непонятное сообщение о несоответствии версий API, мы сможем
сопоставить их с версиями Docker и определить, какой компонент нуждается в об­
новлении. Эта команда обращается к удаленному серверу Docker, так что если кли­
ент по какой-то причине не может подключиться к серверу, он сообщит об ошибке
Книги для программистов: https://clcks.ru/books-for-it
142
Глава б
и выдаст только информацию о версии клиента. Если возникли проблемы с под­
ключением, изучите инструкции, приведенные в предыдущей главе.
Мы можем напрямую войти на сервер Docker и выполнять команды docker из обо­
лочки на сервере, если пытаемся устранить неисправность или просто не хотим
использовать клиент Docker для подключения к удаленной системе. На большин­
стве серверов Docker для этого потребуются привилегии root или членство в груп­
пе docker, чтобы можно было подключиться к uпiх-сокету, на котором Docker ожидает данные.
Поскольку мы установили все компоненты Docker одновременно, команда docker
version покажет, что все версии совпадают:
$ docker version
Client:
Cloud integration: vl.0.24
20.10.17
Version:
1.41
АР! version:
gol.17.11
Go version:
100с701
Git corrnni t:
Mon Jun 6 23:04:45 2022
Built:
darwin/amd64
OS/Arch:
default
Context:
Experimental:
true
Server: Docker Desktop 4.10.1 (82475)
Engine:
20.10.17
Version:
1.41 (minimum version 1.12)
АР! version:
gol.17.11
Go version:
а89Ь842
Git corrnnit:
Mon Jun 6 23:01:23 2022
Built:
linux/amd64
OS/Arch:
false containerd:
Experimental:
Version:
1.6.6
10c12954828e7c7c9b6e0ea9b0c02b01407d3ael
GitCorrnnit:
runc:
1.1.2
Version:
vl.1.2 0-ga916309
GitCorrnnit:
docker-init:
Version:
0.19.0
GitCorrnnit:
de40ad0
Обратите внимание, что клиент и сервер находятся в разных разделах. Здесь версии
клиента и сервера сочетаются, потому что мы установили их вместе. Но так бывает
не всегда. В рабочих системах придется внимательно следить за тем, чтобы везде
была установлена одна версия. При этом в средах разработки и системах сборки
версии часто немного различаются.
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
143
Библиотеки и клиенты API обычно поддерживают разные версии Docker, но это
зависит от версии API. В разделе server указано, что текущая версия API 1.41, а
минимальная - 1.12. Это полезная информация при работе со сторонними клиен­
тами, и теперь вы знаете, как получить эти сведения.
Информация о сервере
Через клиент Docker мы можем многое узнать о сервере Docker. Например, бэкенд
файловой системы сервера Docker, версию ядра, операционную систему, установ­
ленные плагины, среду выполнения, количество сохраненных контейнеров и обра­
зов. Позже мы поговорим о том, что все это значит. Команда docker system info
выведет примерно следующие данные, которые здесь приводятся в сокращенном
виде:
$ docker system info Client:
Plugins:
buildx: Docker Buildx (Docker Inc., v0.8.2)
compose: Docker Compose (Docker Inc., v2.6.1)
extension: Manages Docker extensions (Docker Inc., v0.2.7)
sbom: View the packaged-based Software Bill Of Materials (SВОМ)
scan: Docker Scan (Docker Inc., v0.17.0)
Server:
Containers: 11
Images: 6
Server Version: 20.10.17
Storage Driver: overlay2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries
Runtimes: io.containerd.runc.v2 io.containerd.runtime.vl.linux runc
Default Runtime: runc
Kernel Version: 5.10.104-linuxkit
Operating System: Docker Desktop
OSType: linux
Architecture: хВб 64
У вас вывод может быть другим - все зависит от вашего демона Docker. Это нор­
мально, данные приводятся здесь только для примера. Мы видим, что сервер - это
выпуск Docker Desktop с ядром Linux 5.10.10 4 и драйвером файловой системы
overlay2. На сервере есть несколько образов и контейнеров. Если Docker только что
установлен, здесь будет число О.
Книги для программистов: https://clcks.ru/books-for-it
144
1
Глава б
Обратите внимание на информацию о плагинах. Мы видим все, что поддерживает
эта установка Docker. Примерно так будет выглядеть этот раздел в только что уста­
новленном Docker, в зависимости от того, какие новые плагины сейчас распростра­
няются с дистрибутивом. По сути, Docker представляет собой множество плагинов,
работающих вместе. Это удобно, потому что мы можем устанавливать другие
плагины, созданные сообществом. Нам нужна возможность просматривать, какие
плагины установлены хотя бы для того, чтобы убедиться, что Docker распознает
недавно добавленные плагины.
Обычно образы и контейнеры сохраняются в корневом каталоге по умолчанию
/varЛiЬ/docker. Если вы хотите это изменить, в скриптах запуска демона укажите на
новое расположение с помощью аргумента --data-root. Для тестирования выполните
следующую команду:
$ sudo dockerd \
-Н unix:///var/run/docker.sock \
--data-root="/data/docker"
1
По умолчанию файл конфиrурации для сервера Docker находится в /etc/docker/
daemon.json. Для большинства арrументов, которые мы передаем dockerd, в этом
файле можно задать постоянные значения. Если вы работаете с Docker Desktop,
редактируйте файл в пользовательском интерфейсе.
Мы видим, что установлены три среды выполнения (мы еще поговорим о них поз­
же). runc- среда выполнения Docker по умолчанию. Говоря о контейнерах Linux,
обычно мы представляем контейнеры, собираемые в runc. На этом сервере также
установлены среды выполнения io. containerd . runc.v2 и io. containerd . runtime. vl.linux.
В главе 11 мы рассмотрим и другие варианты.
Загрузка обновлений образов
В следующих примерах мы будем использовать базовый образ UЬuntu. Если вы уже
загружали базовый образ uЬuntu: latest, то можете снова выполнить команду pull,
чтобы автоматически получить обновления, которые были опубликованы с момен­
та последней загрузки.
Это возможно благодаря тому, что тег latest по соглашению представляет послед­
нюю сборку контейнера. Правда, у такого подхода есть недостаток, поскольку тег
не привязан к конкретному образу и может обозначать разные образы в разных час­
тях проекта. Одни программисты помечают им последние стабильные выпуски,
другие - последнюю сборку, выданную системой CI/CD, третьи совсем не исполь­
зуют тег latest. Однако он популярен и особенно полезен в подготовительных
(preproduction) средах, где удобство перевешивает невозможность узнать, какая
действительно версия актуальна.
1
Полный URL: https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file.
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
145
Вызов docker image pull вьщает примерно следующий результат:
$ docker image pull uЬuntu:latest
latest: Pulling from library/uЬuntu
405f018f9dld: Pull coпplete
Digest: sha256:bбb83d3c331794420340093eb706aбf152d9clfa51b262d9bf34594887c2c7ac
Status: Downloaded newer image for uЬuntu:latest docker.io/library/uЬuntu:latest
Здесь показаны только слои, которые изменились с последнего выполнения коман­
ды. Список может быть длиннее или короче или вовсе пустым - зависит от того,
когда вы в последний раз скачивали образ, какие изменения с тех пор бьши отправ­
лены в реестр и сколько слоев содержит целевой образ.
Даже если вы скачали образ с тегом latest, Docker не будет автоматически обнов­
лять локальный образ. Это придется делать вручную. Правда, если мы разверты­
ваем образ на основе более новой копии uЬuntu:latest, клиент Docker загрузит
недостающие слои при развертывании, как и можно было ожидать. Так ведет себя
клиент Docker - другие библиотеки или API могут функционировать иначе. Рекомендуется всегда развертывать в рабочей среде образы, помеченные тегом с ука­
занием фиксированной версии, а не тегом latest. В результате мы сможем гаран­
тированно получать ожидаемую версию, без сюрпризов.
К элементам реестра можно обращаться не только по тегу с обозначением послед­
ней версии (latest) или конкретного номера версии, но и по содержимому, напри­
мер:
sha256:bбb83d3c331794420340093eb706aбf152d9clfa51b262d9bf34594887c2c7ac
Данный тег содержит хешированную сумму содержимого образа, и это самый точ­
ный идентификатор для образов Docker, применение которого гарантирует, что мы
получаем ожидаемую версию, - в отличие от тега с версией, этот тег нельзя пере­
местить. Синтаксис извлечения образов по такому тегу почти такой же, не считая
символа@ :
$ docker image pull uЬuntu@sha256:b6Ь83d3c331794420340093eb706aбf152d ...
В отличие от большинства команд Docker, в которых хеш можно сократить, хеши
SНА-256 остаются без изменений - мы всегда используем полный вариант.
Изучение контейнера
С помощью docker мы можем посмотреть конфигурацию контейнера, даже если он
еще не запущен. Это полезно для отладки и для других ситуаций, когда нам нужно
больше информации.
Давайте запустим контейнер:
$ docker container run --rm -d -t uЬuntu /bin/bash
Зc4f916619a5dfc420396d823b42e8bd30a2f94aЬ5bOf42f052357a68a67309b
Посмотреть все выполняющиеся контейнеры можно с помощью команды docker
container 1s. Так мы убедимся, что все работает, как ожидалось, и сможем скопиро­
вать идентификатор контейнера:
Книги для программистов: https://clcks.ru/books-for-it
146
Глава б
$ docker container 1s
CONTAINER ID IМAGE
СОММАND
Зс4f916619а5 uЬuntu:latest "/Ьin/bash"
STATUS
Up 31 seconds
NAМES
angry_mestorf
В этом случае идентификатор контейнера - Зс4f916619а5. Мы также можем обра­
щаться к контейнеру по динамически назначенному имени angry_mestorf, но многим
инструментам требуется именно идентификатор, так что следует всегда сначала
смотреть на него. Как мы уже говорили, здесь приводится короткая версия иденти­
фикатора, но для Docker это не имеет значения. Как и во многих системах контроля
версий, этот хеш представляет собой префикс гораздо более длинного хеша. Ядро
идентифицирует контейнер по 64-байтовому хешу, но людям это неудобно, так что
Docker поддерживает укороченный хеш.
Команда docker container inspect выдает очень подробный вывод. В следующем
блоке кода мы сократим его до нескольких важных значений. Изучите полный вы­
вод и найдите то, что вас интересует:
$ docker container inspect Зс4f916619а5
[{
"Id": "3c4f916619a5dfc420396d823b42e8Ьd30a2f94ab5b0f42f052357а68а67309Ь",
"Created": "2022-07-17Tl 7:26:53.611762541Z",
"Args": [],
"Image": "sha256:27941809078cc9b2802deb2b0bbбfeedбc ... 7f200e24653533701ee",
"Config": {
"Hostname": "Зс4f916619а5",
"Env": [
"PAТH;/usr/local/sЬin:/usr/local/Ьin:/usr/sЬin:/usr/Ьin:/sЬin:/Ьin"
] 1
"Cmd": [
],
"/Ьin/bash"
"Image": "uЬuntu",
),
}]
Обратите внимание на длинную строку "Id". Это полный уникальный идентифика­
тор контейнера. ·к счастью, нам доступна короткая версия, хотя и ее не назовешь
удобной. Можно заметить, что время создания контейнера здесь указано гораздо
точнее, чем в выводе команды docker container 1s.
Мы видим еще несколько интересных моментов: команду верхнего уровня в кон­
тейнере; окружение, переданное при его создании; образ, на котором он основан;
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
1
147
и имя хоста внутри контейнера. Все эти параметры можно при необходимости на­
страивать при создании контейнера. Например, обычно мы передаем конфигура­
цию через переменные среды, и в выводе команды docker container inspect можно
найти много полезной информации при отладке.
Вы можете теперь остановить выполняющийся контейнер командой docker container
stop Зс4f916619а5.
Изучение командной �болочки
Давайте запустим контейнер с помощью интерактивной командной оболочки Ьash,
чтоб�• заглянуrь внуrрь·. Для этого, как и раньше, выполним команду:
$ docker container run --rm -it uЬuntu:22.04 /bin/bash
Запустится контейнер uьuntu 22. 04 1тs с командной оболочкой ьash в качестве про­
цесса верхнего уровня. Указав тег 22. 04, мы гарантированно получаем конкретную
версию образа. Какие процессы будут выпоJ?IЯться при запуске контейнера?
root@35fdlad27228:/И ps -ef
UID
PID
PPID
С STIME ТТУ
ТIМЕ CMD
О 17:45 pts/0
root
1
о
00:00:00 /bin/bash
О 17:47 pts/0
9
1
00:00:00 ps -ef
root
Не так уж много? Оказывается, когда мы велели docker запустить bash, мы только
bash и получили. Мы находимся внутри целого образа с дистрибутивом Linux, но
никакие процессы не запускаются автоматически. Мы получили только то, о чем
попросили. Всегда помните об этом.
По умолчанию контейнеры Linux ничего не запускают в фоновом режиме, как это
делают виртуальные машины. Контейнеры гораздо легче и не запускают систему
init. Конечно, при желании мы можем запустить целую систему init или систему
tini init (https://github.com/krallin/tini), встроенную в Docker, но это необходимо
сделать вручную. Мы подробнее поговорим об этом в главе 7.
Итак, мы запустили в контейнере командную оболочку. Попробуйте выполнить
несколько команд и посмотреть, какую еще интересную информацию можно найти.
Набор команд ограничен, но мы находимся в базовом образе UЬuntu, так что можем
это исправить, выполнив команды apt-get update, а затем apt-get install для загрузки
пакетов. Правда, эти приложения будут работать, только пока контейнер существу­
ет, - мы меняем верхний слой контейнера, а не базовый образ; Контейнеры "эфе­
мерны" по своей природе - они существуют некоторое время, а после завершения
работы их содержимое не сохраняется.
Закончив изучение контейнера, не забудьте выйти (exit) из командной оболочки,
чтобы завершить работу контейнера:
root@35fdlad27228:/И exit
Книги для программистов: https://clcks.ru/books-for-it
148
Глава 6
Возврат результата
Представьте, насколько неэффективно запускать целую виртуальную машину, что­
бы выполнить одну команду и получить результат. Обычно мы так не делаем, по­
тому что потребуется много времени и придется запускать операционную систему
ради одной команды. Контейнеры Docker и Linux работают не так, как виртуальные
машины: контейнеры очень легкие и запускаются гораздо быстрее, чем операцион­
ная система. Контейнеры Linux идеально подходят для того, чтобы выполнить не­
большое фоновое задание и дождаться кода завершения. Представьте, что это спо­
соб получить удаленный доступ к контейнеризированной системе и к отдельным
командам внутри контейнера с возможностью передавать данные в обе стороны и
возвращать коды выхода.
Это удобно в самых разных сценариях. Например, мы можем удаленно проверять
работоспособность системы или запускать процессы на нескольких компьютерах
через Docker для обработки рабочей нагрузки и получения результата. Инструмен­
ты командной строки Docker отправляют результаты на локальный компьютер.
Если выполнить удаленную команду на переднем плане и не указать иное, docker
перенаправит поток stdin в удаленный процесс, а потоки stdout и stderr - в терми­
нал. Единственное, что мы должны сделать для этого, - запустить команду на
переднем плане и не выделять ТТУ на удаленном компьютере. Кроме того, это
конфигурация по умолчанию: никаких параметров командной строки указывать не
требуется.
При выполнении этих команд Docker создает новый контейнер, выполняет коман­
ду, запрошенную в пространствах имен и cgroup контейнера, удаляет контейнер, а
затем завершает работу, чтобы ничего больше не выполнялось и не занимало лиш­
нее место на диске между вызовами. Следующий код должен дать представление
о доступных возможностях:
$ docker container run --rm uЬuntu:22.04 /bin/false
$ echo $?
1
$ docker container run --rm uЬuntu:22.04 /bin/true
$ echo $?
о
$ docker container run --rm uЬuntu:22.04 /bin/cat /etc/passwd
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
$ docker container run --rm uЬuntu:22.04 /bin/cat /etc/passwd I wc -1 19
Здесь мы выполнили /bin/false на удаленном сервере, и эта команда всегда завер­
шается с кодом 1. Обратите внимание, как ctocker отправил результат на локальный
терминал. Чтобы убедиться, что другие результаты тоже возвращаются, мы выпол­
нили /Ьin/true, который всегда возвращает о, и мы этот ноль получили.
Книги для программистов: https://clcks.ru/books-for-it
т
Сбор информации в Docker
1
149
Затем мы попросили docker выполнить cat /etc/passwd на удаленном контейнере. Мы
получили содержимое файла /etc/passwd, который находится в файловой системе
контейнера. Поскольку это обычный вывод в stdout, мы можем передавать его в ло­
кальные команды.
Предыдущий код передает вывод в локальную команду wc, а не в команду wc в кон­
тейнере. Сам символ вертикальной черты не передается. Если вы хотите передать
на сервер всю команду вместе с этими символами, нужно вызвать полную команд­
ную оболочку на удаленной стороне и передать команду в кавычках, например bash
-с "<команда> 1 <что-то еще>". В предыдущем фрагменте кода получилось бы
docker container run uЬuntu:22.04 /Ьin/ bash -с " /Ьin/cat /etc/passwd I wc -1".
Что происходит в контейнере
Мы можем легко запустить командную оболочку в новом контейнере на базе почти
любого образа, как мы сделали ранее командой docker container run. Но это не то же
самое, что запустить новую командную оболочку внутри существующего контей­
нера, в котором активно выполняется приложение. Каждая команда docker container
run запускает новый контейнер, так что если в существующем контейнере выполня­
ется приложение и нам нужно отладить его внутри контейнера, потребуется другая
команда.
Команда docker container ехес запускает новый интерактивный процесс в контейнере
и относится к Docker. Есть похожая команда, но со стороны Linux, - nsenter.
В следующем разделе мы рассмотрим docker container ехес, а об nsenter поговорим
позже, в разделе "nsenter" главы 11.
Этот подход пригодится при разработке, пока мы активно создаем и тестируем
приложение. Механизм "контейнеры в среде развертывания" (https://
containers.dev/) используют в интегрированных средах разработки, вроде Visual
Studio Code (https://code.visualstudio.com/docs/devcontainers/containers).
В рабочей среде не рекомендуется подключаться через SSH к рабочим серверам,
а этот метод действует почти так же, но иногда нам необходимо посмотреть, что
происходит в среде, и в таких случаях эта команда очень пригодится.
docker container ехес
Давайте рассмотрим самый простой и эффективный способ заглянуть в работаю­
щий контейнер. Сервер dockerd и инструмент командной строки docker поддержива­
ют удаленное выполнение нового процесса в работающем контейнере с помощью
команды docker container ехес. Давайте запустим контейнер в фоновом режиме, а
затем войдем в него командой docker container ехес и вызовем командную оболочку.
Мы не обязаны вызывать именно оболочку - можно выполнять в контейнере от­
дельные команды и просматривать результаты снаружи с помощью docker container
ехес. Но если мы хотим заглянуть внутрь контейнера, проще всего это сделать с по­
мощью командной оболочки.
Книги для программистов: https://clcks.ru/books-for-it
Глава 6
150
Для выполнения docker container ехес нам потребуется идентификатор контейнера.
В этом примере давайте создадим контейнер, который выполняет команду sleep
в течение 600 с:
$ docker container run -d --rm uЬuntu:22.04 sleep 600
9f09ac4bcaa0f201e31895Ы5b479d2c82c30387cf2c8a46e487908d9c285eff
Сокращенный идентификатор этого контейнера - 9f09ac4bcaao. Этот идентифика­
тор можно указать в команде docker container ехес. Командная строка будет очень
похожа на docker container run, и это неудивительно. Мы запрашиваем интерактив­
ный сеанс и псевдо-ТТУ с помощью флагов -i и -t:
$ docker container ехес -it 9f09ac4bcaa0 /bin/bash
root@9f09ac4bcaa0:/#
Итак, у нас есть командная строка с идентификатором контейнера, внутри которого
мы работаем. Это удобно, потому что мы видим, где находимся. Теперь можно
выполнить стандартную команду Linux ps, чтобы посмотреть, что еще выполняется
в контейнере. Мы видим, что при первом запуске контейнера создается процесс
sleep:
root@9f09ac4bcaa0:/# ps -ef
PID
UID
1
7
15
root
root
root
PPID
О
о
7
С STIME ТТУ
О 20:22?
О 20:23 pts/0
О 20:23 pts/0
TIME CMD
00:00:00 sleep 600
00:00:00 /bin/bash
00:00:00 ps -ef
Введите exit, чтобы выйти из контейнера, когда закончите.
Мы можем запустить дополнительные процессы на фоне с помощью docker
container ехес. Можно указать параметр -d, как в docker container run. Если вам
-
важна воспроизводимость при развертывании образа, дважды подумайте, прежде
чем применять такой подход не для отладки. Иначе другие люди должны будут
знать, что следует передать в docker container ехес, чтобы получить желаемый
результат. Если вам все же необходимо запускать дополнительный процесс, луч­
ше пересоберите образ контейнера с этим процессом. Если вы хотите отправлять
сигнал другим программам за пределами контейнера, например, для ротации жур­
налов или перезагрузки конфигурации, лучше использовать команду docker
container kill -s <СИГНАЛ>, указав стандартный сигнал Unix, чтобы передать ин­
формацию процессу внутри контейнера.
docker volume
Docker поддерживает вспомогательную команду volшne, которая позволяет вывести
все тома, сохраненные в корневом каталоге, а затем получить дополнительную
информацию о них, в том числе о месте их физического хранения на сервере.
Такие тома монтируются не с помощью Ьind mount. Это специальные контейнеры
данных, которые предоставляют удобный метод сохранения данных.
Если мы выполним обычную команду docker, которая монтирует каталог методом
Ьind mount, то заметим, что она не создает тома Docker:
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker 1
151
$ docker volume ls
DRIVER
VOLUME NАМЕ
$ docker container run --rm -d -v /trnp:/tпp uЬuntu:latest sleep 120
бfc97c50fb888054e2d0lfOa93aЬЗb3dЬ172b2cd402fclcd616858b2b5138857
$ docker volume ls
DRIVER
VOLUME NАМЕ
Мы можем создать том сами командой:
$ docker volume create my-data
Если вывести все тома, мы увидим примерно следующее:
$ docker volume ls
DRIVER
VOLUME NАМЕ
local
·my-data
$ docker volume inspect my-data·
"CreatedAt": "2022-07-ЗlТlб: 19:42Z",
"Driver": · "local",
11 LaЬels 11 :
{},
"!oюuntpoint": . "/var/liЬ/docker/volumes/my-data/_data",
"Name 11 : "my-data",
110ptions" :
{},
"Scope": 11 local 11
Мы можем запустить контейнер с прикрепленным томом данных следующей
командой:
$ docker container run --rm \
--mount source=my-data,target=/app \
uЬuntu:latest touch /app/my-persistent-data
Контейнер создал в томе данных файл и сразу завершил работу.
Если теперь мы при�юнтируем этот том к другому контейнеру, то наши данные ни­
куда не пропадут:
$ docker container run --rm \
--mount source=my-data,target=/app \
fedora: latest ls -lFa ~/app/my-persistent-d.ata
-rw-r--r-- 1 root root О Jul'Зl 16:24 /app/my-persistent-data
Наконец, мы можем удалить том, если он больще не нужен:
$ docker volume rm my-data my-data
.Если мы попытаемся удалить том, используемый контейнером, .даже если этот
контейнер не выполняется, то получим ошибку:
Error response from daemon: unaЬle to remove volume:
remove my�data: volume is in use - [
d0763e6e8d79e55850ald3aЬ2le9d...,
4b40d52978ea5e784eббddca8bc22... ]
Книги для программистов: https://clcks.ru/books-for-it
152
1
Глава б
Рассмотренные команды позволяют изучить контейнеры во всех деталях. В гла­
ве 11 мы подробно обсудим пространства имен, и вы лучше поймете, как именно
эти компоненты сочетаются и взаимодействуют, образуя контейнер.
Журналирование
Журналирование - важная часть любого приложения в рабочей среде. Когда что­
то идет не так, именно журналы способны помочь нам восстановить работу, поэто­
му важно продумать их ведение. В системах Linux есть несколько способов рабо­
тать с журналами приложений, и некоторые из них эффективнее, чем другие. Если
процесс приложения выполняется на нашем сервере, вывод будет поступать в ло­
кальный файл журнала, к которому у нас есть доступ. Кроме того, вывод может
записываться в буфер ядра, откуда его можно считать с помощью dmesg. Во многих
современных дистрибутивах Llnux с systernd журналы доступны с помощью утили­
ты journalctl. Из-за ограничений контейнеров и структуры Docker при использова­
нии этих способов требуется дополнительная конфигурация. Но это нормально,
потому что журналирование прекрасно подцерживается в Docker.
В некоторых аспектах Docker упрощает журналирование. Во-первых, он записыва­
ет весь обычный текстовый вывод от приложений в управляемых им контейifерах.
Все, что отправляется в потоки stdout и stderr в контейнере, записывается демоном
Docker и передается в настраиваемый бэкенд журналирования. Во-вторых, как и
многие другие компоненты Docker, это модульная система, к которой можно под­
ключать разные плагины. Давайте рассмотрим основные принципы.
docker container logs
Начнем с простейшего варианта: механизм журналирования по умолчанию
в Docker. У этого механизма есть ограничения, о которых мы поговорим чуть поз­
же, но в большинстве типичных случаев применения это удобный рабочий вариант.
Если вы работаете с Docker в среде разработки, скорее всего, другая стратегия
журналирования вам не понадобится. Этот метод существовал с самого начала, он
всем понятен и хорошо подцерживается. Речь о плагине json-file. Для работы с ним
мы задаем команду docker container logs.
Как видно по названию, при использовании стандартного плагина json-file демон
Docker передает журналы приложений в файл JSON для каждого контейнера. Мы
можем в любое время просматривать журналы для любого контейнера.
Отобразить записи в журналах можно путем запуска контейнера nginx:
$ docker container run --rrn -d --narne nginx-test --rrn nginx:latest
А затем:
$ docker container logs nginx-test
2022/07/31 16:36:05 [notice] 1#1: using the "epoll" event rnethod
2022/07/31 16:36:05 [notice] 1#1: nginx/1.23.1
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
153
2022/07/31 16:36:05 [notice] 1#1: built Ьу gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/07/31 16:36:05 [notice] 1#1: OS: Linux 5.10.104-linuxkit
Такой способ удобен, потому что Docker позволяет просматривать журналы уда­
ленно, прямо из командной строки, и это удобно, если в журнал заносится немного
данных.
Чтобы просмотреть только недавние записи, укажите параметр --since и дату
в формате RFC 3339 (например 2002-10-02Т10:ОО:ОО-05:ОО), метку времени Unix
(1450071961), стандартную метку времени (20220731) или строку длительности Go
(5m45s). Также можно указать --tail и число строк с конца, которое нужно отобразить.
Файлы этого журнала хранятся на сервере Docker, по умолчанию в папке
/var/liЬ/docker/containers/<id_кoнтeйнepa>/. В файле с именем <id_кoнтeйнepa>­
json.log каждая строка представляет собой JSОN-объект. Мы увидим примерно
следующее:
{"log":"2022/07/31 16:36:05 [notice] 1#1: using the \"epoll\" event method\n",
"stream":"stdout","time":"2022-07-31T16:36:05.189234362Z"}
Именно поле log было отправлено в поток stdout. В поле stream мы видим, что это
был stdout, а не stderr. В поле time мы видим точное время, когда демон Docker по­
лучил данные. Это нестандартный формат журналирования, но он структурирован,
а это удобно, если мы захотим обрабатывать эти записи в дальнейшем.
Как и в файле журнала, в журналах Docker мы можем просматривать последние
строки по мере их появления с помощью команды docker container logs -f:
$ docker container logs -f nginx-test
2022/07/31 16:36:05 [notice] 1#1: start worker process 35
2022/07/31 16:36:05 [notice] 1#1: start worker process 36
2022/07/31 16:36:05 [notice] 1#1: start worker process 37
2022/07/31 16:36:05 [notice] 1#1: start worker process 38
Это похоже на стандартную команду docker container logs, но клиент продолжит
ждать новые сообщения и будет отображать их по мере поступления с сервера, как
это делает команда Linux tail -f. Нажмите клавиши <Ctrl>+<C>, чтобы в любое
время выйти из потока записей журнала:
$ docker container stop nginx-test
· Указав дополнительные параметры в формате --log-opt tag=" { { . ImageName}} /
{ {. ID}} ", мы можем изменить тег журнала по умолчанию, с которого будет начи­
наться каждая строка в журнале. По умолчанию записи журнала в Docker помеча­
ются первыми 12 символами идентификатора контейнера.
Описанный способ удобен для журналирования на одном хосте. Недостатки такого
подхода связаны с ротацией журналов, удаленным доступом к журналам после
Книги для программистов: https://clcks.ru/books-for-it
154
Глава б
ротации, а также местом на диске, которое занимают большие журналы. Несмотря
на то, что он основан на JSОN-файле, этот механизм настолько эффективен, что
при желании его можно использовать даже в большинстве приложений в рабочей
среде. В сложных средах, правда, потребуется более продуманный подход и
централизованное журналирование.
Параметры по умолчанию в dockerd в настоящее время не пода.ерживают ротацию
журналов. Для рабочей среды укажите параметры --log-opt max-size и --log-opt
max-file в командной строке или файле конфиrурации daemon.json. Эти параметры
ограничивают максимальный размер файла до ротации и максимальное число
хранящихся файлов журнала соответственно, причем max-file не будет работать,
если мы не зададим max-size, чтобы указать Docker, когда выполнять ротацию.
Если этот параметр включен, механизм docker container logs вернет данные толь­
ко из текущего файла журнала.
Расширенные возможности журналирования
/
Если механизма по умолчанию недостаточно (а в больших системах так и будет),
Docker поддерживает настраиваемые бэкенды журналирования. Список доступных
плагинов постоянно растет. На момент написания поддерживаются json-file (мы
уже упомянули его), а также syslog, fluentd, journald, gelf, awslogs, splunk, gcplogs, local
и logentries, которые служат для отправки журналов в популярные фреймворки и
сервисы журналирования.
Список большой, и самый простой способ для масштабных систем. Docker - на­
прямую отправлять журналы контейнеров в syslog из Docker. Выбрать этот вариант
можно с помощью командной строки Docker, указав --log-driver=syslog, или задав
его по умолчанию в файле daemon.json для всех контейнеров.
Файл daemon.json содержит конфиrурацию для сервера dockerd. Обычно он хра­
нится на сервере в каталоге /etc/docker/. Для Docker Desktop этот файл можно ре­
дактировать в пользовательском интерфейсе, в разделе Preferences (Настрой­
ки)-+ Docker Engine. После внесения изменений в файл перезапустите Docker
Desktop или демон dockerd.
Кроме того, доступно несколько сторонних плагинов. У них есть свои недостатки,
и главный из них - усложнение установки и обслуживания Docker.. Однако, воз­
можно, вы найдете стороннюю реализацию, которая идеально вписывается в вашу
систему, а ее достоинства перекрывают неудобства.
Некоторые сложности относятся ко всем драйверам журналирования. Например,
Docker не позволяет задействовать несколько вариантов одновременно. Вы може­
те применить драйвер syslog или gelf, но не вместе с json-file. Только при ис­
пользовании json-file или journald нам доступна команда docker container logs.
Это неожиданный недостаток, о котором обязательно нужно помнить при смене
драйвера.
Некоторые плагины отправляют журналы на удаленный эндпоинт и хранят локаль­
ную копию JSON для команды docker container logs, но не все плагины пода.ержи­
вают этот подход. У каждого драйвера свои подводные камни, так что приходится
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker 1
155
искать компромисс между гарантированной доставкой журналов и риском нару­
шить нормальную работу развертывания Docker. Мы рекомендуем решения на
базе UDP и другие неблокирующие варианты.
Традиционно в большинстве систем Linux есть получатель системных журналов,
например syslog, rsyslog и другие. Этот протокол в разных формах существует уже
давно и неплохо поддерживается большинством развертываний. Если компания
переходит на Docker с традиционной среды Linux или Unix, скорее всего, у нее уже
есть инфраструктура системных журналов, так что обычно это самый простой
вариант миграции.
В более новых дистрибутивах Linux часто предусмотрена система инициализации
Этот
systemd и journald в качестве механизма журналирования по умолчанию.
драйвер отличается от syslog.
И хотя syslog считается традиционным решением, у него есть свои проблемы.
Драйвер Docker syslog поддерживает протоколы TLS, ТСР и UDP, и это удобно, но
следует проявлять осторожность при потоковой передаче журналов с Docker на
удаленный сервер журналов по ТСР или TLS. Проблема в том, что они используют
ориентированные на установку соединения ТСР-сессии, и Docker пытается под­
ключиться к удаленному серверу журналирования при запуске контейнера. Если
установить соединение не удастся, Docker не сможет запустить контейнер. Если это
ваш механизм журналирования по умолчанию, то проблема может возникнуть
в любое время в любом развертывании.
Это неудобно для рабочих систем, поэтому для журналирования с драйвером syslog
рекомендуется использовать UDP. В таком случае, разумеется, журналы не будут
зашифрованы, и их доставка не гарантируется. Существуют разные стратегии жур­
налирования, и каждый раз мы ищем компромисс между сохранностью журналов и
надежностью системы. Обычно рекомендуется склоняться в сторону надежности,
но если вы работаете в защищенной среде аудита, у вас могут быть другие приори­
теты.
Записи журнала можно напрямую отправлять из одного контейнера на удаленный
сервер, совместимый с syslog, задав параметр журнала syslog-address: --log-opt
syslog-address=udp://192.168.42.42:123.
Еще одна сложность с плагинами журналирования - большинство из них являют­
ся блокирующими по умолчанию, т. е. обратное давление (back-pressure) может
привести к проблемам в приложении. Это поведение можно изменить, задав пара­
метр --log-opt mode=non-Ыocking, а затем установив максимальный размер буфера для
журналов: --log-opt max-buffer-size=4m. С такими параметрами заполнение буфера
больше не будет блокировать приложение, потому что самые старые записи в жур­
нале будут удаляться. Необходимо найти баланс между надежностью приложения
и необходимостью собирать все записи журналов.
Книги для программистов: https://clcks.ru/books-for-it
156
Глава 6
Некоторые сторонние библиотеки и программы записывают данные в файловые
системы по разным, иногда совершенно неожиданным причинам. Если вы хотите
создавать контейнеры, которые не будут напрямую записывать данные в файло­
вую систему, задайте параметры --read-only и --mount type=tnq:>fs у команды docker
container run, как мы обсуждали в главе 4. Не рекомендуется записывать журналы
в сам контейнер - до них будет сложно добраться, они не сохранятся после за­
вершения работы контейнера и могут нарушить стабильную работу файловой сис­
темы Docker.
Мониторинг в Docker
Одно из самых важных требований к рабочим системам - наблюдаемость (obser­
vaЬility) и измеряемость. Если мы не видим, как ведет себя рабочая система, мы
вряд ли достигнем желаемого результата с ее помощью. В современных средах не­
обходимо отслеживать все значимые показатели и собирать как можно больш� по­
лезной статистики. Docker поддерживает проверку работоспособности контейнеров
и базовые функции отчетности с помощью docker container stats и docker system
events. Сначала мы рассмотрим их, затем решение, предложенное Google, которое
представляет выходные данные в наглядном виде, и, наконец, экспериментальную
функцию Docker, которая экспортирует метрики контейнера в систему мониторин­
га Prometheus.
Статистика контейнера
Начнем с инструментов CLI, которые поставляются с Docker. У Docker CLI есть
эндпоинт для просмотра важной статистики по выполняющимся контейнерам.
Через этот эндпоинт инструмент командной строки может каждые несколько
секунд передавать базовую статистическую информацию о том, что происходит
в контейнерах. Команда docker container stats, как и команда Linux top, подключает­
ся к терминалу и обновляет строки на экране, отображая текущую информацию.
Сложно показать этот процесс в печатном виде, но мы приведем пример. По умол­
чанию сведения обновляются каждые несколько секунд.
Статистика в командной строке
Запускаем контейнер:
$ docker container run --rm -d --name stress \
docker.io/spkane/train-os:latest \
stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М --timeout 60s
Затем выполняем команду stats, чтобы начать следить за новым контейнером:
$ docker container stats stress
CONTAINER ID NАМЕ
CPU %
МЕМ USAGE/LIMIT
МЕМ % NET I/O
la9f52f0855f stress 476.50% 36.09MiB/7.773GiB 0 .45% 1.0SkВ/0B
PIDS
BLOCK I/O
0В/0В
6
Нажмите клавиши <Ctrl>+<C>, чтобы в любое время выйти из потока stats.
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
1
157
Укажите параметр --no-stream, чтобы получить статистику на момент времени, без
обновления, и вернуться в командную строку после выполнения команды.
Давайте посмотрим, что содержит вывод. У нас есть следующая информация:
♦ идентификатор контейнера без имени;
♦ потребляемый процент ресурсов ЦП, где 100% - это одно целое ядро ЦП;
♦ используемый объем памяти и максимально доступный контейнеру объем памяти;
♦ статистика по сети и блочному вводу-выводу;
♦ число активных процессов в контейнере.
Некоторые данные особенно важны для отладки. Давайте посмотрим, как их можно
использовать.
Обратите внимание на задействованный процент лимита памяти. Слишком строгое
ограничение памяти - одна из самых распространенных проблем с контейнерами
в рабочей среде, потому что из-за него ООМ Killer в ядре Linux снова и снова оста­
навливает контейнер. Команда stats позволяет обнаруживать и решать такие про­
блемы.
Что касается статистики по вводу-выводу, если все наши приложения работают
в контейнерах, эти сведения покажут, как движутся данные по системе. До контей­
неров это было очень сложно узнать.
При отладке также полезно знать число активных процессов в контейнере. Если
приложение порождает дочерние процессы, но не завершает их, они могут бескон­
трольно накапливаться.
Одно из преимуществ docker container stats - возможность просматривать стати­
стику по всем контейнерам в единой сводке. Мы увидим много полезной информа­
ции, даже если думаем, что и так все знаем.
Более того, данные представлены в удобном для человека формате и доступны
в командной строке. Docker API предоставляет еще один эндпоинт, который дает
гораздо больше информации, чем отображается в клиенте. В предыдущих главах
мы пока не применяли API, но в этом случае через API можно получить настолько
больше данных, чем через клиент, что мы посредством curl выполняем запрос
к API и смотрим, что делает наш контейнер. Читать эти данные гораздо сложнее,
зато там больше деталей.
Почти все, что мы делаем через клиент docker, можно сделать через Docker API.
� При необходимости мы можем выполнять все эти задачи программным способом.
�
Ниже в разделе "Эндпоинт API stats" приводится базовая информация о прямом
вызове API.
Книги для программистов: https://clcks.ru/books-for-it
158
1
Глава б
Эндпоинт API, ffats
'
'
.
\
Эндпоинт /stats/, к которому .мы обратимся в :API, будет передавать статистику,
пока мы не закроем подключение. Поскольку, файлы JSON читать неудобно, мы
запросим только одну строку и приведем ее в читаемый вид с ·пqм0щью инструмен­
та jq. У нас должен быть установлен jq версии 2.6 и выше. Если он не.установлен,
пропустите пересьшку в jq, но тогда вы получите данные в необработанном и не­
удобном формате JSON. Если вы предпочитаете другой инструмент структуриро­
ванного вывода JSON, используйте его..
Обычно демоны Docker устанавливаются с предоставлением API только через unix­
coкeт, а не через. ТСР. Мы будем вызывать\ API. �ерез cu'rl с сервера Docker. Если вы
планируете отслеживать этот эндпоинт в рабочей среде; Dock.er API необходимо
предоставлять через ТСР-порт. Мы не рекомендуем такой родход; Н() в докумецта/
ции Docker (https://dockr.ly/2Lzuox2) вь1 найдете подробные инструкции.
Если вы не находитесь. на сервере Docker иnи используете Oocker л�_каr�ьно, про­
верьте содержимое переменной среды DOCKER_нosт с помощью echo $DOCKER_нosт,
чтобы узнать
имя хоста или IР-адрес вашего сервера Docker.
.
.
.
1
Сначала запустите контейнер, о котором будете собирать статистику:
$ docker container run --rm -d --name stress \
docker.io/spkane/train-os:latest \
stress -v --ери 2 --io 1 --vm 2 --vm-bytes 128М --timeout 60s
Контейнер работает, и теперь мы можем получать непрерывный пщ·ок статистики-о
нем в формате JSON, используя curl с указанием имени или хеша контейнера.·
В след�ющих примера� мы выполняем команду curl к сокету Oock�r, но Ml;,1 можем
� обращаться и к riopтy Docker, если он доступен.
�
$ curl --no-buffer -XGET --unix-socket /var/run/docker.sock \
'.
http: //docker/containers/stress/stats
Этот поток статистики в формате JSON не остановится �в�оматически: Нажмите
· · ,
.
клавиши <Ctrl>+<C>, чтобы остановить его.
'
'
.
'
\
Мы можем получить статистику на определенный момент:
$ curl -s -XGET --unix-socket /varl,run/docker.sock \
http://docker/containers/stress/stats I head -n 1 1 jq
.
.
Наконец, используем утилиту jq (https:fstedolan.github.io/jq) или другой инструмент для преобразования JSON в удобочитаемый вид:
$ curl -s -ХGЕТ --unix-socket /yar/run/docker.sock \
http://docker/containers/stress/stats head -n 1 jq
I
1
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
11
read 11 :
159
"2022-07-31Tl7:41:59.10594836Z",
"0001-01-0lT00:00:00Z",
11 preread 11 :
{
11 pids_stats11 :
11 cu rrent":
6,
18446744073709552000
11 l:imi.t 1 1:
},
1
{
_stats
"io_service_Ьytes_recursive 11 :
11 :
11 Ыkio
11
major":
254,
minor": О,
"ор": 11 read 11 ,
11
11
value 11 : О
},
},
11
num_procs 11 : О,
11
storage_stats 11 :
11
11
cpu_stats
{
11 :
{},
cpu_usage 11 : {
11
total_usage 11 :
101883204000,
43818021000,
11
usage_in_usermode 11 : 58065183000
11
usage_in_kerne lmode 1 1:
},
},
memory_stats 1 1:
11
usage 11 : 183717888,
11
{
11
stats 11 :
11
active anon 11 : О,
11
active file 11 : О,
},
"limit":
8346021888
},
11 name 11 :
"/stress",
"id":
11
"9be7c9de26864ac97e07 fc3d8e3f fЬ5bb52cc2ba4 9f569d4ba8d407f87 47851f",
networks 11 :
{
"ethO": {
11 x_Ьytes11 :
r
1046,
"rx_packets1 1 : 9,
Книги для программистов: https://clcks.ru/books-for-it
160
1
Глава б
Информации получилось с избытком. Мы сократили вывод, чтобы не занимать
слишком много места, но он все равно немалый. Теперь вы видите, сколько данных
о каждом контейнере можно получить через API. Мы не будем вдаваться в подроб­
ности, но здесь мы видим очень подробные сведения об использовании памяти, а
также блочного ввода-вывода и процессорных ресурсов.
Если вы сами занимаетесь мониторингом, изучите возможности этого эндпоинта.
Минус в том, что мы видим статистику только по одному контейнеру, и не можем
собрать данные обо всех контейнерах с помощью одного вызова.
Проверки работоспособности контейнеров
Как и остальные приложения, контейнер после запуска может не достичь работо­
способного состояния, в котором он сможет принимать трафик. Иногда в рабочих
системах случаются сбои, и тогда приложение теряет работоспособность.
Во многих рабочих средах существуют стандартизированные способы провер�ь
работоспособность приложений. К сожалению, у нас нет четкого стандарта для
всех организаций, и каждая компания делает это по-своему. Зато есть системы
мониторинга, которые могут работать в разных производственных системах и
упрощают контроль работоспособности. Единый стандарт в этой сфере действи­
тельно не помешал бы.
В Docker для упрощения и стандартизации есть свой механизм проверки работо­
способности. Если мы продолжим метафору с контейнерами для морских грузопе­
ревозок, контейнеры Linux должны выглядеть совершенно одинаково независимо
от содержимого, и механизм проверок работоспособности Docker не только стан­
дартизирует эти проверки для контейнеров, но и позволяет отделить содержимое
контейнера от его "внешнего вида". Это значит, что контейнеры из Docker Hub или
других общих репозиториев могут реализовывать стандартизированный механизм
проверки работоспособности, и он будет работать в любой среде Docker, предна­
значенной для выполнения контейнеров в рабочей среде.
Проверки работоспособности настраиваются во время сборки и создаются с помо­
щью определения НЕАLТНСНЕСК в Dockerfile. Эта директива указывает демону Docker,
какую команду он может выполнять в контейнере, чтобы убедиться в его работо­
способном состоянии. Если код выхода команды О, Docker считает контейнер рабо­
тоспособным. Другие коды выхода указывают, что контейнер неработоспособен, и
тогда планировщик или система мониторинга могут принять меры.
В дальнейших главах мы будем использовать следующий проект для изучения
Docker Compose, а сейчас обратим внимание на пример проверок работоспособно­
сти в Docker. Скачайте копию кода и перейдите в каталог rocketchat-hubot­
demo/mongodЬ/docker/:
$ git clone https://githuЬ.com/spkane/rocketchat-huЬot-derno.git \
--config core.autocrlf=input
$ cd rocketchat-huЬot-derno/mongoc!Ь/docker
В этом каталоге находится Dockerfile и скрипт docker-healthcheck. В Dockerfile мы
увидим следующее содержимое:
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
1
161
FRCМ docker.io/bitnami/mongodЬ:4.4
# Более новый базовый Dockerfile:
# https://githuЬ.com/bitnami/containers/ЬloЬ/
# f9fЬЗf8a6323fЬ768fd488c77d4flllЫ330bdOe/bitnami/mongodЬ
# /5.0/debian-11/Dockerfile
СОРУ docker-healthcheck /usr/local/bin/
# Полезная информация:
# https://docs.docker.com/engine/reference/builder/#healthcheck
# https://docs.docker.com/compose/compose-file/#healthcheck
НЕАLТНСНЕСК СИ> ["docker-healthcheck"]
Файл короткий, потому что основан на базовом образе Mongo2, от которого наш
образ наследует много компонентов, включая точку входа, команду по умолчанию
и порт для взаимодействия.
В начале 2023 года в Bitnami провели рефакторинг репозиториев контейнеров, так
что ссылка указывает на более новую версию Dockerfile, предназначенную для
MongoDB 5.0. В этом примере используется MongoDB 4.4, но мы все равно сможем
понять суть.
EXIOSE 27017
EN'l'RYIOINТ [ "/opt/Ьitnami/scripts/mongodЬ/entrypoint.sh"
СИ> [ "/opt/Ьitnami/scripts/mongodЬ/run.sh"]
Учтите, что Docker перенаправляет трафик на порты контейнера, даже пока кон­
тейнер и базовые процессы все еще запускаются.
В наш Dockerfile мы добавляем один скрипт, который проверяет работоспо­
собность контейнера, и определяем команду проверки работоспособности, которая
запускает этот скрипт.
Соберем контейнер:
$ docker image build -t mongo-with-check:4.4
=> [internal] load build definition from Dockerfile
0.0s
=> => transferring dockerfile: 37В
0.0s
=> [internal] load .dockerignore
0.0s
=> => transferring context: 2В
0.0s
=> [internal] load metadata for docker.io/Ьitnami/mongodЬ:4.4
0.5s
0.0s
0.0s
=> CACHED [1/2] FROМ docker.io/Ьitnami/mongodЬ:4.4@sha256:9162... ae209
=> [2/2] СОРУ docker-healthcheck /usr/local/Ьin/
0.0s
=> exporting to image
0.0s
=> [internal] load build context
=> => transferring context: 40В
0.0s
2 Полный URL: https://gitbub.com/Ьitnami/containers/ЫoЬ/f9tЪ3f8a6323fЬ768fd488c77d4fl 11 Ы330Ьd0е/
Ьit nami/mongodЬ/5.0/deblan-11/Dockerf"de.
Книги для программистов: https://clcks.ru/books-for-it
Глава б
162
=> => exporting layers
0.0s
=> => writing image sha256:aбef ...da808
0.0s
=> => naming to docker.io/library/mongo-with-check:4.4
0.0s
Запустим контейнер и изучим вывод docker container 1s:
$ docker container run -d --rm --name mongo-hc mongo-with-check:4.4
5a807c892428ab0641232c82bd477fc8dll42c9el5c27d5946b8bfe7056e2695
$ docker container 1s
IМAGE
mongo-with-check:4.4
PORTS
STATUS
Up 1 second (health: starting) 27017/tcp
Обратите внимание, что в столбце sтдтus теперь есть раздел health в скобках. При
запуске контейнера будет отображаться health: starting (работоспособность: запус­
кается). Мы можем изменить время, в течение которого Docker ждет инициализа­
ции контейнера, с помощью аргумента --health-start-period для команды docker
container run. Статус изменится на healthy (работоспособный), когда контейнер
окончательно запустится и проверка работоспособности завершится успешно. Для
перехода в работоспособное состояние контейнеру может понадобиться более 40 с:
$ docker container 1s
IМAGE
... mongo-with-check:4.4
STATUS
PORTS
Up 32 seconds (healthy) 27017/tcp
Статус можно запросить напрямую командой docker container inspect:
$ docker container inspect --format=' {{.State.Health.Status))' mongo-hc healthy
$ docker container inspect --format=' ((json .State.Health)}' mongo-hc I jq
11 Status 11 :
11
"healthy",
FailingStreak11 : О,
"Log":
Если контейнер не пройдет проверку работоспособности, статус изменится на
unhealthy (неработоспособный), и нам нужно будет решить эту проблему:
$ docker container 1s
IМAGE
... mongo-with-check:4.4
STATUS
PORTS
Up 9 minutes (unhealthy) 27017/tcp
Сейчас мы можем остановить контейнер с помощью команды docker container stop
mongo-hc.
Как и в большинстве систем, мы можем настроить детали проверок работоспособ­
ности, в том числе их частоту (--health-interval), количество повторных попыток
до того, как контейнер будет признан неработоспособным (--health-retries) и дру­
гие параметры. Проверку работоспособности можно отключить совсем (--no­
healthcheck).
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker 1
163
Это полезная функция, и мы рекомендуем предусмотреть ее во всех контейнерах,
чтобы повысить надежность среды и видимость происходящего в ней. Данную
возможность легко реализовать, потому что она поддерживается многими плани­
ровщиками и системами мониторинга для рабочей среды.
� Полезность проверки работоспособности, конечно, во многом определяется тем,
насколько хорошо она написана и насколько точно определяет состояние сервиса.
�
docker system events
Демон dockerd генерирует поток событий, связанных с жизненным циклом контей­
нера. С его помощью различные части системы узнают, что происходит в других
частях. Мы можем отслеживать в этом потоке события жизненного цикла, которые
происходят с контейнерами на сервере Docker. Для этого, как вы уже могли дога­
даться, есть аргумент в Docker CLI. При выполнении этой команды мы начнем по­
лучать непрерывный поток сообщений. За него будет отвечать долгосрочный
НТТР-запрос к Docker API, который возвращает сообщения в виде JSОN-блобов по
мере поступления. Инструмент Docker CLI расшифровывает их и выводит данные
на терминал.
Поток событий позволяет осуществлять мониторинг или запускать другие дейст­
вия, например оповещения о завершении задания. При отладке мы увидим, что
контейнер завершился сбоем, даже если Docker потом перезапустил его. Позже мы
посмотрим, как напрямую реализовывать инструменты для работы с этим API.
В одном терминале выполним команду events:
$ docker system events
Ничего не произойдет.
В другом терминале запустим следующий контейнер с коротким сроком жизни:
$ docker container run --rm --name sleeper debian:latest sleep 5
В первом терминале, где выполняется команда events, мы увидим примерно сле­
дующее:
...09:59.606... container create d6... (image=debian:latest, name=sleeper)
...09:59.610... container attach d6... (image= debian:latest, name=sleeper)
...09:59.631 ... network connect еа ... (container= d60b..., name=bridge, type= bridge)
...09:59.827 ... container start d6... (image= deЬian:latest, name= sleeper)
...10:04.854... container die d6... (exitCode= 0, image= deЬian:latest, name=sleeper)
...10:04.907 ... network disconnect еа ... (container=d60b..., name=bridge, type= bridge)
...10:04.922... container destroy d6... (image= deЬian:latest, name=sleeper)
Нажмите <Ctrl>+<C>, чтобы в любое время выйти из потока событий.
Как и при просмотре статистики в Docker, мы можем изучить системные собы­
тия Docker через curl с помощью команды, вроде curl --no-buffer -XGET --unix­
socket /var/run/docker.sock http://docker/events.
Книги для программистов: https://clcks.ru/books-for-it
164
Глава б
В этом примере мы запустили временный контейнер, который завершает работу
через 5 с.
События container create, container attach, network connect и container start - это обя­
зательные этапы запуска контейнера. При завершении работы контейнера в журнал
потока событий вносятся записи container die, network disconnect и cbntainer destroy.
Каждое событие обозначает этапы завершения контейнера. Docker сообщает иден­
тификатор образа, на котором основан контейнер. Эти данные могут понадобиться,
например, для привязки развертываний к событиям, потому что развертывание
обычно связано с новым образом.
Если на каком-то сервере контейнеры все время отключаются, поток ctocker system
events позволит узнать, что и когда происходит. Нам не обязательно следить за со­
бытиями в реальном времени - Docker кеширует их, и мы можем посмотреть их
чуть позже. Параметр --since позволяет вывести события, произошедшие после
определенного момента, а параметр --until выдает события, произошедшие до
определенного момента. Мы можем указать оба параметра, чтобы выбрать период,
когда возникла интересующая нас проблема. Параметры используют формат вре­
мени ISO, как в предыдущем примере (например, 2018-02-18Т14:03:31-08:00).
Некоторые события следует отслеживать во что бы то ни стало:
container oom
(в контейнере закончилась память)
container ехес create
container ехес start
container ехес die
(кто-то попытался войти в контейнер с помощью docker container ехес, и это может
указывать на инцидент безопасности).
cAdvisor
Команды docker container stats и docker system events выдают данные в текстовом
формате, а нам нужны графики, чтобы наглядно отслеживать тренды, и для этого
существуют специальные инструменты. Когда вы будете изучать решения для мо­
ниторинга в Docker, вы заметите, что многие популярные инструменты мониторин­
га предоставляют функционал для отслеживания производительности и состояния
контейнеров.
Помимо коммерческих решений от Datadog, GroundWork, New Relic и других вен­
доров, есть бесплатные варианты с открытым исходным кодом, например
Prometheus и даже Nagios. Мы рассмотрим Prometheus далее в этой главе в разделе
"Мониторинг с помощью Prometheus". Вскоре после появления Docker Google вы­
пустил собственный инструмент мониторинга контейнеров в виде хорошо поддер­
живаемого опенсорс-проекта на GitHub - cAdvisor (https://github.com/google/
cadvisor). Инструмент cAdvisor можно использовать вне Docker, но сейчас вы уже
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker 1
165
вряд ли удивитесь, узнав, что самый простой способ реализовать cAdvisor - запус­
тить его как контейнер Linux.
Для установки cAdvisor на большинстве систем Linux достаточно выполнить при­
веденный далее код.
&
Выполните ЭТ'{ комао,ду напрямую на сервере Linux Docker. В Windows или macDS
она работать не будет.
$ docker container run \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/liЬ/docker/:/var/liЬ/docker:ro \
--volume=/dev/disk/:/dev/disk:ro \
--puЫish=BOB0:8080 \
--detach=true \
--name=cadvisor \
--privileged \
--rm \
--device=/dev/kmsg \
gcr.io/cadvisor/cadvisor:latest
UnaЬle to find image 'cadvisor/cadvisor:latest' locally Pulling repository
cadvisor/cadvisor
f0643dafd7f5: Download complete
Ьа9Ьб6За8908: Download complete
Status: Downloaded newer image for cadvisor/cadvisor:latest
f54eбbc0469f60fd74ddf30770039fla7aa36a5edaбef5100cddd9ad5fda350b
В системах на базе Red Hat Enterprise Linux (RHEL) может потребоваться добавить
� следующую строку в команду docker container run: --volume=/cgroup:/cgroup \.
�
Теперь мы сможем перейти к хосту Docker через порт 8080, чтобы открыть веб­
интерфейс cAdvisor (например, http:l/172.17.42.10:8080/), и посмотреть различные
графики с информацией о хосте и отдельных контейнерах (рис. 6.1).
cAdvisor предоставляет REST АРI-эндпоинт, через который система мониторинга
легко может запросить подробную информацию:
$ curl http://172.l7.42.l0:8080/api/v2.l/machine/
Подробности о cAdvisor API можно найти в официальной документации
(https://github.com/google/cadvisor/ЫoЬ/master/docs/api_v2.md).
Подробных сведений, предоставляемых cAdvisor, будет достаточно для получения
почти любых необходимых данных мониторинга и составления большинства нуж­
ных графиков.
Книги для программистов: https://clcks.ru/books-for-it
Глава б
166
Total Uuge
j
0.1& f-----+н--,..
0.12 t---++----t---\-+--.,�•-0.0& f------\---t+--t------+-----t--t--t--\
5:15:ЭОРМ
5:15:40РМ
5:15:50 РМ
■ Tolal
5:16:ООРМ
5:16:10РМ
\
Uuge per Core
5:15:30 РМ
■ Corw О
■ Corw 1
5:15:40 РМ
■ Corw 2
5:15:50РМ
■ Corw 3
■ Corw 4
5:16:00 РМ
■ Corw 5
5:16:10РМ
■ Corw 6
■ Corw 7
Uuge Вreakdown
Рис. 6.1. Примеры графиков ЦП в c:Advisor
Мониторинг с помощью Prometheus
Prometheus (https://prometheus.io/) - это популярное решение для мониторинга
распределенных систем. В основном он активно опрашивает источники и собирает
статистику с эндпоинтов по заданному графику. У Docker есть эндпоинт специаль­
но для Prometheus, через который мы легко можем передавать статистику контей­
нера в систему мониторинга Prometheus. На момент написания книги этот эндпоинт
предоставлялся как экспериментальная функция и по умолчанию был отключен на
сервере dockerd. Как показывает небольшой опыт, это вполне эффективное и акку­
ратное решение, и мы рассмотрим его чуть позже. Решение предназначено для моКниги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker
1
167
ниторинга сервера dockerd, в отличие от других вариантов, которые предоставляют
информацию о контейнерах.
Для экспорта метрик в Prometheus мы изменим конфигурацию сервера dockerd, что­
бы включить экспериментальные функции и предоставить слушатель (\istener) мет­
рик через выбранный порт. Это удобно, потому что не приходится открывать весь
Docker API через слушатель ТСР - потребуется чуть больше конфигурирования,
зато так гораздо безопаснее. Мы можем указать параметры --experimental и
--metrics-addr= в командной строке или внести изменения в файл daemon.json, где
хранится конфигурация демона. Многие текушие дистрибутивы используют
systemd, а изменение конфигурации зависит от особенностей конкретной установки.
Мы будем работать с daemon.json, потому что его проще переносить. Мы проде­
монстрируем этот подход на дистрибутиве UЪuntu Linux 22.04 LTS, в котором этот
файл обычно отсутствует по умолчанию. Давайте добавим файл с помощью любого
редактора.
Кроме того, файл daemon.json для Docker Desktop можно редактировать в пользо­
вательском интерфейсе, в разделе Preferences (Настройки) ..... Docker Engine .
. После внесения изменений в файл перезапустите Docker Desktop или демон
dockerd.
Отредактируйте или добавьте следующие строки в файле daemon.json:
"experimental": true,
"metrics-addr": "О.О.О.О:9323"
Итак, у нас есть файл, который содержит только то, что мы вставили, и ничего
больше.
Когда мы предоставляем доступ к сервису в сети, мы должны учитывать риски для
безопасности. Мы считаем, что метрики достаточно важны, чтобы ради них пойти
на компромисс, но советуем вам обдумать последствия в вашем конкретном сце­
нарии. Например, в большинстве случаев не стоит открывать доступ к метрикам из
Интернета.
Когда мы перезапустим Docker, порт 9323 будет прослушиваться на всех адресах.
К этому порту подключится Prometheus для получения данных. Сначала нужно пе­
резапустить сервер dockerd. Docker Desktop автоматически выполняет перезапуск,
но для сервера Linux Docker выполните команду sudo systemctl restart docker, чтобы
перезапустить демон. После перезапуска никаких ошибок быть не должно. Если
они появились, скорее всего, что-то не так с файлом daemon.json.
Давайте протестируем эндпоинт для получения метрик с помощью curl:
$ curl -s http://localhost:9323/metrics I head -15
# HELP builder_builds_failed_total NumЬer of failed image builds
# ТУРЕ builder-builds-failed-total counter
builder_builds_failed_total{reason="build_canceled"} О
builder_builds_failed_total{reason="build_target_not_reachaЬle_error"} О
Книги для программистов: https://clcks.ru/books-for-it
168
Глава б
builder_builds_failed_total{reason="command_not_supported_error"} О
builder_builds_failed_total{reason="dockerfile_empty_error"} О
builder_builds_failed_total{reason="dockerfile_syntax_error"} О
builder_builds_failed_total{reason="error_processing_commands_error"} О
builder_builds_failed_total{reason="missing_onЬuild_arguments_error"} О
builder_builds_failed_total{reason="unknown_instruction_error"} О
# HELP builder_builds_triggered_total NurnЬer of triggered image builds
# ТУРЕ builder_builds_triggered_total counter builder_builds_triggered_total О
# HELP engine_daemon_container_actions_seconds The numЬer of seconds it
# takes to process each container action
# ТУРЕ engine_daemon_container_actions_seconds histogram
Если вы выполните эту команду локально, то получите примерно такой же вывод.
Он может немного отличаться, это нормально. Главное, чтобы вы не получили со­
общение об ошибке.
Итак, теперь у Prometheus есть точка для извлечения статистики, но нам где-то
нужно запустить сам Prometheus. Давайте сделаем это в контейнере. Сначала на­
пишем простую конфигурацию и поместим ее в /tmp/prometheus/prometheus.yaml.
Создайте файл в любом редакторе и напишите в нем следующий код:
# Сбор метрик каждые 5 секунд, присвоение монитору имени stats-monitor'
gloЬal:
scrape_interval: 5s
external laЬels:
monitor: 'stats-monitor'
# Задание будет называться DockerStats, мы подключимся к docker0
# для получения статистики. Если у docker0 другой IР-адрес, укажите его.
# 127.0.0.1 и localhost не сработают.
scrape_configs:
- joЬ_name: 'DockerStats'
static_configs:
- targets: [ '172.17.0.1:9323']
Для Docker Desktop также можно указать host.docker.internal:9323 или gateway.
docker.internal:9323 вместо 172 .17. О .1:9323. Оба имени хоста будут указывать на
IР-адрес контейнера.
Как указано в файле, используйте IР-адрес моста dockero или IР-адрес интерфейса
ens3 или eth0, поскольку localhost и 127. о. о.1 не маршрутизируются из контейнера.
Здесь мы указали обычный адрес по умолчанию для docker0. Скорее всего, вам этот
вариант подойдет.
Написав конфигурацию, давайте запустим контейнер:
$ docker container run --rm -d -р 9090 :9090 \
-v /tmp/prometheus/prometheus.yaml:/etc/prometheus.yaml \
prom/prometheus --config.file=/etc/prometheus.yaml
Книги для программистов: https://clcks.ru/books-for-it
Сбор информации в Docker 1
169
Таким образом мы запускаем контейнер и монтируем наш файл конфигурации
в контейнер, чтобы он нашел параметры для мониторинга эндпоинта Docker. Если
все запустилось нормально, мы сможем открыть браузер и перейти к порту 9090 на
хосте. Мы увидим окно Prometheus, как на рис. 6.2.
На рис. 6.2 вы видите, что мы выбрали одну из метрик- engine_daemon_events_total
(общее число событий в демоне)- и составили для нее график за короткий пери­
од. Мы можем запросить любую другую метрику в выпадающем меню. С помощью
Prometheus мы можем определить оповещения и политики оповещений на основе
метрик. Этот инструмент позволяет отслеживать гораздо больше информации, чем
сервер dockerd. Мы также можем предоставлять метрики для Prometheus из прило­
жений. Если вам интересно попробовать расширенные возможности, обратите
внимание на dockprom (https://github.com/stefanprodan/dockprom), который ис­
пользует Grafana для создания наглядных панелей мониторинга и запрашивает
метрики контейнера, вроде тех, которые предоставляются эндпоинтом Docker API
/stats.
О PrometheL.s
D Use 1оса1 tine
Q.
�э,
■ Enal>le query hisrory D Enable 1Utocon!l>lete D Enal>le Nghligl!tirQ D - lnler
tngllle_dllfllQft_events_tot1\
Т•Ые
�t
-��- -■
.
L..-liм:1,- �1• ...,_..,_1
■-
----"'•-•_m•---
--��-<•!./ -
:
,._
!
!
!
....
i
. ..
!
,о.оо
2000
10.00
--- --
___/
�
/
....
А Чi
г
/
1
/
/
1
'
.
�
,.
,,....,
Рис. 6.2. Пример графика событий в Prometheus
Книги для программистов: https://clcks.ru/books-for-it
170
1
Глава б
Самостоятельная работа
Теперь вы знаете, как получать информацию о выполняющихся контейнерах. За­
грузите пару контейнеров из реестра Docker Hub и поизучайте их самостоятельно,
чтобы запомнить команды, которые мы рассмотрели. В Docker у вас есть гораздо
больше возможностей, в том числе:
♦ Копирование файлов из контейнера и в контейнер - docker container ер.
♦ Сохранение образа в ТАR-файл - docker image save.
♦ Загрузка образа из ТАR-файла - docker image iпport.
У Docker много функций, и вы будете знакомиться с ними по ходу работы. С каж­
дым новым выпуском функций становится больше. Мы подробно рассмотрим
некоторые из них позже. Пока просто знайте, что их действительно много.
Заключение
В следующей главе мы подробно рассмотрим технические принципы работы
Docker и узнаем, как отлаживать контейнеризированные приложения.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 7
Отладка контейнеров
После развертывания приложения в рабочей среде однажды что-то непременно
пойдет не так, и нам лучше подготовиться к этому моменту. Прежде чем перехо­
дить к более сложным развертываниям, полезно научиться отлаживать контейнеры,
иначе невозможно будет понять, какие проблемы возникли в системе оркестрации.
Давайте рассмотрим принципы отладки контейнеров, тем более что отладка
контейнеризированного приложения мало чем отличается от отладки обычного
процесса в системе. Разве что нам понадобятся другие инструменты, и Docker их
предоставляет. Некоторые из них можно сравнить с обычными системными инст­
рументами, а другие дают гораздо больше.
Важно понимать, что приложение не выполняется в отдельной от других процессов
Docker системе. Они задействуют одно ядро и, в зависимости от конфигурации
контейнера, могут также совместно использовать подсистему хранения и сетевые
интерфейсы. Это значит, что система может предоставить много информации о ра­
боте контейнеров.
Если вы привыкли отлаживать приложения в среде с виртуальными машинами, ва­
шим первым побуждением будет войти в контейнер, чтобы проверить потребление
памяти и ЦП или отладить системные вызовы. Однако это необязательно. Контей­
неры могут ощушаться как слой виртуализации, но на самом деле процессы в кон­
тейнерах - это просто процессы на самом хосте Linux. Чтобы просмотреть список
процессов во всех контейнерах Linux на компьютере, войдите на сервер и введите
ps в любой командной строке. Также можно выполнить команду docker container top
где угодно, чтобы посмотреть список процессов в контейнере с точки зрения базо­
вого ядра Linux. Давайте посмотрим, что можно сделать при отладке контейнери­
зированноrо приложения, не прибегая к помощи docker container ехес или nsenter.
Вывод процесса
Первое, что мы хотим узнать при отладке контейнера, - его содержимое. Мы уже
знаем, что в Docker для этого есть встроенная команда: docker container top. Это не
единственный способ "заглянуть" в контейнер, но определенно самый простой.
Давайте посмотрим, как это работает:
$ docker container run --rm -d --name nginx-debug --rm nginx:latest
796b282bfed33a4ec864a32804ccf5cbbee688b5305f094cбfЬaf20009ac2364
Книги для программистов: https://clcks.ru/books-for-it
172
Глава 7
$ docker container top nginx-debug
UID
root
uuidd
uuidd
uuidd
uuidd
uuidd
uuidd
uuidd
uuidd
PID
2027
2085
2086
2087
2088
2089
2090
2091
2092
PPID
2002
2027
2027
2027
2027
2027
2027
2027
2027
с STIME
о 12:35
о 12:35
о 12:35
о 12:35
о 12:35
о 12:35
о 12:35
о 12:35
о 12:35
ТТУ TIME
?
?
?
?
?
?
?
?
?
00:00
00:00
00:00
00:00
00:00
00:00
00:00
00:00
00:00
CMD
nginx: master process nginx -g daemon off;
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
$ docker container stop nginx-debug
С командой docker container top мы передаем имя или идентификатор контейнера, а
в ответ получаем список процессов внутри контейнера, упорядоченный по PID, как
в обычных выходных данных Linux команды ps.
Правда, здесь есть несколько особенностей. Главная из них - пространство имен
идентификаторов пользователей и файловых систем.
Следует понимать, что имя пользователя для определенного идентификатора поль­
зователя (User ID, UID) может различаться в контейнерах и в хостовой системе.
У некоторых UID и вовсе может не быть именованного пользователя в файле
/etc/passwd контейнера или хоста. Дело в том, что Unix не требует привязывать
к UID именованного пользователя, и пространства имен Linux, о которых мы пого­
ворим подробнее в разделе "Пространства имен" главы 11, обеспечивают некото­
рую изоляцию между пользователями в контейнере и на базовом хосте.
Давайте рассмотрим конкретный пример. Допустим, у нас есть рабочий сервер
Docker на Ubuntu 22.04, а на нем контейнер с дистрибутивом Ubuntu. Если мы вы­
полним следующие команды на хосте Ubuntu, то увидим, что UID 7 имеет имя lp:
$ id 7
uid=7(lp) gid=7(lp) groups=7(lp)
Номер UID здесь не имеет особого значения, вам не нужно его записывать. Он был
выбран, потому что принят по умолчанию на обеих платформах, но представляет
разные имена пользователей.
Если затем мы войдем в стандартный контейнер Fedora на этом хосте Docker, то
увидим, что для UID задано halt в /etc/passwd. Выполнив следующие команды, мы
увидим, что у контейнера совершенно другие представления о том, с каким пользо­
вателем связан UID 7:
$ docker container run --rm -it fedora:latest /bin/bash
rооt@с399сЬ807еЬ7:/1 id 7
uid=7(halt) gid=0(root) groups=0(root)
rооt@с399сЬ807еЬ7:/1 grep х:7: /etc/passwd
halt:x:7:0:halt:/sbin:/sbin/halt
root@409c2a8216Ьl:/1 exit
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
173
Если выполнить ps aux на теоретическом сервере Ubuntu Docker, пока контейнер
выполняется под UID 7 (-u 7), то мы увидим, что хает Docker считает, будто про­
цесс в контейнере работает от имени lp, а не halt:
$ docker container run --rm -d -и 7 fedora:latest sleep 120 55...сб
$ ps aux grep sleep
lp
2388 0.2 О.О
2204
... 0:00 sleep 120
784?
vagrant
2419 о.о о.о
5892 1980 pts/0
... 0:00 grep --color=auto sleep
Это особенно сбивало бы с толку, если бы какой-нибудь известный пользователь,
например nagios или postgres, был настроен в хает-системе, но не в контейнере, при
этом контейнер выполнял бы этот процесс с тем же идентификатором. Из-за раз­
ных пространств имен вывод команды ps выглядит странно. Например, если не
вглядываться, может показаться, что пользователь nagios на хаете Docker выполняет
демон postgresql, запущенный в контейнере.
Одно из решений этой проблемы - назначить ненулевой UID контейнерам. На
серверах Docker мы можем создать пользователя container с UID 5000, а затем
создать того же пользователя в базовых образах контейнера. Если затем запускать
все контейнеры под UID 5000 (-u 5000), мы повысим безопасность системы, по­
скольку процессы в контейнере не будут иметь UID О. Кроме того, будет проще
читать вывод команды ps на хосте Docker, т. к. для всех выполняющихся в контей­
нере процессов будет отображаться пользователь container. В некоторых систе­
мах для этой цели применяется пользователь nobody или daemon, но мы предпочи­
таем указывать container для ясности. В разделе "Пространства имен" главы 11
мы подробнее рассмотрим, как это работает.
Поскольку процесс видит файловую систему по-другому, то пути, которые приво­
дятся в выводе ps, показаны относительно контейнера, а не хоста. В таких случаях
очень полезно знать, что процесс находится в контейнере.
Таким образом мы можем посмотреть на содержимое контейнера с помощью инст­
рументов Docker. Но это не единственный способ, и для отладки не самый удоб­
ный. Если мы зайдем на сервер Docker и выполним обычную команду Linux ps, то
получим полный список всех процессов в контейнерах и за их пределами, и эти
процессы будут выглядеть одинаково. Есть несколько способов просмотреть вывод
процесса, чтобы лучше понять происходящее. Например, мы можем упростить от­
ладку, изучая вывод команды Linux ps в виде дерева, чтобы увидеть все процессы,
происходящие от Docker.
Виртуальная машина Docker Desktop содержит минимальные версии большинства
инструментов Linux, и некоторые из этих команд могут не давать тот же вывод,
который мы получили бы при использовании стандартного сервера Linux в качест­
ве хоста демона Docker.
Вот как может выглядеть вывод при использовании флагов командной строки BSD
для изучения системы, в которой выполняются два контейнера (здесь приводится
только самая интересная часть):
$ ps axlfww
/usr/Ыn/containerd
Книги для программистов: https://clcks.ru/books-for-it
Глава 7
174
... /usr/bin/dockerd -Н fd:// --containerd=/run/containerd/containerd.sock
... \_ /usr/bin/docker-proxy -proto tcp -host-ip О.О.О.О -host-port 8080 \
-container-ip 172.17.0.2 -container-port 8080
... \_ /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 \
-container-ip 172.17.0.2 -container-port 8080
/usr/bin/containerd-shim-runc-v2 -namespace moby -id 97 ...Зd -address /run/ ...
\_ sleep 120
/usr/bin/containerd-shim-runc-v2 -namespace moby -id 69 ...7с -address /run/ ...
Многие команды ps в этом примере работают только на дистрибутивах Linux с пол­
ной поддержкой команды ps. Упрощенные версии Linux, например Alpine, исполь­
зуют командную оболочку BusyBox, которая не полностью поддерживает ps и вы­
дает не все данные. Рекомендуется установить полноценный дистрибутив, например Ubuntu или Fedora CoreOS.
Здесь мы видим, что выполняется один экземпляр containerd. Это главная среда вы­
полнения контейнеров, используемая демоном Docker. У dockerd есть два выпол­
няющихся подпроцесса docker-proxy, о которых мы подробнее поговорим далее
в этой главе в разделе "Изучение сети".
Каждый процесс, использующий containerd-shim-runc-v2, представляет один контей­
нер и все процессы, вьшолняющиеся внутри этого контейнера. В этом примере
у нас два контейнера. Они отображаются как containerd-shim-runc-v2 с дополнитель­
ной информацией о процессе, включая идентификатор контейнера. В этом случае
выполняется один экземпляр Google cadvisor и один экземпляр sleep в другом кон­
тейнере. У каждого контейнера с сопоставленными портами есть как минимум
один процесс docker-proxy, который служит для проброса необходимых сетевых
портов между контейнером и сервером Docker. В этом примере оба процесса docker­
proxy связаны с cadvisor. Один пробрасывает порты для адресов 1Pv4, а другой­
для IPvб.
Поскольку вывод команды ps представлен в виде дерева, мы видим, какие процессы
в каких контейнерах выполняются. Если вы предпочитаете использовать флаги
командной строки Unix SysV, то можете получить похожий, хотя и не такой на­
глядный, результат с помощью ps -еjн:
$ ps -ejH
containerd
dockerd
docker-proxy
docker-proxy
containerd-shim
cadvisor
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
1
175
containerd-shim
sleep
Более лаконичный вид дерева процессов docker можно получить с помощью коман­
ды pstree. Здесь мы указали pidof, чтобы посмотреть дерево, относящееся к docker:
$ pstree 'pidof dockerd'
dockerd--т-<Jocker-proxy-7*[{docker-proxy}]
�ocker-proxy-6*[{docker-proxy}]
L10* [ {dockerd}]
Мы не видим PID, но можем получить представление, как все связано друг с дру­
гом. Это очень наглядный вид, если на хаете выполняется много процессов. Тут нет
ничего лишнего, просто общая схема взаимосвязей. Мы видим те же контейнеры,
что и в предыдущем выводе команды ps, но дерево свернуто, и отображается только
число, например, 7*, если у нас семь одинаковых процессов.
Мы можем просмотреть полное дерево с идентификаторами PID с помощью ко­
манды pstree:
$ pstree -р 'pidof dockerd'
dockerd (866)--т-<Jocker-proxy {3050) -т-{docker-proxy} (3051)
f-{docker-proxy} (3052)
1
f-{docker-proxy} (3053)
1
f-{docker-proxy) {3054)
1
f-{docker-proxy}
(3056)
1
f-{docker-proxy}
(3057)
1
L(
docker-proxy}
(3058)
1
�ocker-proxy (3055) -т- {docker-proxy} (3059)
f-{docker-proxy} (3060)
1
f-{docker-proxy} (3061)
1
f-{docker-proxy} (3062)
1
1
f-{docker-proxy} (3063)
1
L(ctocker-proxy} (3064}
f-{dockerd} (904)
f-{ctockerd} (912)
f-{dockerd} (913}
f-{dockerd} (914)
f-{dockerd} (990)
f-{dockerd} (1014)
f-{dockerd} (1066)
dockerd} (1605)
f-{ctockerd} (1611)
L(ctockerd} (2228)
Н
В этом выводе мы видим все процессы, выполняющиеся в Docker.
Можно заглянуть в один контейнер и посмотреть его процессы, узнав идентифика­
тор главного процесса контейнера и указав его в команде pstree, чтобы вывести все
связанные подпроцессы:
Книги для программистов: https://clcks.ru/books-for-it
176
Глава 7
$ ps aux I grep containerd-shim-runc-v2
root 3072 ... /usr/bin/containerd-shim-runc-v2 -namespace moby -id 69...7с
root 4489 ... /usr/bin/containerd-shim-runc-v2 -namespace moby -id fl ...46
vagrant 4651 ... grep --color=auto shim
$ pstree -р 3072
containerd-shim(3072)--т-<:advisor(3092) -т- {cadvisor) (3123)
f-{cadvisor} (3124)
1
f-{cadvisor} (3125)
1
f-{cadvisor) (3126)
1
f-{cadvisor) (3127)
1
f-{cadvisor)
(3128)
1
f-{cadvisor}
(3180)
1
f-{cadvisor} (3181)
1
L{cadvisor} (3182)
1
f-{containerd-shim} (3073)
f-{containerd-shim} (3074)
f-{containerd-shim} (3075)
f-{ containerd-shim} (3076)
f-{ containerd-shim} (3077)
f-{ containerd-shim} (3078)
f-{containerd-shim} (3079)
f-{ containerd-shim} (3080)
f-{ containerd-shim) (3121)
L{containerd-shim} (3267)
Изучение процесса
Войдя на сервер Docker, мы можем проверять выполняемые процессы с помощью
всех стандартных инструментов отладки. Привычные инструменты, вроде strace,
будут работать обычным образом. В следующем коде мы проверим процесс nginx,
выполняющийся внутри контейнера:
$ docker container run --rm -d --name nginx-debug --rm nginx:latest
$ docker container top nginx-debug
UID
PID PPID
CMD
22983 22954
root
nginx: master process nginx -g daemon off;
systemd+ 23032 22983
nginx: worker process
systemd+ 23033 22983
nginx: worker process
$ sudo strace -р 23032
strace: Process 23032 attached
epoll_pwait(10,
�
�
Когда закончите, нажмите <Ctrl>+<C>, чтобы выйти из процесса strace, если вы
использовали именно его.
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
177
Мы получаем такой же вывод, как и от процессов на хосте, которые выполняются
не в контейнерах. Подобным образом lsof показьmает, что файлы и сокеты, откры­
тые в процессе, работают так, как ожидалось:
$ sudo lsof -р 22983
СОММАND
PID
USER ...NАМЕ
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
nginx
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
22983
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
root
.../
.../
... /usr/sbin/nginx
.../usr/sbin/nginx (stat: No such file or directory)
.../liЬ/aarch64-linux-gnu/libnss_files-2.31.so (stat:
.../liЬ/aarch64-linux-gnu/libc-2.31.so (stat: ...
.../liЬ/aarch64-linux-gnu/libz.so.l.2.ll (path inode=...)
.../usr/liЬ/aarch64-linux-gnu/libcrypto.so.l.l (stat: ...
.../usr/liЬ/aarch64-linux-gnu/libssi.so.l.l (stat:
.../usr/liЬ/aarch64-linux-gnu/libpcre2-8.so.0.10.l (stat:
.../liЬ/aarch64-linux-gnu/libcrypt.so.l.l.O (path ...
.../liЬ/aarch64-linux-gnu/libpthread-2.31.so (stat:
.../liЬ/aarch64-linux-gnu/liЬdl-2.31.so (stat: ...
.../liЬ/aarch64-linux-gnu/ld-2.31.so (stat: ...
.../dev/zero
... /dev/null
...pipe
...pipe
...pipe
...protocol: UNIX-STREAМ
...pipe
...pipe
...protocol: ТСР
...protocol: ТСРvб
...protocol: UNIX-STREAМ
...protocol: UNIX-STREAМ
...protocol: UNIX-STREAМ
Все пуrи к файлам указаны относительно вида на файловую систему изнутри кон­
тейнера, а не с хоста. По этой причине на уровне хост-системы не так-то просто
найти конкретный файл из выполняющегося контейнера. В большинстве случаев
лучше войти в контейнер командой docker container ехес, чтобы смотреть на файлы
с той же точки, с которой их видят процессы внутри контейнера.
Мы можем точно так же запустить отладчик GNU (gdЬ) и другие инструменты про­
верки процессов, если мы работаем с правами root и у нас есть необходимые раз­
решения.
Стоит отметить, что мы можем запустить новый отладочный контейнер, который
будет иметь доступ к процессам в существующем контейнере и предоставит до­
полнительные инструменты для отладки (мы подробно обсудим эту команду позже,
в главе 11 в разделах "Пространства имен" и "Безопасность''):
Книги для программистов: https://clcks.ru/books-for-it
178
Глава 7
$ docker container run -ti --rm --cap-add=SYS_PТRACE \
--pid=container:nginx-debug
spkane/train-os:latest bash
°
[root@e4Ь5d2f3a3a7 /J il ps aux
USER PID %CPU %МЕМ
TIME COMМAND
root 1
О.О О.2
0�00 nginx: master process nginx -g daemon off;
101 30
О.О О.1
0:00 nginx: worker process
101 31
о.о 0.1
0:00 nginx: worker process
0:00 bash
root 136 о.о 0.1
root 152 о.о 0.2
0:00 ps aux
[root@e4b5d2f3a3a7 /]# strace -р 1
strace: Process 1 attached rt_sigsuspend([], 8
[Control-C]
strace: Process 1 detached
<detached ...>
[root@e4b5d2f3a3a7 /]# exit
$ docker container stop nginx-debug
� Нажмите <Ctrl>+<C>, чтобы выйти из процесса зtще.
Контроль процессов
Если у нас есть командная оболочка, напрямую подключенная к серверу Docker,
мы можем во многих смыслах работать с контейнеризированными процессами так
же, как с любыми другими процессами в системе. Если мы работаем удаленно, то
можем отправлять сигналы с помощью docker container kill, потому что это будет
просто. Но если мы уже вошли на сервер Docker - для отладки или потому что
демон Docker не отвечает, - мы можем с помощью kill принудительно завершить
процесс, как завершили бы любой другой.
Если только мы не завершаем в контейнере процесс верхнего уровня (с идентифи­
катором PID 1 ), после завершения процесса контейнер продолжит работать. Воз­
можно, этого мы и хотели, если нам нужно было завершить нестабильный процесс,
но иногда в результате контейнер переходит в непредвиденное состояние. Разра­
ботчики ожидают, что все процессы выполняются, если контейнер отображается
в выводе команды docker container 1s. Кроме того, могут возникнугь проблемы
с планировщиком, например Mesos, или Kubernetes и другими системами, которые
проверяют работоспособность приложения. Помните, что для "внешнего мира" все
контейнеры выглядят одинаково. Если мы завершаем какой-то процесс внутри кон­
тейнера, лучше заменить контейнер целиком. Контейнеры - это абстракция, с ко­
торой взаимодействуют разные инструменты. Ожидается, что внутри контейнеры
остаются предсказуемыми и согласованными.
Завершение процессов - не единственная причина отправлять сигналы. Поскольку
контейнеризированные процессы во многих отношениях похожи на обычные, им
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
1
179
можно передавать все сигналы Unix, которые приводятся в руководстве для коман­
ды Linux kill. Многие программы Unix выполняют специальные действия при по­
лучении определенных сигналов. Например, nginx повторно открывает журналы
при получении сигнала SIGUSRl. Вместе с командой Linux kill можно оmравлять
любой сигнал Unix в процесс контейнера на локальном сервере.
Управление процессами в контейнерах
Если вы не используете оркестратор, например Kubernetes, который управляет несколь­
кими контейнерами в более крупной абстракции, вроде пода (pod), рекомендуется при­
менять механизмы управления процессами в контейнерах в рабочей среде. Это �ожет
быть tini (https://github.com/krallin/tini), upstart (https://upstart.ubuntu.com/), runit
(http://smarden.org/runit), s6 (https://skarnet.org/software/s6) или что-то подобное. Та­
кой подход позволяет работать· с контейнером как с неделимым целым, даже если он
содержит несколько процессов. Все же постарайтесь приложить все усилия, чтобы
в контейнере выполнялся только один процесс. Пусть он отвечает за одну хорошо оп­
ределенную задачу и не превращается в монолит.
В любом случае команда docker container 1s должна показывать присутствие целого
контейнера, чтобы мы не задумывались о работоспособности отдельного процесса
внутри него. Если предположить, что наличие контейнера и отсутствие записи об ошиб­
ках в журнале означает, что все работает, можно считать, что вывод docker container 1s
действительно отражает то, что происходит в системах Docker. Кроме того, так может
считать и применяемая нами система оркестрации.
Мы должны хорошо понимать поведение выбранного сервиса управления процессами,
включая потребление памяти или пространства на диске, обработку сигналов Unix
и т. д., потому что это может повлиять на поведение и производительность контейнера.
Как правило, лучше выбирать самые легкие системы.
Поскольку контейнеры работают так же, как любые другие процессы, важно пони­
мать, каким образом они могут негативно повлиять на приложение. Некоторые
процессы в контейнере пороЖдают дочерние фоновые процессы, которые ответв­
ляются и выполняются в виде демонов, так что родительский процесс больше не
управляет их жизненным циклом. Например, в Jenkins эти процессы часто выходят
из-под контроля: демоны ветвятся на фоне и становятся дочерними процессами
процесса PID 1 в системах Unix, а процесс 1 имеет особый статус и обычно являет­
ся процессом init.
PID 1 отвечает за завершение работы дочерних процессов. По умолчанию в кон­
тейнере главным процессом будет PID 1. Поскольку мы, скорее всего, не будем
управлять завершением работы дочерних процессов в приложении, рано или позд­
но в контейнере накопятся процессы-зомби. Решить эту проблему можно несколь­
кими способами. Например, запустить в контейнере систему инициализации init,
которая сможет выполнять обязанности PID 1. Для этого подойдут sб, runit и дру­
гие механизмы, которые мы упомянули ранее.
Сам Docker предлагает еще более простой вариант, который решает только эту
проблему, не выполняя все задачи системы init. Если указать флаг --init в команде
docker container run, Docker запустит очень маленький процесс init на основе проек­
та tini, который будет выступать в качестве процесса PID 1 в контейнере при
Книги для программистов: https://clcks.ru/books-for-it
180
Глава 7
запуске. То, что мы укажем в Dockerfile в качестве смо, передается в tini, и в ос­
тальном все будет работать вполне ожидаемо. Правда, будет заменено все содер­
жимое раздела ENTRYPOINT в Dockerfile.
Если мы запустим контейнер Linux без флага --init, то в списке процессов увидим
что-то подобное:
$ docker container run --rm -it alpine:3.16 sh
/ # ps -ef
TIME
USER
СОММАND
PID
1
root
0:00
sh
5
root
0:00
ps -ef
/ # exit
Обратите внимание, что в этом случае смо запускается с PID 1, а значит, отвечает за
завершение работы дочерних процессов. Если необходимо, при запуске контейнера
мы можем передать параметр --init, чтобы точно завершить дочерние процессы
при завершении родительского:
$ docker container run --rm -it --init alpine:3.16 sh
/ # ps -ef
PID
USER
TIME
СОММАND
/sbin/docker-init -- sh
1
root
0:00
root
0:00
sh
5
root
0:00
ps -ef
6
/ # exit
Здесь процесс с PID 1 - /sЫn/docker-init. При этом запускается код оболочки, как
указано в командной строке. Поскольку теперь в контейнере есть система инициа­
лизации, обязанности процесса PID 1 будет выполнять она, а не команда, которой
мы вызвали контейнер. В большинстве случаев это как раз то, что нам нужно. Вам
может не понадобиться система инициализации, но она занимает очень мало места,
так что в контейнерах в рабочей среде можно запускать хотя бы tini.
Как правило, процесс init в контейнере понадобится, только если мы запускаем
несколько родительских процессов или некоторые процессы не отвечают на сигна­
лы Unix должным образом.
Изучение сети
Отлаживать контейнеризированные приложения на уровне сети сложнее, чем на
уровне процессов. В отличие от обычных процессов, выполняющихся на хаете,
контейнеры Linux могут подключаться к сети разными способами. Если мы остави­
ли настройки по умолчанию, как это обычно бывает, то все контейнеры будут под­
ключень1 к сети через сеть bridge, по умолчанию создаваемую Docker. Это вирту­
альная сеть, в которой хает служит шлюзом для связи с внешним миром. Эти вир­
туальные сети можно проверять с помощью инструментов, поставляемых с Docker.
Мы можем посмотреть существующие сети командой docker network 1s:
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
$ docker network ls
NEТWORК ID
f9685b50d57c
Bacae 1680cbd
fЬ70d67499d3
NАМЕ
bridge
host
попе
181
SCOPE
DRIVER
bridge
host
null
local
local
local
Здесь мы видим сеть bridge, сеть host, которая доступна для всех контейнеров, рабо­
тающих в режиме host (см.раздел "Сеть хоста" главы 11), и сеть попе, которая пол­
ностью отключает доступ к сети для контейнера. Если вы используете docker compose
или друтие инструменты оркестрации, они могуr создавать дополнительные сети
с друтими именами.
Правда, мы видим только названия сетей и не знаем, что в них происходит. Чтобы
посмотреть, какие контейнеры подключены к определенной сети, можно подать
команду docker network inspect. Мы получим довольно много вывода, в котором
будут видны контейнеры в конкретных сетях, а также информация о самой сети.
Давайте посмотрим на сеть bridge по умолчанию:
$ docker network inspect bridge
"Name": "bridge",
"Driver": "bridge",
"EnaЬleIPvб": false,
"Contai.ners":
"69е9 ...с87с":
"Name": "cadvisor",
},
"IPv4Address": "172.17.0.2/16",
"IPvбAddress":
"а2а8 ...е163":
"Name": "nginx-debug",
"IPv4Address": "172.17.0.3/16",
"IPvбAddress" : ""
},
"Options":
"com.docker.network.Ьridge.default_bridge": "true",
"com.docker.network.bridge.host_Ьinding_ipv4": "О.О.О.О",
"com.docker.network.bridge.name": "docker0",
},
"LaЬels": {}
Книги для программистов: https://clcks.ru/books-for-it
182
Глава 7
Для краткости здесь приводится не вся информация, но мы видим два контейнера
в сети bridge, подключенные к мосту dockerO на хосте. Кроме того, мы видим IР-ад­
реса всех контейнеров (IPv4Address и IPv6Address) и адрес сети host, к которой они
подключены (host_Ьinding_ipv4). Эта информация помогает понять внутреннюю
структуру мостовой сети. Если контейнеры подключены к разным сетям, они могут
не взаимодействовать друг с другом, но это зависит от конфигурации сетей.
Как правило, рекомендуется оставить подключение контейнеров к сети bridge по
умолчанию, если только у вас нет веских причин подключить их к другой сети или
вы не используете docker compose или планировщик, который сам управляет сетями
контейнеров. Кроме того, можно присвоить контейнерам понятные имена, потому
что здесь мы не видим информацию об образах. В этом выводе указаны только
имя и идентификатор, по которым мы можем найти контейнер в листинге docker
container 1s. Некоторые планировщики не присваивают контейнерам понятные
имена, и это затрудняет отладку.
Мы уже видели, что у контейнеров обычно есть свой сетевой стек и свой IР-адрес,
если они выполняются не в режиме host, который мы обсудим в разделе "Сети"
главы 11. А как это будет выглядеть с самого хает-компьютера? У контейнеров есть
своя сеть и адреса, так что они не будут отображаться во всем выводе netstat на
хосте. Мы знаем, что порты, проброшенные на контейнеры, привязаны к хосту.
Команда netstat -an на сервере Docker дает ожидаемый результат:
$ sudo netstat -an
Active Internet connections (servers and estaЫished)
Foreign Address
Proto Recv-Q Send-Q Local Address
tcp
О
О
0.0.0.0:8080
О.О.О.О:*
О.О.О.О:*
о
о
127.0.0.53:53
tcp
О.О.О.О:*
0.0.0.0:22
о
о
tcp
192.168.15.120:63920
о
192.168.15.158:22
о
tcp
. . .*
:::8080
о
tcp6 о
. . .*
о
:::22
tcp6 о
О.О.О.О:*
о
127.0.0.53:53
о
udp
О.О.О.О:*
о
о
192.168.15.158:68
udp
. . .*
о
:::58
raw6 о
State
LISTEN
LISTEN
LISTEN
ESTABLISHED
LISTEN
LISTEN
7
Здесь мы видим все интерфейсы, которые прослушиваем. Наш контейнер привязан
к порту 8080 по IР-адресу о.о.о.о, и это отображается в выводе. Что будет, если мы
захотим, чтобы команда netstat показала имя процесса, привязанного к порту?
$ sudo netstat -anp
Active Internet connections (servers and estaЫished)
Local Address
Foreign Address
Proto
О.О.О.О:*
0.0.0.0:8080
tcp
О.О.О.О:*
127.0.0.53:53
tcp
О.О.О.О:*
0.0.0.0:22
tcp
192.168.15.120:63920
192.168.15.158:22
tcp
. . .*
: ::8080
tcp6
PID/Program name
1516/docker-proxy
692/systemd-resolve
780/sshd: /usr/sЬin
1348/sshd: vagrant
1522/docker-proxy
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
tcp6
udp
udp
raw6
:::22
127.0.0.53:53
192.168.15.158:68
:::58
: : :*
О.О.О.О:*
О.О.О.О:*
: : :*
183
780/sshd: /usr/sЬin
692/system:i-resolve
690/system:i-network
690/system:i-network
Мы видим тот же вывод, но обратите внимание, что к порту привязан docker-proxy.
Дело в том, что в конфигурации по умолчанию у Docker есть прокси, написанный
на Go, который находится между всеми контейнерами и внешним миром. Получа­
ется, что в этом выводе все контейнеры, выполняющиеся через Docker, будут при­
вязаны к docker-proxy. Обратите внимание, что здесь мы не видим связь между
docker-proxy и конкретным контейнером. Это не страшно, потому что команда docker
container ls показывает привязки между контейнерами и портами. Правда, это не­
очевидно, и об этом стоит знать, прежде чем приступать к отладке сбоя в рабочей
среде. Мы можем передать флаг р с командой netstat, чтобы посмотреть, какие пор­
ты привязаны к контейнерам.
Если контейнер использует сеть host, этот уровень пропускается. Здесь нет docker­
proxy, и процесс в контейнере может напрямую привязываться к порту. Он также
отображается как обычный процесс в выходных данных netstat -anp.
Другие команды для проверки сети работают очевидным образом, вкmочая tcpdwrp,
но нужно помнить о docker-proxy между сетевым интерфейсом хоста и контейнером,
а также о том, что у контейнеров есть свои сетевые интерфейсы в виртуальной
сети.
История образа
При сборке и развертывании одного контейнера несложно отследить его происхо­
ждение и базовый образ. Когда мы развертываем больше контейнеров и над при­
ложением работает несколько команд, задача усложняется в разы. Как узнать, на
каких слоях основан определенный контейнер? Тег образа контейнера показывает,
какая сборка приложения используется, но по тегу невозможно ничего узнать
о слоях, на которых строится приложение. К счастью, у нас есть команда docker
image history. Она показывает все слои в рассматриваемом образе, их размеры и
команды, с помощью которых образ собирался:
$ docker image history redis:latest
...CREATED ВУ
IМAGE
e800a8da9469 .../Ьin/sh -с #(nop) CMD ["redis-server"]
<missing>
.../Ьin/sh -с #(nop) EXPOSE 6379
.../Ьin/sh -с #(nop) ENTRYPOINT ["docker-entry.. .
<missing>
.../Ьin/sh -с #(nop) СОРУ file:e873a0e3cl300lb5 ...
<missing>
.../Ьin/sh -с #(nop) WORКDIR /data
<missing>
.../Ьin/sh -с #(nop) VOLUME [/data]
<missing>
.../Ьin/sh -с mkdir /data && chown redis:redis
<missing>
.../Ьin/sh -с set -eux;
savedAptMark="$ (apt-m ...
<missing>
... /Ьin/sh -с #(nop) ENV REDIS_DOWNLOAD_SНA=fO ...
<missing>
<missing>
.../Ьin/sh -с #(nop) ENV REDIS_DOWNLOAD_URL=ht .. .
SIZE
ов
ов
ов
СОММЕNТ
661В
ов
ов
ов
32,4МВ
ов
ов
Книги для программистов: https://clcks.ru/books-for-it
184
Глава 7
<missing>
<missing>
<missing>
<missing>
<missing>
<missing>
.../bin/sh -с #(nop) ENV REDIS VERSION=7.0.4
.../Ьin/sh -с set -eux; savedAptMark="$ (apt-ma...
.../Ьin/sh -с #(nop) ENV GOSU_VERSION=l.14
.../Ьin/sh -с groupadd -r -g 999 redis && usera...
... /Ьin/sh -с #(nop) CMD [ "bash"]
.../Ьin/sh -с #(nop) ADD file:6039adfЬca55ed34a ...
ов
4,ОбМВ
0В
ЗЗlkВ
0В
74,ЗМВ
Команда docker image history пригодится в ситуациях, когда мы пытаемся опреде­
лить, почему итоговый образ получился гораздо больше, чем мы ожидали. Слои
перечисляют по порядку, первый находится снизу, а последний - сверху.
Здесь мы видим, что вывод команды обрезан в некоторых местах. Дпя длинных ко­
манд мы можем добавить параметр --no-trunc к команде docker image history, чтобы
увидеть целую команду, с помощью которой собирался каждый уровень. Конечно,
при наличии --no-trunc вывод будет объемнее, а читать его станет сложнее.
Изучение контейнера
В главе 4 мы узнали, как читать вывод команды docker container inspect, чтобы уз­
нать конфигурацию контейнера. На диске хоста есть каталог, выделенный для кон­
тейнера. Обычно это /var/liЬ/docker/containers. Если заглянуть в каталог, мы увидим
очень длинные хеши SНА:
$ sudo 1s /var/lib/docker/containers
106ead0d55af55bd803334090664e4bc821c76dadf231elaab7798dlbaa19121
28970c706dЬOf69716af43527ed926acbd82581elcef5e4eбffl52fcelb79972
Зc4f916619a5dfc420396d823b42e8bd30a2f94ab5b0f42f052357a68a67309b
589f2ad30138lb7704c9cade7daбb34046ef69ebe3d6929b9bc24785d7488287
959dЬlбlld632dc27a86efcbббflc6268d948dбf22e81e2a22a57610b5070b4d
alel5f197ea0996d3lf69c332f2Ы4el8b727e53735133a230d54657acбaa5dd
bad35aac3f503121abf0e543e697fcade78f0d30124778915764d85fЫ0303a7
bc8c72c965ebca7dЬ9a2b816188773a5864aa38lb8lc3073b9d3e52e977c55ba
daa75fЫ08a33793a3f8fcef7ba65589el24af66bc52c4a070f645fffЬbc498e
e2ac800b58c4c72e240b90068402b7d4734a7dd03402ee2bce3248ccбf44d676
e8085ebcl02b5f51c13cc5c257acb2274e7f8dl645af7baad0cbбfe8eef36e24
f8e46faa3303d93fc424e289d09b4ffЬalfc7782b9878456e0fellflf6814e4b
Выглядит непонятно, но это просто длинная форма идентификаторов контейнеров.
Если мы хотим посмотреть конфигурацию для конкретного контейнера, можно уз­
нать его короткий идентификатор с помощью команды docker container 1s, а затем
найти соответствующий каталог:
$ docker container 1s
CONTAINER ID
IМAGE
с58ЬfеffЬ9еб
gcr.io/cadvisor/cadvisor:v0.44.1-test
СОММАND
"/usr/Ьin/cadvisor..."
Мы можем взять короткий идентификатор из docker container 1s и сопоставить его с
выводом 1s /var/liЬ/docker/containers, чтобы узнать, что нам нужен каталог, начи­
нающийся с с58ЫеffЬ9еб. Здесь удобно использовать функцию автозавершения ко­
манды с помощью клавиши <ТАВ>. Если требуется точное совпадение, выполните
Книги для программистов: https://clcks.ru/books-for-it
Отладка контейнеров
185
docker container inspect c58ЬfeffЬ9e6 и найдите длинный идентификатор в ее выводе.
Этот каталог содержит интересные файлы, связанные с контейнером:
$ cd /var/liЬ/docker/containers/\
c58ЬfeffЬ9e6e607f3aacb4a06ca473535Ьf9588450f08be46baa230aЬ43fld6
$ 1s -la total 48
drwx--x--- 4 root root 4096 Aug 20 10:38
drwx--x--- 30 root root 4096 Aug 20 10:25 . .
-rw-r----- 1 root root 635 Aug 20 10:34 с58Ьf... fld6-json.log
drwx------ 2 root root 4096 Aug 20 10:24 checkpoints
-rw------- 1 root root 4897 Aug 20 10:38 config.v2.json
-rw-r--r-- 1 root root 1498 Aug 20 10:38 hostconfig.json
-rw-r--r-- 1 root root 13
Aug 20 10:24 hostnarne
-rw-r--r-- 1 root root 174 Aug 20 10:24 hosts
drwx--x--- 2 root root 4096 Aug 20 10:24 mounts
-rw-r--r-- 1 root root 882 Aug 20 10:24 resolv.conf
-rw-r--r-- 1 root root 71
Aug 20 10:24 resolv.conf.hash
Как мы видели в главе 5, этот каталог содержит несколько файлов, примонтирован­
ных прямо к контейнеру с помощью Ьind mount, например hosts, resolv.conf и
hostname. Если вы используете механизм журналирования по умолчанию, то в этом
каталоге Docker будет хранить файл JSON, содержащий записи, которые отобра­
жаются по команде docker container logs, конфигурацию JSON для вывода docker
container inspect (config.v2.json) и сетевую конфигурацию для контейнера
(hostconfig.json). С помощью файла resolv.conf.hash Docker определяет, что файл
контейнера отклонился от текущего файла на хосте, чтобы его можно бьmо обно­
вить.
Этот каталог содержит важную информацию, которая поможет решить проблему
в случае серьезных сбоев. Даже если мы не можем войти в контейнер или docker не
отвечает, мы можем изучить конфигурацию контейнера. Кроме того, можно по­
смотреть, как эти файлы монтируются изнутри контейнера. Старайтесь не редакти­
ровать эти файлы. Docker ожидает, что они содержат достоверные данные, и если
вы их измените, может произойти что-нибудь неприятное. Рассматривайте их как
еще один источник информации о том, что происходит в контейнере.
Изучение файловой системы
Независимо от выбранного бэкенда, Docker использует многослойную файловую
систему, с помощью которой можно отслеживать изменения в любом контейнере.
Она помогает не только при сборке образов, но и при попытке понять, изменил ли
контейнер Linux что-нибудь, и если да, то что. Распространенная проблема с кон­
тейнеризированными приложениями - они могут записывать данные в файловую
систему контейнера. Обычно это нежелательно, и при отладке следует проверять,
записывают ли процессы что-то в контейнер. Иногда полезно изучить файлы жур­
налов в контейнере. Этот функционал также встроен в инструмент командной
строки docker и предоставляется через API. Давайте посмотрим, что мы получим.
Запустим контейнер и изучим его по имени:
Книги для программистов: https://clcks.ru/books-for-it
186
Глава 7
$ docker container run --rm -d --name nginx-fs nginx:latest
1272b950202dЬ25ee030703515f482e9ed576f8e64c926e4e535ballf7536cc4
$ docker container diff nginx-fs С /run
А /run/nginx.pid С /var
С /var/cache
С /var/cache/nginx
А /var/cache/nginx/scgi_terrq:,A /var/cache/nginx/uwsgi_terrq:, А /var/cache/nginx/client_terrq:,
А /var/cache/nginx/fastcgi_terrq:, А /var/cache/nginx/proxy_terrq:,
С /etc
С /etc/nginx
С /etc/nginx/conf.d
С /etc/nginx/conf.d/default.conf
$ docker container stop nginx-fs nginx-fs
Каждая строка начинается с А или с, по первой букве от added (добавлено) или
changed (изменено) соответственно. Мы видим, что контейнер выполняет nginx, что
данные записывались в файл конфигурации nginx и что в новом каталоге
/var/cache/nginx созданы временные файлы. В результате можно понять, как ис­
пользуется файловая система контейнера, оптимизировать и контролировать ее
применение.
Дальнейшее детальное изучение требует применять команды docker container export,
docker container ехес, nsenter и т. д., чтобы посмотреть, что именно находится в фай­
ловой системе. Для начала работы будет достаточно команды docker container diff.
Заключение
Вы уже хорошо представляете себе, как развертывать и отлаживать отдельные кон­
тейнеры в среде разработки и рабочей среде, но как масштабировать эту систему
для крупных экосистем приложений? В следующей главе мы рассмотрим простой
инструмент оркестрации Docker - Docker Compose. С его помощью можно перей­
ти от одиночного контейнера Linux к системе оркестрации производственного
класса. Это хороший вариант для сред разработки и конвейеров DevOps.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА8
Принципы работы
Docker Compose
Мы подробно рассмотрели, как с помощью команд docker собирать, запускать, от­
слеживать и отлаживать приложения. Научившись работать с отдельными контей­
нерами, вы захотите делиться своими проектами и создавать более сложные
приложения, работающие на нескольких контейнерах. Особенно это касается сред
разработки, в которых мы можем запустить целый стек контейнеров, чтобы имити­
ровать несколько рабочих сред на локальном компьютере.
В стеке каждому контейнеру потребуется соответствующая конфигурация, чтобы
приложение правильно функщюнировало. Настраивать каждый отдельный контей­
нер сложно, особенно если приложение написано кем-то другим. Во время разра­
ботки для выполнения этой задачи обычно используют shеll-скрипты (скрипты
командной оболочки), которые собирают и запускают множество контейнеров по
одной схеме. Это работает, но новым сотрудникам сложно разобраться в системе,
и эти скрипты непросто обслуживать, когда со временем в проект добавляется все
больше изменений. Не всегда одни и те же скрипты подойдут для разных проектов.
Поэтому Docker, Inc. предлагает инструмент Docker Compose, ориентированный
в основном на разработчиков. Этот инструмент поставляется с Docker Desktop,
но его можно установить, выполнив эти инструкции (https://docs.docker.com/
соm pose/install).
Изначально Docker Compose был отдельным приложением, написанным на Python,
которое запускалось командой docker-compose. Эта команда считается первой вер­
сией Docker Compose, и недавно ее заменил Docker Compose версии 2. Этот
� Docker Compose полностью переписан на Go и поставляется как nлагин для клиен­
та Docker. Если docker compose version возвращает результат, плагин установлен.
Если не установлен, установите его сейчас.
Docker Compose - полезный инструмент, оптимизирующий разные задачи по раз­
работке, которые раньше считались очень трудоемкими и вызывали много ошибок.
С его помощью разработчики быстро запускают сложные приложения, компили­
руют приложения, не создавая громоздкие локальные среды разработки и т. д.
В этой главе мы рассмотрим, как эффективно применять возможности Docker
Compose. Во всех следующих примерах нам понадобится репозиторий GitHub.
Если вы будете выполнять примеры, но еще не загрузили код, приведенный в гла­
ве 6, запустите следующую команду:
$ git clone https://githuЬ.com/spkane/rocketchat-huЬot-demo.git \
--config core.autocrlf=input
Книги для программистов: https://clcks.ru/books-for-it
188
Глава 8
В этом примере файлы shеll-скриптов docker-compose.yaml были обрезаны для
краткости. Обязательно используйте файлы из этого репозитория Git для выпол­
нения примеров.
Этот репозиторий содержит конфигурацию, которая понадобится нам для запуска
полноценного веб-сервиса с хранилищем MongoDB, сервер для коммуникаций
Rocket.Chat с открытым исходным кодом, бот Hubot ChatOps (https://goo.gl/
hKT3QW) и инстанс zmachine-api для развлекательных целей.
Настройка Docker Compose
Прежде чем мы начнем выполнять команду docker compose, давайте посмотрим, ка­
кой специальный инструмент она заменяет, - изучим shеll-скрипты, с помощью
которых мы могли бы собрать и развернуть локальную копию сервиса для разра­
ботки и локального тестирования в Docker. Вывод будет очень длинным и подроб­
ным, зато вы увидите, почему Docker Compose гораздо удобнее shеll-скриптов.
�
�
Не рекомендуем выполнять этот скрипт. Это просто пример, и в вашей среде он
может не сработать или что-то испортить.
Jl!/Ьin/bash
jl Не запускайте этот скриnт.
exit 1
JI Сам скриnт JI
JI Примечание. Скриnт не обновляется в соответствии с файлом docker-coпpose
JI и nриводится только для информации.
set -е set -u
if [ $JI -ne О ] && [ ${1} = "down" ] ; then
docker rm -f huЬot 11 true
docker rm -f zmachine 11 true
docker rm -f rocketchat 11 true
docker rm -f mongo-init-replica 11 true
docker rm -f mongo 11 true
docker network rm botnet 11 true
echo "Environment torn down ..."
exit О
fi
JI Глобальные настройки
export РОRТ="ЗООО"
export ROOТ_URL="http://127.0.0.l:3000"
export MONGO_URL="mongodЬ://mongo:27017/rocketchat"
export MONGO_OPLOG_URL="mongodЬ://mongo:27017/local"
export МAIL_URL="smtp://smtp.email"
export RESPOND_TO_DM="true" export HUBOТ_ALIAS=". "
export LISTEN_ON_ALL_PUBLIC="true" export ROCКEТCНAT_AUTH="password"
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
189
export RОСКЕТСНАТ URL ="rocketchat:3000"
export RОСКЕТСНАТ ROOM=""
export RОСКЕТСНАТ USER="huЬot"
export ROCКETCНAT_PASSWORD="bot-pw 1 " export ВСУГ_NАМЕ="Ьоt"
export EXTERNAL_SCRIPTS="huЬot-help,huЬot-diagnostics,huЬot-zmachine"
export HUBCYГ_ZМACHINE_SERVER="http://zmachine:80"
export HUBCYГ_ZМACHINE_ROOMS="zmachine"
export HUBCYГ_ZМACHINE_CYГ_PREFIX="ot"
docker build -t spkane/mongo:4.4 ./mongodЬ/docker
docker push spkane/mongo:4.4
docker pull spkane/zmachine-api:latest
docker pull rocketchat/rocket.chat:5.0.4
docker pull rocketchat/huЬot-rocketchat:latest
docker rm -f huЬot 11 true
docker rm -f zmachine 11 true
docker rm -f rocketchat 11 true
docker rm -f mongo-init-replica 11 true
docker rm -f mongo 11 true
docker network rm botnet 11 true
docker network create -d bridge botnet
docker container run-d \
--name=mongo \
--network=botnet \
--restart unless-stopped \
-v $(pwd)/mongodЬ/data/dЬ:/data/dЬ \
spkane/mongo:4.4 \
mongod --oplogSize 128 --replSet rs0
sleep 5
docker container run-d \
--name=mongo-init-replica \
--network=botnet \
spkane/mongo:4.4 \
'mongo mongo/rocketchat --eval "rs.initiate ( { id: "rsO", memЬers: [ { ... '
sleep 5
docker container run-d \
--name=rocketchat \
--network=botnet \
--restart unless-stopped \
-v ${pwd)/rocketchat/data/uploads:/app/uploads \
-р 3000:3000 \
-е PORT=${PORT} \
-е ROCYГ_URL=${ROCYГ_URL} \
-е MONGO_URL=${MONGO_URL} \
-е MONGO_OPLOG_URL=${MONGO_OPLOG_URL} \
-е МAIL_URL=${МAIL_URL} \
rocketchat/rocket.chat:5.0.4
Книги для программистов: https://clcks.ru/books-for-it
190
1
Глава 8
docker container run-d\
--name=zmachine\
--network=botnet\
--restart unless-stopped \
-v $(pwd)/zmachine/saves:/root/saves\
-v $(pwd)/zmachine/zcode:/root/zcode \
-р 3002:80\
spkane/zmachine-api:latest
docker container run-d\
--name=huЬot\
--network=botnet\
--restart unless-stopped\
-v $(pwd)/huЬot/scripts:/horne/huЬot/scripts\
-р 3001:8080\
-е RESPOND ТО DM="true"\
-е НUВОТ ALIAS=". "\
-е LISTEN ON ALL PUBLIC="true"\
-е ROCKETCНAT_AUTH="password"\
-е RОСКЕТСНАТ URL="rocketchat:3000"\
-е RОСКЕТСНАТ RООМ=""\
-е RОСКЕТСНАТ USER="huЬot"\
-е ROCKEТCНAT_PASSWORD="bot-pw!"\
-е ВОТ NAМE="bot"\
-е EXTERNAL_SCRIPТS="huЬot-help,huЬot-diagnostics,huЬot-zmachine"\
-е HUBOТ_ZМACHINE_SERVER="http://zmachine:80"\
-е НUВОТ-ZМACHINE-ROOМS="zmachine"\
-е НUВОТ-ZМACHINE-ОТ-PREFIX="ot"\
rocketchat/huЬot-rocketchat:latest
echo "Environrnent setup ..."
exit О
Скорее всего, вы понимаете большую часть скрипта, и успели заметить, что его
сложно читать, ему не хватает гибкости, его будет прqбл_ематично редактировать
и что-то легко может пойти не так. Если бы мы попытались здесь обработать все
возможные ошибки, чтобы сделать скрипт воспроизводимым, он стал бы в два-три
раза длиннее. Если мы не проделаем большую работу по обработке ошибок для
основного функционала, нам придется каждый раз переписывать большую часть
логики для нового проекта. Это не лучший подход к созданию стабильного процес­
са. К счастью, теперь у нас есть Docker Compose, с которым процесс гораздо проще
воспроизводить, читать, понимать и обслуживать.
По сравнению с этим неудобным shеll-скриптом, в котором много лишнего кода и
слабых мест,. Docker Compose обычно· использует один декларативный файл УАМL
(https://yaml.org/) для каждого проекта - docker-compose.yaml. Этот файл конфи­
гурации легко читается и воспроизводится - он будет работать одинаково у каж­
дого пользователя. Здесь приводится пример файла docker-compose.yaml, который
может заменить предыдущий нестабильный shеll-скрипт:
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
191
version: '3'
services:
mongo:
build:
context: ../mongodЬ/docker
image: spkane/mongo:4.4
restart: unless-stopped
environment:
м::NGODB REPLICA SET К)[)Е: primary
м::NGODB-REPLICA-SЕТ-NАМЕ: rs0
м::NGODB PORT NUМВER: 27017
м::NGODB_INITIAL_PRIМARY_HOST: mongodЬ
м::NGODB INITIAL PRIМARY PORT NUМВER: 27017
м::NGODB-ADVERTISED_ноs�: mongo
м::NGODB ENAВLE JOURNAL: "true"
ALLOW ЕМРТУ PASSWORD: "yes"
# Порт 27017 уже открыт в базовом файле
# См. более новый базовый Dockerfile:
# https://githuЬ.com/bitnami/containers/ЫoЬ/
# f9fb3f8a6323fb768fd488c77d4f111Ы330bd0e/bitnami/
# mongodЬ/5.0/debian-11/Dockerfile#L52
networks:
- botnet
rocketchat:
image: rocketchat/rocket.chat:5.0.4
restart: unless-stopped
laЬels:
traefik.enaЫe: "true"
traefik.http.routers.rocketchat.rule: Host('127.0.0.1 ')
traefik.http.routers.rocketchat.tls: "false"
traefik.http.routers.rocketchat.entrypoints: http
volumes:
- "../rocketchat/data/uploads:/app/uploads"
environment:
ROOТ_URL: http://127.0.0.1:3000
PORT: 3000
м::NGO_URL: "mongodЬ://mongo:27017/rocketchat?replicaSet=rs0"
м::NGO_OPLOG_URL: "mongodЬ://mongo:27017/local?replicaSet=rs0"
DEPLOY МЕТНОD: docker
depends_on:
mongo:
condition: service_healthy
ports:
- 3000:3000
networks:
- botnet
Книги для программистов: https://clcks.ru/books-for-it
Глава 8
192
zmachine:
image: spkane/zmachine-api:latest
restart: unless-stopped
volU1118s:
- "../zmachine/saves:/root/saves"
- "../zmachine/zcode:/root/zcode"
depencls_on:
- rocketchat
expose:
- "ВО"
networks:
- botnet
huЬot:
image: rocketchat/huЬot-rocketchat:latest
restart: unless-stopped
volU1118s:
- ".. /huЬot/scripts:/home/huЬot/scripts"
environment:
RESIOm то IМ: "true"
НUВОТ ALIAS: ". "
LISТEN 00 ALL PUВLIC: "true"
RОСКЕТСВАТ_АUТН: "password"
RОСКЕТСВАТ URL: "rocketchat:3000"
RОСКЕТСВАТ RОСМ: ""
RОСКЕТСВАТ USER: "huЬot"
ROCКEТCНAT_PASSIORD: "bot-pw!"
ВОТ NАМЕ: "bot"
EXТERNAL_SCRIPТS: "huЬot-help,huЬot-diagnostics,huЬot-zmachine"
НUВОТ_ZМACНINE_SERVER: "http://zmachine:80"
НUВОТ ZМACНINE R()(],IS: "zmachine"
НUВОТ ZМACНINE CJr РRЕПХ: "ot"
depends_on:
- zmachine
ports:
- 3001:8080
networks:
- botnet
networks:
Ьotnet:
driver: bridge
В файле docker-compose.yaml можно легко поискать все важные требования для
каждого сервиса, а также их взаимодействия друг с другом. Вдобавок мы получаем
валидацmо и проверку логики, которые мы даже не писали в shell-cкpиme и в кото­
рых мы, скорее всего, временами ошибались бы, несмотря на все старания.
Что мы указали для Compose в этом файле формата УАМL? Первая строка указы­
вает Docker Compose, для какой версии языка конфигурации Compose (https://
docs.docker.com/compose/compose-file) предназначен этот файл:
version: ' 3 '
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
193
Оставшийся код разделен на две части: services (сервисы) и networks (сети).
Начнем с раздела networks. В этом файле docker-compose.yaml мы определяем одну
именованную сеть Docker:
networks:
Ьotnet:
driver: bridge
Это очень простая конфигурация, которая указывает Docker Compose, что нужно
создать одну сеть с именем Ьotnet с помощью драйвера bridge по умолчанию, кото­
рый будет соединять сеть Docker с сетевым стеком хоста.
Самая важная часть конфигурации находится в разделе services - здесь мы указы­
ваем, какие приложения хотим запускать. В этом примере в разделе services опре­
делены пять сервисов: mongo, mongo-init-replica, rocketchat, zmachine и huЬot. У каждого
сервиса есть свои разделы, в которых указано, как его собирать, настраивать и за­
пускать.
Например, у сервиса mongo первый подраздел называется build и содержит мюч
context. Таким образом, Docker Compose понимает, что может собрать этот образ, а
файлы, необходимые для сборки, находятся в каталоге ../../ mongodЬ/docker, кото­
рый расположен на два уровня выше каталога, содержащего файл docker­
compose.yaml:
Ьuild:
context: ../ .. /mongodЬ/docker
В Dockerfile в каталоге mongodЬ/docker мы увидим следующее:
FRCМ mongo:4.4
СОРУ docker-healthcheck /usr/local/bin/
# Полезная информация:
# https://docs.docker.com/engine/reference/builder/#healthcheck
# https://docs.docker.com/compose/compose-file/#healthcheck
НЕАLТНСНЕСК СМ> ["docker-healthcheck"]
Обратите внимание на строку HEALTHCHECK (проверка работоспособности). В ней ука­
зано, какую команду Docker должен запустить, чтобы проверить работоспособ­
ность контейнера. Docker ничего не будет делать по результатам проверки, но
сообщит о работоспособности, чтобы другие компоненты могли использовать
эту информацию. Если интересно, изучите скрипт docker-healthcheck в каталоге
mongodЬ/docker.
Следующий параметр, image, определяет тег образа, который мы хотим применить
к сборке или загрузить (если не собираем образ), а затем запустить:
image: spkane/mongo:4.4
С помощью параметра restart мы указываем Docker, когда нужно перезапускать
контейнеры. В большинстве случаев контейнеры следует перезапускать всегда,
если только мы явно не остановили их:
restart: unless-stopped
Книги для программистов: https://clcks.ru/books-for-it
194
Глава 8
Далее идет раздел environment. Здесь мы можем определить переменные среды,
которые хотим передать контейнеру:
environment:
�GOOB_REPLICA_SEТ_КX>E: primary
�GOOB REPLICA-SЕТ-Nl\МE: rs0
�GOOB PORT NUМВER: 27017
�GOOB_INITIAL_PRIМARY_HOST: mongodЬ
�GOOB INITIAL PRIМARY PORT NUМВER: 27017
�GOOB_ADVERTISED_НОSТNАМЕ: mongo
�GOOB ENAВLE JOORNAL: "true"
ALLOW_ЕМРТУ_PASSWORD: "yes"
-
В последнем подразделе для сервиса mongo, networks, мы указываем Docker Compose,
к какой сети должен быть подкmочен контейнер:
networks:
- botnet
Теперь давайте перейдем к сервису rocketchat. У этого сервиса нет подраздела build,
но есть тег образа, который запрещает Docker Compose собирать образ и указывает,
что необходимо извлечь и запустить готовый образ Docker с указанным тегом.
У этого сервиса есть подраздел volumes (тома), который мы не видели раньше.
У многих сервисов есть данные, которые необходимо хранить на протяжении раз­
работки, несмотря на эфемерность контейнеров. Самый простой способ - монти­
ровать внутрь контейнеров локальный каталог. В разделе volumes мы можем указать
все локальные каталоги, которые нужно примонтировать к контейнеру, и опре­
делить их путь. Следующая команда с помощью Ьind mount примонтирует
../rocketchat/data/uploads к /app/uploads внутри контейнера:
volumes:
- " ../rocketchat/data/uploads:/app/uploads"
Возможно, вы заметили, что мы не определили volume для MongoDB, и это может
показаться странным. В подключенном томе было бы удобно хранить файлы базы
данных, но MongoDB не сможет записывать данные в нативную файловую систему
Windows, поэтому для максимальной совместимости мы будем записывать данные
из базы данных в контейнер в этом проекте.
В результате при удалении контейнера командой вроде docker compose down все
данные в экземпляре MongoDB будут утеряны.
Мы могли бы решить проблему с хранением в MongoDB с помощью контейнера
с томом данных (https://docs.docker.com/storage/volumes/#create-and-manage­
volumes), но в этом примере мы используем Ьind mount для томов.
Локальное хранилище на хаете для контейнеров в рабочей среде почти никогда не
рекомендуется. Это очень удобно при разработке, поскольку мы используем один
хост, но в рабочей среде контейнеры часто развертываются на любом узле, где
есть свободное место и ресурсы, и если файлы хранятся в файловой системе од­
ного хоста, мы потеряем к ним доступ. Если нам понадобится хранилище с отелеКниги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
195
живанием состояния в рабочей среде, мы применяем сетевое хранилище, Kuber­
netes Persistent Volumes и т. д.
В разделе environment у сервиса rocketchat у значения MONGO_URL не указан IР-адрес или
полное доменное имя. Дело в том, что все сервисы выполняются в одной сети
Docker, и Docker Compose настраивает каждый контейнер, чтобы он искал осталь­
ные по имени сервиса. Получается, что мы можем легко настраивать URL таким
образом, чтобы указывать на имя сервиса и внутренний порт контейнера, к которо­
му нужно подключиться. Если мы что-то поменяем, эти имена будут указывать на
нужный контейнер в стеке. Это удобно, потому что любой читающий сразу увидит
зависимость для контейнера:
environment:
MONGO URL: "mongodЬ://mongo:27017/rocketchat?replicaSet=rs0"
Файл docker-compose.yaml также может ссылаться на переменные среды в форма­
те ${ИМЯ_ПЕРЕМЕННОЙ), чтобы извлекать секреты, не сохраняя их в файле. Docker
Compose также поддерживает файл .env (https://docs.docker.com/compose/env­
file), в котором удобно хранить секреты и переменные среды. Например, если они
отличаются у разных разработчиков.
В разделе ctepends_on (зависит от) определяется контейнер, который необходимо за­
пустить до эtого контейнера. По умолчанию ctocker сощ:юsе отвечает только за вы­
полнение, а не за работоспособность контейнера, но мы можем задействовать
функционал HEALTHCHECK в Docker и условный оператор в Docker Compose, чтобы
сервис, от которого зависит наш контейнер, был работоспособен, прежде чем
Docker Compose запустит новый сервис. Стоит отметить, что это важно только при
запуске. Docker сообщит о сервисах, которые позже станут неработоспособными,
но ничего не сделает, чтобы исправить ситуацию, если контейнер существует. Если
контейнер не существует, Docker перезапустит его, если мы указали это поведение
в настройках:
dependз_on:
mongo:
condition: service_healthy
Функционал проверок работоспособности Docker подробно описан в разделе "Про­
верки работоспособности контейнеров" главы 6. Также можно изучить докумен­
тацию по Docker и Docker Compose (https://dockr.ly/2wt366J).
В подразделе ports мы можем определить все порты, которые хотим пробросить из
контейнера на хост:
ports:
- 3000:3000
В сервисе zmachine есть только один пока незнакомый нам подраздел - expose.
В этом разделе мы указываем Docker, что хотим открыть порт для других контейКниги для программистов: https://clcks.ru/books-for-it
196
1
Глава 8
неров в сети Docker, но не для базового хоста. Поэтому мы не указываем порт на
хосте для проброса этого порта:
expose:
- "80"
Возможно, вы заметили, что мы открываем порт для zmachine, но не открыли порт
в сервисе mongo. Мы могли бы открыть порт mongo, но в этом нет необходимости, пото­
му что он уже открыт в базовом mongo Dockerfile (https://github.com/docker-library/
mongo/ЫoЬ/58bdba62b65bldlelea5cbde54c1682f120e0676/3.2/Dockerfile#L95). Ино­
гда это не очевидно, и стоит изучить docker image history для собранного образа.
Здесь мы рассмотрели довольно сложный пример, так что вы уже получили неко­
торое представление о том, как работает Docker Compose, но его возможности
гораздо шире. В файле docker-compose.yaml можно настроить очень много, вклю­
чая настройки безопасности, квоты на ресурсы и др. Подробную информацию
о конфигурации читайте в официальной документации по Docker Compose (https://
docs.docker.com/compose/compose-file ).
Запуск сервисов
В файле УАМL мы настроили набор сервисов для приложения. Таким образом мы
указываем Compose, что хотим запустить и какая конфигурация потребуется.
Давайте запустим наши сервисы. Прежде чем вьmолнить первую команду Docker
Compose, нужно убедиться, что мы находимся в том же каталоге, что и файл
docker-compose.yaml:
$ cd rocketchat-huЬot-demo/compose
Перейдя в нужный каталог, проверим правильность конфигурации:
$ docker compose config
Если все нормально, эта команда выведет наш файл конфигурации. Если есть про­
блемы, команда выдаст ошибку с дополнительными сведениями:
services.mongo Additional property builder is not allowed
Мы можем собрать любые нужные контейнеры с помощью параметра build. Серви­
сы, использующие образы, будут пропущены:
$ docker compose build
=> [internal] load build definition from Dockerfile
0.0s
0.0s
=> => transferring dockerfile: 32В
=> [internal] load .dockerignore
0.0s
=> => transferring context: 2В
0.0s
=> [internal] load metadata for docker.io/bitnami/mongodЬ:4.4
1.2s
=> [auth] bitnami/mongodЬ:pull token for registry-1.docker.io
0.0s
0.0s
=> [internal] load build context
0.0s
=> => transferring context: 40В
0.0s
=> [1/2] FROМ docker.io/bitnami/mongodЬ:4.4@sha256:9162...ae209
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
;> CACHED (2/2] СОРУ docker-healthcheck /usr/local/bin/
;> exporting to image
;> ;> exporting layers
;> ;> writing image sha256: a6ef ...da808
;> ;> naming to docker.io/spkane/mongo:4.4
197
O.Os
O.Os
O.Os
O.Os
O.Os
Давайте запустим.-"веб-сервис в фоновом режиме, выполнив следующую команду:
$ docker СОЩ)ОSе up -d
[+]
Running 5/5
.. Network COЩJOSe_botnet
.. Container coЩJose-mongo-1
.. Container coЩJose-rocketchat-1
... Container COЩ)ose-zmachine-1
.. Container coЩJose-huЬot-1
Created
Healthy
Started
Started
Started
O.Os
62.0s
62.Зs
62.5s
62.6s
Docker Compose перед именами сетей и контейнеров указывает имя контейнера.
По умолчанию это имя каталога, в котором находится файл docker-compose.yaml.
Поскольку эта команда выполнялась в каталоге compose, все имена начинаются
С СОЩ)ОSе.
&
1
Для пользователей Windows: при первом запуске сервисов Windows может запро­
сить разрешение для vpnkit, и Docker Desktop for Windows может запросить доступ
к диску. Разрешите vpnkit и предоставьте доступ к сетевым папкам и томам, чтобы
все работало правильно.
Когда все будет готово, мы можем изучить записи журналов для всех сервисов
(рис. 8.1):
$ docker coЩJose logs
Commit Hash: 29fb34babc
ompose-rocketchat-1 1 1
Connit Branch: НЕАО
ompose-rocketchat-1 1 1
ompose-rocketchat-1 1 1
ompose-rocketchat-1 1 +-----------------+
ompose-nюngo-1
1 {"t":{"$date":"2823-83-31T28:48:84.893+88:88"},•s•:•r•, •c•:"REPL",
"ctx":"Rep\Coord-8","msg•:•starting rep\ication app\ier thread'}
ompose-mongo-1
1 {'t':{'$date':'2823-83-31T28:48:84.893+88:88'},"s':'I", "c':"REPL",
"ctx":'Rep\Coord-8',"msg":'Starting rep\ication reporter thread"}
ompose-hubot-1
1 hubot-zmachineQ)ll.1.8 node_nюdu\es/hubot-zniachine
ompose-hubot-1
1 npm info ok
'id':21388
"id':21381
Рис. 8.1. Вывод команды docker COЩJOse logs
Вывод не очень наглядный, но обратите внимание, что записи выделены разными
цветами в зависимости от сервиса, и указано время, когда Docker получил записи
журнала. Благодаря этому гораздо проще следить за происходящим, даже когда
несколько сервисов записывают события в журнал одновременно.
Возможно, потребуется некоторое время, чтобы Rocket.Chat настроил базу данных
и подготовился принимать подключения. Когда в журналах Rocket.Chat появится
строка, содержащая SERVER RUNNING, все готово к работе:
Книги для программистов: https://clcks.ru/books-for-it
198
Глава В
$ docker compose logs rocketchat I grep "SERVER RUNNING"
compose-rocketchat-1 1 1
SERVER RUNNING
Итак, мы успешно запустили относительно сложное приложение, состоящее из не­
скольких контейнеров. Давайте внимательно изучим детали приложения и поймем ,
как работает Compose. Следующий раздел не связан напрямую с самим Docker, но
мы увидим, как просто можно с помощью Docker Compose запустить полноценные
веб-сервисы.
Принципы работы Rocket.Chat
В этом разделе мы ненадолго отвлечемся от Docker и рассмотрим Rocket.Chat. Мы
вынесли его в отдельный раздел, чтобы вы лучше поняли, как он работает, и уви­
дели, насколько он упрощает создание сложной среды с помощью Docker
Compose. Можете пропустить этот раздел и перейти к разделу "Применение Docker
Compose" далее в этой главе.
Позже мы изучим, что происходит за кулисами, но сначала нужно кратко рассмот­
реть созданный нами стек приложения. Rocket.Chat (https://rocket.chat/), основное
приложение, которое мы запустили с помощью Docker Compose, представляет
собой клиент-серверное приложение-чат с открытым исходным кодом. Чтобы
посмотреть, как оно работает, давайте в браузере перейдем по адресу http://
127.0.0.1:3000.
Далее откроем экран Admin Info (Информация об администраторе) для Rocket.Chat
(рис. 8.2).
Step 1 of 3
Admin lnfo
We need this to create an admin profile inside your worl<space
Full name
1
1
1
student
Username
student
Email
student@example.com
-
Password
student-pw!
---- -
--
--- -
- -
---
---
-
<§>
1
1
1
1
Рис. 8.2. Экран с информацией об администраторе в Rocket.Chat
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
1
199
Заполните форму, как на рис. 8.2:
♦ Full name: student
♦ Username: student
♦ Email: student@example.com
♦ Password: student-pw!
Нажимаем кнопку Nf�xt (Далее) и переходим на экран информации об организации
Organization Info (рис. 8.3).
Step 2 of 3
Organization lnfo
Please, Ьеаг with us. This info will help us personalize уоuг workspace
Organlzatlon name
1
1
1
1
training
Organization type
Community
V
Organizatlon industry
Education
V
Organization slze
1-10 people
V
Country
-
United States
I ... J.
1
1
1
1
--------V
-- --
- ---
------- - - -
Рис. 8.3. Экран с информацией об организации в Rocket.Chat
Здесь можно указать что уrодно, например:
♦ Organization name: training
♦ Organization type: Community
♦ Organization industry: Education
♦ Organization size: 1-1О people
♦ Country: United States
Нажимаем кнопку Next (Далее) и переходим на экран регистрации сервера Register
Your Server (рис. 8.4).
Здесь мы удалим все данные и флажки и нажмем на ссылку Continue as standalone
(Продолжить на автономном сервере), чтобы перейти на экран конфигурации авто­
номного сервера Standalone Server Configuration (рис. 8.5).
Нажимаем синюю кнопку Confirm (Подтвердить).
Книги для программистов: https://clcks.ru/books-for-it
200
Глава В
Step Э of З
Register Your Server
✓ MoЬile push notifications
✓ lntegration with extemal providers (WhatsApp, FaceЬook, Telegram,
Twitter)
✓ Access to marketplace apps
Cloud account ernall 0
1 Please enter your Email
This field is required
О Кеер те informed aЬout news and events
О I agree with Terms and Conditions and Privacy Policy
Ву registering I agree to receive relevant product and security updates
Continue а s standalone
Рис. 8.4. Экран регистрации сервера в Rocket.Chat
Step З of З
Standalone Server Confirmation
& Some of the servlces wlll Ье uмvailaЫe or wlll requlre manual setup
0 ln order to send push notitications you need to compile and puЬlish your own арр to
Google Play and Арр Store
0 Need to manually integrate with external services
Рис. 8.5. Экран подтверждения автономного сервера в Rocket.Chat
Если вы используете localhost или открываете Rocket.Chat в браузере не по адре­
су 127. о. о .1, то может открыться всплывающее окно с вопросом о том, хотите ли
вы обновить SIТE_URL. В большинстве случаев вы можете изменить это значение
на свое собственное.
Итак, мы вошли в полноценный чат-клиент, но это еще не все. Конфигурация
Docker Compose запустила экземпляр ассистента Hubot (https://hubot.github.com/)
и непонятный zmachine. Давайте посмотрим, что это.
Наш сервер Rocket.Chat совсем новый, так что у нас нет пользователя для бота. Да­
вайте это исправим. Нажмем на фиолетовый квадрат с буквой S в левом верхнем
углу. В открывшемся меюо выбираем пункт Administration (Администрирование)
(рис. 8.6).
На панели администрирования выбираем Users (Пользователи) (рис. 8.7).
Нажмем кнопку New (Создать) в правом верхнем углу, чтобы перейти на экран
добавления пользователя Add User (рис. 8.8).
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
Е1
1
201
estudent
online
STATUS
8 online
О away
О busy
О offline
@ Custom Status...
� Administration
1) Omnichannel
& Му Account
0 Logout
Рис. 8.6. Панель администрирования в Rocket.Chat
AdrrwlistJatiul1
х
Users
о.
Search Users
Email .�
Name ...,
�
2: lrwites
Rocket.Cat
@rocket.cat
11!11 student
1;1 @sutdent
Status:
Online
student@example....
Online
Рис. 8.7. Экран пользователей Rocket.Chat
Заполните форму:
♦ Name: hubot
♦ Usemame: hubot
♦ Email: hubot@example.com
♦ Отметьте: Verified (Подтверждено, отмечено синим)
♦ Password: bot-pw!
♦ Roles: bot
♦ Отключите переключатель: Send welcome email (Отправить приветственное сообщение, отмечено серым).
Нажмем кнопку Save (Сохранить), чтобы создать пользователя.
Чтобы бот мог войти в систему, отключим двухфакторную аутентификацию, кото­
рая включена по умолчанию: выбираем пункт Settings (Настройки) в самом низу
панели администрирования в левой части браузера (рис. 8.9).
Книги для программистов: https://clcks.ru/books-for-it
Глава 8
202
Name
1 huЬot
(D lnfo
Usemame
1 huЬot
@
w lrnport
19
# Rooms
Email
1 huЬot@example.com
Verified
е users
� lnvites
& Conn8c:tМty Services
в
Status Мess.ge
Viewlogl
О1 Custom Sounds
• Feder8tlon DнhЬoan:I
Bio
ф Apps
в Emlil lnЬous
(а) Custom Emoji
Nickname
ф 1nt8C,"81io1IS
• OAulhApps
вм..
Password
Custom u- St8tus
1 ........ .
Require password change
Set random password and send Ьу email
(]1
(]1
Roles
Select а Role
Join default channels
Рис. 8.9. Настройки администрирования
в Rocket.Chat
Settings
Send welcome email
Accounta
Рис. 8.8. Экран добавления пользователя
Rocket.Chat
Modify workspace memЬer
account settings.
Рис. 8.10. Настройки аккаунта в Rocket.Chat
Оrкроется экран настроек Settings (рис. 8.1 О).
В строке поиска вводим totp и нажимаем кнопку Open (Оrкрыть) в окне Accounts
(Аккаунты). Оrкроется длинный список настроек (рис. 8.11).
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
+--
203
Accounts
л
Two Factor Authentication
о
ЕnаЫе Two Factor Authentication
lf deactivated, this setting will deactivate all Two Factor Authentication.<br>То force
users to use Two Factor Authentication, the admin has to configure the 'user' role to
Рис. 8.11. Настройки ТОТР в Rocket.Chat
Прокрутим список до раздела Two Factor Authentication (Двухфакторная аутенти­
фикация), откроем его и уберем флажок ЕnаЫе Two Factor Authentication (Вклю­
чить двухфакторную аутентификацию).
Нажимаем кнопку Save changes (Сохранить изменения).
В правом верхнем углу панели администрирования нажимаем на значок х, чтобы
закрыть панель (рис. 8.12).
Administration
0 lnfo
(!] lmport
Рис. 8.12. Закрываем панель администрирования Rocket.Chat
На левой панели в разделе Channels (Каналы) выбираем general (общий)
(рис. 8.13).
*
:j:j: general
IJ
student HasJoined the channel. 1:14 РМ
111 huЬot Has Jomed the channel, 1 :21 РМ
Рис. 8.13. Общий канал в Rocket.Chat
Если в канале не появилось сообщение Hubot has joined the channel (Hubot присое­
динился к каналу), укажите Docker Compose, что нужно перезапустить контейнер
Hubot. В результате Hubot попробует снова войти на сервер чата, ведь теперь у нас
есть пользователь для сервиса:
$ docker compose restart huЬot Restarting unix_huЬot_l ... done
Книги для программистов: https://clcks.ru/books-for-it
204
Глава В
Если все получилось, мы сможем вернуться в браузер, чтобы отправлять Hubot
команды в окне чата.
�
Hubot должен автоматически присоединиться к каналу General при входе на сер­
вер, но при необходимости мы можем отправить в канал следующее сообщение,
чтобы явно пригласить Hubot: / invite @huЬot
Мы можем получить сообщение от внутреннего администратора rocket.cat, в кото­
ром будет сказано, что сервис уже присоединился (@hubot is already in here). Это
нормально.
Переменные среды, которые использовались для конфигурации Hubot, определяют
для него псевдоним в виде точки. Давайте введем . help, чтобы убедиться, что бот
отвечает. Если все работает, мы получим список команд, которые бот готов прини­
мать:
> help
adapter - Reply with the adapter
echo <text> - Reply back with <text>
help - Displays all of the help commands that this bot knows aЬout.
help <query> - Displays all help commands that match <query>.
ping - Reply with pong
time - Reply with current time
Введем команду из списка, чтобы получить ответный сигнал:
. ping
Hubot должен в ответ прислать PONG.
Если мы введем:
. time
Hubot в ответ пришлет время, заданное на сервере.
Давайте теперь попробуем создать новый канал, введя /create zmachine в окне чата.
Мы можем нажать на новый канал zmachine на левой панели и пригласить Hubot
в чат командой /invite @huЬot.
В ответ мы можем получить от Hubot сообщение: There's по game for zmachine!
� (для zmachine нет игр). Это нормально.
�
Теперь введем в окне чата следующие команды, чтобы поиграть в чат-версию зна­
менитой игры Colossal Cave Adventure:
. z start adventure
more look
go east examine keys get keys
z save firstgame
z stop
z start adventure
z restore firstgame inventory
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
1
205
Осторожно! Игра затягивает. Если вы в нее еще не играли и хотите узнать больше,
изучите следующие ресурсы: 1) Интерактивная литература (interactive fiction);
1
2) Симулятор; 3) Разработка; 4) Игры ; 5) Соревнования.
Итак, вы увидели, как с помощью Docker Compose можно легко настраивать, за­
пускать и администрировать сложные веб-сервисы, которым требуется несколько
компонентов для выполнения задачи. В следующем разделе мы подробнее рас­
смотрим возможности Docker Compose.
Мы можем упростить подготовку Rocket.Chat, используя MongoDB с предваритель­
но настроенной базой данных Rocket.Chat, но мы рассмотрели подробный пр�мер,
чтобы вы понимали, как это работает.
Применение Docker Compose
Итак, у нас есть полный стек Rocket.Chat, и мы понимаем, что делает приложение.
Теперь можно подробнее рассмотреть, как работают сервисы. Некоторые стандарт­
ные команды Docker также могут использоваться как команды Compose, но для
определенного стека, а не одного контейнера или всех контейнеров на хосте. С по­
мощью команды docker compose top мы можем посмотреть контейнеры и выполняю­
щиеся в них процессы:
$ docker compose top
compose-huЬot-1
UID PID
CMD
/usr/Ьin/qemu-x86_64 /Ьin/sh /Ьin/sh -с node -е "console.l... "
1001 73342
/usr/Ьin/qemu-x86_64 /usr/local/Ьin/node node node modules/...
1001 73459
compose-mongo-1
CMD
UID PID
1001 71243 ... /usr/Ьin/qemu-x86_64 /opt/Ьitnami/mongodЬ/Ьin/mongod /opt/...
compose-rocketchat-1
CMD
UID PID
65533 71903 ... /usr/Ьin/qemu-x86_64 /usr/local/Ьin/node node main.js
compose-zmachine-1
CMD
UID PID
root 71999
/usr/Ьin/qemu-x86_64 /usr/local/Ьin/node node /root/src/server.js
root 75078
/usr/Ьin/qemu-x86_64 /root/src/.. /frotz/dfrotz /root/src/ ...
Так же, как можем войти в выполняющийся контейнер Linux с помощью команды
docker container ехес, мы можем выполнять команды в контейнерах через Docker
1 Полный URL: https://ifaгcЬive.oгg/indexes/if-aгcЬiveXgamesXzcode.html.
Книги для программистов: https://clcks.ru/books-for-it
206
Глава В
Compose с помощью docker compose ехес. Поскольку инструмент docker compose новее,
у него есть удобные сокращения по сравнению со стандартными командами docker.
При использовании docker compose ехес не нужно передавать параметры -i -t, и мы
можем указать имя сервиса Docker Compose, чтобы не запоминать имя или иденти­
фикатор контейнера:
$ docker compose ехес mongo bash
I have no name!@0078134f9370:/$ mongo
MongoDB shell version v4.4.15
connecting to: mongodЬ://127.0.0.1:27017/?compressors;disaЫed&.. .
Implicit session: session { "id" : UUID("daec9543-ЬЬ9c-4e8c-baбb... ")
MongoDB server version: 4.4.15
rs0:PRIМARY> exit Ьуе
I have no name!@0078134f9370:/$ exit
exit
Команды docker compose logs и docker compose ехес, пожалуй, самые полезные для
решения проблем. Если Docker Compose совсем не может собрать образ или за­
пустить контейнер, используйте стандартные команды docker, чтобы отладить об­
раз и контейнер, как обсуждалось в разделах "Решение проблем со сборками" гла­
вы 4 и "Что происходит в контейнере" главы 6.
С помощью Docker Compose мы можем запустить (start), остановить (stop) и
в большинстве сред приостановить (pause) и возобновить (unpause) один контейнер
или все контейнеры:
$ docker compose stop zmachine
[+] Running 1/1
:: Container compose-zmachine-1
$ docker compose start zmachine
[+] Running 2/2
:: Container compose-mongo-1
:: Container compose-zmachine-1
$ docker compose pause
[+] Running 4/0
"Container compose-mongo-1
"Container compose-zmachine-1
.. Container compose-rocketchat-1
.. Container compose-huЬot-1
$ docker compose unpause
[+] Running 4/0
"Container compose-zmachine-1
"Container compose-huЬot-1
" Container compose-rocketchat-1
"Container compose-mongo-1
Stopped
0.3s
Healthy
Started
0.5s
0.4s
Paused
Paused
Paused
Paused
0.0s
0.0s
0.0s
0.0s
Unpaused
Unpaused
Unpaused
Unpaused
0.0s
0.0s
0.0s
0.0s
Наконец, когда мы захотим завершить работу и удалить все контейнеры, созданные
Docker Compose, мы можем выполнить следующую команду:
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
$ docker compose down
[+] Running 5/5
"Container compose-huЬot-1
"Container compose-zmachine-1
"Container compose-rocketchat-1
"Container compose-mongo-1
:: Network compose _botnet
�
�
Removed
Removed
Removed
Removed
Removed
207
10.4s
0.ls
О.бs
0.95
0.ls
Если удалить контейнер MongoDB командой docker compose down, то все данные
в экземпляре MongoDB будут потеряны.
Управление конфигурацией
У Docker Compose есть возможности, которые заметно повышают гибкость файлов
docker-compose.yaml. В этом разделе мы посмотрим, как не писать фиксированные
значения конфигурации в файлах docker-compose.yaml, но при этом легко исполь­
зовать их по умолчанию.
Значения по умолчанию
Если мы посмотрим на раздел services:rocketchat: environment в файле docker­
compose.yaml, то увидим что-то вроде этого:
environment:
RESPCND то I:М: true 11
11
НUВОТ ALIAS: 11 11
LISТEN 00 ALL PUВLIC: true 11
RОСКЕТСНАТ_АUТН: password"
RОСКЕТСНАТ URL: "rocketchat:3000"
RОСКЕТСНАТ RОСМ: " 1
RОСКЕТСНАТ USER: "huЬot"
RОСКЕТСНАТ_PASSIORD: "bot-pw ! "
ВОТ NАМЕ: 11 bot"
EXТERNAL_SCRIPТS: "huЬot-help,huЬot-diagnostics,huЬot-zmachine 11
НUВOТ_ZМACHINE_SERVER: "http://zmachine:80"
НUВОТ ZМACHINE ROCМS: "zmachine"
НUВОТ ZМACHINE (Л PREFIX: "ot"
•
11
11
1
Если мы заглянем в docker-compose-defaults.yaml в том же каталоге, тот же раздел
будет выглядеть следующим образом:
environment:
RESPCND_ТO_Ш: ${HUBar_RESPOND_TO_DM:-true}
НUВOТ_ALIAS: ${HUBOT_ALIAS:-. )
LISТEN_CN_ALL_PUВLIC: ${HUBm'_LISTEN_ON_ALL_PUBLIC:-true)
RОСКЕТСНАТ_АUТН: ${HUBOT_ROCKETCНAT_AUTH:-password)
RОСКЕТСНАТ URL: ${HUBm'_ROCKETCНAT_URL:-rocketchat:3000)
Книги для программистов: https://clcks.ru/books-for-it
208
Глава 8
RОСКЕТСНАТ RОСМ: ${НUВОГ_RОСКЕТСНАТ_RООМ:-}
RОСКЕТСНАТ USER: ${HUBOГ_ROCKEТCНAT_USER:-huЬot}
RОСКЕТСНАТ_PASSIORD: ${нuваг_RОСКЕТСНАТ-PASSWORD:-bot-pw ! }
ЮТ_NАМЕ: ${НUВОГ_ВОГ_NАМЕ:-Ьоt}
Пl'ERNAL_SCRIPТS: ${HUBOГ_EXTERNAL_SCRIPТS:-huЬot-help,
huЬot-diagnostics,huЬot-zmachine}
НUВOТ_ZМIICIIINE_SERVER: ${HUBOГ_ZМACHINE_SERVER:-http://zmachine:80}
НUВOТ_ZМ11CHINE_ROCМS: ${HUBOГ_ZМACHINE_ROOМS:-zmachine}
НUВOТ_ZМIICIIINE_OТ_PREFIX: ${HUBOГ_ZМACHINE_OГ_PREFIX:-ot}
Здесь используется интерполяция переменных (https://docs.docker.com/compose/
compose-flle/#interpolation), которую Docker Compose позаимствовал напрямую из
распространенных командных оболочек Unix вроде bash.
В исходном файле для переменной среды ROCKETCНAT_PASSWORD указано фиксированное
значение "bot-pw! ":
RCX:КEТCНAT_PASSIORD: "bot-pw!"
Применяя новый подход, мы указываем, что для ROCКETCНAT_PASSWORD нужно задать
значение переменной нuваг_RОСКЕТСНАТ_PASSWORD, если оно задано в среде пользователя,
а если нет, то для RОСКЕТСНАТ_PASSWORD нужно задать значение по умолчанию bot-pw ! :
RCX:КE'l'CНAT_PASSIORD: ${НUВОГ_RОСКЕТСНАТ_PASSWORD:-bot-pw ! }
Такой подход дает больше гибкости, потому что мы можем настраивать почти что
угодно, при этом указывая значения по умолчанию для большинства распростра­
ненных ситуаций. Давайте протестируем это, выполнив команду docker сопроsе up
с новым файлом:
$ docker сопроsе -f docker-corrpose-defaults.yaml up -d
Running 5/5
:: Network corrpose_botnet
.. Container corrpose-mongo-1
"Container corrpose-rocketchat-1
.. Container coщ:юse-zmachine-1
.. Container corrpose-huЬot-1
[+]
Created
Healthy
Started
Started
Started
0.0s
31.0s
31.2s
31.5s
31.8s
По умолчанию мы получим тот же стек, который запустили раньше. Однако мы
можем вносить изменения, просто задав одну или несколько переменных среды
в терминале, прежде чем выполнять команды docker сопроsе:
$ docker сопроsе -f docker-corrpose-defaults.yaml down
$ docker сопроsе -f docker-corrpose-defaults.yaml config 1 \
grep ROCКETCНAT_PASSWORD
ROCKETCНAT_PASSWORD: bot-pw 1
$ HUBOГ_ROCКETCНAT_PASSWORD="my-unique-pw" docker сопроsе \
-f docker-corrpose-defaults.yaml config 1 \
grep ROCKETCНAT_PASSWORD
RОСКЕТСНАТ PASSWORD: my-unique-pw
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
209
В этих примерах Docker Compose обрабатывает пустую переменную среды так же,
как если бы ей была назначена пустая строка. Если пустая строка является допус­
тимым значением в конкретном случае, следует изменить формат строки подста­
новки переменной: $ {имя_ПЕРЕМЕННОЙ-значение-по-умолчанию J. Прочтите документацию
по этой функции (https://docs.docker.com/compose/compose-file/#interpolation),
чтобы изучить все ее возможности.
Что если мы не хотим предоставлять значение по умолчанию и требуем, чтобы
пользователь сам задал значение? Эго легко.
Вам может не понравиться, что мы передаем пароль в командной строке, ведь его
будет видно в списке системных процессов, но не волнуйтесь, с этой проблемой
мы разберемся чуть позже.
Обязательные значения
Чтобы задать обязательное значение, мы немного меняем строку подстановки
переменной. Передавать пароль по умолчанию - плохая идея, так что давайте сде­
лаем его обязательным значением.
В файле docker-compose-defaults.yaml переменная RОСКЕТСНАТ _PASSWORD определена
следующим образом:
ROCКE'l'CНAT_PASSWORD: ${HUBOТ_ROCKETCНAT_PASSWORD:-bot-pw!}
В более новом файле docker-compose-env.yaml мы видим следующее определение:
ROCКEТCНAT_PASSWORD: ${HUBOТ_ROCKEТCНAT_PASSWORD:?HUBOТ_ROCKETCНAT_PASSWORD must Ье set!)
Вместо значения по умолчанию здесь определяется строка ошибки, которая возни­
кает, если в среде для переменной не указана непустая строка. Если мы запустим
сервис, получим сообщение об ошибке:
$ docker compose -f docker-compose-env.yaml up -d
invalid interpolation format for
services.huЬot.eпvironment.ROCKETCНAT PASSWORD.
You may need to escape any $ with another $.
required variaЫe HUBOТ_ROCKEТCНAT_PASSWORD is missing а value:
НUВОТ-RОСКЕТСНАТ-PASSWORD must Ье set!
По выводу можно понять, что не так, и в последних двух строках это очевидно.
В последней строке мы видим конкретное сообщение об ошибке, которое мы опре­
делили ранее, - для него можно указать текст в зависимости от ситуации.
Если мы передадим свой пароль, все заработает:
$ HUBOТ_ROCKETCНAT_PASSWORD="a-Ь3tt3r-pw" docker compose \
-f docker-compose-env.yaml up -d
[+] Running 5/5
.. Network compose_botпet
Created
Healthy
.. Container compose-mongo-1
.. Container compose-rocketchat-1
Started
0.0s
31.0s
31.Зs
Книги для программистов: https://clcks.ru/books-for-it
210
Глава В
:: Container compose-zmachine-1
Started
:: Container compose-huЬot-1
Started
$ docker compose -f docker-compose-env.yaml down
31.5s
31.Bs
Файл dotenv
Передать одну переменную среды не так сложно, но если мы хотим передать много
пользовательских значений или один секрет, вряд ли стоит использовать для этого
локальный терминал. Дпя этого и пригодится файл .env (dotenv).
.env - это стандарт специального файла (https://www.dotenv.org/docs/security/env)
для программ, которым требуется дополнительная информация о конфигурации,
относящейся к локальной среде.
В предыдущем примере мы должны были указать пароль, чтобы запустить среду
Docker Compose. Мы можем каждый раз передавать переменные в среду, но по не­
скольким причинам это не лучшее решение. Было бы удобно иметь возможность
безопасно задавать переменные для среды с одним пользователем и снизить веро­
ятность ошибок.
По сути, файл .env представляет собой список пар ключей и значений. Поскольку
файл должен быть уникальным для локальной среды и обычно содержит хотя бы
один секрет, мы должны убедиться, что случайно не отправим его в систему кон­
троля версий. Если мы используем git, нужно просто внести файл .env в .gitignore,
и в нашем случае он уже указан там:
$ grep .env ../.gitignore
.env
Если мы работаем в системе с одним пользователем, теперь можно безопасно соз­
дать файл .env в том же каталоге, где находится файл docker-compose.yaml.
В этом примере в файле .env может быть примерно следующее содержимое:
нuваг-RОСКЕТСНАТ- PASSWORD=th2l@stPW!
Можно добавлять много пар ключей и значений, но мы не будем усложнять и огра­
ничимся паролем. Если выполнить git status после создания этого файла, можно
заметить, что git полностью игнорирует новый файл, как мы и хотели:
$ git status On branch main
Your branch is up to date with 'origin/main'.
nothing to cormtit, working tree clean
Файл .env не является shеll-скриптом Unix. Между этим форматом и подходом
к определению переменных в стандартном shell-cкpиnтe есть небольшие, но важ­
ные различия. Самое важное из них: в большинстве случаев не следует заключать
значения в кавычки.
В предыдущем разделе, когда мы выполнили docker compose -f docker-compose­
env. yaml up -d без указания HUBOГ_ROCKETCНAT_PASSWORD, мы получили ошибку, но если
мы повторим попытку после создания файла .env, все получится:
Книги для программистов: https://clcks.ru/books-for-it
Принципы работы Docker Compose
$ docker сощ:,оsе -f docker-coщ:,ose-env.yaml up -d
[+] Running 5/5
.. Network coщ:,ose_botnet
Created
Healthy
Container coщ:,ose-mongo-1
Started
Container coщ:,ose-rocketchat-1
.. Container coщ:,ose-zmachine-1
Started
.. Container coщ:,ose-huЬot-1
Started
..
..
211
0.0s
31.ls
31.Зs
31.5s
31.8s
Давайте убедимся, что значение, указанное для переменной ROCKETCНAT_PASSWORD, сов­
падает с тем, что мы задали в файле .env:
$ docker сощ:,оsе \
-f docker-coщ:,ose-env.yaml config 1
grep ROCKETCНAT_PASSWORD
ROCKETCНAT_PASSWORD: th2l@stPW!
\
Как видите, это именно то значение, которое мы указали в файле .env. Дело в том,
что Docker Compose всегда считывает пары "ключ-значение", определенные в фай­
ле .env, который находится в том же каталоге, что и наш файл docker-compose.yaml.
Тут важно понимать приоритетность. Сначала Docker Compose считывает все зна­
чения по умолчанию, заданные в файле docker-compose.yaml. Затем он читает файл
.env и меняет значения по умолчанию на значения, указанные здесь. Наконец, он
переходит к переменным среды, заданным в локальной среде, и переопределяет
ранее заданные значения.
Это значит, что значения по умолчанию должны быть самыми общими, а затем
каждый пользователь определяет собственные значения в файле .env, и, наконец,
в редких случаях можно использовать локальные переменные среды, чтобы внести
нестандартные изменения для конкретного случая. С помощью этих возможностей
Docker Compose помогает собирать воспроизводимые процессы, которые будут
достаточно гибкими, чтобы их можно было адаптировать к большинству стандарт­
ных рабочих процессов.
В Docker Compose есть и другие возможности, которые мы здесь не рассматрива­
ем, например переопределение файлов (https://docs.docker.com/compose/
extends). Когда вы начнете регулярно использовать Docker Compose, изучите
документацию (https://docs.docker.com/compose), чтобы найти другие функции,
которые могут пригодиться для ваших проектов.
Заключение
Мы рассмотрели, какие задачи можно выполнять с помощью Docker Compose и как
применять этот инструмент, чтобы упростить себе работу и повысить воспроизво­
димость окружений разработки. В следующей главе мы рассмотрим инструменты
для масштабирования Docker в центре обработки данных и в облаке.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 9
Переход на контейнеры
в рабочей среде
Мы рассмотрели инструменты для запуска стека контейнеров на одном хосте, а
теперь выясним, как проделать это в реальных условиях. В этой главе мы будем
разворачивать контейнеры в большой рабочей среде. Вам придется внести очень
много корректировок, чтобы адаптировать примеры к своим приложениям и сре­
дам, но вы получите хорошее общее представление о том, как философия Docker
работает на практике.
Подготовка к развертыванию
Перевод собранного и настроенного приложения в рабочие системы - один из
самых СЛОЖI;!ЫХ этапов всего цикла разработки. К счастью, контейнеризация упро­
щает эту задачу. Представляете, как сложно и неудобно было загружать товары на
корабли до изобретения контейнеров? Ящики, тюки и бочки всех форм и размеров
приходилось заносить на суда вручную, а потом так же вручную выгружать, при
этом думая, что вынести сначала, чтобы вся эта куча не развалилась, как башня
Дженrа (https://en.wikipedia.org/wiki/Jenga).
Грузовые контейнеры стандартных размеров произвели настоящий переворот. Эти
контейнеры можно упаковывать и сгружать в логическом порядке, и все содержи­
мое прибывает вместе, как положено. В современных портах для погрузки и вы­
грузки используется специальное оборудование. Примерно так же работает Docker.
Все контейнеры Linux подцерживают одинаковый внешний интерфейс, а инстру­
менты просто помещают их на нужные серверы, независимо от того, что находится
внутри.
Если у нас есть рабочая сборка приложения, нам не требуется детально настраивать
инструменты для его развертывания. При желании развернуть приложение только
на одном сервере достаточно будет интерфейса командной строки docker. Если сер­
веров несколько, то нам потребуется более сложный инструмент для работы с кон­
тейнерами. В любом случае нам придется передать приложению некоторые данные
и учесть разные факторы, прежде чем развертывать контейнеризированное прило­
жение в рабочей среде.
При развертывании приложения с Docker мы следуем определенной процедуре:
1. Локально собираем и тестируем образ Docker на машине разработки.
2. Собираем официальный образ для тестирования и развертывания, обычно в кон­
вейере непрерывной интеграции или системе сборки.
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
213
3. Отправляем образ в реестр.
4. Развертываем образ Docker на сервере, а затем настраиваем и запускаем контей­
нер.
Со временем все эти этапы сольются в единый рабочий процесс:
♦ Оркестрация сборки, тестирования и сохранения образов и развертывание контейнеров на серверах в рабочей среде.
На деле все несколько сложнее. В двух словах, развертывание в рабочей среде
должно отвечать трем критериям:
♦ Процесс должен быть воспроизводимым. При каждом запуске должно происхо­
дить одно и то же, причем в идеале - для всех приложений.
♦ Конфигурация должна обрабатываться автоматически. Мы определяем конфи­
гурацию приложения в определенной среде, а затем точно знаем, что эта конфи­
гурация будет соблюдаться при каждом развертывании.
♦ Должен поставляться исполняемый программный пакет, который можно запустить.
Чтобы создать такой процесс, нужно учесть много факторов. Здесь мы рассмотрим
общую схему, на которую вы сможете опираться при разработке этого процесса для
своего приложения.
Docker в рабочих средах
Мы рассмотрели разные возможности Docker и обсудили общие стратегии развер­
тывания в рабочей среде. Прежде чем мы перейдем к детальному изучению рабо­
чих контейнеров, давайте посмотрим, как Docker вписывается в традиционную и
более современную рабочую среду. При переходе на Docker с традиционной систе­
мы вы выбираете, какие задачи будете делегировать Docker, инструменту развер­
тывания, более комплексной платформе, вроде Kubemetes или облачной системы
оркестрации контейнеров. Некоторые задачи вы, возможно, предпочтете оставить
традиционной инфраструктуре. Мы успешно переносили множество систем с тра­
диционного подхода на контейнеры, и для этого есть немало эффективных реше­
ний. Давайте рассмотрим обязательные компоненты и структуру современных и
традиционных вариантов, чтобы вам проще было сделать выбор.
На рис. 9.1 приведено несколько аспектов, которые необходимо учитывать в рабо­
чей среде, современные инструменты для этих аспектов, а также более традицион­
ные системы, которые эти инструменты могут заменить. Мы разделим аспекты на
две категории - с одними работает сам Docker, а для других понадобится так на­
зываемая платформа. Платформа - это система, которая обычно охватывает кла�
стер серверов· и предоставляет единый интерфейс для управления контейнерами
Linux. Это может быть единая система, вроде Kubemetes или Docker Swann, или
несколько взаимодействующих компонентов. При переходе на полностью контей­
неризированную систему с планировщиком платформа может дублировать некото­
рые задачи. Давайте рассмотрим каждый из этих аспектов.
Книги для программистов: https://clcks.ru/books-for-it
214
Глава 9
Системный компонент
Аспект
Устаревший компонент
Приложение
Платформа
Kubernetes, Docker Swarm,
Mesos (с Marathon или Aurora)
Обнаружение сервисов
Планирование
Мониторинг
Статические балансировщики нагрузки
Скрипты развертывания, оркестрация
Nagios/Sensu, дежурные инженеры
Syslog, rsyslog, logfiles
Docker Engine
Скрипты развертывания, оркестрация,
FAT jar, ZIР-файлы, ТАR-файлы,
git clone, scp, rsync
Виртуальные машины
Puppet, Chef
Система init
Скрипты развертывания
Рис. 9.1. Роль Docker в рабочей системе
На рис. 9.1 приложение находится на верхушке стека. Оно полагается на остальные
аспекты рабочей среды. В некоторых случаях мы целенаправленно работаем с эти­
ми аспектами, а в других нам на помощь приходят неожиданные инструменты.
В любом случае в рабочей среде приложению так или иначе требуются почти все
эти аспекты, и мы должны это учитывать. Если вы хотите перейти на контейнеры
Linux, подумайте, как вы решаете эти задачи сейчас и как будете решать их в новой
системе.
Мы начнем с того, что нам хорошо известно, а потом будем двигаться снизу вверх.
Мы хорошо знаем наше приложение, и оно находится на вершине стека. Все
остальное помогает обеспечивать функционал, но именно приложение имеет цен­
ность для бизнеса, а другие аспекты просто поддерживают его, позволяют надежно
работать в большом масштабе и стандартизируют его работу. Порядок элементов
под приложением важен, но отдельно каждый верхний слой не зависит от ниж­
них - все они нужны для самого приложения.
Поскольку контейнеры Linux и Docker обеспечивают большую часть этого функ­
ционала, контейнеризация системы упростит выбор в этих аспектах. Когда мы дой­
дем до той части стека, которая обеспечивается платформой, будет сложнее, и сна­
чала нужно разобраться в том, что находится ниже.
Начнем с управления заданиями.
Управление заданиями
Управление заданиями Uob control) - важное требование в современных разверты­
ваниях. На нашей схеме (см. рис. 9.1) оно входит в нижний блок. Ни одна система
не будет работать без управления заданиями. Обычно эту задачу выполняет опера­
ционная система или одна из систем инициализации Linux (systemd, System v init,
runit, вsо rc и т. д.). Мы указываем для операционной системы информацию о про­
цессе, который хотим запустить, а затем настраиваем поведение для его перезапус­
ка, перезагрузки конфигурации и управления жизненным циклом приложения.
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
215
За остановку и запуск приложения отвечают эти системы. Кроме того, в некоторых
случаях они позволяют приложению работать более стабильно. Например, переза­
пускают его при сбое. Разным приложениям требуются разные подходы к управле­
нию заданиями. В традиционной системе Linux можно запускать и останавливать
задания по времени с помощью cron. Инструмент systemd может перезапускать при­
ложение при сбое. Подход к выполнению этих задач зависит от особенностей сис­
темы. Увы, различных реализаций существует очень много.
Если мы переходим на контейнеры, нам нужен способ снаружи управлять всеми
заданиями более или менее одинаково. Нам потребуется чуть больше метаданных
о них, чтобы получить желаемый результат, но мы не хотим заглядывать в контей­
нер. Docker Engine предоставляет целый набор команд для управления заданиями,
например docker container start, docker container stop, docker container run И docker
container kill, которые, по сути, соответствуют основным этапам жизненного цикла
приложения. Все платформы, которые строятся на контейнерах Docker, включая
Kubemetes, также используют этот жизненный цикл. Мы поместили этот аспект в
нижнюю часть стека, потому что это самая низкоуровневая абстракция, которую
Docker предоставляет для приложения. Даже если бы мы не задействовали никакие
другие возможности Docker, это все равно был бы отличный ·инструмент, ведь он
предоставляет одинаковый подход для всех приложений и платформ, на которых
выполняются контейнеры Docker.
Ограничения ресурсов
Над управлением заданиями находятся ограничения ресурсов (resource limits).
В системах Linux мы можем использовать контрольные группы Linux (control
groups, cgroups, https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html)
напрямую, чтобы управлять ограничениями ресурсов. Это довольно распростра­
ненный подход, но обычно применяются другие инструменты, вроде команды
ulimit (https:/Лinuxconfig.orgЛimit-user-environment-with-ulimit-Iinux-command),
и разные настройки среды выполнения приложения, например виртуальных машин
Java, Ruby или Python. В облачных системах у нас появилась возможность запус­
кать отдельные виртуальные серверы, чтобы ограничивать ресурсы для одного
бизнес-приложения. Такой подход позволял защититься от " шумных соседей", но
не давал такого детального контроля, как контейнеры.
Используя контейнеры Linux, мы можем ограничивать потребление ресурсов кон­
тейнерами с помощью cgroups. Мы сами решаем, хотим ли ограничивать в рабочей
среде доступ приложения к памяти, месту на диске или вводу-выводу. Обычно ре­
комендуется установить ограничения, чтобы контролировать потребности прило­
жения. В противном случае мы не сможем воспользоваться одним из главных пре­
имуществ контейнерных приложений и выполнять несколько приложений на одной
машине так, чтобы они не мешали друг другу. Мы уже видели, что Docker дает нам
такую возможность, и это одна из причин, почему стоит использовать контейнеры.
В главе 5 мы рассматривали, с помощью каких аргументов мы управляем ресурса­
ми в Docker.
Книги для программистов: https://clcks.ru/books-for-it
216
Глава 9
Сети
Подробно о сетях в Docker мы поговорим в главе 11, а здесь мы кратко рассмотрим,
как контейнеризированная система может управлять подключением приложений
к сети. Docker предоставляет широкий набор вариантов конфигурации для сетей.
Выберите для рабочей среды один механизм и используйте его для всех контейне­
ров - разные подходы лучше не комбинировать. Если вы применяете платформу,
вроде Kubemetes, некоторые решения будут приняты за вас. К счастью, большая
часть сложностей, связанных с сетями, не относится к приложению в контейнере.
Docker или платформа сами позаботятся о сетях, а приложение будет одинаково
работать в контейнере на локальном компьютере и в рабочей среде, если мы будем
следовать нескольким правилам:
1. Docker или платформа сами будут динамически пробрасывать порты и сообщать
об этом приложению. Обычно это делается с помощью переменной среды.
2. Избегайте протоколов FTP или RTSP, которые направляют обратный трафик
через случайные порты. На контейнеризированной платформе это будет очень
неудобно поддерживать.
3. Воспользуйтесь службой DNS, которую предоставляет контейнеру Docker или
среда выполнения.
Если следовать этим правилам, приложение даже не поймет, где его развернули.
Большинство рабочих сред позволяют определять фактическую конфигурацию и
применять ее в среде выполнения. Docker Compose, режим Docker Swarm,
Kubemetes и среды выполнения облачных провайдеров, вроде ECS, делают все за
нас.
Конфигурация
Всем приложениям нужен доступ к своей конфигурации. Конфигурацию можно
разделить на два уровня. На нижнем уровне определяется, как должна быть на­
строена среда Linux вокруг приложения. У контейнеров для этого есть Dockerfile,
с помощью которого мы можем всегда создавать одинаковую среду. В более тра­
диционной системе мы могли бы управлять конфигурацией, например, с помощью
Chef, Puppet или AnsiЫe. С контейнерами эти системы тоже сочетаются, но обычно
мы не применяем их для предоставления зависимостей приложениям. За это отве­
чает Docker и Dockerfile. Даже если содержимое Dockerfile меняется для разных
приложений, механизм и инструменты будут одинаковыми, и это очень удобно.
Следующий уровень - конфигурация, которая применяется к самому приложе­
нию. Эту тему мы ранее подробно обсуждали. По умолчанию в Docker для этого
предназначены переменные среды, и такой подход работает на всех современных
платформах. В некоторых системах проще оказываются более традиционные фай­
лы конфигурации. В Kubernetes, например, это относительно просто, но мы не
советуем этот подход, если вы стремитесь создать по-настоящему переносимое
контейнеризированное приложение. Мы считаем, что это ухудшает наблюдаемость
приложения, и не рекомендуем задействовать файлы конфигурации. Подробнее
о переменных среды мы поговорим в главе 13.
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
217
Упаковка и поставка
Упаковку и поставку мы объединим в один раздел. Это область, в которой контей­
неризированная система заметно опережает традиционную. Если вернуться к ана­
логии с грузовыми контейнерами, у нас есть единая стандартная упаковка, образ
контейнера, и стандартный способ его перемещения - реестр Docker и инструмен­
ты image pull и image push. В традиционных системах пришлось бы создавать отдель­
ные инструменты развертывания и надеяться, что их можно стандартизировать,
чтобы применять для нескольких приложений. При работе в многоязычной среде
это привело бы к проблемам. В контейнеризированной среде нужно продумать, как
мы будем упаковывать приложения в образы и как эти образы хранить.
Самый простой способ хранения - платная подписка в коммерческом реестре
образов. Если это допустимо для вашей компании, рассмотрите такой вариант.
Несколько облачных провайдеров, включая Amazon, предлагают сервисы для раз­
мещения образов, которые можно развернуть в вашей среде, и это тоже неплохой
метод. Конечно, мы можем создать частный реестр, как обсуждалось в разделе
"Управление частным реестром" главы 4. Нам доступно немало решений, и следу­
ет рассмотреть разные варианты.
Журналирование
Журналирование - это аспект, который находится на границе между задачами,
которые мы можем поручить Docker, и задачами, которыми будет управлять плат­
форма. Как мы уже видели в главе 6, Docker может собирать все журналы из кон­
тейнеров и передавать их в другое место. По умолчанию журналы остаются в ло­
кальной системе. Это очень удобно для сред ограниченного размера, и вы можете
выбрать такой вариант, если вам подходит хранилище на локальном хосте. Ваша
платформа будет обрабатывать журналы от множества приложений на различных
системах, так что, возможно, стоит централизовать журналирование, чтобы улуч­
шить видимость и упростить устранение неисправностей. При проектировании
такой системы опирайтесь на главу 6, где мы подробно рассматриваем журналиро­
вание. Некоторые системы, например Kubemetes, используют свои подходы к сбо­
ру журналов. Со стороны приложения нам достаточно указать, что оно отправляет
записи в stdout или stderr, а Docker или платформа позаботятся об остальном.
Мониторинг
Это первый аспект системы, который Docker или контейнеры Linux не берут на се­
бя полностью, но он все же получает преимущества от стандартизации, обеспечи­
ваемой Docker. Возможность проводить стандартные проверки работоспособности,
которые мы обсуждали в главе 6, упрощает мониторинг работоспособности прило­
жений. Во многих системах за мониторинг отвечает сама платформа, а планиров­
щик динамически завершает работу неработоспособных контейнеров и даже может
перенести рабочую нагрузку на другой сервер или перезапустить ее в той же сис­
теме. В более старых системах мониторинг контейнеров реализован с помощью
Книги для программистов: https://clcks.ru/books-for-it
218
Глава 9
существующих решений, таких как Nagios, Zabbix или другие традиционные сис­
темы мониторинга. Как мы видели в главе 6, существуют и более современные ва­
рианты, например Prometheus. Коммерческие решения для мониторинга произво­
дительности приложений (Application Performance Monitoring, АРМ), например
New Relic, Datadog или Honeycomb, предлагают первоклассную поддержку для
контейнеров Linux и приложений внутри них. Если вы уже используете подобное
решение для мониторинга своего приложения, скорее всего, вам почти не придется
ничего менять.
В более старых системах при возникновении проблем приходилось привлекать ин­
женеров, которые принимали решения о необходимых мерах. В динамических сис­
темах эти процессы во многом автоматизированы и выполняются в рамках плат­
формы. В переходный период система будет основана на комбинации подходов, и
инженеры будут получать оповещения, только если платформа не может решить
проблему. В любом случае окончательное решение всегда за человеком, но в кон­
тейнеризированных системах решать проблемы гораздо проще, потому что у нас
есть стандартизированные механизмы, единые для нескольких приложений.
Планирование
Как распределить сервисы по серверам? Контейнеры можно легко перемещать, по­
тому что Docker предоставляет для этого удобные механизмы. Мы можем оптими­
зировать потребление ресурсов, повысить надежность приложения, обеспечить са­
мовосстановление сервисов и добиться динамического масштабирования. Какой
инструмент должен принимать эти решения?
В более старых системах мы выделяли сервису несколько серверов. В скриптах
развертывания мы прописывали список серверов, и при каждом развертывании но­
вое приложение работало только на этом наборе серверов. Модель с одним серви­
сом на сервер была широко распространена при первых попытках виртуализации
в частных центрах обработки данных. Облачные системы поддерживали эту мо­
дель, предоставляя удобные виртуальные серверы с нужными характеристиками.
Отчасти это динамическое поведение в системах, вроде АWS, обрабатывалось
с помощью автоматического масштабирования. Но если мы переходим на контей­
неры и начинаем запускать много сервисов на одном виртуальном сервере, мас­
штабирование и динамические изменения на уровне сервера нам не помогут.
Распределенные планировщики
Распределенные планировщики используют Docker, чтобы мы могли смотреть на
целую сеть серверов так, будто это один компьютер. Мы назначаем политики, ука­
зывая, как должно выполняться приложение, и система сама определяет, где и
сколько экземпляров запускать. Если что-то идет не так на сервере или в приложе­
нии, планировщик запускает его снова на доступном работоспособном ресурсе, ко­
торый отвечает требованиям приложения. Этот подход соответствует изначальной
задумке основателя Docker, lnc. Соломона Хайкса (https://www.linkedin.com/in/
solomonhykes)- мы хотим запускать приложения где угодно, не беспокоясь о том,
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
219
как они будут развертываться. Как правило, нулевой простой при развертывании
в этой модели достигается с помощью сине-зеленого развертывания (https://
martinfowler.com/Ыiki/ВlueGreenDeployment.html) - это подход, при котором
мы запускаем новое поколение приложения наряду со старым, а затем медленно
переносим трафик со старого стека на новый.
Если использовать знаменитую метафору Келси Хайтауэра (Kelsey Hightower)
(https://youtu.be/НIAXp0-M6SY?t=10m23s), планировщик будто играет в "Тетрис",
на лету размещая сервисы по серверам, подбирая лучшее соответствие.
Kubernetes (https://kubernetes.io/), представленный Google в 2014 году, не быллер­
вым в своем роде - сначала появились Mesos и Cloud Foundry, - но сейчас он
бесспорный лидер среди планировщиков в контейнерных системах. В ранних вы­
пусках Kubernetes учитывался опыт Google с собственной внутренней системой
Borg (https://kubernetes.io/Ыog/2015/04/borg-predecessor-to-kubernetes), который
стал доступен сообществу в виде открытого исходного кода. Kubernetes изначально
строился на Docker и контейнерах Linux и поддерживает не только Docker
containerd, но и несколько других сред выполнения контейнеров, которые также ис­
пользуют контейнеры Docker. Kubernetes - комплексная система со множеством
компонентов. У Kubernetes есть много коммерческих и облачных разновидностей.
Cloud Native Computing Foundation (https://landscape.cncf.io/members?category=
certifie� -ku bernetes-distribu tio n %2С certified-kubernetes-hosted %2 Ccertified­
kubernetes-installer&grouping=category) предлагает сертификацию, чтобы каждый
дистрибутив отвечал определенным стандартам, принятым в сообществе Kuber­
netes. Kubernetes - очень эффективный инструмент, но эта сфера постоянно разви­
вается, и за нововведениями довольно сложно уследить. Если вы разрабатываете
новую систему с нуля, рассмотрите Kubernetes. Если у вас нет другого опыта и вы
используете облако, то самым простым вариантом будет реализация, предлагаемая
вашим провайдером. Kubernetes - это популярная платформа, но далеко не един­
ственный вариант.
В 2015 году компания Docker, Inc. представила режим Docker Swarm, изначально
разработанный под Docker. Это неплохой вариант, если вам требуется очень про­
стой инструмент оркестрации, который полностью находится в пределах платфор­
мы Docker и поддерживается одним вендором. Режим Docker Swarm так и не стал
популярным, и поскольку Docker активно интегрирует Kubernetes, это не самый
очевидный вариант.
Оркестрация
Когда мы говорим о планировщиках, то часто имеем в виду не только распределе­
ние заданий по ресурсам, но и возможности оркестрации, т. е. администрирования
и организации приложений и развертываний по всей системе. Планировщик может
автоматически перемещать задания на лету или разрешать вам запускать задачи на
отдельных серверах. В более старых системах для этого были предусмотрены спе­
циальные инструменты оркестрации.
В большинстве современных контейнерных систем все задачи по оркестрации,
включая планирование, обрабатываются основным программным обеспечением
Книги для программистов: https://clcks.ru/books-for-it
220
1
Глава 9
для управления кластером, например Kubemetes, Swaпn, системой управления кон­
тейнерами конкретного провайдера и т. д.
Планирование, несомненно, лучшая среди всех возможностей платформы. Хотя
именно из-за нее приходится больше всего переделывать приложения при переносе
в контейнер. Многие традиционные приложения разрабатывались без поддержки
обнаружения сервисов и изменения выделенных ресурсов, так что приходится про­
делать большую работу, чтобы адаптировать их к действительно динамичной сре­
де. Поэтому при переносе приложения в контейнеризированную систему поначалу
вы можете не применять планировщик. Часто лучше начать с контейнеризации
приложений в традиционной системе, и только затем переходить к более динамич­
ной системе планирования. Иначе говоря, сначала приложение будет работать
в контейнерах на тех же серверах, где было развернуто изначально, а когда вы убе­
дитесь, что все работает, добавляйте планировщик.
Обнаружение сервисов
Обнаружение сервисов - это механизм, с помощью которого приложение находит
все сервисы и ресурсы, необходимые для работы. Редкое приложение обходится
без зависимостей от внешних компонентов, так что обнаружение сервисов не пона­
добится разве что статическим сайтам, не имеющим состояния. Все остальные
должны каким-то образом получать информацию об окружающей системе. Обычно
в этом задействовано несколько систем, но они тесно связаны друг с другом.
Возможно, вы удивитесь, но в традиционных системах обнаружением сервисов
в основном занимаются балансировщики нагрузки, которые отвечают за надеж­
ность и масштабирование, а еще они следят за всеми эндпоинтами, связанными
с определенным сервисом. Иногда они настраиваются вручную, иногда работают
динамически, но другие системы находят конечные точки сервиса с помощью из­
вестного адреса или имени через балансировщик нагрузки. Это в своем роде обна­
ружение сервисов, и в более старых системах для этого широко использовались
балансировщики нагрузки. В современных средах они тоже часто применяются
в этих целях, даже если мало напоминают традиционные балансировщики нагруз­
ки. Другие варианты обнаружения сервисов в старых системах - статические кон­
фигурации базы данных или файлы конфигурации приложения.
Как вы видели на рис. 9.1, Docker не выполняет обнаружение сервисов, разве что
при использовании режима Docker Swarm. В подавляющем большинстве систем за
обнаружение сервисов отвечает платформа. Получается, что это первый аспект,
которым вам нужно будет заняться в более динамичной системе. Контейнеры легко
перемещаются, и традиционная система, построенная для статически развертывае­
мых приложений, их не поддержит. Каждая платформа по-разному реализует обна­
ружение сервисов, и вам следует выбрать вариант, подходящий для вашего случая.
Docker Swarm (классический Swarm) (https://github.com/docker-archive/classicswarm)
и режим Docker Swarm - это не одно и то же. Подробнее о режиме Docker Swarm
мы поговорим в главе 10.
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
221
Примеры механизмов обнаружения сервисов, с которыми вы, возможно, работали:
♦ балансировщики нагрузки со стандартными адресами;
♦ Round-roЬin DNS;
♦ SRV-записи DNS;
♦ динамический DNS;
♦ многоадресный DNS;
♦ оверлейные сети со стандартными адресами;
♦ протоколы Gossip;
♦ протокол Apple Bonjour (https://en.wikipedia.org/wiki/Вonjour_(software));
♦ Apache ZooKeeper (https://zookeeper.apache.org/);
♦ HashiCorp's Consul (https://www.consul.io/);
♦ etcd (https://etcd.io/).
Как видите, список длинный, но вариантов гораздо больше. Некоторые из этих сис­
тем также выполняют и другие задачи, помимо обнаружения сервисов, и это может
еще больше запутать. Пример обнаружения сервисов, на котором удобно изучать
эту концепцию, - механизм связывания, используемый Docker Compose в главе 8.
Этот механизм задействует систему DNS, поставляемую ctockerd, которая позволяет
одному сервису в Docker Compose указывать имя другого сервиса и возвращать
IР-адрес нужного контейнера. Kubemetes в простейшем виде тоже предлагает по­
добную систему с внедренными переменными среды. Но это самые простые формы
обнаружения в современных системах.
Часто интерфейс для этих систем предусматривает стандартные имена и/или порты
для сервиса. Мы можем указать http://service-a.example.com, чтобы обратиться
к сервису А по стандартному имени. Или же по http://services.example.com:service­
a-port можно обратиться к этому же сервису по имени и порту. В современных
средах часто принят другой подход. В новой системе этот процесс обычно управля­
ем и довольно органичен. С платформы обращаться к более традиционным систе­
мам проще, чем в обратном направлении. Обычно лучше начать с системы, где мы
представляем динамически настраиваемые балансировщики нагрузки, к которым
легко обращаться из старой среды. Хотя в долгосрочной перспективе это не иде­
альное решение. Kubernetes для этого предоставляет маршруты Ingress, и это не­
плохой вариант для пользователей этой платформы.
Примеры реализации такого подхода:
♦ в Kubemetes - контроллеры Ingress 1, включая Traefik2 или Contour
(https://projectcontour.io/) и др.;
♦ Linkerd service mesh (https://linkerd.io/);
1 Полный URL-aдpec: https://kubernetes.io/docs/concepts/services-netw orking/ingress.
2 Полный URL-aдpec: https://doc.traefik.io/traefik/providers/kubernetes-ingress.
Книги для программистов: https://clcks.ru/books-for-it
222
1
Глава 9
♦ отдельный Sidecar (https://github.com/NinesStack/sidecar) для обнаружения сервисов с прокси Lyft Envoy (https://github.com/envoyproxy/envoy);
♦ Istio service mesh (https://istio.io/) и Lyft Envoy.
Если в вашей системе сочетаются современные и традиционные подходы и техно­
логии, направить трафик в новую контейнеризированную систему обычно сложнее,
и следует обдумать все заранее.
Заключение по рабочей среде
Многие люди начинают с простых инструментов оркестрации Docker. Однако
когда контейнеров станет больше и вы будете развертывать их чаще, без распреде­
ленных планировщиков не обойтись. Такие инструменты, как Kubernetes, позволя­
ют абстрагировать отдельные серверы и целые центры обработки данных в огром­
ные пулы ресурсов для выполнения контейнеров.
Конечно, сушествуют и другие решения, но эти самые популярные и часто упоми­
наемые. В этой сфере постоянно пщ1вляются новы,е технологии, так что изучите
тему, прежде чем приступать к делу.
В любом случае сначала создайrе и запустите инфраструктуру контейнеров Linux, а
затем присматривайтесь к сторонним инструментам. Встроенные инструменты
Docker вполне могут подойти для вашей ситуации. Начните с самого простого ин­
струмента для определенной задачи, но помните, что у вас очень широкий выбор,
и для контейнеров Linux появляется все больше эффективных решений.
Docker в конвейере DevOps
Изучив и реализовав весь этот функционал, мы получим надежную рабочую среду.
Как узнать, что все работает? Одно из главных преимуществ Docker - возмож­
ность тестировать приложения и все его зависимости в точности в той среде, в ко­
торой оно в итоге будет выполняться. Docker не гарантирует тестирование внешних
зависимостей, вроде баз данных, и мы не получаем волшебную платформу тести­
рования, но зато можем протестировать вместе зависимости от библиотек и другого
кода. При изменении базовых зависимостей проблемы могут возникнуть даже
у организаций с продуманными стратегиями тестирования. С помощью Docker мы
можем собрать образ, запустить его на компьютере разработки, а потом протести­
ровать тот же образ в конвейере непрерывной интеграции, прежде чем отправить
на рабочие сервер1;,1.
Тестировать приложение в контейнерах не сложнее, чем 'тестировать само при­
ложение, если тестовая среда поддерживает рабочие нагрузки контейнеров Linux.
Давайте рассмотрим пример.
Краткий обзор
Давайте создадим пример рабочей среды для вымышленной компании. Это будет
типичная среда, в которую мы затем добавим Docker.
Книги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
223
У нашей вымышленной компании есть пул рабочих серверов, на которых выпол­
няются демоны Docker и где развернуты разные приложения. У сервера координа­
ции конвейера есть несколько рабочих узлов сборки и тестирования. Пока мы опус­
тим развертывание, и вернемся к нему, когда вымышленное приложение будет про­
тестировано и готово к поставке.
На рис. 9.2 изображен типичный рабочий процесс для тестирования контейнеризи­
рованноrо приложения, который включает следующие шаги:
1. Сборка запускается извне - например, с помощью вызова вебхука из репозитория исходного кода или вручную разработчиком.
2. Сервер сборки запускает сборку образа контейнера.
3. На локальном сервере создается образ.
4. Образ помечается тегом с номером сборки или версии или хешем коммита.
5. Для запуска тестового набора настраивается новый контейнер, основанный на
собранном образе.
6. Для контейнера выполняются тесты, результат передается серверу сборки.
7. Отмечается, прошла сборка тесты или нет.
8. Если прошла, она передается в реестр образов или другой механизм хранения.
Это похоже на обычный процесс тестирования приложений. Как минимум нам тре­
буется задание, которое запускает тестовый набор. С помощью этих действий мы
сначала создаем образ контейнера и вызываем в контейнере тестовый набор.
Запуск сборки
Уведомление
о неудаче
Рис. 9.2. Схема процесса тестирования в Docker
Давайте посмотрим, как этот процесс будет работать в нашем вымышленном при­
ложении. Мы только что обновили приложение и отправили последний код в репо­
зиторий Git. У нас есть хук, который на каждый коммит запускает сборку, так что
Книги для программистов: https://clcks.ru/books-for-it
224
Глава 9
на сервере сборки выполняется задание, которое также активирует демон dockerd.
Задание на сервере сборки назначает задачу тестовому узлу. На рабочем узле не
запущен dockerd, но установлен инструмент командной строки Docker. Мы выпол­
няем команду docker irnage build для удаленного демона dockerd, чтобы создать новый
образ на удаленном сервере Docker.
Образ контейнера нужно собрать точно таким, каким мы отправим его в рабочую
среду. Если для тестирования нужно временно что-то изменить, используем пре­
доставленные извне параметры, либо через переменные среды, либо через аргу­
менты командной строки. Мы хотим протестировать именно ту сборку, которую
будем поставлять, так что это очень важно.
После сборки образа задание тестирования создаст и выполнит новый контейнер на
основе нового образа. Образ настроен для выполнения приложения в рабочей сре­
де, но для тестирования нужно выполнить другую команду. Эго нормально. Docker
позволяет просто указать эту команду в конце команды docker container run. В рабо­
чей среде воображаемый контейнер запустит supervisor, который, в свою очередь,
запустит экземпляр nginx и экземпляры веб-сервера Ruby Unicom. Для тестирования
нам не потребуется nginx и не нужно запускать веб-приложение. Вместо этого зада­
ние сборки вызывает контейнер:
$ docker container run -е ENVIRONMENT=testing -е АРI_КЕУ=12345 \
-it awesome_app:versionl /opt/awesome_app/test.sh
Мы выполнили docker container run, а еще передали в контейнер пару переменных
среды: ENVIRONMENТ и API_REY. Эго могут быть новы� значения или замена для значе­
ний, которые Docker уже экспортировал. Мы также указали определенный тег versio1,l. Благодаря этому тегу мы сможем использовать нужный образ, даже если
одновременно будет запущена еще одна сборка. Затем мы переопределяем коман­
ду, которую контейнер должен выполнять, в строке смо в Dockerfile - мы будем
вызывать тестовый скрипт /opt/awesome_app/test.sh. В нашем примере это необяза­
тельно, но иногда необходимо переопределить в Dockerfile ENTRYPOINT (--entrypoint),
чтобы можно было выполнить в контейнере что-то, кроме команды по умолчанию.
Всегда указывайте точный тег Docker (обычно это версия или хеш коммита) для
образа в тестовом задании. Если вы всегда указываете просто latest, невозможно
гарантировать, что другое задание не заменит этот тег сразу после начала сборки.
Если мы указываем точный тег, то можем быть уверены, что тестируем нужную
сборку.
Обратите внимание, что docker container run завершится с кодом возврата команды,
которая вызывалась в контейнере. Эго значит, что по коду выхода мы увидим,
успешно ли выполнены тесты. Если тестовый набор хорошо продуман, больше нам
ничего не понадобится. Если нужно выполнить несколько шагов или на код выхода
невозможно положиться, можно записать все выходные данные тестового прогона
в файл, а затем поискать в них сообщения о статусе. Так происходит с нашей вы­
мышленной сборкой: мы выводим выходные данные тестового набора и в послед­
ней строке в test.sh увидим Result: succESS! (Результат: ПРОЙДЕНО) или Result:
FAILURE! (Результат: НЕ ПРОЙДЕНО). Если вы применяете этот механизм, убедиКниги для программистов: https://clcks.ru/books-for-it
Переход на контейнеры в рабочей среде
1
225
тесь, что проверяете не случайные строки. Например, слово success должно стоять
в последней строке файла, и строка должна точно соответствовать стандартному
выводу. В этом случае мы смотрим на последюою строку файла, видим success и
помечаем сборку как прошедшую тест.
Нужно выполнить еще один шаг, связанный с контейнером. Мы хотим отправить
образ сборки, которая прошла тест, в реестр. Реестр хранит образы для сборок и
развертываний, а также позволяет делиться ими с коллегами и использовать для
других сборок. Это место, куда мы складываем успешные сборки с тегами. Скрипт
сборки выполнит команду docker irnage tag, чтобы присвоить образу подходящие
теги, возможно, включая latest, а затем запустит docker irnage push, чтобы отправить
образ в реестр.
Вот и все. Как видите, процесс мало чем отличается от тестирования обычного
приложения. Мы воспользовались клиент-серверной архитектурой Docker, чтобы
вызвать тест не на основном сервере тестирования, а также заключили тесты в кон­
солидированный shеll-скрипт, чтобы получить выходной статус. В целом это очень
похоже на большинство современных подходов к системе сборки.
Система нашей вымышленной компании позволяет убедиться, что в рабочую среду
будут поставляться только приложения, которые прошли тесты на том же дистри­
бутиве Linux, с теми же библиотеками и теми же параметрами сборки. Контейнер
можно протестировать с внешними зависимостями, например с базами данных и
кешами, без имитаций. Конечно, гарантировать успех на 100% невозможно, но это
гораздо ближе к истине, чем рулетка, в которую мы играем, когда создаем системы
развертывания без контейнеров.
Если вы используете Jenkins для непрерывной интеграции или ищете способ тес­
тировать Docker с масштабированием, существует множество неплохих плагинов
(https://plugins.jenkins.io/) для Docker, Mesos и Kubemetes. Многие управляемые
� коммерческие платформы предоставляют контейнеризированные среды непре­
рывной интеграции, включая CircleCI (https://circleci.com/) и GitHub Actions
(https://github.Ыog/2022-02-02-build-ci-cd-pipeline-github-actions-four-steps).
Внешние зависимости
Как насчет внешних зависимостей, которые мы пока пропустили? Например, базы
данных или экземпляры Memcached или Redis, с которыми нужно протестировать
контейнер? Если нашему вымышленному приложению требуется база данных или
экземпляр Memcached или Redis, мы должны протестировать эту внешюою зависи­
мость в надежной тестовой среде. Для этого бьmо бы удобно использовать контей­
нер. Приложив некоторые усилия, мы можем сделать это с помощью таких инстру­
ментов, как Docker Compose (https://github.com/docker/compose), который мы
подробно обсуждали в главе 8. В Docker Compose задание сборки может выражать
некоторые зависимости между контейнерами, а Compose будет органично их под­
ключать.
Нам пригодится возможность тестировать приложение в среде, которая очень
похожа на реальные условия, и Compose упрощает эту задачу. Нам по-прежнему
Книги для программистов: https://clcks.ru/books-for-it
226
1
Глава 9
потребуются тестовые фреймворки для конкретных языков, но эту среду действи­
тельно легко оркестрировать.
Заключение
Мы увидели, как контейнеризированное приложение взаимодействует с внешней
средой и как разграничиваются разные аспекты, а далее рассмотрим, как собирать
кластеры Docker для поддержки современных глобальных приложений, доступных
непрерывно и по требованию.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 10
Масштабирование контейнеров
Одно из главных преимуществ контейнеров заключается в том, что они не зависят
от базового оборудования и операционной системы, поэтому приложение не огра­
ничено определенным хостом или средой. Такой подход позволяет легко масшта­
бировать приложения без сохранения состояния не только в пределах центра обра­
ботки данных, но и на разных облачных платформах, избегая при этом типичных
трудностей, возникающих в традиционных системах. Контейнер в одном облаке не
отличить от контейнера в другом, - как грузовые контейнеры на разных судах.
Многие организации выбирают готовое облачное развертывание контейнеров
Linux, чтобы сразу получить все преимущества масштабируемой платформы и не
тратить ресурсы на создание собственной системы. Так действительно проще, но
создать платформу в облаке или своем центре обработки данных не так уж сложно,
и мы рассмотрим некоторые варианты.
Все крупные облачные провайдеры предлагают нативную поддержку контейнеров
Linux. Лучшие публичные облака для контейнеров Linux:
♦ Amazon Elastic Container Service (https://aws.amazon.com/ecs);
♦ Google Cloud Run (https://cloud.google.com/run);
♦ Azure Container Apps (https://azure.microsoft.com/en-us/services/container-apps).
Те же компании предлагают эффективные решения с Kubernetes:
♦ Amazon Elastic Kubernetes Service (https://aws.amazon.com/eks);
♦ Google Kubernetes Engine (https://cloud.google.com/kubernetes-engine);
♦ Azure Kubernetes Service (https://azure.microsoft.com/en-us/services/kubernetes-
service).
Установить Docker на экземпляре Linux в публичном облаке несложно, но это лишь
первый шаг к созданию полноценной рабочей среды. Вы можете сделать это само­
стоятельно или выбрать один из множества инструментов от облачных провайде­
ров, Docker, Inc. и обширного сообщества. Большинство инструментов будут рабо­
тать одинаково хорошо в публичном облаке и вашем центре обработки данных.
Среди планировщиков и более сложных систем есть достаточно вариантов, кото­
рые повторяют большую часть функционала, предлагаемого провайдерами публич­
ных облаков. Даже если вы используете публичное облако, у вас могут быть при­
чины выбрать другую среду для контейнеров Linux вместо готового решения.
В этой главе мы рассмотрим варианты запуска контейнеров Linux в большом мас­
штабе. Начнем с простого - режима Docker Swarm, - а затем перейдем к более
Книги для программистов: https://clcks.ru/books-for-it
228
1
Глава 10
сложным инструментам: Kubemetes и облачным решениям. В этих примерах мы
увидим, как с помощью Docker создать очень гибкую платформу для рабочих
нагрузок приложения.
Режим Docker Swarm
Создав среду выполнения контейнеров в виде Docker Engine, инженеры Docker
принялись разрабатывать решения для оркестрации отдельных хостов Docker и эф­
фективного размещения на них контейнеров. Первый подобный инструмент назы­
вался Docker Swarm. Мы уже говорили, что есть два инструмента с названием
Swarm, и оба от компании Docker, Inc.
Изначальный отдельный Docker Swarm теперь называется классическим Docker
Swarm (https://github.com/docker-archive/classicswarm), а вторая реализация из­
вестна как режим Swarm (https://docs.docker.com/engine/swarm). Это не отдельный
продукт, он встроен в клиент Docker. Встроенный режим Swarm предлагает гораздо
больше возможностей, чем отдельный инструмент, и в итоге полностью его заме­
нит. Режим Swarm не нужно устанавливать отдельно, он уже доступен во всех сис­
темах с Docker, и в этом его большое преимущество. Здесь мы будем говорить
именно об этой реализации Docker Swarm. Теперь вы понимаете, что у Docker
Swarm есть две реализации, и это поможет вам не запутаться, когда будете искать
информацию в Интернете.
Docker Swarm предоставляет для инструмента командной строки ctocker единый ин­
терфейс, но работать через него можно с целым кластером, а не с одним демоном
Docker. Главная задача Swarm - управлять кластерными вычислительными ресур­
сами с помощью инструментов Docker. Он постоянно развивается и теперь содер­
жит несколько плагинов планировщиков с разными стратегиями для назначения
контейнеров хостам, а также обеспечивает базовые возможности обнаружения сер­
висов. Однако это по-прежнему всего один компонент более сложного решения.
Кластеры Swarm могут содержать один или несколько менеджеров, которые вы­
ступают как центральный хаб управления для кластера Docker. Лучше создавать
нечетное число менеджеров. В каждый момент времени только один менеджер ру­
ководит кластером. Добавляя в Swarm новые узлы, мы объединяем их в кластер,
которым можно легко управлять с помощью инструментов Docker.
Давайте запустим кластер Swaпn. Нам понадобится три или больше серверов Linux,
связанных друг с другом по сети. На каждом сервере должен быть установлен по­
следний выпуск Docker Community Edition из официальных репозиториев Docker.
; В главе 3 приводятся инструкции по установке пакетов dock«-ce на Linux.
В этом примере у нас будет три сервера UЬuntu с docker-ce. Подключимся по ssh
к серверу, который будет менеджером Swarm, а затем вьшолним команду swarrn init,
указав IР-адрес для менеджера Swarm:
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
229
$ ssh 172.17.4.1
uЬuntu@172.17.4.1:$ sudo docker swarm init --advertise-addr 172.17.4.1
Swarm initialized: current node (hypysglii5syybd2zewбovuwq) is now а manager.
То add а worker to this swarm, run the following command:
docker swarm join --token SWМТКN-1-14......a4o55z0lzq 172.17.4.1:2377
То add а manager to this swarm, run 'docker swarm join-token manager'
and follow the instructions.
�
� 1
Вы должны обеспечить безопасность кластера с режимом Docker Swarm, но здесь
мы не будем обсуждать этот вопрос. Прежде чем запустить режим Docker Swarm
на долговременных системах, изучите все параметры и примите меры для защиты
среды.
Во многих примерах в этой главе необходимо указывать корректный IР-адрес для
� управляющего и рабочих узлов.
-
Это действие инициализирует менеджер Swaлn, и мы получим токен для узлов,
которым нужно присоединиться к кластеру. Сохраните этот токен в безопасном
месте, например в менеджере паролей. Не бойтесь его потерять - мы всегда мо­
жем получить его, выполнив в менеджере следующую команду:
sudo docker swarm join-token --quiet worker
Вы можете проверить свой текущий прогресс, запустив локальный клиент docker
с IР-адресом нового менеджера:
$ docker -Н 172.17.4.1 system info
Swarm: active
NodeID: 19gfcj7xwii5deveu3raf4782
Is Manager: true
ClusterID: mvdaf2xsqwjwrb94kgtn2mzsm
Managers: 1
Nodes: 1
Default Address Pool: 10.0.0.0/8
SuЬnetSize: 24
Data Path Port: 4789
Orchestration:
Task History Retention Limi.t: 5
Raft:
Snapshot Interval: 10000
NumЬer of Old Snapshots to Retain: О
HeartЬeat Tick: 1
Election Tick: 10
Dispatcher:
HeartЬeat Period: 5 seconds
Книги для программистов: https://clcks.ru/books-for-it
230
Глава 10
СА Configuration:
Expiry Duration: 3 months
Force Rotate: О
Autolock М&nagers: false
Root Rotation In Proqress: false
Node Address: 172.17.4.1
мanager Addresses:
172.17.4.1:2377
Мы можем просмотреть все узлы, которые сейчас находятся в кластере, следующей
командой:
$ docker -Н 172.17.4.1 node 1s
STATUS
НОSТNАМЕ
ID
19...82 * ip-172-17-4-1 Ready
AVAILAВILITY МANAGER STATUS ENGINE VERSION
Active
Leader
20.10.7
Теперь можно добавить в кластер Swarm еще два сервера в качестве рабочих узлов.
Вот что мы сделали бы в рабочей среде при желании увеличить масштаб, и при ис­
пользовании Swaпn это довольно легко:
$ ssh 172.17.4.2 \
"sudo docker swarm join --token SWМТКN-1-14...... a4o55z01zq 172.17.4.1:2377"
This node joined а swarm as а worker.
$ ssh 172.17.4.3 \
"sudo docker swarm join --token SWМТКN-1-14...... a4o55z01zq 172.17.4.1:2377"
This node joined а swarm as а worker.
Иногда необходимо добавлять новые управляющие узлы, и это так же просто, как
; добавлять рабочие, - достаточно передать токен присоединения менеджера, а не
рабочего узла. Выполните команду docker swarm join-token manager на любом ак­
тивном узле, чтобы узнать токен.
Если мы снова выполним команду docker node 1s, то увидим, что в кластере уже три
узла, и только один из них помечен как Leader:
$ docker -Н 172.17.4.1 node 1s
ID
HOSTNAМE
STATUS AVAILAВILITY МANAGER STATUS ENGINE VERSION
19...82 * ip-172-17-4-1 Ready
Active
Leader
20.10.7
3d... 7Ь
ip-172-17-4-2 Ready
Active
20.10.7
ip...qe
ip-172-17-4-3 Ready
Active
20.10.7
Это все, что нужно сделать для запуска кластера Swarm в режиме Swarm
(рис. 10.1).
Теперь нужно создать сеть для взаимодействия сервисов. В Swaпn есть сеть по
умолчанию - ingress, но мы можем легко создать дополнительные сети для улуч­
шенной изоляции:
$ docker -Н 172.17.4.1 network create --driver=overlay default-net
ckwh5ph4ksthvx6843ytrl5ik
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
$ docker -Н 172.17.4.1 network 1s
NEТWORК ID
NАМЕ
bridge
494ela1Ьf8f3
default-net
xqgshgOnurzu
2e7d2d7aaf0f
docker_gwbridge
df0376841891
host
n8kjdбoa44fr
ingress
Ь4720еа13Зdб
none
DRIVER
bridge
overlay
bridge
host
overlay
null
231
SCOPE
local
swarm
local
local
swarm
local
Кластер в режиме Docker Swarm
Рис. 10.1. Простой кластер в режиме Docker Swarm
До сих пор мы занимались только инфраструктурой и не развернули никакой биз­
нес-логики. Давайте запустим в кластере первый сервис с помощью команды:
$ docker -Н 172.17.4.1 service create --detach=true --name quantum \
--replicas 2 --puЬlish puЬlished=80,target=8080 --network default-net \
spkane/quantum-game:latest
tiwtsЬf270mh83032kuhwv07c
Этот сервис запускает контейнеры с игрой Quantum Game - это браузерная голо­
воломка с квантовой механикой, и она поинтереснее, чем очередной Hello World
(https://github.com/stared/quantum-game).
�
Во многих примерах мы указываем тег latest, но не забывайте, что он не подходит
для рабочей среды. Здесь нам удобно отправлять обновления с помощью этого
тега, но он непостоянный, поэтому его нельзя прикреплять к конкретной долго­
срочной версии. При использовании тега latest развертывания теряют воспроизводимость. В итоге на разных серверах будут развернуты разные версии приложе­
ния.
Давайте выполним команду docker service ps с именем созданного сервиса, чтобы
посмотреть наши контейнеры:
Книги для программистов: https://clcks.ru/books-for-it
232
Глава 10
$ docker -Н 172.17.4.1 service ps quanturn
NАМЕ
ID
IМAGE
NODE
DESIRED... CURRENT ... ERROR PORTS
rk...13 quanturn.1 spkane/qua ... ip-172-17-4-1 Running
Running...
lz ...tЗ quanturn.2 spkane/ qua ... ip-172-17-4-2 Running
Running...
Режим Swann использует маршрутизирующую mesh-ceть, чтобы автоматически
перенаправлять трафик в контейнер, который может обслужить запрос. Когда мы
указываем опубликованный порт в команде docker service create, mesh-ceть позволя­
ет подключиться к этому порту на любом из трех узлов и направляет нас к веб­
приложению. Мы упомянули три узла, но у нас запущено всего два экземпляра.
В традиционной системе для этого пришлось бы создать отдельный слой обратного
прокси, но режим Swann делает все за нас.
Давайте протестируем сервис, введя в браузере IР-адрес любого из узлов:
http://172.17.4.l/
Если все работает нормально, отобразится игровая доска Quantum Game (https://
quantumgame.io/):
То get а list of all the services, we can use +service ls+:
$ docker -Н 172.17.4.1 service ls
PORTS
ID
NАМЕ
MODE
REPLICAS IМAGE
spkane/quanturn-garne:latest *:80-> 8080/tcp
iu... 9f quanturn replicated 2/2
Мы видим общую информацию, но иногда этого недостаточно. При необходимости
Docker предоставляет подробные метаданные не только о контейнерах, но и о сер­
висах. Для этого выполним команду service inspect:
$ docker -Н 172.17.4.1 service inspect --pretty quanturn
iuohбoxrec9fk67ybwuikutqa
m:
Nаше:
quanturn
Servioe Моdе: Replicated
Replicas: 2
Plaoement:
Updateeonfig:
Parallelism: 1
0n failure: pause Мonitoring Period: Ss
Мах failure ratio: О
Update order: stop-first
RollЬackConfig:
Parallelism: 1
0n failure: pause
Мonitoring Period: Ss
Мах failure ratio: О
RollЬack order: stop-first
ContainerSpec:
Image: spkane/quanturn-garne:latest@sha256:lf57...4a8c
Init: false
Resouroes:
Networks: default-net
Endpoint Моdе: vip
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
233
Ports:
PuЬlishedPort = 80
Protocol = tcp
TargetPort = 8080
PuЫishМode = ingress
Здесь много информации, давайте отметим самое важное. Это реплицированный
сервис с двумя репликами, как мы видели в выводе команды service 1s. Кроме того,
Docker проверяет работоспособность сервиса каждые 5 с. Обновление будет вы­
полняться методом stop-first (сначала остановить), т. е. сначала экземпляров станет
N - 1, и только потом запустится новый экземпляр, чтобы их снова стало N. Мы
можем всегда запускать N + 1 экземпляров, чтобы во время обновлений не испыты­
вать недостатка в узлах. Для этого нужно указать параметр --update-order=start­
first (сначала запустить) в команде service update. Аналогичное поведение настрое­
но и для отката, и мы также можем изменить его: --rollback-order=start-first.
В реальном мире нужно будет не просто запустить сервис, но и менять его масштаб
в обе стороны. Было бы очень неудобно, каждый раз развертывать его заново, рис­
куя при этом столкнуться с дополнительными проблемами. К счастью, режим
Swarm позволяет масштабировать сервисы одной командой. Чтобы увеличить чис­
ло запущенных экземпляров с двух до четырех, мы можем выполнить следующую
команду:
$ docker -Н 172.17.4. 1 service scale --detach=false quantUПF4
quanturn scaled to 4
overall progress: 4 out of 4 tasks
1/4: running
>]
[
2/4: running
=============>]
[=====
3/4: running
==================>]
4/4: running
================>]
verify: Service converged
В предыдущей команде мы указали --detach=false, чтобы было проще увидеть, что
� происходит.
�
Теперь можно задать service ps, что�ы убедиться, что мы добились желаемых
результатов. Эту команду мы уже выполняли ранее, но теперь у нас должно быть
больше копий. Получается, мы запросили больше копий, чем узлов?
$ docker -Н 172.17.4.1 service ps quanturn
NАМЕ
IМAGE
ID
NODE
DESIRED ... CURRENT ... ERROR PORTS
rk ...13 quanturn.1 spkane/quan ... ip-172-17-4-1 Running
Running ...
lz ...t3 quanturn.2 spkane/quan ... ip-172-17-4-2 Running
Running ...
mh ...g8 quanturn.3 spkane/quan ... ip-172-17-4-3 Running
Running ...
сп ...хЬ quanturn.4 spkane/quan... ip-172-17-4-1 Running
Running ...
Обратите внимание: два сервиса выполняются на одном хаете. Вас это удивило?
Возможно, это снижает отказоустойчивость, но по умолчанию приоритет для
Книги для программистов: https://clcks.ru/books-for-it
234
Глава 10
Swarm - обеспечить запрошенное число экземпляров, а не распределить отдель­
ные контейнеры по хостам. Если узлов недостаточно, на некоторых будет по не­
сколько копий. В реальном мире стоит тщательно обдумывать вопросы размещения
и масштабирования. Что будет, если на одном хосте запущено несколько копий, а
мы теряем целый узел? Будет ли приложение с этими ограниченными ресурсами
все еще обслуживать пользователей?
Развернуть новый выпуск приложения можно с помощью команды docker service
update. У этой команды много параметров, но вот один пример:
$ docker -Н 172.17.4.1 service update --update-delay 10s \
--update-failure-action rollback --update-monitor 5s \
--update-order start-first --update-parallelism 1 \
--detach=false \
--image spkane/quantum-game:latest-plus quantum
quantum
overall progress: 4 out of 4 tasks
1/4: running
===>]
[
2/4: running
>]
3/4: running
>]
4/4: running
=�======>]
verify: Service converged
При выполнении этой команды Swarm обновит сервис, по одному контейнеру за
раз, делая паузы между обновлениями. После этого мы сможем перейти по URL­
aдpecy сервиса в режиме инкогнито (чтобы не мешал локальный кеш браузера)
и убедиться, что теперь фон зеленый, а не голубой.
Итак, мы применили обновление, но как быть, если возникли проблемы? Нам нуж­
на возможность развернуть предыдущий выпуск, чтобы все снова заработало. Мы
можем откатиться до предыдущей версии с голубым фоном с помощью команды
service rollback, которую мы уже упоминали:
$ docker -Н 172.17.4.1 service rollback quantum
quantum
rollback: manually requested rollback
overall progress: rolling back update: 4 out of 4 tasks
1/4: running
[>
2/4: running
[>
3/4: running
[>
4/4: running
[>
verify: Service converged
Это лучший механизм отката для сервиса, не отслеживающего состояние. Нам не
приходится следить за предыдущей версией- Docker делает это за нас. Мы просто
указываем, что хотим откатить версию, и он извлекает предыдущие метаданные из
внутреннего хранилища и выполняет откат. Как и во время развертывания, Docker
проверяет работоспособность контейнеров, чтобы убедиться, что откат работает
правильно.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
�
235
Приложение всегда откатывается до последней развернутой версии, так что если
мы выполним его несколько раз подряд, лишь две версии будут сменять друг
друга.
На основе docker service существует команда docker stack, которая позволяет развер­
тывать специальный файл docker-compose.yaml в режиме Docker Swarm или в кла­
стере Kubemetes. Если мы вернемся к репозиторию Git из главы 8, то сможем раз­
вернуть измененную версию этого стека контейнеров в кластере в режиме Swarm:
$ git clone https://githuЬ.com/spkane/rocketchat-huЬot-demo.git \
--config core.autocrlf=input
В этом репозитории есть каталог stack с измененной версией файла docker­
compose.yaml, который мы использовали раньше:
$ cd rocketchat-huЬot-demo/stack
При желании запустить его в кластере в режиме Swarm можно было бы вьшолнить
следующую команду:
$ docker -Н 172.17.4.1 stack deploy --compose-file docker-compose-stack.yaml \
rocketchat
Creating network rocketchat_default
Creating service rocketchat_huЬot
Creating service rocketchat_mongo
Creating service rocketchat_rocketchat
Creating service rocketchat_zmachine
Теперь можно посмотреть, какие стеки находятся в кластере и какие сервисы были
добавлены:
$ docker -Н 172.17.4.1 stack 1s
SERVICES
ORCHESTRATOR
NАМЕ
rocketchat 4
Swarm
$ docker -Н 172.17.4.1 service 1s
NАМЕ
IМAGE
ID
2/2 spkane/quantum-game:latest
iu... 9f quantum
1/1 rocketchat/huЬot-rocketchat:latest
nh...jd ..._huЬot
1/1 spkane/mongo:4.4
gw...qv ..._mongo
1/1 rocketchat/rocket.chat:5.0.4
mЗ ... vd... rocketchat
lb... 91 ... zmachine
1/1 spkane/zmachine-api:latest
PORTS
*:80->8080/tcp
*:3001->8080/tcp
*:3000->3000/tcp
Этот стек предназначен только для демонстрации. Он не был тщательно протес­
тирован для такого применения, но позволяет получить представление о том, как
создать аналогичный стек.
Возможно, вы заметили, что для запуска всех контейнеров потребуется время,
и Hubot продолжит перезапускаться. Это нормально, поскольку Rocket.Chat еще не
настроен. Подробнее о настройке Rocket.Chat см. в главе 8.
Сейчас мы можем в браузере перейти через порт 3000 к одному из узлов Swarm
(например, http://172.17.4.1:ЗOOO/ в этом примере) и увидим страницу начальной
настройки Rocket.Chat.
Книги для программистов: https://clcks.ru/books-for-it
236
Глава 10
С помощью команды docker stack ps можно посмотреть все контейнеры, управляе­
мые стеком:
$ docker -Н 172.17.4.1 stack ps -f "desired-state=running" rocketchat
NАМЕ
ID
IМAGE
...CURRENT SТАТЕ...
NODE
Running 14 seconds ago
huЬot.1
rocketchat/huЬot-rocket... -1
bS...lh...
Running 11 minutes ago
spkane/mongo:4.4...
eq...88... _mongo.1
-2
Running 11 minutes ago
rocketchat.1 rocketchat/rocket.chat:... -3
Sx...8u...
Running 12 minutes ago
zmachine.1
spkane/zmachine-api:lat... -4
rS...х4...
Затем мы можем снести стек:
$ docker -Н 172.17.4.1 stack rm rocketchat
Removing service rocketchat huЬot
Removing service rocketchat_mongo
Removing service rocketchat rocketchat
Removing service rocketchat_zmachine
Removing network rocketchat_default
Если мы попробуем сразу запустить все заново, могут возникнуть непредвиденные
ошибки. Нужно подождать некоторое время, пока кластер не закончит удалять ста­
рую сеть для стека и т. д.
Что будет, если на одном из серверов возникла ошибка и нужно его отключить?
В этом случае можно вывести все сервисы с одного узла с помощью параметра
--availaЬility в команде docker node update.
Давайте еще раз посмотрим, какие узлы остались в кластере:
docker -Н 172.17.4.1 node 1s
HOSTNAМE
STATUS
ID
Ready
19...82 * ip-172-17-4-1
ip-172-17-4-2
Ready
3d...7Ь
ip-172-17-4-3
Ready
ip... qe
AVAILAВILITY
Active
Active
Active
МANAGER
Leader
STATUS
ENGINE VERSION
20.10.7
20.10.7
20.10.7
Проверим, где сейчас выполняются контейнеры:
$ docker -Н 172.17.4.1 service ps -f "desired-state=running" quantum
ID
NАМЕ
IМAGE
NODE
DESIRED... CURRENT...
spkane/qua... ip-172-17-4-1
sc... lh quantum.1
Running...
Running
Running
spkane/qua... ip-172-17-4-2
ах.. . от quantum.2
Running...
р4...8h quantum.3
Running...
spkane/qua... ip-172-17-4-3
Running
g8...tw quantum. 4
spkane/qua... ip-172-17-4-1
Running
Running...
ERROR PORTS
В предыдущей команде мы использовали фильтр, чтобы в выводе отображались
только текущие процессы. По умолчанию Docker также показывает предыдущие
контейнеры в виде дерева, чтобы мы видели данные об обновлениях и откатах.
Если мы решим, что сервер по адресу 172.17.4. з нужно остановить, мы можем пере­
вести задачи с этого узла на другой хост, изменив состояние availaЬility на drain
в Swann:
Книги для программистов: https://clcks.ru/books-for-it
237
Масштабирование контейнеров
$ docker -Н 172.17.4.1 node update --availaЬility drain ip-172-17-4-3
ip-172-17-4-3
Если мы сейчас проверим узел, то увидим, что для параметра AvailaЬility (доступ­
ность) установлено ctrain:
$ docker -Н 172.17.4.1 node inspect --pretty ip-172-17-4-3
m:
ipohyw73hvf70td9licnls9qe
Hostname:
ip-172-17-4-3
Joined at:
2022-09-04 16:59:52.922451346 +0000 utc
Status:
Ready
State:
AvailaЬility:
Drain
Address:
172.17.4.3
Platform:
Operating System: linux
х86 64
Architecture:
Resources:
CPUs:
Мemory:
Plugins:
2
7.795GiB
Log:
awslogs, fluentd, gcplogs, gelf, journald, json-file, local,
logentries, splunk, syslog
bridge, host, ipvlan, macvlan, null, overlay
Network:
Volume:
local
20.10.7
Engine Version:
ТLS Info:
TrustRoot:
Issuer SuЬject:
Issuer PuЬlic Кеу: ...
Как это отразится на сервисе? Мы указали одному узлу прекратить выполнять
копии сервиса, и они должны исчезнуть с него или перенестись на другие узлы.
Чего мы добились? Давайте снова проверим сервис и убедимся, что все контейнеры
с этого хоста перенесены на другой:
$ docker -Н 172.17.4.1 service ps -f "desired-state=running" quantum
ID
NАМЕ
IМAGE
NODE
DESIRED... CURRENT...
sc... lhquantum.1 spkane/qua... ip-172-17-4-1
Running
Running...
ах...omquantum.2 spkane/qua... ip-172-17-4-2
Running
Runnirig.. .
р4...8hquantum.3 spkane/qua... ip-172-17-4-2
Running...
Running
g8...twquantum.4 spkane/qua... ip-172-17-4-1
Running
Running...
ERROR
PORTS
Сейчас мы можем спокойно остановить узел и сделать все необходимое, чтобы
вернуть его в работоспособное состояние. Когда мы будем готовы добавить узел
обратно в кластер Swarm, мы выполним следующую команду:
$ docker -Н 172.17.4.1 node update --availaЬility active ip-172-17-4-3
ip-172-17-4-3
Книги для программистов: https://clcks.ru/books-for-it
238
Глава 10
· Мы не будем проверять узел снова, но вы всегда можете выполнить команду node
inspect, чтобы посмотреть, что там происходит.
При возвращении узла в кластер контейнеры не будут автоматически переносить­
ся обратно, но при новом развертывании или обновлении контейнеры равномерно
распределятся по узлам.
Когда мы закончим, можно будет удалить сервис и сеть следующими командами:
$ docker -Н 172.17.4.1 service rm quantшn quantшn
$ docker -Н 172.17.4.1 network rm default-net
default-net
Убедимся, что ни сервиса, ни сети больше нет:
$ docker -Н 172.17.4.1 service ps quantшn
no such service: quantшn
$ docker -Н 172.17.4.1 network 1s
NETWORК ID
494elalbf8f3
2e7d2d7aaf0f
df0376841891
n8kjdбoa44fr
Ь4720еа13Зdб
NАМЕ
bridge
docker_gwbridge
host
ingress
none
DRIVER
bridge
bridge
host
overlay
null
SCOPE
local
local
local
swarm
local
Пока это все. Сейчас мы можем остановить все серверы в кластере Swarm, если они
нам больше не нужны.
Мы проделали большую работу, и вы получили представление о том, как использо­
вать режим Swarm в Docker Engine. Теперь вы можете приступить к созданию кла­
стеров Docker для своих нужд.
Kubernetes
В этом разделе мы посмотрим, как работает Kubernetes. Впервые Kubernetes был
представлен публике на конференции DockerCon (https://events.docker.com/events/
dockercon) в 2014 году. С тех пор эта платформа для контейнеров активно развива­
ется и, пожалуй, является самой популярной. Это не самый старый и не самый зре­
лый проект - Mesos был запущен в 2009, еще до повсеместного распространения
контейнеров, - но Kubernetes был разработан специально для контейнерных рабо­
чих нагрузок, предлагает целый ряд возможностей, которых становится все больше,
и поддерживается большим сообществом, куда входят давние пользователи Docker
и контейнеров Linux. Благодаря этим преимуществам популярность Kubernetes
с каждым годом растет. На DockerCon EU в 2017 году компания Docker, Inc. объ­
явила, что поддержка Kubernetes будет встроена в сам Docker Engine. Docker Desktop
может запустить кластер Kubernetes с одним узлом, и клиент может развертывать
стеки контейнеров в целях разработки. Это удобный переход для разработчиков,
которые используют Docker локально и развертывают контейнеры в Kubernetes.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
1
239
Как и сам Linux, Kubernetes поставляется в разных платных и бесплатных дистри­
бутивах. Нам доступно много вариантов с различной степенью поддержки возмож­
ностей. Kubernetes популярен, так что в нашем распоряжении есть удобные инст­
рументы для локальной разработки.
В этой книге мы рассматриваем основы работы с Kubernetes и его интеграцию
с контейнерами Linux, но не будем углубляться в экосистему Kubernetes. Настоя­
тельно рекомендуем прочитать книгу Kubernetes: Up & Running Брендана Бернса
(O'Reilly) (https://www.oreilly.com/library/view/kubernetes-up-and/9781098110192)
или что-то подобное, чтобы больше узнать о главных принципах и терминах.
Инструмент Minikube
Minikube - один из базовых инструментов для управления локальной установкой
Kubernetes, и мы начнем с него, потому что большинство концепций, с которыми
вы познакомитесь при работе с Minikube, можно применить к реализации
Kubernetes, включая варианты, которые мы рассмотрим после Minikube.
Нам доступно много других вариантов для запуска локального кластера Kuber­
netes. Мы начинаем с minikuЬe, потому что контейнер или виртуальная машина,
которые он запускает, представляют собой стандартную установку Kubernetes
с одним узлом. Кроме инструментов, которые мы обсудим в этом разделе, рекомен­
дуем изучить kЗs (https://kЗs.io/), kЗd (https://kЗd.io/), k0s (https://k0sprojectio/) и
microk8s (https://microk8s.io/).
Общие сведения о Minikube
Minikube - это полноценный дистрибутив Kubernetes для одного экземпляра. Он
управляет контейнером или виртуальной машиной на компьютере, причем мы ис­
пользуем такие же инструменты Kubernetes, как и в рабочей среде. В чем-то это
похоже на Docker Compose - мы получаем полноценный стек в локальной среде,
но у Minikube, в отличие от Compose, есть все API для рабочей среды. Если мы
планируем применять Kubernetes в рабочей среде, мы можем создать на своем ком­
пьютере среду, которая пусть не по масштабу, но по функциям будет очень близка
к рабочей.
Уникальность Minikube заключается в том, что целым дистрибутивом можно
управлять из одного двоичного файла, который мы загружаем и запускаем локаль­
но. Он автоматически определяет, какой менеджер контейнеров или виртуальных
машин установлен на локальном компьютере, и запускает контейнер или виртуаль­
ную машину со всеми необходимыми сервисами Kubernetes, а значит, начать рабо­
ту очень просто.
Давайте установим Minikube.
Установка Minikube
Большая часть установки будет одинаковой для всех платформ, потому что после
установки инструментов именно через них мы будем взаимодействовать с вирту­
альной машиной, где запущен Kubernetes. Инструкции по установке ищите в раздеКниги для программистов: https://clcks.ru/books-for-it
240
Глава 10
ле для вашей операционной системы. Когда установите и запустите инструмент,
следуйте общей документации.
Для Minikube нам потребуются два инструмента: minikuЬe и kuЬectl. Обе утилиты
представляют собой статические двоичные файлы без внешних зависимостей, по­
этому их будет легко установить в нашем простом примере.
Существуют и другие способы установки Minikube. Здесь приводится самый про­
стой (по нашему мнению) метод для каждой платформы. Если у вас свои предпоч­
тения, смело используйте другой подход. В Windows, например, можно задейство­
вать менеджер пакетов Chocolatey (https://chocolatey.org/), а в Linux - систему упа­
ковки Sпар (https://snapcraft.io/).
macOS
Как мы уже упоминали, в системе должен быть установлен Homebrew. Если его
еще нет, следуйте инструкциям из главы 3. После установки Homebrew установить
клиент minikuЬe будет очень просто:
$ brew install minikuЬe
Homebrew загрузит и установит Minikube. В зависимости от конфигурации вы уви­
дите что-то подобное:
=> Downloading https://ghcr.io/v2/homebrew/core/kuЬernetes-cli/.../l.25.0
Already downloaded: ... /Homebrew/downloads/...kuЬernetes-cli...manifest.json
=> Downloading https://ghcr.io/v2/homebrew/core/kuЬernetes-cli/Ьlobs/sha256...
Already downloaded: .../Homebrew/downloads/...kuЬernetes-cli--1.25...bottle.tar.gz
=> Downloadinghttps://ghcr.io/v2/homebrew/core/minikuЬe/manifests/l.26.1
Already downloaded: .../Homebrew/downloads/...minikuЬe-1.26.1...._manifest.json
=> Downloading https://ghcr.io/v2/homebrew/core/minikuЬe/Ыobs/sha256:...
Already downloaded: .../Homebrew/downloads/ ...minikuЬe--1.26.1... bottle.tar.gz
==> Installing dependencies for minikuЬe: kuЬernetes-cli
==> Installing minikuЬe dependency: kuЬernetes-cli
=> Pouring kuЬernetes-cli--l.25.0.arm64_monterey.bottle.tar.gz
DJ /opt/homebrew/Cellar/kuЬernetes-cli/1.25.0: 228 files, 52.ВМВ
=> Installing minikuЬe
==> Pouring minikuЬe--l.26.1.arm64_monterey.bottle.tar.gz
=> Caveats
Bash completion has been installed to:
/opt/homebrew/etc/bash_completion.d
=> Surmiary
/opt/homebrew/Cellar/minikuЬe/1.26.1: 9 files, 70.бМВ
==> Running 'brew cleanup minikuЬe' ...
DisaЬle this behavior Ьу setting HOМEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOМEBREW NO ENV HINTS (see 'man brew').
==> Caveats
=> minikuЬe
Bash completion has been installed to:
/opt/homebrew/etc/bash_completion.d
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
241
Вот и все. Давайте убедимся, что все установлено:
$ which minikuЬe
/opt/homebrew/bin/minikuЬe
lJ.N
�
Homebrew в системах arm64 устанавливает Minikube в папке /opt/homebrew/Ьin, а
не /usr/local/bin.
Если вы не получили ответа, убедитесь, что в переменной среды РАТН есть
/usr/local/Ьin и /opt/homebrew/Ьin. Если все в порядке, minikuЬe будет установлен.
Инструмент
kuЬectl
установится автоматически, потому что это зависимость
minikuЬe, но мы можем установить его явно с помощью brew. Как правило, версия
kuЬectl в Homebrew совпадает с текущим выпуском minikuЬe, так что выполним ко­
манду brew install, чтобы избежать несоответствий:
$ brew install kuЬernetes-cli
Проверим установку так же, как для minikuЬe:
$ which kuЬectl
/opt/homebrew/Ьin/kuЬectl
Все получилось.
Windows
Как и при установке Docker Desktop в Windows, установите Hyper-V или другую
платформу виртуализации для запуска виртуальной машины Kubernetes. Для уста­
новки minikuЬe просто загрузите двоичный файл и поместите его в папку, которая
указана в переменной РАТН, чтобы его можно было выполнять в командной строке.
Загрузите последний выпуск minikuЬe на GitHub (https://github.com/kubernetes/
minikube/releases/latest). Переименуйте загруженный исполняемый файл Windows
в minikube.exe, иначе придется вводить слишком много букв.
Больше о процессе установки Windows и исполняемом двоичном файле см. в до­
� кументации по установке Minikube (https://minikuЬe.sigs.k8s.io/docs/start).
._
Теперь нужно загрузить последний выпуск Kubernetes CLI, kuЬectl, для выполнения
команд. К сожалению, это нельзя сделать с помощью пути /latest, так что полу­
чите послед}Щ)ю версию (11ttps://storage.googleapis.com/kubernetes-release/release/
staЫe.txt) с сайта, а затем вставьте в URL: https://storage.googleapis.com/
kubernetes-release/release/<BEPCИЯ>/bin/windows/amd64/kubectl.exe.
После загрузки убедитесь, что инструмент доступен в переменной РАТН.
Linux
В Linux вам понадобится установить Docker, а также КУМ (Linux Kernel-based
Virtual Machine) или VirtualВox, чтобы minikuЬe мог создать виртуальную машину
Книги для программистов: https://clcks.ru/books-for-it
242
Глава 10
Kubernetes и управлять ею. Поскольку minikuЬe умещается в одном двоичном файле,
после его установки нам не нужно устанавливать дополнительные пакеты. Благо­
даря статической компоновке minikuЬe будет работать на любом дистрибутиве. Мы
могли бы уложить всю установку в одну строку, но разделим процесс на несколько
этапов, чтобы его было проще понять и исправить, если что-то пойдет не так. На
момент написания двоичный файл размещается на googleapis, где обычно бывают
очень стабильные URL. Итак:
# Загружаем файл и сохраняем как 'minikuЬe'
$ curl -Lo minikuЬe \
https://storage.googleapis.com/minikuЬe/releases/latest/minikuЬe-linux-amd64
# Делаем его исполняемым
$ chmod +х minikuЬe
# Переносим файл в /usr/local/Ыn
$ sudo mv miпikuЬe /usr/local/Ыn/
Убедитесь, что у вас есть /usr/loca\/Ьin. Теперь у нас есть minikuЬe, и нам нужно
получить еще kuЬectl:
# Получаем последнюю версию
$ KUBE_VERSION=$(curl -s \
https://storage.googleapis.com/kuЬernetes-release/release/staЫe.txt)
# Получаем исполняемый файл
$ curl -10 \
https://storage.googleapis.com/kuЬernetes-release/\
release/$(KUBE_VERSION)/Ьin/liпux/amd64/kuЬectl
# Делаем его исполняемым
$ chmod +х kuЬectl
# Переносим файл в /usr/local/Ыn
$ sudo mv kuЬectl /usr/local/Ып/
Один из URL в этом примере продолжается на следующей строке, чтобы вписаться
в ширину. Возможно, потребуется пересобрать его и убрать обратные косые чер­
ты, чтобы команда работала в вашей среде.
Итак, мы все установили, давайте приступать к работе.
Запуск Kubernetes
Мы можем использовать установленный minikuЬe, чтобы запустить кластер
Kubernetes. Обычно это довольно просто, никакой предварительной конфигурации
не требуется. В этом примере мы увидим, что miпikuЬe использует драйвер docker,
хотя можно было выбрать и другие.
Запустить minikuЬe можно следующей командой:
$ minikuЬe start
� minikuЬe vl.26.1 оп Darwiп 12.5.1 (arm64)
• Automatically selected the docker driver. Other choices: parallels, ssh, ...
:1' Usiпg Docker Desktop driver with root privileges
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
243
.J# Starting control plane node minikuЬe in cluster minikuЬe
� Pulling base image ...
F1 Downloading KuЬernetes vl.24.3 preload ...
> preloaded-images-k8s-v18-vl...: 342.82 MiB / 342.82 MiB 100.00% 28.22 М
> gcr.io/k8s-minikuЬe/kicbase: 348.00 MiB / 348.00 MiB 100.00% 18.13 MiB
> gcr.io/kBs-minikuЬe/kicbase: О В [ __________] ?% ? p/s 16s
11\ Creating docker container (CPUs=2, Memory=4000MB) ...
81 Preparing KuЬernetes vl.24.3 on Docker 20.10.17 ...
• Generating certificates and keys
• Booting up control plane ...
• Configuring RВАС rules ...
Р Verifying KuЬernetes components...
• Using image gcr.io/k8s-minikuЬe/storage-provisioner:v5
.... EnaЫed addons: storage-provisioner, default-storageclass
;$, Done! kuЬectl is now configured to use "minikuЬe" cluster and
"default" namespace Ьу default
Что мы сейчас сделали? Одна команда Minikube выполняет много действий. В этом
примере мы запустили один контейнер Linux, который предоставляет работающую
установку Kubernetes в локальной системе. Если использовать с minikuЬe один из
драйверов виртуализации, то можно создать полноценную виртуальную машину
с Kubernetes, а не один контейнер.
Затем в контейнерах Linux на хосте запускаются все необходимые компоненты
Kubemetes. Мы можем изучить контейнер или виртуальную машину minikuЬe:
$ minikuЬe ssh
docker@minikuЬe:~$
В кластере Kubemetes, скорее всего, вы нечасто будете использовать SSH с ко­
мандной строкой. Мы хотим посмотреть, что установили, и, к счастью, при нали­
чии minikuЬe в среде можно запускать сразу много процессов. Давайте посмотрим,
что выполняется на экземпляре Docker в кластере Kubernetes:
docker@minikuЬe:~$ docker container 1s
... ID
48...cf
4е...Bd
ld...3d
82...d3
27...10
15...се
ff...3d
33... с5
30...97
f5...41
5Ь...08
87...се
5а...14
бf... Ос
IМAGE
Ьа...57
ed...е8
pause:3.6
7а...dc
pause:3.6
pause:3.6
f9...55
pause:3.6
а9 ...df
53...аб
Bf...73
pause:3.6
pause:3.6
pause:3.6
СОММАND
... NAМES
"/storage-provisioner" ...k8s_storage-provisioner_storage-...
"/coredns -conf /etc..."
k8s POD coredns-6d4b75cbбd-...
"/pause"
k8s POD coredns-6d4b75cbбd-.. .
"/usr/local/Ьin/kuЬe..."
k8s_kuЬe-proxy_kuЬe-proxy-...
"/pause"
k8s_POD_kuЬe-proxy-zbбw2_kuЬe-...
"/pause"
...kBs_POD_storage-provisioner_kuЬe-...
"kuЬe-controller-man... "
kBs_kuЬe-controller-manager_kuЬe-...
"/pause"
k8s_POD_kuЬe-controller-manager-...
"etcd --advertise-cl..."
k8s-etcd-etcd-minikuЬe-kuЬe-...
"kuЬe-apiserver --ad..."
k8s-etcd-etcd-minikuЬe-kuЬe-...
"kuЬe-scheduler --аи . . . "
k8s-kuЬe-scheduler-kuЬe-scheduler-...
"/pause"
k8s_POD_kuЬe-apiserver-...
"/pause"
k8s-POD-etcd-minikuЬe-kuЬe-...
"/pause"
k8s POD kuЬe-scheduler-...
Книги для программистов: https://clcks.ru/books-for-it
244
Глава 10
Мы не будем подробно рассматривать каждый компонент, просто посмотрим, как
работает механизм. Компоненты легко обновлять, ведь это просто контейнеры, их
версии отслеживаются, и мы можем извлекать их из базового репозитория контей­
неров.
Давайте выйдем из командной оболочки в системе Minikube:
docker@minikuЬe:~$ exit
Команды minikube
Для экономии места и времени мы не будем рассматривать все команды minikuЬe.
Попробуйте выполнить их сами без параметров, изучите вывод и поэксперименти­
руйте. Мы остановимся на самых интересных командах, а по ходу установки стека
приложений изучим еще несколько.
Ранее мы вызывали команду minikuЬe ssh, чтобы посмотреть, что происходит в сис­
теме. Это удобный способ отлаживать или изучать систему напрямую. Не обраща­
ясь прямо к системе Minikube, мы можем проверить состояние кластера еще одной
командой minikuЬe:
$ minikuЬe status
minikuЬe
type: Control Plane host: Running kuЬelet: Running apiserver: Running
kuЬeconfig: Configured
Мы видим, что все в порядке. Еще две полезные команды:
Команда
Действие
minikuЬe ip
minikuЬe update-check
Получает IР-адрес виртуальной машины Minikube
Проверяет версию Minikube относительно последнего выпуска
Чтобы обновить версию, используйте тот же механизм, с помощью которого изна­
чально установили инструмент.
Команда minikuЬe status также показывает, что kuЬeconfig правильно настроен. Это
необходимо, чтобы kuЬectl знал, как подключиться к кластеру.
Мы запустили кластер Kubemetes командой minikuЬe start. Будет логично предпо­
ложить, что minikuЬe stop остановит все компоненты Kubemetes и виртуальную
машину или контейнер Linux. Чтобы очистить среду, можно удалить кластер
командой minikuЬe ctelete.
Kubernetes Dashboard
После запуска Minikube мы можем работать не только с инструментами командной
строки, но и с графическим интерфейсом Kubemetes Dashboard. Для этого у нас
есть команда minikuЬe ctashЬoarct, которая открывает в браузере Kubemetes Dashboard
с правильным IР-адресом и портом. На панели много компонентов, и в этой книге
мы не сможем рассмотреть их все, но вы можете изучить их самостоятельно. Если
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
245
у вас уже есть опыт работы с Kubemetes, некоторые термины в боковом меню
панели будут вам знакомы, но во многих придется разбираться. Если под рукой нет
компьютера, на рис. 10.2 приводится скриншот - как выглядит пустая установка
Minikube, если нажать кнопку Service в боковом меню.
Рис. 10.2. Kubernetes Dashboard (nример)
Если нажать ссылку Nodes (Узлы) в разделе Cluster (Кластер) в левом меню, мы
увидим один узел в кластере с именем minikube. Это запущенный нами контейнер
или виртуальная машина, и панель, как и остальные компоненты, размещена в од­
ном из контейнеров, которые мы видели, когда подключались к системе Minikube
раньше. Давайте что-нибудь развернем в кластере, а потом снова посмотрим на
панель.
Почти все, что мы видим на панели, можно посмотреть с помощью команды
kuЬectl, так что мы можем легко писать разные shеll-скрипты.
Например, если мы выполним kuЬectl get services или kuЬectl get nodes, то уви­
дим ту же информацию, что и на панели.
Изучая панель, вы заметите, что сам Kubemetes отображается как компонент сис­
темы - так же будет отображаться и приложение.
�lЖl
Нажмите комбинацию клавиш <Ctrl>+<C>, чтобы выйти из процесса minikuЬe
dashЬoard и вернуться в терминал.
Контейнеры и поды Kubernetes
Итак, мы легко запустили кластер Kubemetes в локальной среде. Давайте вернемся
к теории и узнаем, что Kubemetes добавляет поверх абстракции контейнеров.
Книги для программистов: https://clcks.ru/books-for-it
246
1
Глава 10
Kubemetes появился как результат работы Google с очень большой платформой.
У Google возникали проблемы, типичные для рабочей среды, и компания искала
способы упростить их понимание и решение. В процессе разработки инженеры
Google создали сложную систему новых абстракций, и для Kubemetes выработался
свой набор терминов. Мы не будем рассматривать их все, но остановимся на глав­
ной из новых абстракций, которая находится на уровень ниже контейнера и назы­
вается pod (под).
l,ж
�
Символ Docker - кит Моби, а слово pod в английском языке обозначает стаю ки­
тов.
В терминологии Kubemetes под - это один или несколько контейнеров, у которых
общие cgroups и пространства имен. С помощью cgroups и пространств имен мы так­
же можем изолировать контейнеры друг от друта в пределах одного пода. Под ин­
капсулирует все процессы или приложения, которые нужно развернуть вместе как
функциональную единицу, которой будет управлять планировщик. Все контейнеры
в поде могут взаимодействовать друт с другом на localhost, так что налаживать об­
наружение не требуется. Так почему бы просто не развернуть один большой кон­
тейнер сразу со всеми приложениями? Потому что в поде, в отличие от большого
контейнера, мы можем ограничивать ресурсы для отдельных приложений и состав­
лять приложение из обширного ряда публичных контейнеров Linux.
Кроме того, администраторы Kubemetes часто используют поды, например, чтобы
контейнер запускался при запуске пода с правильной конфигурацией для всех, что­
бы задействовать общий ресурс или чтобы объявлять приложение для других сер­
висов. Поды дают более детализированный контроль, поскольку нам не приходится
группировать все в один контейнер. Еще одно преимущество пода - возможность
монтировать общие тома.
Время жизни подов схоже с контейнерами Linux. Они эфемерны, и их можно по­
вторно развертывать на новых хостах на протяжении жизненного цикла приложе­
ния или хоста. У контейнеров в поде один IР-адрес для общения с внешним миром,
так что на уровне сети они выглядят как единый объект. В одном контейнере мы
запускаем только один экземпляр приложения, а в одном поде - один экземпляр
контейнера. Считайте, что под - это группа контейнеров Linux, которые во многих
смыслах работают как один контейнер. Если нам нужен только один контейнер,
Kubernetes все равно развертывает под, но внутри будет один контейнер. К сча­
стью, планировщик Kubemetes имеет дело только с одной абстракцией - с подом.
Контейнеры управляются компонентами среды выполнения, которые составляют
под, а также выбранной нами конфигурацией.
Важное различие между подом и контейнером - мы не создаем поды на этапе
сборки. Поды - абстракция среды выполнения, которая определяется в манифесте
JSON или УАМL и существует только в Kubernetes. Мы собираем контейнеры
Linux и отправляем их в реестр, а затем определяем и развертываем поды с помо­
щью Kubemetes. В реальности мы не описываем под напрямую - за нас его создаКниги для программистов: https://clcks.ru/books-for-it
247
Масштабирование контейнеров
ют инструменты в ходе развертывания. В кластере Kubemetes под - это единица
выполнения и планирования. Все гораздо сложнее, но это базовая концепция. Про­
ще всего понять ее на примере. Система, состоящая из подов, выглядит сложнее,
чем система из контейнеров, но с ними работать гораздо эффективнее.
Практический пример
При работе с подами в Kubernetes мы обычно управляем ими через такую абстрак­
цию, как развертывание (deployment). Развертывание - это определение пода
с дополнительной информацией, включая конфигурацию репликации и монитор.дн­
га работоспособности. В двух словах, это определение пода и немного метаданных
о нем. Давайте рассмотрим базовое развертывание.
Самое простое, что можно развернуть в Kubernetes, - под с одним контейнером.
Давайте на примере приложения httpbin (https://httpbln.org/) рассмотрим основы
развертывания в Kubernetes. Назовем наше развертывание hello-minikuЬe.
До этого мы пользовались командой minikuЬe, но для выполнения операций в самом
Kubernetes нам понадобится команда kuЬectl, которую мы установили чуть раньше:
$ kuЬectl create deployment hello-minikuЬe \
--image=kennethreitz/httpbin:latest --port= B0
deployment.apps/hello-minikuЬe created
Чтобы посмотреть, что произошло, выполним команду kuЬectl get all и получим
самые важные объекты в кластере:
$ kuЬectl get all
NАМЕ
READY
AGE
STAТUS
RESTARTS
2m39s
1/1
Running
pod/hello-minikuЬe-ff49df9b8-svl68
о
NАМЕ
service/kuЬernetes
ТУРЕ
ClusterIP
NАМЕ
deployment.apps/hello-minikuЬe
CLUSTER-IP
10.96.0.1
READY
1/1
NАМЕ
replicaset.apps/hello-minikuЬe-ff49df9b8
EXTERNAL-IP
<none>
PORT (S)
443/ТСР
UP-TO-DATE
1
AVAILABLE
1
AGE
2m39s
DESIRED
1
CURRENT
1
READY
1
AGE
98m
AGE
2m39s
При выполнении этой команды Kubernetes создал развертывание, ReplicaSet для
масштабирования и под. Давайте убедимся, что у пода для параметра sтюus (ста­
тус) установлено значение Running (выполняется). Если у вашего пода другой статус,
выполните команду еще пару раз, пока статус не изменится. Сервис service/
kuЬernetes представляет сам Kubernetes. А где наш сервис? Пока мы его не видим.
Он находится в том же состоянии, в каком пребывал бы контейнер Linux, если бы
мы не указали ему открыть порт. Давайте укажем Kubemetes открыть сервис:
$ kuЬectl expose deployment hello-minikuЬe --type=NodePort
service/hello-minikuЬe exposed
Книги для программистов: https://clcks.ru/books-for-it
Глава 10
248
Теперь у нас есть сервис, с которым можно взаимодействовать. Сервис является
оберткой над одним или несколькими развернутыми приложениями и указывает,
как обращаться к приложению. В этом случае у нас есть NodePort, который предос­
тавляет на каждом узле в кластере порт, через который можно связаться с подами.
Давайте узнаем у Kubernetes, как связаться с подом:
$ kuЬectl get services
AGE
EXTERNAL-IP PORT (S)
ТУРЕ
CLUSTER-IP
NАМЕ
80:32557/ТСР
8s
NodePort
hello-minikuЬe
10.105.184.177 <none>
107m
<none>
10.96.0.1
443/ТСР
ClusterIP
kuЬernetes
Можно подумать, что теперь к сервису можно подключиться по адресу
http://l 0.105.184.177:8080, но эти адреса недоступны с нашей хает-системы, потому
что Minikube работает в контейнере или виртуальной машине. Нам понадобится
команда minikuЬe, чтобы узнать, где найти сервис:
$ minikuЬe service hello-minikuЬe --url http:l/192.168.99.100:30616
В некоторых конфигурациях может отобразиться сообщение:
1 Because you are using а Docker driver on darwin, the terminal needs to Ье open
to run it.
(Вы используете драйвер Docker на darwin, поэтому для запуска нужно открыть
терминал.) Это значит, что пока мы не можем установить соединение между хос­
том и сервисами Kubernetes, и при работе с приложением команда должна про­
должать выполняться. Мы можем использовать локальный браузер или открыть
еще один терминал, чтобы запустить команду, вроде curl.
Когда закончите, нажмите <Ctrl>+<C> в исходном сеансе, чтобы завершить коман­
ду minikuЬe service.
Эту команду, как и многие другие команды Kubernetes, можно вставлять в скрипты,
и ее удобно вводить в командной строке. Если мы хотим открывать ее с помощью
curl, можно просто включить вызов minikuЬe в запросе в командной строке:
$ curl -Н foo:bar $(minikuЬe service hello-minikuЬe --url)/get
11
args 11 :
{},
"headers": {
"Accept": 11 */* 11,
"Foo": 11bar",
"Host": "127.О.О .1:56695",
"User-Agent": "curl/7.85.О"
},
"origin": "172.17.0.1",
"url": "http:l/127.0.0.1:56695/get"
Инструмент httpЬin - это простой API НТТР-запросов и ответов, с помощью кото­
рого можно тестировать и проверять НТТР-сервисы. Не самое интересное прило­
жение, но мы видим, что можем связаться с сервисом и получить от него ответ
через curl.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
1
249
Это простейший вариант применения. Мы почти ничего не настраивали, Kubemetes
сделал все необходимое с настройками по умолчанию. Далее мы рассмотрим более
сложный пример, но сначала давайте завершим работу сервиса и развертывания.
Для этого выполним две команды: одна удалит сервис, а другая удалит разверты­
вание:
$ kuЬectl delete service hello-minikuЬe
service "hello-minikuЬe" deleted
$ kuЬectl delete deployrnent hello-minikuЬe
deployrnent.apps "hello-rninikuЬe" deleted
$ kuЬectl get all
NАМЕ
service/kuЬernetes
ТУРЕ
ClusterIP
CLUSTER-IP
10.96.0.1
EXTERNAL-IP
<none>
PORT(S)
AGE
443/ТСР
138m
Развертывание реалистичного стека
Давайте развернем что-нибудь более похожее на настоящий стек. Это будет при­
ложение, которое извлекает документы PDF из бакета SЗ, кеширует их на локаль­
ном диске и по запросу преобразует отдельные страницы в изображения PNG, ис­
пользуя кешированный документ. Файлы кеша мы будем записывать за пределами
контейнера, в более постоянном и стабильном хранилище. На этот раз мы хотим
создать воспроизводимое развертывание, поэтому не будем развертывать приложе­
ние с помощью серии команд CLI, которые приходится сначала запоминать, а по­
том аккуратно вводить, стараясь избежать ошибок. Kubernetes, как и Docker
Compose, позволяет определить стек в одном или нескольких файлах УАМL, кото­
рые содержат все нужные определения. Другие инструменты в рабочей среде реа­
лизуют очень похожий подход.
Сейчас мы создадим сервис lazyraster, который будет по запросу преобразовывать
изображение в растровый формат. Под этим именем приложение будет обозначено
в определении УАМL. Постоянный том (persistent volume) будет называться cache­
data. У Kubernetes своя терминология, и мы не сможем рассмотреть все понятия,
но сейчас нужно разъяснить еще два: PersistentVolume и PersistentVolumeClaim.
PersistentVolume - это физический ресурс, который мы выделяем в кластере.
Kubemetes поддерживает разные типы томов, от локального хранилища на узле до
томов Amazon Elastic Block Store (Amazon EBS) (https://docs.aws.amazon.com/
АWSEC2/latest/UserGuide/AmazonEBS.html) в А WS и аналогичных решений
в других облаках. Он также поддерживает Network File System (NFS) (https://
en.wikipedia.org/wiki/Network_File_System) и другие, более современные сетевые
файловые системы. Жизненный цикл данных в PersistentVolume не зависит от при­
ложения или развертываний - данные сохраняются между развертываниями. Мы
используем его для кеша. PersistentVolumeClaim - это связь между физическим ре­
сурсом PersistentVolume и приложением, которому он требуется. Мы можем создать
политику, которая разрешит один запрос на чтение-запись или много запросов на
чтение. Для этого приложения мы выбираем один запрос на чтение и запись в cache­
data Persistent Volume.
Книги для программистов: https://clcks.ru/books-for-it
250
Глава 10
Если хотите узнать больше об этих концепциях, изучите официальный глоссарий
(https://kubernetes.io/docs/reference/glossary/?fundamental=true) с терминами
Kubernetes. Это может очень пригодиться. В статьях глоссария есть ссылки на
страницы с более подробной информацией.
Получим файл, который мы будем использовать в этом разделе:
$ git clone \
https://githuЬ.com/Ыuewhalebook/\
docker-up-and-running-Зrd-edition.git --config core.autocrlf=input
Cloning into 'docker-up-and-running-Зrd-edition' ...
$ cd docker-up-and-running-Зrd-edition/chapter_l0/kuЬernetes
URL в этом примере продолжается на следующей строке, чтобы вписаться в ши­
рину. Возможно, потребуется пересобрать его и убрать обратные косые черты,
чтобы команда работала.
Для начала изучим манифест в файле lazyraster-service.yaml. Полный манифест со­
стоит из нескольких файлов УАМL, разделенных символами ---. Мы рассмотрим
каждый раздел.
Определение сервиса
apiVersion: vl
kind: Service
metadata:
name: lazyraster
laЬels:
арр: lazyraster
spec:
type: NodePort
ports:
- port: 8000
targetFort: 8000
protocol: ТСР
selector:
арр: lazyraster
В первом разделе определяется объект service (сервис). Во втором и третьем разде­
лах определяются объекты PersistentVolumeClaim (запрос на постоянный том) и
Deployment (фактическое развертывание). Мы сообщили Kubernetes, что сервис будет
вызывать lazyraster, и он будет предоставляться через порт 8000, который соответ­
ствует фактическому порту 8000 в нашем контейнере. Мы предоставили его через
механизм NodePort, с помощью которого приложение предоставляется через один и
тот же номер порта на каждом хосте. Это похоже на флаг --puЬlish в команде docker
container run. В случае применения minikuЬe это удобно, потому что мы запускаем
только один экземпляр, а NodePort упрощает доступ к нему с компьютера. Как это
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
251
часто бывает в Kubemetes, кроме NodePort есть и другие варианты. Возможно, вы
найдете механизм, который лучше подходит для вашей рабочей среды. NodePort
хорошо работает с minikuЬe, но подходит и для других балансировщиков нагрузки
с более статической конфигурацией.
Итак, возвращаемся к определению Service. Объект Service будет подключаться
к Deployment через selector, который присутствует в разделе spec. Метки в Kubemetes
часто служат для обозначения схожих компонентов, чтобы их можно было связать
друг с другом. Метки представляют собой пары произвольно определяемых ключей
и значений, к которым мы можем обращаться для определения компонентов систJ;:­
мы. Здесь selector указывает Kubemetes, что нужно искать объекты Deployments
с меткой арр: lazyraster. Обратите внимание, что та же метка указана и у самого
объекта service. Это удобно, если мы хотим позже определить все компоненты вме­
сте, но именно в разделе selector объект Deployment привязывается к Service. Итак,
у нас есть service, но пока он ничего не делает. Нам нужны дополнительные опре­
деления, чтобы Kubernetes делал то, что мы хотим.
Определение PersistentVolumeC/aim
apiVersion: vl
kind: PersistentVolumeClaim
metadata:
name: cache-data-claim
laЬels:
арр: lazyraster
spec:
accessМodes:
- ReadWriteOnce
resources:
requests:
storage: l00Mi
В следующем разделе определяются объекты PersistentVolumeClaim и Persistent Volume.
PersistentVolumeClaim позволяет указать том и сообщить, что у нас есть токен для
доступа к этому тому определенным образом. Обратите внимание, что здесь мы не
определили PersistentVolume, потому что Kubemetes делает это сам с помощью так
называемого динамического выделения томов.
В нашем случае все просто: нам нужен запрос на чтение и запись к тому, и мы по­
зволяем Kubemetes поместить его в контейнер тома. Представьте, что приложение
будет развертываться в облаке, где и так доступно динамическое выделение. В та­
ком сценарии нам не нужны отдельные вызовы для создания тома в облаке. Мы
хотим, чтобы за это отвечал Kubemetes. Это и есть динамическое выделение томов.
Здесь подобная процедура создаст контейнер для хранения постоянных данных, а
затем примонтирует его к поду при запросе. В этом разделе мы просто присваиваем
имя, запрашиваем 100 МБ данных и указываем Kubemetes, что это единичный
запрос на чтение-запись к тому.
Книги для программистов: https://clcks.ru/books-for-it
252
Глава 10
В этой сфере для Kubernetes существует множество предложений - доступные
варианты томов ОТ'-lасти зависят от вашего облака или провайдера. Изучите их и
выберите наиболее подходящее решение для вашей рабочей среды.
Определение Deployment
apiVersion: apps/vl
kind: Deployment
metadata:
name: lazyraster
laЬels:
арр: lazyraster
spec:
selector:
matchLaЬels:
арр: lazyraster
s trategy:
type: RollingUpdate
template:
metadata:
laЬels:
spec:
арр: lazyraster
containers:
- image: relistan/lazyraster:demo
name: lazyraster
env:
- name: RASTER RING ТУРЕ
value: memЬerlist
- name: RASTER BASE DIR
value: /data
ports:
- containerPort: 8000
name: lazyraster
volumeМounts:
- name: cache-data
mountpath: /data
volumes:
- name: cache-data
persistentVolumeClaim:
claimName: cache-data-claim
Объект Deployment создает поды и использует контейнер Linux для приложения. Мы
задаем некоторые метаданные приложения, включая его имя и метку, как в других
определениях. Мы также применяем еще один selector, чтобы найти остальные не­
обходимые ресурсы. В разделе strategy мы выбрали плавающие обновления
RollingUpdate, чтобы поды обновлялись по одному во время развертывания.
Книги для программистов: https://clcks.ru/books-for-it
253
Масштабирование контейнеров
В разделе template мы указали, как делать копии развертывания. Определение кон­
тейнера включает имя образа Docker, порты для подключения, тома для монти­
рования, а также переменные среды, которые нужны приложению lazyraster.
В последней части раздела spec мы указываем PersistentVolumeClaim с именем cache­
data-claim.
Итак, мы определили приложение. Давайте теперь развернем его.
Мы можем написать здесь много разных параметров и директив, чтобы указать
Kubernetes, что делать с приложением. Здесь мы рассматриваем несколько простых вариантов. Изучите документацию по Kubernetes, чтобы узнать больше.
Развертывание приложения
Прежде чем продолжить, давайте заглянем в кластер Kubemetes с помощью коман­
ды kuЬectl:
$ kuЬectl get all
PORT (S)
EXTERNAL-IP
AGE
CLUSTER-IP
ТУРЕ
NАМЕ
<none>
10.96.0.1
443/ТСР
ClusterIP
160m
service/kuЬernetes
Пока мы только определили сервис с именем service/kuЬernetes. В Kubemetes приня­
то перед именем объекта указывать его тип (кind), который иногда сокращается до
двух или трех букв. Например, service может обозначаться как svc. Если интересно,
посмотрите все ресурсы и их сокращенное название с помощью команды kuЬectl
api-resources. Давайте запустим сервис, развертывание и том в кластере:
$ kuЬectl apply -f ./lazyraster-service.yaml
service/lazyraster created
persistentvolumeclaim/cache-data-claim created
deployment.apps/lazyraster created
Вывод выглядит ожидаемо: у нас есть сервис, запрос на постоянный том и развер­
тывание. Вот что находится в кластере:
$ kuЬectl get all
READY
AGE
RESTARTS
STAТUS
NАМЕ
1/1
Running
17s
pod/lazyraster-644cb5c66c-zsjxd
о
NАМЕ
service/kuЬernetes
service/lazyraster
ТУРЕ
ClusterIP
NodePort
NАМЕ
deployment.apps/lazyraster
CLUSTER-IP
10.96.0.1
10.109.116.225
READY
1/1
NАМЕ
replicaset.apps/lazyraster-644cb5c66c
EXTERNAL-IP
<none>
<none>
PORT (S)
AGE
443/ТСР
lбlm
8000:32544/TCP17s
UP-TO-DATE
1
AVAILABLE
1
AGE
17s
DESIRED
1
CURRENT
1
READY
1
AGE
17s
Мы видим незнакомый компонент, но не видим том или запрос на него. Информа­
цию о томах нужно запрашивать отдельно:
Книги для программистов: https://clcks.ru/books-for-it
254
1
Глава 10
$ kuЬectl get pvc
NАМЕ
STATUS
cache-data-claim Bound
VOLUME
CAPACITY
pvc-la... 41 lOOMi
ACCESS MODES
RWO
STORAGECLASS
standard
AGE
65s
Несмотря на то что get all означает "получить все", kuЬectl get all эти данные не
показывает. Отображаются самые распространенные ресурсы. Изучите шпаргалку
с командами kuЬectl (https ://kubernetes. io/docs/reference/kubectl/cheatsheet).
Что такое replicaset.apps в выходных данных команды get all? Это ReplicaSet­
pecypc Kubemetes, который следит за наличием достаточного количества экземпля­
ров приложения и их работоспособностью. Обычно мы не настраиваем ReplicaSet
сами, потому что им управляет созданное нами развертывание. При необходимости
мы можем контролировать ReplicaSet, но обычно такая необходимость не возни­
кает.
Мы не указали в kuЬectl, сколько экземпляров нам нужно, поэтому он всего один.
Мы видим, что желаемое и текущее состояния совпадают. Вернемся к этому чуть
позже, а сначала давайте подключимся к приложению и посмотрим, что у нас есть:
$ minikuЬe service --url lazyraster
http://192.168.99.100:32185
У вас IР-адрес и порт могут отличаться. Это нормально, они предоставляются
динамически. Поэтому мы используем команду minikuЬe, чтобы она управляла за нас
этими данными.
Кстати, minikuЬe предупредит, если необходимо непрерывно выполнять команду
service, пока вы изучаете сервис lazyraster. Скопируйте полученный адрес и вставьте
его в адресной строке браузера следующим образом: bttp://<192.168.99.100:32185>/
documents/docker-up-and-running-puЫic/sample.pdf?page=l. Замените адрес и порт
на свои значения.
Потребуется подключение к Интернету, потому что приложению lazyraster нужно
будет извлечь PDF из бакета SЗ, а затем отрисовать первую страницу в формате
PNG, т. е. выполнить растеризацию. Если все получилось, вы увидите копию
обложки предыдущего издания книги. Этот PDF включает две страницы, так что
измените аргумент на ?page=2. Вы заметите, что вторая страница будет готова гораз­
до быстрее, чем первая, потому что приложение кеширует данные в постоянном
томе. Вы можете указать ширину, например width=204B, или запросить JPEG вместо
PNG - imageType=image/jpeg. Первую страницу можно преобразовать в очень боль­
шой JPEG: bttp://<192.168.99.100:32185>/documents/docker-up-and-running-puЫic/
sam-ple.pdf?page= l&imageType=image/jpeg&width=2048.
Если у вас есть публичный бакет SЗ с другими PDF, просто замените имя бакета
docker-up-and-running-puЬlic в URL на ваш вариант. Если хотите поэкспериментиро­
вать с приложением, загляните в репозиторий Nitro/lazyraster на GitHub (https://
github.com/NitroЛazyraster).
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
255
Увеличение масштаба
В реальном мире мы не только развертываем, но и обслуживаем приложения. Одно
из главных преимуществ запланированных рабочих нагрузок - возможность ме­
нять их масштаб по желанию в рамках ресурсов, доступных в системе. У нас есть
только один узел Minikube, но мы все же можем увеличить масштаб сервиса, чтобы
лучше распределить нагрузку и повысить надежность во время развертываний.
Kubemetes позволяет легко увеличивать и уменьшать масштаб. Для нашего сервиса
понадобится всего одна команда. Затем мы изучим выходные данные kuЬectl и по­
смотрим на Kubernetes Dashboard, чтобы убедиться, что масштаб увеличен.
В Kubemetes мы увеличиваем масштаб не для сервиса, а для развертывания. Вот
как это будет выглядеть:
$ kuЬectl scale --replicas=2 deploy/lazyraster
deployment.apps/lazyraster scaled
Что-то произошло, но что именно?
$ kuЬectl get deployment/lazyraster
UP-TO-DATE
NАМЕ
READY
2
2/2
lazyraster
AVAILABLE
2
AGE
lбm
Теперь запущены два экземпляра приложения. Давайте заглянем в журнал:
$ kuЬectl logs deployment/lazyraster
Found 2 pods, using pod/lazyraster-644cb5c66c-zsjxd
Trying to clear existing Lazyraster cached files (if any) in the background ...
Launching Lazyraster service ...
time="2022-09-10T21:14:16Z" level=info msg="Settings
time="2022-09-10T21:14:16Z" level=info msg=" * BaseDir: /data"
time="2022-09-10T21:14:16Z" level=info msg=" * HttpPort: 8000"
time="2022-09-10T21:14:16Z" level=info msg=" * LoggingLevel: info"
time="2022-09-10T21:14:16Z" level=info msg="
time="2022-09-10T21:14:16Z" level=info msg="Listening on tcp://:6379"
Мы запросили журналы развертывания, но Kubemetes показывает, что запущены
два пода, и отображает журналы только для одного из них. Мы видим, что реплика
запускается. Если мы хотим посмотреть конкретный экземпляр, можно запросить
журналы для определенного пода - kuЬectl logs pod/lazyraster-644cb5c66c-zsjxd, но
сначала надо найти нужный под с помощью команды kuЬectl get pods.
Теперь запущены две копии приложения. Давайте посмотрим, как это выглядит на
Kubernetes Dashboard, выполнив команду minikuЬe dashЬoard. Из левого меню перей­
дем в раздел Workloads (Рабочие нагрузки) ---t Deployments (Развертывания) и на­
жмем на развертывание lazyraster. Откроется экран, как на рис. 10.3.
Книги для программистов: https://clcks.ru/books-for-it
256
1
Глава 10
Рис. 10.3. Сервис lazyraster на панели (пример)
Изучите разные разделы на Kubernetes Dashboard, чтобы увидеть, что там еще есть.
Теперь вы знаете гораздо больше и можете в чем-то разобраться самостоятельно.
У kuЬectl есть много разных параметров, и многие из них понадобятся вам в рабо­
чей среде. Настоятельно рекомендуем ознакомиться со шпаргалкой по командам
(https://kubernetes.io/docs/reference/kubectl/cheatsheet).
Когда закончите, нажмите комбинацию клавиш <Ctrl>+<C>, чтобы завершить
команду minikuЬe dashЬoard.
kubectl API
Пока мы не рассматривали API, но вы уже знаете, что в Docker очень удобен про­
стой API для написания скриптов, программирования и других общих задач.
Вы можете написать программу, которая будет напрямую взаимодействовать
с Kubernetes API, но для локального развертывания и других простых сценариев
используйте kuЬectl как удобный прокси для Kubernetes. Он предоставляет мини­
малистичный API, доступный через curl, и инструменты командной строки для
работы с JSON. Пример того, что мы можем сделать:
$ kuЬectl proxy
Starting to serve on 127.0.0.1:8001
Теперь сам kuЬectl предоставляет нам веб-АРI в локальной системе. Рекомендуем
изучить другие доступные возможности, но сейчас давайте рассмотрим отдельные
экземпляры приложения lazyraster. Для этого перейдем по следующему URL­
aдpecy в браузере или через curl в другом окне терминала: http:/Лocalhost:8001/
api/vl/ namespaces/default/endpointsЛazyraster.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
257
Выходных данных много, но нас интересует раздел suЬsets:
"suЬsets":
"addresses":
"ip": "172.17.0.5",
"nocleName": "minikuЬe",
"targetRef": {
"kind": "Pod",
"namespace": "default",
"name": "lazyraster-644cb5c66c-zsjxd",
"uid": "9631395d-7e68-47fa- bb9f-964ld724d8f7"
},
"ip": "172.17.0.6",
"nocleName": "minikuЬe",
"targetRef": {
"kind": "Pod",
"namespace": "default",
"паше": "lazyraster-644cb5c66c-pvcmj",
"uid": "e909d424-7a91-4a74-aed3-69562Ь74b422"
],
"ports":
}
"port": 8000,
"protocol": "ТСР"
]
}
]
Что интересно, оба экземпляра выполняются на одном хосте Minikube, но у них
разные IР-адреса. При создании облачного приложения, которому требуется знать,
где выполняются другие его экземпляры, это удобный подход.
Нажмите <Ctrl>+<C>, чтобы выйти из процессов kuЬectl proxy, а затем удалите раз­
вертывание и все его компоненты следующей командой. Kubernetes потребуется
около минуты, чтобы все удалить и вернуть нас в терминал:
$ kuЬectl delete -f ./lazyraster-service.yaml
service "lazyraster" deleted persistentvolwneclaim "cache-data-claim" deleted
deployment.apps "lazyraster" deleted
Книги для программистов: https://clcks.ru/books-for-it
258
Глава 10
Наконец, мы можем удалить кластер Minikube, если закончили работу с ним:
$ minikuЬe delete
� Deleting "minikuЬe" in docker ...
� Deleting container "minikuЬe" ...
(), Removing /Users/spkane/.minikuЬe/machines/minikuЬe
- Removed all traces of the "minikuЬe" cluster.
Kubernetes - это развитая система с активным сообществом. Minikube - лишь
верхушка айсберга. Если интересно, изучите другие дистрибутивы и инструменты
Kubernetes.
Kubernetes, интегрированный в Docker Desktop
Docker Desktop поддерживает встроенный кластер Kubemetes с одним узлом, кото­
рый можно включить в настройках приложения.
Встроенный кластер Kubemetes не так легко настроить, но это удобный вариант,
если мы хотим изучить базовый функционал Kubemetes.
Чтобы включить в Docker Desktop встроенный функционал Kubemetes, запустите
Docker Desktop, а затем нажмите на значок Docker с китом на панели задач и выбе­
рите пункт Preferences (Настройки). На вкладке Kubernetes выберите флажок
ЕпаЫе Kubernetes (Включить), а затем нажмите кнопку Apply & Restart (Приме­
нить и перезапустить), чтобы внести необходимые изменения в виртуальной маши­
не. В первый раз Docker выполнит команду kuЬeadm для создания кластера Kuber­
netes (https://kubernetes.io/docs/reference/setup-tools/kubeadm).
Если вы хотите узнать больше о включении встроенного Kubernetes в Docker
Desktop, прочтите эту статью (https://www.docker.com/Ыog/how-kubernetes­
works-under-the-hood-with-docker-desktop).
При этом будет создан новый контекст kuЬectl с именем docker-desktop, и мы автома­
тически перейдем в этот контекст.
Проверить текуший контекст можно следующей командой:
$ kuЬectl config current-context
docker-desktop
При необходимости мы можем перейти в нужный контекст:
$ kuЬectl config use-context docker-desktop --namespace;default
Switched to context "docker-desktop".
Наконец, если мы хотим убрать текущий контекст, можно задать команду:
$ kuЬectl config unset current-context
Property "current-context" unset.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
1
259
Запустив кластер, мы можем взаимодействовать с ним так же, как с любым другим
кластером Kubemetes, - с помощью команды kuЬectl. При завершении работы
Docker Desktop кластер Kubemetes тоже останавливается.
Если хотите полностью отключить кластер Kubemetes, вернитесь на панель
Preferences, перейдите на вкладку Kubernetes и уберите флажок ЕпаЫе Kuber­
netes.
Kind
Последний инструмент для Kubemetes, который мы рассмотрим, - kind. Это про­
стое и удобное средство позволяет управлять кластером Kubemetes, который со­
стоит из одного или нескольких контейнеров Linux, запущенных в Docker. Назва­
ние kind - это акроним, обозначающий Kubemetes in Docker (Kubernetes в Docker).
Кроме того, тип объекта в Kubemetes определяется в API по полю Kind.
Искать информацию об этом инструменте в Интернете будет непросто, так что
ориентируйте�ь на главный сайт (https://kind.sigs.k8s.io/) с самим инструментом и
документациеи к нему.
Инструмент kind - это что-то среднее между упрощенным кластером Kubemetes,
встроенным в виртуальную машину Docker, и виртуальной машиной minikuЬe, кото­
рая иногда чересчур усложняется. kind распространяется в одном двоичном файле,
и его можно установить с помощью любого менеджера пакетов или на странице
выпусков kind (https://github.com/kubernetes-sigs/kind/releases), где можно загру­
зить актуальный выпуск для вашей системы. Если вы загружаете файл вручную,
переименуйте его в kind, скопируйте в каталог по соответствующему пути и убеди­
тесь, что у него есть необходимые разрешения для запуска.
После установки kind попробуем создать первый кластер:
$ kind create cluster --name test
Creating cluster "test" ...
✓ Ensuring node image (kindest/node:vl.25.3)
✓ Preparing nodes
✓ Writing configuration
✓ Starting control-plane •
✓ Installing CNI �
✓ Installing StorageClass М
Set kuЬectl context to "kind-test"
You can now use your cluster with:
kuЬectl cluster-info --context kind-test
Thanks for using kind
1
�
По умолчанию эта команда запускает один контейнер Docker, который представля­
ет кластер Kubemetes с одним узлом, используя последний стабильный релиз
Kubemetes, поддерживаемый инструментом kind.
Книги для программистов: https://clcks.ru/books-for-it
260
1
Глава 10
kind задает для текущего контекста кластер Kubernetes, поэтому мы сразу можем
выполнять команды kuЬectl:
$ kuЬectl cluster-info
KuЬernetes control plane is running at https://127.0.0.1:56499
CoreDNS is running at
https://127.0.0.1:56499/api/vl/namespaces/kuЬe-system/services/kuЬe-dns:dns/proxy
То further debug and diagnose cluster proЫems, use _'kuЬectl cluster-info dump'.
Мы можем посмотреть отредактированную версию информации, с помощью кото­
рой kuЬectl подключается к серверу Kubernetes:
$ kuЬectl config view --minify
apiVersion: vl
clusters:
- cluster:
certificate-authority-data: DATA+OМITTED
server: https://127.0.0.1:56499
name: kind-test
contexts:
- context:
cluster: kind-test
user: kind-test
name: kind-test
current-context: kind-test
kind: Config
preferences: {}
users:
- name: kind-test
user:
client-oertificate-data: REDACTED
client-key-data: REDACTED
У kind есть расширенные функции (https://kind.sigs.k8s.io/docs/user/quick-start/
#advanced), и их можно использовать через файл конфигурации, который мы пере­
даем с аргументом --config при запуске кластера.
Вот несколько полезных функций:
♦ Изменение текущей версии Kubernetes.
♦ Запуск нескольких рабочих узлов.
♦ Запуск нескольких узлов плоскости управления для тестирования высокой дос­
тупности.
♦ Проброс портов между Docker и локальным хостом.
♦ Включение и отключение функций Kubernetes с помощью feature gate (https://
kubernetes.io/docs/reference/command-line-tools-reference/feature-gates).
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
1
261
♦ Экспорт журналов компонентов ruюскости управления· с помощью kind export
logs и многое другое.
Применяя kind, помните, что Kubemetes выполняется в одном или нескольких кон­
тейнерах, которые, в свою очередь, могут находиться в виртуальной машине Linux,
если вы работаете с Docker Desktop или подобным инструментом. Это значит, что
при запуске кластера необходимо будет дополнительно настроить проброс портов.
Для этого можно использовать extraPortMappings в конфигурации kind.
Давайте удалим кластер:
$ kind delete cluster --name test
Deleting cluster "test" ...
Amazon ECS и Fargate
Одна из самых популярных облачных платформ Amazon AWS в середине 2014 года
предложила АWS Elastic Beanstalk (https://amzn.to/2wNalrL) для нативной под­
держки контейнеров. Правда, этот сервис назначает экземпляру Amazon только
один контейнер, а значит, это не идеальное решение для краткосрочных или легких
контейнеров. Amazon Elastic Compute Cloud (Amazon ЕС2) - это отличная плат­
форма для размещения среды Docker, и поскольку у самого Docker достаточно воз­
можностей, нам не нужно добавлять много инструментов для эффективной работы.
Однако у Amazon есть решение, созданное специально для контейнеров, - Amazon
Elastic Container Service (Amazon ECS). В последние несколько лет Amazon разви­
вает новые продукты, например Elastic Kubemetes Services (EKS) и АWS Fargate.
Fargate - это просто маркетинговое название для обозначения функции ECS, ко­
торая позволяет AWS автоматически управлять всеми узлами в кластере контей­
неров, чтобы мы могли сосредоточиться на развертывании сервиса, а не на ин­
фраструктуре.
ECS - это набор инструментов, который координирует несколько компонентов
АWS. При использовании ECS мы можем добавить Fargate. Если добавляем, нам
почти ничего не приходится делать самим. Если мы обходимся без Fargate, то в до­
полнение к узлам кластера, которые возьмут на себя рабочую нагрузку, потребует­
ся добавить в кластер один или несколько экземтшяров ЕС2 с Docker и специаль­
ным агентом Amazon ECS. Если мы запустим Fargate, нам не придется самим
управлять кластером. В любом случае мы запускаем кластер и отправляем в него
контейнеры.
Агент Amazon ECS (https://github.com/aws/amazon-ecs-agent), который мы упомя­
нули, координирует работу кластера и планирует контейнеры на хостах. Мы взаи­
модействуем с ним, только когда управляем традиционным кластером ECS без
Fargate.
Книги для программистов: https://clcks.ru/books-for-it
262
1
Глава 10
Базовая настройка AWS
Предполагается, что у вас есть доступ к аккаунту А WS и вы знакомы с сервисом.
Посетите страницу https://aws.amazon.com/free, чтобы узнать о ценах и создать
аккаунт. Amazon предлагает бесплатный доступ, и вам хватит его для эксперимен­
тов в этой книге, если у вас нет платного аккаунта. После создания аккаунта А WS
вам потребуется хотя бы один административный пользователь, пара ключей,
Amazon Virtual Private Cloud (Amazon VPC) и группа безопасности по умолчанию
в вашей среде. Если у вас чего-то не хватает, следуйте инструкциям в документа­
ции Amazon (https://amzn.to/2FcPDSL).
Настройка ролей IAM
Роли Amazon Identity and Access Management (Amazon IАМ) позволяют разрешать
пользователям разный набор действий в облачной среде. Нам нужна возможность
предоставлять необходимые разрешения. Для работы с ECS необходимо создать
роль ecsinstanceRole, к которой привязана управляемая роль Amэ.zonEC2ContainerServiceRole.
Самый постой способ это сделать - войти на консоль AWS (https://console.aws.
amazon.com/) и перейти в раздел ldentity and Access Management (https://console.
aws.amazon.com/iam/home).
Проверьте, установлена ли необходимая роль. Если она уже существует, убеди­
тесь, что она правильно настроена, потому что за последние годы правила немно­
го изменились.
1. На левой панели выбираем раздел Roles (Роли).
2. Нажимаем кнопку Create role (Создать роль).
3. В разделе АWS Service (Сервис А WS) выбираем Elastic Container Service.
4. В разделе Select your use case (Выберите вариант применения) выбираем Elastic
Container Service.
5. Нажимаем кнопку Next: Permissions (Далее: Разрешения).
6. Нажимаем Next: Review (Далее: Проверка).
7. В поле Role Name (Имя роли) вводим ecslnstanceRole.
8. Нажимаем кнопку Create role (Создать роль).
Если вы хотите хранить конфигурацию контейнеров в бакете объектного хранили­
ща SЗ, изучите документацию (https://amzn.to/2PNapOL) по конфигурации агента
контейнеров Amazon ECS.
Настройка AWS CLI
Amazon предлагает инструменты командной строки для работы с инфраструктурой,
использующей API. Установите последнюю версию AWS CLI. У Amazon есть подКниги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
1
263
робная документация (https://amzn.to/lPCpPNA) по установке инструментов, но
мы вкратце приведем эти инструкции здесь.
Установка
Здесь мы рассмотрим установку на нескольких операционных системах, но не за­
бывайте, что всегда можно запустить А WS CLI через контейнер Docker. Вы можете
сразу перейти к разделу с нужной операционной системой или прочитать все раз­
делы, если вам нравятся инструкции по установке или просто любопытно.
macOS
В главе 3 мы устанавливали Homebrew. Если вы выполнили те инструкции, устано­
вите А WS CLI следующей командой:
$ brew update
$ brew install awscli
Windows
Amazon предоставляет стандартный установщик MSI для Windows, который можно
загрузить из Amazon SЗ для вашей архитектуры:
♦ 32-разрядная Windows (https://s3.amazonaws.com/aws-cli/AWSCLI32.msi);
♦ 64-разрядная Windows (https://s3.amazonaws.com/aws-cli/АWSCLI64.msi).
Другие ОС
Инструменты Amazon CLI написаны на Python, так что на большинстве платформ
их можно устанавливать с помощью менеджера пакетов Python pip следующей
командой в командной оболочке:
$ pip install awscli --upgrade --user
На некоторых платформах pip не установлен по умолчанию и нужно использовать
менеджер пакетов easy_install:
$ easy_install awscli
Конфигурация
Убедитесь, что установлен AWS CLI версии не ниже 1.7.0:
$ aws --version
aws-cli/1.14.50 Python/3.6.4 Darwin/17.3.0 botocore/1.9.3
Для настройки А WS CLI нужен идентификатор ключа доступа А WS и секретный
ключ доступа А WS. Мы выполним команду configure и должны будем ввести дан­
ные для аутентификации и настройки по умолчанию:
$ aws configure
AWS Access Кеу ID (None]: ЕХАМРLЕЕХАМРLЕЕХАМРLЕ
AWS Secret Access Кеу (None]: ExaMPleKEy/7EXAМPL3/EXaMPLeEXAМPLEKEY
Default region name (None]: us-east-1
Default output format (None]: json
Книги для программистов: https://clcks.ru/books-for-it
264
Глава 10
Сейчас подходящий момент, чтобы убедиться, что инструменты CLI работают
нормально. Для этого вьmолним следующую команду, чтобы вывести пользовате­
лей IАМ в аккаунте:
$ aws iam list-users
Если все пошло по плану и вы выбрали JSON в качестве выходного формата по
умолчанию, то увидите примерно следующее:
"Users": [
"Path": "/",
"administrator",
"ExmaPLЗExmaPLЗExmaPLЗEx",
"Arn": "arn:aws:iam::936262807352:user/myuser",
"CreateDate": "2021-04-08Т17:22:23+00:ОО",
"PasnordLastUsed": "2022-09-0STlS:56:21+00:00"
"UserNallle":
"Userid":
Экземпляры контейнеров
После установки необходимых инструментов в первую очередь следует создать
хотя бы один кластер, в котором будут регистрироваться новые хосты Docker.
1-Ж
�
Кластер по умолчанию так и называется - default. Если вы решите оставить это
имя, вам не придется указывать --cluster-name в последующих командах.
Для начала нужно создать в сервисе контейнеров кластер, а затем мы сможем за­
пускать в нем задачи. В следующих примерах создадим кластер с именем fargate­
testing:
$ aws ecs create-cluster --cluster-name fargate-testing
"cluster": (
"arn:aws:ecs:us-east-1:1 ... 2:cluster/fargate-testing",
"fargate-testing",
"clusterArn":
"cluste:Name":
"status": "ACTIVE",
"reqisteredContainerinstanoesCount":
О,
"pendi.ngТasksCount": О,
О,
"runningТasksCount":
"activeServioesCount":
О,
"statistics": [],
"taqs":
[],
"settinqs":
"name": "containerinsights",
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
265
"value": "disaЫed"
),
"capacityProviders": [],
"defaul tcapaci tyProviderStrategy" : []
До выхода А WS Fargate нужно было создавать экземпляры А WS ЕС2 с помощью
docker и ecs-agent, а затем добавлять их в кластер. При желании мы и сейчас можем
применить этот подход (Ес2 launch type), но Fargate упрощает запуск динамического
кластера, который будет менять масштаб в зависимости от рабочей нагрузки.
Задачи
Мы создали кластер контейнеров, и теперь можем использовать его для работы.
Давайте создадим хотя бы одно определение задачи. В Amazon ECS определением
задачи называется список контейнеров, объединенных в одну группу.
Чтобы создать первое определение задачи, откройте любой редактор, скопируйте
следующий код JSON и сохраните его как файл webgame-task.json в текущем ката­
логе:
"containerDefinitions":
"name":
11
web-game 11 ,
"image": 11 spkane/quantum-game",
''cpu": о,
"portмappings": (
11 containerPort 11 :
"hostPort": 8080,
"protocol": 11 tcp 11
8080,
),
essential 11 : true,
"environment": [),
11mountpoints 11 : [),
11 volumesFrom11 : [)
11
],
11 family 11 :
1 f g
11
1 ar ate-game ,
orkМode 11 : 11awsvpc 11,
"volumes": [],
11 placementconstraints 11 : [],
11 requiresCompatiЬilities 11 :
11 netw
11 FARGATE 11
],
Книги для программистов: https://clcks.ru/books-for-it
Глава 10
266
"cpu": "256",
"memory": "512"
Мы можем выгрузить эти и некоторые другие файлы:
git clone \ https://githuЬ.com/Ыuewhalebook/\
docker-up-and-running-Зrd-edition.git \
--config core.autocrlf=input
URL продолжается на следующей строке, чтобы вписаться в ширину страницы.
Возможно, потребуется пересобрать его и убрать обратные косые черты, чтобы
команда работала.
В определении задачи мы указываем, что хотим создать группу задач fargate-game
с одним контейнером web-game с игрой Quantum (https://github.com/stared/quantum­
game). В одной из предыдущих глав мы видели, как образ Docker запускает брау­
зерную игру на основе квантовой механики.
Fargate ограничивает параметры, которые мы можем задать в этой конфигурации,
включая networkМode (сетевой режим), cpu (процессорные ресурсы) и memory (па­
мять). В документации AWS (https://amzn.to/2PkliGR) подробно описаны парамет­
ры в определении задач.
В этом определении задачи ограничиваем использование памяти и процессора кон­
тейнером, а также указываем для Amazon, критичен ли этот контейнер для нашей
задачи. Флаг essential (критически важен) удобен, когда в задаче определено не­
сколько контейнеров, но не все они необходимы для успешного выполнения зада­
чи. Если для essential задано true и контейнер не запускается, все контейнеры,
определенные в задаче, будут остановлены, а задача помечена как завершившаяся
сбоем. Кроме того, с помощью определения задачи мы можем задать почти все
типичные переменные и настройки, которые мы указали бы в Dockerfile или в ко­
мандной строке docker container run.
Для отправки этого определения задачи на Amazon выполним следующую команду:
$ aws ecs register-task-definition --cli-input-json file://./webgame-task.json
"taskDefinition": {
"taskDefinitionArn": "arn:aws:ecs:... : task-definition/fargate-game:1",
"containerDefinitions":
"name": "web-game",
"image": "spkane/quantum-game",
"cpu": О,
"portмappings": [
"containerPort": 8080,
"hostFort": 8080,
"protocol": "tcp"
],
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
267
essential 11 true,
environment11
111110untpoints 11
11
:
11
:
:
11 volumesFrom11 :
],
11 family 11 :
11
11 volumes 11 :
11 status 11 :
[] ,
[]
f argate-game11
awsvpc11
11 networkМod 11
e :
11 rev ision 11 :
[],
11
,
,
1,
[] ,
1 ACTIVE 1 1,
1
11 requiresAttriЬutes 11 :
11
name 11
:
11
name11
:
1
1
com.amazonaws.ecs.capaЬility .docker-remote-api.1.18 11
11
ecs.capaЬility.task-eni11
},
],
11
placementConstraints": [] ,
11 compatiЬilities 11 :
11 ЕС2"
,
"FARGATE 11
],
11 requiresCompatiЬilities":
11 FA RG ATE 11
],
11
11
cpu 11 11 256",
memory11 н512 11
:
:
11 registeredAt 11 :
11 registeredВy11 :
'
2022-09-05Т09:10:18.184000-07:0011
"arn:aws: iam::...:user/me11
11
,
Теперь можно посмотреть все определения задач:
$ aws ecs list-task-definitions
11 taskDefinitionArns 11 :
[
"arn:aws:ecs:us-east-1: ...:task-definition/fargate-game:l",
Мы готовы создать первую задачу в кластере, выполнив команду, приведенную
далее. Аргумент count позволяет указать, сколько копий задачи необходимо развер­
нуть в кластере. Для этого задания будет достаточно одной.
Измените команду, чтобы указать идентификатор подсети и группы безопасности
для вашего А WS УРС. Эти данные можно найти на консоли А WS (https://console.
Книги для программистов: https://clcks.ru/books-for-it
Глава 10
268
aws.amazon.com/vpc/home) или с помощью команд АWS CLI aws ес2 describe­
suЬnets и aws ес2 describe-security-groups. Вы также можете указать АWS, что нужно
назначить задачам публичный IР-адрес, задав примерно следующую сетевую кон­
фигурацию:
awsvpcConfiguration= {suЬnets= [suЬnet-aЬcdl234],
securityGroups= [sg-aЬcdl234],
assignPuЬlicip=ENABLED}
Публичный IР-адрес может понадобиться, если мы используем публичные подсети:
$ aws ecs create-service --cluster fargate-testing --service-name \
fargate-game-service --task-definition fargate-game:l --desired-count 1 \
--launch-type "FARGATE" --network-configuration \
"awsvpcConfiguration= {suЬnets= [suЬnet-abcdl234],\
securityGroups= [sg-aЬcdl234]}"
"service":
{
"arn:aws:ecs:...:service/fargate-game-service",
"fargate-game-service",
"cluзterArn": "arn:aws:ecs: ...: cluster/fargate-testing",
"loaclВalancers" : [],
"serviceRagistries": [],
"status": "ACTIVE",
"serviceArn":
"serviceNaшe":
"desiredCount": 1,
"runningCount": О,
О,
"FARGATE",
"platformVersion": "LATEST",
"platformFami.ly": "Linux",
"taskDefinition": "arn:aws:ecs: ...:task-definition/fargate-game:l",
"deployшentconfiguration": {
"pendingCount":
"launchТype":
"deployшentcircui tвreaker":
false,
false
"enaЬle":
"rollЬack":
},
"JDIOvjпnпnP-trcent": 200,
"lllinilllualНealthyPercent": 100
},
"deployшents":
[
"id": "ecs-svc/ ...",
"statuз": "PRIМARY",
"taskDefinition": "arn:aws:ecs:... definition/fargate-game:l",
"desiredCount": 1,
"pendingCount":
О,
"runningCount": О,
"failedтasks":
о,
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
269
2022-09-05Т09:14:51.653000-07:00",
"2022-09-05Т09: 14:51.653000-07:00",
11
launchТype 11 : "FARGATE 11 ,
11
platformVersion 11 : "1.4. О",
11 platformFami.ly 11 : 11 Linux",
11
createdAt 11 :
11 upd
11
11
11
atedAt :
networkConfiguration 11 :
),
11
11
rolloutstate 11 :
],
11
"IN_PROGRESS",
"ECS deployment ecs-svc/... in progress."
rolloutstateReason 11 :
roleArn":
"...aws-service-role/ecs.arnazonaws.com/AWSServiceRoleForECS",
"events 11 : [],
"createdAt":
"2022-09-05Т09: 14:51.653000-07:00",
[
"placementconstraints 11 : ],
[] ,
"placementstrategy11 :
"networkConfiguration 11 :
),
"REPLICA",
"arn:aws:iam::... :user/me",
"schedulingStrategy 11 :
11
createdВy":
"enaЫeECSМanagedTags 11 : false,
11
propagateTags 11 : "NONE",
11
enaЬleExecuteCOIIIIDal1d. 11 : false
Для Fargate и сети awsvpc требуется роль для ECS, связанная с сервисом. В пре­
дыдущих выходных данных есть строка, которая заканчивается следующим обра­
зом:
"role/aws-service-role/ecs.arnazonaws.com/ AWSServiceRoleForECS"
Обычно она создается автоматически, но мы можем сделать это вручную:
$ aws iam create-service-linked-role \
--aws-service-name ecs.amazonaws.com
Мы можем просмотреть все сервисы в кластере следующей командой:
$ aws ecs list-services --cluster fargate-testing
11 serviceArns 11 : [
"arn:aws:ecs:us-west-2:...:service/fargate-testing/fargate-game-service"
Следующая команда предоставляет все детали о сервисе:
$ aws ecs describe-services --cluster fargate-testing \
--services fargate-game-service
Книги для программистов: https://clcks.ru/books-for-it
Глава 10
270
"services":
[
11 dep
loyments 11 :
ecs-svc/...11
11 s
tat us 11 : PRIМARY",
11 taskDefinition 11 : "arn: ...: task-definition/fargate-game: 1",
11 desiredCount 11 : 1,
11
dingCount 11 : 1,
11
id":
11
1
11
pen
11 runningCount 11 :
11 c
1
1
reatedAt 11 :
updatedAt 11 :
О,
11 2022-09-05Т09:14: 51.653000-07:00 11 ,
"2022-09-05Т09:14:51.653000-07:00 11 1
11
11
1 launch'l'ype ; "FARGATE /
11
1p
s
e
latformV r ion : "1.4.0",
1
1
1
1
1p
1
latformFamily 11 :
inux 11
,
networkConfiguration 11 :
),
11 rolloutstate 11 :
11
1L
1
1
1
IN_PROGRESS",
1
ECS deployment ecs-svc/... progress. 1
rolloutstateReason 11 :
1
1
],
roleArn 11 : 11 •••role/ecs.amazonaws.com/AWSServiceRoleForECS 11 ,
11 events 11 : [
11
11
id":
11
83Ьd5с2ееd5d4866ЬЬ7ес8с3с938666с",
2022-09-05Т09:14:54.950000-07:00 11
11 createdAt 11 :
11mess age 11
:
11
11 ( •••
,
game-service) has started 1 tasks: ( ...) . 1
1
],
],
11failures 11 :
[]
Их этих выходных данных можно многое узнать обо всех задачах в сервисе. Сейчас
у нас запущена одна задача.
Значение task-definition содержит имя, за которым следует число (fargate­
game: 1), указывающее на версию. Если мы внесем изменения и снова зарегистри­
руем задачу командой aws ecs register-task- definition, версия изменится, т. е.
мы будем ссылаться на новую версию в командах, например aws ecs updateservice. Если мы не изменим число, то продолжим запускать контейнеры с помо­
щью старого JSON. Номера версий позволяют легко откатывать изменения и тес­
тировать новые версии без последствий для будущих экземпляров.
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
271
Мы можем просмотреть отдельные задачи в кластере:
$ aws ecs list-tasks --cluster fargate-testing
[
"arn:aws:ecs:...:task/fargate-testing/83bd5c2eed5d4866bЬ7ec8c3c938666c"
"taskArns":
Сейчас в кластере только одна задача, поэтому список будет очень маленьким.
Чтобы узнать больше об отдельной задаче, можно выполнить следующую команду,
указав соответствующий идентификатор задачи:
$ aws ecs describe-tasks --cluster fargate-testing \
--task 83bd5c2eed5d4866bЬ7ec8c3c938666c
"tasks":
[
"attachments":
"details":
"name": "networkinterfaceid",
"eni-00a40225208c9411a"
"value":
),
"name": "privateIPv4Address",
"172.31.42.184"
"value":
],
"attriЬutes":
],
"us-west-2b",
"arn:aws:ecs:us-west-2: ...:cluster/fargate-testing",
"connectivity": "CONNECTED",
"connectivityAt": "2022-09-05Т09:23:46.929000-07:00",
"containers": [
"availaЬilityZone":
"clusterArn":
"arn: ... :container/fargate-testing/ ... ",
"arn:...:task/fargate-testing/...",
"containerArn":
"taskArn":
"name":
11
web-g ame 11 ,
"image": "spkane/quantum-game",
Книги для программистов: https://clcks.ru/books-for-it
Глава 10
272
11 runtimeid 11 :
11 laststatus 11 :
"83bd...998",
"RUNNING",
11 networkinterfaces 11 :
[
ntid 11 : "ddaЬ...37За 11 ,
11 attachme
11 priv ateipv4Address 11 :
],
11 healthStatus11 :
"cpu": "О"
],
11
cpu
11 :
"172.31.42.184"
"UNКNOWN",
"256",
"createdAt 11 : "2022-09-05ТО9:23:42.700000-07:00",
"desirec\Status": "RUNNING",
"enaЬleExecuteeoaaand": false,
"qroup 11 : "service:fargate-game-service",
11 healt h Status 11 : "UNКNO�IN",
"laststatus 11 : "RUNNING",
11 launchТype 11 :
"FARGATE",
1111181110ry11 : "512",
11 overrides 11 : {
11 contai.ner0verrides 11 :
11 name 11 :
"web-game"
],
inferenceAcceleratorOverrides" : []
},
11 platfot111Version 11 : "1.4.0",
11 platforшFamily 11 : "Linux",
11pullStartedAt 11 : "2022-09-05Т09:59:36.554000-07:00",
11 pullStoppedAt": "2022-09-05Т09:59:46.361000-07:00",
11 startedAt 11 : "2022-09-05Т09:59:48.546000-07:00",
" startec1Вy11 : "ecs-svc/ ...",
11 tags 11 : [ ,
]
11 taskArn 11 : "arn:aws:...:task/fargate-testing/83bd... бббс",
11 taskDefinitionArn 11 : "arn:aws:...:task-definition/fargate-game:l",
11 version 11 : 4,
"epheшeralStorage 11 :
11 sizeinGiB 11 :
20
11
],
11 failures":
[]
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
273
Если ключ laststatus имеет значение PENDING (В ожидании), скорее всего, сервис по­
ка запускается. Выведите информацию о задаче снова и убедитесь, что состояние
изменилось на RUNNING (Выполняется). Когда ключ lastStatus будет иметь значение
RUNNING, мы сможем протестировать контейнер.
В некоторых сетях задача не сможет загрузить образ. Если возникнет ошибка:
"stoppedReason": "CannotPullContainerError: inspect image has Ьееп retried 5 time(s):
failed to resolve ref \"docker.io/spkane/quantum-game:latest\": failed to do request: Head
https://registry-1.docker.io/v2/spkane/quantum-game/manifests/latest: dial tcp 54.83.42.
1
45:443: i/o timeout", изучите руководство по устранению неполадок .
Тестирование задачи
Вам понадобится современный браузер для подключения к контейнеру и тестиро­
вания игры.
В предыдущих выходных данных для частного IPv4-aдpeca (privateIPv4Address) ука­
зано значение 172.31.42.184. У вас будут другие цифры.
Если вам нужно больше информации о настройках сети для вашей задачи и
экземпляре ЕС2, на котором она запущена, скопируйте идентификатор сетевого
интерфейса networkinterfaceid в выходных данных aws ecs describe-tasks и до­
бавьте его в команду aws ес2 describe-network-interfaces --network-interface-ids,
чтобы узнать всю необходимую информацию, включая значение PuЫicip, если вы
настроили в сервисе публичный IР-адрес.
Убедитесь, что подключены к сети, которая имеет доступ к публичному или част­
ному IР-адресу хоста, а затем откройте браузер и перейдите к порту 8080 по этому
IР-адресу.
В этом примере частный URL-aдpec будет выглядеть следующим образом:
http:l/172.31.42.184:8080/
Если все работает, как ожидалось, откроется игра Quantum Game.
Официальную версию игры вы найдете на сайте https://quantumgame.io.
Мы поймем, если вы отложите книгу и на несколько часов увлечетесь игрой и изу­
� чением квантовой механики. Книга подождет. Продолжите, когда будете готовы.
�
Остановка задачи
Итак, у нас выполняется задача. Давайте узнаем, как ее остановить. Нам пона­
добится ее идентификатор, и мы можем его получить, снова выведя все задачи,
выполняющиеся в кластере:
1 Полный URL:
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_cannot_pull_image.html.
Книги для программистов: https://clcks.ru/books-for-it
274
Глава 10
$ aws ecs list-tasks --cluster fargate-testing
11
taskArns 11 :
1
1
[
arn:aws:ecs: ...:task/fargate-testing/83bd5c2eed5d4866bЬ7ec8c3c938666c 11
Мы также можем узнать идентификатор из информации о сервисе:
$ aws ecs describe-services --cluster fargate-testing \
--services fargate-game-service
id11 : "6Ь7f...0384",
"2022-09-05ТО9:59:23.917000-07:00",
11message 11 : " • • • : (task 83bd5c2eed5d4866bЬ7ec8c3c938666c) . "
11
11 createdAt 11 :
Наконец, мы можем остановить задачу, выполнив следующую команду с указанием
идентификатора:
$ aws ecs stop-task --cluster fargate-testing \
--task 83bd5c2eed5d4866bb7ec8c3c938666c
"STOPPED",
11 desiredStatus11 :
11
lastStatus 11 : "RUNNING",
11 stopCode 11 :
"Userinitiated",
"stoppedReason 11 : 11Task stopped Ьу user 11 ,
"stoppingAt 11 : 11 2022-09-0STl0:29:05.110000-07:00",
Если мы снова посмотрим на задачу по тому же идентификатору, то увидим, что
ключ laststatus имеет значение STOPPED (Остановлено):
$ aws ecs describe-tasks --cluster fargate-testing \
--task 83bd5c2eed5d4866bb7ec8c3c938666c
11
desiredStatus11 : 11 STOPPED",
11 lastStatus":
11
STOPPED 11 ,
Книги для программистов: https://clcks.ru/books-for-it
Масштабирование контейнеров
275
Если мы посмотрим задачи в кластере, то получим пустой набор:
$ aws ecs list-tasks --cluster fargate-testing
"taskArns" : [ ]
Теперь мы можем создавать более сложные задачи, которые связывают несколько
контейнеров, и использовать инструменты ECS и Fargate для запуска хостов и раз­
вертывания задач в кластере.
Очистить среду ECS можно следующими командами:
$ aws ecs delete-service --cluster fargate-testing \
--service fargate-game-service --force
$ aws ecs delete-cluster --cluster fargate-testing
Заключение
В этой главе мы рассмотрели много инструментов. Вряд ли вы будете использовать
их все, потому что их функции во многом совпадают, однако каждый из них под­
держивает свой подход к рабочей среде и решает разные проблемы. Изучив эти ин­
струменты, вы получите хорошее представление о том, какие варианты позволят
вам создать среду для контейнеров Linux.
В основе всех этих инструментов лежит легко переносимый формат образов Docker
для контейнеров Linux, а также возможность абстрагировать большую часть систе­
мы Linux, поэтому мы можем легко переносить приложения между центрами обра­
ботки данных и облачными платформами. Осталось выбрать идеальный подход для
вашей организации, а затем реализовать его.
В следующей главе мы будем рассматривать технические тонкости экосистемы
Docker, включая безопасность, сети и хранение.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 11
Расширенные концепции
В этой главе мы рассмотрим расширенные концепции. Предполагается, что вы уже
довольно хорошо знакомы с Docker, используете его в рабочей среде или как
минимум часто с ним работаете. Мы подробно обсудим, как работают контейнеры,
а также поговорим о безопасности, сетях и плагинах Docker, альтернативных сре­
дах выполнения и других расширенных конфигурациях.
Также мы рассмотрим изменения, которые можно внести в установку Docker. Ино­
гда это удобно, но в Docker, как и в большинстве других платформ, неплохие
настройки по умолчанию, и нам не следует их менять без особой причины и без
четкого понимания того, что мы делаем. Вам потребуется время, чтобы методом
проб и ошибок подстроить систему под вашу среду, но мы не рекомендуем менять
настройки по умолчанию, если вы не до конца понимаете последствия этих изме­
нений.
Контейнеры в деталях
Обычно мы говорим о контейнерах Linux как о едином объекте, хотя на самом деле
они реализуются с помощью нескольких отдельных механизмов, встроенных в ядро
Linux и работающих вместе: контрольные группы (control groups, cgroups), про­
странства имен, безопасный режим вычислений (Secure Computing Mode, seccomp) и
SELinux или AppArmor. Вместе они инкапсулируют процесс. Контрольные группы
отвечают за ограничения ресурсов, пространства имен позволяют процессам ис­
пользовать ресурсы с одинаковыми именами и изолируют их от остальных объек­
тов, Secure Computing Mode ограничивает доступные системные вызовы, а SELinux
и AppArmor обеспечивают дополнительную изоляцию процессов в целях безопас­
ности. Итак, зачем нужны cgroups и пространства имен?
Прежде чем мы углубимся в детали, давайте рассмотрим аналогию, которая помо­
жет вам понять, как эти компоненты взаимодействуют при работе контейнера.
Представьте, что компьютер - это большой открытый склад, полный работников
(процессов). На складе много пространства и ресурсов, но работники мешают друг
другу, а ресурсы получает тот, кто успеет схватить их первым.
Когда мы используем Docker и контейнеры Linux для рабочих нагрузок, склад пре­
вращается в офисное помещение, в котором у каждого работника есть свой каби­
нет. В каждом кабинете есть все необходимое для работы, и, как правило, теперь
сотрудники могут заниматься своим делом, не заботясь о том, что делают осталь­
ные.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
277
Пространства имен - это стены кабинета, благодаря которым процессы не могут
бесконтрольно взаимодействовать с соседями. Контрольные группы - это аренд­
ная плата за ресурсы. Когда процесс запускается впервые, ему выделяется процес­
сорное время и хранилище на каждый цикл, а также память, которая будет доступ­
на ему в каждый момент. Таким образом работники (процессы) получают необхо­
димые ресурсы без ущерба для коллег. Представьте себе худший пример шумного
соседа, и вы поймете, почему так важно окружить кабинеты толстыми стенами. На­
конец, Secure Computing Mode, SELinux и AppAлnor - это служба безопасности,
благодаря которой даже в случае инцидента, скорее всего, придется просто запол­
нить чуть больше документов и отправить отчет.
cgroups
В традиционных распределенных системах нам приходится выполнять каждую ин­
тенсивную задачу на отдельном виртуальном сервере. Например, не следует запус­
кать приложения на севере базы данных, потому что они захватят все ресурсы, а
производительность баз данных упадет.
Использовать для этого реальное оборудование было бы слишком дорого, и на вы­
ручку приходят виртуальные серверы, которые позволяют размещать на дорогом
оборудовании несколько конкурирующих приложений, а слой виртуализации отве­
чает за распределение ресурсов. Так дешевле, но все же недостаточно экономно,
если нам не нужны остальные возможности виртуализации, потому что использо­
вание нескольких ядер приводит к дополнительным издержкам для приложений.
Обслуживать виртуальные машины тоже недешево, зато облачные вычисления до­
казали свою полезность и высокую эффективность при применении правильных
инструментов.
Если нам требуется только разделение ресурсов, было бы гораздо удобнее работать
на одном ядре, не запуская еще один экземпляр операционной системы. В течение
многих лет мы могли выражать только свои "пожелания" о приоритетности
(niceness) выполнения процессов и не могли задавать жесткие ограничения, как
в виртуальных машинах. Эти пожелания нельзя было детально настраивать, можно
было только указать, что этому процессу следует выделить больше операций ввода­
вывода или меньше ЦП, чем остальным. Контейнеры Linux обеспечивают деталь­
ный контроль с помощью cgroups, которые были разработаны специально для реше­
ния этих проблем еще до Docker.
Контрольные группы позволяют ограничивать ресурсы, предоставляемые роди­
тельским и дочерним процессам. Это механизм, с помощью которого ядро Linux
контролирует ограничения памяти, подкачки, процессора, хранилища и опера­
ций ввода-вывода, встроенный в ядро и впервые появившийся в 2007 году в
Linux 2.6.24. В официалъной документации по ядру (https://www.kernel.org/doc/
Documentation/cgroup-v2.txt) они определены как "механизм иерархической орга­
низации процессов и контролируемого распределения ресурсов системы в иерар­
хии". Важно отметить, что эти настройки применяются к процессу и всем его
дочерним процессам, и такая организация отражает структуру контейнеров.
Книги для программистов: https://clcks.ru/books-for-it
278
Глава 11
У контрольных групп Linux было два выпуска: версия 1 (https://www.kernel.org/doc/
Documentation/cgroup-v1/cgroups.txt) и версия 2 (https://www.kernel.org/doc/
Documentation/cgroup-v2.txt). Проверьте, какая версия используется в рабочей сре­
де, чтобы задействовать все доступные возможности.
Каждому контейнеру Linux назначается уникальная группа cgroup. Все процессы
в контейнере входят в одну группу, т. е. мы можем контролировать ресурсы для
каждого контейнера, не беспокоясь о том, какие процессы в нем выполняются.
Если мы добавляем в контейнер процессы и развертываем его снова, Docker может
назначить ту же политику, и она будет применяться ко всему контейнеру и ко всем
процессам в нем.
Мы уже говорили, что можем настраивать cgroups в Docker через API. Этот интер­
фейс позволяет управлять памятью, подкачкой и использованием диска, но cgroups
дает гораздо больше возможностей, например мы можем помечать сетевые пакеты
от контейнера тегами, чтобы приоритизировать трафик. Эти функции могут пона­
добиться для контроля контейнеров. Группы cgroups обрабатывают много данных
о ресурсах, необходимых каждой группе, а значит, при их применении в ядре мож­
но найти много интересной статистики о потребляемых процессами ресурсах про­
цессора, ОЗУ, операциях ввода-вывода и т. д. Docker с помощью cgroups не только
ограничивает ресурсы, но и предоставляет данные по ним. Мы можем посмотреть
много разных метрик, например с помощью команды docker container stats.
Файловая система /sys
Даже если мы настроили cgroups в Docker, для тонкого управления ими потребуется
наше вмешательство. Это самый удобный способ контролировать параметры, по­
тому что изменения можно вносить не только при создании контейнера, но и на
лету.
В системах с systemd для этого предусмотрены инструменты командной строки,
вроде systemctl. Поскольку cgroups встроены в ядро, самый универсальный способ взаимодействовать с ядром напрямую через файловую систему /sys. Файловая сис­
тема /sys предоставляет несколько параметров и выходных данных ядра. Мы
можем использовать ее с простыми инструментами командной строки, чтобы опре­
делять поведение ядра.
Этот метод настройки cgroups для контейнеров работает только напрямую на серве­
ре Docker, не через API. При реализации этого метода мы должны продумать, как
написать скрипт с нужными параметрами для нашей среды.
.
Изменение значений cgroups отдельно, вне конфигурации Docker, может ломать
воспроизводимость развертывания. Если не применить изменения в процессе �аз­
вертывания, параметры вернутся к значениям по умолчанию при замене контеине­
ров. Некоторые планировщики обрабатывают эти изменения. Если в вашей рабочей среде есть планировщик, изучите документацию, чтобы узнать, как вносить
воспроизводимые изменения.
Давайте рассмотрим пример, в котором изменим параметры процессорного време­
ни в cgroups для только что запущенного контейнера. Нам потребуется длинный
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
279
идентификатор контейнера, который нужно найти в файловой системе /sys. Вот как
это будет выглядеть:
$ docker container run -d spkane/train-os \
stress -v --cpu 2 --io 1 --vm 2 --vm-bytes 128М --timeout 360s
dcbb...8e86fldc0a9le7675d3c93895cbбaбd8337le25b7f0bd62803ed8e86
С помощью docker container run мы узнали длинный идентификатор контейнера:
dcbb...Be86fldc0a9le7675d3c93895cbбaбd8337le25Ь7f0bd62803ed8e86. Понятно, почему обыч­
но Docker обрезает его.
В этих примерах может потребоваться обрезать идентификатор, чтобы он помес­
тился н� стандартной странице, но не забывайте, что мы должны использовать
длинныи вариант.
Итак, у нас есть идентификатор, и мы можем найти cgroup контейнера в файловой
системе /sys. Эта файловая система организована таким образом, что каждый тип
параметров сгруппирован в модуль, который можно предоставлять в разных местах
файловой системы. Следовательно, изучая параметры процессорного времени, мы
не увидим, например, параметры блочного ввода-вывода. Исследуйте /sys само­
стоятельно, чтобы узнать, что еще в ней есть. Сейчас нас интересует контроллер
процессорных ресурсов. Нам потребуются права root в системе, потому что мы
будем менять параметры ядра.
Не забывайте про использование nsenter, как мы обсуждали в главе 3. Выполним
команду docker container run --rm -it -privileged --pid=host deЬian nsenter -t 1
-m -u -n -i sh, чтобы получить доступ к хосту Docker, даже если мы не можем под­
ключиться к серверу через SSH.
$ ls /sys/fs/cgroup/docker/dcbb ...8е86
cgroup.controllers
cpuset.cpus.partition
cgroup.events
cpuset.mems
cgroup.freeze
cpuset.mems.effective
cgroup.max.depth
hugetlb.2MB.current
cgroup.max.descendants
hugetlb.2MB.events
cgroup.procs
hugetlb.2MB.events.local
cgroup.stat
hugetlb.2MB.max
cgroup.suЬtree_control
hugetlb.2MB.rsvd.current
cgroup.threads
hugetlb.2МВ.rsvd.max
cgroup.type
io.Ьfq.weight
cpu.max
io.latency
cpu.stat
io.max
cpu.weight
io.stat
cpu.weight.nice
memory.current
cpuset.cpus
memory.events
cpuset.cpus.effective
memory.events.local
memory.high
memory.low
memory.max
memory.min
memory.oom.group
memory.stat
memory.swap.current
memory.swap.events
memory.swap.high
memory.swap.max
pids.current
pids.events
pids.max
rdma.current
rdma.max
Книги для программистов: https://clcks.ru/books-for-it
280
Глава 11
Точнь�й путь зависит от дистрибутива Linux на сервере Docker, а также от хеша
� контеинера.
�
Под cgroups мы видим каталог docker, который содержит все контейнеры Linux, за­
пущенные на этом хосте. Мы не можем задать cgroups для того, чего нет на хосте,
потому что они применяются только к выполняющимся процессам. Это важно
помнить. Docker повторно применяет параметры cgroups при запуске и остановке
контейнеров. Без этого механизма приходилось бы все делать вручную.
Давайте проверим долю процессорного времени (CPU weight) для этого контейнера
(https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface--files). В главе 5
мы рассматривали, как устанавливать эти значения с помощью аргумента команд­
ной строки --cpus в команде docker container run. Если ничего не задавать, мы уви­
дим значение по умолчанию:
$ cat /sys/fs/cgroup/docker/dcbb...Be86/cpu.weight 100
Здесь 100 означает отсутствие ограничений на процессорное время. Давайте ука­
жем ядру, что нужно ограничить ресурсы наполовину:
$ echo 50 > /sys/fs/cgroup/docker/dcbb...Be86/cpu.weight
$ cat /sys/fs/cgroup/docker/dcbb...Be86/cpu.weight
50
В рабочей среде не следует с помощью этого метода менять настройки cgroups на
лету, но мы рассматриваем этот пример, чтобы лучше понять механику. Для вы­
полняющегося контейнера можно задать команду docker container update
(https://dockr.ly/2PPC4P1). Также рассмотрите параметр --cgroup-parent для
docker container run (https://dockr.ly/2PTLaKK).
Итак, мы изменили настройки контейнера на лету. Это эффективный метод, с по­
мощью которого можно задавать любые настройки cgroups для контейнера. Но не
стоит забывать, что контейнеры не существуют долго, и при перезапуске вернутся
значения по умолчанию:
$ docker container stop dcbb...ВеВб dсЬЬ ...ВеВб
$ cat /sys/fs/cgroup/docker/dcbb...Be86/cpu.weight
cat: /sys/fs/.../cpu.weight: No such file or directory
Мы видим, что после остановки контейнера этот путь больше не существует. Если
мы запустим его снова, увидим значение 100:
$ docker container start dcbb...ВеВб
dcbb...ВеВб
$ cat /sys/fs/cgroup/docker/dcbb...Be86/cpu.weight
100
Если бы мы хотели изменить эту настройку в рабочей среде через файловую систе­
му /sys, это нужно было бы сделать напрямую. Например, с помощью демона,
который наблюдает за потоком docker system events и меняет настройки при запуске
контейнера.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
281
Мы можем создавать индивидуальные cgroups за пределами Docker, а затем при­
креплять к ним новые контейнеры с помощью аргумента --cgroup-parent для ко­
манды docker container create. С помощью этого механизма планировщики запус­
� кают несколько контейнеров в одной группе cgroup (например, поды Kubernetes).
Пространства имен
Внутри каждого контейнера есть файловая система, сетевые интерфейсы, диски и
другие ресурсы, которые кажутся уникальными для контейнера, хотя ядро предос­
тавляется для всех процессов в системе. Основной сетевой интерфейс на фактиче­
ском компьютере, например, всего один, но в контейнере это будет выглядеть так,
будто весь сетевой интерфейс находится в его распоряжении. Это полезная абст­
ракция, благодаря которой контейнер работает как полноценный компьютер. Такая
возможность реализуется в ядре с помощью пространств имен Linux. Пространства
имен предоставляют контейнеру отдельную, уникальную и принадлежащую только
ему версию традиционно глобального ресурса.
Пространства имен не так легко изучить в файловой системе, как cgroups, но
большую часть информации можно найти в. иерархиях /prod*/ns/* и /prod*/task/
*/ns/*. В более новых выпусках Linux также можно использовать команду lsns.
Вместо единого пространства имен, однако, по умолчанию контейнеры используют
пространство имен для каждого ресурса в ядре: rrюunt (файловая система), uтs, IPC,
PID, network (сеть) и user (пользователь), а также частично реализованное простран­
ство имен time (время). По сути, контейнер представляет собой несколько про­
странств имен, координируемых Docker. Зачем они нужны?
♦ Пространства имен файловой системы (mount namespaces) - с их помощью
Linux создает видимость того, что у контейнера есть собственная полноценная
файловая система. Это более надежная альтернатива chroot jail, которая работа­
ет на самых глубоких уровнях ядра, так что в пространство имен входят даже
системные вызовы rrюunt и unmount. Если для входа в контейнер вы используете
docker container ехес или nsenter, о которых мы поговорим позже в этой главе, то
увидите корень файловой системы /. Мы знаем, что это не настоящий корневой
раздел, и это обозначение возможно благодаря пространству имен файловой
системы.
♦ Пространства имен uтs (Unix Time Sharing) - предоставляют контейнеру от­
дельное имя хоста и доменное имя. Они также используются более старыми сис­
темами, вроде NIS, для определения принадлежности хоста к домену. Когда мы
входим в контейнер и видим имя хоста, которое не совпадает с именем компью­
тера, - это работа пространства имен.
Чтобы контейнер использовал пространство имен UTS хоста, можно указать пара­
метр --uts=host при запуске контейнера с помощью docker container run. Для дру­
гих пространств имен есть похожие команды.
Книги для программистов: https://clcks.ru/books-for-it
282
1
Глава 11
♦ Пространства имен IPC - изолируют системы очередей сообщений System V
IPC и POSIX контейнера от аналогичных систем хоста. Некоторые механизмы
IPC задействуют ресурсы файловой системы, например именованные каналы,
и за них отвечают пространства имен файловой системы. Пространство имен IPC
руководит общей памятью и семафорами, которые не являются ресурсами фай­
ловой системы, но не должны выходить за пределы контейнера.
♦ Пространства имен PID - мы уже видели, что можем просматривать все процес­
сы в контейнерах в выводе Linux ps на сервере Linux. Внутри контейнера у про­
цессов другие идентификаторы PID, потому что используется пространство имен
PID. У процесса есть уникальный PID в каждом пространстве имен, куда он вхо­
дит. Если мы заглянем в /proc или выполним команду ps, то увидим только про­
цессы внутри пространства имен PID контейнера.
♦ Пространства имен сети - позволяют контейнеру работать с отдельными сете­
выми устройствами, портами и т. д. Если мы выполним команду docker container
1s и увидим привязанные порты, это будут порты из обоих пространств имен.
Внутри контейнера nginx может быть привязан к порту 80, но сетевой интерфейс
ограничен пространством имен. Благодаря этому пространству имен у контей­
нера есть сетевой стек, который кажется принадлежащим только ему.
♦ Пространства имен пользователей - обеспечивают изоляцию между идентифи­
каторами пользователей и групп в контейнере и на хосте Linux. Ранее мы изуча­
ли вывод ps снаружи и внутри контейнера и видели разные идентификаторы
пользователей. Причина как раз в пространствах имен пользователей. Новый
пользователь в контейнере не становится новым пользователей в главном про­
странстве имен хоста Linux и наоборот. Правда, здесь есть несколько особенно­
стей. Например, uro о (root) в пространстве имен пользователей означает не то
же самое, что UID о на хаете, хотя работа с правами root в контейнере действи­
тельно повышает риски, и именно поэтому набирают популярность такие меха­
низмы, как контейнеры без root (rootless). Мы поговорим о брешах в системе
безопасности чуть позже.
♦ Пространства имен cgroup - это пространство имен появилось в ядре Linux 4.6
в 2016 году, чтобы скрыть принадлежность cgroup, в которую входит процесс.
Если какой-то процесс попытается проверить, в какую cgroup входит любой дру­
гой процесс, он получит путь относительно cgroup, заданный в момент создания,
но не истинное расположение и идентификацию cgroup.
♦ Пространства имен времени - изначально для времени не существовало про­
странства имен, поскольку этого ресурса нет в ядре Linux, и полноценно ограни­
чить его пространствами имен было бы очень сложно. Однако в ядре Linux 5.6
в 2020 году была добавлена поддержка пространства имен времени (https://
man7.org/linux/man-pages/man7/time_namespaces.7.html), которое позволяет
контейнерам использовать собственное системное время.
На момент написания в Docker напрямую не подцерживается отдельное время для
контейнеров, но при необходимости можно использовать дополнительные меха­
низмы.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
283
Сочетая все пространства имен, Linux может предоставить визуальную и в некото­
рых случаях функциональную изоляцию, благодаря которой контейнер похож на
виртуальную машину, даже если выполняется на том же ядре. Давайте подробнее
рассмотрим некоторые пространства имен.
Сейчас сообщество активно работает над повышением безопасности контейнеров.
Рассматриваются способы улучшить поддержку гооtlеss-контейнеров (https://
rootlesscontaine.rs/), чтобы обычные пользователи могли создавать, запускать и
� администрировать контейнеры локально без специальных привилегий. В Docker
для этого можно использовать режим rootless (https://docs.docker.com/engine/
security/rootless). Новые среды выполнения контейнеров, вроде Google gVisor
(https://github.com/google/gvisor), также пробуют разные способы работать в бо­
лее защищенных изолированных средах, при этом используя преимущества кон­
тейнеризированных рабочих процессов.
Изучение пространств имен
Пространство имен uтs проще всего продемонстрировать, так что давайте с помо­
щью команды docker container ехес войдем в контейнер. На сервере Docker введите
следующую команду:
$ hostname docker-desktop
Не забывайте, что можно выполнить команду docker container run --rm -it -­
privileged --pid=host deЬian nsenter -t 1 -m -u -n -i sh, которую мы рассматри­
вали в главе 3, чтобы получить доступ к хосту Docker, даже если мы не можем
подключиться к серверу по SSH.
В локальной системе выполним следующую команду:
$ docker container run -ti --rm uЬuntu \
bash -с 'echo "Container hostname: $(hostname)"'
Container hostname: 4сdЬббd4495Ь
Команда docker container run начинает интерактивный сеанс ( -ti), а затем выполняет
команду hostname через /Ьin/bash в контейнере. Поскольку команда hostname выполня­
ется в пространстве имен контейнера, мы получаем сокращенный идентификатор
контейнера, который используется как имя хоста по умолчанию. Это очень простой
пример, но он показывает, что мы находимся не в пространстве имен хоста.
Еще один простой для понимания пример - пространства имен PID. Давайте созда­
дим новый контейнер:
$ docker container run -d --rm --name pstest spkane/train-os sleep 240
6e005f895e259ed03c4386b5aeb03e0a50368ccl73078007bбdlbeaa8cd7dded
$ docker container ехес -ti pstest ps -ef
UID
root
root
PID
1
13
PPID
С
О
STIME
О 15:33
О 15:33
о
ТТУ
?
pts/0
TIME
CMD
00:00:00
00:00:00
sleep 240
ps -ef
Docker может показать нам идентификаторы процесса с точки зрения хоста:
$ docker container top pstest
UID
root
PID
31396
PPID
31370
С
О
STIME
15:33
ТТУ
?
TIME
00:00:00
CMD
sleep 240
Книги для программистов: https://clcks.ru/books-for-it
284
Глава 11
Мы видим, что изнутри контейнера исходная команда от Docker выглядит как
sleep 240 и ей назначен PID 1 в контейнере. Возможно, вы помните, что этот PID
обычно используется процессом init в системах Unix. В этом случае команда sleep 240,
с которой мы запустили контейнер, является первым процессом, поэтому получает
PID 1. Но в главном пространстве имен сервера Docker мы видим, что процессу на­
значен PID 31396, и это дочерний процесс для процесса PID 31370.
Если любопытно, выполните команду, чтобы узнать, у какого процесса PID 31370:
$ docker container run --pid=host uЬuntu ps -р 31370
PID
31370
ТТУ
?
TIME
00:00:00
CMD
containerd-shim
Теперь можно удалить контейнер, запущенный в последнем примере:
$ docker container rm -f pstest
Остальные пространства имен работают примерно так же, и вы уже получили неко­
торое представление об этом. Стоит отметить, что при первом использовании
nsenter в главе 3 мы передали довольно сложные аргументы, выполняя команду для
входа в контейнер с сервера Docker. Давайте рассмотрим часть, связанную с nsenter
в команде docker container run --rm -it --privileged --pid=host deЬian nsenter -t 1 -m -u
-n -i sh.
Оказывается, что nsenter -t 1 -m -u -n -i sh- то же самое, что nsenter --target 1
--mount --uts --net -ipc sh. Эта команда указывает, что нужно ориентироваться на
PID 1 и открыть командную оболочку в тех же пространствах имен mount, uts, net и
ipc, в которых находится этот процесс.
Вероятно, теперь вы лучше понимаете, как работают пространства имен. Для даль­
нейшего изучения попробуйте с помощью nsenter входить в разные пространства
имен в одноразовом контейнере, чтобы лучше понять происходящее.
Именно благодаря пространствам имен контейнер выглядит как контейнер. Если
добавить cgroups, получим надежную изоляцию между процессами на одном яд­
ре ОС..
Безопасность
Мы много говорили о том, как Docker обеспечивает изоляцию для приложений,
позволяет ограничивать потребление ресурсов и использует пространства имен,
чтобы обеспечить уникальность контейнера. Мы также упомянули такие техноло­
гии, как Secure Computing Mode, SELinux и AppArmor. Одно из преимуществ кон­
тейнеров - возможность заменить виртуальные машины в некоторых вариантах
применения. Давайте посмотрим, что по умолчанию будет изолировано, а что­
нет.
Сейчас вы уже понимаете, что контейнер не так хорошо изолирован, как виртуаль­
ная машина. На протяжении всей книги мы говорили о том, что контейнеры - это
просто процессы, выполняющиеся на сервере Linux. Пространства имен обеспечиКниги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
285
вают изоляцию, но контейнеры не так хорошо защищены, как нам хотелось бы,
особенно по сравнению с легковесными виртуальными машинами.
У контейнеров высокая производительность, и они занимают мало места, потому
что используют общее ядро сервера Linux. Этот факт и является главной проблемой
безопасности контейнеров Linux. Дело в том, что в ядре не все ограничено про­
странствами имен. Мы рассмотрели все пространства имен, которые обособляют
работу контейнера, но в ядре еще остаются аспекты, которые совершенно не изоли­
рованы, а пространства имен ограничивают контейнер, только если у него нет пол­
номочий запросить у ядра доступ к другому пространству имен.
Контейнеризированные предложения лучше защищены, чем неконтейнеризирован­
ные, потому что cgroups и стандартные пространства имен предоставляют изоляцию
от базовых ресурсов хоста, но не надейтесь, что контейнеры смогут заменить адек­
ватные меры безопасности. В рабочей среде запускайте контейнеры с теми права­
ми, которые нужны приложению. Если приложение должно выполняться на серве­
ре от имени непривилегированного пользователя, таким же образом его следует
запускать и в контейнере. В Docker мы легко можем запускать процессы контейне­
ров от имени непривилегированных пользователей, и почти всегда следует именно
так и поступать.
Аргумент --userns-remap для команды dockerd и rootless-peжим позволяют запус­
кать все контейнеры в контексте непривилегированного пользователя и группы
в хост-системе. Эти подходы помогают защитить хост от множества типов атак.
Подробнее о userns-remap см. в официальной документации по функции (https://
dockr.ly/2BYfWze) и демону Docker (https://dockr.ly/2LE9gG2).
Режим без root подробно рассматривается в разделе "Режим rootless" этой главы.
Давайте поговорим о типичных рисках и мерах контроля безопасности.
UJDO
Главный риск, связанный с контейнерами, заключается в том, что если мы не ис­
пользуем режим rootless или функционал userns-remap в демоне Docker, пользова­
тель root в контейнере будет являться пользователем root в системе. Существуют
дополнительные ограничения для root в контейнере, и пространства имен эффек­
тивно изолируют root в контейнере от самых опасных частей файловых систем
/proc и /sys. Но если мы работаем от имени uro о, у нас есть rооt-доступ, и если мы
каким-то образом получим доступ к защищенным ресурсам в файловой системе
или за пределами пространства имен, ядро будет считать нас пользователем root, а
значит, предоставит доступ к ресурсу. Если мы не настроили другие параметры,
Docker запускает все сервисы в контейнере как root, т. е. мы должны управлять
привилегиями в приложениях так же, как в любой стандартной системе Linux. Да­
вайте рассмотрим некоторые ограничения для доступа root и обсудим очевидные
бреши. Мы не сможем подробно рассмотреть все вопросы безопасности в контей­
нере, но вы получите некоторое представление о типах рисков.
Книги для программистов: https://clcks.ru/books-for-it
286
Глава 11
Для начала давайте запустим контейнер и перейдем в bash, используя публичный
образ UЬuntu, как в следующем коде. Установив нужные инструменты, мы обсу­
дим, какой доступ нам нужен:
$ docker container run --rm -ti uЬuntu /bin/bash
root@808a2Ь8426dl:/# apt-get update
root@808a2Ь8426dl:/# apt-get install -у kmod
root@808a2Ь8426dl:/# lsmod
Module
xfrm user
xfrm_algo
shiftfs
grpcfuse
vmw_vsock_virtio_transport
vmw_vsock_virtio_transport_common
vsock
Size
36864
16384
28672
16384
16384
28672
36864
Used Ьу
1
1 xfrm user
о
о
2
1 vmw_vsock_virtio_transport
9 vmw_vsock_virtio_transport common...
В Docker Desktop мы видим в списке только несколько модулей, но в обычной сис­
теме Linux список может быть очень длинным. С помощью команды lsmod мы по­
просили ядро показать загруженные модули. Неудивительно, что мы можем полу­
чить список из контейнера, потому что у пользователя с обычными правами всегда
есть такая возможность. Если мы выполним этот код на сервере Docker, получим
такой же результат, и это еще раз подтверждает, что контейнер обращается к тому
же ядру Linux, которое запущено на сервере. Если мы видим модули ядра, что
будет, когда мы попробуем удалить модуль floppy?
root@808a2Ь8426dl:/# rmmod shiftfs
rmmod: ERROR: ../libkmod/libkmod-module.c:799 kmod_module_remove_module()
rmmod: ERROR: could not remove module shiftfs: Operation not permitted
root@808a2Ь8426dl:/# exit
Такое же сообщение об ошибке было бы выдано, если бы мы от имени непривиле­
гированного пользователя попросили ядро удалить модуль. Как видите, ядро стара­
ется запретить нам выполнять опасные операции. Поскольку мы находимся в огра­
ниченном пространстве имен, ядро не дает нам доступ к пространству имен верхне­
го уровня. По сути, мы просто надеемся, что в ядре нет ошибок, из-за которых мы
смогли бы повысить привилегии в контейнере. Если бы у нас это получилось, мы
получили бы права root и смогли бы вносить изменения, разрешенные ядром.
Давайте рассмотрим простой пример того, что может пойти не так. Запустим обо­
лочку bash в контейнере, где /etc сервера Docker примонтирован к пространству
имен контейнера. Не забывайте, что любой пользователь, который может запустить
контейнер на сервере Docker, способен выполнить те же действия, потому что мы
не можем настроить это запрет в Docker. Для этого требуются сторонние инстру­
менты вроде SELinux.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
287
В этом примере предполагается, что вы используете Docker CLI в системе Linux,
где есть файл /etc/shadow. Этого файла не будет на хостах Windows или macOS
с Docker Desktop.
$ docker container run --rm -it -v /etc:/host_etc uЬuntu /bin/bash
rооt@еб74еЬ96ЬЬ74:/# more /host_etc/shadow
root: 1 :16230:0:99999:7:::
daemon:*:16230:0:99999:7:::
bin:*:16230:0:99999:7:::
sys:*:16230:0:99999:7:::
irc:*:16230:0:99999:7:::
nobody:*:16230:0:99999:7:::
libuuid: !:16230:0:99999:7:::
syslog:*:16230:0:99999:7:::
messagebus:*:16230:0:99999:7:::
kmatthias:$1$aTAYQТ.j$3xamPL3dHGow4ITBdRhl:16230:0:99999:7:::
sshd:*:16230:0:99999:7:::
lxc-dnsmasq: !:16458:0:99999:7:::
rооt@еб74еЬ96ЬЬ74:/# exit
Здесь мы указали параметр -v, чтобы Docker подключил путь хоста к контейнеру.
Мы выбрали /etc, и это небезопасно, зато мы видим, что получили права root в кон­
тейнере, и у root есть разрешения на доступ к файлам по этому пути. Итак, у нас
есть доступ к файлу /etc/shadow на сервере Linux, где содержатся зашифрованные
пароли всех пользователей. Мы можем выполнять много друтих операций, и по
умолчанию наши возможности только частично ограничены.
Не следует запускать процессы контейнера с UID о, потому что любой эксплойт,
из-за которого процесс выйдет за пределы своих пространств имен, откроет при­
вилегирован�ый доступ к хосту. У стандартных контейнеров должен быть неприви­
легированныи UID.
Самый простой способ решить потенциальные проблемы с использованием uro о
в контейнерах - указать, что Docker должен всегда назначать для контейнеров
другой uro.
Для этого можно передать аргумент -u команды docker container run. В следующем
примере мы выполняем команду whoami, чтобы показать, что у нас есть права root по
умолчанию, и мы можем читать файл /etc/shadow внутри этого контейнера:
$ docker contai�er run --rm spkane/train-os:latest whoami
root
$ docker container run --rm spkane/train-os:latest cat /etc/shadow
root: !locked::0:99999:7:::
bin:*:18656:0:99999:7:::
daemon:*:18656:0:99999:7:::
adm:*:18656:0:99999:7:::
lp:*:18656:0:99999:7:::
Книги для программистов: https://clcks.ru/books-for-it
288
Глава 11
В этом примере при добавлении -и 500 мы становимся новым непривилегирован­
ным пользователем и больше не можем читать файл /etc/shadow:
$ docker container run --rm -и 500 spkane/train-os:latest whoarni
user500
$ docker container run --rm -и 500 spkane/train-os:latest cat /etc/shadow
cat: /etc/shadow: Permission denied
Кроме того, рекомендуется добавить директиву USER в Dockerfile, чтобы контейне­
ры, созданные на основе этих файлов, по умолчанию запускались как непривилеги­
рованные пользователи:
FRCМ fedora:34
RUN useradd -u 500 -m myuser
USER 500:500
СМD
[ "whoarni"]
Если мы создадим этот Dockerfile, а затем соберем и запустим его, мы увидим, что
whoarni возвращает myuser вместо root:
$ docker image build -t user-test .
[+] Building 0.5s (6/6) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 36В
=> [internal] load .dockerignore
=> => transferring context: 2В
=> [internal] load metadata for docker.io/library/fedora:34
=> (1/2] FROM docker.io/library/fedora:34@sha256:321d... 2697
=> CACHED (2/2] RUN useradd -u 500 -m myuser
=> exporting to image
=> => exporting layers
=> => writing image sha256:4727... 30d5
=> => naming to docker.io/library/user-test
$ docker container run --rm user-test myuser
0.0s
0.0s
0.0s
0.0s
0.4s
0.0s
0.0s
0.0s
0.0s
0.0s
0.0s
Режим rootless
Привилегированные процессы с правами root требуются контейнерам для запуска и
управления, но это серьезная угроза для безопасности. Если мы используем функ­
цию --userns-remap демона Docker, сам демон выполняется как привилегированный
процесс, даже если у запускаемых им контейнеров не будет привилегий.
В режиме rootless (https://docs.docker.com/engine/security/rootless) мы можем за­
пускать демон и все контейнеры без прав root, и это позволяет заметно повысить
уровень безопасности системы.
Режим rootless работает только в системе Linux, и Docker рекомендует UЬuntu, по­
этому давайте рассмотрим пример с новой системой Ubuntu 22.04.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
�
289
В этом примере предполагается, что мы входим в систему как обычный непривиле­
гированный пользователь и у нас уже установлен Docker Engine (https:1/docs.
docker.com/engine/install/ubuntu).
Сначала нам нужно убедиться, что dЬus-user-session и uidrnap установлены. Если dЬus­
user-session не установлен, нужно выйти из системы, выполнить следующую
команду и снова войти:
$ sudo apt-get install -у dЬus-user-session uidrnap
dЬus-user-session is already the newest version (l.12.20-2uЬuntu4).
Setting up uidrnap (l:4.8.l-2uЬuntu2) ...
Это не строгое требование, но если в системе запущен демон Docker, следует его
откmочить и перезагрузиться:
$ sudo systemctl disaЫe --now docker.service docker.socket
Synchronizing state of docker.service with SysV service script with
/liЬ/systemd/systemd-sysv-install.
Executing: /liЬ/systemd/systemd-sysv-install disaЬle docker
Removed /etc/systemd/system/sockets.target.wants/docker.socket.
Removed /etc/systemd/system/multi-user.target.wants/docker.service.
$ sudo shutdown -r now
Когда система запустится, снова подключитесь через SSH к серверу как обычный
пользователь и убедитесь, что больше не видите /var/run/docker.sock:
$ 1s /var/run/docker.sock
1s: cannot access '/var/run/docker.sock': No such file or directory
Следующий шаг - запустить скрипт установки режима rootless, который установ­
щик Docker загружает в /usr/Ьin:
$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/me/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
• docker.service - Docker Application Container Engine (Rootless)
Loaded: loaded (/home/me/.config/systemd/user/docker.service; ...)
+ DOCKER HOST=unix:///run/user/1000/docker.sock /usr/bin/docker version Client: Docker
Engine - Community
Version:
20.10.18
Server: Docker Engine - Community
Engine:
Version:
20.10.18
Книги для программистов: https://clcks.ru/books-for-it
290
Глава 11
+ systemctl --user еnаЫе docker.service
Created symlink /home/me/.config/systemd/user/default.target.wants/
docker.service � /home/me/.config/systemd/user/docker.service.
[INFO] Installed docker.service successfully.
[INFO] То control docker.service, run:
'systemctl --user (startlstoplrestart) docker.service'
[INFO] То run docker.service оп system startup, run:
'sudo loginctl enaЫe-linger me'
[INFO] Creating CLI context "rootless"
Successfully created context "rootless"
[INFO] Make sure the following environment variaЬles are set
(or add them to ~/.bashrc):
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
uro в переменной DOCKER_нosт эдесь должен совпадать с UID пользователя, эапус­
� тившего скрипт. В данном случае это UID 1000.
�
Этот скрипт выполнил несколько проверок, чтобы убедиться, что наша система
готова, а затем установил и запустил файл сервиса systemd для пользователя
в $ { НОМЕ) /.config/systemd/user/docker.service. При желании каждый пользователь
в системе может сделать то же самое.
Демон Docker пользователя можно контролировать так же, как большинство серви­
сов systemd. Несколько простых примеров:
$ systemctl --user restart docker.service
$ systemctl --user stop docker.service
$ systemctl --user start docker.service
Чтобы демон Docker пользователя выполнялся, когда пользователь не в системе,
нужно с помощью sudo включить параметр systemd linger, а затем разрешить демону
Docker выполняться при запуске системы:
$ sudo loginctl enaЬle-linger $(whoami)
$ systemctl --user еnаЫе docker
Сейчас можно было бы добавить эти переменные среды в наши файлы запуска
командной оболочки, но нужно убедиться, что эти переменные заданы в текущем
терминале:
$ export PATH=/usr/bin:$PATH
$ export DOCKER_HOST=unix:///run/user/1000/docker.sock
Мы можем запустить стандартный контейнер:
$ docker container run --rm hello-world
Hello from Docker!
This message shows that your installation appears to Ье working correctly.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
291
For more examples and ideas, visit:
https://docs.docker.com/get-started/
Вы заметите, что некоторые привилегированные контейнеры, которые мы рассмат­
ривали в предыдущих разделах, не будут работать в этой среде:
$ docker container run --rm -it --privileged --pid=host debian nsenter \
-t 1 -m -u -n -i sh
docker: Error response from daemon: failed to create shim task: OCI runtime
create failed: runc create failed: unaЬle to start container process: error
during container init: error mounting "proc" to rootfs at "/proc":
mount proc:/proc (via /proc/self/fd/7), flags: Охе:
operation not permitted: unknown.
Дело в том, что в режиме rootless у контейнера не может быть больше привилегий,
чем у пользователя, который его запускает, хотя и кажется, что у контейнера есть
все права root:
$ docker container run --rm spkane/train-os:latest whoami
root
Давайте подробнее рассмотрим этот вопрос, запустив небольшой контейнер с ко­
мандой sleep 480s:
$ docker container run -d --rm --name sleep spkane/train-os:latest sleep 480s
lf8ccec0a834537da20c6e07423f9217efe34c0eac94f0b0el78fb9761234lef
Если мы посмотрим на процесс внутри контейнера, станет ясно: нам только кажет­
ся, что он выполняется с правами root:
$ docker container ехес sleep ps auxwww
USER
PID %CPU %МЕМ
VSZ
root
root
1 0.1 О.О
7 о.о о.о
2400
7780
RSS ТТУ
824?
3316?
STAT
START
Ss
Rs
17:51
17:51
ТIМЕ СОММАND
0:00 sleep 480s
0:00 ps auxwww
Если мы посмотрим на процессы в системе Linux, то увидим, что команда sleep вы­
полняется локальным пользователем с именем те, а не пользователем root:
$ ps auxwww I grep sleep
3509 О.О О.О 2400 824?
Ss 10:51 0:00 sleep 480s
me 3569 О.О О.О 17732 2360 pts/0 S+ 10:51 0:00 grep --color=auto sleep
те
root в rооtlеss-контейнере соответствует самому пользователю. Процессы в контей­
нерах не могут использовать разрешения, которых нет у пользователя, запустивше­
го демон, и поэтому мы можем разрешать пользователям в многопользовательской
системе запускать контейнеры, не рискуя дать им слишком много привилегий.
�
-
На сайте Docker есть инструкции по удалению режима rootless
{https://docs.docker.com/engine/securitylrootless/#uninstall).
Книги для программистов: https://clcks.ru/books-for-it
292
Глава 11
Привилегированные контейнеры
Иногда у контейнера должны быть специальные привилегии (capaЬilities) в ядре
(https://man7.org/linux/man-pages/man7/capabllities.7.html), которые обычно мы
не предоставляем контейнерам. Например, для подкточения USВ-накопителя,
изменения сетевых конфигураций или для создания нового устройства Unix.
В следующем коде мы пьrгаемся изменить МАС-адрес контейнера:
$ docker container run --пn -ti spkane/train-os /bin/bash
[root@280d4dclб407 /]1 ip link ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNКNOWN mode
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT ...
link/ipip О.О.О.О brd О.О.О.О
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop �tate DOWN mode DEFAULT ...
link/tunnel6 :: brd :: permaddr 12b5:6flb:a7e9::
22: eth0@if23: <BROADCAST,МULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
link/ether 02:42:ас:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid О
[root@fc4589fЬ8778 /Jf ip link set eth0 address 02:Оа:03:ОЬ:04:Ос
RТNEТLINK answers: Operation not permitted
[root@280d4dclб407 /]1 exit
Как видите, не работает. Дело в том, что ядро Linux не дает непривилегированному
контейнеру выполнить операцию. Именно этого мы обычно и добиваемся, но если
в данном случае мы хотим, чтобы контейнер сделал то, что задумано, самый про­
стой способ повысить его привилегии - запустить его с аргументом --privileged=
true.
Мы не рекомендуем выполнять команду ip link set eth0 address в следующем
примере, поскольку она изменит МАС-адрес сетевого интерфейса контейнера. Мы
показываем это� пример, чтобы объяснить механизм. Выполняйте команду исклю­
чительно на свои страх и риск.
$ docker container run -ti --пn --privileged=true spkane/train-os /bin/bash
[root@BSЗeOefSddбЗ /Jf ip link ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNКNOWN mode
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT ...
link/ipip О.О.О.О brd О.О.О.О
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN mode DEFAULT ...
link/tunnel6 :: brd :: permaddr 12b5:6flb:a7e9::
22: eth0@if23: <BROADCAST,МULTICAST,UP,LOWER UP> mtu 1500 qdisc noqueue
link/ether 02:42:ас:11:00:02 brd ff:ff:ff�ff:ff:ff link-netnsid О
[root@BSЗeOefSddбЗ /]1 ip link set eth0 address 02:Оа:03:ОЬ:04:Ос
[root@BSЗeOefSddбЗ /JI ip link show eth0
26: eth0@if27: <BROADCAST,МULTICAST,UP,LOWER UP> mtu 1500 qdisc noqueue
link/ether 02:Оа:03:ОЬ:04:Ос brd ff:ff:ff�ff:ff:ff link-netnsid О
[root@BSЗeOefSddбЗ /Jf exit
В этом выводе мы уже не получаем ошибку, а запись link/ether для eth0 изменилась.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
293
Проблема с аргументом --privileged=true заключается в том, что мы даем контей­
неру очень широкие привилегии, и в большинстве случаев нам достаточно будет
одной или двух привилегий в ядре.
Если мы изучим наш привилегированный контейнер, то увидим, что предоставлен­
ные ему привилегии никак не связаны с изменением МАС-адреса. Мы можем
делать даже то, что приведет к проблемам в Docker и на хаете. В следующем коде
мы примонтируем раздел диска с базовой хает-системы, выведем все базовые кон­
тейнеры Linux с Docker в системе и изучим некоторые важные файлы:
$ docker container run -ti --rm --privileged=true spkane/train-os /bin/bash
[root@664a896983d7 /]1 пюunt /dev/vdal /mnt && \
ls -F /mnt/docker/containers 1 \
head -n 10
047df420fбdlf227a26667f83e477f608298c25b0cdad2e149a781587aae5ell/
0888b9f97Ыecc4261f637404e0adcc8ef0c8df291Ь87c9160426e42dc9b5dea/
174ea3ec35cd3a576bedбf475b477Ьla474d897ece15acfc46e61685aЬb3101d/
leddad26ee64c4b29eЫ64b71d56d680739922b3538dc8aaбc6966fce61125b0/
22b2aa38a687f423522ddl74fdd85d578eb21c9c8ecl54a0f9b841ld08fбfd4Ь/
23879e3b9cdбa42ale09dc8e96912ad66e80ec09949c744dl177a911322e7462/
266fe7da627d2e8ec5429140487e984c8d5d36a26bb3cc36a88295e38216e8a7/
2cb6223e115c12ae729d968dЬOd2f29a934b4724f0c9536e377e0dЬd566fll02/
306f00e86122b69eeba9323415532a12f88360alббlf445fc7d64c07249eb0ce/
333b85236409f873d07cd47f62ecla987df59f688a20ldf744f40f98b7e4ef2c/
[root@664a896983d7 /]i ls -F /mnt/docker/containers/047d... Sell/
047df420fбdlf227a26667f83e477f608298c25b0cdad2e149a781587aae5ell-json.log
checkpoints/
config.v2. j son
hostconfig.j son
hostname
hosts
mounts/
resolv.conf
resolv.conf.hash
[root@664a896983d7 /]1 cat /mnt/docker/containers/047d...5ell/047...ell-json.log
{"log":"047df420fбdl\r\n","stream":"stdout","time":"2022-09-14T15:18:29.... "}
[root@664a896983d7 /]i exit
� Не меняйте и не удаляйте эти файлы - это может привести к непредсказуемым
.,..
последствиям для контейнеров или базовой системы Linux.
�
Как видите, мы можем выполнять команды и получать доступ к данным, которые
должны быть нам недоступны.
Для изменения МАС-адреса нам достаточно привилегии CAP_NEТ_ADМIN. Вместо того
чтобы предоставлять контейнеру все привилегии, мы можем дать только одну,
запустив контейнер Linux с аргументом --cap-add:
Книги для программистов: https://clcks.ru/books-for-it
294
Глава 11 .
$ docker container run -ti --пn --cap-add=NET_ADMIN spkane/train-os /bin/bash
[root@087c02a3cбe7 /]И ip link show eth0
36: eth0@if37: <BROADCAST,МULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
link/ether 02:42:ас:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid О
[root@087c02a3cбe7 /]И ip link set eth0 address 02:Оа:03:ОЬ:04:Ос
[root@087c02a3cбe7 /]И ip link show eth0
36: eth0@if37: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
link/ether 02:Оа:03:ОЬ:04:Ос brd ff:ff:ff:ff:ff:ff link-netnsid О
[root@087c02a3cбe7 /]И exit
Обратите внимание: теперь мы можем изменить МАС-адрес, но не можем выпол­
нять команду mount в контейнере:
$ docker container run -ti --пn --cap-add=NET_ADMIN spkane/train-os /bin/bash
[root@b84a06ddaa0d /]И mount /dev/vdal /mnt mount: /mnt: permission denied.
[root@b84a06ddaa0d /]И exit
Мы также можем отнять у контейнера определенные привилегии. Допустим, по
соображениям безопасности мы хотим отключить tcpduпp во всех контейнерах, но
при тестировании контейнеров мы обнаруживаем, что команда tcpduпp установлена
и ее легко можно выполнять:
$ docker container run -ti --пn spkane/train-os:latest tcpduпp -i eth0
dropped privs to tcpduпp
tcpduпp: verbose output suppressed, use -v[v] ... for full protocol decode
listening on eth0, link-type ENl0MB (Ethernet), snapshot length 262144 bytes
15:40:49.847446 IP6 fe80::23:6cff:fed6:424f > ff02::16: НВН ICMP6, ...
15:40:49.913977 ARP, Request who-has _gateway tel1 5614703ffee2, length 28
15:40:49.914048 ARP, Request who-has _gateway tel1 5614703ffee2, length 28
15:40:49.914051 ARP, Reply _gateway is-at 02:49:9b:d9:49:4e (oui Unknown),
15:40:49.914053 IP 5642703bbff2.45432 > 192.168.75.8.domain: 44649+ PTR? ...
Мы можем удалить tcpduпp из образов, но не можем помешать кому-то установить
утилиту обратно. Самый эффективный способ решить эту проблему - определить,
какая привилегия нужна для выполнения tcpduпp, и удалить эту привилегию для
контейнера. Для этого можно добавить аргумент --cap-drop=NET_RAW в команду docker
container run:
$ docker container run -ti --пn --cap-drop=NET_RAW spkane/train-os:latest \
tcpduпp -i eth0
tcpduпp: eth0: You don't have permission to capture on that device
(socket: Operation not permitted)
Задавая аргументы --cap-add и --cap-drop для docker container run, мы можем управ­
лять отдельными привилегиями в ядре Linux (https://man7.orgЛinux/man-pages/
man7/capabllities.7.html).
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
295
Имейте в виду, что отдельные привилегии Linux не только разрешают определен­
ные системные вызовы, но и могут предоставлять другие возможности. Например,
просмотр всех устройств в системе или возможность менять системное время.
Secure Computing Mode
В версии ядра Linux 2.6.12, выпущенной в 2005 году, появилась новая функция
безопасности - Secure Computing Mode, или sессощ:,. Этот механизм позволяет
односторонне перевести процесс в состояние, в котором ему будет разрешено
делать только системные вызовы exit(), sigreturn() и read() или write() по дескрип­
торам уже открытых файлов.
Расширение sессощ:,, называемое sессощ:,-Ьрf, использует правила Berkeley Packet
Filter (BPF) (https://www.kernel.org/doc/Documentation/networking/filter.txt) для
Linux, позволяя нам создать политику с явным списком системных вызовов, кото­
рые процесс может выполнять в режиме Secure Computing Mode. С помощью
sессощ:,-Ьрf в Docker мы можем создавать профили с детализированным контролем
системных вызовов для процессов в контейнерах.
По умолчанию все контейнеры используют Secure Computing Mode, и к ним привя­
заны стандартные профили. Вы можете прочитать документацию о Secure
Computing Mode (https://docs.docker.com/engine/security/seccomp) и системных
вызовах в профиле по умолчанию. Также изучите JSON с политикой по умолчанию
(https://github.com/moby/moby/ЫoЬ/master/profiles/seccomp/default.json).
Давайте рассмотрим пример. Используем программу strace для трассировки сис­
темных вызовов, которые делает процесс при попытке размонтировать файловую
систему командой umount.
�
�
Эти примеры нужны для демонстрации - нам не следует размонтировать файло­
вые системы, если мы не уверены, к чему это приведет.
$ docker container run -ti --rm spkane/train-os:latest umount /sys/fs/cgroup
umount: /sys/fs/cgroup: must Ье superuser to Шll!\ount.
$ docker container run -ti --rm spkane/train-os:latest \
strace umount /sys/fs/cgroup
execve("/usr/bin/umount", [ "umount", "/sys/fs/cgroup"], Ox7fff902ddЬe8
= -1 ЕРЕRМ (Operation not permitted)
umount2("/sys/fs/cgroup", О)
=8
write(2, "umount: ", Bumount:
write(2, "/sys/fs/cgroup: must Ье superuse" ...,
45/sys/fs/cgroup: must Ье superuser to unmount.) = 45
write(2, "\n", 1
dup(l)
close(З)
dup(2)
= 1
= 3
= о
= 3
Книги для программистов: https://clcks.ru/books-for-it
296
Глава 11
close(3)
exit_group(32)
+++ exited with 32 +++
= О
?
Мы уже знаем, что команды монтирования не работают в контейнере со стандарт­
ными разрешениями, и из-за strace мы получим ошибку "операция не разрешена",
когда команда umount попытается использовать системный вызов umount2.
Эrо можно исправить, дав контейнеру привилегию SYS_домш:
$ docker container run -ti --rm --cap-add=SYS_ADMIN spkane/train-os:latest \
strace umount /sys/fs/cgroup
execve("/usr/Ьin/umount", ["umount", "/sys/fs/cgroup"], 0x7ffd3e4452b8
umount2("/sys/fs/cgroup", О)
dup(l)
close(3)
dup(2)
close(3)
exit_group(0)
+++ exited with О+++
= о
= 3
= о
= 3
= о
?
Не забывайте, что параметр --cap-add=SYS_ADMIN позволяет выполнять разные дейст­
вия, в том числе монтировать системные разделы следующим образом:
$ docker container run -ti --rm --cap-add=SYS_ADMIN spkane/train-os:latest \
mount /dev/vdal /mnt
Эгу проблему можно решить более эффективно с помощью профиля sессопр. В от­
личие от sессопр, аргумент --cap-add разрешает много системных вызовов и дает
лишние привилегии. Привилегия САР_SYS_ADMIN дает особенно много нежелательных
разрешений. Профиль sессопр позволяет точно указать, какие системные вызовы
следует включить или отключить.
В профиле sессопр содержится примерно следующее:
"defaultAction":
"SCMP_ACT_ERRNO",
1,
"defaultErrnoRet":
"archМap": [
"architecture": "SCMP_ARCH_Х86_64",
"suЬArchitectures": "SCMP_ARCH_X86", "SCMP ARCH ХЗ2"
),
],
"syscalls":
"names":
"accept",
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
297
"accept4",
11
access 11 ,
"adjtimex",
],
"waitid",
"waitpid",
"write",
"writev"
"action": "SCMP АСТ ALLOW"
},
"names":
"bpf",
"clone",
],
"umount2",
"unshare"
"action": "SCMP_АСТ _ALLOW",
"includes": {
"caps": [
"САР SYS ADMIN"
},
В файле JSON мы видим список поддерживаемых архитектур, набор правил по
умолчанию и группу системных вызовов, которые входят в каждую привилегию.
В нашем случае задано действие по умолчанию sсмР_дет_ERRNO, которое генерирует
ошибку при попытке сделать не указанный в списке вызов.
Если вы подробно изучите профиль по умолчанию, то заметите, что привилегия
CAP_SYS_ADMIN управляет доступом к 37 системным вызовам, и это очень много, го­
раздо больше, чем стандартные 4-6 системных вызовов, входящих в большинство
остальных привилегий.
В нашем случае нам требуется особый функционал, предоставляемый CAP_SYS_ADMIN,
но мы хотим запретить остальные системные вызовы. Чтобы добавить только один
нужный системный вызов, мы можем создать политику Secure Computing Mode на
основе предоставляемой в Docker политики по умолчанию.
Для начала извлекаем политику по умолчанию и делаем ее копию:
$ wget https://raw.githuЬusercontent.com/moby/moby/master/profiles/seccomp/default.json
$ ер default.json umount2.json
Книги для программистов: https://clcks.ru/books-for-it
298
Глава 11
URL продолжается на следующей строке, чтобы вписаться в ширину. Возможно,
потребуется пересобрать его и убрать обратные косые черты, чтобы команда
работала в вашей среде.
Редактируем файл, удаляя системные вызовы, которые по умолчанию предоставля­
ет САР_SYS_ADMIN. В этом случае мы оставляем всего два системных вызова, чтобы
команды strace и umount работали корректно.
Нужный раздел файла заканчивается следующим блоком JSON:
"inclucles": {
"caps": [
"САР SYS ADМIN"
Следующий diff показывает точные изменения, которые нужно внести в нашем
случае:
$ diff -u -US default.json umount2.json
diff -u -U5 default.json umount2.json
2022-09-25 13:23:57.000000000 -0700
-- default.json
+++ шnount2.json
2022-09-25 13:38:31.000000000 -0700
@@ -575,34 +575,12 @@
},
"names":
"bpf 11
,
1
"clone
1,
"clone3",
== 11
� fanotify_init
:========
fsconfig 11
:========:::::;;::::::
::::,. fsmount",
11
fsopen",
:= =========:
fspick",
p_dcookie",
"looku
:========::::::=
:::: .. mount",
"mount_setattr",
;::::::::::::;======
::::;,,move_mount",
"name_to_handle_at",
::===========
=;; open_tree
1p
erf_ event_open
:============
::::;,,quotactl
"quotactl_fd 11
:==========: ..
setdomainname",
"sethostname",
:=========
=: setns",
11,
11
,
11
11
11 ,
11,
1
11,
,
11
Книги для программистов: https://clcks.ru/books-for-it
===========:.::
Расширенные концепции
1
299
"syslog",
"umount",
===============::::,,urnount2",
_________"unshare"
+
"umount2"
],
"action": "SCMP_ACT_ALLOW",
"includes":
"caps": [
"САР SYS ADMIN"
Теперь можно протестировать обновленный профиль seccomp и убедиться, что он
может выполнять umount, но не mount:
$ docker container run -ti --rm --security-opt seccomp=umount2.json \
--cap-add=SYS_ADMIN spkane/train-os:latest /bin/bash
[root@15b8a26Ьбcfe /]# strace umount /sys/fs/cgroup
execve("/usr/Ьin/umount", ["umount", "/sys/fs/cgroup"], Ox7ffece9ebc38 ...
close(З)
О
= ?
exit_group(O)
+++ exited with О+++
[root@15b8a26Ьбcfe /]# mount /dev/vdal /mnt
mount: /mnt: permission denied.
[root@15b8a26Ьбcfe /]# exit
Если все пошло по плану, команды strace и umount должны были сработать, а mount нет. В реальных условиях было бы гораздо безопаснее переделать приложение
таким образом, чтобы ему не требовались особые привилегии, но если этого не
избежать, используйте эти инструменты, чтобы защитить контейнеры, не мешая их
работе.
� Вы можете полностью отключить профиль Secure Computing Mode по умолчанию,
задав --security-opt seccomp=unconfined, однако запускать контейнер без ограни­
чений - плохая идея. Такой подход возможен разве что при попытке понять, какие
именно системные вызовы необходимо определить в профиле.
Преимущество Secure Computing Mode в том, что он позволяет нам выбирать воз­
можности контейнера в нижележащ!;)м ядре Linux. Для большинства контейнеров
не нужно настраивать индивидуальные профили, но это очень удобный инстру­
мент, когда мы создаем контейнер с дополнительными полномочиями и хотим из­
бежать лишних рисков для безопасности всей системы.
SELinux и AppArmor
В предыдущих разделах мы видели, как контейнеры используют cgroups и про­
странства имен. SELinux (https://www.redhat.com/en/topics/linux/what-is-selinux)
и AppArmor (https://apparmor.net/) в экосистеме Linux позволяют усилить безо­
пасность контейнеров. В данном разделе мы кратко рассмотрим эти две системы.
SELinux и AppArmor расширяют возможности безопасности в системах Unix.
Книги для программистов: https://clcks.ru/books-for-it
300 1 Глава 11
SELinux разработан Агентством национальной безопасности США, широко ис­
пользуется Red Hat и подцерживает детализированный контроль. AppArmor выпол­
няет почти те же функции, но работать с ним немного удобнее, чем с SELinux.
По умолчанию Docker поставляется с адекватными профилями, подцерживающими
одну из этих систем. Мы можем настраивать эти профили, разрешая или запрещая
те или иные возможности. Если вы используете Docker в рабочей среде, проанали­
зируйте риски, чтобы понять, какие дополнительные факторы нужно учитывать.
Мы кратко рассмотрим преимущества этих систем.
Обе системы обеспечивают ма1:1датный кон_троль доступа - механизм безопасно­
сти, который предоставляет пользователям (инициаторам) в системе права на дос­
туп к ресурсу (целевому объекту). С его помощью можно запретить любым пользо­
вателям, в том числе с правами root, доступ к части системы. Политику можно
применить ко всему контейнеру, чтобы ограничить все процессы. Здесь мы не
будем подробно расписывать конфигурацию этих систем, потому что на это пона­
добилось бы несколько глав. Профили по умолчанию могут, например, блокиро­
вать доступ к частям файловых систем /proc и /sys, которые было бы опасно пре­
доставлять контейнеру, хотя они и находятся в пространстве имен контейнера.
С помощью профилей по умолчанию также можно ограничить область файловой
системы, чтобы контейнеры не могли использовать точки подключения, которые
им нельзя видеть.
Если вы планируете применять контейнеры Linux в рабочей среде, обязательно
присмотритесь к AppArmor или SELinux. В целом обе системы почти одинаковые,
но в контексте Docker у SELinux есть одно неудобное ограничение - он полноцен­
но работает только в системах, которые подцерживают метаданные файловых сис­
тем, а значит, будет совместим не со всеми драйверами хранилища Docker. Для
AppArmor, с другой стороны, не нужны метаданные файловой системы, а значит,
он работает со всеми бэкендами Docker. Выбор между ними зависит от дистрибу­
тива. Возможно, вам придется выбрать бэкенд файловой системы, который под­
держивает используемую систему безопасности.
Демон Docker
С точки зрения безопасности демон Docker и его компоненты - это единственный
новый риск для инфраструктуры. Приложения в контейнерах не представляют бо­
лее серьезных рисков и даже лучше защищены, чем если бы мы развернули их вне
контейнеров. С другой стороны, без контейнеров мы не стали бы запускать dockerd,
демон Docker. Мы можем запустить Docker таким образом, чтобы не открывать
в сеть никакие порты. Это рекомендованный подход, который по умолчанию при­
нят в большинстве установок Docker.
В большинстве дистрибутивов конфигурация по умолчанию изолирует Docker от
сети, предоставляя только локальный сокет Docker. Поскольку мы не можем уда­
ленно администрировать Docker с такими настройками, многие просто добавляют
в конфигурацию нешифрованный порт 2375. Это удобно для начала работы
с Docker, но небезопасно. Не открывайте Docker для внешнего мира без убедительКниги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
301
ной причины. А если открываете, то обеспечьте надежную защиту. Обычно плани­
ровщики работают на каждом узле и взаимодействуют с Docker через сокет домена
Unix, а не через сетевой порт.
Если мы хотим предоставить доступ к демону из сети, то должны принять меры,
чтобы защитить Docker в рабочей среде. В любом случае мы ожидаем, что сам
демон Docker будет устойчив к таким угрозам, как переполнение буфера или
состояние гонки - к двум самым распространенным классам уязвимостей. Это от­
носится к любому сетевому сервису. В демоне Docker риск гораздо выше, посколь­
ку обычно он выполняется от имени root, может запускать в системе что угодно и
у него нет интегрированного управления доступом на основе ролей.
Дriя защиты Docker можно применять те же принципы, что и для других сетевых
демонов, - шифрование трафика и аутентификация пользователей. Зашифровать
в Docker трафик довольно просто, а вот с аутентификацией все сложнее. Если у вас
есть SSL-сертификаты для защиты НТТР-трафика к хостам, например wildсаrd­
сертификаты для домена, используйте TLS для шифрования всего трафика к серве­
рам Docker через порт 2376. Это неплохое начало. Инструкции приведены в доку­
ментации Docker (https://docs.docker.com/engine/security/protect-access).
Настройка аутентификации потребует больше усилий. В Docker нет детализиро­
ванной авторизации - доступ либо предоставляется, либо не предоставляется. Зато
контроль аутентификации с помощью подписанных сертификатов работает до­
вольно эффективно. К сожалению, это означает, что мы не можем просто включить
аутентификацию, не настроив центр сертификации. Если у вашей организации он
уже есть, вам повезло. Управление сертификатами, включая их защиту и распреде­
ление, требует продуманной стратегии. Вьmолните следующие действия:
1. Настройте метод генерации и подписания сертификатов.
2. Создайте сертификаты для сервера и клиентов.
3. Настройте Docker таким образом, чтобы он требовал сертификаты с параметром
--tlsverify.
Подробные инструкции по настройке сервера и клиента, а также простого центра
сертификации см. в документации Docker.
Поскольку это демон, который почти всегда выполняется с привилегиями, и по­
скольку у него есть прямой контроль над приложениями, не стоит открывать Docker
напрямую для Интернета. Если мы хотим взаимодействовать с хостами Docker за
пределами нашей сети, можно использовать VPN или SSН-туннель для доступа
к защищенному инсталляционному серверу uump host).
Расширенная конфигурация
Снаружи Docker выглядит как монолит, но за внешней поверхностью скрывается
много настраиваемых компонентов. Например, бэкенды журналирования, которые
мы рассматривали в разделе "Журншzирование" главы 6. Мы также можем изменить
бэкенд хранилища образов для всего демона, выбрать другую среду выполнения
Книги для программистов: https://clcks.ru/books-for-it
302
1
Глава 11
или настроить отдельные контейнеры таким образом, чтобы они использовали дру­
гую сетевую конфигурацию. Это сложные сценарии, которые нужно очень хорошо
понимать, прежде чем реализовывать. Сначала мы рассмотрим сетевую конфигура­
цию, затем бэкенды хранилища и, наконец, попробуем другую среду выполнения
контейнеров вместо runc, которая поставляется с Docker по умолчанию.
Сети
Мы уже рассматривали сетевые уровни между контейнерами Linux и реальной се­
тью. Давайте подробнее обсудим, как это работает. Docker поддерживает широкий
набор сетевых конфигураций, но мы начнем с настроек по умолчанию. На рис. 11.1
мы видим схему типичного сервера Docker с тремя контейнерами в частной сети
справа. У одного из них есть общедоступный порт (порт ТСР 10520), предостав­
ляемый на сервере Docker. Мы проследим, как входящий запрос попадает в кон­
тейнер Linux и как контейнер Linux может устанавливать исходящее подключение
к внешней сети.
Виртуальная сеть
Сервер Docker
Входящий запрос
к открытому
порту ТСР 10520
NAT
dockerO
172.16.23.7
• • ••
'ii
172.16.23.1
1
1 Исходящий запрос
1 от контейнера
Рис. 11.1. Сеть на типичном сервере Docker
Если в сети у нас есть клиент, который хочет обратиться к серверу nginx, доступно­
му через порт ТСР 80 в контейнере 1, запрос пройдет через интерфейс ethO на сер­
вере Docker. Поскольку Docker знает, что это общедоступный порт, он запустил
экземпляр docker-proxy, чтобы прослушивать порт 10520. Запрос передается в про­
цесс docker-proxy, который перенаправляет его в нужный контейнер по указанному
адресу и порту в частной сети. В обратную сторону трафик по запросу проходит по
тому же маршруту.
Исходящий трафик от контейнера идет по другому пути, в обход docker-proxy. На
этой схеме контейнер 3 хочет обратиться к серверу в Интернете. У него есть адрес
в частной сети 172.16.23.1, а его маршрут по умолчанию - интерфейс dockero
172.16.23.7. Он отправляет трафик по этому адресу. Сервер Docker видит, что это
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
303
исходящий трафик и перенаправление трафика включено. Поскольку виртуальная
сеть является частной, трафик отправляется с общедоступного адреса. Запрос про­
ходит через уровень преобразования сетевых адресов (Network Address Translation,
NAТ) ядра и передается во внешнюю сеть через интерфейс eth0 на сервере. Обрат­
ный трафик проходит по тому же маршруту. NAT работает в одну сторону, так что
контейнеры в виртуальной сети в ответных пакетах будут видеть реальные сетевые
адреса.
Вы могли заметить, что это непростая конфигурация, но с ней Docker выглядит
довольно прозрачным. Кроме того, он предоставляет дополнительную защиту для
стека Docker, поскольку контейнеры используют отдельные пространства имен се­
ти, находятся в отдельных частных сетях и не имеют доступа к таким компонентам,
как шина DBus (Desktop Bus) или iptaЬles в основной системе.
Давайте рассмотрим, что именно происходит. Интерфейсы, которые отображаются
в ifconfig или ip addr show в контейнере Linux, на самом деле представляют собой
виртуальные интерфейсы Ethemet в ядре сервера Docker. Они сопоставляются
с пространством имен сети контейнера и получают имена, которые мы видим внут­
ри контейнера. Давайте посмотрим, что мы получим при выполнении ip addr show
на сервере Docker. Мы сократим выходные данные для ясности и экономии места:
$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNКNOWN group
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 brd 127.255.255.255 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state
link/ether 02:50:00:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 172.16.168.178/24 brd 192.168 .65.255 scope global dynamic ...
valid_lft 4908sec preferred_lft 3468sec
inet6 fe80::50:ff:fe00:l/64 scope link valid_lft forever preferred_lft forever
7: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
link/ether 02:42:9c:d2:89:4f brd ff:ff:ff:ff:ff:ff
inet 172.17.42 .1/16brd 172.17.255 .255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:9cff:fed2:894f/64 scope link
valid_lft forever preferred_lft forever
185: veth772de2a@if184: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
link/ether 9а:а9:24:Ь7:5а:31 brd ff:ff:ff:ff:ff:ff
link-netnsid 1 inet6
fe80::98a9:24ff:feb7:5a31/64 scope link
valid_lft forever preferred_lft forever
Мы видим, что у нас есть нормальный lоорЬасk-интерфейс, настоящий интерфейс
Ethemet eth0, а затем интерфейс моста Docker, docker0, который мы рассматривали
Книги для программистов: https://clcks.ru/books-for-it
304
1
Глава 11
ранее. Оrсюда весь трафик от контейнеров Linux перенаправляется за пределы вир­
rуальной сети. В этих выходных данных обратите внимание на интерфейс
veth772de2a. При создании контейнера Docker создает два вирrуальных интерфейса:
�дин на стороне сервера, прикреплен к мосrу docker0, а другой - прикреплен к про­
странству имен контейнера. Здесь мы видим интерфейс на стороне сервера. Вы за­
метили, что мы не видим IР-адрес? Это потому, что интерфейс привязан к мосrу.
В пространстве имен контейнера у интерфейса будет другое имя.
Как и для многих других компонентов Docker, мы можем выбрать другую реализа­
цию прокси. Можно задать настройку --userland-proxy-path=<path>, но для этого вряд
ли найдутся серьезные причины, если только вы не используете очень специализи­
рованную сеть. Флаг --userland-proxy=false для dockerd полностью отключает userland­
proxy, и трафик между локальными контейнерами будет перенаправляться с помо­
щью функционала Hairpin NAТ (https://www.geeksforgeeks.org/network-address­
translation-nat). Если вам нужны службы с высокой пропускной способностью,
рассмотрите этот подход.
Hairpin NAТ описывает взаимодействие сервисов в сети с NAТ через общедоступ­
ные IР-адреса. Трафик при этом проходит из исходного сервиса в Интернет, до
внешнего интерфейса для маршрутизатора NAT, а затем возвращается в исход­
ную сеть к сервису назначения. Этот U-образный маршрут по форме напоминает
шпильку для волос - hairpin.
Сеть хоста
Реализация по умолчанию выглядит довольно сложно, но мы можем запускать кон­
тейнер без полноценной сетевой конфигурации, создаваемой Docker. docker-proxy
может ограничивать пропускную способность для сервисов, обрабатывающих
большие объемы данных, - весь сетевой трафик может проходить через процесс
docker-proxy до получения контейнером. Что будет, если мы отключим сетевой уро­
вень Docker? В Docker это можно сделать отдельно для каждого контейнера с по­
мощью параметра --net=host в командной строке. Иногда, например, когда мы
запускаем приложения с высокой пропускной способностью, нам это необходимо,
но при этом мы теряем гибкие возможности, предлагаемые Docker. Давайте по­
смотрим, как работает этот механизм.
К этой конфигурации тоже нужно отнестись очень серьезно. Она влияет на работу
и на систему безопасности и, возможно, не подойдет для вашей ситуации. Внима­
тельно изучите все факторы и последствия, прежде чем использовать ее.
Давайте запустим контейнер с параметром --net=host и посмотрим, что получится:
$ docker container run --rm -it --net=host spkane/train-os bash
[root@docker-desktop /]1 docker container run --rm -it --net=host \
spkane/train-os ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNКNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
305
inet 127.0.0.1/8 brd 127.255.255.255 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: eth0: <BROAOCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
state UP group default qlen 1000 link/ether 02:50:00:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.65.3/24 brd 192.168.65.255 scope global dynamic
noprefixroute eth0 valid_lft 4282sec preferred_lft 2842sec
inet6 fe80::50:ff:fe00:l/64 scope link valid_lft forever preferred_lft forever
7: docker0: <NO-CARRIER,BROAOCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
state OOWN group default link/ether 02:42:9c:d2:89:4f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:9cff:fed2:894f/64 scope link valid_lft forever preferred_lft forever
8: br-340323d07310: <NO-CARRIER,BROADCAST,МULTICAST,UP> mtu 1500 qdisc
noqueue state OOWN group default link/ether 02:42:56:24:42:Ь8 brd ff:ff:ff:ff:ff:ff
inet 172.22.0.1/16 brd 172.22.255.255 scope global br-340323d07310
valid_lft forever preferred_lft forever
11: br-0lf7537b9475: <NO-CARRIER,BROAOCAST,MULTICAST,UP> mtu 1500 qdisc
noqueue state OOWN group default link/ether 02:42:ed:14:67:61 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global br-0lf7537b9475
valid_lft forever preferred_lft forever
inet6 fc00:f853:ccd:e793::l/64 scope global valid_lft forever preferred_lft forever
inet6 fe80::42:edff:fel4:6761/64 scope link valid_lft forever preferred_lft forever
inet6 fe80::l/64 scope link valid_lft forever preferred_lft forever
Выглядит знакомо? Дело в том, что когда мы запускаем контейнер с сетью Host,
контейнер выполняется пространствах имен сети и UTS, в которых находится сам
хост. Имя хоста сервера docker-desktop, и в командной оболочке мы видим, что
у контейнера то же имя хоста:
[root@docker-desktop /]# hostname docker-desktop
Если мы выполним команду mount и посмотрим, что подключено, то увидим, что
в Docker по-прежнему присутствуют каталоги /etc/resolv.conf, /etc/hosts и
/etc/hostname. Как и ожидалось, каталог /etc/hostname содержит только имя хоста
сервера:
[root@docker-desktop /]# mount
overlay оп/ type overlay (rw,relatime,lowerdir;/var/liЬ/docker/overlay2/...)
/dev/vdal оп/etc/resolv.conf type ext4 (rw,relatime)
/dev/vdal оп/etc/hostname type ext4 (rw,relatime)
/dev/vdal оп/etc/hosts type ext4 (rw,relatime)
[root@docker-desktop /]# cat /etc/hostname docker-desktop
Чтобы убедиться, что мы видим все нормальные сети на сервере Docker, давайте
изучим выходные данные ss и посмотрим, доступны ли нам сокеты, используемые
Docker:
Книги для программистов: https://clcks.ru/books-for-it
306
Глава 11
root@852dl8f5c38d:/# ss I grep docker
и str ЕSТАВ О О /run/guest-services/docker.sock 18086 * 16860
и str ЕSТАВ О О /var/run/docker.sock
�
�
21430 * 21942
Если бы демон Docker прослушивал ТСР-пот, например 2375, его тоже можно
было бы увидеть. Найдите другой ТСР-порт, если известно, что он используется.
Если мы поищем docker в выходных данных обычного контейнера в своем про­
странстве имен, то не получим никаких результатов:
$ docker container run --rm -it spkane/train-os bash -с
"ss I grep docker"
Итак, мы действительно находимся в пространстве имен сети сервера. Это значит,
что если мы собираемся запускать сетевой сервис с высокой пропускной способно­
стью, мы можем ожидать приличную производительность сети по умолчанию.
С другой стороны, мы можем попытаться подключиться к портам, которые кон­
фликтовали бы с портами на сервере, поэтому нужно проявлять особую осторож­
ность при назначении портов.
Конфигурирование сетей
Мы можем использовать не только сеть по умолчанию или сеть хоста. Команда
docker network позволяет создавать несколько сетей на разных драйверах. Кроме
того, с ее помощью мы можем просматривать и контролировать сетевые уровни
Docker, а также их подключение к контейнерам, запущенным в системе.
Мы можем просмотреть все сети, доступные в Docker, следующей командой:
$ docker network 1s
SCOPE
DRIVER
NETWORК ID
NАМЕ
local
bridge
bridge
5840а6с23373
local
lc22b4582189
host
host
с128ЬfdЬеООЗ
none
null
local
Мы также можем посмотреть детали отдельных сетей командой docker network
inspect, указав идентификатор сети:
$ docker network inspect 5840а6с23373
"Name": "bridge",
"Id": "5840... fc94",
"Created": "2022-09-2ЗТО1: 21: 55.6979079582",
"Scope": "local",
"Driver": "bridge",
"EnaЬleIPvб": false,
"!РАМ": {
"Driver": "default",
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
307
"Options": null,
"Config": [
"SuЬnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
},
"Internal": false,
"AttachaЬle": false,
"Ingress": false,
"ConfigFrom": {
"Network" :
1111
},
"ConfigOnly": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enaЫe_icc": "true",
"com.docker.network.bridge.enaЫe_ip_masquerade": "true",
"com.docker.network.bridge.host_Ьinding_ipv4": "О.О.О.О",
"com.docker.network.bric\ge.name": "dockerO",
"com.docker.network.driver.mtu": "1500"
},
"LaЬels": {)
Сети Docker можно создавать и удалять, а также подкточать и откточать от от­
дельных контейнеров с помощью подкоманды network.
Итак, у нас есть сеть в режиме Host, сеть в режиме None и сеть в режиме Bridge
с Hairpin NAТ. Нам доступны другие режимы, с помощью которых можно созда­
вать различные топологии в Docker. Самые распространенные из них - overlay и
macvlan. Давайте рассмотрим их возможности:
♦ overlay - этот драйвер используется в режиме Swarm для создания оверлейной
сети между хостами Docker, чтобы установить частную сеть между всеми кон­
тейнерами поверх реальной сети. Это удобно для Swaпn, но не применяется
в других случаях.
♦ macvlan - этот драйвер создает реальный МАС-адрес для каждого контейнера и
предоставляет его по сети с помощью выбранного интерфейса. Для этого ком­
мутатор должен поддерживать более одного МАС-адреса на физический порт.
В результате все контейнеры будут видимы напрямую в базовой сети. При пере­
ходе с традиционной системы на контейнеры это может быть очень удобно.
У такого подхода есть свои недостатки, например при отладке сложнее опреде­
лить, с какого хоста поступает трафик, есть риск переполнения таблиц МАС­
адресов на коммутаторах, чрезмерного использования ARP хостами и других
Книги для программистов: https://clcks.ru/books-for-it
308
Глава 11
проблем в базовой сети. Поэтому драйвер macvlan целесообразен только в тех
случаях, когда вы хорошо понимаете, как работает нижележащая сеть и как ею
управлять.
Здесь возможно несколько конфигураций, но базовый вариант настроить довольно
просто:
$ docker network create -d macvlan \
--suЬnet= 172.16.16.0/24 \
--gateway=172.16.16.1 \
-о parent=eth0 ourvlan
$ docker network 1s
NETWORК ID
NАМЕ
DRIVER
SCOPE
5840абс23373
bridge
local
bridge
lc22b4582189
host
host
local
с128ЬfdЬеООЗ
none
null
local
8218с0есс9е2
ourvlan
macvlan
local
$ docker network rm 8218с0есс9е2
Мы можем з�претить Docker назначать конкретные адреса, указывая их как имено­
� ванные дополнительные адреса: --aux-address= "my- router= 172.16.16.129".
-
На сетевом уровне Docker можно многое настроить под конкретный вариа·нт при­
менения, но обычно все оставляют конфигурацию по умолчанию с сетью хоста и
пользовательским режимом без прокси. Мы можем настроить для контейнеров
имена серверов DNS, параметры преобразователя, шлюзы по умолчанию и др.
В разделе о сетях в документации Docker (https://docs.docker.com/network) под­
робно описаны некоторые конфигурации.
Для расширенной сетевой конфиrурации в Docker изучите Weave - хорошо под­
держиваемый инструмент оверлейных сетей для запуска контейнеров на несколь­
ких хостах Docker (https://github.com/weaveworks/weave). Он похож на драйвер
overlay, но мы можем настроить гораздо больше параметров и обойтись без
Swarm. Еще одно решение - Project Calico (https://www.tigera.io/project-calico).
Если вы используете Kubernetes, у которого своя конфиrурация сети, изучите
Container Network lnterface (CNI) (https://www.cni.dev/), а также Cilium (https://
cilium.io/), которые предоставляет надежные сети на базе eBPF для контейнеров.
Хранилище
За чтение и запись всех данных для образов и контейнеров на сервере Docker отве­
чает бэкенд хранилища. Docker предъявляет к нему довольно строгие требования:
хранилище должно поддерживать слои, с помощью которых Docker отслеживает
изменения и контролирует занимаемое пространство и объем данных, передавае­
мых при развертывании новых образов. Благодаря механизму копирования при за­
писи Docker может запустить новый контейнер из имеющегося образа, не копируя
образ целиком, потому что хранилище поддерживает эту возможность. Благодаря
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
309
бэкенду хранилища мы можем экспортировать образы в виде групп изменений
в слоях, а также сохранять состояние работающих контейнеров. В большинстве
случаев нам потребуется помощь ядра, чтобы эффективно выполнять эти задачи,
потому что контейнер видит объединение всех слоев под ним, которые не копиру­
ются в сам контейнер, а просто предоставляются ему. Только когда мы вносим из­
менения, в файловую систему контейнера что-то записывается. Мы видим эти слои,
когда загружаем или выгружаем новый образ из реестра, вроде Docker Hub. Демон
Docker извлекает и отправляет каждый слой по отдельности, а если некоторые слои
совпадают с уже сохраненными, он задействует кешированный слой. При отправке
в реестр мы иногда даже видим, к какому образу относится слой.
Docker работает со слоями с помощью нескольких драйверов ядра. Кодовая база
Docker содержит код, который реализует взаимодействие со многими бэкендами,
и мы можем выбрать, какой из них будет использоваться при перезапуске демона.
Давайте рассмотрим преимущества и недостатки доступных вариантов.
У решений есть свои ограничения, и вам следует изучить их, чтобы подобрать под­
ходящий вариант. В некоторых случаях выбор зависит от установленного дистри­
бутива Linux. Проще всего использовать драйверы, встроенные в ядро вашего
дистрибутива, - всегда лучше придерживаться хорошо протестированных реше­
ний. Обычно ,следует выбирать варианты, которые лучше всего поддерживаются,
чтобы проблем возникало как можно меньше. Кроме того, разные бэкенды показы­
вают разную статистику через Docker Remote API (конечная точка /info) для отсле­
живания систем Docker. Давайте сравним доступные варианты файловых систем:
♦ Overlay
Overlay (ранее OverlayFS) - это объединенная файловая система, в которой
несколько слоев группируются таким образом, что выглядят как единая файло­
вая система (https://www.kerneJ.org/doc/htmlЛatest/filesystems/overlayfs.html).
Overlay, пожалуй, лучший выбор для Docker, совместимый с большинством ос­
новных дистрибутивов. Если вы используете ядро Linux старше версии 4.0 (или
3.10.0-693 для RНEL), это решение вам не подойдет. Эта файловая система на­
столько надежная и производительная, что ради нее стоит обновить операцион­
ную систему для хостов Docker, даже если компания использует более старый
дистрибутив. Файловая система Overlay входит в мейнлайновое ядро Linux, а
значит, это очень стабильное решение с гарантированной долгосрочной под­
держкой. Docker поддерживает две версии Overlay: overlay и overlay2. Как можно
догадаться, лучше использовать overlay2, потому что это более быстрая, надеж­
ная и эффективная версия с поддержкой inode.
Сообщество Docker часто совершенствует подцержку различных файловых сис­
тем. Дополнительную информацию о подцерживаемых файловых системах
см. в официальной документации (https://docs.docker.com/storage/storagedriver).
♦ AuFS
На момент написания этой книги использовать aufs уже не рекомендуется,
но все-таки это исходный бэкенд для Docker. AuFS (Advanced multilayered
unification filesystem, https://aufs.sourceforge.net/) - это объединенная файлаКниги для программистов: https://clcks.ru/books-for-it
310
Глава 11
вая система, поддерживающаяся многими популярными дистрибутивами Linux.
Она никогда не входила в основную ветвь ядра, а потому доступна не во всех
дистрибутивах. Например, она не поддерживается в новых версиях Red Hat и
Fedora. Она не поставляется в стандартном дистрибутиве Ubuntu, но входит
в пакет Ubuntu linux-image-extra.
Из-за отсутствия популярности этого бэкенда были разработаны другие вариан­
ты. Если у вас старые дистрибутивы, которые поддерживают AuFS, то вы може­
те рассмотреть этот вариант, но лучше все же обновить ядро до версии, которая
поддерживает Overlay или Btrfs.
♦ btrfs
B-Tree File System (Btrfs, https://Ьtrfs.wiki.kernel.org/index.php/Мain_Page)­
этo файловая система с копированием при записи, а значит, она хорошо подхо­
дит для образов Docker. Как и aufs, но, в отличие от devicemapper, Docker широко
использует этот бэкенд, так что он отличается стабильностью и производитель­
ностью в рабочей среде. Он поддерживает до нескольких тысяч контейнеров
в одной системе. Недостаток для систем Red Hat - Btrfs не поддерживает
SELinux. Система btrfs - это второй оптимальный вариант после драйвера
overlay2. Популярный способ использовать бэкенды btrfs для контейнеров Linux,
при этом не выделяя файловой системе целый том, - упаковать Btrfs в файл и
примонтировать файл как блочное устройство: mount -о loop file.Ьtrs /mnt. С по­
мощью этого метода можно создать файловую систему для контейнеров Linux
на 50 ГБ, не отдавая все ценное локальное хранилище драйверу Btrfs.
♦ Device Mapper
Изначально Device Mapper был создан Red Hat для их дистрибутивов, которые
не поддерживали AuFS в первое время существования Docker, а сейчас это
бэкенд по умолчанию для всех дистрибутивов Linux на базе Red Hat. В некото­
рых версиях Red Hat Linux это может быть единственным доступным вариан­
том. Device Mapper уже давно встроен в ядро Linux и работает очень стабильно.
Демон Docker использует его немного нетрадиционно, и раньше этот вариант не
отличался стабильностью. Из-за сомнительного прошлого мы рекомендуем по
возможности выбирать другие бэкенды. Если ваш дистрибутив поддерживает
только драйвер devicemapper, ничего страшного, но в других ситуациях стоит
предпочесть overlay2 или Ьtrfs. По умолчанию devicemapper задействует режим
loop-lvm без конфигурации. Он работает медленно и обычно подходит только для
разработки. Если вы выберете драйвер devicemapper, убедитесь, что он использует
· режим direct-1 vm за пределами среды разработки.
Больше о режимах devicemapper в Docker см. в официальной документации (https://
docs.docker.com/storage/storagedriver/device-mapper-driver). В статье блога,
опубликованной в 2014 году, описана история разных бэкендов хранилища Docker
(https://developers.redhat.com/Ыog/2014/09/30/overview-storage-scalabllity-docker).
♦ VFS
Среди поддерживаемых драйверов Virtual File System {vfs) самый простой
(и медленный) для запуска. Он не поддерживает копирование при записи, а вмеКниги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
З11
сто этого создает новый каталог и копирует все имеющиеся данные. Изначально
он предназначался для тестирования и монтирования томов хоста. Драйвер vfs
заметно замедляет создание новых контейнеров, зато работает быстро в рантай­
ме. Это простой механизм, в котором мало что может пойти не так. Компания
Docker, Inc. не рекомендует использовать его в рабочей среде, так что будьте
осторожны, если выберете его для реальных условий.
♦ ZFS
Файловая система ZFS, созданная Sun Microsystems, - это самая продвинутая
файловая система с открытым исходным кодом для Linux. Из-за ограничений
лицензирования она не поставляется в основной ветке Linux, зато благодаря
проекту ZFS on Linux (https://zfsonlinux.org/) довольно легко устанавливается.
Docker может работать поверх файловой системы ZFS и использовать расши­
ренные возможности копирования при записи для реализации работы со слоями.
Учитывая, что ZFS не находится в мейнлайновом ядре и не поставляется
с большинством крупных коммерческих дистрибутивов, при выборе этого вари­
анта потребуются дополнительные усилия. Если вы уже применяете ZFS в рабо­
чей среде, для вас это может стать лучшим выбором.
От бэкенда хранилища зависит производительность контейнеров. Если мы поме­
няем бэкенд на сервере Docker, все имеющиеся образы хотя и не пропадут, но
станут невидимыми, пока мы не включим этот бэкенд снова. Будьте осторожны.
Команда docker system info показывает, какой бэкенд хранилища сейчас использует­
ся в системе:
$ docker system info
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
userxattr: false
Docker показывает базовую файловую систему, если она есть. Здесь мы видим
драйвер overlay2, и он поддерживается файловой системой ext. В некоторых случа­
ях, например при использовании devicemapper на разделах raw или с btrfs, отдельной
базовой файловой системы не будет.
Бэкенды хранилища можно менять с помощью файла конфигурации daemon-json или
аргументов командной строки с командой dockerd при запуске. Если мы хотим
в системе Ubuntu перейти с aufs на devicemapper, то можем выполнить следующую
команду:
$ dockerd --storage-driver=devicemapper
Это сработает почти в любой системе Linux, которая поддерживает Docker, потому
что devicemapper есть почти везде. То же самое относится к overlay2 в современных
Книги для программистов: https://clcks.ru/books-for-it
312
Глава 11
ядрах Linux, но для вкточения других драйверов понадобятся дополнительные ме­
ханизмы. Например, без aufs в ядре (обычно в виде модуля) Docker не запустится,
если для драйвера хранилища выбран aufs. Это относится также к Btrfs и ZFS.
Выбрать подходящий драйвер хранилища для конкретной системы и развертыва­
ния - это одно из самых важных решений, которые необходимо принять при пла­
нировании Docker для рабочей среды. Придерживайтесь консервативного подхода:
выберите решение, которое хорошо поддерживается вашим ядром и дистрибути­
вом. Раньше это было довольно серьезной проблемой, но сейчас большинство
драйверов достигли неплохой зрелости. Проявляйте осторожность, эксперименти­
руя с новыми бэкендами. Как показывает опыт, драйверам хранилища требуется
некоторое время, прежде чем их можно будет спокойно использовать в рабочих
системах.
nsenter
Утилита nsenter (namespace enter - вход в пространство имен) позволяет входить
в любое пространство имен Linux. Эта утилита включена в пакет uf.il-linux
с kernel.org (bttps://mirrors.edge.kernel.org/pub/linux/utils/util-linux). С помощью
nsenter мы можем войти в контейнер Linux с самого сервера, даже если сервер
dockerd не отвечает и нам не доступна команда docker container ехес. Мы также
можем использовать его, чтобы выполнять задачи в контейнере с правами root на
сервере, даже если docker container ехес нам этого не позволяет. Это бывает очень
полезно при отладке. Обычно нам вполне достаточно docker container ехес, но nsenter
все равно должен быть под рукой.
Большинство дистрибутивов Linux поставляются с достаточно новым пакетом util­
linux, который содержит nsenter. Если в вашем дистрибутиве его нет, самый простой
способ получить nsenter - установить его через сторонний контейнер Linux
(https://github.com/jpetazzo/nsenter).
Контейнер извлекает образ Docker из реестра Docker Hub, а затем выполняет
контейнер Linux, который установит инструмент командной строки nsenter
в /usr/locaVЬin. Поначалу это может показаться странным, но это удобный способ
удаленно устанавливать nsenter на любом сервере Docker с помощью одной коман­
ды docker.
В отличие от docker container ехес, которую можно выполнять удаленно, nsenter ра­
ботает только на самом сервере, напрямую или через контейнер. В нашем примере
мы предусмотрели для nsenter специальный контейнер. Как и в примере с docker
container ехес, нужно запустить контейнер:
$ docker container run -d --пn uЬuntu:22.04 sleep 600
fd521174d66dc32650dl65e0ce7dd97255c7b3624c34cЫdll9d955284382ddf
docker container ехес работает просто, но nsenter не так удобен, потому что ему ну­
жен PID фактического верхнеуровневого процесса в контейнере, а его еще нужно
найти. Давайте запустим nsenter вручную и посмотрим, что происходит.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
313
Сначала нужно найти ID выполняющегося контейнера, потому что иначе nsenter не
сможет к нему обратиться. Для этого выполним команду docker container ls:
$ docker container ls
CONTAINER ID
fd521174d66d
СОММАND
IМAGE
uЬuntu:22.04 "sleep 1000"
NAМES
angry_albattani
Нужный ID находится в первом поле - fd521174d66d. Теперь найдем PID:
$ docker container inspect --format \{{.State.Pid\}} fd521174d66d 2721
Мы также можем получить реальный PID процессов в контейнере, выполнив ко­
манду docker container top с указанием ID контейнера. В нашем примере он будет
выглядеть следующим образом:
$ docker container top fd521174d66d
UID PID
PPID С STIME ТТУ TIME
CMD
root 2721 2696 О 20:37? 00:00:00 sleep 600
Измените аргумент --target в следующей команде, указав идентификатор процесса,
полученный в предыдущей команде, и вызовите nsenter:
$ docker container run --rm -it --privileged --pid=host debian \
nsenter --target 2721 --all
# ps -ef
ТIМЕ CMD
PPID
С STIME ТТУ
PID
UID
root
root
root
# exit
1
11
15
о
о
11
О 20:37?
О 20:51?
О 20:51?
00:00:00 sleep 600
00:00:00 -sh
00:00:00 ps -ef
Результат очень похож на то, что мы получили бы с помощью docker container ехес,
потому что, по сути, он делает то же самое.
Аргумент командной строки --all указывает nsenter, что мы хотим войти во все
пространства имен, используемые процессом, который указан в --target.
Отладка контейнеров без командной оболочки
Если нам нужно провести диагностику в контейнере, у которого нет командной
оболочки Unix, придется приложить дополнительные усилия. Например, мы можем
запустить контейнер, в котором только один исполняемый файл:
$ docker container run --rm -d --name outyet-small \
--puЬlish mode=ingress,puЬlished=8090,target=8080 \
spkane/outyet:1.9.4-small 4f6de24d4c9c794c884afa758ef5b33ea38c0lf8ec9314dcddd9fadc25cla443
Давайте посмотрим на процессы, которые выполняются в этом контейнере:
$ docker container top outyet-small
UID PID
PPID
С
STIME ТТУ
root 61033
61008 О
22:43 ?
TIME
00:00:00
CMD
/outyet -version 1.9.4 -poll 600s ...
Книги для программистов: https://clcks.ru/books-for-it
314
Глава 11
Если мы попробуем запустить командную оболочку Unix в контейнере, то получим
ошибку:
$ docker container ехес -it outyet-small /bin/sh
OCI runtime ехес failed: ехес failed: unaЬle to start container process: ехес:
"/Ьin/sh": stat /Ьin/sh: no such file or directory: unknown
Затем мы можем запустить второй контейнер, с командной оболочкой, и другие
полезные инструменты, чтобы новый контейнер видел процессы в первом кон­
тейнере, использовал тот же сетевой стек, что и первый контейнер, а также полу­
чил несколько дополнительных привилегий, которые будут необходимы при от­
ладке:
$ docker container run --rm -it --pid=container:outyet-small \
--net=container:outyet-small --cap-add sys_ptrace \
--cap-add sys_admin spkane/train-os /Ьin/sh
sh-5.1#
Если мы введем 1s в этом контейнере, то увидим в файловой системе образ
spkane/train-os, который содержит /Ьin/sh и все отладочные инструменты, но не
включает никакие файлы из контейнера outyet-small:
sh-5.1* 1s
Ьin dev home lib64
media opt root sЬin sys usr
boot etc lib lost+found mnt
proc run srv tmp var
Если мы введем ps -ef, то увидим все процессы исходного контейнера, потому что
мы указали Docker использовать пространство имен контейнера outyet-small, пере­
дав аргумент --pid=container:outyet-small:
sh-5.1# ps -ef
UID
root
root
root
PID
1
29
36
PPID
о
о
29
с
о
о
о
STIME
22:43
22:47
22:49
ТТУ
?
pts/0
pts/0
TIME
00:00:00
00:00:00
00:00:00
CMD
/outyet -version 1.9.4 -poll 600s . . .
/Ьin/sh
ps -ef
Поскольку мы используем тот же сетевой стек, можно даже указать в curl порт,
к которому привязан сервис outyet первого контейнера:
sh-5.1# curl localhost:8080
<!DOCTYPE html><html><Ьody><center>
<h2>Is Go 1.9.4 out yet?</h2>
<hl>
<а href="https://go.googlesource.com/go/+/gol.9.4">YES!</a>
</hl>
<p>Hostname: 155914f7cбcd</p>
</center></Ьody></html>
Сейчас можно использовать strace или другие инструменты для отладки приложе­
ния, а затем выйти (exit) из нового контейнера отладки, при этом исходный кон­
тейнер продолжит выполняться.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
�
�
З 15
Когда закончите, нажмите <Ctrl>+<C>, чтобы выйти из процесса strace, если вы
использовали именно его.
sh-5.1# strace -р 1
strace: Process 1 attached
futex(0x963698, FUTEX_WAIT, О, NULL л Cstrace: Process 1 detached
<detached ...>
sh-5.1# exit exit
В этом примере у нас не было доступа к файловой системе. Если мы хотим посмот­
реть или скопировать файлы из контейнера, можно выполнить команду docker
container export, чтобы экспортировтаь ТАR-файл с файловой системой контейнера:
$ docker container export outyet-small -о export.tar
Затем можно с помощью tar посмотреть или извлечь файлы:
$ tar -tvf export.tar
-rwxr-xr-x
drwxr-xr-x
-rwxr-xr-x
drwxr-xr-x
drwxr-xr-x
drwxr-xr-x
-rwxr-xr-x
-rwxr-xr-x
lrwxrwxrwx
-rwxr-xr-x
_drwxr-xr-x
drwxr-xr-x
-rw-r--r--
-rwxr-xr-x
drwxr-xr-x
drwxr-xr-x
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
о
Jul
Jul
Jul
Jul
Jul
Jul
Jul
Jul
Jul
Jul
Apr
17
17
17
17
17
17
17
17
17
17
24
Apr 24
261407 Mar 13
5640640 Apr 24
Jul 17
о
Jul 17
о
16:04
16:04
16:04
16:04
16:04
16:04
16:04
16:04
16:04
16:04
2021
2021
2018
2021
16:04
16:04
.dockerenv
dev/
dev/console
dev/pts/
dev/shm/
etc/
etc/hostname
etc/hosts
etc/mtab -> /proc/mounts
etc/resolv.conf
etc/ssl/
etc/ssl/certs/
etc/ssl/certs/ca-certificates.crt
outyet
proc/
sys/
Когда закончите, удалите export.tar и остановите контейнер outyet-small командой
docker container stop outyet-small.
Мы можем исследовать файловую систему контейнера с сервера Docker, напря­
мую перейдя к ее расположению в системе хранения сервера. Обычно это будет
путь /var/liЫ docker/overlay/fd5... но все зависит от установки Docker, бэкенда хра­
� нилища и хеша контейнера. Мы можем определить корневой каталог Docker с по­
мощью команды docker system info.
Структура Docker
Docker состоит из пяти компонентов на стороне сервера, которые предоставляют
единую точку взаимодействия через API. Эти компоненты: dockerd, containerd, runc,
containerd-shim-runc-v2 и docker-proxy, который мы рассматривали в разделе "Сети"
Книги для программистов: https://clcks.ru/books-for-it
316
Глава 11
данной главы. Мы рассмотрели много примеров работы с dockerd и его API. Он от­
вечает за оркестрацию всех компонентов, составляющих Docker. При запуске кон­
тейнера Docker использует containerd для работы с экземплярами контейнеров.
Раньше все эти задачи выполнял сам процесс dockerd, но у такого подхода было
несколько недостатков:
♦ У dockerd было слишком много заданий.
♦ Монолитная среда исполнения не позволяла легко менять компоненты.
♦ dockerd приходилось контролировать жизненный цикл самих контейнеров, и его
нельзя было перезапустить или обновить без потери всех запущенных контей­
неров.
Еще одна причина, по которой пришлось реализовать containerd, заключается в том,
что контейнеры не представляют собой единую абстракцию. На платформах Linux
это процессы, которые включают пространства имен, cgroups и правила безопасно­
сти в AppArmor или SELinux. Кроме того, Docker может работать в Windows и,
возможно, на других платформах в будущем. Компонент containerd представляет
внешнему миру стандартный уровень, не зависящий от реализации, так что разра­
ботчики имеют дело с концепциями высокого уровня, вроде контейнеров, задач и
снапшотов, а не с отдельными системными вызовами Linux. Этот подход заметно
упрощает демон Docker и позволяет таким платформам, как Kubemetes, напрямую
интегрироваться в containerd, без необходимости в Docker API. Kubemetes много лет
использовал оболочку (shim) Docker, но сейчас задействует containerd напрямую.
На рис. 11.2 представлены все компоненты Docker, и мы рассмотрим их подробнее:
♦
dockerd - один на сервер. Предоставляет API, собирает образы контейнеров,
обеспечивает высокоуровневое сетевое управление, включая тома, журналиро­
вание, статистические отчеты и т. д.
♦ docker-proxy - один на правило проброса портов. Каждый экземпляр отвечает за
перенаправление трафика по определенному протоколу (TCP/UDP) с указанного
IР-адреса и порта хоста на указанный IР-адрес и порт контейнера.
dockerd-proxy
dockerd
dockerd-proxy
,,------
'1
1
1
1
containerd-shim-runc-v2
containerd-shim-runc-v2
containerd-shim-runc-v2
контейнер 1
контейнер 2
контейнер З
Рис. 11.2. Структура Docker
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
317
♦ containerd - один на сервер. Управляет жизненным циклом, исполнением, фай­
ловой системой с копированием при записи, а также низкоуровневыми сетевыми
драйверами.
♦ containerd-shim-runc-v2 - один на контейнер. Отвечает за дескрипторы файлов,
переданные контейнеру (например, stdin/out), и сообщает статус выхода.
♦ runc - создает контейнер и выполняет его, собирает статистику и докладывает
о событиях жизненного цикла.
Компоненты dockerd и containerd взаимодействуют друг с другом через сокет, обыч­
но сокет Unix, используя gRPC API (https://grpc.io/). В этой паре dockerd будет кли­
ентом, а containerd- сервером. runc- это инструмент CLI, который считывает
конфигурацию из файла JSON на диске и выполняется с помощью containerd.
При запуске нового контейнера dockerd проверяет, присутствует ли образ, или
извлекает его из репозитория, указанного в имени образа (в будущем эту задачу
может взять на себя containerct, который уже поддерживает извлечение образов).
Демон Docker отвечает за большую часть остальных задач по созданию контейнера,
например запуск docker-proxy для настройки проброса портов. Затем он отправляет
containerd запрос на запуск контейнера. Containerd на основе образа применяет кон­
фигурацию, переданную от dockerd, чтобы создать комплект (bundle) OCI (https://
www.opencontainers.org/), который можно выполнить с помощью runc.
Цитата с сайта OCI: "Open Container lnitiative (OCI) - это легкая и открытая систе­
ма управления (проект), образованная под эгидой Linux Foundation с целью созда­
ния открытых отраслевых стандартов для форматов и сред выполнения контейне­
� ров. Проект OCI запущен 22 июня 2015 года компаниями Docker, CpreOS и другими
лидерами в отрасли контейнеров".
Затем выполняется процесс containerd-shim-runc-v2 для запуска контейнера, для чего
запускается runc. Однако после запуска контейнера runc не будет выполняться,
и containerd-shim-runc-v2 будет фактическим родительским процессом нового кон­
тейнерного процесса.
Если мы запустим контейнер и посмотрим на вывод ps axlf на сервере Docker, то
увидим родительские и дочерние процессы и отношения между ними. Процесс
PID 1 - /sЬin/init, и это родительский процесс для containerd, dockerd и containerd­
shim-runc-v2.
Виртуальная машина Docker Desktop содержит минимальные версии большинства
инструментов Linux, и некоторые из этих команд моrут не давать тот же вывод,
который мы получили бы при использовании стандартного сервера Linux в качест­
ве хоста демона Docker.
$ docker container run --rm -d \
--puЫish mode=ingress,puЫished=8080,target=80 \
--name nginx-test --rm nginx:latest
08b5cffed7baaf32b3af50498f7e5c5fa7ed35e094fa6045c205a88746fe53dd
$ ps axlf
Книги для программистов: https://clcks.ru/books-for-it
318
Глава 11
...PID
PPID СОММАND
...5171 1
...5288 1
...5784 5288
...5791 5288
...5807 1
...5829 5807
...5880 5829
...5881 5829
...5882 5829
...5883 5829
/usr/bin/containerd
/usr/bin/dockerd -Н fd:// --containerd=/run/cont .../containerd ....
\_ /usr/bin/docker-proxy -proto tcp -host-ip ... -host-port 8080
\_ /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port
/usr/bin/containerd-shim-runc-v2 -namespace moby -id
\_ nginx: master process nginx -g daemon off;
\_ nginx: worker process
\_ nginx: worker process
\_ nginx: worker process
\_ nginx: worker process
Что делает runc? Он собирает и запускает контейнер, а затем завершает работу, а
его дочерние процессы переходят к его родительскому процессу, containerd-shim­
runc- v2. В памяти остается минимум кода, чтобы управлять дескрипторами файлов
и кодами выхода для containerd.
Чтобы лучше понять, что происходит, давайте посмотрим, как запускается контей­
нер. Мы вернемся к нашему контейнеру nginx, поскольку он очень легкий{ и кон­
тейнер продолжает выполняться в фоновом режиме:
$ docker container 1s
CONTAINER ID IМAGE.
08b5cffed7ba nginx:latest
СОММАND
"/docker-ent ..."
PORTS
0.0.0.0:8080->80/tcp
NAМES
nginx-test
Давайте посмотрим на систему под этим углом с помощью инструмента CLI среды
выполнения runc. Мы видим почти то же, что и с точки зрения ctr, клиента CLI для
containerd, но с runc удобнее работать, и он находится на нижнем уровне:
$ sudo runc --root /run/docker/runtime-runc/moby list
BUNDLE
ID
PID
... OWNER
08Ь5...53dd 5829 ... .../io.containerd.runtime.v2.task/moby/08b5...53dd ... root
Обычно для выполнения этой команды нужны права root. В отличие от Docker CLI,
мы не можем полагаться на разрешения демона Docker, чтобы получить доступ
к функционалу на нижнем уровне. При использовании runc нам нужен прямой дос­
туп к этим привилегиям. В выводе runc мы видим наш контейнер. Это бандл
(bundle) среды выполнения по стандартам OCI, который представляет наш контей­
нер с тем же идентификатором. Мы также получаем PID контейнера, и это PID на
хосте для приложения, выполняющегося в контейнере:
$ ps -edaf
root
systemd+
systemd+
systemd+
systemd+
grep 5829
5829
5807
5880
5829
5829
5881
5882
5829
5883
5829
nginx: master process nginx -g daemon off;
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
З 19
В нутри бандла мы увидим набор именованных каналов для нашего контейнера:
$ sudo 1s -la /run/docker/containerd/08b5 ...53dd
total О
drwxr-xr-x 2 root root 80 Oct 1 08:49 .
drwxr-xr-x 3 root root 60 Oct 1 08:49 ..
prwx------ 1 root root О Oct 1 08:49 init-stderr
prwx------ 1 root root О Oct 1 08:49 init-stdout
В каталоге /run/containerd/io.containerd.runtime.v2.task/moby находится много до­
полнительных файлов, связанных с контейнером:
$ sudo 1s -la /run/containerd/io.containerd.runtime.v2.task/moby/08b5 ...53dd/
total 32
drwx-----root
1 08:49
240
3 root
Oct
drwx--x--x
3 root
root
60
Oct
1 08:49
1 08:49
root
-rw-r--r-1 root
89
address
Oct
1 08:49
root
Oct
-rw-r--r-1 root
9198
config.json
root
4
init.pid
-rw-r--r-Oct
1 08:49
1 root
root
prwx-----о
1 root
Oct
1 08:49
log
1 08:49
root
log.json
-rw-r--r-1 root
Oct
о
1
08:49
root
options.json
82
Oct
-rw------1 root
1
rootfs
08:49
root
40
Oct
drwx--x--x
2 root
1 08:49
runtime
4
root
Oct
-rw------1 root
32 Oct 1 08:49 shim-Ьinary-path
-rw------- 1 root root
lrwxrwxrwx 1 root root 119 Oct 1 08:49 work -> /var/liЬ/containerd/io ...
Файл config.json - это очень подробный эквивалент того, что выводит Docker по
команде docker container inspect. Он очень большой, поэтому здесь мы его не приво­
дим, но вы можете сами заглянуть в него и изучить конфигурацию. Например,
обратите внимание на записи для режима Secure Computing Mode, который мы об­
суждали ранее в этой главе.
Если хотите подробнее изучить runc, поэкспериментируйте с инструментом CLI.
Большая часть уже доступна в Docker, и обычно это более общая и полезная ин­
формация, чем та, которую предоставляет runc. Рекомендуем изучить подробности,
чтобы лучше понять работу контейнеров и стека Docker. Кроме того, будет инте­
ресно понаблюдать за событиями с выполняющимися контейнерами, о которых
сообщает runc. Для просмотра событий у нас есть команда runc events. В ходе обыч­
ной работы контейнера в потоке событий мало что происходит, но runc регулярно
передает статистику среды исполнения, которую мы можем просмотреть в формате
JSON:
$ sudo runc --root /run/docker/runtime-runc/moby events 08Ь5 ...53dd
{ "type":"stats", "id": "08Ь5 ... 53dd", "data": { "cpu": { "usage": { " ..." }}}}
Для экономии места мы удалили большую часть вывода предыдущей команды,
но это должно выглядеть знакомо, ведь мы уже выполняли команду docker container
stats. Угадайте, откуда Docker по умолчанию берет эту статистику. Все верно, от
runc.
Остановите пример контейнера с помощью docker container stop nginx-test.
Книги для программистов: https://clcks.ru/books-for-it
320
1
Глава 11
Альтернативные среды выполнения
Как мы уже упоминали в главе 2, runc можно заменить другими нативными средами
выполнения, подцерживающими стандарт OCI. Например, crun (https://github.com/
containers/crun), который авторы описывают как "быстрая и потребляющая мало
памяти среда выполнения контейнеров по стандарту OCI, полностью написанная
на С". Другие альтернативы, например railcar и rkt, уже устарели и практически не
встречаются. В следующем разделе мы рассмотрим изолированную среду выпол­
нения от Google - gVisor (https://gvisor.dev/), которая предназначена для недове­
ренного кода.
Kata Containers (https://github.com/kata-containers) - интересный опенсорс­
nроект, который предоставляет среду выполнения, использующую виртуальные
машины как уровень изоляции для контейнеров. На момент написания Kata версии 3
работает с Kubernetes, но не с Docker. Разработчики Kata вместе с разработчиками
Docker (https://github.com/kata-containers/kata-containers/issues/5321) сейчас
работают над совместимостью и документацией. Возможно, проблема будет ре­
шена в публичном выпуске Docker 22.06.
gVisor
В середине 2018 года Google представил новый взгляд на среды выполнения, вы­
пустив инструмент gVisor. Он подцерживает стандарты OCI и может использовать­
ся с Docker. Однако gVisor также выполняется в пользовательском пространстве и
изолирует приложения с помощью реализации системных вызовов там же, а не
механизмов изоляции ядра. Он не перенаправляет вызовы в ядро, а реализует их
сам с помощью вызовов ядра. Самое очевидное преимущество такого подхода улучшенная безопасность, поскольку сам gVisor выполняется в пользовательском
пространстве, а значит, изолирован от ядра. Если возникнут проблемы с безопасно­
стью, они останутся в пользовательском пространстве, и все механизмы контроля
безопасности в ядре, о которых мы говорили, продолжат действовать. Из недостат­
ков - производительность ниже, чем у решений, использующих ядро или вирту­
альные машины.
Если вашим процессам не требуется большой масштаб, но нужна более безопасная
изоляция, присмотритесь к gVisor. Обычно gVisor целесообразен, когда в контей­
нерах должен выполняться код, поступающий от конечных пользователей, и вы не
можете быть уверены, что этот код безвреден. Давайте на примере посмотрим, как
работает gVisor.
Инструкции по установке см. в документации по gVisor (https://gvisor.dev/docs/
user_guide/quick_startfdocker). Он написан на Go и поставляется как один испол­
няемый файл, без пакетов. После установки контейнеры можно запустить в среде
выполнения runsc. Чтобы продемонстрировать разные уровни изоляции в gVisor,
мы запустим командную оболочку и сравним возможности со стандартным кон­
тейнером.
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
321
Для начала запустим командную оболочку для gVisor и осмотримся:
$ docker container run --rm --runtirne=runsc -it alpine /bin/sh
Мы получим командную оболочку в контейнере Alpine Linux. Самое заметное раз­
личие мы увидим в выводе команды mount:
$ docker container run --rm --runtime=runsc -it alpine /Ьin/sh -с "mount"
попе оп/type 9р (rw,trans=fd,rfdno=4,wfdno=4,aname=/,...)
попеon /dev type tпpfs (rw,mode=0755)
попе оп/sys type sysfs (ro,noexec,dentry_cache_limit= l000)
попе оп/proc type proc (rw,noexec,dentry_cache_lirnit= l000)
попе оп
/dev/pts type devpts (rw,noexec)
попеon /dev/shm type tпpfs (rw,noexec,mode= 1777,size=67108864)
попе оп
/etc/hosts type 9р (rw,trans=fd,rfdno=7,wfdno=7,...)
попеon /etc/hostname type 9р (rw,trans=fd,rfdno=б,wfdno=б,...)
попеon /etc/resolv.conf type 9р (rw,trans=fd,rfdno=5,wfdno=5,...)
попon
е /tпр type tпpfs (rw,mode=01777)
Здесь мы не видим ничего особенно интересного. Давайте сравним этот вывод
с тем, что мы получаем от традиционного контейнера в runc:
$ docker container run --rm -it alpine /Ьin/sh -с "mount"
overlay оп/type overlay (rw,relatime,...)
proc оп /proc type proc (rw,nosuid,nodev,noexec,relatime)
tпpfs оп/dev type tпpfs (rw,nosuid,size=65536k,mode=755,inode64)
devpts оп/dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,...)
sysfs оп/sys type sysfs (ro,nosuid,nodev,noexec,relatime)
·cgroup п
о /sys/fs/cgroup type cgroup2 (ro,nosuid,nodev,noexec,relatirne)
mqueue п
о /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatirne)
shm оп/dev/shm type tпpfs (rw,nosuid,nodev,noexec,relatime,...)
/dev/sdaЗ оп/etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro)
deyPts п
о /dev/console type devpts (rw,nosuid,noexec,relatime,gid=5,...)
proc оп /proc/bus type proc (ro,nosuid,nodev,noexec,relatime)
tпpfs on /proc/asound type tпpfs (ro,relatime,inode64)
В выводе было 24 строки, но здесь мы приводим не все. Как видите, данных очень
много, и они показывают взгляд на ядро из контейнера. -Если сравнить это с не­
большим количеством данных от gVisor, разница в степени изоляции будет оче­
видна. Мы не будем вдаваться в подробности, но стоит изучить вьшод и для ip addr
show. В gVisor:
$ docker container run --rm --runtime=runsc alpine ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65522
link/loopback 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
inet 127.0.0.1/8 scope global dynamic
Книги для программистов: https://clcks.ru/books-for-it
322
Глава 11
2: eth0: <UP,LOWER_UP> mtu 1500
link/ether 02:42:ас:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global dynamic
В обычном контейнере Linux:
$ docker container run --rm alpine ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNКNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:0D:DD:00:D0:DD
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
44: ethD@if45: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc
noqueue state UP
link/ether D2:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
Даже файловая система Linux /proc открывает гораздо меньше в контейнере gVisor:
$ docker container run --rm --runtime=runsc alpine ls -С /proc
1
cgroups
andline
cpuinfo
net
self
sentry-meminfo
stat
filesystems
loadavg
meminfo
mounts
sys
thread-self
uptime
version
Сравним с обычным контейнером Linux:
$ docker container run --rm alpine ls -С /proc
1
acpi
asound
bootconfig
buddyinfo
bus
cgroups
andline
consoles
cpuinfo
crypto
devices
diskstats
dma
driver
dynamic_debug
execdomains
fЬ
filesystems
fs
interrupts
iomem
ioports
irq
kallsyms
kcore
key-users
keys
kmsg
kpagecgroup
kpagecount
kpageflags
loadavg
locks
mdstat
meminfo
misc
modules
mounts
щ:,t
mtd
mtrr
net
pagetypeinfo
partitions
pressure
schedstat
scsi
self
slaЬinfo
softirqs
stat
swaps
sys
sysrq-trigger
sysvipc
thread-self
timer list
tty
uptime
version
version signature
vmallocinfo
vmstat
zoneinfo
Книги для программистов: https://clcks.ru/books-for-it
Расширенные концепции
1
323
gVisor и подобные изолированные среды позволяют более безопасно выполнять
ненадежные рабочие нагрузки, обеспечивая барьер между приложением и нижеле­
жащим ядром.
Заключение
Мы кратко рассмотрели расширенные концепции Docker. Теперь вы лучше пони­
маете, как все работает и в какую сторону можно продолжать самостоятельное изу­
чение. Этих знаний будет достаточно, чтобы понимать, с чего начать реализацию
Docker в своих системах.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 12
Развитие экосистемы
Набор инструментов для работы с контейнерами Linux постоянно растет и развива­
ется, тем более что Kubemetes уже много лет продолжает набирать популярность.
В этой главе мы кратко рассмотрим ряд инструментов, появившихся благодаря
Docker и обычно предназначенных для специфических сценариев. Это не полный
обзор, а несколько примеров, чтобы вы получили представление о некоторых дос­
тупных вариантах.
Клиентские инструменты
В этом разделе мы рассмотрим три инструмента командной строки: nerdctl, podman и
buildah. Эти инструменты могут быть полезны тем, кто знаком с Docker и его стан­
дартными рабочими процессами.
nerdctl
Хотя по умолчанию во многих средах на базе containerd устанавливается crictl 1,
стоит обратить внимание на nerdctl, простой в использовании CLI, совместимый
с Docker, для containerd. nerdctl предлагает очень простой путь миграции для поль­
зователей и скриптов, которые применяют Docker, но поддерживают системы
containerd без демона Docker.
Если мы запустим небольшой кластер Kubemetes с kind, который мы рассматривали
в разделе 11Kind11 главы 1 О, то получим кластер Kubemetes на базе containerd, с кото­
рым мы не сможем напрямую взаимодействовать через Docker CLI:
$ kind create cluster --name nerdctl
Creating cluster "nerdctl" ...
$ docker container ехес -ti nerdctl-control-plane /bin/bash
Мы вошли в контейнер kind/KuЬernetes.
.
В следующей команде curl мы должны загрузить подходящую версию для нашей
архитектуры. Замените $ {ARCHJ на amd64 или arrn64, в зависимости от используемой
системы. Попробуйте загрузить последнюю версию nerdctl (https://github.com/
containerd/nerdctl/releases).
1 Полный URL: https://github.com/kubernetes-sigs/cri-tools/ЫoЬ/master/docs/crictl.md.
Книги для программистов: https://clcks.ru/books-for-it
Развитие экосистемы
325
Оrредактировав следующую команду curl и собрав ее в одну строку, вы сможете
загрузить и извлечь клиент nerdctl, а затем выполнить несколько команд:
root@nerdctl-control-plane:/t curl -s -1 \
"https://githuЬ.com/containerd/nerdctl/releases/download/v0.23.0/\
nerdctl-0.23.0-linux-${ARCH).tar.gz" -о /trrp/nerdctl.tar.gz
root@nerdctl-control-plane:/1 tar -С /u_sr/local/Ьin -xzf /trrp/nerdctl.tar.gz
root@nerdctl-control-plane:/t nerdctl namespace list
NАМЕ
k8s.io
CONTAINERS
18
IМAGES
VOLUMES
LAВELS
24
О
root@nerdctl-control-plane:/1 nerdctl --namespace k8s.io container list
CONTAINER ID IМAGE
NAМES
07ae69902dll registry.k8s.io/pause:3.7
Ob24ldЬ0485f registry.k8s.io/coredns/coredns:vl.9.3
k8s://kuЬe-system/core .. .
k8s://kuЬe-system/core...
root@nerdctl-control-plane:/1 nerdctl --namespace k8s.io container run --rm \
--net;host deЬian sleep 5
docker.io/library/debian:latest:
index-sha256:e538 ...4bff:
manifest-sha256:9b0e...2f7d:
config-sha256:d917...d33c:
layer-sha256:f606...5ddf:
elapsed: 6.4 s
root@nerdctl-control-plane:/1 exit
resolved
done
done
done
done
total:
1+++++++++++++++++++++++++++1
1+++++++++++++++++++++++++++1
1+++++++++++++++++++++++++++1
1+++++++++++++++++++++++++++1
1+++++++++++++++++++++++++++1
52.5 М (8.2 MiB/s)
В большинстве случаев команды docker можно использовать в nerdctl почти без
изменений. Единственное заметное отличие - необходимость часто указывать
значение пространства имен.
Это связано с тем, что containerd предоставляет API в пространстве имен (https://
github.com/containerd/containerd/ЫoЬ/main/docs/namespaces.md), и нужно указы­
вать, с каким именно API мы собираемся взаимодействовать.
Выйдя из контейнера kind, мы можем его удалить.
$ kind delete cluster --name nerdctl
Deleting cluster "nerdctl" ...
podman и buildah
Утилиты podman (https://podman.io/) и Ьuildah (https://buildah.io/) - это инструмен­
ты от Red Hat, которые были созданы на ранних этапах использования контейне­
ров, чтобы обходиться без демона вроде Docker. Они широко применяются сооб­
ществом Red Hat и предоставляют иной подход к сборке образов и выполнению
контейнеров.
Читайте подробнее о podman и buildah для пользователей Docker в блоге Red Hat
(https://developers.redhat.com/blog/2019/02/21/podman-and-buildah-for-docker­
;
users).
Книги для программистов: https://clcks.ru/books-for-it
326
Глава 12
$ kind create cluster --name podman
Creating cluster "podman" ...
$ docker container ехес -ti podman-control-plane /bin/bash
� Инс,рукции по установке и исполозоваsию kind см. в paзiJene "Кind" гпавы 10.
Мы находимся в контейнере kind/KuЬernetes:
root@podman-control-plane:/# apt update
Get:l http://security.uЬuntu.com/uЬuntu jammy-security InRelease (110 kВ]
root@podman-control-plane:/# apt install -у podman
Reading package lists... Done
root@podman-control-plane:/# podman container run -d --rm --name test debian sleep 120
9bбb333313c0d54e2daбcda49f2787bc521368ld90dacl45a9f64128f3el8631
root@podman-control-plane:/# podman container list
CONTAINER ID IМAGE
СОММАND
NAМES
test
548a2f709785 docker.io/library/debian:latest sleep 120
root@podman-control-plane:/# podman container stop test
test
В отличие от docker (который работает с демоном Docker) и nerdctl (который рабо­
тает с containerct), podman обходится без контейнерного движка и напрямую взаимо­
действует со средой выполнения контейнеров, например runc.
Хотя мы можем собирать контейнеры с помощью podman build, buildah предоставляет
расширенный интерфейс для сборки образов, так что можно написать весь процесс
в скрипте и не использовать формат Dockerfile (или Containerfile в терминологии
podman).
Мы не будем подробно рассматривать buildah, но вы можете попробовать простой
пример с контейнером kind, и если вас интересуют альтернативы традиционному
подходу с Dockerfile или новые подходы, предоставляемые интерфейсом LLB
BuildКit (https://github.com/moby/Ьuildkit#exploring-llb), читайте о buildah на GitHub
(https://github.com/containers/Ьuildah) и в блоге Red Hat (https://www.redhat.com/
sysadmin/building-buildah).
Попробуйте запустить скрипт buildah в контейнере kind, выполнив следующие
команды:
root@podman-control-plane:/# cat > apache.sh «"EOF"
#!/usr/bin/env bash
set -х
ctrl=$(buildah from "${1:-fedora}")
## Получить все обновления и установить сервер apache
buildah run "$ctrl" -- dnf update -у
buildah run "$ctrl" -- dnf install -у httpd
Книги для программистов: https://clcks.ru/books-for-it
Развитие экосистемы
327
## Включить аннотации на этапе сборки
buildah config --anпotatioп "com.example.build.host;$(uпame -n)" "$ctrl"
## Запустить сервер и открыть порт
buildah config --cmd "/usr/sЬin/httpd -D FOREGROUND" "$ctrl"
buildah config --port 80 "$ctrl"
## Закоммитить контейнер с именем образа
buildah commit "$ctrl" "${2:-myrepo/apache)"
EOF
root@podman-control-plane:/# chmod +х apache.sh
root@podman-control-plane:/# ./apache.sh
++ buildah from fedora
+ ctrl;fedora-working-container-1
+ buildah run fedora-working-container-1 -- dnf update -у
Writing manifest to image destination Storing signatures
037c7a7c532a47be67f389d7fd3e4bbba64670e080Ы20d93744el47df5adf26
root@podman-control-plane:/# exit
Выйдя из контейнера kind, мы можем его удалить.
$ kind delete cluster --name podman Deleting cluster "podman"
Универсальные инструменты разработчика
Несмотря на все преимущества Docker Desktop, изменения в лицензировании
Docker, а также развитие технологий приводят к тому, что некоторые люди и орга­
низации ищут альтернативные инструменты. В этом разделе мы рассмотрим
Rancher Desktop и Podman Desktop, которые предоставляют часть функционала
Docker Desktop, а также собственные интересные функции.
Rancher Desktop
Rancher Desktop (https://rancherdesktop.io/) похож на Docker Desktop и предназна­
чен специально для интеграции с Kubemetes. Он использует kЗs (https://k3s.io/) как
сертифицированный легкий бэкенд Kubemetes и containerd или dockerd ( moby) в каче­
стве среды выполнения контейнеров.
Выйдите из Docker (и/или Podman) Desktop, если один из них запущен, прежде чем
экспериментировать с Rancher Desktop, поскольку все они работают на виртуаль­
ной машине, которая потребляет системные ресурсы.
После загрузки, установки и запуска Rancher Desktop мы получим локальный кла­
стер Kubemetes, который по умолчанию использует containerd и управляется через
nerdctl.
Книги для программистов: https://clcks.ru/books-for-it
328
1
Глава 12
Папка, в которую Rancher Desktop устанавливает файл nerdctl, зависит от опера­
ционной системы. Убедитесь, что используете версию, которая упакована
с Rancher Desktop.
$ ${HOМE)/.rd/bin/nerdctl --namespace k8s.io irnage list
REPOSITORY
TAG
IМAGE ID
... PLATFORМ
moby/buildkit
v0.8.3
171689е43026
... linux/amd64
moby/buildkit
<none>
171689е43026
... linux/amd64
SIZE
119.2 MiB
119.2 MiB
В10В SIZE
53.9 MiB
53.9 MiB
Не забудьте выйти из Rancher Desktop, когда закончите, иначе виртуальная машина
продолжит потреблять ресурсы.
Podman Desktop
Podman Desktop (https://podman-desktop.io/) не использует демон, но предоставля­
ет удобный инструмент, привычный разработчикам во всех популярных операци­
онных системах.
Выйдите из Docker (и/или Rancher) Desktop, если один из них запущен, прежде чем
экспериментировать с Podman Desktop, поскольку все они работают на виртуаль­
ной машине, которая потребляет системные ресурсы.
После загрузки, установки и запуска Podman Desktop откроется окно приложения
на вкладке Ноше. Если Podman Desktop не найдет Podman CLI в системе, он пред­
ложит его установить. Нажмите на кнопку Install (Установить), чтобы установить
клиент podrnan. Когда виртуальная машина Podman Desktop, которой можно управ­
лять в командной строке через podrnan rnachine, не запущена, щелкните кнопку
Run Podman (Запустить) и подождите. Появится сообщение Podman is running
(Podman запущен).
Папка, в которую Podman Desktop устанавливает файл podrnan, зависит от операци­
онной системы. Убедитесь, что используете версию, которая устанавливается че­
рез Podman Desktop.
Давайте посмотрим, что у нас получилось:
$ podrnan run quay.io/podrnan/hello
! ... Hello Podrnan World ... !
--"-/ -
~~~ 1
/ (О)
~/ о о\
1 =(Х)= 1~
-\
(О) \
-= ( , У, ) =- 1
/
\ 1~~
~1
~~~1
/ (О (О) \
=(У_)=u
1
Книги для программистов: https://clcks.ru/books-for-it
Развитие экосистемы
1
329
Project:
https://githuЬ.com/containers/podman
Website:
https://podman.io
Documents: https://docs.podman.io
Twitter:
@Poclman io
Когда закончите экспериментировать с Podman Desktop, завершите работу вирту­
альной машины: на вкладке Preferences (Настройки) выберите разделы Resources
(Ресурсы)- Podman - Podman Machine (Машина Podman) и нажмите кнопку
Stop.
Теперь можно выйти из приложения Podman Desktop.
�
�
Запускать и останавливать виртуальную машину Podman можно с помощью команд
podman machine start и podman machine stop.
Заключение
Docker уже занимает прочное место в истории технологий, ведь он дополнил суще­
ствующие контейнеры Linux концепцией образов, и с его появлением инженеры по
всему миру получили удобный доступ к этим технологиям.
Мы можем спорить о том, стало ли сейчас лучше, чем было до контейнеров Linux и
Docker, а также о том, какие инструменты и рабочие процессы лучше, но в итоге
все зависит от того, как применять эти инструменты и организовьmать эти про­
цессы.
Ни один инструмент не сможет волшебным образом решить все проблемы, и если
применять его неправильно, станет только хуже. Поэтому так важно тщательно об­
думать рабочие процессы как минимум с трех точек зрения. Во-первых, что должен
поддерживать рабочий процесс на входе и на выходе. Во-вторых, насколько легко
будет людям, которым придется использовать этот рабочий процесс каждый день
или раз в год. В-третьих, насколько легко запускать и поддерживать этот процесс,
чтобы обеспечить беспроблемную и безопасную работу сис:гемы.
Как только вы хорошо себе представите, чего пытаетесь достичь, вы сможете
подобрать инструменты, подходящие для этих целей.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 13
Устройство
контейнерной платформы
Реализуя любую технологию в рабочей среде, всегда учитывайте возможность сис­
темы противостоять неожиданным проблемам, которые неизбежно возникнут.
Docker предлагает широкие возможности, но требует внимания к деталям при
создании полноценной платформы. Как любая другая технология, которая быстро
развивается, он не обходится без неприятных ошибок, проявляющихся в разных
компонентах контейнерной платформы.
Если мы не будет торопиться развертывать Docker в имеющейся среде, а тщательно
выстроим контейнерную платформу вокруг него, то сможем использовать много­
численные преимущества контейнеров, при этом защитившись от некоторых рис­
ков, присущих быстро развивающимся проектам.
Ни одна технология не решит все ваши проблемы как по волшебству, и Docker не
исключение. Чтобы реализовать его потенциал, необходимо тщательно продумать,
как и почему следует его применять. В маленьких проектах Docker можно вопло­
тить без · лишних сложностей, но если вы планируете поддерживать крупную сис­
тему с масштабированием по требованию, необходимо осознанно подходить к про­
ектированию приложений и платформы, чтобы максимально эффективно вложить
инвестиции в технологии. Если изначально спроектировать платформу правильно,
впоследствии будет гораздо проще адаптировать рабочий процесс. Хорошо проду­
манная контейнерная платформа и процесс развертывания отличаются легкостью и
лаконичностью, но при этом соответствуют всем техническим и регулятивным тре­
бованиям. Приложение должно выполняться на динамическом фундаменте, кото­
рый легко можно модернизировать вместе с развитием технологий и процессов
в организации.
В этой главе мы рассмотрим два документа: The Twelve-Factor Арр (Приложение
двенадцати факторов, https://12factor.net/) и The Reactive Manifesto (Манифест ре­
активных систем, https://www.reactivemanifesto.org/), приложение к The Reactive
Principles (Принципы реактивных систем, https://www.reactiveprinciples.org/), и
обсудим, как они применяются к Docker и созданию надежных контейнерных
платформ. Оба документа содержат интересные идеи, которые помогут вам в про­
ектировании и реализации отказоустойчивой и удобной в сопровождении контей­
нерной платформы.
Книги для программистов: https://clcks.ru/books-for-it
Устройство контейнерной платформы
331
Приложение двенадцати факторов
В ноябре 2011 года, задолго до выпуска Docker, сооснователь Heroku Адам Циггинс
с коллегами выпустил статью под названием The Twelve-Factor Арр. В этом доку­
менте описываются 12 методов, сформулированных на основе опыта инженеров
Heroku (https://www.heroku.com/) и применимых к проектированию успешного
приложения для современной среды SaaS на основе контейнеров.
Мы не обязаны учитывать все 12 факторов при создании приложения, но такие
приложения будут идеально сочетаться с Docker. В этой главе мы рассмотрим каж­
дый фактор и узнаем, как они поддерживают цикл разработки:
♦ Кодовая база.
♦ Зависимости.
♦ Конфигурация.
♦ Сторонние службы.
♦ Сборка, выпуск, выполнение.
♦ Процессы.
♦ Привязка портов.
♦ Параллелизм.
♦ Утилизируемость.
♦ Паритет среды разработки и рабочей среды.
♦ Журналы.
♦ Администрирование.
Кодовая база
В системе контроля версий должна быть одна кодовая база.
У одного приложения может одновременно выполняться несколько экземпляров,
но все они должны происходить из одного репозитория. Каждый образ Docker для
приложения должен собираться из единого репозитория исходного кода, в котором
содержится весь код, необходимый для сборки контейнера Linux. При таком под­
ходе код легко можно будет собирать повторно, а все сторонние зависимости будут
определяться в репозитории и автоматически подгружаться при сборке.
Это означает, что для сборки приложения нам не придется совмещать код из раз­
ных источников. У нас могут быть зависимости от программных пакетов из друго­
го репозитория, просто нам нужен четкий механизм определения того, из какого
кода мы собрали приложения. Docker упрощает управление зависимостями, но это
не поможет, если при сборке приложения мы извлекаем и сшиваем код из многих
репозиториев. Такой процесс гораздо хуже воспроизводится, ведь мы должны знать
конкретное "заклинание", чтобы заставить систему работать.
Для проверки дайте новому разработчику в компании чистый ноутбук и документ
с указаниями, а затем посмотрите, сможет ли он собрать ваше приложение меньше
Книги для программистов: https://clcks.ru/books-for-it
332
1
Глава 13
чем за час. Если не сможет, скорее всего, процесс нужно оптимизировать и упро­
стить.
Зависимости
Зависимости должны быть явно объявленными и изолированными.
Не стоит надеяться, что зависимость будет предоставляться каким-то иным спосо­
бом, например через установку в операционной системе. Все зависимости, которые
нужны вашему приложению, должны быть хорошо определены в базе кода, чтобы
их можно было получать в процессе сборки. При таком подходе приложение смо­
жет выполняться после развертывания, не ожидая, пока другие люди или процессы
установят нужные библиотеки. Это особенно важно при использовании контейне­
ров, ведь процессы в контейнерах изолированы от операционной системы хоста,
и обычно у них нет доступа к компонентам за пределами ядра хоста и файловой
системы образа контейнера.
Dockerfile и файлы конфигурации для определенного языка, вроде Node package.json
или Ruby Gemfile, должны определять все внутренние зависимости, необходимые
приложению, чтобы образ выполнялся корректно в любой системе, в которой будет
развернут. Больше не нужно переживать, что после развертывания приложения
в рабочей среде вдруг обнаружится, что важные библиотеки отсутствуют или уста­
новлены не в тех версиях. Такой подход гарантирует высокую степень надежности
и воспроизводимости, а также безопасности системы. Если для исправления про­
блемы с безопасностью мы обновляем OpenSSL или библиотеки libyaml, исполь­
зуемые контейнеризированнь1м приложением, то можем быть уверены, что прило­
жение всегда и в любой среде будет развертываться с нужной версией.
Важно отметить, что многие базовые образы Docker весят больше, чем нужно.
Процесс приложения будет выполняться на разделяемом ядре, и в образе должны
быть только те файлы, которые требуются процессу. Удобно, когда базовые образы
легкодоступны, но иногда они могут маскировать зависимости. Разработчики часто
начинают с минимальной установки Alpine, Ubuntu или Fedora, но эти образы все
равно содержат много файлов и приложений операционной системы, которые не
нужны процессу. О некоторых файлах, используемых приложением, вы можете
даже не знать, например приложение компилируется с помощью системной биб­
лиотеки musl в Alpine и glibc во многих других базовых образах. Мы должны по­
нимать все зависимости, даже при контейнеризации приложений. Кроме того, нуж­
но помнить о вспомогательных инструментах, которые могут входить в образы.
Иногда трудно найти баланс между простотой отладки и сокращением поверхности
атаки для приложений и окружений.
Чтобы узнать, какие файлы требуются в образе, можно сравнить небольшой базо­
вый образ с образом для статически скомпонованной программы, написанной на
Go или С. Эти приложения можно спроектировать таким образом, чтобы запускать
напрямую в ядре Linux без дополнительных библиотек или файлов.
Чтобы лучше понять этот принцип, вернитесь к упражнениям в разделе "Контроль
размера образов" главы 4, где мы рассмотрели сверхлегкий контейнер spkane/
Книги для программистов: https://clcks.ru/books-for-it
Устройство контейнерной платформы
1
333
scratch-helloworld, а затем изучили базовую файловую систему и сравнили его с по­
пулярным базовым образом alpine.
Мы должны не только продумать управление слоями файловой системы в образах,
но и удалить из образа все, кроме самого необходимого, чтобы система работала
эффективнее, а команды docker image pull выполнялись быстро. Для приложений,
написанных на интерпретируемых языках, потребуется больше файлов, потому что
нужно будет устанавливать большие среды выполнения и графы зависимостей, но
старайтесь поддерживать минимальный базовый слой, чтобы контролировать зави­
симости. Docker помогает упаковывать их, но продумывать подход все равно при­
дется вам.
Конфигурация
Храните конфигурацию в переменных среды, а не в файлах в репозитории.
При таком подходе будет проще развертывать одну и ту же кодовую базу в разных
окружениях, например в промежуточной или в рабочей, не поддерживая сложную
конфигурацию в коде и не пересобирая контейнер для каждой среды. Кодовая база
будет выглядеть гораздо аккуратнее, если в ней не будут храниться имена баз дан­
ных или пароли. Что еще важнее, нам не придется хранить в репозитории предпо­
лагаемые настройки среды развертывания, поэтому приложения будет очень просто
развертывать где угодно. Кроме того, у нас будет возможность протестировать
именно тот образ, который мы будем поставлять в рабочую среду. Это бьmо бы не­
возможно, если бы пришлось собирать отдельный образ для каждого окружения со
всеми соответствующими конфигурациями.
Как мы уже видели в главе 4, для этого можно вьmолнить команду docker container
run с аргументом командной строки -е. Строка -е АРР_ENV=production указывает
Docker задавать для переменной окружения АРР_ENV значение production в каждом
новом контейнере.
В реальном мире мы могли бы извлечь образ для чат-бота Hubot с установленным
Rocket.Chat (https://www.rocket.chat/). Запустить контейнер можно следующей
командой:
$ docker container run \
--rm --пате huЬot -d \
•-е ENVIRONМENT="development" \
-е RОСКЕТСНАТ URL='rocketchat:3000' \
-е ROCKETCНAT_ROOM=' general' \
-е RESPOND ТО DM=true \
-е RОСКЕТСНАТ USER=bot \
-е RОСКЕТСНАТ PASSWORD=bot \
-е ROCKETCНAT_AUTH=password \
-е вот NAME=bot \
-е EXTERNAL_SCRIPTS=huЬot-pugme,huЬot-help \
docker.io/rocketchat/huЬot-rocketchat:latest
Книги для программистов: https://clcks.ru/books-for-it
334
Глава 13
Здесь мы передаем целый набор переменных среды в контейнер при создании.
Когда процесс в контейнере запускается, он получает доступ к переменным среды
для правильной настройки в среде вьщолнения. Эти компоненты конфигурации
теперь представляют собой внешнюю зависимость, которую можно внедрить во
время выполнения.
Есть много других способов передать эти данные контейнеру, включая хранилище
ключей и значений, вроде etcd и consul. Переменные среды - это универсальный
вариант и удобная отправная точка для большинства проектов. С их помощью
можно легко настраивать контейнер, потому что они хорошо поддерживаются
платформой и всеми популярными языками программирования. Кроме того, они
улучшают наблюдаемость приложений, потому что конфигурацию можно легко
проверить, выполнив команду docker container inspect.
Если это приложение Node.js, вроде huЬot, затем можно написать следующий код,
чтобы принимать решения на основе этих переменных среды:
switch(process.env.ENVIRONMENT) {
case 'development':
console.log(' [INFO) Running in development');
case 'staging' :
console.log(' [INFO] Running in staging');
case 'production':
console.log(' [INFO) Running in production');
default:
console.log(' [WARN) Environment value is unknown');
Точный метод передачи этих данных конфигурации в контейнер зависит от инст­
рументов, выбранных для проекта, но почти все они позволяют гарантировать, что
каждое развертывание будет содержать необходимые настройки для среды.
Когда конфигурация хранится не в исходном коде, мы легко можем развертывать
совершенно одинаковые контейнеры в разных окружениях, ничего не меняя и не
храня конфиденциальную информацию в репозитории. Кроме того, мы можем тща­
тельно тестировать образы контейнеров, прежде чем развертывать их в рабочей
среде, используя один и тот же образ во всех средах:
$ docker container stop huЬot
Если вам нужен процесс для управления секретами в контейнерах, изучите доку­
ментацию (https:1/docs.docker.com/engine/swann/secrets) по команде docker
;
secret, которая работает с режимом Docker Swarm и HashiCorp Vault (https:l/www.
vaultproject.io/).
Сторонние службы
Сторонние службы следует считать подкточаемыми ресурсами.
Локальные базы данных не считаются более надежными, чем сторонние сервисы,
и их тоже следует отнести к этой категории. Приложения должны корректно обКниги для программистов: https://clcks.ru/books-for-it
Устройство контейнерной платформы
1
335
рабатывать потерю подключенного ресурса. Когда мы реализуем «изящную»
деградацию (graceful degradation) в приложении и не надеемся на постоянную дос­
тупность всех ресурсов, включая файловую систему, наше приложение будет
выполнять столько функций, сколько сможет, даже если внешние ресурсы будут
недоступны.
Docker не решает эти задачи напрямую, и хотя мы всегда должны продумывать на­
дежность сервисов, при использовании контейнеров это особенно важно. В контей­
нерах мы чаще всего достигаем высокой доступности с помощью горизонтального
масштабирования и последовательного развертывания, не применяя динамическую
миграцию долгосрочных процессов, как в традиционных виртуальных машинах.
Это значит, что конкретные экземпляры сервиса будут появляться и исчезать,
и приложение должно корректно обрабатывать эту смену.
Кроме того, поскольку у контейнеров Linux ограничены ресурсы файловой систе­
мы, мы не можем надеяться, что нам будет доступно локальное хранилище,
и должны явно реализовать эту потребность в зависимостях приложения.
Сборка, выпуск, выполнение
Этапы сборки, выпуска и выполнения должны быть строго разделены.
Соберите код, выпустите его с соответствующей конфигурацией, а затем разверни­
те. Это позволяет контролировать процесс и выполнять каждый этап, не запуская
весь рабочий процесс. Когда каждый шаг выполняется отдельно, мы быстрее полу­
чаем обратную связь и реагируем на потенциальные проблемы.
В рабочем процессе Docker мы должны четко разделять каждый этап развертыва­
ния. Да, мы можем одним щелчком запускать процесс сборки, тестирования и раз­
вертывания, если полностью доверяем тестам, но не должно быть необходимости
пересобирать контейнер только для того, чтобы развернуть его в другой среде.
Docker поддерживает этот фактор, потому что реестр образов позволяет удобно
передать образ с этапа сборки на этап поставки в рабочую среду. Если процесс
сборки создает образы и отправляет их в реестр, при развертывании можно просто
извлекать эти образы на серверы и запускать.
Процессы
Запускайте приложение в виде одного или нескольких процессов без сохранения
состояния.
Все данные следует хранить в стороннем хранилище, чтобы экземпляры приложе­
ния можно было легко развертывать повторно, не теряя важные данные сеанса. Не
стоит хранить критичные состояния на диске в эфемерном контейнере или в памяти
одного из его процессов, а контейнеризированные приложения всегда следует счи­
тать эфемерными. В по-настоящему динамической контейнерной среде контейнеры
можно уничтожать и создавать повторно по желанию. Такая гибкость поддержива­
ет быстрое развертывание и восстановление после сбоев, как того требуют совре­
менные рабочие процессы Agile.
Книги для программистов: https://clcks.ru/books-for-it
336
1
Глава 13
Насколько это возможно, создавайте приложения, которые хранят состояние столь­
ко времени, сколько необходимо для обработки и ответа на один запрос. В таком
случае при остановке одного контейнера неприятные последствия будут мини­
мальными. Если хранить состояние необходимо, используйте удаленное хранили­
ще, вроде Redis, PostgreSQL, Memcache или даже Amazon SЗ, в зависимости от тре­
бований к отказоустойчивости.
Привязка портов
Предоставляйте сервисы через привязку портов.
У приложения должен быть отдельный порт, по которому к нему можно обращать­
ся. Приложения должны быть привязаны к порту напрямую, не полагаясь на внеш­
ний демон наподобие inetd. Мы должны быть уверены в том, что через этот порт
обращаемся к самому приложению. Большинство современных веб-платформ
могут напрямую привязываться к порту и обслуживать собственные запросы.
Чтобы открыть порт из контейнера, как описано в главе 4, можно выполнить ко­
манду docker container run с аргументом командной строки --puЬlish. Если, напри­
мер, указать --puЬlish mode=ingress, puЫished=BO, target=BOBO, Docker будет знать, что
порт контейнера 8080 нужно проксировать на порт хоста 80.
Статически скомпонованный контейнер Go Hello World из раздела "Контроль раз­
мера образов" главы 4 - отличный пример такого подхода, потому что в контей­
нере нет ничего, кроме приложения, которое поставляет контент в браузер. Нам не
пришлось включать в сборку другие веб-серверы, которые потребовали бы допол­
нительной конфигурации, усложнили систему и увеличили число потенциальных
точек отказа.
Параллелизм
Масштабирование должно осуществляться через процессы.
Приложения должны поддерживать параллелизм и горизонтальное масштабирова­
ние. Изменять количество ресурсов для существующего экземпляра сложно. Гораз­
до проще будет добавлять и удалять экземпляры, поддерживая гибкость инфра­
структуры. Чтобы запустить еще один контейнер на новом сервере, потребуется
несравнимо меньше усилий и затрат по сравнению с добавлением ресурсов для ба­
зовой виртуальной или физической системы. Возможности горизонтального мас­
штабирования позволяют платформе гораздо быстрее реагировать на меняющиеся
потребности в ресурсах.
В главе 1 О мы видели пример того, как легко можно масштабировать сервис с по­
мощью режима Docker Swarm, просто выполнив команду:
$ docker service scale myservice=B
Тут очень кстати будут такие инструменты, как режим Docker Swarm, Mesos и
Kubernetes. Реализовав кластер Docker с динамическим планировщиком, мы можем
легко добавить еще три экземпляра контейнера в кластер при увеличении нагрузки,
Книги для программистов: https://clcks.ru/books-for-it
Устройство контейнерной платформы
1
337
а затем так же легко удалить два экземпляра приложения, когда нагрузка снова
спадет.
Утил изируемость
Возможность быстрого запуска и корректного завершения работы повышает устой­
чивость приложения.
Сервисы должны быть задуманы как эфемерные. Мы уже говорили об этом, обсуж­
дая хранение состояния за пределами контейнера. Приложение должно уметь быст­
ро запускаться и завершаться, чтобы поддерживать динамическое горизонтальное
масштабирование, последовательные развертывания и реагирование на неожидан­
ные проблемы. Сервисы должны корректно реагировать на сигнал SIGTERМ от опера­
ционной системы и уверенно обрабатывать аппаратные отказы. Что еще важнее, мы
не должны волноваться о работоспособности отдельных контейнеров. Пока прило­
жение обслуживает запросы, разработчики не должны думать о состоянии каждого
компонента системы. Нам нужна возможность легко отключать или повторно раз­
вертывать неисправные узлы, не тратя время на планирование этих операций и не
следя за работоспособностью остальной части кластера.
В главе 7 мы видели, что Docker отправляет стандартные сигналы Unix контейне­
рам, когда останавливает или завершает их. Получается, любое контейнеризиро­
ванное приложение может определять эти сигналы и принимать необходимые меры
для их корректного завершения.
Паритет среды разработки и рабочей среды
Среда разработки, промежуточная и рабочая среда должны быть максимально по­
хожи друг на друга.
Используйте одинаковые процессы и программные пакеты для сборки, тестирова­
ния и развертывания сервисов во всех средах. Одни и те же люди должны работать
над всеми средами, и с физической точки зрения все среды должны как можно
больше походить друг на друга. Очень важно обеспечить воспроизводимость. Поч­
ти любая проблема, которую мы обнаруживаем в рабочей среде, указывает на сбой
в процессе. Если в каком-то аспекте рабочая среда отличается от промежуточной,
этот аспект становится источником дополнительных рисков. Из-за подобных рас­
хождений проблемы в рабочей среде станут заметны, когда предотвращать их уже
будет слишком поздно.
Эта рекомендация перекликается с некоторыми из предыдущих, но здесь особое
внимание уделяется рискам, возникающим из-за различий между средами, и хотя
такие различия характерны для многих организаций, в контейнеризированной ин­
фраструктуре их проще избежать. Серверы Docker можно создавать идентичными
для всех сред, а изменения конфигурации в разных средах обьi4но затрагивают
только конечные точки, к которым подключается сервис, а не поведение прило­
жения.
Книги для программистов: https://clcks.ru/books-for-it
338
Глава 13
Журналы
Рассматривайте журналы как потоки событий.
Сервисы не должны отвечать за пересылку или хранение журналов. Пусть события
поступают без буферизации в потоки sтооuт и STDERR и обрабатываются за пределами
приложения. В среде разработки потоки sтооuт и STDERR можно легко просмотреть, а
в промежуточной и рабочей среде их можно перенаправлять куда угодно, в том
числе в центральный сервис журналирования. У разных сред свои особенности об­
работки журналов, и эту логику не следует записывать в приложение. Когда все
события поступают в потоки sтооuт и STDERR, менеджер процессов верхнего уровня
может обрабатывать журналы тем методом, который лучше подходит для той или
иной среды, чтобы разработчики могли сосредоточиться на основном функционале
приложения.
В главе 6 мы рассматривали команду ctocker container logs, которая собирает выход­
ные данные из потоков sтооuт и STDERR контейнера и записывает их в журналы. Если
хранить журналы в виде случайных файлов в файловой системе контейнера, полу­
чить к ним доступ будет трудно. Мы можем настроить Docker так, чтобы отправ­
лять журналы в локальную или удаленную систему журналирования с помощью
таких инструментов, как rsyslog, journald или fluentd.
Если мы используем на серверах менеджер процессов или систему инициализации,
вроде systemd или upstart, то можем легко направить вывод процесса в sтооuт и STDERR,
откуда монитор процессов будет записывать их и отправлять на удаленный хост
журналирования.
Администрирование
Задачи администрирования/управления должны выполняться как однократные
процессы.
Однократные задачи администрирования должны выполняться из той же кодовой
базы и конфигурации, которые использует приложение. Это позволяет избежать
проблем с синхронизацией и расхождениями в коде и схеме. Инструменты управ­
ления часто существуют в виде одноразовых скриптов или находятся в другой ко­
довой базе, хотя гораздо безопаснее создавать инструменты управления в кодовой
базе приложения и задействовать одинаковые библиотеки и функции для выполне­
ния нужных задач. При таком походе надежность возрастает, потому что инстру­
менты ориентированы на те же пути выполнения кода, что и приложение.
На практике это означает, что нам не следует выполнять задачи по администриро­
ванию и обслуживанию с помощью случайных скриптов в стиле cron. Гораздо эф­
фективнее будет включить все эти скрипты и функции в кодовую базу приложения.
Если они не должны выполняться в каждом экземпляре приложения, мы можем
запускать специальный краткосрочный контейнер или команду docker container ехес
в работающем контейнере, чтобы выполнить задачу по обслуживанию. Эта коман­
да активирует задание, сообщает о его статусе и завершает работу.
Книги для программистов: https://clcks.ru/books-for-it
Устройство контейнерной платформы
339
Заключение по 12 факторам
Манифест The Twelve-Factor Арр написан не конкретно про Docker, но все эти ре­
комендации можно применить к разработке и развертыванию приложений на плат­
форме Docker. Отчасти это связано с тем, что статья определила развитие самого
Docker, а также с тем, что манифест повлиял на многие подходы, которых придер­
живаются современные архитекторы программного обеспечения.
Манифест реактивных систем
В июле 2013 года сооснователь и технический директор Typesafe Джонас Бонер
(Jonas Boner) выпустил документ под названием The Reactive Manifesto (https://
www.reactivemanifesto.org/). В сотрудничестве с коллективом авторов он сформу­
лировал манифест о том, как в последние годы менялись ожидания относительно
устойчивости приложений и как приложения должны предсказуемо реагировать на
разные формы взаимодействия, включая события, пользователей, нагрузку и сбои
(https://www.lightbend.com/Ыog/why-do-we-need-a-reactive-manifesto).
В The Reactive Manifesto реактивными системами называются отказоустойчивые,
масштабируемые и событийно-ориентированные системы с быстрым откликом.
Быстрый отклик
Система отвечает сразу, если вообще может ответить.
Это значит, что приложение должно очень быстро реагировать на запросы. Пользо­
ватели не хотят ждать, и у нас редко бывают обоснованные причины медлить. Если
мы создаем контейнеризированный сервис для рендеринrа больших РDF-файлов,
он должен сразу отвечать пользователю, что получил задание, чтобы пользователь
мог заниматься своими делами, пока не получит еще одно сообщение о том, что
задача выполнена, а итоговый PDF можно загрузить по такому-то адресу.
Отказоустойчивость
Сис�:ема должна реагировать на запросы даже в случае сбоя.
Когда по какой-то причине произойдет сбой, будет хуже, если сервис перестанет
отвечать. Лучше корректно обработать сбой и динамически сократить функционал
приложения или хотя бы показать пользователю простое и понятное сообщение
о проблеме, а также сообщить о сбое команде сопровождения.
Масштабируемость
Система должна оставаться отзычивой при изменчивых рабочих нагрузках.
В Docker мы для этого динамически развертываем и завершаем контейнеры по
мере изменения спроса, так что приложение всегда быстро обрабатывает запросы
сервера, но при этом не расходует ресурсы зря.
Книги для программистов: https://clcks.ru/books-for-it
340
Глава 13
Ориентация на события
Реактивные системы используют асинхронную передачу сообщений, чтобы уста­
навливать границы между компонентами, обеспечивая слабую связанность, изоля­
цию и прозрачное расположение.
Хотя Docker не выполняет эти задачи напрямую, этот принцип подчеркивает, что
иногда приложение перегружено запросами или недоступно. Если мы передаем со­
общения между сервисами асинхронно, мы повышаем вероятность того, что запро­
сы не потеряются, и сервис обработает их, как только сможет.
Заключение
Все четыре принципа Манифеста реактивных систем требуют предусмотреть
«изящную» деградацию и определить четкое разгр·аничение обязанностей в прило­
жениях. В динамических контейнерных средах все зависимости обрабатываются
как хорошо спроектированные подключаемые ресурсы, поэтому мы можем под­
держивать число экземпляров N + 2 в стеке приложения, масштабировать отдель­
ные сервисы и быстро заменять неработоспособные узлы.
Надежность сервиса определяется самой ненадежной его зависимостью, поэтому
так важно учитывать эти принципы в каждом компоненте платформы.
Принципы The Reactive Manifesto хорошо сочетаются с принципами The Twelve­
Factor Арр и рабочим процессом Docker. Эти документы описывают подходы, ко­
торых следует придерживаться для успешной разработки приложений в соответст­
вии с требованиями отрасли. Многие из этих идей можно без особых сложностей
реализовать в любой организации с помощью Docker.
Книги для программистов: https://clcks.ru/books-for-it
ГЛАВА 14
Заключение
Вы уже получили неплохое представление об экосистеме Docker и видели много
примеров того, какие преимущества Docker и контейнеры Linux могут дать вашей
организации. Мы рассмотрели распространенные проблемы и рекомендации, осно­
ванные на многолетнем опыте использования контейнеров Linux в рабочей среде.
Как показывает опыт, преимущества Docker не так сложно реализовать, и они дей­
ствительно заметно повышают результативность. Как и другие эффективные тех­
нологии, Docker требует некоторых компромиссов, но в итоге приносит пользу и
команде разработчиков, и организации. Если вы реализуете рабочий процесс
Docker и интегрируете его в уже имеющиеся процессы, то заметите ощутимые пре­
имущества.
В этой главе мы рассмотрим место Docker среди других технологий, а также пого­
ворим о том, какие задачи решает Docker и какую пользу он принесет вашей орга­
низации.
Дальнейшее развитие
Очевидно, что контейнеры с нами надолго, хотя некоторые уже давно говорят, буд­
то скоро Docker потеряет свою популярность. В основном потому, что само слово
Docker вызывает много ассоциаций 1 • Мы говорим о компании с годовой выручкой
в 50 млн долларов, которая в 2019 году была продана Mirantis через два года после
реструктуризации? Или мы говорим о клиентском инструменте docker, исходный
код которого кто угодно может загрузить (https://github.com/docker/cli), изменить
и собрать?
Людям нравится предсказывать будущее, а реальность обычно лежит где-то по­
середине, скрытая под неочевидными деталями.
В 2020 году Kubernetes объявил о прекращении поддержки dockershim с выпуска
Kubernetes v 1.24 (https://kubernetes.io/Ыog/2022/02/17/dockershim-faq). На тот мо­
мент многие подумали, что пора прощаться с Docker, совершенно забывая о том,
что Docker - это, скорее, инструмент разработки, а не компонент рабочей среды.
Да, его можно применять в рабочей среде по разным причинам, но он предназначен
для того, чтобы оптимизировать упаковку и тестирование приложения. Kubernetes
задействует Container Runtime lnterface (CRI), который не был реализован Docker, а
1 Полный URL: https://www.tutorialworks.com/differenr.e-docker-containerd-runc-crio-oci.
Книги для программистов: https://clcks.ru/books-for-it
342
Глава 14
значит, требовалась дополнительная оболочка, dockershim, чтобы можно было ис­
пользовать Docker Engine через CRI. Это объявление не касается позиции Docker
в экосистеме, и такой шаг был сделан, скорее, чтобы упростить сопровождение
крупного опенсорс-проекта, развиваемого добровольцами. Может, Docker и не вы­
полняется на ваших серверах Kubernetes, но в большинстве случаев это никак не
повлияет на цикл разработки и выпуска программного обеспечения. Если вы не
используете Kubernetes с Docker CLI, чтобы напрямую отправлять запросы к кон­
тейнерам на узле Kubernetes, то вряд ли заметите какие-то изменения после этого
перехода.
Разработчики Docker создали и продолжают подцерживать новую оболочку shim,
называемую cri-dockerd (https://github.com/Мirantis/cri-dockerd), с помощью кото­
рой Kubernetes будет и дальше работать с Docker, если нам требуется такой рабо­
чий процесс.
Что любопытно, Docker также развивается в сторону неконтейнерных технологий,
вроде WebAssemЬ\y (Wasm, https://docs.docker.com/desktop/wasm), чтобы допол­
нить контейнеры и повысить простоту и скорость разработки.
Получается, что Docker как удобный набор инструментов останется с нами надол­
го, но при этом нам доступны и другие инструменты в экосистеме, чтобы при необ­
ходимости или при желании дополнить или заменить его. Благодаря различным
стандартам, вроде OCI, и их широкому применению, многие инструменты работа­
ют с образами и контейнерами, создаваемыми и администрируемыми другими ин­
струментами.
Зачем нужен Docker
Традиционное развертывание часто состоит из множества этапов, каждый из кото­
рых не только усложняет процесс для разработчиков, но и повышает риски, связан­
ные с поставкой приложения в рабочую среду. Docker сочетает рабочий процесс и
простой набор инструментов специально для упрощения этих задач. Он помогает
оптимизировать процессы разработки по стандартам отрасли, и его продуманный
подход часто повышает эффективность коммуникаций и надежность приложений.
Благодаря Docker и контейнерам Linux разработчикам не приходится:
♦ Решать проблемы с существенными различиями между средами развертывания.
♦ Каждый раз переписывать логику конфигурации и журналирования в приложе­
ниях.
♦ Использовать устаревшие процессы сборки и выпуска, которые требуют не­
сколько итераций взаимодействия между командами разработки и сопровож­
дения.
♦ Придерживаться сложных и ненадежных процессов развертывания.
♦ Управлять разными версиями зависимостей для приложений, которые размеще­
ны на одном оборудовании.
Книги для программистов: https://clcks.ru/books-for-it
Заключение
343
♦ Управлять несколькими дистрибутивами Linux в одной организации.
♦ Выстраивать одноразовые процессы развертывания для каждого приложения
в рабочей среде.
♦ Работать с уникальной базой кода каждого приложения при установке патчей
и проверке уязвимостей
и т. д.
Поскольку для передачи ответственности используется репозиторий, Docker упро­
щает коммуникации между командами сопровождения и разработчиками или меж­
ду несколькими командами разработчиков в одном проекте. Docker позволяет
объединить все зависимости приложения в один программный пакет, чтобы разра­
ботчики не задумывались о вариантах дистрибутивов Linux, версиях библиотек и
методах компиляции ресурсов или упаковки приложений. Команды сопровождения
не занимаются процессом сборки, и только разработчики отвечают за зависимости.
Рабочий процесс Docker
Рабочий процесс Docker помогает организациям решать по-настоящему сложные
проблемы - отчасти те же проблемы, для которых используются процессы
DevOps. Главное препятствие для успешного внедрения практик DevOps в компа­
нии - многие люди даже не представляют, с чего начать. Они считают, что с по­
мощью инструментов можно решить проблемы, которые на самом деле связаны
с непродуманными процессами. Когда мы добавляем инструменты виртуализации,
автоматизированного тестирования, разработки или управления конфигурациями,
мы просто меняем характер проблемы, не решая ее саму.
Docker можно было бы принять за очередной инструмент, который якобы исправит
ваши бизнес-процессы, но попробуйте посмотреть на него под другим углом. При
использовании Docker весь свой жизненный цикл, от изначальной задумки до пре­
кращения поддержки, приложение проводит в одной экосистеме. В отличие от дру­
гих инструментов, нацеленных на одиночный аспект конвейера DevOps, Docker
помогает заметно улучшить почти каждый шаг процесса. Этот рабочий процесс
часто предъявляет строгие требования, зато упрощает внедрение базовых принци­
пов DevOps. В результате разработчики начинают лучше понимать весь жизненный
цикл приложения, а специалисты по сопровождению могут поддерживать гораздо
более широкий ряд приложений в одной среде выполнения. В выигрыше остаются
все
. Меньше программных пакетов
для развертывания
Docker позволяет сократить количество программных пакетов, которые приходится
развертывать. Результат сборки умещается в одном образе Docker, который содер­
жит все, что требуется приложению в Linux для запуска, и все это работает в одной
Книги для программистов: https://clcks.ru/books-for-it
344
1
Глава 14
защищенной среде выполнения. Контейнеры легко развертываются в современных
дистрибугивах Linux, но благодаря четкому разделению между клиентом и серве­
ром Docker разработчики могуг собирать приложения и в другой операционной
системе, при этом удаленно используя среду контейнеров Linux.
Благодаря Docker разработчики создают образы Docker, которые с первой же про­
верки концепции можно запускать локально, тестировать с помощью автоматизи­
рованных инструментов, а затем развертывать в среде интеграции или рабочей сре­
де без пересборки. Мы можем быть уверены, что приложение, запущенное в рабо­
чей среде, в точности совпадает с приложением, которое мы тестировали. Нам не
приходится ничего заново компилировать или упаковывать при развертывании,
и это позволяет сократить риски, присущие большинству подходов к развертыва­
нию. Кроме того, мы получаем один этап сборки вместо трудоемкого и рискован­
ного процесса, состоящего из компилирования и упаковки нескольких сложных
распространяемых компонентов.
Образы Docker упрощают установку и конфигурацию приложения - все про­
граммные компоненты, которые нужны приложению в современном ядре Linux,
умещаются в образе, поэтому мы избегаем конфликтов зависимостей, часто возни­
кающих в традиционной среде. В результате мы можем запускать на одном сервере
несколько приложений, использующих разные версии системных компонентов.
Оптимизация хранения и получения
Docker использует слои файловой системы, чтобы можно было собирать контейне­
ры из нескольких образов. Мы экономим много времени и сил при развертывании,
потому что отправляем только важные изменения. Кроме того, контейнеры зани­
мают гораздо меньше места на диске, потому что задействуют один базовый образ
на нижнем слое, а также процесс копирования при записи для записи новых или
измененных файлов на верхний слой. Благодаря этому приложения проще масшта­
бировать, ведь можно запускать несколько копий на одних и тех же серверах, не
передавая по сети файлы для каждого нового экземпляра.
Для размещения и получения образов Docker применяет реестр, который помогает
четко разделить обязанности команд в соответствии с принципами DevOps. Разра­
ботчики собирают приложение, тестируют его, отправляют итоговый образ
в реестр, а затем развертывают образ в рабочей среде. Специалисты по сопровож­
дению, со своей стороны, создают инструменты для развертывания и управления
кластерами, чтобы извлекать образы из реестров, стабильно запускать их и гаран­
тировать их работоспособность. Команды сопровождения могуг отправлять разра­
ботчикам обратную связь и просматривать все результаты тестов на этапе сборки,
не дожидаясь, пока проблемы возникнуг в рабочей среде. Такой подход позволяет
обеим сторонам сосредоточиться на своих задачах, а не на пересылке друг другу
пакетов.
Книги для программистов: https://clcks.ru/books-for-it
Заключение
1
345
Результат
Вместе с опытом работы в Docker приходит осознание того, что контейнеры созда­
ют эффективный уровень абстракции между всеми программными компонентами и
базовой операционной системой. Организации могут постепенно отказываться от
создания индивидуальных физических серверов или виртуальных машин для
большинства приложений и вместо них развертывать идентичные хосты Docker,
которые представляют собой единый пул ресурсов для динамического развертыва­
ния приложений, тратя при этом гораздо меньше усилий.
Изменения коснутся не только процессов, но и общей культуры разработки в орга­
низации. Разработчики берут на себя больше ответственности за весь стек прило­
жения, включая мельчайшие детали, к_оторыми обычно занимались другие специа­
листы. Команды сопровождения больше не пытаются упаковывать и развертывать
сложные деревья зависимости, мало что понимая о самом приложении.
В хорошо организованном рабочем процессе Docker разработчики сами компили­
руют и упаковывают приложение, так что им проще обеспечить его корректную
работу в любых средах. При этом можно не беспокоиться о том, что специалисты
по сопровождению внесут заметные изменения в среду приложения. Команды
сопровождения, с другой стороны, занимаются поддержкой приложений, а также
созданием надежной и стабильной платформы для выполнения приложений. При
таком подходе формируется здоровая обстановка с четким разграничением обязан­
ностей по поставке приложения, и сложности между командами возникают гораздо
реже.
Хорошо продуманный рабочий процесс принесет преимущества не только компа­
нии, но и ее клиентам. Сотрудничество между командами приведет к повышению
качества программного обеспечения, оптимизации процессов и ускоренной постав­
ке кода в рабочую среду. Продуманный рабочий процесс Docker помогает органи­
зациям достигать стратегических целей - повышать качество обслуживания кли­
ентов и удовлетворять потребности бизнеса.
Итоги
Теперь у вас достаточно знаний для того, чтобы реализовать современный процесс
сборки и развертывания на основе контейнеров. Поэкспериментируйте с Docker
в небольшом масштабе, на ноутбуке или виртуальной машине, чтобы лучше по­
нять, как все эти компоненты взаимодействуют друг с другом, а потом подумайте
о том, как постепенно внедрять этот подход в своей организации. У каждой компа­
нии или отдельного разработчика будет свой путь в зависимости от уникальных
потребностей и навыков. Если не знаете, с чего начать, попробуйте улучшить раз­
вертывание с помощью простых инструментов, а затем перейдите к таким задачам/
как обнаружение сервисов или распределенное планирование. На основе Docker
можно создать систему любой сложности, но начать всегда можно с простого.
Надеемся, у вас получится применить новые знания и воспользоваться преимуще­
ствами Docker и контейнеров Linux.
Книги для программистов: https://clcks.ru/books-for-it
Предметный указатель
А
Amazon Elastic Container Service,
Amazon ECS 261
Amazon Identity and Access Management,
Amazon IАМ 262
AnsiЫe 48
AnsiЫe Docker 49
Application Performance Monitoring, АРМ 218
в
BuildKit 47, 99, 103, 105
с
Capistrano 45
Centurion 49
Chocolatey 56
Chocolatey for Windows 53
Cloud Native Computing Foundation, CNCF 33
CodeShip 47
Colima 63
Command Line Interface, CLI 28
Completely Fair Scheduler, CFS 125
Consul 44
D
DevOps 23, 30, 94, 222, 343
Docker
◊ CLI 37
◊ демон 36, 300
◊ клиент 37
◊ клиент установка 52
◊ компоненты 315
◊ образ 67
◊ преимущества 25
◊ пространства имен 281
◊ сервер установка 64
◊ структура 316
◊ топологии 307
Docker CLI 37, 156
Docker Compose 187
Docker Registry 82
Docker Swarm 228
Dockerfile 46, 68
dockprom 169
Domain Name Service, DNS 118
Е
Elastic Kubernetes Services, EKS 261
F
Fabric 48
G
gRPC API 317
gVisor 320
н
Harbor 77
HashiCorp Packer 43
HashiCorp Vault 334
Helios 49
Heroku 31
Homebrew 55, 240
Homebrew for macOS 53
J
Jenkins 47
к
Kind 259
Kubernetes 49, 238
Книги для программистов: https://clcks.ru/books-for-it
Предметный указатель
L
Linux Containers, LXC 114
Linux Kernel-based Virtual Machine,КУМ 241
м
МАС-адрес 118,293
Minikube 239
◊ установка 240
N
Network Address Translation,NAТ 303
Nomad 49
о
Open Container Initiative (OCI) 67,317
Open Container Initiative,OCI 34
р
Podman Desktop 328
PostgreSQL 44
Prometheus 50,164,166
R
Rancher 49
Rancher Desktop 327
Red Hat Quay 77
ReplicaSet 254
Rocket.Chat 198,333
5
Secure Computing Mode 295
Security-Enhanced Linux,SELinux 42
SELinux 120,286,299
т
The Reactive Manifesto 339
The Twelve-Factor Арр 330
Travis CI 47
V
Vagrant 28,58
347
Б
Балансировщик нагрузки 220
Библиотека
◊ musl 76
◊ vpnkit 39
г
Гипервизор 59,112
д
Динамическое выделение томов 251
ж
Журналирование 152,217
и
Инструкция
◊ ARG 74
◊ ENTRYPOINT 109
◊ ENV 69,74
◊ RUN 69
◊ WORКDIR 70
Инструмент
◊ gVisor 320
◊ kind 259
◊ podman 325
◊ Podman Desktop 328
к
Клиент Docker 28
Команда
◊ build 72
◊ docker compose top 205
◊ docker container create 114
◊ docker container diff 186,188
◊ docker container ехес 149
◊ docker container kill 133
◊ docker container logs 152
◊ docker container 1s 73
◊ docker container pause 132
◊ docker container stats 156
◊ docker container stop 75
◊ docker container top 171
◊ docker container update 125
Книги для программистов: https://clcks.ru/books-for-it
348
Предметный указатель
◊ docker image history 93, 183
◊ docker image inspect 69
◊ docker network inspect 181, 306
◊ docker network 1s 180
◊ docker search 81
◊ docker secret 334
◊ docker stack 235
◊ docker system info 143
◊ docker system prune 135
◊ docker version 142
◊ dockerimage push 81
◊ kubectl get pods 255
◊ mount 117
◊ ps 174
◊ ulimit 129
◊ volume 150
Команды для управления заданиями 215
Конвейер непрерывной интеграции 222
Контейнер 112
◊ остановка 131
◊ перезапуск 131
◊ приостановка 134
Контейнеризация 23
Контроль доступа 300
м
Мост 39
о
Обнаружение сервисов 221
Образ
◊ Docker 29
◊ базовый 75
◊ загрузка обновлений 144
◊ запуск 73
◊ история 183
◊ манифест 111
◊ многоэтапная сборка 90
◊ оптимизация 85
◊ отладка 105
◊ отправка в репозиторий 80
◊ сборка 70
Оркестрация 219
п
Планировщик 21 8
Платформа 213
Под 246
Проблема "шумный сосед" 121
Процесс тестирования приложения 223
р
Развертывание 247
Реестр 35
◊ образов
а публичный 76
частный 77
Режим моста 39
Ресурсы
◊ ввода-вывода 128
◊ ограничение 215
◊ памяти 126
◊ процессора 122
0
с
Сервер Docker 28
Среда выполнения 34
◊ низкоуровневая 34
ф
Файл
◊ config.json 319
◊ daemon.json 154, 167
◊ docker-compose.yaml 190, 235
◊ docker-compose-env.yaml 209
◊ dotenv 210
◊ lazyraster-service.yaml 250
◊ Vagrantfile 60
◊ webgame-task.json 265
Файловые системы 309
х
Хранилище 67
◊ внешнее 44
Книги для программистов: https://clcks.ru/books-for-it
06 авторах
Шон Кейн - основатель компании techlabs.sh (https://techlabs.sh/) и главный про­
изводственный инженер в SuperOrЬital (https://superorbltal.io/). Шан специализи­
руется на процессах DevOps, включая Kubernetes, Docker, Terraform и т. д., не толь­
ко реализуя их в работе, но и обучая этому других. У него большой опыт в сопро­
вождении программного обеспечения в разных отраслях. Шан - изобретатель и
автор патентов, связанных с контейнерами, который любит писать и говорить об
этой технологии. Он обожает путешествовать и ходить в походы. Шан живет на
северо-западном побережье США с женой, детьми и собаками.
Карл Маттиас - вице-президент по архитектуре в Community.com (https://www.
community.com/). Более 25 лет он работает главным инженером и руководителем
в известных технических компаниях. Ему нравятся сложные задачи, распределен­
ные системы, Go, Ruby, Elixir, масштабируемые хранилища данных, автоматизиро­
ванные инфраструктуры и повторяемые системы.
Книги для программистов: https://clcks.ru/books-for-it
06 изображении на обложке
На обложке данной книги изображен синий кит (Balaenoptera musculus). Синие
киты достигают 33 метров в длину и могут весить 200 тонн. Это самое крупное
современное животное и самое крупное из когда-либо существовавших на Земле.
Детеныш синего кита рождается размером с бегемота и прибавляет по 90 кг в день.
Взрослые киты имеют длинное и узкое тело, небольшой спинной плавник, два
плавника по бокам и горизонтальный хвост. Свое название киты получили за сине­
серый окрас.
Синие киты мигрируют и обитают во всех океанах. Обычно они кормятся в холод­
ных полярных регионах, а затем отправляются в более теплые тропические воды
для размножения. Синие киты перемещаются поодиночке или парами и общаются
с помощью сложных звуковых сигналов. Синие киты входят в семейство полосати­
ковых (balaenopteridae), а значит, питаются, пропуская добычу через костные пла­
стины, называемые китовым усом. Питаются почти исключительно планктонными
ракообразными. В день они расходуют 1,5 млн ккал и съедают до 3500 кг планкто­
на. Из-за своих размеров и способности быстро перемещаться практически не име­
ют естественных хищников.
Раньше этот вид был широко распространен и насчитывал сотни тысяч особей.
Киты были слишком быстрыми и большими животными для охотников, но с изо­
бретением гарпунного ружья в конце XVIII века китобои научились успешно охо­
титься на синего кита. За долгие десятилетия китовой охоты популяция значитель­
но сократилась. В 1966 году охота на синего кита была запрещена по всему миру.
Это по-прежнему вымирающий вид, хотя теперь его численность растет.
O'Reilly часто размещает на обложках виды, которым угрожает вымирание, и все
они очень важны для мира. Если вы хотите поддержать китовую популяцию, то
можете стать волонтером или внести пожертвование в организацию по охране
китов, например в Общество по охране китов и дельфинов (https://whales.org/).
Иллюстрация на обложке выполнена Карен Монтгомери (Karen Montgomery) на
основе черно-белой гравюры из книги "А History of British Quadrupeds, Including the
Cetacea".
Книги для программистов: https://clcks.ru/books-for-it
Шон П. Кейн, Карл Мапиас
Docker. Вводный курс
3-е издание
Перевод с английского М. Попович
ТОО"АЛИСТ'
010000, Республика Казахстан,
г. Астана, пр. Сарыарка, д. 17, ВП 30
Подписано в печать 23.07.24.
Формат 70х100 1/16 . Печать офсетная. Усл. печ. л. 28,38.
Тираж 1500 экз. Заказ № 10116.
Отпечатано с готового оригинал-макета
ООО "Принт-М", 142300, РФ, М.О., г. Чехов, ул. Полиграфистов, д.
Книги для программистов: https://clcks.ru/books-for-it
Книги для программистов: https://clcks.ru/books-for-it