Docker на кількох вузлах без Kubernetes: реальні варіанти та жорсткі обмеження

Було корисно?

У вас більше одного сервера. У вас є контейнери. Ви не хочете Kubernetes — можливо, тому що команда маленька,
навантаження рутинні (і це добре), або в вас уже достатньо складних компонентів, що розбуджують серед ночі.
Але ви все одно хочете «багатовузловість»: планування, сервісну дискавері, відмовостійкість і оновлення без SSH-рулетки.

Сутність: «Docker через хости» — це не одна фіча. Це купа рішень щодо мережі, ідентичності,
зберігання й семантики відмов. Зробити це без Kubernetes можна абсолютно. Треба лише прийняти
обмеження одразу — особливо щодо станового зберігання й мережі — і обрати підхід, що відповідає вашій
операційній зрілості.

Що насправді означає «мультивузловий Docker»

Коли кажуть «мультивузловий Docker», зазвичай мають на увазі одну або кілька із цих можливостей:

  • Планування: вибрати хост для контейнера й відновити його після відмови.
  • Сервісна дискавері: знаходити «річ» за ім’ям, а не за IP.
  • Мережа: контейнери спілкуються між вузлами зі стабільними іменами та адекватними політиками.
  • Оновлення: прокочування вперед, відкат, не перетворювати продакшн на кригу.
  • Обробка стану: постійні дані, які не «пересоздаються» в небуття.
  • Модель здоров’я: що означає «здорово» і хто вирішує, що перезапускати.

Kubernetes упакував ці вибори в єдину (хоч і складну) систему. Без Kubernetes ви складаєте свою.
Це може бути перевагою: менше абстракцій, менше «магічних» контролерів, простіша ментальна модель. Або пасткою:
ви винаходите найскладніші 20% (стан, ідентичність, мережа), відчуваючи продуктивність у легких 80%.

Корисне питання не «Як робити мультивузловий Docker без Kubernetes?». А: Який піднабір можливостей мені справді потрібен?
Якщо у вас статeless API за лоадбалансером, вам вистачить мінімальної оркестрації.
Якщо у вас бази даних по контейнерах на всіх вузлах — ви або серйозно займаєтесь зберіганням,
або граєте в рольову гру на тему надійності.

Факти та історія, що мають значення в 2026

  • Оригінальна мультивузлова історія Docker не була про Swarm: рання «кластерація Docker» спиралася на зовнішні системи (Mesos, експерименти на базі etcd) до того, як Swarm дозрів.
  • Swarm mode (2016) прийшов із вбудованим Raft: кворум менеджерів — це справжня розподілена система; ставтеся до неї відповідно.
  • «Docker Machine» мав свій час: він автоматизував провізіонування вузлів у до-IaC епоху; більшість замінили його на Terraform/Ansible і забули.
  • Kubernetes переміг частково через стандартизацію очікувань: сервісна дискавері, rolling deploys і декларативний бажаний стан стали обов’язковими.
  • Оверлейні мережі існували задовго до контейнерів: VXLAN старший за більшість контейнерних платформ; він досі робоча конячка для L2-подібних мультивузлових мереж.
  • Рантайми контейнерів відійшли від Docker: containerd і runc стали окремими компонентами; «Docker» часто — це UX-нашарування зверху.
  • Станові контейнери завжди були спірними: аргумент «pets vs cattle» не старіє; він просто перемістився в PV, CSI-драйвери та класи зберігання.
  • Сервісна дискавері пройшла фази: від статичних конфігів до ZooKeeper/etcd/Consul до «оркестратор — джерело правди». Без K8s ви обираєте фазу.
  • iptables vs nftables досі має значення: контейнерні мережі досі спотикаються об семантику файрволів хосту й версії ядра.

Одна ідея, варта стікера на моніторі, в парафразі Ворнера Вогелса: Все рано чи пізно ламається; проєктуйте й експлуатуйте, припускаючи, що відмови — нормальні.
Якщо ваш мультивузловий план потребує ідеальних мереж і невмирущих дисків, це не план. Це надія.

Реалістичні варіанти (і для кого вони)

1) Docker Swarm mode

Swarm — найпряміша відповідь, якщо ви хочете «Docker, але на кількох хостах» з мінімальною додатковою інфраструктурою.
Він дає планування, сервісну дискавері, rolling updates, секрети й оверлейну мережу. Інтеграція тісна.
Екосистема спокійніша за Kubernetes, але спокій — не те саме, що мертвість. У багатьох компаніях спокій — це перевага.

Обирайте Swarm якщо: вам потрібна цілісна платформа, сервіси здебільшого без стану, і ви можете миритися з меншою кількістю інтеграцій.
Уникайте Swarm якщо: вам потрібні складні політики, ізоляція мульти-орендарності або ви плануєте наймати людей, що знають лише Kubernetes.

2) HashiCorp Nomad (з Docker)

Nomad — це планувальник, який легше піддається розумінню, ніж Kubernetes, при цьому надає справжню оркестрацію.
Він добре працює з Consul та Vault і охоче запускає Docker-контейнери. Також може планувати не контейнерні робочі навантаження,
що іноді важливо в brownfield-середовищах.

Обирайте Nomad якщо: хочете планування та health checks без широти Kubernetes і вам комфортний стек HashiCorp.
Уникайте Nomad якщо: потрібен «ринок» контролерів і операторів для кожної нішевої системи.

3) systemd + Docker Compose + лоадбалансер

