От Linux-администратора к Kubernetes-инженеру: полный курс по контейнеризации

Комплексная программа подготовки системных администраторов, охватывающая путь от базовых концепций Docker до управления промышленными кластерами Kubernetes. Курс фокусируется на практических навыках настройки сетей, хранилищ, безопасности и автоматизации CI/CD процессов.

1. Основы виртуализации и концепция контейнеризации: переход к микросервисной архитектуре

Основы виртуализации и концепция контейнеризации: переход к микросервисной архитектуре

В 2014 году компания Google опубликовала статью, в которой призналась, что запускает более двух миллиардов контейнеров в неделю. Для системного администратора того времени, привыкшего к тяжеловесным виртуальным машинам, которые загружаются минутами и требуют гигабайты оперативной памяти только на работу операционной системы, эта цифра казалась фантастической. Как можно управлять таким количеством сущностей, не обрушив инфраструктуру? Ответ кроется в фундаментальном сдвиге парадигмы: переходе от виртуализации оборудования к виртуализации операционной системы.

Эволюция изоляции ресурсов: от физических серверов к гипервизорам

Чтобы понять, зачем нам нужны контейнеры, необходимо вспомнить «темные века» системного администрирования — эпоху Bare Metal (физических серверов). В этой модели на один физический сервер устанавливалась одна операционная система, а в ней — одно или несколько приложений. Основная проблема заключалась в неэффективном использовании ресурсов. Если серверу требовалось 128 ГБ оперативной памяти для пиковых нагрузок, а 90% времени он потреблял лишь 8 ГБ, остальные 120 ГБ простаивали, буквально сжигая деньги компании на электричество и обслуживание.

Ситуация усложнялась конфликтами зависимостей. Попытка запустить на одном сервере два приложения, одно из которых требует Python 2.7, а другое — Python 3.10, превращалась в административный кошмар. Появление виртуализации (Hardware Virtualization) решило проблему утилизации ресурсов.

Гипервизор — специальный слой программного обеспечения — позволил разделять один физический сервер на несколько виртуальных машин (ВМ). Каждая ВМ обладает собственным виртуальным оборудованием: процессором, памятью, сетевыми картами и дисками. Главное отличие этой модели — наличие гостевой ОС (Guest OS) внутри каждой виртуальной машины.

