Запуск контейнеров и сохранение данных
Что это такое
Запустить контейнер — значит стартовать процесс из образа с некоторой конфигурацией: значения окружения, опубликованные порты, подключённое хранилище и имя. Контейнеры обычно недолговечны — один процесс, одна задача, — и интересная работа идёт вокруг них: как они находят друг друга, где живут их данные и как наблюдают за их логами и кодами возврата. Именно это окружение решает, станет ли контейнеризованная система управляемой или превратится в задачу по отладке.
Почему это важно
Обещание контейнера в том, что он одноразовый, и одноразовость безопасна только пока внутри него нет ничего ценного. Почти каждый болезненный инцидент с контейнерами — нарушение этого правила: база данных, записанная в записываемый слой и потерянная при перезапуске, загруженные файлы, исчезнувшие вместе с заменённым контейнером, два контейнера, не видящие друг друга, потому что оказались в разных сетях. Правильно устроенное окружение и позволяет заменять контейнер не задумываясь.
Ключевые идеи
Контейнеры — именованные процессы, а не машины
Контейнер запускает один процесс на переднем плане, и когда этот процесс завершается, контейнер останавливается. Поэтому политика перезапуска заменяет супервизор, и поэтому запуск демона и веб-сервера в одном контейнере ведёт к путанице: жизнь контейнера измеряется только одним из них. Конфигурация приходит через значения окружения и подключённые файлы.
docker run --env PORT=8080 --publish 8080:8080 --name api myapp:1.0
процесс завершился с 0 -> контейнер остановлен
процесс завершился с 1 -> контейнер остановлен и помечен как сбойный
Сети дают контейнерам имена для связи
Контейнеры, подключённые к одной пользовательской сети, видят друг друга по имени контейнера, и именно так сервис находит свою базу данных без жёстко прописанного адреса. Старая сеть по умолчанию этого не даёт, и её использование — обычная причина, по которой пример с двумя контейнерами не работает. На современном хосте или в оркестраторе сеть часто создаётся и управляется за вас, но принцип тот же.
Тома живут дольше контейнеров и остаются единственным долговечным диском
Именованный том лежит вне записываемого слоя контейнера и переживает его удаление; bind-монтирование напрямую отображает каталог хоста. И то, и другое — способ сохранить состояние между перезапусками. Решить нужно, какое состояние вообще должно лежать на машине: в настоящем развёртывании оно обычно живёт в управляемом сервисе, а том остаётся для кэшей и временного места.
именованный том: живёт после удаления контейнера, им управляет движок
bind-монтирование: путь хоста, удобно для разработки
записываемый слой: исчезает вместе с контейнером
Логи идут в стандартный вывод, а не в файлы
Поскольку контейнер умирает вместе со своими файлами, полезное соглашение — писать логи и ошибки в стандартный вывод и стандартный поток ошибок и позволять окружающей платформе их собирать. Лог-файл внутри контейнера становится невидимым в момент замены контейнера.
Коды возврата управляют перезапуском и проверками работоспособности
Код возврата ноль означает успех, ненулевой — отказ, и окружающая система по нему решает, перезапускать, повторять или оповещать. Процесс, проглотивший ошибку и завершившийся с нулём, неотличим от отработавшего успешно, — так тихо сломанное задание продолжает рапортовать об успехе.
Compose и оркестраторы формализуют те же идеи
Запускать несколько контейнеров вручную быстро становится неудобно. Compose описывает несколько контейнеров, их сеть и тома для локальной разработки; оркестратор делает то же на множестве машин, добавляя планирование и самовосстановление. И то, и другое — удобство над одними и теми же решениями: образ, окружение, сеть, том, политика перезапуска.
Практика
Запустите сервис с томом, который его переживёт
Запустите контейнер, который пишет файл в подключённый каталог, удалите контейнер и запустите новый с тем же томом.
Готово, когда: второй контейнер читает файл, записанный первым.
Соедините два контейнера по имени
Запустите небольшой веб-сервис и базу данных в одной пользовательской сети и заставьте сервис обращаться к базе по имени контейнера, а не по адресу.
Готово, когда: сервис подключается по имени, и вы можете объяснить, почему в сети по умолчанию это не сработало бы.
Частые ошибки
- Писать важные данные в записываемый слой — они теряются вместе с контейнером; исправить: используйте том или управляемое хранилище.
- Запускать несколько служб в одном контейнере — время жизни контейнера отслеживает только одну из них; исправить: один процесс на контейнер.
- Полагаться на сеть по умолчанию для разрешения имён — контейнеры не находят друг друга по имени; исправить: подключите их к пользовательской сети.
- Логировать в файл внутри контейнера — доказательства исчезают вместе с контейнером; исправить: пишите в стандартный вывод, а собирать пусть платформа.
- Завершаться с нулём после ошибки — для любого супервизора отказ выглядит как успех; исправить: возвращайте ненулевой код, когда работа не выполнена.
Что читать дальше
- Образы и слои (06-cloud-devops/06-docker/01-images-and-layers) — как собирается артефакт, который здесь запускается.
- Docker (06-cloud-devops/06-docker) — модель упаковки, стороной выполнения которой является этот дочерний урок.
- Kubernetes (06-cloud-devops/14-kubernetes) — что приходит на смену ручным
runиcompose, когда появляется несколько машин и сервисов.