Це підхід «дорослого bash-скрипта». Ви запускаєте Compose на кожному хості, керуєте деплоєм через CI (rsync, артефакти, образи),
а попереду — нормальний лоадбалансер. Це не гламурно. Працює. Але покладає на вас вирішення дискавері, прокатування та реакції на відмови.

Обирайте якщо: маєте кілька вузлів, стабільні навантаження та цінуєте прозорість понад функції.
Уникайте якщо: хочете автоматичного переселення після втрати хоста або часто деплоїте й масштабируєтесь.

4) DIY планування (будь ласка, не треба) + сервісна дискавері

Люди ще пробують: власний «шедулер», що обирає хост по CPU, запускає docker run через SSH і реєструє в Consul.
Це може працювати… поки не з’явиться другий режим відмови. Третій режим відмов стане кар’єрним обмежувачем.

Жарт #1: Саморобна оркестрація — як писати власну базу даних: пізнавально, дорого і ви зробите це двічі.

Docker Swarm: варіант «достатньої оркестрації»

Swarm mode вбудовано в Docker Engine. Ініціалізуєте менеджера, приєднуєте воркери й визначаєте сервіси.
Отримаєте контрольний шар з консенсусом Raft. Це означає: менеджери зберігають стан, і потрібен кворум.
Втрачений кворум — і ваш кластер стає музейним експонатом.

Сильні сторони Swarm

  • Операційна простота: один бінарник, одна ментальна модель, мінімум залежностей.
  • Модель сервісу: репліки, rolling updates, health checks і routing mesh.
  • Секрети: вбудована дистрибуція в таски, шифрування в транзиті й у спокої (у журналу Raft).
  • Прийнятні дефолти: багато команд досягають успіху, бо у Swarm менше налаштувань, які можна зіпсувати.

Обмеження Swarm (які болять)

  • Станові сервіси — це ваша проблема: шедулер може перемістити таск, але ваші дані не телепортуються.
  • Мережа «досить хороша», поки не стане ні: проблеми оверлею перетворюються на багаторівневі полювання: ядро, MTU, conntrack, iptables, VXLAN.
  • Екосистема: менше сторонніх інтеграцій, менше операторів і готових шаблонів.
  • Day-2 масштабування: вам рано чи пізно знадобляться політики та RBAC, які Swarm не акцентує.

Практичні завдання (з командами, виводом і рішеннями)

Завдання 1: Перевірити статус swarm і ролі вузлів

cr0x@server:~$ docker info | sed -n '/Swarm:/,/Runtimes:/p'
Swarm: active
 NodeID: 8q2m0h9v6q6f0m8c7xqkqz0vv
 Is Manager: true
 ClusterID: k1l2m3n4o5p6q7r8s9t0u1v2w
 Managers: 3
 Nodes: 9
 Default Address Pool: 10.0.0.0/8
 SubnetSize: 24
 Data Path Port: 4789
 Orchestration:
  Task History Retention Limit: 5
 Raft:
  Snapshot Interval: 10000
  Number of Old Snapshots to Retain: 0
  Heartbeat Tick: 1
  Election Tick: 10
 Dispatcher:
  Heartbeat Period: 5 seconds
 CA Configuration:
  Expiry Duration: 3 months
 Autolock Managers: false

Що це означає: Swarm активний, цей вузол — менеджер, і у вас 3 менеджери (для кворуму потрібно 2).
Рішення: Якщо у вас 1 менеджер, виправте це до того, як довірите Swarm чимось крім лабораторії. Для 3 менеджерів забезпечте рознесення по доменах відмов.

Завдання 2: Перевірити здоров’я вузлів і доступність

cr0x@server:~$ docker node ls
ID                            HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS  ENGINE VERSION
8q2m0h9v6q6f*                 mgr-a      Ready   Active        Leader          25.0.3
3k4l5m6n7o8p9q0r1s2t3u4v5w6x   mgr-b      Ready   Active        Reachable      25.0.3
7y8z9a0b1c2d3e4f5g6h7i8j9k0l   mgr-c      Ready   Active        Reachable      25.0.3
1a2b3c4d5e6f7g8h9i0j1k2l3m4n   wrk-a      Ready   Active                        25.0.3

Що це означає: Менеджери доступні; воркери готові.
Рішення: Якщо будь-який менеджер «Unreachable», розглядайте це як ризик кворуму і розслідуйте перед розгортаннями.

Завдання 3: Переглянути стан rollout сервісу

cr0x@server:~$ docker service ps api --no-trunc
ID                          NAME         IMAGE                   NODE   DESIRED STATE  CURRENT STATE           ERROR  PORTS
u1v2w3x4y5z6                api.1        registry/app:1.9.2      wrk-a  Running        Running 2 hours ago
a7b8c9d0e1f2                api.2        registry/app:1.9.2      wrk-b  Running        Running 2 hours ago
g3h4i5j6k7l8                api.3        registry/app:1.9.2      wrk-c  Running        Running 2 hours ago

Що це означає: Усі репліки працюють; немає циклу перезапусків.
Рішення: Якщо бачите «Rejected» або «Failed», зупиніть rollout і перевірте логи/події перед тим, як шукати привидів у лоадбалансері.

Завдання 4: Підтвердити опубліковані порти та поведінку routing mesh

cr0x@server:~$ docker service inspect api --format '{{json .Endpoint.Ports}}'
[{"Protocol":"tcp","TargetPort":8080,"PublishedPort":80,"PublishMode":"ingress"}]

