Базове розуміння Docker як «ізольованої коробки для застосунку» є достатнім для локальної розробки, але фундаментально недостатнім для проектування високонавантажених High-Availability (HA) систем. Для розробки архітектури Kubernetes важливо розуміти контейнер не як віртуальну машину, а як з Ordinary Linux Process зі специфічними обмеженнями на рівні ядра (Kernel Primitives).
---
1. Фундамент: Linux Kernel Primitives
Контейнерів у структурі ядра Linux як окремого типу об'єктів не існує. Контейнер — це абстракція над трьома стовпами ядра Linux:
1.1. Namespaces (Ізоляція ресурсів)
Namespaces обмежують те, що процес може бачити:
- PID Namespace: Забезпечує ізоляцію дерева процесів. Процес у контейнері бачить себе як
PID 1, хоча в Global Namespace ядра він має звичайний PID (наприклад,14820). - NET Namespace: Окремий мережевий стек (Loopback, IP-адреси, таблиці маршрутизації, правила iptables/nftables).
- MNT Namespace (Mount): Ізольована точка монтування файлової системи.
- IPC Namespace: Ізоляція міжпроцесної взаємодії (System V IPC, POSIX message queues).
- UTS Namespace: Ізоляція hostname та NIS domain name.
- USER Namespace: Мапінг UID/GID контейнера на Unprivileged UID/GID на хості (критично для Security Hardening).
- CGROUP Namespace: Приховує конфігурацію самих cgroups.
1.2. Control Groups / cgroups v2 (Обмеження ресурсів)
cgroups обмежують те, скільки ресурсів процес може споживати:
- Memory Limits: Налаштування
limits.memory. При перевищенні ліміту ядро викликає OOM Killer (Out of Memory), який надсилаєSIGKILL(exit code 137). - CPU Shares & Quotas:
cfs_quota_usтаcfs_period_usвизначають дробове споживання CPU (Committed Bursts).- В K8s це транслюється у
resources.requests(weight/shares) таresources.limits(CFS Hard Quotas).
- I/O Throttling (blkio): Обмеження IOPS та bandwidth для конкретних блочних пристроїв.
1.3. OverlayFS & Storage Drivers (Шари файлової системи)
Docker та контейнерні рантайми використовують Copy-on-Write (CoW) файлові системи (найчастіше Overlay2):
- Lowerdir: Read-only шари образу (Image Layers).
- Upperdir: Read-Write шар контейнера (Container Layer).
- Mergedir: Об'єднане представлення (Union Mount), яке бачить процес всередині контейнера.
> Arch Standard / Anti-Pattern: Ніколи не пишіть стійкі дані (State) у Upperdir (Read-Write шар). Це викликає деградацію I/O продуктивності через CoW та призводить до втрати даних при перезапуску. Використовуйте Stateless Architecture або монтуйте ефемерні/постійні томи (Volumes/PVC).
---
2. Еволюція Рантаймів: OCI Spec, containerd та CRI
Базова стаття описує Docker як моноліт. У сучасній хмарній архітектурі Docker розбитий на шари згідно зі стандартами Open Container Initiative (OCI).
+------------------------------------------------------------------+
| Kubernetes Kubelet / CRI |
+------------------------------------------------------------------+
| (gRPC via CRI)
v
+------------------------------------------------------------------+
| containerd / CRI-O (Daemon) |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| runc / crun (OCI Runtime) |
+------------------------------------------------------------------+
| (syscalls: clone, unshare, pivot_root)
v
+------------------------------------------------------------------+
| Linux Kernel |
+------------------------------------------------------------------+
1. High-Level Runtime (containerd, CRI-O): Забезпечує завантаження образів, керування мережею, розпакування шарів та відстеження життєвого циклу.
2. Low-Level Runtime (runc, crun): Взаємодіє безпосередньо з ядром Linux. Виконує системні виклики (clone(), unshare(), setns(), pivot_root()) і одразу завершує роботу, залишаючи процес запущеним під наглядом containerd-shim.
3. CRI (Container Runtime Interface): gRPC API в Kubernetes, яке дозволяє Kubelet працювати з будь-яким сумісним рантаймом без перекомпіляції.
---
3. Перехід від Docker Container до Kubernetes Pod
У Kubernetes одиницею розгортання є не контейнер, а Pod.
Що таке Pod з точки зору ядра?
Pod — це група контейнерів, які поділяють між собою один і той самий Linux Network та IPC Namespace, а також спільно використовують томи (Volumes).
+-----------------------------------------------------------------------+
| POD |
| |
| +-----------------------------------------------------------------+ |
| | Pause Container (Утримує NET/IPC Namespaces, IP, Ports) | |
| +-----------------------------------------------------------------+ |
| |
| +------------------------------+ +-------------------------------+ |
| | App Container | | Sidecar / Logging Container | |
| | (PID Namespace A) | | (PID Namespace B) | |
| +------------------------------+ +-------------------------------+ |
| |
| +-----------------------------------------------------------------+ |
| | Shared Volumes (Mounted in MNT Namespaces of both containers) | |
| +-----------------------------------------------------------------+ |
+-----------------------------------------------------------------------+
1. Pause Container (Infrastructure Container): При створенні Поду спочатку запускається найменший pause контейнер, який створює та утримує Network Namespace (отримує Pod IP).
2. Main & Sidecar Containers: Усі інші контейнери Поду приєднуються до Network Namespace контейнера pause через setns(). Вони бачать один одного через localhost і ділять діапазон портів.
---
4. Патерни Архітектури Контейнеризованих Застосунків
4.1. Патерн PID 1 та Обробка Сигналів (Graceful Shutdown)
Процес з PID 1 у Linux має особливий статус: за замовчуванням ядро не застосовує до нього стандартну поведінку сигналів (наприклад, SIGTERM ігнорується, якщо процес явно не зареєстрував Signal Handler).
- Проблема: Якщо застосунок запущено через shell-скрипт (
ENTRYPOINT npm startабоENTRYPOINT ["sh", "-c", "..."]), PID 1 стає/bin/sh. Shell не передаєSIGTERMдо підпроцесу (Node.js/Python/Java). При зупинці K8s чекаєterminationGracePeriodSeconds(30 сек) і вбиває процес жорсткимSIGKILL. - Arch Solution:
1. Використовувати Exec-форму в Dockerfile: ENTRYPOINT ["/binary", "arg1"].
2. Використовувати легкі ініціалізатори систем, такі як tini або dumb-init.
4.2. Multi-Stage Builds & Distroless / Scratch
Для мінімізації Attack Surface та розміру образу в Production використовуються багатоетапні збірки (Multi-stage builds):
# Stage 1: Build Environment
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .
# Stage 2: Minimal Runtime Environment
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]
- Чому це важливо: Втискання підсумкового образу до мінімуму (відсутність
/bin/sh,curl,apt) унеможливлює більшість векторів RCE-атак.
4.3. Sidecar & Envoy Mesh Pattern
Контейнеризація дозволяє розділити бізнес-логіку та системну інфраструктуру:
- Main Container: Виконує лише чисто бізнес-функціонал.
- Sidecar Container (Envoy/Istio Proxy): Перехоплює весь мережевий трафік, здійснює TLS-термінацію (mTLS), Circuit Breaking, Retry logic, Distributed Tracing (Jaeger/Zipkin) та експорт метрик у Prometheus.
---
5. Kubernetes & Native Production Readiness
Щоб контейнер працював стабільно в K8s, він повинен відповідати вимогам Cloud-Native систем:
5.1. Health Checks (Probes)
- Startup Probe: Перевіряє, чи ініціалізувався застосунок (важливо для «важких» Java/C++ застосунків). Блокує Liveness/Readiness перевірки до завершення.
- Liveness Probe: Перевіряє, чи не завис процес (Deadlock). У разі невдачі Kubelet перезапускає контейнер (
RestartPolicy). - Readiness Probe: Перевіряє, чи готовий застосунок приймати мережевий трафік. У разі невдачі Pod виключається з endpoints відповідного K8s Service (трафік на нього не іде).
5.2. Resource Management & QoS Classes
Kubernetes призначає Quality of Service (QoS) клас Поду на основі конфігурації ресурсів:
1. Guaranteed: requests == limits для всіх ресурсів (CPU та Memory). Найвищий пріоритет. Захищений від OOM-виселення при дефіциті ресурсів на ноді.
2. Burstable: requests < limits або вказано лише один із параметрів. Дозволяє ефективно використовувати нетипові піки навантаження, але має ризик бути знищеним OOM Killer під час високої утилізації вузла.
3. BestEffort: requests та limits не вказані. Перші кандидати на знищення (eviction) при будь-якому дефіциті ресурсів на Node.
---
Підсумкова порівняльна матриця: Docker vs Kubernetes Architecture
| Архітектурний аспект | Docker (Single Node / Docker Compose) | Kubernetes (Distributed Cluster) |
| --- | --- | --- |
| Мінімальна одиниця | Single Container | Pod (група ко-локалізованих контейнерів) |
| Мережева модель | Bridge / Host / Overlay (Docker Swarm) | Flat Network: Кожен Pod має унікальну маршрутизовану IP у кластері |
| Оркестрація та State | Ручний або через скрипти, декларативний compose | Self-Healing, Auto-Scaling (HPA/VPA), Rolling Updates, Declarative CRDs |
| Конфігурація | Environment variables, .env | ConfigMaps, Secrets, External Secrets Operator (Vault) |
| Забезпечення Security | AppArmor, Seccomp, User Namespaces | SecurityContext, NetworkPolicies, RBAC, OPA Gatekeeper / Kyverno |
| Рантайм | Docker Engine (dockerd) | CRI-сумісний runtime (containerd, CRI-O) |
---
Висновок для системного архітектора
Контейнеризація — це не інструмент пакування artifacts, це модель ізольованого виконання процесів у Linux. Docker створив доступний стандарт для розробників, але масштабування та стійкість у production досягається розумінням низькорівневих механізмів Linux та архітектурних патернів оркестрації Kubernetes.