С точки зрения изоляции — это идеал. Процессы в одной ВМ никак не могут повлиять на процессы в другой, даже если произойдет критический сбой ядра. Однако за эту безопасность приходится платить «налогом на виртуализацию». Каждая гостевая ОС потребляет ресурсы:

  • Дисковое пространство (минимум несколько гигабайт под системные файлы).
  • Оперативная память (ядро гостевой ОС и системные демоны забирают 500 МБ–1 ГБ еще до запуска полезного приложения).
  • Процессорное время на эмуляцию инструкций и работу планировщика гостевой ОС.
  • Если нам нужно запустить 50 микросервисов, создание 50 виртуальных машин приведет к тому, что большая часть ресурсов кластера будет тратиться на поддержание жизнедеятельности 50 копий ядра Linux, а не на выполнение бизнес-логики.

    Контейнеризация как виртуализация на уровне ядра

    Контейнеризация предлагает иной подход. Вместо того чтобы виртуализировать «железо», мы виртуализируем операционную систему. Все контейнеры на хосте используют одно и то же ядро родительской ОС.

    > Контейнер — это изолированная группа процессов, запущенная в пространстве пользователя, которая разделяет ядро хостовой операционной системы с другими контейнерами.

    В отличие от виртуальной машины, контейнер не несет в себе ядро. Внутри него находятся только бинарные файлы приложения и необходимые библиотеки. Это делает контейнеры невероятно легкими: их размер может измеряться мегабайтами, а запуск происходит за доли секунды, так как не требуется время на загрузку ОС (Bootstrapping).

    Механизмы изоляции в Linux

    Многие начинающие администраторы ошибочно полагают, что контейнер — это некая «магическая коробка». На самом деле, в Linux нет такого объекта, как «контейнер». Это абстракция, созданная комбинацией трех ключевых технологий ядра:

  • Namespaces (Пространства имен). Эта технология определяет, что процесс может видеть. Существует несколько типов namespaces:
  • - pid: процесс видит только свои дочерние процессы и считает себя PID 1. - net: контейнер получает свой сетевой стек (IP-адрес, таблицу маршрутизации). - mnt: изоляция точек монтирования файловой системы. - uts: изоляция имени хоста и домена. - ipc: изоляция межпроцессного взаимодействия. - user: позволяет иметь права root внутри контейнера, оставаясь обычным пользователем на хосте.

  • Control Groups (cgroups). Если namespaces определяют видимость, то cgroups определяют, сколько ресурсов процесс может потребить. С помощью cgroups администратор может жестко ограничить лимиты по CPU, RAM и I/O для конкретного контейнера. Без этого один «прожорливый» процесс мог бы занять всю память хоста и вызвать срабатывание OOM Killer, который завершит работу критически важных служб.
  • Union File Systems (UnionFS). Это технология, позволяющая накладывать несколько файловых систем друг на друга, создавая иллюзию единой структуры. Это основа «слоеного пирога» образов Docker. Если десять контейнеров запущены на базе одного образа Ubuntu, они разделяют одни и те же файлы образа в режиме «только чтение», экономя дисковое пространство.
  • Сравнение моделей: ВМ vs Контейнеры

    Для наглядности сравним характеристики двух подходов в таблице:

    | Характеристика | Виртуальные машины (ВМ) | Контейнеры | | :--- | :--- | :--- | | Изоляция | Полная (на уровне оборудования) | Частичная (на уровне ядра) | | Операционная система | Каждая ВМ имеет свою гостевую ОС | Все разделяют ядро хоста | | Скорость запуска | Минуты | Миллисекунды / Секунды | | Потребление ресурсов | Высокое (накладные расходы ОС) | Минимальное | | Портативность | Зависит от формата гипервизора | Высокая (запустится везде, где есть Docker/Podman) | | Безопасность | Выше (пробой ядра ВМ не опасен для хоста) | Ниже (уязвимость в ядре хоста общая для всех) |

    Представьте ситуацию: вы системный администратор в компании, которая разрабатывает онлайн-магазин. В период распродаж нагрузка на сервис поиска товаров возрастает в 10 раз. Если поиск работает в ВМ, вам нужно запустить 10 новых копий ВМ. Это займет 15–20 минут. За это время пользователи уйдут к конкурентам. Если же поиск упакован в контейнеры, вы можете масштабировать его до 100 экземпляров за 30 секунд. В этом и заключается главная ценность контейнеризации для современного бизнеса — эластичность.

    Переход к микросервисной архитектуре

    Контейнеризация стала катализатором перехода от монолитных приложений к микросервисным. Чтобы понять этот процесс, нужно осознать ограничения монолита.

    Монолит — это приложение, где все функции (авторизация, оплата, каталог, логистика) собраны в один исполняемый файл или архив.

  • Проблема масштабирования: Если функции оплаты требуется больше памяти, вам придется масштабировать весь монолит целиком, даже если каталог товаров простаивает.
  • Проблема надежности: Ошибка в модуле генерации PDF-отчетов может привести к утечке памяти, которая обрушит весь сервер, включая прием платежей.
  • Технологический стек: Вы привязаны к одному языку программирования и одной версии библиотек для всего проекта.
  • Микросервисная архитектура разделяет приложение на независимые компоненты, которые общаются между собой по сети (обычно через HTTP/REST или очереди сообщений). Каждый микросервис выполняет одну задачу.

    Пример разделения:

  • auth-service: управляет пользователями.
  • order-service: обрабатывает заказы.
  • payment-gateway: связывается с банками.
  • Почему микросервисы невозможны без контейнеров?

    Теоретически, микросервисы можно запускать и на обычных серверах. Но на практике вы быстро столкнетесь с «комбинаторным взрывом» сложности. Если у вас 50 микросервисов, и каждый написан на своем языке (Go, Java, Node.js), вам нужно поддерживать 50 разных окружений на каждом сервере.

    Контейнер решает проблему "It works on my machine" (у меня на компьютере работает). Разработчик упаковывает код вместе со всеми зависимостями в образ. Администратор берет этот образ и запускает его в любой среде — от тестового стенда до продакшена. Контейнер гарантирует, что среда выполнения будет идентичной.

    Однако микросервисы приносят новую проблему: сложность управления. Если раньше у вас было 2 монолита, то теперь у вас 200 контейнеров. Как следить за их состоянием? Как обновлять их без простоя? Как распределять нагрузку? Именно для решения этих задач появились системы оркестрации, такие как Kubernetes.

    Математическая модель эффективности

    Эффективность инфраструктуры можно выразить через коэффициент плотности размещения (Density Factor). Допустим, у нас есть физический сервер с ресурсами .

    В модели виртуальных машин потребление ресурсов одной ВМ () складывается из ресурсов приложения () и ресурсов гостевой ОС ():

    Количество приложений , которые мы можем запустить:

    В модели контейнеризации ресурсы гостевой ОС отсутствуют на уровне каждого юнита, добавляется лишь мизерный оверхед рантайма контейнеров (), который распределяется на все контейнеры:

    Поскольку обычно значительно больше (особенно при большом количестве сущностей), всегда будет больше , часто в несколько раз. Это позволяет компаниям экономить до 40–70% затрат на облачную инфраструктуру при миграции в контейнеры.

    Границы применимости: когда контейнеры — это плохо?

    Несмотря на популярность технологии, профессор педагогики обязан предостеречь от «серебряной пули». Существуют сценарии, где контейнеризация может быть избыточной или вредной:

  • Тяжелые базы данных с огромным I/O. Хотя современные хранилища в Kubernetes стали надежнее, запуск огромного кластера Oracle или MS SQL внутри контейнера часто добавляет лишний слой абстракции там, где нужна максимальная производительность дисковой подсистемы.
  • Приложения с жесткой привязкой к ядру. Если ваше ПО требует специфических модулей ядра или работает на уровне драйверов устройств (например, системы захвата трафика), изоляция контейнера будет мешать, и вам придется «прокидывать» в него столько прав, что смысл изоляции исчезнет.
  • Legacy-монолиты с состоянием (Stateful). Если приложение хранит данные в локальных файлах по жестким путям и не умеет работать в нескольких экземплярах, простая упаковка его в Docker не сделает его облачным (Cloud Native). Вы получите «тяжелый» контейнер, которым неудобно управлять.
  • Безопасность и «поверхность атаки»

    С точки зрения безопасности, переход к контейнерам меняет модель угроз. В виртуальной машине граница безопасности — это аппаратная эмуляция. Чтобы «выпрыгнуть» из ВМ на хост, хакеру нужно найти уязвимость в гипервизоре (например, в VMware или KVM), что случается крайне редко.

    В контейнерах границей является системный вызов (syscall) к ядру Linux. Если в ядре есть уязвимость (например, нашумевшая Dirty COW), процесс внутри контейнера может получить права root на физическом хосте. Поэтому в контейнерной среде критически важно:

  • Не запускать процессы от имени root внутри контейнера.
  • Использовать минималистичные базовые образы (например, Alpine Linux), чтобы уменьшить количество потенциально уязвимых бинарных файлов.
  • Регулярно обновлять ядро хостовой ОС.
  • Роль системного администратора в новую эпоху

    Для администратора Linux переход к контейнерам означает смену фокуса. Раньше вашей главной задачей была настройка конкретных серверов (их называли «питомцами» — Pets), вы знали каждый сервер по имени, лечили его, когда он болел.

    В мире контейнеров и Kubernetes серверы превращаются в «скот» (Cattle). Если сервер вышел из строя, вы не чините его. Вы просто исключаете его из кластера, а оркестратор автоматически перезапускает контейнеры на других исправных узлах. Ваша задача теперь — не конфигурировать софт вручную, а описывать желаемое состояние системы в виде кода (Infrastructure as Code).

    Этот путь мы и начнем с изучения Docker — инструмента, который сделал контейнеризацию доступной каждому инженеру.

    10. Реализация CI/CD процессов и автоматизация деплоя приложений в K8s

    Реализация CI/CD процессов и автоматизация деплоя приложений в K8s

    Представьте, что ваше приложение обновляется десять раз в день. В традиционном администрировании Linux это означало бы десять ручных подключений по SSH, ручную сборку артефактов, остановку сервисов и молитву о том, чтобы конфигурационные файлы на сервере совпали с теми, что лежат в репозитории. В мире Kubernetes и контейнеризации такой подход считается антипаттерном. Если инфраструктура — это код, то доставка этого кода должна быть полностью автоматизированным конвейером, где вмешательство человека сводится к нажатию кнопки «Merge» или «Approve».

    Философия CI/CD в облачных средах

    Переход от администрирования отдельных серверов к управлению кластером Kubernetes требует переосмысления жизненного цикла ПО. Мы разделяем этот процесс на две фундаментальные части: Continuous Integration (CI) и Continuous Delivery/Deployment (CD).

    Continuous Integration (Непрерывная интеграция) фокусируется на коде и артефактах. Как только разработчик отправляет изменения в Git, запускается процесс:

  • Линтинг и статический анализ кода.
  • Запуск Unit-тестов.
  • Сборка Docker-образа.
  • Сканирование образа на уязвимости (например, через Trivy).
  • Публикация образа в Container Registry.
  • Continuous Delivery (Непрерывная доставка) — это подготовка инфраструктуры к изменениям. Здесь в игру вступает Kubernetes. Мы не просто запускаем контейнер, мы обновляем декларативное описание системы. Если CI заканчивается созданием образа myapp:v1.2.0, то CD начинается с изменения манифеста Deployment, чтобы он указывал на этот новый тег.

    Пакетный менеджер Helm: зачем он нужен в автоматизации

    До сих пор мы работали с «голыми» YAML-манифестами. Но в реальном проекте одно приложение — это десятки файлов: Deployment, Service, Ingress, ConfigMap, Secret, HPA. Если вам нужно развернуть три копии этого стека (Dev, Staging, Production), копирование и правка YAML вручную приведет к катастрофе.

    Helm решает проблему шаблонизации. Он позволяет превратить статические манифесты в динамические чарты (Charts), где переменные значения (количество реплик, лимиты ресурсов, доменные имена) выносятся в отдельный файл values.yaml.

    Структура Helm-чарта

    Типичный чарт выглядит так:
  • Chart.yaml: Метаданные (название, версия).
  • values.yaml: Значения по умолчанию для шаблонов.
  • templates/: Директория с YAML-файлами, содержащими вставки на языке Go Templates.
  • charts/: Зависимости от других чартов.
  • Рассмотрим пример шаблона в templates/deployment.yaml:

    При деплое Helm подставит значения из values.yaml, сгенерирует итоговый YAML и отправит его в API Kubernetes. Это критически важно для CI/CD, так как конвейер может передавать параметры сборки (например, динамический тег образа) прямо в команду установки: helm upgrade --install my-app ./chart --set image.tag=v1.2.0

    Архитектура конвейера: Push-модель vs Pull-модель

    Существует два принципиально разных подхода к автоматизации деплоя в Kubernetes.

    Push-модель (классическая)

    Инструмент автоматизации (GitLab CI, GitHub Actions, Jenkins) имеет прямой доступ к API-серверу Kubernetes. После успешной сборки образа пайплайн выполняет команду kubectl apply или helm upgrade.
  • Плюсы: Простота настройки, привычный поток управления.
  • Минусы: Необходимо хранить учетные данные кластера (kubeconfig) внутри системы CI, что является риском безопасности. Кроме того, если кто-то изменит состояние кластера вручную, CI об этом не узнает до следующего запуска.
  • Pull-модель (GitOps)

    Внутри кластера работает специальный агент (например, ArgoCD или Flux). Он постоянно следит за Git-репозиторием с манифестами. Как только в репозитории появляется изменение, агент «подтягивает» его и применяет к кластеру.
  • Плюсы: Высокая безопасность (кластер сам забирает данные, внешние системы не имеют доступа к API), автоматическое исправление «дрейфа конфигурации» (Configuration Drift). Если вы вручную удалите под, ArgoCD увидит несоответствие с Git и восстановит его.
  • Минусы: Требует отдельного репозитория для инфраструктурных манифестов и установки дополнительных компонентов в кластер.
  • Практическая реализация CI: Сборка и оптимизация

    Рассмотрим этап CI на примере GitLab CI. Основная задача здесь — получить безопасный и компактный образ.

    Нюанс с тегированием: Никогда не используйте тег :latest в автоматизации. Kubernetes кэширует образы на узлах. Если вы обновите образ с тем же тегом :latest, K8s может решить, что у него уже есть актуальная версия, и не станет скачивать изменения. Используйте SHA коммита или версию из git tag.

    Безопасность в CI: Важным этапом является проверка образа. Утилита Trivy позволяет прервать пайплайн, если в базовом образе найдены критические уязвимости: trivy image --exit-code 1 --severity CRITICAL P > 0.05P$ — вероятность ошибки).

    Траблшутинг автоматизированного деплоя

    Когда что-то идет не так в автоматическом конвейере, Linux-администратору нужно уметь смотреть «сквозь» слои абстракций:

  • Ошибка сборки образа: Проверяем логи CI-раннера. Часто проблема в нехватке места на диске или отсутствии доступа к базовому образу.
  • ImagePullBackOff: CI успешно запушил образ, но K8s не может его скачать. Проверяем imagePullSecrets и права доступа к Registry.
  • CrashLoopBackOff после деплоя: Проверяем kubectl logs и kubectl describe pod. Часто автоматизация подставила неверный ConfigMap или секрет.
  • Дрейф конфигурации в GitOps: Если ArgoCD показывает статус OutOfSync`, смотрим Diff. Возможно, кто-то применил патч вручную или в шаблоне Helm есть непредсказуемые значения (например, генерация случайных паролей при каждом запуске).
  • Замыкание цикла автоматизации

    Реализация CI/CD в Kubernetes превращает инфраструктуру из хрупкого набора серверов в гибкую, самовосстанавливающуюся систему. Мы ушли от ручных манипуляций к декларативному описанию. Теперь роль администратора (теперь уже SRE или DevOps-инженера) заключается в проектировании надежных конвейеров и обеспечении безопасности этого процесса. Помните: автоматизация — это не просто скрипты, это культура доверия к коду и тестам, подкрепленная мощными инструментами оркестрации.

    2. Docker: архитектура, установка и базовые операции управления контейнерами

    Docker: архитектура, установка и базовые операции управления контейнерами

    Представьте, что вам нужно перевезти хрупкий стеклянный декор из Лиссабона в Токио. Вы не нанимаете плотников для постройки индивидуального ящика под каждый бокал прямо на палубе корабля. Вы используете стандартный морской контейнер. Ему все равно, что внутри — электроника или мебель, а крану в порту все равно, на какое судно его ставить. В мире ИТ таким стандартом стал Docker. До его появления системные администраторы тратили до своего времени на борьбу с несоответствием окружений: «у разработчика работает, а на сервере — нет». Docker превратил программное обеспечение в стандартные юниты, которые одинаково запускаются на ноутбуке и в облачном кластере.

    Клиент-серверная архитектура Docker

    Многие новички воспринимают Docker как монолитную утилиту, но это заблуждение. Docker спроектирован как распределенное клиент-серверное приложение. Понимание этой структуры критично для траблшутинга, особенно когда вы начнете работать с удаленными хостами или настраивать права доступа.

    Архитектура состоит из трех основных компонентов:

  • Docker Daemon (dockerd): Это «мозг» системы. Сервис, который работает в фоновом режиме на хост-машине. Он слушает запросы от API, управляет объектами (образами, контейнерами, сетями, томами) и взаимодействует с ядром Linux через библиотеку containerd.
  • Docker Client (docker): Интерфейс командной строки (CLI), с которым взаимодействует администратор. Когда вы вводите docker run, клиент отправляет команду демону через REST API. Важно понимать: клиент и демон могут находиться на разных машинах.
  • Docker Registry: Хранилище образов. По умолчанию это Docker Hub, но в корпоративных средах часто используют приватные реестры (Harbor, GitLab Registry).
  • Взаимодействие происходит по следующей цепочке. Клиент обращается к демону через Unix-сокет (/var/run/docker.sock) или TCP-порт. Демон проверяет наличие нужного образа локально. Если его нет, он идет в Registry, скачивает слои образа и затем дает команду containerd запустить процесс.

    Роль containerd и runc

    Современный Docker — это «слоеный пирог». Чтобы соответствовать стандартам OCI (Open Container Initiative), Docker был разделен на части: * containerd: Занимается высокоуровневым управлением — скачиванием образов, управлением хранилищем и сетью. * runc: Низкоуровневая утилита, которая непосредственно взаимодействует с функциями ядра (Namespaces и Cgroups), чтобы создать изолированную среду и запустить в ней процесс.

    Эта модульность позволяет обновлять Docker, не останавливая работающие контейнеры (при включении функции live-restore), и обеспечивает совместимость с другими инструментами, такими как Kubernetes, который сейчас использует containerd напрямую, минуя Docker-демон.

    Подготовка системы и установка

    Для системного администратора Linux установка Docker — это не просто запуск скрипта из интернета. Это настройка репозиториев, управление правами доступа и понимание того, какие изменения вносятся в систему.

    Системные требования и зависимости

    Docker требует 64-битную версию Linux с версией ядра не ниже 3.10 (рекомендуется 5.x и выше для стабильной работы современных драйверов хранилища, таких как Overlay2). Основная нагрузка ложится на оперативную память, так как каждый контейнер — это процесс, но Docker сам по себе потребляет ничтожно мало ресурсов по сравнению с гипервизорами.

    Пошаговый алгоритм для систем на базе Debian/Ubuntu

  • Удаление старых версий: В системе могут быть пакеты docker, docker-engine или docker.io. Их нужно вычистить, чтобы избежать конфликтов зависимостей.
  • Настройка репозитория: Использование официального репозитория Docker гарантирует получение обновлений безопасности быстрее, чем из стандартных репозиториев дистрибутива.
  • Установка пакетов: Нам нужны docker-ce (Community Edition), docker-ce-cli, containerd.io и docker-buildx-plugin.
  • После установки демон запускается автоматически. Проверить статус можно через systemctl status docker.

    Управление правами: безопасность vs удобство

    По умолчанию Docker-демон привязан к Unix-сокету, владельцем которого является root. Это означает, что для любой команды Docker требуется sudo. Для удобства администраторов создается группа docker. Добавление пользователя в эту группу позволяет запускать контейнеры без sudo.

    > Критическое замечание по безопасности: Добавление пользователя в группу docker эквивалентно предоставлению ему прав root. Поскольку Docker позволяет монтировать произвольные директории хоста в контейнер, пользователь может запустить контейнер, примонтировать /etc и изменить пароль любого пользователя или прочитать конфиденциальные данные. В продакшн-средах рекомендуется использовать sudo с четко настроенными правилами sudoers или переходить на Rootless-режим.

    Жизненный цикл контейнера

    Контейнер — это не виртуальная машина. Это процесс, ограниченный рамками изоляции. Как только главный процесс в контейнере (PID 1) завершается, контейнер останавливается. Это фундаментальное отличие, которое часто сбивает с толку администраторов, привыкших к ВМ, которые «просто работают» в фоне.

    Основные состояния

  • Created: Ресурсы выделены, файловая система подготовлена, но процесс еще не запущен.
  • Running: Процесс запущен и выполняется.
  • Paused: Процессы внутри контейнера «заморожены» с помощью сигналов SIGSTOP (через cgroups freezer). Память не освобождается, но процессорное время не тратится.
  • Exited: Процесс завершился (успешно или с ошибкой). Файловая система контейнера сохраняется, его можно перезапустить.
  • Deleted: Контейнер полностью удален из системы.
  • Командный интерфейс: базовый синтаксис

    Команды Docker следуют структуре: docker <объект> <действие> [параметры]. Хотя старый синтаксис типа docker run все еще работает, рекомендуется использовать новый структурированный формат: docker container run. Это помогает избежать путаницы, когда команд становится много.

    Рассмотрим классический пример: docker container run -d --name web-server -p 8080:80 nginx

    Разбор параметров: * -d (detached): Запускает контейнер в фоновом режиме. Если его не указать, ваш терминал будет привязан к выводу (stdout/stderr) процесса внутри контейнера. * --name: Присваивает человекочитаемое имя. Если его не указать, Docker сгенерирует случайное имя (например, focused_shannon). * -p 8080:80: Проброс портов. Порт 8080 на хост-машине перенаправляется на порт 80 внутри контейнера. * nginx: Имя образа.

    Глубокое погружение в управление процессами

    Как администратору, вам нужно не просто запускать контейнеры, но и понимать, что происходит внутри них.

    Инспекция и логирование

    Когда контейнер ведет себя странно, первым делом мы смотрим логи. Docker перехватывает потоки stdout и stderr главного процесса. docker container logs -f <name> — ключ -f (follow) позволяет наблюдать за логами в реальном времени, аналогично tail -f.

    Если логов недостаточно, мы используем docker container inspect. Эта команда возвращает огромный JSON-объект со всей конфигурацией: от IP-адреса до настроек лимитов памяти. Чтобы не читать весь JSON, используйте фильтры: docker container inspect --format='{{.NetworkSettings.IPAddress}}' web-server

    Выполнение команд в работающем контейнере

    Иногда нужно зайти «внутрь» контейнера, чтобы проверить конфиг или доступность базы данных. Для этого используется exec: docker container exec -it web-server /bin/bash * -i (interactive): Держит STDIN открытым. * -t (tty): Выделяет псевдотерминал.

    Важно: в контейнере может не быть bash. В легковесных образах (например, на базе Alpine) используйте /bin/sh.

    Остановка и удаление: SIGTERM vs SIGKILL

    Когда вы вводите docker container stop, Docker отправляет процессу PID 1 сигнал SIGTERM. У приложения есть 10 секунд (по умолчанию), чтобы корректно завершить сессии, закрыть файлы и выйти. Если оно не успевает, отправляется SIGKILL, и процесс убивается принудительно. docker container kill сразу отправляет SIGKILL.

    Для очистки системы от остановленных контейнеров используйте docker container prune. Это полезная команда для регламентных работ, предотвращающая переполнение дискового пространства старыми слоями и метаданными.

    Ресурсные ограничения (Cgroups на практике)

    Одна из главных задач администратора — не дать одному «прожорливому» контейнеру уронить весь сервер. Docker позволяет жестко ограничить потребление CPU и RAM.

    Лимиты памяти

    Если контейнер превысит лимит памяти, ядро Linux просто убьет его (OOM Kill). --memory="512m" — жесткий лимит. --memory-reservation="256m" — «мягкий» лимит. Docker позволит занять больше, если на хосте есть свободная память, но при дефиците ресурсов первым делом принудительно вернет контейнер к этому значению.

    Лимиты CPU

    В отличие от памяти, превышение лимита CPU не убивает контейнер, а лишь замедляет его (дросселирование/throttling). --cpus="1.5" — гарантирует, что контейнер получит не более полутора ядер суммарно. --cpu-shares — относительный приоритет. Если два контейнера борются за процессор, тот, у кого shares выше, получит больше времени.

    Пример запуска с ограничениями: docker run -d --name db --memory="1g" --cpus="2" postgres

    Работа с файловой системой и интерактивный режим

    Для Linux-администратора важно понимать, где физически находятся данные контейнера. По умолчанию это /var/lib/docker/. Docker использует Copy-on-Write (CoW) стратегию.

    Когда вы запускаете контейнер, Docker берет неизменяемые слои образа и накладывает сверху один тонкий «записываемый слой» (Writable Layer). Все изменения, которые делает приложение (запись логов, создание файлов), происходят именно в этом слое. Если контейнер удалить, этот слой исчезнет навсегда.

    Интерактивный запуск для тестов

    Часто нужно быстро проверить какую-то гипотезу или версию софта. docker run --rm -it alpine sh Флаг --rm — киллер-фича для администратора. Он означает: «удалить контейнер сразу после того, как я из него выйду». Это сохраняет систему в чистоте при проведении экспериментов.

    Траблшутинг: если что-то пошло не так

    Администрирование Docker — это на умение читать ошибки.

  • Ошибка "Permission Denied" при обращении к сокету: Проверьте, добавлен ли ваш пользователь в группу docker и обновили ли вы сессию (команда newgrp docker).
  • Контейнер запускается и тут же падает: Проверьте логи (docker logs). Скорее всего, основной процесс завершается с ошибкой. Помните: контейнер живет, пока живет процесс. Если вы запускаете docker run -d ubuntu, он упадет сразу, потому что в Ubuntu по умолчанию запускается bash, которому нечего делать без интерактивного ввода, и он завершается. Чтобы такой контейнер «жил», используйте docker run -dt ubuntu.
  • Конфликт портов: Если порт уже занят другим приложением на хосте, Docker не сможет запустить контейнер. Используйте netstat -tulpn или ss -tulpn, чтобы найти виновника.
  • Исчерпание места на диске: Docker активно использует место под образы и логи. Команда docker system df покажет, сколько места занято и что именно (образы, контейнеры, тома) потребляет ресурсы.
  • Нюанс с зомби-процессами

    В контейнере ваше приложение — это PID 1. В обычной ОС PID 1 (например, systemd) отвечает за «усыновление» и очистку процессов-сирот. Многие приложения не умеют этого делать. Если внутри контейнера плодятся зомби-процессы, используйте флаг --init. Он добавит внутрь контейнера крошечный init-процесс (обычно tini), который будет корректно обрабатывать сигналы и чистить зомби.

    Сравнение Docker с традиционными методами

    Чтобы закрепить материал, сравним Docker с привычным системным администрированием.

    | Характеристика | Традиционный Linux-сервер | Docker-контейнер | | :--- | :--- | :--- | | Изоляция | На уровне пользователей/прав | На уровне Namespaces и Cgroups | | Скорость запуска | Минуты (загрузка ОС) | Миллисекунды (запуск процесса) | | Вес | Гигабайты | Мегабайты | | Обновление | apt upgrade (риск сломать зависимости) | Замена образа на новую версию | | Переносимость | Зависит от дистрибутива и библиотек | Полная (где есть Docker, там работает) |

    Docker не заменяет Linux-администрирование, он переносит его на новый уровень абстракции. Вам по-прежнему нужно знать, как работают сети, как настраивать права доступа и мониторить нагрузку, но теперь вы делаете это в стандартизированной среде.

    Запуск первого контейнера nginx — это только начало. В следующих главах мы разберем, как собирать собственные образы, чтобы упаковать в них ваши уникальные приложения, и как объединять их в сложные сети. Но фундамент — это понимание того, что Docker-демон управляет процессами через containerd, а вы управляете демоном через CLI, соблюдая баланс между удобством и безопасностью системы.

    3. Работа с Docker-образами, написание Dockerfile и оркестрация через Docker Compose

    Работа с Docker-образами, написание Dockerfile и оркестрация через Docker Compose

    Почему один и тот же бинарный файл корректно работает на ноутбуке разработчика, но вызывает «Segmentation fault» на сервере в дата-центре? Ответ кроется в неявных зависимостях: версии системных библиотек, конфигурационных файлах и переменных окружения. Docker-образ решает эту проблему, превращая приложение и всё его окружение в неизменяемый артефакт. Однако создание эффективного образа — это не просто копирование файлов в контейнер, а тонкая работа с многослойной структурой и оптимизацией сборки.

    Анатомия Docker-образа и магия слоев

    Docker-образ — это инертный, доступный только для чтения шаблон, из которого разворачивается контейнер. Если контейнер можно сравнить с запущенным процессом, то образ — это исполняемый файл на диске. Ключевое отличие заключается в том, что образ состоит из множества слоев, организованных иерархически.

    Каждый слой представляет собой набор изменений в файловой системе. Когда вы выполняете команду в процессе сборки, Docker создает новый слой, фиксируя состояние ФС. Благодаря механизму UnionFS, эти слои объединяются в единое дерево каталогов, которое видит приложение внутри контейнера.

    Принцип неизменяемости и кэширование

    Важнейшая характеристика слоев — их неизменяемость (immutability). После того как слой создан, он получает уникальный хэш (SHA-256) и больше никогда не меняется. Если вам нужно изменить файл, Docker не правит существующий слой, а создает новый «поверх», где файл заменяется или удаляется.

    Этот подход дает два критических преимущества:

  • Экономия места: Если у вас есть 10 образов, базирующихся на ubuntu:22.04, базовые слои Ubuntu будут храниться на диске в единственном экземпляре.
  • Ускорение сборки: Docker проверяет, менялись ли инструкции и исходные файлы. Если изменений нет, он берет готовый слой из локального кэша, не выполняя команду заново.
  • Однако кэширование работает линейно. Если вы изменили инструкцию на 3-м шаге из 10, Docker будет вынужден пересобрать все последующие шаги (с 4-го по 10-й), так как их хэши зависят от состояния предыдущего слоя.

    Создание собственного образа: Dockerfile

    Dockerfile — это текстовый манифест, содержащий последовательность команд для сборки образа. Это «рецепт», который делает процесс создания инфраструктуры воспроизводимым и версионируемым (Infrastructure as Code).

    Основные инструкции и их влияние на слои

    Рассмотрим базовый набор инструкций, который использует каждый администратор:

    * FROM: Определяет базовый образ. С этого начинается любая сборка. Рекомендуется использовать официальные образы (например, python:3.11-slim или alpine), чтобы минимизировать поверхность атаки и размер. * WORKDIR: Устанавливает рабочую директорию для всех последующих команд (RUN, CMD, ENTRYPOINT, COPY). Это аналог cd в Linux, но с важным отличием: если директории не существует, Docker создаст её автоматически. * COPY и ADD: Копируют файлы из контекста сборки (вашей локальной папки) в образ. COPY — предпочтительный вариант для простых файлов. ADD умеет распаковывать архивы и скачивать файлы по URL, что делает его поведение менее предсказуемым. * RUN: Выполняет команду в процессе сборки. Каждая инструкция RUN создает новый слой. * ENV: Устанавливает переменные окружения, которые будут доступны как во время сборки, так и при запуске контейнера. * EXPOSE: Носит информационный характер. Она указывает, какие порты контейнер планирует слушать, но не «пробрасывает» их на хост-машину автоматически.

    Разница между CMD и ENTRYPOINT

    Это один из самых частых камней преткновения. * ENTRYPOINT определяет основную команду, которая запускается при старте контейнера. Её сложно переопределить при запуске через CLI. * CMD задает аргументы по умолчанию для ENTRYPOINT. Если ENTRYPOINT не указан, CMD работает как самостоятельная команда.

    Представьте контейнер как утилиту командной строки. ENTRYPOINT — это само название утилиты (например, ls), а CMD — флаги по умолчанию (-la). Если пользователь при запуске docker run укажет свои аргументы, они полностью заменят содержимое CMD, но ENTRYPOINT останется неизменным.

    Искусство оптимизации: Multi-stage builds

    Типичная проблема начинающего администратора — огромный размер образа. Например, для компиляции приложения на Go или Java нужны компиляторы, библиотеки разработки и инструменты сборки (Maven, Gradle). Но для работы уже скомпилированного бинарного файла всё это — лишний груз, увеличивающий образ на сотни мегабайт и создающий дыры в безопасности.

    Технология Multi-stage build позволяет использовать несколько инструкций FROM в одном Dockerfile. Вы можете собрать приложение в «тяжелом» образе с инструментами сборки, а затем скопировать только готовый результат в «легкий» финальный образ.

    Пример логики многоэтапной сборки:

  • Stage 1 (build): Используем golang:1.21-alpine. Устанавливаем зависимости, компилируем код.
  • Stage 2 (final): Используем чистый alpine или даже scratch (пустой образ). Копируем исполняемый файл из первого этапа.
  • Результат: вместо 800 МБ образа с исходниками и компилятором мы получаем 15-20 МБ, содержащих только бинарный файл и необходимые системные библиотеки.

    Управление образами: Тегирование и Registry

    После сборки образ получает ID, но работать с ним через хэши неудобно. Для идентификации используются теги. Полное имя образа выглядит так: registry.example.com/project/image-name:tag.

    Если тег не указан, Docker автоматически подставляет :latest. Это опасная практика в продакшене. Тег latest — это просто указатель, который сегодня может ссылаться на одну версию, а завтра на другую. Для стабильности инфраструктуры всегда следует использовать конкретные версии (семантическое версионирование, например :1.2.4) или хэш коммита из Git.

    Для хранения образов используются Registry. Самый известный — Docker Hub, но в корпоративной среде часто применяются приватные решения: GitLab Container Registry, Harbor или облачные сервисы (AWS ECR, Google GCR).

    Команда docker push отправляет локальные слои в удаленное хранилище. Важно понимать: Docker не отправляет весь образ целиком, если часть его слоев уже есть в Registry. Он проверяет хэши и докачивает только измененные части, что значительно экономит трафик.

    Оркестрация локальных сред: Docker Compose

    Запуск одного контейнера — задача тривиальная. Но современное приложение — это ансамбль: фронтенд, бэкенд, база данных, кэш (Redis) и очередь сообщений. Запускать их вручную через docker run, прописывая сети и зависимости — путь к ошибкам.

    Docker Compose — это инструмент для определения и запуска многоконтейнерных приложений. Вся конфигурация описывается в одном файле docker-compose.yaml.

    Структура YAML-манифеста

    Файл Compose обычно состоит из трех основных разделов:

  • services: Описание самих контейнеров. Здесь указываются образы, порты, переменные окружения и тома.
  • networks: Описание виртуальных сетей. По умолчанию Compose создает общую сеть для всех сервисов в файле, позволяя им обращаться друг к другу по именам сервисов (встроенный DNS).
  • volumes: Описание именованных хранилищ для персистентных данных.
  • Управление зависимостями и порядком запуска

    Инструкция depends_on позволяет указать порядок запуска. Например, бэкенд не должен стартовать раньше базы данных. Однако важно помнить: Docker считает сервис «запущенным», когда процесс внутри контейнера стартовал. Это не гарантирует, что база данных уже готова принимать соединения. Для решения этой проблемы часто используются скрипты-ожидания (wait-for-it.sh) или механизмы Healthchecks.

    Healthcheck — это команда, которую Docker периодически выполняет внутри контейнера, чтобы проверить его работоспособность (например, curl -f http://localhost/health). Compose и оркестраторы могут опираться на статус этой проверки при управлении жизненным циклом приложения.

    Практический сценарий: Развертывание стека Python + PostgreSQL

    Представим задачу: развернуть веб-приложение на Flask, которое сохраняет данные в Postgres.

  • Dockerfile для приложения:
  • Мы выберем python:3.10-slim. В нем мы установим зависимости через pip, используя кэширование слоев: сначала скопируем requirements.txt и установим их, и только потом скопируем код приложения. Это позволит не перекачивать библиотеки при каждом изменении одной строчки кода.

  • docker-compose.yaml:
  • В сервисе db (Postgres) мы обязательно укажем переменную POSTGRES_PASSWORD и примонтируем Volume, чтобы данные не исчезли после перезагрузки контейнера. В сервисе web мы укажем build: ., чтобы Compose сам собрал образ из локального Dockerfile.

    С помощью одной команды docker-compose up -d система: * Создаст изолированную сеть. * Скачает образ Postgres. * Соберет образ Flask-приложения. * Запустит оба контейнера в правильном порядке. * Настроит проброс портов (например, 8080 на хосте в 5000 в контейнере).

    Безопасность и «хороший тон» при сборке

    Администратор несет ответственность не только за работоспособность, но и за безопасность образов. Вот несколько критических правил:

  • Не запускайте процессы от root: По умолчанию Docker запускает команды в контейнере от имени суперпользователя. Если злоумышленник взломает приложение и совершит «побег» из контейнера, он получит права root на хост-системе. Используйте инструкцию USER в Dockerfile для переключения на не привилегированного пользователя.
  • Минимизируйте количество слоев: Вместо пяти инструкций RUN apt install ... используйте одну, объединяя команды через &&. Это уменьшает метаданные образа.
  • Очищайте кэш пакетных менеджеров: В той же инструкции RUN, где устанавливаются пакеты, добавляйте удаление списков репозиториев (rm -rf /var/lib/apt/lists/*), иначе они останутся в слое навсегда, увеличивая его размер.
  • Используйте .dockerignore: Это аналог .gitignore. Он предотвращает попадание в контекст сборки (и, следовательно, в слои) папок .git, локальных виртуальных окружений, логов и секретных файлов .env. Это не только уменьшает размер, но и защищает конфиденциальные данные.
  • Troubleshooting: Почему образ не собирается или не работает?

    Проблемы с образами обычно делятся на два типа: ошибки сборки и ошибки выполнения.

    Ошибки сборки (Build time): Чаще всего связаны с отсутствием зависимостей или сетевыми проблемами. Если apt-get не может достучаться до репозиториев, проверьте DNS на хосте. Если сборка падает на шаге установки специфической библиотеки (например, psycopg2 для Python), скорее всего, в образе не хватает системных dev-пакетов (gcc, libpq-dev), необходимых для компиляции расширений.

    Ошибки выполнения (Runtime): Если контейнер запускается и тут же падает («Exited»), первым делом проверьте логи: docker logs <container_id>. Частая причина — отсутствие переменных окружения. Если приложение ожидает DB_HOST, а вы забыли передать её через ENV или Compose, оно упадет с ошибкой конфигурации. Другая проблема — завершение основного процесса. Помните: контейнер живет ровно столько, сколько живет процесс с PID 1. Если вы запускаете скрипт, который уходит в фон, контейнер решит, что работа окончена, и завершится.

    Переход к масштабированию

    Docker Compose идеален для разработки и небольших проектов на одном сервере. Однако он не умеет автоматически восстанавливать упавшие контейнеры на разных физических узлах, не знает о балансировке нагрузки между серверами и не поддерживает сложные стратегии обновления (например, Canary или Blue-Green).

    Понимание того, как упаковать приложение в эффективный образ и как связать несколько компонентов в одну систему через Compose — это фундамент. Без него невозможно двигаться к оркестрации промышленного уровня. Когда ваше приложение вырастает из рамок одного сервера, на сцену выходят Docker Swarm и Kubernetes, которые оперируют теми же образами, но на уровне целых кластеров.

    Образ становится универсальной валютой в мире DevOps: разработчик «чеканит» эту монету, а администратор обеспечивает её свободное и безопасное обращение в инфраструктуре.

    4. Сетевое взаимодействие и стратегии хранения данных в экосистеме Docker

    Сетевое взаимодействие и стратегии хранения данных в экосистеме Docker

    Представьте, что вы развернули базу данных PostgreSQL в контейнере, накопили в ней терабайты транзакций, а затем решили обновить версию образа. Вы выполняете docker rm и docker run, и внезапно обнаруживаете, что все данные исчезли вместе со старым контейнером. Или другая ситуация: ваш веб-сервер внутри контейнера пытается достучаться до API, запущенного в соседнем контейнере, но получает ошибку «Connection refused», хотя оба процесса исправны. Эти сценарии — классические «грабли» администраторов, переходящих с традиционных серверов на контейнеризированную инфраструктуру. Понимание того, как Docker изолирует и связывает трафик, а также как он управляет жизненным циклом данных, отделяя их от жизненного цикла процессов, является критическим порогом в освоении оркестрации.

    Анатомия сетевого стека Docker: от виртуальных мостов до изоляции

    В традиционном администрировании Linux сетевой интерфейс (например, eth0) жестко привязан к операционной системе. В Docker ситуация сложнее: каждый контейнер должен иметь собственный сетевой стек, IP-адрес и таблицу маршрутизации, оставаясь при этом способным общаться с внешним миром и другими контейнерами. Это достигается за счет комбинирования Linux Namespaces (сетевых пространств имен) и виртуальных Ethernet-пар (veth).

    Когда Docker-демон запускается, он создает виртуальный мост — интерфейс docker0. Его можно увидеть в выводе команды ip addr. По умолчанию этот интерфейс получает IP-адрес из диапазона .

    Механизм работы veth-пар

    При запуске контейнера Docker создает пару виртуальных интерфейсов. Один из них помещается внутрь сетевого пространства имен контейнера (обычно переименовывается в eth0), а второй остается в пространстве имен хоста и подключается к мосту docker0.

    Процесс передачи пакета выглядит так:

  • Приложение в контейнере отправляет пакет на свой eth0.
  • Пакет через «виртуальный кабель» (veth) попадает на мост docker0 на хосте.
  • Мост работает как обычный L2-коммутатор: он либо пересылает пакет другому контейнеру на том же мосту, либо, если адрес внешний, передает его стеку хоста для маршрутизации и NAT.
  • Стандартные сетевые драйверы

    Docker предлагает несколько встроенных драйверов, каждый из которых решает свои задачи. Выбор драйвера определяет уровень изоляции и производительности.

    | Драйвер | Описание | Основной кейс использования | | :--- | :--- | :--- | | Bridge | Создает виртуальную сеть на хосте (по умолчанию). | Локальная разработка, небольшие приложения. | | Host | Контейнер использует сетевой стек хоста напрямую. | Максимальная производительность, отсутствие NAT. | | None | Полная сетевая изоляция (только loopback). | Безопасные вычисления, пакетная обработка данных. | | Overlay | Создает распределенную сеть поверх нескольких хостов. | Docker Swarm, кластерные решения. | | Macvlan | Назначает контейнеру реальный MAC-адрес из физической сети. | Legacy-приложения, чувствительные к IP-адресации. |

    #### Драйвер Bridge: стандарт де-факто По умолчанию используется сеть bridge. Важно понимать различие между дефолтной сетью bridge и пользовательскими мостами (user-defined bridges). В дефолтной сети контейнеры могут обращаться друг к другу только по IP-адресам. В пользовательских сетях (созданных через docker network create) работает встроенный DNS-сервер Docker, позволяющий обращаться к контейнерам по их именам.

    #### Драйвер Host: когда скорость важнее изоляции В режиме host контейнер не получает собственного IP. Если ваше приложение слушает порт 80, оно займет порт 80 прямо на физическом интерфейсе сервера.

  • Плюс: Отсутствие накладных расходов на NAT и мосты. Скорость передачи данных идентична работе процесса без Docker.
  • Минус: Конфликты портов. Вы не сможете запустить два контейнера с Nginx на одном хосте в режиме host.
  • Публикация портов и механизм DNAT

    Для того чтобы внешний мир мог достучаться до сервиса внутри контейнера, используется механизм публикации портов (-p или --publish). Когда вы выполняете docker run -p 8080:80 nginx, Docker совершает несколько действий в подсистеме Netfilter (iptables) вашего Linux-хоста.

    Создается правило в таблице nat цепочки DOCKER, которое перенаправляет входящий трафик с порта 8080 хоста на IP-адрес контейнера и порт 80. Математически это можно представить как отображение множества портов хоста на порты контейнера :

    где соответствие задается администратором вручную или динамически.

    Важно помнить, что Docker по умолчанию правит iptables, обходя стандартные цепочки INPUT многих файрволов (например, UFW на Ubuntu). Если вы закрыли порт 8080 в UFW, но пробросили его через Docker, порт все равно будет открыт для внешнего мира. Для корректной настройки фильтрации трафика Docker-контейнеров необходимо работать с цепочкой DOCKER-USER.

    Стратегии хранения данных: почему контейнер должен быть эфемерным

    Философия Docker гласит: контейнер может быть уничтожен в любой момент без потери важных данных. Это реализуется через вынос состояния (state) за пределы файловой системы контейнера. Как мы помним из предыдущих лекций, файловая система контейнера состоит из слоев только для чтения и одного верхнего слоя для записи (Writable Layer). Все, что записано в Writable Layer, удаляется вместе с контейнером.

    Для сохранения данных Docker предлагает три основных механизма: Volumes, Bind Mounts и tmpfs.

    Volumes (Тома): управляемое хранилище

    Тома — это предпочтительный способ хранения. Они полностью управляются Docker и изолированы от прямого доступа процессов хоста. По умолчанию они хранятся в /var/lib/docker/volumes/.

    Преимущества томов:

  • Переносимость: Тома легче бэкапить и мигрировать, чем Bind Mounts.
  • Безопасность: Процессы на хосте за пределами Docker не могут случайно изменить данные.
  • Драйверы: Можно использовать сторонние драйверы для подключения облачных хранилищ (AWS EBS, Azure Disk) или сетевых шар (NFS, Ceph).
  • Bind Mounts: привязка к файловой системе хоста

    Этот метод позволяет смонтировать любую директорию или файл с хост-машины в контейнер. Пример: -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro. Здесь :ro (read-only) — важный флаг безопасности, предотвращающий изменение конфигов хоста изнутри контейнера.

    Bind Mounts незаменимы при разработке (когда код на хосте должен мгновенно отражаться в контейнере), но опасны в продакшене из-за жесткой привязки к путям файловой системы конкретного сервера.

    Сравнение механизмов хранения

    | Характеристика | Volumes | Bind Mounts | tmpfs | | :--- | :--- | :--- | :--- | | Место хранения | /var/lib/docker/volumes/ | Любая точка на хосте | Оперативная память | | Управление | Docker CLI | Пользователь ОС | Docker CLI / Ядро | | Изоляция | Высокая | Низкая | Максимальная | | Производительность | Высокая | Высокая | Экстремальная |

    Глубокое погружение в Bind Mounts и права доступа

    Одна из самых частых проблем Linux-администратора при работе с Bind Mounts — несоответствие UID/GID. Внутри контейнера процесс может работать от пользователя www-data с UID 33. Если вы монтируете папку с хоста, где владелец — ваш текущий пользователь с UID 1000, контейнер получит ошибку Permission denied.

    Docker не делает автоматического маппинга пользователей при монтировании (в отличие от некоторых реализаций Podman). Решения здесь три:

  • Использование флага --user (id -g) при запуске.
  • Настройка chown на хосте для целевой папки под UID приложения в контейнере.
  • Использование Docker Volumes, где Docker берет на себя часть забот по установке прав при первом создании тома.
  • Сетевое взаимодействие в Docker Compose: создание изолированных сред

    Docker Compose автоматически создает для проекта отдельную сеть типа bridge. Это позволяет изолировать микросервисы одного приложения от другого.

    Рассмотрим пример docker-compose.yml:

    В этом сценарии:

  • Создается сеть project_default.
  • Контейнер backend может обращаться к базе по имени хоста db. Docker резолвит это имя во внутренний IP контейнера.
  • База данных не публикует порты наружу (нет секции ports), что является лучшей практикой безопасности. Доступ к ней имеет только backend.
  • Данные базы вынесены в именованный том db_data, который переживет docker-compose down.
  • Продвинутые сетевые сценарии: Macvlan и IPv6

    Иногда возникают задачи, когда контейнер должен выглядеть как полноценный узел в физической сети предприятия. Например, при запуске систем мониторинга трафика или старых корпоративных приложений, которые не умеют работать с NAT.

    Драйвер Macvlan позволяет назначить контейнеру MAC-адрес и сделать его видимым в локальной сети через физический интерфейс хоста. > Предупреждение: Для работы Macvlan сетевая карта хоста должна поддерживать «promiscuous mode» (смешанный режим), а на многих облачных платформах (AWS, GCP) использование Macvlan запрещено на уровне инфраструктуры сети.

    Что касается IPv6, то Docker поддерживает его, но по умолчанию он выключен. Для включения требуется правка /etc/docker/daemon.json и настройка маршрутизации через ip6tables. В современных Kubernetes-кластерах поддержка IPv6 (Dual Stack) становится стандартом, но в чистом Docker это до сих пор требует ручной конфигурации мостов.

    Troubleshooting: как искать проблемы в сети и дисках

    Когда «ничего не работает», системный администратор должен следовать алгоритму исключения уровней.

    Отладка сети

  • Проверка резолвинга: Зайдите в контейнер (docker exec -it <id> sh) и попробуйте ping <имя_соседа>. Если пинг не идет, проверьте, находятся ли контейнеры в одной сети: docker network inspect <имя_сети>.
  • Проверка проброса портов: Используйте netstat -tulpn на хосте. Убедитесь, что docker-proxy слушает нужный порт.
  • Анализ трафика: Инструмент tcpdump — ваш лучший друг. Вы можете запустить его на интерфейсе veth конкретного контейнера, чтобы увидеть, доходят ли до него пакеты.
  • Отладка хранилищ

  • Инспекция путей: docker inspect <id> покажет секцию Mounts. Проверьте поле Source (путь на хосте) и Destination (путь в контейнере).
  • Проверка свободного места: Контейнеры могут забивать диск логами. Используйте docker system df для оценки потребления ресурсов и docker system prune для очистки неиспользуемых объектов.
  • Проверка блокировок: Иногда при аварийном завершении Docker-демона точки монтирования остаются «повисшими» в ядре. В этом случае помогает umount -l на хосте, но это крайняя мера.
  • Безопасность сетевых соединений

    По умолчанию Docker-контейнеры в одной сети могут общаться без ограничений. В высоконагруженных и безопасных системах это недопустимо. Хотя Docker не предоставляет встроенных Network Policies (как Kubernetes), вы можете:

  • Использовать разные сети для разных групп микросервисов.
  • Настраивать внешние файрволы.
  • Использовать TLS для шифрования трафика между сервисами (Service Mesh на минималках).
  • Особое внимание уделите флагу --icc=false (Inter-Container Communication) в настройках демона. Если его установить в false, контейнеры на дефолтном мосту docker0 потеряют связь друг с другом, что значительно снизит риск горизонтального перемещения злоумышленника в случае взлома одного из приложений.

    Эволюция к Kubernetes

    Подходы к сети и данным в Docker являются фундаментом для понимания Kubernetes. В K8s концепция bridge эволюционирует в CNI (Container Network Interface), а volumes превращаются в сложную систему Persistent Volumes (PV) и Claims (PVC). Однако принципы остаются прежними:

  • Сеть должна быть абстрагирована от железа.
  • Данные должны жить дольше, чем вычислительные мощности.
  • Имена сервисов важнее их IP-адресов.
  • Овладев инструментами docker network и docker volume, вы подготовите почву для работы с распределенными системами, где количество узлов и связей между ними растет экспоненциально.

    5. Альтернативные инструменты контейнеризации: практическое знакомство с Podman

    Альтернативные инструменты контейнеризации: практическое знакомство с Podman

    Почему, если Docker стал фактическим стандартом индустрии, инженеры Red Hat решили создать проект, который на первый взгляд кажется его точной копией? Ответ кроется в архитектурном изъяне, который долгое время считался «неизбежным злом»: Docker Daemon. Если этот процесс, запущенный с правами root, падает, вместе с ним «умирают» все контейнеры на хосте. Более того, наличие централизованного демона создает единую точку отказа и потенциальную дыру в безопасности. Podman (Pod Manager) предлагает принципиально иной подход — запуск контейнеров как обычных дочерних процессов пользователя, без необходимости в фоновом демоне.

    Архитектурная деконструкция: почему без демона лучше

    В классической схеме Docker клиент взаимодействует с демоном dockerd через REST API. Демон, в свою очередь, общается с containerd, а тот — с runc. Проблема в том, что dockerd является владельцем всех процессов контейнеров. В Linux-администрировании это нарушает принцип минимальных привилегий: чтобы запустить простейший контейнер, пользователю часто нужны права в группе docker, что фактически эквивалентно беспарольному доступу к sudo.

    Podman реализует модель fork/exec. Когда вы вводите команду запуска, процесс Podman напрямую порождает процесс контейнера (через conmon и runc). Это превращает контейнер в обычный процесс Linux, видимый в дереве ps как дочерний процесс вашей оболочки или системного менеджера systemd.

    Сравнение моделей управления

    | Характеристика | Docker | Podman | | :--- | :--- | :--- | | Архитектура | Клиент-серверная (Daemon-based) | Бездемонная (Daemonless) | | Привилегии | Требует root или группу docker | Поддерживает Rootless по умолчанию | | Запуск контейнеров | Через центральный демон | Прямой запуск (fork/exec) | | Совместимость | Собственный стандарт, ставший базой | Полная совместимость с OCI | | Группировка | Только отдельные контейнеры | Поддержка концепции Pods (как в K8s) |

    Отсутствие демона упрощает интеграцию контейнеров с системными инструментами Linux. Например, аудит событий через auditd в Docker затруднен, так как все действия логируются от имени демона. В Podman каждое действие привязано к конкретному пользователю, вызвавшему команду, что критически важно для систем с высокими требованиями к безопасности (PCI DSS, государственные стандарты).

    Механика Rootless-режима и отображение идентификаторов

    Самая мощная функция Podman — возможность запускать контейнеры пользователем без прав root. Однако контейнеру внутри часто требуется считать себя «рутом» (например, чтобы сменить владельца файла или занять порт). Для решения этой задачи используется механизм User Namespaces и отображение идентификаторов (UID/GID mapping).

    Когда обычный пользователь (допустим, с UID 1000) запускает контейнер, Podman использует файл /etc/subuid. В этом файле прописаны диапазоны «запасных» UID, которые выделяются пользователю для нужд контейнеризации.

    Рассмотрим типичную запись в /etc/subuid: user1:100000:65536

    Это означает, что пользователю user1 разрешено использовать 65 536 идентификаторов, начиная со 100 000. Внутри контейнера процесс может иметь UID 0 (root), но на хосте ядро Linux будет видеть его как UID 100000. Это создает непреодолимый барьер: даже если злоумышленник «вырвется» из контейнера, он окажется в системе как непривилегированный пользователь с UID 100000, у которого нет прав доступа ни к чему, кроме ресурсов самого контейнера.

    Ограничения Rootless-режима

    Несмотря на безопасность, у работы без root есть нюансы:

  • Порты: Вы не сможете «пробросить» порт ниже 1024 (например, 80 или 443), так как в Linux это привилегированные порты. Решение — использовать порты вроде 8080 или изменить параметр ядра net.ipv4.ip_unprivileged_port_start.
  • Сеть: Традиционные мосты (bridge) требуют прав root для создания veth-пар. Podman решает это через slirp4netns — эмуляцию стека TCP/IP в пространстве пользователя, что немного медленнее нативных решений.
  • Хранилища: Использование NFS или некоторых специфических файловых систем при монтировании томов может вызвать проблемы с правами доступа из-за маппинга UID.
  • Практический переход: от docker к podman

    Разработчики Podman сделали всё, чтобы переход был бесшовным. Даже существует официальный совет: alias docker=podman. Командный интерфейс совпадает на 99%.

    Установка и первая настройка

    В дистрибутивах семейства RHEL/Fedora Podman является основным инструментом. В Debian/Ubuntu установка выполняется стандартно:

    После установки важно проверить настройки хранилища. В отличие от Docker, который хранит всё в /var/lib/docker, Podman для каждого пользователя создает отдельное хранилище в ~/.local/share/containers/storage.

    Работа с реестрами (Registries)

    Docker по умолчанию ищет образы на Docker Hub. Podman более демократичен и требует явного указания реестров в конфиге /etc/containers/registries.conf. Если вы введете podman pull nginx, инструмент предложит выбрать, из какого реестра скачать образ (docker.io, quay.io, gcr.io), если это не настроено заранее.

    Рекомендуется использовать полные имена образов для исключения неоднозначности: podman pull docker.io/library/nginx:latest

    Концепция Pods: Kubernetes на вашем локальном хосте

    Главное отличие Podman от Docker, отраженное в названии — поддержка Pods (подов). Под — это группа из одного или нескольких контейнеров, которые разделяют одни и те же сетевые ресурсы, хранилища и пространство имен IPC.

    Это прямой мостик к Kubernetes. Если в Docker Compose вы описываете связь сервисов через виртуальную сеть, то в поде Podman контейнеры видят друг друга через localhost.

    Создание и управление подом

    Представим сценарий: нам нужно запустить связку из веб-сервера и лог-парсера.

  • Создаем пустой под:
  • podman pod create --name my-app -p 8080:80
  • Добавляем в него контейнер с Nginx:
  • podman run -d --pod my-app --name web nginx
  • Добавляем вспомогательный контейнер (sidecar):
  • podman run -d --pod my-app --name logger alpine sh -c "while true; do date >> /var/log/access.log; sleep 5; done"

    Теперь оба контейнера находятся внутри одного сетевого периметра. Если вы зайдете внутрь контейнера logger, вы сможете обратиться к web просто по адресу 127.0.0.1:80.

    Интеграция с Kubernetes YAML

    Podman умеет генерировать и читать манифесты Kubernetes. Это киллер-фича для администраторов. Вы можете настроить окружение локально с помощью команд CLI, а затем выгрузить готовый YAML для деплоя в кластер: podman generate kube my-app > my-app.yaml

    И наоборот, если у вас есть готовый манифест пода для K8s, Podman может запустить его локально без разворачивания тяжелого кластера вроде Minikube: podman play kube my-app.yaml

    Управление жизненным циклом через systemd

    Поскольку у Podman нет демона, возникает вопрос: как автоматически запускать контейнеры после перезагрузки сервера? В Docker за это отвечает параметр --restart always, который обрабатывается демоном. В мире Podman за автозапуск отвечает системный менеджер systemd.

    Podman включает встроенную утилиту для генерации unit-файлов: podman generate systemd --name my-container --files --new

    Флаг --new инструктирует systemd создавать контейнер при старте службы и удалять его при остановке. Это гарантирует, что контейнер всегда запускается из «чистого» состояния образа, что соответствует принципам неизменяемой инфраструктуры.

    Для Rootless-контейнеров unit-файлы размещаются в ~/.config/systemd/user/. Чтобы служба продолжала работать после того, как пользователь вышел из системы (logout), необходимо включить «задерживание» (lingering) для пользователя: loginctl enable-linger user1

    Сравнение производительности и безопасности: Podman vs Docker

    В высоконагруженных системах разница в архитектуре проявляется в потреблении памяти. Docker-демон постоянно потребляет ресурсы, даже если ни один контейнер не запущен. Podman потребляет память только в момент выполнения команд. Однако при запуске контейнера Podman порождает процесс conmon (container monitor), который следит за состоянием контейнера, пишет логи и обрабатывает сигналы. conmon написан на языке C и крайне легок (потребляет около 2-5 МБ RAM).

    Безопасность: атака через Docker Socket

    В Docker основной вектор атаки — доступ к /var/run/docker.sock. Любой процесс, имеющий доступ к этому сокету, может отправить команду на запуск привилегированного контейнера с монтированием корня хостовой системы (-v /:/host), что означает полный захват сервера.

    В Podman такого сокета по умолчанию нет (хотя его можно включить для совместимости с инструментами вроде Portainer). Каждая операция выполняется с правами текущего пользователя, и механизмы SELinux/AppArmor в дистрибутивах вроде RHEL настроены так, чтобы максимально изолировать процессы Podman даже друг от друга.

    Troubleshooting и тонкая настройка

    При работе с Podman администраторы часто сталкиваются с ошибками доступа к файлам при монтировании томов (volumes). Это связано с тем самым маппингом UID.

    Флаг :Z и :z

    Если в системе включен SELinux, попытка смонтировать директорию с хоста в контейнер приведет к ошибке Permission Denied, даже если права доступа в Linux настроены верно. Docker часто требует ручной настройки контекстов. Podman предлагает автоматизацию через суффиксы:

  • podman run -v /data:/mnt:z — Podman пометит контент в /data как разделяемый между несколькими контейнерами.
  • podman run -v /data:/mnt:Z — Podman пометит контент как эксклюзивный для этого контейнера.
  • Отладка сетевых проблем в Rootless

    Так как Rootless-контейнеры используют slirp4netns, они не имеют своего IP-адреса, доступного с хоста напрямую. Команда podman inspect может показать IP-адрес, но вы не сможете пропинговать его из основной ОС. Для отладки сетевых соединений внутри пода используйте: podman run --rm -it --pod my-app nicolaka/netshoot — это запустит контейнер с набором утилит (tcpdump, htop, curl) внутри того же сетевого пространства имен, где работает ваше приложение.

    Альтернативы внутри альтернатив: Skopeo и Buildah

    Podman — это часть «святой троицы» инструментов контейнеризации от Red Hat, которые разделяют обязанности Docker на специализированные утилиты.

  • Buildah: Специализируется исключительно на сборке образов. В отличие от podman build (который внутри использует Buildah), сама утилита Buildah позволяет собирать образы без Dockerfile, используя обычные bash-скрипты. Это удобно для создания образов из существующих артефактов без лишних слоев.
  • Skopeo: Инструмент для работы с удаленными реестрами без скачивания образа. С помощью Skopeo можно проверить наличие тега, скопировать образ напрямую из одного реестра в другой (например, из Docker Hub в приватный Harbor) или просмотреть метаданные образа.
  • Пример использования Skopeo для инспекции образа без pull: skopeo inspect docker://docker.io/library/fedora

    Это экономит трафик и время, особенно если вам нужно просто узнать размер образа или его переменные окружения.

    Когда стоит остаться на Docker?

    Несмотря на все преимущества, Podman не является «серебряной пулей». Есть сценарии, где Docker всё еще предпочтительнее:

  • Docker Swarm: Если ваша инфраструктура завязана на Swarm, Podman вам не подойдет, так как он не поддерживает этот протокол оркестрации (вместо него предлагается переход на Kubernetes).
  • Сложные CI/CD пайплайны: Некоторые плагины Jenkins или GitLab Runner жестко завязаны на наличие /var/run/docker.sock. Хотя Podman может эмулировать этот сокет через podman.socket, в сложных случаях это может работать нестабильно.
  • Windows/macOS: Docker Desktop предоставляет более зрелый и удобный графический интерфейс и виртуализацию для этих ОС, хотя Podman Machine активно развивается в этом направлении.
  • Итоговое видение перехода к Kubernetes

    Изучение Podman — это не просто освоение альтернативного CLI. Это психологическая и техническая подготовка к работе с Kubernetes. Работа с подами, понимание ограничений Rootless-режима, использование YAML-манифестов вместо длинных цепочек docker run — всё это формирует правильный инженерный подход.

    Администратор, привыкший к Podman, воспринимает контейнер не как магическую сущность внутри «черного ящика» Docker, а как обычный процесс Linux, ограниченный стандартными механизмами ядра. Это понимание критически важно при troubleshooting сложных кластеров, где проблемы часто кроются на стыке системных вызовов, прав доступа к файловым системам и сетевых пространств имен.

    6. Оркестрация с Docker Swarm: развертывание, масштабирование и отказоустойчивость

    Оркестрация с Docker Swarm: развертывание, масштабирование и отказоустойчивость

    Когда количество контейнеров в вашей инфраструктуре переваливает за десяток, а приложения начинают требовать высокой доступности и распределения по нескольким физическим серверам, возможностей Docker Compose становится недостаточно. Представьте ситуацию: один из ваших серверов выходит из строя в три часа ночи. В классической схеме с одиночными контейнерами это означает простой сервиса до тех пор, пока администратор вручную не перезапустит нагрузку на другом узле. Оркестрация решает эту проблему, превращая группу разрозненных серверов в единый вычислительный ресурс, способный к самовосстановлению.

    От одиночных хостов к кластерной логике

    До сих пор мы рассматривали контейнеризацию в рамках одного хоста. Docker Swarm — это встроенный в Docker инструмент оркестрации, который позволяет объединить несколько машин в кластер. Его главное преимущество для системного администратора заключается в низком пороге вхождения: если вы знаете Docker CLI и умеете писать docker-compose.yml, вы уже на 80% готовы к работе со Swarm.

    В отличие от Kubernetes, который требует сложной настройки сетевых плагинов, баз данных (etcd) и сертификатов, Swarm поставляется «из коробки». Он использует тот же API, что и стандартный Docker, и не требует установки дополнительных бинарных файлов. Это делает его идеальным выбором для малых и средних проектов, где избыточность Kubernetes может стать обузой для команды эксплуатации.

    Архитектура кластера: Менеджеры и Воркеры

    Кластер Swarm (или «рой») строится на разделении ролей между узлами. Это фундаментальное отличие от работы с одиночным сервером, где одна машина выполняет и функции управления, и функции исполнения.

  • Manager Nodes (Менеджеры): Это «мозги» кластера. Они отвечают за поддержание желаемого состояния системы. Менеджеры принимают запросы от пользователя, планируют запуск контейнеров на свободных узлах и следят за тем, чтобы количество запущенных реплик соответствовало заданному. Для обеспечения отказоустойчивости самих менеджеров используется алгоритм консенсуса Raft.
  • Worker Nodes (Воркеры): Это «мускулы» кластера. Их единственная задача — запускать контейнеры (в терминологии Swarm — задачи или tasks) и обеспечивать их работу. Воркеры не принимают решений о планировании и не хранят состояние кластера.
  • Важно понимать, что менеджер по умолчанию также является воркером и может исполнять полезную нагрузку, хотя в высоконагруженных системах рекомендуется переводить менеджеры в режим drain, чтобы они занимались только управлением.

    Алгоритм консенсуса Raft и отказоустойчивость управления

    Почему мы не можем просто поставить два менеджера для надежности? Здесь вступает в силу математика распределенных систем. Swarm использует алгоритм Raft для того, чтобы менеджеры могли договориться о состоянии кластера. Для принятия любого решения (например, запуска нового сервиса) требуется кворум — строгое большинство голосов.

    Количество узлов-менеджеров в кластере рассчитывается по формуле , где — это количество узлов, которые кластер может потерять без потери работоспособности.

  • Если у вас 3 менеджера, кворум составляет 2. Вы можете потерять 1 узел ().
  • Если у вас 5 менеджеров, кворум составляет 3. Вы можете потерять 2 узла ().
  • Именно поэтому количество менеджеров всегда должно быть нечетным. Четное количество (например, 2 или 4) не увеличивает отказоустойчивость, а лишь усложняет достижение кворума. Если кворум потерян (например, из трех менеджеров упали два), кластер переходит в режим "read-only": существующие контейнеры продолжат работать, но вы не сможете изменить конфигурацию, перезапустить упавший сервис или добавить новый узел, пока большинство менеджеров не вернется в строй.

    Инициализация и расширение кластера

    Процесс превращения обычного сервера в узел Swarm начинается с команды docker swarm init. В этот момент Docker генерирует уникальный идентификатор кластера и выпускает корневой сертификат (CA), который будет использоваться для взаимной TLS-аутентификации всех узлов.

    При инициализации вы указываете advertise-addr — IP-адрес, по которому другие узлы смогут связаться с менеджером.

    После выполнения команды Docker выведет токен присоединения. Безопасность Swarm построена на этих токенах: существует отдельный токен для воркеров и отдельный для менеджеров. Чтобы добавить новый узел, достаточно выполнить на нем docker swarm join --token <TOKEN> 192.168.1.10:2377.

    Вся коммуникация между узлами шифруется по умолчанию. Это критически важное отличие от ранних версий Docker, где настройка TLS была отдельным «кругом ада» для администратора. Здесь же ротация сертификатов происходит автоматически каждые 90 дней (настраиваемо).

    Декларативный подход: от Container к Service

    В обычном Docker мы оперируем понятием «контейнер». В Swarm мы переходим на уровень выше — к «сервисам» (Services). Разница принципиальна:

  • Императивный подход (Container): «Запусти мне Nginx». Если Nginx упадет, Docker (без дополнительных флагов) ничего не предпримет.
  • Декларативный подход (Service): «Я хочу, чтобы в кластере всегда работало 5 реплик Nginx». Если один узел сгорит, Swarm увидит расхождение между желаемым (5) и текущим (4) состоянием и автоматически запустит пятую реплику на другом живом узле.
  • Команда создания сервиса выглядит так:

    Здесь мы не просто запускаем процесс, мы описываем целевое состояние. Swarm разбросает эти 3 реплики по доступным воркерам, учитывая их загрузку и доступность ресурсов.

    Модели развертывания: Replicated vs Global

    Swarm предлагает две стратегии размещения задач:

  • Replicated (Реплицированная): Вы указываете точное количество копий. Менеджер распределяет их по кластеру. Это стандарт для веб-приложений и API.
  • Global (Глобальная): На каждом узле кластера запускается ровно одна копия сервиса. Это незаменимо для инфраструктурных задач: агентов мониторинга (Zabbix/Prometheus exporter), сборщиков логов (Fluentd) или антивирусных сканеров. При добавлении нового узла в кластер Swarm автоматически запустит на нем глобальный сервис без вашего участия.
  • Сетевая магия: Routing Mesh и Overlay

    Одной из самых впечатляющих функций Docker Swarm является Routing Mesh (маршрутизирующая сеть). Представьте: у вас кластер из 10 узлов. Сервис Nginx запущен только на трех из них. На какой IP-адрес слать трафик пользователю?

    Благодаря Routing Mesh вы можете отправить запрос на IP-адрес любого узла кластера (даже того, где Nginx не запущен). Docker примет пакет на порту 80 и с помощью внутренних правил IPVS (IP Virtual Server) перенаправит его на один из узлов, где контейнер реально запущен. Для внешнего мира весь кластер выглядит как один огромный сервер с открытыми портами.

    Это работает благодаря двум компонентам:

  • Ingress Network: Специальная сеть, которая обрабатывает входящий трафик для сервисов.
  • Overlay Network: Виртуальная сеть, объединяющая контейнеры на разных физических хостах в единое L2-пространство. Контейнеры могут общаться друг с другом по именам сервисов (встроенный DNS), не зная, на каких именно серверах они находятся.
  • При создании сети типа overlay Docker использует протокол VXLAN (Virtual Extensible LAN), инкапсулируя Ethernet-фреймы в UDP-пакеты. Это позволяет преодолеть ограничения маршрутизации между физическими подсетями ваших серверов.

    Управление данными: проблема Statefull в кластере

    Если с Stateless-приложениями (которым не нужно хранить данные, например, фронтенд на React) в Swarm все просто, то базы данных требуют особого внимания. Тома (Volumes), которые мы изучали ранее, в Swarm по умолчанию остаются локальными для узла.

    Если ваш сервис PostgreSQL был запущен на Node-1 и хранил данные в локальном томе, то при падении Node-1 Swarm перезапустит Postgres на Node-2. Однако Node-2 ничего не знает о данных на Node-1. Postgres запустится с пустой базой.

    Для решения этой проблемы в Swarm применяются три стратегии:

  • Placement Constraints (Ограничения размещения): Мы можем жестко привязать сервис к конкретному узлу по его ID или метке (label).
  • Это надежно, но убивает смысл оркестрации: если Node-1 упадет, база не поднимется на другом узле.
  • External Storage (Внешние хранилища): Использование сетевых файловых систем (NFS, Ceph, GlusterFS). Тома монтируются на все узлы кластера, и контейнер видит свои данные, где бы он ни запустился.
  • Cloud Plugins: В облачных средах (AWS, GCP) используются плагины, которые переподключают облачные диски (EBS) к тому узлу, где в данный момент запускается контейнер.
  • Стек (Stack): Docker Compose в мире Swarm

    Команда docker service create удобна для быстрых тестов, но для продакшена она не подходит — слишком много параметров нужно держать в голове. Здесь на сцену выходит понятие Stack.

    Stack — это группа связанных сервисов, которые описываются в YAML-файле, идентичном формату Docker Compose, но с дополнительной секцией deploy.

    Пример файла production-stack.yml:

    Развертывание стека выполняется одной командой:

    Swarm сам создаст сети, скачает образы и распределит задачи. Если вы измените количество реплик в файле и снова запустите stack deploy, Swarm вычислит разницу (diff) и применит только изменения, не прерывая работу всего стека.

    Обновление сервисов без простоя (Rolling Updates)

    Одна из сложнейших задач администратора — обновление приложения без прерывания обслуживания пользователей. Swarm решает это через механизм Rolling Updates.

    В секции update_config (см. пример выше) мы указываем:

  • parallelism: 2 — обновлять по 2 контейнера за раз.
  • delay: 10s — ждать 10 секунд между группами обновлений.
  • Процесс выглядит так: Swarm останавливает две старые реплики, запускает две новые с новым тегом образа, проверяет их Healthcheck (если он настроен) и, если все хорошо, переходит к следующей паре. Если новые контейнеры начинают падать, Swarm может автоматически остановить обновление или даже откатиться к предыдущей версии (rollback_config).

    Это дает колоссальное преимущество перед ручным управлением: риск «положить» весь сервис неудачным релизом сводится к минимуму.

    Секреты и конфигурации: безопасность прежде всего

    В прошлой статье мы упоминали, что передавать пароли через переменные окружения (ENV) — плохая практика, так как они видны в docker inspect. Swarm предлагает встроенное решение: Secrets и Configs.

  • Secrets: Предназначены для чувствительных данных (пароли, SSH-ключи, SSL-сертификаты). При создании секрета он передается на менеджеры и хранится там в зашифрованном виде (в памяти Raft-лога). Когда сервис запускается, секрет монтируется в контейнер как временный файл в /run/secrets/, который существует только в оперативной памяти (tmpfs).
  • Configs: Работают аналогично, но для нечувствительных данных (nginx.conf, настройки логирования).
  • Пример использования секрета в стеке:

    Даже если злоумышленник получит доступ к файловой системе сервера-воркера, он не найдет там паролей в открытом виде, так как они не пишутся на диск.

    Сравнение с Docker Swarm и Kubernetes: когда что выбирать?

    Часто возникает вопрос: зачем учить Swarm, если есть Kubernetes? Как профессор педагогики, я настаиваю на последовательности. Swarm — это мост между администрированием одиночного сервера и сложными распределенными системами.

    | Характеристика | Docker Swarm | Kubernetes | | :--- | :--- | :--- | | Сложность установки | Низкая (swarm init) | Высокая (требует спец. инструментов) | | Кривая обучения | Пологая (знакомый CLI) | Крутая (новые абстракции: Pods, ReplicaSets, Ingress) | | Ресурсоемкость | Минимальная | Значительная (минимум 2 ГБ RAM для control-plane) | | Гибкость | Ограничена встроенными функциями | Практически безгранична за счет CRD и операторов | | Автомасштабирование | Только через сторонние скрипты | Встроено (HPA, VPA) |

    Swarm идеально подходит для:

  • Внутренних инструментов компании.
  • Стейджинг-окружений.
  • Проектов, где команда администрирования состоит из 1-2 человек.
  • Окружений с ограниченными ресурсами (Edge computing, IoT-шлюзы).
  • Kubernetes же становится необходим, когда вам нужно автоматическое масштабирование в зависимости от нагрузки на CPU, сложная логика деплоя (Canary releases) или когда количество микросервисов исчисляется сотнями.

    Поиск и устранение неисправностей (Troubleshooting)

    Работа в кластере добавляет новый слой неопределенности: «Где мой контейнер?». Если в обычном Docker мы писали docker ps, то в Swarm основной командой становится docker service ps <service_name>.

    Она показывает историю всех попыток запуска задач. Если вы видите статус Rejected или Failed, это первый сигнал к действию.

  • Проверка событий: docker service inspect --pretty <service_name> покажет общую конфигурацию и последние ошибки планировщика.
  • Логи: docker service logs -f <service_name> агрегирует логи со всех реплик в один поток. Это невероятно удобно — вам не нужно бегать по серверам и искать, где именно упал процесс.
  • Состояние узлов: docker node ls покажет, все ли менеджеры и воркеры в статусе Ready. Если узел в статусе Disconnected, Swarm уже должен был начать перенос задач с него.
  • Типичная ошибка новичка — забыть открыть порты в Firewall между узлами. Для работы Swarm критически важны:

  • TCP порт 2377 (управление кластером).
  • TCP/UDP порт 7946 (коммуникация между узлами/gossip).
  • UDP порт 4789 (трафик overlay-сети).
  • Замыкание мысли

    Docker Swarm — это мощный и незаслуженно обделенный вниманием инструмент. Он привносит в жизнь администратора порядок, автоматизирует рутинные задачи по перезапуску упавших сервисов и позволяет бесшовно обновлять приложения. Понимая принципы работы Swarm — кворум менеджеров, декларативные сервисы и overlay-сети — вы закладываете фундамент для изучения Kubernetes. Ведь многие концепции, такие как желаемое состояние и балансировка трафика, в K8s работают по схожим (хотя и более сложным) принципам. Переход к следующему этапу нашего курса — архитектуре Kubernetes — станет логичным развитием этих идей на уровне планетарных масштабов.

    7. Архитектура Kubernetes и пошаговое развертывание кластера с помощью kubeadm

    Архитектура Kubernetes и пошаговое развертывание кластера с помощью kubeadm

    Представьте, что вы управляете флотом из тысячи кораблей, каждый из которых — это контейнер. В Docker Swarm вы были капитаном, который отдавал команды вручную или через простые скрипты. Но когда флот разрастается до масштабов глобальной логистической империи, вам нужна не просто рация, а полноценный центр управления полетами, способный автоматически прокладывать курсы, чинить поломки на лету и перераспределять ресурсы без вашего участия. Kubernetes (K8s) — это и есть такой центр управления. Однако, прежде чем нажать кнопку «Пуск», системный администратор должен понимать, из каких шестеренок состоит этот колосс и как собрать его вручную, чтобы в случае аварии не смотреть в пустой экран терминала.

    Анатомия Control Plane: мозг кластера

    Kubernetes спроектирован по классической схеме Master-Worker, но сложность его внутренних компонентов значительно выше, чем в Docker Swarm. Узел, выполняющий роль Master (в терминологии K8s — Control Plane), не просто хранит состояние, он постоянно сравнивает текущую реальность с вашим идеальным описанием в YAML-файлах.

    API Server (kube-apiserver)

    Это «лицо» и единственный вход в кластер. Все компоненты — и внутренние, и внешние (например, ваш kubectl) — общаются только с ним. API Server — это REST-интерфейс, который проверяет подлинность запросов, проводит аутентификацию и записывает данные в хранилище. Важно понимать: API Server не хранит состояние сам по себе, он является интеллектуальным прокси-сервером перед базой данных.

    etcd: хранилище истины

    Это распределенная база данных типа «ключ-значение», основанная на алгоритме консенсуса Raft. В etcd хранится абсолютно всё: конфигурации, секреты, текущее состояние подов и узлов. > Если вы потеряли данные etcd и у вас нет бэкапа — вы потеряли кластер. Даже если все ваши серверы с приложениями работают, Kubernetes больше не знает о них. > > Kubernetes Documentation: Operating etcd clusters

    Для обеспечения отказоустойчивости количество узлов etcd должно быть нечетным (), чтобы избежать ситуации «split-brain», когда кластер разделяется на две части и каждая считает себя главной.

    Scheduler (kube-scheduler)

    Это «диспетчер ресурсов». Когда вы просите запустить новый контейнер, планировщик ищет для него подходящее место. Он учитывает:
  • Запросы ресурсов (сколько RAM и CPU нужно приложению).
  • Ограничения (например, «не запускать базу данных на том же узле, где работает другая база»).
  • Политики аффинити (Affinity/Anti-affinity) и «загрязнения» узлов (Taints/Tolerations).
  • Controller Manager (kube-controller-manager)

    Это набор бесконечных циклов управления. Контроллер следит за состоянием объектов. Например, Replication Controller замечает, что один из трех подов упал, и отправляет запрос API-серверу на создание нового. Он не «чинит» контейнер, он приводит систему к заданному состоянию.

    Рабочие узлы: исполнительные механизмы (Worker Nodes)

    Если Control Plane — это штаб, то Worker Nodes — это цеха, где кипит работа. Здесь работают три ключевых компонента.

    kubelet: агент на местах

    Это самый важный компонент на рабочем узле. Kubelet получает от API-сервера спецификации подов (PodSpecs) и следит за тем, чтобы контейнеры, описанные в них, были запущены и здоровы. Он общается напрямую со средой выполнения контейнеров (Container Runtime).

    Container Runtime

    Kubernetes не запускает контейнеры сам. Он делегирует это среде выполнения через интерфейс CRI (Container Runtime Interface). Раньше стандартом был Docker, но сейчас чаще используются containerd или CRI-O. Это делает K8s универсальным: ему неважно, как именно изолирован процесс, если среда выполнения соблюдает стандарт.

    kube-proxy: сетевой регулировщик

    Этот компонент отвечает за сетевые правила на узлах. Он реализует концепцию Service (виртуальный IP для группы контейнеров). Kube-proxy манипулирует правилами iptables или IPVS на хосте, чтобы трафик, приходящий на определенный порт, попадал в нужный контейнер, даже если тот переехал на другой сервер.

    Подготовка инфраструктуры к развертыванию

    Прежде чем запустить kubeadm, необходимо привести Linux-серверы в состояние, пригодное для работы кластера. В отличие от Docker, Kubernetes крайне чувствителен к настройкам ядра и сети.

    Требования к узлам

    Для минимального жизнеспособного кластера (1 Master + 2 Workers) рекомендуется:
  • ОС: Ubuntu 22.04 LTS или Debian 11/12.
  • Ресурсы: минимум 2 CPU и 2 ГБ RAM на Master-узле (на 1 CPU kubeadm просто откажется запускаться без форсирования).
  • Уникальные Hostname, MAC-адреса и product_uuid для каждого узла.
  • Отключение Swap

    Это обязательное требование. Kubernetes предполагает, что он полностью контролирует распределение памяти. Если начнется свопинг, планировщик не сможет адекватно оценивать производительность, а kubelet может повести себя непредсказуемо.

    Сетевой плагин (CNI): соединяем поды

    Если вы сейчас введете kubectl get nodes, вы увидите, что мастер находится в статусе NotReady. Это нормально. Kubernetes — это модульная система, и в ней «из коробки» нет сети для общения подов между разными узлами. Нам нужно установить CNI (Container Network Interface) плагин.

    Популярные решения:

  • Flannel: максимально простое, использует VXLAN. Хорошо для обучения.
  • Calico: мощное, поддерживает сетевые политики безопасности (Network Policies) и BGP.
  • Cilium: современное, базируется на eBPF, обеспечивает высокую производительность.
  • Для нашего примера установим Flannel:

    Как только сетевые поды запустятся, статус узла изменится на Ready.

    Подключение Worker-узлов

    На рабочих узлах (где уже установлены containerd, kubeadm и kubelet) нужно выполнить команду join, которую выдал мастер.

    Что происходит во время Join?

  • Узел скачивает сертификат CA с мастера и проверяет его подлинность по хешу.
  • Узел генерирует свою пару ключей и отправляет запрос на подпись (CSR) мастеру.
  • Kubelet на воркере получает подписанный сертификат и авторизуется в API.
  • Мастер регистрирует новый узел и начинает планировать на него задачи.
  • Если вы потеряли команду join, её можно сгенерировать заново на мастере: kubeadm token create --print-join-command

    Проверка состояния и первый запуск приложения

    Проверим, что все узлы видят друг друга:

    Вы должны увидеть список всех ваших серверов со статусом Ready.

    Теперь запустим тестовое приложение (Nginx), чтобы убедиться, что сеть работает:

    Команда expose создаст объект Service. Поскольку мы указали тип NodePort, Kubernetes откроет случайный порт в диапазоне 30000-32767 на всех узлах кластера. Узнать этот порт можно командой:

    Теперь вы можете зайти по IP любого узла (мастера или воркера) на этот порт и увидеть приветственную страницу Nginx.

    Нюансы эксплуатации и Troubleshooting

    Развертывание — это только начало. В процессе работы с kubeadm вы столкнетесь с рядом особенностей.

    Срок действия сертификатов

    kubeadm создает сертификаты сроком на 1 год. Если их не обновить, кластер «умрет» ровно через 365 дней. Проверить срок действия можно командой: kubeadm certs check-expiration Обновление выполняется одной командой kubeadm certs renew all.

    Проблемы с Cgroup Driver

    Если после установки kubelet постоянно перезагружается, первой делом проверьте логи: journalctl -u kubelet. Если вы видите ошибку misconfiguration: kubelet cgroup driver: "systemd" is different from docker cgroup driver: "cgroupfs", значит, вы пропустили шаг настройки containerd.toml.

    Очистка кластера

    Если вы ошиблись в конфигурации и хотите начать заново, не нужно переустанавливать ОС. Используйте reset:

    Сравнение: kubeadm vs Managed Kubernetes

    Вы только что прошли путь ручного развертывания. В облаках (AWS EKS, Google GKE, Yandex Managed Service for Kubernetes) большую часть этой работы берет на себя провайдер.

  • В Managed K8s вы не видите Control Plane. Вы не управляете etcd и не платите за ресурсы мастер-узлов.
  • В Self-hosted (kubeadm) у вас полный контроль. Вы можете тюнинговать API Server, использовать кастомные аутентификаторы и хранить данные там, где считаете нужным.
  • Для системного администратора умение работать с kubeadm — это базовый навык «выживания». Облако может быть недоступно или слишком дорого, а локальный кластер требует понимания того, как kubelet общается с API и почему etcd требует быстрых дисков (SSD/NVMe) из-за чувствительности алгоритма Raft к задержкам записи (latency).

    Kubernetes — это не просто софт, это операционная система для дата-центра. И как любая ОС, она требует бережной настройки фундамента: сети, прав доступа и синхронизации времени. Убедитесь, что на всех узлах настроен NTP (например, через chrony), иначе расхождение во времени даже в несколько секунд приведет к инвалидации SSL-сертификатов и развалу кластера.

    8. Основные объекты Kubernetes: управление Pods, Deployments и сетевыми сервисами

    Основные объекты Kubernetes: управление Pods, Deployments и сетевыми сервисами

    Представьте, что ваш кластер Kubernetes — это огромный оркестр, где каждый инструмент должен вступить в нужный момент, играть свою партию и замолчать по сигналу дирижера. В мире Docker мы оперировали отдельными контейнерами, но в Kubernetes мы переходим на уровень декларативного управления объектами. Здесь администратор не «запускает процесс», а описывает «желаемое состояние» системы. Если в Docker Swarm мы мыслили категориями сервисов, то в Kubernetes архитектура объектов гораздо богаче и гибче. Понимание того, как Pod, Deployment и Service взаимодействуют между собой, — это фундамент, без которого невозможно построить ни одну отказоустойчивую систему.

    Pod: неделимая единица вычислений

    В Kubernetes вы никогда не запускаете контейнер напрямую. Вместо этого вы создаете Pod (Под). Это самая маленькая и простая единица в объектной модели Kubernetes. Если провести аналогию с биологией, то контейнер — это органелла, а Pod — это клетка. Клетка может состоять из нескольких органелл, но она является минимальной самостоятельной единицей жизни.

    Почему не просто контейнер?

    Основная идея Pod заключается в обеспечении тесной связи между процессами. Контейнеры внутри одного пода разделяют:

  • Сетевое пространство (Network Namespace): У всех контейнеров в поде один и тот же IP-адрес и порт-пространство. Они могут обращаться друг к другу через localhost.
  • IPC (Inter-Process Communication): Контейнеры могут использовать стандартные механизмы взаимодействия процессов POSIX.
  • Хранилище (Volumes): Вы можете смонтировать один и тот же том в несколько контейнеров пода для обмена файлами.
  • Типичный пример использования многоконтейнерного пода — паттерн Sidecar. Основной контейнер исполняет код приложения (например, Python Flask), а вспомогательный (sidecar) занимается сбором логов или обновлением конфигурационных файлов из внешнего репозитория.

    Жизненный цикл и эфемерность

    Важно понимать: поды эфемерны. Они «рождаются», получают ресурсы, работают и «умирают». Если узел (Node), на котором запущен под, выходит из строя, Kubernetes не пытается «починить» этот конкретный под. Он просто создает новый на другом узле. Именно поэтому у пода нет фиксированного IP-адреса, на который можно полагаться в долгосрочной перспективе.

    Статусы пода (Pod Phase):

  • Pending: Под принят системой, но один или несколько образов контейнеров еще не созданы или не скачаны.
  • Running: Под привязан к узлу, все контейнеры созданы, и хотя бы один находится в процессе запуска или работы.
  • Succeeded: Все контейнеры в поде успешно завершились и не будут перезапущены.
  • Failed: Все контейнеры завершились, и хотя бы один завершился с ошибкой (ненулевой код выхода).
  • Unknown: Состояние пода не может быть определено (обычно из-за проблем со связью с узлом).
  • Анатомия YAML-манифеста

    Все взаимодействие с Kubernetes происходит через API, чаще всего посредством YAML-файлов. Любой объект в K8s имеет четыре обязательных поля верхнего уровня:

  • apiVersion: Версия API, используемая для создания объекта.
  • kind: Тип объекта (Pod, Service, Deployment).
  • metadata: Данные, позволяющие идентифицировать объект (имя, пространство имен, метки/labels).
  • spec: Описание желаемого состояния (самая важная часть, специфичная для каждого типа объекта).
  • Пример простейшего пода:

    Здесь labels (метки) играют критическую роль. Это пары «ключ-значение», которые не влияют на работу самого пода, но используются другими объектами (например, сервисами) для поиска и группировки ресурсов.

    Deployment: управление масштабированием и обновлениями

    Если поды эфемерны, как нам обеспечить работу десяти копий приложения? Мы могли бы создать десять манифестов Pod, но это путь к административному аду. Для автоматизации управления подами используется Deployment.

    Deployment — это контроллер, который следит за тем, чтобы в кластере всегда было запущено нужное количество реплик вашего приложения. Он не управляет подами напрямую, а делает это через промежуточный объект — ReplicaSet.

    Механизм работы Deployment

    Когда вы создаете Deployment, происходит следующее:

  • Deployment создает ReplicaSet.
  • ReplicaSet создает указанное количество подов.
  • Если вы удалите под вручную, ReplicaSet заметит расхождение между «желаемым» и «текущим» состоянием и немедленно запустит новый экземпляр.
  • Это реализует концепцию Self-healing (самовосстановления). Для администратора Linux это означает смену парадигмы: мы больше не заходим по SSH, чтобы перезапустить упавший сервис, мы настраиваем контроллер, который делает это за нас.

    Стратегии обновления (Rolling Update)

    Одно из главных преимуществ Deployment — управление процессом обновления ПО без простоя (Zero Downtime). По умолчанию используется стратегия RollingUpdate.

    Допустим, у нас есть 3 реплики приложения версии v1. Мы обновляем образ в манифесте до v2.

  • Kubernetes создает новый ReplicaSet для версии v2.
  • Он запускает один под v2.
  • Как только под v2 проходит проверку готовности (Readiness Probe), Kubernetes удаляет один под v1.
  • Процесс повторяется, пока все поды не станут версии v2.
  • Вы можете контролировать этот процесс параметрами:

  • maxSurge: Сколько подов сверх нормы можно создать в процессе обновления (например, 25%).
  • maxUnavailable: Сколько подов могут быть недоступны одновременно.
  • Описание Deployment в YAML

    Обратите внимание на секцию selector. Она связывает Deployment с подами. Метки в template.metadata.labels должны строго совпадать с selector.matchLabels. Если вы ошибетесь здесь, Deployment не сможет найти «свои» поды и будет бесконечно пытаться их создать.

    Сетевые сервисы: Service и стабильные точки входа

    Как мы уже выяснили, поды постоянно умирают и рождаются, получая при этом новые IP-адреса. Если наше фронтенд-приложение должно обращаться к бэкенду, оно не может использовать IP-адрес пода. Для решения этой проблемы существует объект Service.

    Service — это абстракция, которая определяет логический набор подов и политику доступа к ним. Сервис получает стабильный IP-адрес (ClusterIP) и DNS-имя, которые не меняются на протяжении всей жизни сервиса.

    Селекторы и связность

    Сервис находит поды для балансировки трафика с помощью Label Selector. Если у вас есть 10 подов с меткой app: backend, и вы создаете сервис с селектором app: backend, сервис автоматически начнет распределять трафик между ними. Список актуальных IP-адресов подов хранится в объекте Endpoints (или EndpointSlice в новых версиях), который обновляется автоматически.

    Типы сервисов

    В Kubernetes существует четыре основных типа сервисов, каждый из которых решает свою задачу:

  • ClusterIP (по умолчанию):
  • Сервис доступен только внутри кластера. Это идеальный вариант для внутренних баз данных или микросервисов, которым не нужен доступ извне. Трафик идет на виртуальный IP сервиса, а затем проксируется (обычно через kube-proxy) на один из подов.

  • NodePort:
  • Сервис становится доступен извне кластера через статический порт на каждом узле (в диапазоне 30000–32767). Если вы обратитесь к <NodeIP>:30000, трафик попадет в сервис. Это простой, но не самый безопасный и масштабируемый способ организации внешнего доступа.

  • LoadBalancer:
  • Использует балансировщик нагрузки облачного провайдера (AWS, GCP, Azure). Kubernetes автоматически создаст внешний балансировщик и направит трафик в ваш сервис. Для локальных кластеров (Bare Metal) этот тип требует установки дополнительных инструментов, таких как MetalLB.

  • ExternalName:
  • Своеобразный DNS-алиас. Он не использует селекторы и проксирование, а просто возвращает CNAME-запись (например, для доступа к внешней базе данных Amazon RDS так, будто она находится внутри кластера).

    Пример манифеста сервиса:

    Жизненно важные проверки: Liveness и Readiness Probes

    Просто запустить контейнер недостаточно. Процесс внутри может висеть в «зомби-состоянии» или не успеть инициализироваться. Чтобы Kubernetes понимал реальное состояние приложения, используются пробы.

    Liveness Probe (Проверка живучести)

    Определяет, жив ли контейнер. Если проверка провалена, Kubernetes убивает контейнер, и он перезапускается в соответствии с restartPolicy. Пример: Приложение зависло в бесконечном цикле и не отвечает.

    Readiness Probe (Проверка готовности)

    Определяет, готово ли приложение принимать входящий трафик. Если проверка провалена, IP-адрес этого пода удаляется из списка Endpoints сервиса. Трафик на него не идет, но сам под не перезапускается. Пример: Приложение загружает тяжелый кэш при старте в течение 30 секунд.

    Методы проверки

  • httpGet: Выполняет HTTP-запрос. Код ответа считается успешным.
  • exec: Выполняет команду внутри контейнера. Код выхода — успех.
  • tcpSocket: Проверяет, открыт ли TCP-порт.
  • Граничные случаи и нюансы эксплуатации

    Проблема «Split-Brain» и кворум

    Хотя мы обсуждаем объекты управления, помните, что за ними стоит Control Plane. Если в Deployment указано 3 реплики, а связь между мастером и узлами нарушена, мастер может решить, что поды умерли, и запустить их на других узлах. Это может привести к дублированию процессов, что критично для приложений, работающих с данными. Kubernetes минимизирует эти риски, но администратор должен четко понимать настройки terminationGracePeriodSeconds — времени, которое дается поду на корректное завершение.

    Ресурсные лимиты и Requests

    В манифесте Deployment крайне важно указывать requests и limits.
  • requests: Гарантированный объем ресурсов. Планировщик (Scheduler) использует это значение, чтобы найти подходящий узел.
  • limits: Максимальный объем. Если процесс превысит лимит по памяти, он будет убит OOM Killer. Если по CPU — он будет «задушен» (throttling), но продолжит работу.
  • Отсутствие лимитов — «бомба замедленного действия». Один прожорливый под может парализовать весь рабочий узел, вытеснив системные процессы.

    Изоляция через Namespaces

    Все объекты, которые мы рассмотрели, существуют внутри Namespaces (пространств имен). Это логические кластеры внутри одного физического. По умолчанию используется пространство default. Для администратора это основной инструмент разграничения окружений (dev, staging, prod) и квотирования ресурсов.

    Взаимодействие объектов в динамике

    Давайте соберем пазл воедино. Когда пользователь вводит kubectl apply -f deployment.yaml:

  • API Server проверяет права пользователя и валидность YAML.
  • Объект Deployment записывается в etcd.
  • Deployment Controller видит новый объект и создает ReplicaSet.
  • ReplicaSet Controller видит, что нужно 3 пода, а их 0, и создает записи о подах.
  • Scheduler видит поды без назначенного узла (nodeName пуст), анализирует ресурсы узлов и выбирает подходящие, записывая имя узла в объект пода.
  • Kubelet на конкретном узле видит, что ему назначен под, вызывает Container Runtime (например, containerd) для запуска контейнеров.
  • Если создан Service, kube-proxy на всех узлах обновляет правила iptables или IPVS, чтобы трафик на IP сервиса перенаправлялся на новые поды.
  • Эта цепочка событий демонстрирует мощь декларативного подхода. Мы не командуем «запусти», мы говорим «пусть будет», и десятки независимых контроллеров работают синхронно, чтобы привести реальность к нашему описанию.

    Освоение Pods, Deployments и Services — это переход от ручного управления серверами к проектированию распределенных систем. В следующих главах мы увидим, как эти базовые кирпичики дополняются более сложными конструкциями: Ingress для управления внешним HTTP-трафиком и PersistentVolumes для работы с состоянием (State), что позволит запускать в кластере не только легкие веб-приложени, но и тяжелые базы данных.

    9. Администрирование, безопасность RBAC и мониторинг состояния кластера Kubernetes

    Администрирование, безопасность RBAC и мониторинг состояния кластера Kubernetes

    Представьте, что вы успешно развернули кластер и запустили в нем десятки приложений. Все работает, трафик идет, но внезапно один из разработчиков случайно удаляет критически важный сервис, а другой — по ошибке выкатывает образ, который начинает потреблять всю доступную память узла, «роняя» соседние поды. В этот момент администратор осознает: запустить Kubernetes — это лишь 10% пути. Остальные 90% — это обеспечение безопасности, разграничение прав доступа и создание системы мониторинга, которая скажет о проблеме раньше, чем раздастся звонок от руководства.

    Безопасность на уровне API: аутентификация и авторизация

    Любое действие в Kubernetes — от создания пода до просмотра логов — проходит через kube-apiserver. Безопасность кластера строится на трех этапах обработки запроса: аутентификация (кто вы?), авторизация (что вам разрешено?) и Admission Control (соответствует ли ваш запрос политикам кластера?).

    В Kubernetes нет встроенного объекта «User» (пользователь), который хранился бы в базе etcd. Кластер доверяет внешним источникам: сертификатам X.509, токенам или внешним провайдерам (OIDC). Однако для системных процессов и приложений внутри кластера существует объект ServiceAccount.

    Ролевая модель доступа (RBAC)

    Role-Based Access Control (RBAC) — это стандарт де-факто для управления доступом в K8s. Модель строится на четырех основных объектах, которые можно разделить на две группы: локальные (в рамках Namespace) и глобальные (на уровне всего кластера).

  • Role и ClusterRole: определяют набор правил (разрешений). Здесь мы описываем, к каким ресурсам (pods, services, secrets) и какие действия (get, list, watch, create, update, delete) разрешены.
  • RoleBinding и ClusterRoleBinding: связывают конкретного субъекта (User, Group или ServiceAccount) с конкретной ролью.
  • Разница между ними критична для безопасности. Если вы создаете Role, она действует только внутри одного пространства имен. Если вы создаете ClusterRole, она дает права на ресурсы во всем кластере, включая узлы (Nodes) и сами Namespaces.

    > Принцип наименьших привилегий (POLP) гласит: никогда не давайте права Cluster-администратора приложению, которому нужно просто читать конфиги в своем Namespace. > > Kubernetes RBAC Documentation

    Рассмотрим структуру типичной роли для разработчика, которому нужно управлять подами, но нельзя трогать сетевые настройки:

    Чтобы эта роль заработала, ее нужно привязать к пользователю или ServiceAccount через RoleBinding. Без этой связки роль — это просто неиспользуемый список разрешений.

    Изоляция и безопасность контекста выполнения

    Даже если у пользователя ограничены права в API, он может попытаться атаковать кластер «изнутри» контейнера. По умолчанию контейнеры в Kubernetes запускаются с довольно широкими привилегиями. Например, процесс внутри контейнера может иметь UID 0 (root), что при определенных условиях позволяет выйти за пределы изоляции и получить доступ к хостовой ОС.

    Security Context и Pod Security Standards

    Для управления безопасностью на уровне процессов используется securityContext. Он настраивается как для всего пода, так и для конкретных контейнеров. Ключевые параметры, которые должен знать администратор:

    * runAsNonRoot: заставляет kubelet проверять образ перед запуском. Если в Dockerfile указан пользователь root, контейнер не запустится. * readOnlyRootFilesystem: делает файловую систему контейнера доступной только для чтения. Это блокирует большинство эксплойтов, которые пытаются скачать и запустить вредоносный скрипт в /tmp. * allowPrivilegeEscalation: запрещает процессам получать больше прав, чем у родительского процесса (предотвращает использование setuid бинарников). * capabilities: позволяет ювелирно настраивать права Linux. Например, можно забрать все права и оставить только NET_BIND_SERVICE, если приложению нужно только слушать 80-й порт.

    С версии 1.25 в Kubernetes на смену старым PodSecurityPolicies (PSP) пришли Pod Security Admission (PSA). Теперь мы просто вешаем метку на Namespace, указывая уровень строгости: privileged, baseline или restricted.

    Это действие мгновенно запретит запуск любых подов в пространстве имен production, если они не соответствуют строгим критериям безопасности (например, запускаются от root).

    Управление секретами и конфиденциальными данными

    Одной из самых частых ошибок начинающих администраторов является хранение паролей и API-ключей в обычных ConfigMaps или, что еще хуже, в коде приложения. В Kubernetes для этого существует объект Secret.

    Однако важно понимать: по умолчанию секреты в K8s хранятся в etcd в кодировке base64. Это не шифрование, а просто кодирование. Любой, кто имеет доступ к API или к файлам etcd, может легко их прочитать.

    Для обеспечения реальной безопасности администратор должен внедрить один из двух подходов:

  • Encryption at Rest: настройка kube-apiserver для шифрования данных перед записью в etcd (используя KMS — Key Management Service).
  • Внешние хранилища (External Secrets): использование таких инструментов, как HashiCorp Vault. В этом случае в кластере создается лишь «ссылка» на секрет, а само значение подтягивается в момент запуска пода.
  • Мониторинг: от метрик до алертинга

    Мониторинг Kubernetes кардинально отличается от мониторинга обычных серверов. В мире Linux мы следим за конкретными IP и именами хостов. В K8s поды эфемерны: они рождаются и умирают постоянно. Поэтому мониторинг должен быть основан на метках (labels) и автоматическом обнаружении сервисов (Service Discovery).

    Стек Prometheus и Grafana

    Золотым стандартом в экосистеме является Prometheus. Его архитектура идеально ложится на модель Kubernetes: * Prometheus Server: собирает метрики методом «pull» (опрашивает цели). * Exporter: преобразует метрики приложения или системы в формат, понятный Prometheus (например, node-exporter для метрик железа). * Alertmanager: обрабатывает алерты и отправляет уведомления (Slack, Telegram, Email). * Grafana: визуализирует данные в виде красивых дашбордов.

    Для администратора критически важно настроить сбор метрик с трех уровней:

  • Уровень инфраструктуры: загрузка CPU, RAM, дисков и сети на физических/виртуальных узлах.
  • Уровень кластера (Control Plane): задержки API-сервера, длина очереди планировщика, состояние etcd.
  • Уровень приложений: количество 5xx ошибок, время ответа (latency), бизнес-метрики.
  • Особое внимание стоит уделить kube-state-metrics. Это сервис, который слушает API Kubernetes и генерирует метрики о состоянии объектов: сколько реплик в Deployment сейчас доступно, какие поды находятся в статусе Pending и почему.

    Анализ логов: EFK/Loki

    Метрики говорят нам, что что-то сломалось. Логи говорят нам, почему это произошло. Поскольку логи контейнеров исчезают вместе с ними, их необходимо агрегировать во внешнем хранилище.

    Популярные стеки: * EFK (Elasticsearch, Fluentd, Kibana): мощный, но ресурсоемкий инструмент. Подходит для глубокого анализа больших объемов текста. * Grafana Loki: «Prometheus для логов». Не индексирует весь текст, а только метаданные (метки), что делает его гораздо легче и дешевле в эксплуатации.

    Администрирование и обслуживание кластера

    Работа администратора не заканчивается на настройке прав и мониторинга. Кластер требует регулярного ухода: обновления версий, ротации сертификатов и очистки ресурсов.

    Ротация сертификатов

    Все компоненты K8s общаются между собой по TLS. Срок действия сертификатов, созданных kubeadm, обычно составляет 1 год. Если их не обновить вовремя, кластер «ослепнет»: компоненты перестанут доверять друг другу, и управление станет невозможным.

    Проверка срока действия: kubeadm certs check-expiration

    Обновление: kubeadm certs renew all

    После обновления необходимо перезапустить компоненты Control Plane (статические поды в /etc/kubernetes/manifests), чтобы они подхватили новые файлы.

    Обновление версии Kubernetes

    Kubernetes выпускает минорные обновления (например, с 1.28 на 1.29) примерно три раза в год. Жизненный цикл каждой версии — около года. Процесс обновления должен быть строго последовательным: нельзя прыгнуть с 1.26 сразу на 1.29.

    Алгоритм безопасного обновления узла:

  • Cordon: пометить узел как недоступный для новых подов (kubectl cordon <node>).
  • Drain: «выселить» текущие поды на другие узлы (kubectl drain <node> --ignore-daemonsets).
  • Upgrade: обновить kubeadm, выполнить kubeadm upgrade apply, затем обновить kubelet и kubectl.
  • Uncordon: вернуть узел в строй (kubectl uncordon <node>).
  • Этот процесс гарантирует, что ваше приложение останется доступным (при условии наличия нескольких реплик и правильно настроенных PodDisruptionBudgets), пока узлы по очереди уходят на обслуживание.

    Квоты и лимиты: защита от «прожорливых» соседей

    В многопользовательской среде (Multi-tenancy) необходимо ограничивать потребление ресурсов. Без этого один криво написанный микросервис с утечкой памяти может вызвать OOM Kill для критически важных компонентов кластера.

    Для этого используются два механизма:

  • ResourceQuotas: ограничивают суммарное потребление ресурсов в рамках Namespace (например, не более 20 CPU и 100 ГБ RAM на весь отдел маркетинга).
  • LimitRange: устанавливают значения по умолчанию для контейнеров, если разработчик забыл их указать, а также задают минимальные и максимальные границы.
  • Пример ResourceQuota:

    Если суммарный запрос (requests) всех подов в Namespace team-alpha достигнет 4 ядер, Kubernetes запретит создание новых подов, пока старые не будут удалены или лимиты не будут пересмотрены.

    Troubleshooting: когда всё пошло не так

    Администрирование — это прежде всего умение быстро находить причину отказа. В Kubernetes большинство проблем диагностируется по стандартному алгоритму:

  • Проверка событий (Events): kubectl get events -A --sort-by='.lastTimestamp'. Это «черный ящик» кластера, где фиксируются ошибки планирования, нехватка ресурсов или сбои при скачивании образов.
  • Инспекция объекта: kubectl describe pod <name>. В нижней части вывода всегда есть список последних событий, относящихся именно к этому объекту.
  • Логи контейнера: kubectl logs <name> --previous. Флаг --previous позволяет увидеть логи предыдущего (упавшего) воплощения контейнера, что бесценно при диагностике CrashLoopBackOff.
  • Проверка состояния узлов: kubectl describe node <name>. Ищите раздел Conditions. Если вы видите MemoryPressure или DiskPressure в статусе True, значит, узлу физически плохо.
  • Часто проблема кроется в сети. Если поды не видят друг друга, проверьте состояние сетевого плагина (CNI) и правила iptables на узлах. Инструменты вроде ephemeral containers (kubectl debug) позволяют запустить временный контейнер с сетевыми утилитами прямо внутри сетевого пространства имен проблемного пода, не меняя его манифест.

    Завершая обзор администрирования, важно помнить: Kubernetes — это самовосстанавливающаяся система, но она восстанавливается только в рамках тех правил, которые вы ей задали. Правильно настроенный RBAC защитит от человеческого фактора, лимиты — от программных ошибок, а мониторинг даст время на реакцию до того, как инцидент станет катастрофой.