Що це означає: Порт 80 опублікований через ingress (routing mesh). Будь-який вузол може приймати з’єднання і пересилати до тасків.
Рішення: Якщо відлагоджуєте перебивчасті таймаути, розгляньте перехід на режим публікації host і використання зовнішнього лоадбалансера для прозорішого маршруту трафіку.

Завдання 5: Перевірити оверлейні мережі та пірінг

cr0x@server:~$ docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
1c2d3e4f5g6h   ingress           overlay   swarm
7h8i9j0k1l2m   backend           overlay   swarm
a1b2c3d4e5f6   bridge            bridge    local
f6e5d4c3b2a1   host              host      local

Що це означає: Є стандартна ingress-мережа й кастомний оверлей.
Рішення: Якщо «ingress» відсутній або пошкоджений, мережі сервісів працюватимуть, як будинок з привидами. Виправте мережу кластера перед тим, як звинувачувати додаток.

Завдання 6: Переконатися, що порти gossip/control-plane доступні

cr0x@server:~$ ss -lntup | egrep ':(2377|7946|4789)\b'
tcp   LISTEN 0      4096          0.0.0.0:2377      0.0.0.0:*    users:(("dockerd",pid=1123,fd=41))
tcp   LISTEN 0      4096          0.0.0.0:7946      0.0.0.0:*    users:(("dockerd",pid=1123,fd=54))
udp   UNCONN 0      0             0.0.0.0:7946      0.0.0.0:*    users:(("dockerd",pid=1123,fd=55))
udp   UNCONN 0      0             0.0.0.0:4789      0.0.0.0:*    users:(("dockerd",pid=1123,fd=56))

Що це означає: Порт менеджера Swarm (2377), gossip (7946 tcp/udp) і VXLAN (4789/udp) слухають.
Рішення: Якщо цих портів немає або вони блокуються файрволом хоста, оверлейна мережа і членство вузлів зазнають несподіваних відмов.

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

Поняття Nomad просте: один планувальник, легке кластерування, зрозумілі jobspec і менше рухомих частин.
У практиці «менше рухомих частин» правда, поки ви не додасте Consul для дискавері й Vault для секретів,
тоді ви все ще простіше за Kubernetes, але не зовсім у поході з кремезною сокирою.

Де Nomad найкраще підходить

  • Змішані навантаження (VM, raw exec, Docker-контейнери) в одному планувальнику.
  • Команди, що хочуть явні job-и й алокації натомість безлічі контролерів.
  • Середовища, що вже використовують Consul/Vault.

Обмеження Nomad (практичні, не ідеологічні)

  • Інтеграції зі зберіганням: вам усе одно потрібна історія томів, що переживе відмову вузла.
  • Мережа: її можна зробити чисто, але потрібно визначитися: host networking, bridge, CNI, service mesh тощо.
  • Очікування екосистеми додатків: багато вендорів очікують Kubernetes-об’єкти, а не Nomad job-и.

Практичні завдання

Завдання 7: Швидко перевірити здоров’я кластера Nomad

cr0x@server:~$ nomad server members
Name     Address          Port  Status  Leader  Raft Version  Build  Datacenter  Region
nomad-1  10.20.0.11       4648  alive   true    3             1.7.5  dc1         global
nomad-2  10.20.0.12       4648  alive   false   3             1.7.5  dc1         global
nomad-3  10.20.0.13       4648  alive   false   3             1.7.5  dc1         global

Що це означає: 3 сервери, один лідер, raft здоровий.
Рішення: Якщо немає лідера, припиніть розгортання; виправте кворум/мережу спочатку.

Завдання 8: Переглянути алокації job-а і прочитати підказки про помилки

cr0x@server:~$ nomad job status api
ID            = api
Name          = api
Type          = service
Priority      = 50
Status        = running
Datacenters   = dc1
Task Groups   = web (3 running)

Latest Deployment
ID          = 3c1b2a9d
Status      = successful
Description = Deployment completed successfully

Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created   Modified
a1b2c3d4  n-11      web         17       run      running  2h ago    2h ago
e5f6g7h8  n-12      web         17       run      running  2h ago    2h ago
i9j0k1l2  n-13      web         17       run      running  2h ago    2h ago

Що це означає: Розгортання успішне; алокації стабільні.
Рішення: Якщо алокації «failed», запустіть nomad alloc status і перевірте помилки драйвера (pull образів, конфлікти портів, обмеження CPU/mem).

Compose + systemd + SSH: гострі інструменти, гострі краї

Цей підхід поширений у малих і середніх командах та в регульованих середовищах, де «нова платформа» — неприємне слово.
Також поширений в ентерпрайзах, де існують платформи, але вам не дозволяють їх використовувати.

Модель: кожен хост виконує відомий набір контейнерів. Ви деплоїте через CI (rsync, артефакти, образи),
перезапускаєте через systemd і використовуєте лоадбалансер або DNS для розподілу трафіку. Відновлення забезпечує або LB (для stateless),
або люди (для stateful). Так, це менш «cloud-native». Але це зрозуміло о 02:00.

Практичні завдання

Завдання 9: Підтвердити реальний стан контейнерів на хості

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES        IMAGE                    STATUS                  PORTS
api          registry/app:1.9.2       Up 2 hours              0.0.0.0:8080->8080/tcp
worker       registry/worker:1.9.2    Up 2 hours
postgres     postgres:16              Up 14 days              0.0.0.0:5432->5432/tcp

Що це означає: Цей хост несе базу даних і аплікаційні контейнери; БД має довгий uptime.
Рішення: Якщо «postgres» живе на кожному хості «для зручності», зупиніться і спроєктуйте правильне розміщення стану. Зручність — як народжуються аварії.

