Когда количество серверов перевалило за сотню, а ночные дежурства стали нормой из-за расхождений окружений, я понял: ручная настройка пакетов и правка конфигов по 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

  1. Обновите пакетную базу:
    sudo apt update
  2. Установите Docker Engine и зависимости:
    sudo apt install docker.io

    Либо, если хотите самую свежую версию, добавьте официальный репозиторий Docker и установите docker-ce. Но для большинства задач пакета docker.io достаточно.

  3. Запустите и активируйте сервис:
    sudo systemctl start docker
    sudo systemctl enable docker
  4. Проверьте установку:
    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

  1. Создать volume:
    docker volume create my-volume
  2. Запустить контейнер с 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

  1. Выбор образа: используйте официальные образы (например, postgres:15, nginx:latest) и фиксируйте тег, а не latest в продакшене.
  2. Volumes: обязательно добавьте volume для всех данных, которые должны пережить пересоздание контейнера (базы, логи, конфиги).
  3. Проброс портов: убедитесь, что порт не занят на хосте, и по возможности ограничьте слушающий IP.
  4. Сеть: если сервисы взаимодействуют, создайте общую сеть, чтобы они могли обращаться друг к другу по DNS-именам.
  5. Переменные окружения: укажите пароли и настройки через -e или --env-file, не хардкодьте в образе.
  6. Ресурсы: ограничьте память и CPU, если сервис не критичен, чтобы избежать шумного соседа.
  7. Healthcheck: настройте HEALTHCHECK в Dockerfile или в compose — это позволит оркестратору или systemd понимать, жив ли сервис, и автоматически перезапускать его.
  8. Обновление: периодически обновляйте образы и сканируйте их на уязвимости.
  9. Тестирование: проверьте доступность сервиса через 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 пайплайны. Ваша инфраструктура станет прозрачнее, надёжнее, а ночные звонки — реже.