Когда количество серверов перевалило за сотню, а ночные дежурства стали нормой из-за расхождений окружений, я понял: ручная настройка пакетов и правка конфигов по SSH — путь в никуда. Docker стал тем инструментом, который позволил перейти от тушения пожаров к воспроизводимым деплоям. Это не «игрушка для разработчиков», а фундамент надёжности для привычных сервисов: Zabbix, Nginx, PostgreSQL, Redis. В этой статье я покажу, как использовать Docker с позиции инженера по надёжности — с акцентом на сохранность данных, сетевую изоляцию, автоматизацию и типовые грабли, на которые сам наступал.
Почему сисадмин должен перейти на Docker: от ручного администрирования к IaC
Классический подход — зайти по SSH, выполнить apt install, поправить конфиг в vim — работает на десятке серверов. Но при масштабе 50+ машин начинается хаос: окружение на продакшене дрейфует относительно тестового, версии библиотек не совпадают, а восстановление после сбоя превращается в многочасовой квест. Docker решает эту проблему через упаковку всего необходимого в образ — код, среду выполнения, зависимости и настройки. Такой образ запускается одинаково на любом хосте, независимо от версии ОС. Это первый шаг к Infrastructure as Code: инфраструктура описывается декларативно, версионируется и автоматизируется, как код приложения.
Ключевые преимущества контейнеризации для админа
| Преимущества | Описание |
|---|---|
| Идентичность среды | Образ содержит всё вплоть до системных библиотек. Сервис работает одинаково на ноутбуке разработчика, в CI и на продакшене. Это убивает классическое «на моей машине всё работает» и снижает количество инцидентов, связанных с окружением. |
| Изоляция сервисов | Контейнеры разделяют пользовательские пространства, но используют общее ядро хоста. Это даёт плотность упаковки выше, чем у виртуальных машин, и при этом каждый сервис живёт в своей песочнице. Однако не стоит забывать, что уязвимость ядра хоста компрометирует все контейнеры — поэтому патчинг хоста остаётся критичным. |
| Быстрый деплой | Вместо часовой установки пакетов и разрешения зависимостей — одна команда docker run. На практике это сокращает время развёртывания нового инстанса Zabbix или Nginx с минут до секунд, а откат к предыдущей версии делается сменой тега образа. |
| Версионирование инфраструктуры | Образы хранятся в реестре (Docker Hub, приватный registry) и тегируются. Вы можете точно зафиксировать версию PostgreSQL 15.3 и быть уверенным, что она не изменится при следующем деплое. В связке с Terraform или Ansible это даёт полную воспроизводимость окружения. |
| Отказоустойчивость | При падении контейнера его можно автоматически перезапустить с помощью политик перезапуска (--restart=always). Данные, вынесенные в volumes, не зависят от жизненного цикла контейнера, поэтому даже удаление контейнера не приводит к потере информации — при условии, что volume настроен правильно. |
Когда Docker не нужен
Docker — не серебряная пуля. Есть сценарии, где его использование неоправданно или даже вредно:
- Сервис требует прямого доступа к ядру ОС: специфические драйверы, модули безопасности вроде SELinux с кастомными политиками, некоторые low-level сетевые утилиты. Контейнеризация здесь добавит прослойку, которая усложнит отладку.
- Устаревшие системы: если у вас зоопарк из CentOS 6 и Windows Server 2008, где установка Docker Engine невозможна или не поддерживается, проще временно автоматизировать их через Ansible, а миграцию планировать позже.
- Высоконагруженные базы данных без тюнинга: из коробки overlay-сеть и файловая система контейнера могут давать накладные расходы по I/O. Для чувствительных к задержкам систем стоит проводить бенчмарки и, возможно, использовать host-сеть или вовсе оставить базу на голом железе, пока не созреет решение с Kubernetes и локальными томами.
В большинстве же случаев для привычных сервисов — веб-серверов, мониторинга, кэшей — Docker становится стандартом, который упрощает жизнь и снижает вероятность человеческой ошибки.
Установка Docker Engine на Linux: база для работы
Для работы с контейнерами на Linux нужен Docker Engine — фоновый демон dockerd, CLI-клиент и инструменты для управления образами и сетями. Я рекомендую ставить его из официального репозитория, а не через snap: snap-версия иногда ведёт себя нестандартно с путями к сокету, что ломает интеграцию с инструментами оркестрации.
Инструкция для Ubuntu 22.04 и Debian
- Обновите пакетную базу:
sudo apt update - Установите Docker Engine и зависимости:
sudo apt install docker.ioЛибо, если хотите самую свежую версию, добавьте официальный репозиторий Docker и установите
docker-ce. Но для большинства задач пакетаdocker.ioдостаточно. - Запустите и активируйте сервис:
sudo systemctl start docker sudo systemctl enable docker - Проверьте установку:
docker --version sudo docker run hello-world
Установка на macOS и Windows
На macOS и Windows используется Docker Desktop — приложение, которое под капотом запускает виртуальную машину Linux и внутри неё Docker Engine. Скачайте установщик с официального сайта, установите и запустите. После этого команды docker будут доступны в терминале. Для production на Windows Server я бы советовал использовать WSL2 или нативную Linux-ВМ — Docker Desktop там не предназначен для высоких нагрузок.
Типовые ошибки при установке
- Ошибка
Permission denied: по умолчанию доступ к сокету Docker имеет только root. Можно добавить пользователя в группуdocker:sudo usermod -aG docker $USERи перелогиниться. Но помните: это даёт пользователю фактически root-права на хосте, поэтому в продакшене лучше использовать sudo или rootless-режим. - Сервис не запускается: проверьте статус через
sudo systemctl status docker. Часто проблема в конфликте с другими демонами или нехватке места в/var. Логи смотрите черезjournalctl -u docker. - Docker не видит контейнеры: если демон остановлен, перезапустите его:
sudo systemctl restart docker. Убедитесь, что нет проблем с файловой системой overlay2.
Основы работы с контейнерами: образы, запуск, управление
Что такое образ и контейнер
Образ (Image) — это неизменяемый шаблон, содержащий код приложения, среду выполнения, библиотеки, переменные окружения и конфигурационные файлы. Контейнер (Container) — запущенный экземпляр образа, к которому добавляется слой для записи. Можно провести аналогию с классами и объектами: образ — класс, контейнер — объект. Один образ может породить множество контейнеров, каждый со своим состоянием.
Запуск контейнера: команда docker run
Основная команда для запуска контейнера:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Ключевые опции:
-d— запуск в фоновом режиме (daemon). Если опустить, логи пойдут в stdout, что удобно при отладке.-p 8000:8000— проброс порта: порт 8000 на хосте мапится на порт 8000 в контейнере.--name— задать имя контейнеру, чтобы не использовать ID.
Пример запуска веб-сервера Nginx:
docker run -d -p 8080:80 --name my-nginx nginx:latest
После запуска проверьте доступность: curl http://localhost:8080. Если контейнер сразу падает, смотрите логи: docker logs my-nginx.
Управление контейнерами
| Команда | Описание |
|---|---|
docker ps |
Показать запущенные контейнеры |
docker ps -a |
Показать все контейнеры (включая остановленные) |
docker stop <container> |
Остановить контейнер |
docker start <container> |
Запустить остановленный контейнер |
docker rm <container> |
Удалить контейнер (безвозвратно, если не используются volumes) |
docker images |
Показать все скачанные образы |
docker rmi <image> |
Удалить образ |
Для отладки незаменимы docker logs <container> и docker exec -it <container> /bin/bash — последняя запускает shell внутри контейнера.
Пример: запуск PostgreSQL
docker run -d \
--name postgres-db \
-e POSTGRES_PASSWORD=mysecret \
-v postgres-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:15
-e POSTGRES_PASSWORD=mysecret— переменная окружения для пароля суперпользователя.-v postgres-data:/var/lib/postgresql/data— volume для сохранения данных. Критически важно! Без него все данные исчезнут при удалении контейнера. На заре контейнеризации я потерял тестовую базу именно так — с тех пор volume всегда первый пункт в чек-листе.
Volumes и Bind Mounts: как не потерять данные при перезапуске
Контейнер по своей природе эфемерен: при удалении или пересоздании все изменения внутри его файловой системы теряются. Для сохранения данных между запусками используются тома (volumes) и монтирование каталогов хоста (bind mounts).
Volumes vs Bind Mounts
| Тип | Описание | Пример использования |
|---|---|---|
| Volume | Управляется Docker, хранится в /var/lib/docker/volumes/. Идеален для данных баз данных, так как Docker обеспечивает изоляцию и возможность лёгкого бэкапа через плагины. |
-v postgres-data:/var/lib/postgresql/data |
| Bind Mount | Прямая ссылка на директорию или файл хоста. Удобно для конфигов и логов, которые нужно менять на лету без пересборки образа. | -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf |
На практике для production-данных я всегда выбираю volume — меньше проблем с правами доступа и переносимостью. Bind mount хорош для разработки или когда нужно шарить конфиги между хостом и контейнером.
Создание и использование Volume
- Создать volume:
docker volume create my-volume - Запустить контейнер с volume:
docker run -d -v my-volume:/data --name my-container alpine
Bind Mount для конфигов
Для веб-сервера Nginx часто нужно подложить свой конфиг:
docker run -d \
-p 80:80 \
-v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-v /usr/share/nginx/html:/usr/share/nginx/html \
nginx:latest
Флаг :ro монтирует файл в режиме только для чтения — хорошая практика безопасности. Важно: пользователь внутри контейнера (обычно nginx или www-data) должен иметь доступ к файлам на хосте. Если uid/gid не совпадают, получите 403 Forbidden, и это будет неочевидно. Я обычно заранее проверяю права или собираю кастомный образ с подходящим пользователем.
Проверка данных
После запуска контейнера с volume данные сохраняются даже при удалении контейнера:
docker rm -f my-container
docker run -d -v my-volume:/data --name new-container alpine
Данные останутся на месте. Для бэкапа volume можно использовать временный контейнер:
docker run --rm -v my-volume:/data -v $(pwd):/backup busybox tar czf /backup/backup.tar.gz -C /data .
Сетевая модель Docker: проброс портов, сети и изоляция
Проброс портов: флаг -p
Проброс портов связывает порт хоста с портом контейнера, делая сервис доступным извне. Синтаксис: -p [host_ip:]host_port:container_port. Если не указать IP, порт будет слушать на всех интерфейсах (0.0.0.0), что в продакшене может быть небезопасно. Лучше явно указать IP, если сервис должен быть доступен только локально: -p 127.0.0.1:8080:80.
Если порт уже занят на хосте, Docker выдаст ошибку. Проверьте занятость:
sudo ss -tulpn | grep :8080
Либо используйте другой порт, либо освободите текущий.
Сети Docker: изоляция сервисов
Docker предоставляет несколько драйверов сетей:
bridge— стандартная изолированная сеть для контейнеров на одном хосте.host— контейнер использует сеть хоста напрямую, без изоляции. Полезно для высокопроизводительных приложений, но теряется сетевая песочница.none— контейнер без сети.
Для взаимодействия нескольких контейнеров создайте пользовательскую bridge-сеть:
docker network create my-network
Запустите контейнеры в этой сети:
docker run -d --name app --network my-network my-app
docker run -d --name db --network my-network postgres
Теперь контейнер app может обращаться к db по имени хоста db — это работает благодаря встроенному DNS-серверу Docker. В гибридных средах, где часть сервисов на bare-metal, а часть в облаке, такой подход упрощает связность, но для multi-host потребуется overlay-сеть и оркестратор (Swarm/Kubernetes).
Типовые ошибки сети
- Контейнер не доступен извне: проверьте, что порт проброшен (
-p) и не заблокирован фаерволом хоста. - Контейнеры не видят друг друга: убедитесь, что они в одной сети (
--network). Проверьте DNS внутри контейнера:docker exec app nslookup db. - Ошибка
Address already in use: порт занят на хосте. Используйте другой порт или остановите процесс, который его слушает.
Деплой привычных сервисов: пошаговые кейсы
Кейс 1: Zabbix для мониторинга
Zabbix в контейнере разворачивается за пару минут, но требует связки с базой данных. Я обычно запускаю MySQL или PostgreSQL отдельным контейнером и объединяю их в одну сеть.
# Сеть
docker network create zabbix-net
# База данных PostgreSQL
docker run -d \
--name zabbix-db \
--network zabbix-net \
-e POSTGRES_USER=zabbix \
-e POSTGRES_PASSWORD=zabbix \
-e POSTGRES_DB=zabbix \
-v zabbix-db-data:/var/lib/postgresql/data \
postgres:15
# Zabbix-сервер
docker run -d \
--name zabbix-server \
--network zabbix-net \
-e DB_SERVER_HOST=zabbix-db \
-e POSTGRES_USER=zabbix \
-e POSTGRES_PASSWORD=zabbix \
-e POSTGRES_DB=zabbix \
-p 10051:10051 \
-v zabbix-data:/var/lib/zabbix \
zabbix/zabbix-server-pgsql:latest
# Веб-интерфейс
docker run -d \
--name zabbix-web \
--network zabbix-net \
-e DB_SERVER_HOST=zabbix-db \
-e POSTGRES_USER=zabbix \
-e POSTGRES_PASSWORD=zabbix \
-e POSTGRES_DB=zabbix \
-e ZBX_SERVER_HOST=zabbix-server \
-p 8080:8080 \
zabbix/zabbix-web-nginx-pgsql:latest
Порт 10051 нужен для подключения агентов, а веб-интерфейс будет доступен на порту 8080. Не забудьте настроить агенты на хостах, указав IP или DNS-имя Docker-хоста. При обновлении достаточно сменить тег образа и пересоздать контейнеры — данные останутся в volumes.
Кейс 2: Nginx + PHP-FPM
Для веб-серверов с PHP удобно разделить Nginx и PHP-FPM по контейнерам и связать их через сеть.
# Сеть
docker network create web-net
# PHP-FPM
docker run -d \
--name php-fpm \
--network web-net \
-v /var/www/html:/var/www/html \
php:8.2-fpm
# Nginx
docker run -d \
--name nginx \
--network web-net \
-p 80:80 \
-v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
-v /var/www/html:/var/www/html \
nginx:latest
В конфиге Nginx в качестве upstream указывайте имя контейнера php-fpm:9000. Благодаря пользовательской сети Docker сам разрешит это имя. Такой подход позволяет независимо обновлять каждый компонент.
Кейс 3: PostgreSQL + Redis
Классическая связка для кэширования и хранения:
docker network create data-net
docker run -d \
--name redis \
--network data-net \
-v redis-data:/data \
redis:7
docker run -d \
--name postgres \
--network data-net \
-e POSTGRES_PASSWORD=secret \
-v pg-data:/var/lib/postgresql/data \
postgres:15
Для продакшена стоит дополнительно настроить репликацию и бэкапы, но как отправная точка этого достаточно. Контейнеры видят друг друга по именам redis и postgres.
Docker Compose: автоматизация деплоя многокомпонентных сервисов
Когда сервисов больше двух, управлять ими через отдельные команды docker run становится неудобно. Docker Compose позволяет описать всю инфраструктуру в одном YAML-файле и управлять ей как единым целым. Это чистой воды Infrastructure as Code: файл можно версионировать в Git и автоматически разворачивать через CI/CD.
Пример: docker-compose.yml для Nginx + PostgreSQL
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html
networks:
- app-net
restart: always
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: example
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-net
restart: always
networks:
app-net:
volumes:
db-data:
Здесь явно описаны volumes, сеть и политика перезапуска. Для локальной разработки я часто добавляю .env файл с переменными, чтобы не хардкодить пароли.
Запуск и управление
- Запуск всех сервисов:
docker-compose up -d - Остановка:
docker-compose down - Пересоздание (после изменения конфигурации):
docker-compose up -d --force-recreate
Compose отлично подходит для dev/staging-окружений и небольших продакшен-проектов на одном хосте. Если нужно обернуть его в systemd для автоматического запуска при загрузке, можно создать юнит, который вызывает docker-compose up -d. Но при масштабировании на несколько хостов пора смотреть в сторону Kubernetes.
Безопасность и настройки: как не открыть сервер для хакеров
Ограничение доступа к портам
Не пробрасывайте порты на все интерфейсы бездумно. Если сервис должен быть доступен только локально, используйте -p 127.0.0.1:8080:80. Для публичных сервисов обязательно настройте фаервол на хосте (iptables/ufw), чтобы ограничить доступ по IP.
Запуск в ограниченном режиме
- Никогда не используйте
--privileged: это даёт контейнеру полный доступ к хосту. Если злоумышленник проникнет в такой контейнер, он получит контроль над всей машиной. Вместо этого добавляйте только необходимые capabilities, например,--cap-add=NET_ADMINдля сетевых утилит. - Ограничивайте ресурсы:
docker run -d --memory="256m" --cpus="1.0" my-appЭто предотвращает захват всех ресурсов одним контейнером и облегчает FinOps-учёт.
- Монтируйте корневую ФС только для чтения:
--read-onlyс пробросом volumes для записываемых директорий.
Обновление образов
Образы могут содержать уязвимости. Регулярно обновляйте их:
docker pull nginx:latest
docker-compose up -d --force-recreate
Внедрите сканирование образов в CI/CD пайплайн — Trivy или Docker Scout. Я обычно сканирую все образы перед деплоем в staging.
Аудит контейнеров
Периодически проверяйте запущенные контейнеры:
docker ps
docker stats
Удаляйте неиспользуемые образы, контейнеры и volumes:
docker system prune -a --volumes
Но будьте осторожны с флагом --volumes — он удалит все тома, не привязанные к контейнерам. Лучше сначала сделать бэкап.
Чек-лист: перед деплоем сервиса в Docker
- Выбор образа: используйте официальные образы (например,
postgres:15,nginx:latest) и фиксируйте тег, а неlatestв продакшене. - Volumes: обязательно добавьте volume для всех данных, которые должны пережить пересоздание контейнера (базы, логи, конфиги).
- Проброс портов: убедитесь, что порт не занят на хосте, и по возможности ограничьте слушающий IP.
- Сеть: если сервисы взаимодействуют, создайте общую сеть, чтобы они могли обращаться друг к другу по DNS-именам.
- Переменные окружения: укажите пароли и настройки через
-eили--env-file, не хардкодьте в образе. - Ресурсы: ограничьте память и CPU, если сервис не критичен, чтобы избежать шумного соседа.
- Healthcheck: настройте
HEALTHCHECKв Dockerfile или в compose — это позволит оркестратору или systemd понимать, жив ли сервис, и автоматически перезапускать его. - Обновление: периодически обновляйте образы и сканируйте их на уязвимости.
- Тестирование: проверьте доступность сервиса через
curlили веб-интерфейс, а также логи на наличие ошибок.
FAQ: частые вопросы сисадмина о Docker
Что делать, если контейнер не запускается?
Первым делом смотрите логи: docker logs <container>. Если контейнер даже не создаётся, проверьте синтаксис команды. Частые причины: ошибка в конфигурационном файле, неверные переменные окружения, отсутствие файла, на который ссылается bind mount, или занятый порт. Команда docker inspect <container> покажет CMD и Entrypoint — возможно, процесс внутри завершается сразу.
Как сохранить данные при обновлении образа?
Используйте volume: данные останутся в томе, не зависящем от контейнера. Процесс обновления: стяните новый образ, остановите и удалите старый контейнер, запустите новый с тем же volume. Перед обновлением обязательно сделайте бэкап тома — на всякий случай.
Можно ли запустить Docker на Windows Server?
Да, но лучший путь — использовать WSL2 и Docker Engine внутри него, либо развернуть полноценную Linux-ВМ с Docker. Docker Desktop для Windows Server официально не поддерживается, а Microsoft предлагает свои контейнеры Windows, но экосистема там беднее. Для production-нагрузок я бы рекомендовал Linux.
Как обновить сервис без потери данных?
Классический подход — запустить новый контейнер с тем же volume, остановить старый, удалить старый. В более сложных сценариях можно использовать blue-green деплой: поднять новую версию на другом порту, протестировать, затем переключить трафик через reverse-прокси. В Docker Compose это делается через docker-compose up -d --no-deps --force-recreate <service>.
Что если порт занят на хосте?
Проверьте, кто его слушает: sudo ss -tulpn | grep :порт. Если это не критичный процесс, можно его остановить. Или используйте другой порт на хосте: -p 8081:80. В продакшене лучше иметь фиксированные порты, описанные в документации, чтобы не создавать путаницу.
Вывод: Docker как шаг к инженерии надёжности
Docker — это не просто способ быстро запустить сервис. Это фундамент, на котором строится современная инженерия надёжности. Он позволяет уйти от ручного конфигурирования, обеспечивает идентичность окружений, упрощает версионирование инфраструктуры и даёт возможность автоматизировать деплой до нажатия одной кнопки. Начав с контейнеризации привычных сервисов — Zabbix, Nginx, PostgreSQL — вы закладываете основу для дальнейшего роста: Ansible для управления конфигурациями, Terraform для облачных ресурсов, Kubernetes для оркестрации, SRE-практики для управления инцидентами и FinOps для контроля затрат.
Современный админ — это инженер по надёжности, который автоматизирует всё, включая собственные задачи. Docker — первый шаг в этом направлении. Начните с малого: заверните в контейнер тестовый веб-сервер, добавьте volume, настройте сеть. Затем переходите к Docker Compose, внедряйте CI/CD пайплайны. Ваша инфраструктура станет прозрачнее, надёжнее, а ночные звонки — реже.