Завдання 10: Використати systemd, щоб Compose поводився як сервіс

cr0x@server:~$ systemctl status app-stack
● app-stack.service - Docker Compose App Stack
     Loaded: loaded (/etc/systemd/system/app-stack.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-01-03 09:12:44 UTC; 2h 3min ago
   Main PID: 1459 (docker)
      Tasks: 18 (limit: 18952)
     Memory: 612.4M
        CPU: 9min 12.180s
     CGroup: /system.slice/app-stack.service
             ├─1467 /usr/bin/docker compose up
             └─... containers ...

Що це означає: Ваш стек прив’язано до init; перезавантаження не «забуде» його запустити.
Рішення: Якщо у вас немає такої нудної опори, ви рано чи пізно будете дебажити «випадкову аварію», яка фактично — «хост перезавантажився».

Мультивузлова мережа: куди йде впевненість помирати

Мультивузлова мережа ніколи не просто «відкрити кілька портів». Це MTU, інкапсуляція, conntrack-таблиці, асиметрична маршрутизація,
правила файрволу, які ви забули, і той апдейт ядра, що змінив поведінку nftables.

Якщо ви використовуєте Swarm overlay (VXLAN), ви створюєте інкапсульовану мережу поверх вашої інфраструктури.
Це може чудово працювати — доки ваш піднятий шар не має меншого MTU, ніж ви припустили, або команда безпеки
не заблокує UDP 4789, бо «ми це не використовуємо». Спойлер: тепер використовуєте.

Практичні задачі

Завдання 11: Швидко виявити симптоми невідповідності MTU

cr0x@server:~$ ip link show dev eth0 | sed -n '1,2p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff

Що це означає: MTU = 1450 (поширений у хмарних мережах зі вже активними VXLAN/GRE).
Рішення: Якщо ваш оверлей очікує 1500, а підмережа 1450 — з’являться дивні таймаути і часткові відповіді. Узгодьте MTU або налаштуйте MTU оверлею відповідно.

Завдання 12: Перевірити виснаження conntrack (класичне «працює, поки не перестане»)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 248901
net.netfilter.nf_conntrack_max = 262144

Що це означає: Ви близькі до максимуму conntrack.
Рішення: Якщо лічильник наближається до максимуму під час піків трафіку, відключаться з’єднання й виникатимуть «рандомні» відмови. Збільшіть максимум (з урахуванням пам’яті) і/або змініть патерни трафіку.

Завдання 13: Підтвердити, що VXLAN-трафік оверлею проходить

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 4789 -c 5
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:12:01.123456 IP 10.20.0.11.53422 > 10.20.0.12.4789: UDP, length 98
10:12:01.223455 IP 10.20.0.12.4789 > 10.20.0.11.53422: UDP, length 98
10:12:01.323441 IP 10.20.0.13.48722 > 10.20.0.11.4789: UDP, length 98
5 packets captured

Що це означає: VXLAN-пакети є на лінії.
Рішення: Якщо ви нічого не бачите, хоча сервіси намагаються комунікувати, ймовірно блоки файрволу/правила безпеки або неправильна маршрутизація між вузлами.

Жарт #2: Оверлейні мережі — відмінний спосіб вивчити аналіз пакетів — переважно тому, що вибору у вас не буде.

Зберігання для контейнерів на кількох хостах: реальність, не відчуття

Stateless-навантаження легко розкидати по вузлах. Stateful — там, де платформи виправдовують своє існування.
Без Kubernetes у вас немає абстракцій CSI або стандартного життєвого циклу PV. Ви все одно можете робити stateful-системи правильно.
Але потрібно свідомо обрати модель зберігання.

Три розумні моделі зберігання

Модель A: Локальне зберігання, прив’язане розміщення (просто, чесно)

Ви запускаєте stateful-сервіси на конкретних хостах з локальними дисками (LVM, ZFS, ext4). Розміщення фіксуєте (Swarm constraints, Nomad constraints або «цей хост запускає БД»).
Відновлення — процедура, а не ілюзія.

Плюси: Швидко, просто, менше залежностей. Мінуси: Втрата хоста означає ручне відновлення, якщо не додано реплікацію на рівні додатку.

Модель B: Мережеве файлове сховище (NFS)

NFS — як тарган інфраструктури: виживає все, включно з сильними думками. Для багатьох робочих навантажень — спільні завантаження, артефакти, контент з читанням — підходить.
Для write-heavy баз даних часто перетворюється на муку.

Плюси: Прямолінійність, широка підтримка. Мінуси: Латентність, семантика блокувань і проблеми «шумного сусіда»; «повільно» стає стилем життя.

Модель C: Розподілений блок (Ceph RBD) або кластерний файловий СК

Ceph дає репліковані блочні пристрої по вузлах. Потужний інструмент з операційними витратами.
Якщо у вас немає людей, що можуть це підтримувати, ви жорстоко вивчите зберігання. Якщо є — це може бути відмінно.

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

Практичні задачі

Завдання 14: Перевірити використання Docker-томів і вибір драйвера

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     pgdata
local     uploads

Що це означає: Це локальні томи. Вони живуть на хості.
Рішення: Якщо очікуєте, що контейнер переселиться на інший хост і збереже дані — локальні томи цього не зроблять. Фіксуйте розміщення або використовуйте спільне/розподілене зберігання.

Завдання 15: Знайти, де фактично лежать локальні Docker-томи

cr0x@server:~$ docker volume inspect pgdata --format '{{.Mountpoint}}'
/var/lib/docker/volumes/pgdata/_data

Що це означає: Дані під Docker-датою на цьому хості.
Рішення: Резервуйте їх як хост-дані, а не «дані контейнера». Якщо реінсталюєте хост або змінюєте Docker root dir — плануйте міграції.

Завдання 16: Перевірити стан диска й насичення (істина на рівні хоста)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server)  01/03/2026  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    4.22    8.77    0.00   74.70

