Программирование и IT
Что такое Docker
Docker упаковывает программу вместе со всем, что ей нужно для запуска, в один образ — и этот образ одинаково стартует на ноутбуке разработчика и на сервере. Ниже — что такое образ, чем он отличается от контейнера, как написать первый Dockerfile и где эта технология лишняя.
В этой статье
Какую боль он снимает#
Программа редко состоит из одного файла. Ей нужна определённая версия языка,
десяток библиотек, переменные окружения, иногда системные пакеты. На машине
автора всё это уже стоит, на машине коллеги — другой версии, на сервере — третьей.
Отсюда вечное «у меня работает».
Docker решает это переносом границы: упаковывается не программа, а целиком
слепок файловой системы, внутри которого программа уже установлена и настроена.
Запускается этот слепок одинаково везде, где есть Docker.
Виртуальная машина решает ту же задачу иначе — она несёт в себе полную
операционную систему с собственным ядром, весит десятки гигабайт и стартует
минутами. Контейнер пользуется ядром хозяйской системы и изолирован средствами
самого ядра, поэтому весит десятки мегабайт и поднимается за доли секунды. На
macOS и Windows Docker всё же держит одну маленькую виртуальную машину с Linux —
контейнеры внутри неё.
Образ и контейнер#
Путаница между этими словами мешает понять всё остальное, поэтому разберём её
сразу.
Образ — неизменяемый шаблон, набор слоёв файловой системы плюс описание,
что запускать. Его собирают один раз, хранят в реестре, скачивают. Изменить
образ нельзя, можно только собрать новый.
Контейнер — запущенный экземпляр образа с добавленным поверх слоем для
записи. Из одного образа можно поднять десять контейнеров, они не видят файлы
друг друга.
Ближайшая аналогия из программирования: образ — класс, контейнер — объект.
Из установочного диска тоже можно поставить систему на сколько угодно машин, а
сам диск от этого не меняется.
Практическое следствие: всё, что контейнер записал в свой слой, исчезает при
его удалении. Первый неприятный опыт почти у всех одинаков — база данных
прожила в контейнере неделю и пропала вместе с ним. Лечится томами, о них
ниже.
Первые команды#
Первая строка скачает образ и запустит программу внутри, вторая поднимет
Ubuntu и бросит тебя в её оболочку, третья запустит веб-сервер в фоне:
Разбор ключей последней строки: -d — работать в фоне, -p 8080:80 — порт
8080 на твоей машине ведёт на порт 80 внутри контейнера, --name web — имя,
по которому дальше к нему обращаться, nginx — образ.
Порядок в -p запоминается так: слева то, что снаружи, справа — что внутри.
Если поменять местами, браузер по адресу localhost:8080 ничего не найдёт.
docker logs — первое, что смотрят, когда контейнер запустился и сразу упал.
Вторая подсказка там же: docker ps -a покажет код выхода в столбце состояния.
Образы копятся незаметно и занимают гигабайты. Проверить расход поможет
docker system df, освободить место — docker system prune; команда удаляет
остановленные контейнеры и неиспользуемые образы, поэтому читай её вопрос
перед подтверждением.
Dockerfile#
Образ описывают текстовым файлом с именем Dockerfile. Каждая инструкция —
отдельный слой.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PORT=8000
EXPOSE 8000
CMD ["python", "app.py"]
Построчно:
FROM— основа, готовый образ из реестра. Варианты с пометкойslimили
alpineвесят в разы меньше полных.WORKDIR— рабочий каталог внутри образа, дальше все пути считаются от него.COPY— перенести файлы из твоей папки в образ.RUN— выполнить команду во время сборки.ENV— переменная окружения.EXPOSE— пометка о занятом порте; сама по себе она ничего не открывает,
порт публикует ключ-pпри запуске.CMD— что запустить, когда контейнер стартует.
Почему список зависимостей копируется отдельно и раньше остального кода: Docker
кеширует слои и пересобирает только те, что после изменившегося. Правка одной
строки кода при таком порядке не заставляет заново скачивать все библиотеки.
Поменяешь порядок — каждая сборка будет длиться минутами вместо секунд.
Рядом кладут .dockerignore — список того, что в образ не попадёт: каталог
.git, локальные виртуальные окружения, файлы с ключами. Файл устроен как
.gitignore и заодно ускоряет сборку.
Собрать и запустить:
Точка в конце build — это контекст сборки, каталог, откуда берутся файлы для
COPY. Метка -t даёт образу имя и версию; без версии подставится latest,
и через месяц ты не поймёшь, что именно работает на сервере.
Данные: тома#
Чтобы данные пережили удаление контейнера, их выносят наружу.
Именованный том Docker создаёт и хранит сам:
Второй способ — подставить внутрь каталог со своей машины:
Первый вид — именованный том: подходит для баз, работает быстро, лежит в
хозяйстве Docker. Второй — монтирование каталога: правка файла у тебя тут же
видна внутри, поэтому им пользуются при разработке.
Учебная база, поднятая такой командой, — самый простой способ тренировать
SQL-запросы без установки сервера на свою систему:
надоело — удалили контейнер и том.
Когда сервисов больше одного, команды перестают помещаться в голове, и их
описывают в файле compose.yaml:
Сервисы из одного файла видят друг друга по именам: приложение подключается к
базе по адресу db, а не по IP.
Когда Docker не нужен#
Технология стоит времени на изучение и добавляет слой, в котором тоже бывают
поломки. Есть задачи, где он лишний.
- Один скрипт на своей машине. Виртуальное окружение языка решает вопрос
зависимостей и не требует ничего ставить. - Статический сайт. Готовые страницы раскладываются файлами, упаковывать
нечего. - Программа с графическим окном. Прокинуть интерфейс наружу можно, но
возни больше, чем пользы. - Тяжёлая работа с диском на macOS и Windows. Монтированные каталоги там
идут через виртуальную машину и заметно медленнее.
И обратная сторона: контейнер не делает программу безопасной автоматически.
Запуск от суперпользователя внутри, ключи, вшитые в образ, ключ --privileged
на всякий случай — всё это сводит изоляцию к нулю.
Частые ошибки#
Контейнер стартует и сразу останавливается. Контейнер живёт ровно столько,
сколько работает его главный процесс. Программа завершилась — контейнер
закрылся. Смотри docker logs.
Порт занят. bind: address already in use означает, что порт слева в -p
уже кем-то взят; найти виновника поможет ss -tulpn — команда разобрана в
шпаргалке по командам Linux.
Изменения в коде не видны. Образ собран со старой копией файлов. Либо
пересобери, либо смонтируй каталог ключом -v.
Ключи и пароли внутри образа. Всё, что попало в слой, достаётся из образа
кем угодно, даже если следующая инструкция это удалила. Секреты передают
переменными окружения при запуске — тот же принцип, что и с
ключами доступа к API.
План по этапам
- Запустить чужой образПоднять nginx командой docker run с пробросом порта и открыть страницу в браузере.
- Осмотреться внутриЗайти в работающий контейнер через docker exec, посмотреть файлы, выйти и убедиться, что контейнер жив.
- Собрать свой образНаписать Dockerfile из пяти инструкций для маленькой программы и собрать его с меткой версии.
- Проверить кеш слоёвИзменить строку кода, пересобрать и сравнить время с первой сборкой; затем переставить COPY и повторить.
- Сохранить данныеПоднять базу с именованным томом, записать строку, удалить контейнер, поднять заново и найти строку на месте.
Начать изучать эту тему у себя
План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.
Проверь себя
1.Какая команда покажет список работающих контейнеров?
2.Контейнер запущен с ключом -p 8080:80. По какому порту к нему обращаться с твоей машины?
3.Что получается в результате выполнения docker build по Dockerfile?
4.Что нужно, чтобы данные базы пережили удаление контейнера?
Источники
-
Документация DockerПервоисточник: справочник по Dockerfile, командам и composeбесплатно
-
Реестр образов Docker HubОфициальные образы языков и баз данных с описанием переменныхбесплатно
-
Документация PostgreSQLПригодится, когда база поднята контейнеромбесплатно
Было полезно?