Device            r/s     w/s   rkB/s   wkB/s  await  aqu-sz  %util
nvme0n1         45.2   210.1  1824.3  9312.7  18.40    2.31  96.8

Що це означає: Диск близький до насичення (%util ~97%) з відчутним await.
Рішення: Якщо «проблема кластера» — один гарячий диск, вирішіть зберігання: швидші носії, кращий кеш, менше шумних навантажень або перемістіть їх з вузла.

Завдання 17: Якщо використовуєте NFS, підтвердити опції монтування й ризик латентності

cr0x@server:~$ mount | grep nfs
10.30.0.50:/exports/uploads on /mnt/uploads type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,sec=sys,clientaddr=10.20.0.21)

Що це означає: NFSv4.2 з великими rsize/wsize і hard-монтуванням (добре для коректності, не завжди для хвильової латентності).
Рішення: Якщо додатки зависають під час глюків NFS-сервера, це очікувана поведінка з hard. Вирішіть, чи віддаєте перевагу коректності (зазвичай) або відзивчивості (рідко).

Завдання 18: Перевірити тиск файлової системи на рівні контейнера

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        18        27.4GB    10.2GB (37%)
Containers      51        9         1.8GB     1.1GB (61%)
Local Volumes   12        7         814.3GB   120.5GB (14%)
Build Cache     0         0         0B        0B

Що це означає: Томи домінують у використанні диска; очищення образів вам не допоможе.
Рішення: Якщо диски заповнюються, потрібен lifecycle менеджмент томів і резервні копії, а не ритуал docker image prune.

Безпека, секрети та ідентичність між хостами

Мультивузловість означає більше меж довіри. Single-host Docker може дозволити «ну, на тому ж боксі».
З кількома вузлами ви передаєте секрети, відкриваєте порти й даєте шедулерам діяти від вашого імені.

Що робити (і чого уникати)

  • Робіть: використовуйте Swarm secrets або зовнішній менеджер секретів. Не впікайте секрети в образи.
  • Робіть: закрийте Docker API сокет; це root з кращим маркетингом.
  • Робіть: обертайте сертифікати кластера (Swarm це робить; все одно слід моніторити строк дії).
  • Уникайте: виставляти Docker Engine TCP без mTLS і жорстких мережевих політик.

Практичні задачі

Завдання 19: Перевірити ризик експозиції демона Docker

cr0x@server:~$ ss -lntp | grep dockerd
LISTEN 0      4096       127.0.0.1:2375      0.0.0.0:*    users:(("dockerd",pid=1123,fd=7))

Що це означає: Docker API на localhost:2375 (незашифрований). Це все ще ризиковано, але не миттєво катастрофічно.
Рішення: Якщо прив’язано до 0.0.0.0 — виправляйте негайно. Якщо потрібен віддалений контроль, використовуйте SSH-тунелювання або mTLS на 2376 з whitelist-фаєрволом.

Завдання 20: Підтвердити використання секретів Swarm у сервісі

cr0x@server:~$ docker service inspect api --format '{{json .Spec.TaskTemplate.ContainerSpec.Secrets}}'
[{"File":{"Name":"db_password","UID":"0","GID":"0","Mode":292},"SecretID":"p4s5w0rds3cr3t","SecretName":"db_password"}]

Що це означає: Сервіс споживає секрет Swarm як файл з mode 0444 (292).
Рішення: Якщо секрети передаються як змінні середовища, припускайте їх витік в логи та дампи краху. Переважно використовувати файлові секрети, коли можливо.

Наблюваність і операції: логування, метрики, трейси

Мультивузловість без Kubernetes не означає «без наблюваності». Це означає, що ви не зможете покладатися на інструменти Kubernetes, щоб заклеїти прогалини.
Потрібно стандартизувати логи, метрики й базову телеметрію хостів — бо ви будете дебажити міжвузлові проблеми й хотітимете факти.

Практичні задачі

Завдання 21: Перевірити шторм перезапусків контейнерів і корелювати з тиском на хості

cr0x@server:~$ docker inspect api --format 'RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'
RestartCount=7 OOMKilled=true ExitCode=137

Що це означає: Контейнер OOM-killed і перезапущений; код виходу 137 це підтверджує.
Рішення: Збільште ліміт пам’яті, виправте витік пам’яті або зменшіть конкуренцію. Не «вирішуйте» додаванням реплік, якщо кожна репліка OOM-иться під навантаженням.

Завдання 22: Побачити тиск пам’яті на вузлі (Linux не брешe)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        27Gi       1.2Gi       512Mi       3.0Gi       1.8Gi
Swap:          4.0Gi       3.8Gi       256Mi

Що це означає: Невеликий обсяг доступної пам’яті і swap майже повний: ви в небезпечній зоні.
Рішення: Якщо багато свопу — латентність стрибає. Додайте RAM, звужуйте ліміти контейнерів або перемістіть навантаження. «Все нормально» — не стратегія управління пам’яттю.

Завдання 23: Виявити помилки демона Docker щодо мережі й iptables

cr0x@server:~$ journalctl -u docker --since "1 hour ago" | tail -n 10
Jan 03 10:44:11 server dockerd[1123]: time="2026-01-03T10:44:11.112233Z" level=warning msg="could not delete iptables rule" error="iptables: No chain/target/match by that name."
Jan 03 10:44:15 server dockerd[1123]: time="2026-01-03T10:44:15.334455Z" level=error msg="failed to allocate gateway (10.0.2.1): Address already in use"
Jan 03 10:44:15 server dockerd[1123]: time="2026-01-03T10:44:15.334499Z" level=error msg="Error initializing network controller"

Що це означає: Мережевий контролер Docker незадоволений; невідповідності ланцюгів iptables і конфлікти gateway.
Рішення: Припиніть метушню з контейнерами. Виправте стан мережі хоста (можливо, застарілі правила, конфліктні мости або невідповідний бекенд файрволу), а тоді чисто перезапустіть Docker.

Швидкий план діагностики

Мультивузлові відмови зазвичай — одне з чотирьох: контрольний шар, мережевий під/оверлей, латентність зберігання або ресурсний тиск.
Трюк — знайти яке саме за хвилини, а не години.

Перше: підтвердити, що контрольний шар в порядку

  • Swarm: docker node ls показує доступних менеджерів і готових воркерів.
  • Nomad: nomad server members показує лідера й живі пірінги.
  • Якщо контрольний шар деградував, припиніть деплої та «rolling restarts». Ви лише ще більше потривожите стан.

Друге: перевірити ресурсний тиск на уражених вузлах

  • Пам’ять: OOM kills, використання swap, free -h, лічильники перезапусків контейнерів.
  • CPU: черга виконання і насичення (використовуйте top або pidstat якщо є).
  • Диск: iostat -xz і заповненість файлової системи.
  • Якщо один вузол «гарячий», віддренуйте його (Swarm) або зупиніть планування там (Nomad) перед дебагом коду додатка.

Третє: валідувати мережеві основи

  • Порти, що слухають: 2377/7946/4789 для Swarm oверлеїв.
  • MTU: підтвердити підмережу і очікування оверлею.
  • Conntrack: переконатися, що не досягнуто межі.
  • Якщо оверлейний трафік відсутній (tcpdump нічого не показує), шукайте файрвол, маршрути або політику security group.

Четверте: ізолювати зберігання як вузьке місце (особливо для «рандомних» таймаутів)

  • Використання диска і await: iostat -xz.
  • Поведіка NFS: монтування, відгук сервера і чи зависають додатки на hard-mонтах.
  • Зростання томів: docker system df і використання файлової системи.
  • Якщо зберігання повільне — все вище здається зламаним. Виправляйте базовий шар спочатку.

Поширені помилки: симптом → корінна причина → виправлення

1) «Контейнери не можуть достукатися один до одного між хостами»

Симптом: виклики між сервісами таймаутяться; DNS імена резолвляться, але з’єднання зависають.

Корінна причина: VXLAN (UDP 4789) заблоковано або невідповідність MTU призводить до втрат фрагментів.

Виправлення: дозволити UDP 4789 наскрізь; узгодити MTU; перевірити tcpdump і невеликий тест з payload; розглянути host publish + зовнішній LB для прозорості.

2) «Менеджер Swarm став доступним лише для читання / застряг»

Симптом: деплої зависають, сервіси не конвергуються, статус менеджера стрибає.

Корінна причина: втрата кворуму або нестабільне з’єднання менеджерів; іноді латентність диска на Raft-журналах менеджера.

Виправлення: запустіть 3 чи 5 менеджерів; рознесіть по доменах відмов; забезпечте надійні диски для менеджерів; не колокуйте менеджерів на найшумніших нодах з точки зору I/O.

3) «Ми збільшили репліки, але стало повільніше»

Симптом: вище латентність після додавання реплік; більше 5xx.

Корінна причина: спільне вузьке місце: база даних, NFS, conntrack або обмеження LB; також каскад кеш-штампедів.

Виправлення: виміряйте вузьке місце; налаштуйте пули з’єднань; внісіть rate-limit; масштабувати залежність, а не лише stateless-рівень.

4) «Випадкові скидання з’єднань під час піків трафіку»

Симптом: переривчасті відмови, що зникають, коли ви дивитесь.

Корінна причина: conntrack-table повний, виснаження епhemeral портів або стрибки стану файрволу хоста.

Виправлення: перевірте nf_conntrack; збільшіть ліміти; зменшіть ритміку з’єднань за допомогою keepalives; налаштуйте LB; уніфікуйте таймаути.

5) «Stateful контейнер переселили і втрачені дані»

Симптом: сервіс перезапускається на іншому хості і приходить «порожнім».

Корінна причина: локальний том на старому хості; шедулер зробив саме те, що ви попросили, а не те, що ви мали на увазі.

Виправлення: фіксуйте stateful навантаження на вузлах; використовуйте реальну реплікацію (Postgres streaming replication тощо); або впровадьте спільне/розподілене зберігання свідомо.

6) «Оновлення ламають мережу після перезавантаження»

Симптом: після апгрейду ядра/файрволу Docker-мережі не ініціалізуються.

Корінна причина: невідповідність iptables та nftables, застарілі правила, змінені дефолти.

Виправлення: стандартизувати OS/білди; підтвердити бекенд файрволу; тестувати поведінку після reboot; мати план відкату для мережевих компонентів хоста.

Чеклісти / покроковий план

Покроково: як обрати правильний не-Kubernetes підхід

  1. Інвентаризуйте типи навантажень: stateless API, бекграундні задачі, stateful бази даних, спільне файлове зберігання.
  2. Вирішіть контракт відмов: авто-реседжулінг проти ручного фейловера для станових частин.
  3. Виберіть рівень оркестрації:
    • Потрібен реседжулінг і сервісна дискавері: Swarm або Nomad.
    • Потрібно просто «запускати те саме на цих хостах»: Compose + systemd.
  4. Спроєктуйте мережу: оверлей проти host networking; визначте порти; підтвердьте MTU і політики файрволу.
  5. Спроєктуйте зберігання: локальне pinned, NFS або розподілений блок; запишіть RPO/RTO і протестуйте відновлення.
  6. Модель безпеки: розповсюдження секретів, TLS, контроль приєднання вузлів, найменші привілеї.
  7. Базова наблюваність: централізовані логи, метрики, алерти на тиск вузлів і слід розгортання.
  8. Програйте вправи відмов: вбийте вузол, порвіть лінк, наповніть диск і підтвердьте, що поведінка відповідає очікуванням.

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

  • 3 менеджерних вузла (якщо Swarm), рознесених по доменах відмов.
  • Часова синхронізація (chrony/ntpd) на всіх вузлах; дрейф викликає дивні проблеми в TLS і системах консенсусу.
  • Правила файрволу явно дозволяють потрібні порти між вузлами.
  • Сумісні версії ОС/ядра по вузлах (або щонайменше протестовані комбінації).
  • Задокументована топологія зберігання: де живуть дані, як їх бекапити й відновлювати.
  • Документація поведінки лоадбалансера: health checks, таймаути, повторне використання з’єднань.
  • Runbooks для drain вузлів, відкатів і відновлення кворуму кластера.

Практичні задачі: drain і контроль відкату

Завдання 24: Безпечно віддренувати проблемний Swarm вузол

cr0x@server:~$ docker node update --availability drain wrk-b
wrk-b

Що це означає: Swarm перемістить таски з wrk-b (окрім global-сервісів).
Рішення: Використовуйте це, коли вузол має помилки диска, проблеми ядра або ресурсний треш. Не тримайте його «Active» і сподівайтеся на краще.

Завдання 25: Поставити поганий Swarm rollout на паузу

cr0x@server:~$ docker service update --update-parallelism 0 api
api

Що це означає: Паралелізм 0 фактично зупиняє прогрес оновлення.
Рішення: Використовуйте, коли нові таски падають і треба зупинити кровотечу, поки ви дивитесь логи і обмеження.

Завдання 26: Відкотити Swarm сервіс до попередньої специфікації

cr0x@server:~$ docker service update --rollback api
api

Що це означає: Swarm поверне сервіс до попередньої конфігурації/образу.
Рішення: Якщо остання зміна корелює з помилками — робіть rollback рано. Далі дебажте. Гордість — не SLO.

Три міні-історії з корпоративного життя

Міні-історія 1: Інцидент, спричинений неправильною передумовою

Середньої величини SaaS-команда використовувала Docker Swarm для stateless-сервісів і вирішила «законтейнеризувати все», щоб уніфікувати деплої.
Команда баз даних погодилась з однією умовою: «Ми лишимо дані на томі».
Вони створили локальний Docker-том і пішли далі.

Через місяць воркер-вузол загинув — блок живлення, не акуратне завершення. Swarm переселив таск бази даних на інший вузол.
Контейнер стартував чисто. Health check пройшов. Додаток почав писати до зовсім нової пустої бази.
За кілька хвилин клієнти помітили відсутність даних. Сапорт підняв тривогу. Усі одночасно подумали: «Чи ми щойно розділили реальність?»

Неправильна передумова була тонка: вони думали, що «том» означає «переносимий». У Docker локальний volume — локальний.
Swarm зробив свою роботу і перемістив таск. Зберігання зробило свою роботу і залишилось на диску померлого сервера.

Відновлення вимагало підняти вузол на стіл із тестовим живленням, щоб витягти дані, потім відновити їх у «новому» інстансі БД.
Пізніше архітектуру перебудували з реплікацією на рівні додатка і зафіксували stateful таски на конкретних вузлах з явними constraints.
Висновок з постмортему був коротким: «Ми ставились до локального стану як до ресурсів кластера».

Міні-історія 2: Оптимізація, що обернулась проти

Внутрішня платформа хотіла швидших деплойів. Їхній Swarm тягнув образи з регістру, який іноді уповільнювався під навантаженням.
Хтось запропонував: «Давайте передбатчимо образи на всіх вузлах щовечора, щоб деплойти не чекали мережі».
Звучало розумно й дуже DevOps-ово.

Нічна задача запускала docker pull десятка великих образів на кожному вузлі. Це спрацювало — деплойти стали швидшими.
Потім з’явився новий симптом: близько 01:00 внутрішні API почали таймаутитися, черги повідомлень відставали.
Це не повний збій. Це був гірший варіант: повільна, хитка система, що викликала сварки інженерів.

Причина не в CPU. Утворився дисковий і мережевий контеншн. Завантаження шарів сильно навантажило зберігання, заповнило page cache марними даними
і створило сплески egress, що перетнулися з бекапами. Під тиском ядро почало агресивно звільняти пам’ять.
Латентність стрибнула. Ретраи помножились. Система увійшла у зворотний зв’язок.

Виправлення було нудним: припинити масове передзавантаження на всіх вузлах одночасно, рознести його в часі, обмежити швидкість і не дозволяти «оптимізації деплоїв» конкурувати з продакшн-трафіком.
Також виміряли розміри образів і зменшили їх — бо найпростіший спосіб економії продуктивності — не переміщати байти, що не потрібні.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Фінансова команда запускала Nomad з Docker для внутрішніх сервісів. Нічого особливого: три Nomad-сервери, пул клієнтів, Consul для дискавері.
Команда була надмірно ретельна в двох речах: задокументовані runbook-и і рутинні тести відновлення для stateful-компонентів.
Люди кепкували з цього. Тихо, всі також на них покладалися.

Одної п’ятниці контролер масиву зберігання почав фліпати. Латентність не вибухнула одразу; вона хиталась.
Додатки почали випадково таймаутитись. На виклику on-call інженер пройшов швидкий діагностичний план: контрольний шар здоровий, CPU нормальний, пам’ять в нормі, далі iostat.
Await диска зростав. Логи показували ретраї в кількох сервісах. Це було зберігання, а не код.

Вони виконали runbook: віддренували найгірше вражені клієнти, переключили stateful сервіс на репліку з іншим бекендом,
зменшили write amplification, призупинивши пакетну задачу. Паралельно відновили недавній бекап у чистому середовищі, щоб перевірити цілісність.
Цей останній крок забрав час, але закрив паніку: був валідний копія.

Коли інцидент закінчився, постмортем був майже розчаровуюче спокійний. Найбільший урок: нудні практики працюють.
Вправи з відновлення не гламурні, але вони перетворюють катастрофи на неприємні післяобідня.

FAQ

1) Чи можу я запускати Docker Compose на кількох серверах?

Не як єдиний когерентний «кластер» з плануванням. Compose — на кожному хості окремо. Ви можете розгорнути той самий файл Compose на кількох хостах,
але вам доведеться самостійно керувати балансуванням навантаження, дискавері та фейловером.

2) Чи «мертвий» Docker Swarm?

Swarm не на піку популярності, але це відрізняється від смерті. Він стабільний, інтегрований і досі використовується.
Ризик — це інерція екосистеми: менше шаблонів від сторонніх, менше кандидатів з прямим досвідом і менше інтеграцій від вендорів.

3) Яке найбільше обмеження мультивузлового Docker без Kubernetes?

Життєвий цикл stateful-навантажень і переносимість зберігання. Запланувати процес легко. Запланувати дані безпечно — важко.
Якщо ви не спроєктуєте зберігання свідомо, рано чи пізно втратите дані або доступність.

4) Варто використовувати оверлейні мережі чи host networking?

Для простоти і продуктивності host networking плюс реальний лоадбалансер часто легше в експлуатації.
Оверлеї допомагають з сервісним зв’язком і портативністю, але додають режими відмов (MTU, блокування UDP, conntrack).
Обирайте залежно від того, наскільки команда готова до дебагу.

5) Як робити сервісну дискавері без Kubernetes?

Swarm має вбудований DNS сервісів на оверлейних мережах. У Nomad поширений вибір — Consul.
У світі Compose+systemd можна використовувати статичні upstream-и в лоадбалансері, DNS-записи або систему дискавері типу Consul — головне, тримати це узгодженим.

6) Що з управлінням секретами?

Swarm secrets підходять для багатьох випадків. Для більших середовищ або потреб з комплаєнсом — використовуйте спеціальний менеджер секретів.
Уникайте секретів у змінних середовища, коли можна; вони витікають у багато місць.

7) Чи можна безпечно запускати бази даних у контейнерах на кількох хостах?

Так, але треба підібрати модель реплікації/фейловера відповідно до бази і вашої операційної зрілості.
«Просто запхати в контейнер і дозволити шедулеру пересунути» — шлях до жалю.

8) Скільки менеджерів потрібно у Swarm?

Використовуйте 3 або 5 менеджерів. Один менеджер — єдине місце відмови. Два менеджери не витримають одну відмову без втрати кворуму.
Три менеджери дозволяють витримати одну відмову і продовжувати роботу.

9) Який хороший базовий варіант без Kubernetes для малої команди?

Для переважно stateless-сервісів: Swarm з 3 менеджерами, зовнішній лоадбалансер і чітка історія зберігання для кількох stateful-компонентів.
Для «переважно фіксованого розміщення»: Compose + systemd + лоадбалансер, плюс задокументовані runbook-и.

Практичні наступні кроки

  1. Запишіть ваш контракт відмов: що відбувається, коли вузол помирає, і хто/що переміщує навантаження.
  2. Виберіть один рівень оркестрації і дотримуйтесь його протягом року: Swarm, Nomad або Compose+systemd. Змішування — шлях складності в маскарадній вусі.
  3. Проєктуйте зберігання спочатку для stateful-сервісів: pinned local + реплікація, NFS для відповідних навантажень або розподілений блок, якщо можете це оперувати.
  4. Укріпіть мережу: підтвердіть MTU, ємність conntrack і потрібні порти. Тестуйте раніше, ніж продакшн примусить вас.
  5. Оперіоналізуйте: процедури drain вузлів, команди відкату, вправи бекапу/відновлення і швидкий чекліст діагностики, яким реально користуються.

Мультивузловий Docker без Kubernetes — цілком здійсненна річ. Переможний рух — не ігнорувати складність.
Переможний рух — обрати ту складність, за яку ви платитимете свідомо, вдень і з моніторингом.

← Попередня
Стратегія гарячої заміни дисків у ZFS: як замінювати диски без паніки
Наступна →
Спеціальні малі блоки ZFS: як прискорити навантаження з дрібними файлами

Залишити коментар