Модуль 1.1: Що таке контейнери?
Складність:
[QUICK]— Базові концепціїЧас на проходження: 30-35 хвилин
Попередні вимоги: Немає
Що ви зможете зробити
Розділ «Що ви зможете зробити»Після цього модуля ви зможете:
- Пояснити, що таке контейнери та яку конкретну проблему («працює на моєму комп’ютері») вони вирішують
- Порівняти контейнери з віртуальними машинами та пояснити, коли використовувати кожну з них
- Описати, як контейнери використовують функції ядра Linux (namespaces, cgroups) для ізоляції програм
- Спрогнозувати, що станеться при зупинці та перезапуску контейнера (що зберігається, а що ні)
Чому це важливо
Розділ «Чому це важливо»Контейнери — це будівельні блоки розгортання сучасних застосунків. Перш ніж ви зможете зрозуміти Kubernetes (оркестратор контейнерів), вам потрібно зрозуміти, що таке контейнери та які проблеми вони вирішують.
Це не про зазубрювання технічних деталей — це про розуміння того «чому», яке дає сенс всьому іншому.
Проблема, яку вирішують контейнери
Розділ «Проблема, яку вирішують контейнери»Класична проблема розгортання
Розділ «Класична проблема розгортання»Розробник: «На моєму комп'ютері все працює!»Експлуатація (Ops): «Але це не працює на продакшені».Розробник: «У мене Python 3.9, правильні бібліотеки, коректні шляхи...»Експлуатація: «На продакшені Python 3.7, інші бібліотеки, інші шляхи...»Усі: 😤Це проблема узгодженості середовища. Застосунки залежать від:
- Версії операційної системи
- Версії середовища виконання (Python, Node, Java)
- Версій бібліотек
- Конфігураційних файлів
- Змінних оточення
- Шляхів до файлів
Коли будь-який із цих елементів відрізняється між розробкою та продакшеном — усе ламається.
Традиційні рішення (які не масштабувалися)
Розділ «Традиційні рішення (які не масштабувалися)»Рішення 1: Детальна документація
README.md:1. Встановіть Python 3.9.72. Запустіть `pip install -r requirements.txt`3. Налаштуйте змінні оточення...4. Налаштуйте шляхи...(Ніхто це не читає. А коли читають — воно вже застаріло.)Рішення 2: Віртуальні машини
Передавайте всю операційну систему:- Працює стабільно- Але 10 ГБ+ на один застосунок- Хвилини на запуск- Важке використання ресурсів- Важко керувати в масштабахРішення: Контейнери
Розділ «Рішення: Контейнери»А що, якби ми могли запакувати:- Застосунок- Його залежності- Його конфігурацію- Усе, що йому потрібно для роботи
У легку, переносну одиницю, яка працює однаково скрізь?
Це і є контейнер.Контейнери vs Віртуальні машини
Розділ «Контейнери vs Віртуальні машини»┌─────────────────────────────────────────────────────────────┐│ VMs vs КОНТЕЙНЕРИ │├─────────────────────────────────────────────────────────────┤│ ││ ВІРТУАЛЬНІ МАШИНИ КОНТЕЙНЕРИ ││ ┌─────────────────────┐ ┌─────────────────────┐ ││ │ App A │ App B │ App C│ │ App A │ App B │ App C│ ││ ├───────┼───────┼──────┤ ├───────┼───────┼──────┤ ││ │Guest │Guest │Guest │ │Container Runtime │ ││ │OS │OS │OS │ │(containerd) │ ││ ├───────┴───────┴──────┤ ├──────────────────────┤ ││ │ Hypervisor │ │ Host OS │ ││ ├──────────────────────┤ ├──────────────────────┤ ││ │ Host OS │ │ Hardware │ ││ ├──────────────────────┤ └──────────────────────┘ ││ │ Hardware │ ││ └──────────────────────┘ ││ ││ Кожна VM: повна копія ОС Контейнери: спільна Host OS││ Розмір: гігабайти Розмір: мегабайти ││ Запуск: хвилини Запуск: секунди ││ Ізоляція: апаратний рівень Ізоляція: рівень процесів ││ │└─────────────────────────────────────────────────────────────┘Ключові відмінності
Розділ «Ключові відмінності»| Аспект | Віртуальна машина | Контейнер |
|---|---|---|
| Розмір | Гігабайти | Мегабайти |
| Запуск | Хвилини | Секунди |
| ОС | Повна гостьова ОС на VM | Спільне ядро хоста |
| Ізоляція | Апаратна віртуалізація | Ізоляція процесів |
| Портативність | Формати образів VM різняться | Універсальні образи контейнерів |
| Щільність | ~10-20 VM на сервер | ~100-ні контейнерів на сервер |
Зупиніться та подумайте: Вам доручено мігрувати монолітний застосунок 15-річної давнини, який вимагає спеціальної, сильно модифікованої версії ядра Linux для належної роботи. Чи виберете ви контейнеризацію цього застосунку, чи запустите його у віртуальній машині? (Підказка: подумайте про те, що контейнери використовують спільно, а що надають VM).
Як працюють контейнери
Розділ «Як працюють контейнери»Подумайте про це: Якщо контейнери не є віртуальними машинами, як вони ізолюють застосунки? VM створює повністю окрему операційну систему. Контейнери використовують ядро ОС хоста, але «обманюють» кожен процес, змушуючи його думати, що він має власну файлову систему, мережу та дерево процесів. Цей трюк полягає в самому Linux — двох функціях ядра, які називаються namespaces (для ізоляції) та cgroups (для обмеження ресурсів).
Контейнери використовують функції ядра Linux для створення ізольованих середовищ:
1. Namespaces (Ізоляція)
Розділ «1. Namespaces (Ізоляція)»Namespaces змушують процес думати, що він має власну систему:
┌─────────────────────────────────────────────────────────────┐│ LINUX NAMESPACES │├─────────────────────────────────────────────────────────────┤│ ││ Namespace Що він ізолює ││ ───────────────────────────────────────────────────────── ││ PID ID процесів (контейнер бачить PID 1) ││ NET Мережеві інтерфейси, IP, порти ││ MNT Точки монтування файлової системи ││ UTS Ім'я хоста та домен ││ IPC Міжпроцесна взаємодія ││ USER ID користувачів та груп ││ ││ Результат: процес думає, що він один у системі ││ │└─────────────────────────────────────────────────────────────┘Зупиніться та подумайте: Уявіть, що ізоляція мережевого простору імен (
NETnamespace) повністю вийшла з ладу, але всі інші простори імен продовжують працювати. Яка саме катастрофа станеться, якщо ви спробуєте запустити три окремі контейнери вебсервера на одному хості, і всі вони налаштовані слухати порт 80?
2. Control Groups (Обмеження ресурсів)
Розділ «2. Control Groups (Обмеження ресурсів)»cgroups обмежують обсяг ресурсів, які може використовувати контейнер:
Контейнер A: макс. 512 МБ RAM, 0.5 CPUКонтейнер B: макс. 1 ГБ RAM, 1 CPUКонтейнер C: макс. 256 МБ RAM, 0.25 CPU
Кожен контейнер обмежений і не може «заморити голодом» інші3. Union Filesystems (Шаруваті образи)
Розділ «3. Union Filesystems (Шаруваті образи)»Образи контейнерів будуються шарами:
┌─────────────────────────────────────────────────────────────┐│ ШАРИ ОБРАЗУ КОНТЕЙНЕРА │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────┐ ← Код вашої прогр.││ │ Layer 4: COPY app.py /app │ (крихітний) ││ ├─────────────────────────────────────┤ ││ │ Layer 3: pip install flask │ ← Залежності ││ ├─────────────────────────────────────┤ (кешовані) ││ │ Layer 2: apt-get install python3 │ ← Runtime ││ ├─────────────────────────────────────┤ (кешовані) ││ │ Layer 1: Ubuntu 22.04 base │ ← Базова ОС ││ └─────────────────────────────────────┘ (спільна) ││ ││ Переваги: ││ - Шари розподіляються між образами ││ - Потрібно перебудовувати лише змінені шари ││ - Ефективне зберігання та передача ││ │└─────────────────────────────────────────────────────────────┘Образи контейнерів та реєстри
Розділ «Образи контейнерів та реєстри»Що таке образ контейнера?
Розділ «Що таке образ контейнера?»Образ контейнера — це шаблон тільки для читання, що містить:
- Мінімальну операційну систему (часто Alpine Linux, ~5 МБ)
- Код вашого застосунку
- Залежності (бібліотеки, середовища виконання)
- Конфігурацію
Думайте про це як про клас у програмуванні — це креслення.
Що таке контейнер?
Розділ «Що таке контейнер?»Контейнер — це запущений екземпляр образу.
Думайте про це як про об’єкт — це втілення.
Образ → Контейнер(Клас → Об'єкт)(Креслення → Будівля)(Рецепт → Страва)Реєстри контейнерів
Розділ «Реєстри контейнерів»Образи зберігаються в реєстрах:
┌─────────────────────────────────────────────────────────────┐│ РЕЄСТРИ КОНТЕЙНЕРІВ │├─────────────────────────────────────────────────────────────┤│ ││ Публічні реєстри: ││ ┌────────────────────────────────────────────┐ ││ │ Docker Hub hub.docker.com │ ││ │ GitHub Container ghcr.io │ ││ │ Quay.io quay.io │ ││ └────────────────────────────────────────────┘ ││ ││ Хмарні реєстри: ││ ┌────────────────────────────────────────────┐ ││ │ AWS ECR *.dkr.ecr.*.amazonaws.com │ ││ │ Google GCR gcr.io │ ││ │ Azure ACR *.azurecr.io │ ││ └────────────────────────────────────────────┘ ││ ││ Використання: ││ docker pull nginx # З Docker Hub ││ docker pull gcr.io/project/app # З Google ││ │└─────────────────────────────────────────────────────────────┘Іменування образів
Розділ «Іменування образів»Образи контейнерів мають специфічний формат іменування:
[registry/][namespace/]repository[:tag]
Приклади:nginx # Docker Hub, library/nginx:latestnginx:1.25 # Docker Hub, конкретна версіяmycompany/myapp:v1.0.0 # Docker Hub, власний простір іменgcr.io/myproject/myapp:latest # Google Container Registryghcr.io/username/app:sha-abc123 # GitHub Container RegistryТеги важливі
Розділ «Теги важливі»nginx:latest # Будь-що найновіше (непередбачувано!)nginx:1.25 # Конкретна версія (краще)nginx:1.25.3 # Точна версія (найкраще для продакшену)
Правило: ніколи не використовуйте :latest у продакшеніПовчальна історія: Один стартап розгорнув свій контейнер бази даних, використовуючи
postgres:latest. Протягом шести місяців усе працювало бездоганно. Одного разу вночі сервер перезавантажився, завантаживши новий образ:latest, який виявився оновленням мажорної версії з несумісними форматами файлів. База даних відмовилася запускатися, що призвело до 12 годин простою, поки вони гарячково намагалися відкотити версію та відновити дані. Фіксуйте свої теги!
Чи знали ви?
Розділ «Чи знали ви?»-
Контейнери не є новими. У Unix був chroot у 1979 році. FreeBSD Jails з’явилися у 2000 році. Linux Containers (LXC) — у 2008 році. Docker просто зробив їх доступними (2013).
-
Більшість контейнерів використовують Alpine Linux як основу. Він займає лише 5 МБ. Порівняйте з Ubuntu (~70 МБ) або повною VM (гігабайти).
-
Образи контейнерів є незмінними (immutable). Після створення вони ніколи не змінюються. Це ключ до відтворюваності.
-
Кита Docker звати Moby Dock. Кит несе контейнери (морські контейнери) на своїй спині.
Типові помилки
Розділ «Типові помилки»| Помилка / Хибне уявлення | Реальність / Виправлення |
|---|---|
| «Контейнери — це легкі VM» | Контейнери використовують ядро хоста. VM мають власне ядро. Це фундаментально різні технології. |
| Ставлення до контейнерів як до VM | Підключення до контейнерів через SSH для встановлення оновлень або виправлення конфігурацій — це антипатерн. Контейнери мають бути незмінними: якщо потрібні зміни — зберіть новий образ. |
| Зберігання даних всередині контейнера | Файлові системи контейнерів за замовчуванням є ефемерними. Коли контейнер видаляється — дані зникають. Завжди використовуйте зовнішні томи (volumes) для постійних даних. |
| «Контейнери менш безпечні» | Це інша модель загроз, а не гірша. Правильно налаштовані контейнери дуже безпечні, але запуск усього під root всередині контейнера — поширена і небезпечна помилка. |
Аналогія: Морські контейнери
Розділ «Аналогія: Морські контейнери»Зупиніться та подумайте: Якщо ви записуєте дані всередині запущеного контейнера — наприклад, файл логів або запис у базі даних — а потім контейнер аварійно завершує роботу і перезапускається, чи вважаєте ви, що ці дані збережуться? Це одна з найважливіших речей, яку слід розуміти про контейнери, і помилка в цьому призводила до реальної втрати даних на продакшені. Контейнери є ефемерними за замовчуванням — їхня файлова система тимчасова. Усе, що не збережено у томі, зникає, коли контейнер «помирає».
Назва «контейнер» походить від морських перевезень:
До морських контейнерів (1950-ті):- Кожен товар пакувався по-різному- Ручне завантаження/розвантаження- Товари пошкоджувалися в дорозі- Кораблі спеціалізувалися на типах вантажів- Повільно, дорого, ненадійно
Після морських контейнерів:- Стандартний розмір для всього- Автоматизоване завантаження/розвантаження- Захищений вміст- Будь-яке судно може везти будь-який контейнер- Швидко, дешево, надійно
Програмні контейнери:- Стандартний формат для будь-якого застосунку- Автоматизоване розгортання- Захищені від різниці в оточенні- Працюють всюди, де працюють контейнери- Швидко, портативно, надійноКонтрольні запитання
Розділ «Контрольні запитання»-
Сценарій: Застосунок розробника на Node.js ідеально працює на його ноутбуці MacOS, але аварійно завершується на продакшн-сервері Ubuntu через відсутність бібліотеки компіляції C++. Запитання: Як саме контейнер вирішує цю конкретну проблему?
Відповідь
Образ контейнера пакує не лише код застосунку Node.js, але й точне середовище виконання операційної системи (наприклад, конкретну базу Debian) та всі залежності системного рівня (як-от бібліотека C++). Оскільки контейнер запускає одне й те саме запаковане середовище і на ноутбуці, і на сервері, відсутність бібліотеки на хост-сервері Ubuntu більше не має значення. Застосунок використовує запаковану бібліотеку всередині контейнера. -
Сценарій: Ваша компанія об’єдналася з іншою фірмою та отримала у спадок критично важливий застарілий застосунок, який працює лише на Windows Server 2012. Ваша інфраструктура повністю базується на Linux. Запитання: Чи можете ви запакувати цей застосунок Windows у стандартний контейнер і запустити його на своїх серверах Linux? Чому так або чому ні?
Відповідь
Ні, не можете. Контейнери використовують ядро операційної системи хоста. Стандартний контейнер, що працює на хості Linux, покладається на ядро Linux. Застосунку Windows потрібне ядро Windows. Щоб запустити цей застосунок, вам знадобиться віртуальна машина з повною гостьовою ОС Windows або сервер Windows, здатний запускати контейнери Windows. -
Сценарій: Ви запускаєте три різні контейнери вебзастосунків на одному хост-сервері. Усі три застосунки жорстко налаштовані слухати порт 8080. Запитання: Чому хост-сервер не видає помилку «Port already in use» (порт уже використовується) під час запуску другого та третього контейнерів?
Відповідь
Це завдяки простору імен мережі Linux (`NET` namespace). Кожен контейнер отримує власний ізольований мережевий стек, включаючи власну віртуальну IP-адресу та власний набір портів. З точки зору кожного контейнера, він є єдиним процесом, що використовує порт 8080 на своєму ізольованому мережевому інтерфейсі. Хост забезпечує маршрутизацію трафіку до правильної віртуальної IP-адреси контейнера. -
Сценарій: Щойно розгорнутий застосунок на Java має серйозний витік пам’яті. За лічені хвилини він намагається виділити 64 ГБ оперативної пам’яті, що є повною потужністю хост-сервера. Запитання: Якщо цей застосунок працює у правильно налаштованому контейнері, що завадить йому обвалити хост-сервер, і яка функція Linux за це відповідає?
Відповідь
Контейнер буде примусово зупинено (OOMKilled — Out Of Memory) до того, як він зможе обвалити хост, за умови, що були встановлені ліміти ресурсів. За це відповідає функція Linux під назвою `cgroups` (Control Groups). cgroups забезпечують жорсткі обмеження на maximalnu кількість процесора та пам'яті, яку може споживати певний процес (або контейнер), захищаючи хост та інші контейнери від вичерпання ресурсів. -
Сценарій: Сайт електронної комерції відчуває величезний сплеск трафіку під час флеш-розпродажу. Один контейнер кошика для покупок перевантажений, і оркестратору потрібно негайно розширитися до 10 екземплярів. Запитання: Чи потрібно системі створювати 9 нових образів контейнерів чи запускати 9 нових контейнерів? Поясніть різницю.
Відповідь
Система запустить 9 нових контейнерів з 1 існуючого образу контейнера. Образ контейнера — це статичний шаблон або креслення тільки для читання. Контейнер — це запущений екземпляр цього креслення. Оскільки образи є незмінними шаблонами, ви можете створювати стільки ідентичних запущених контейнерів з одного образу, скільки дозволяє ваше апаратне забезпечення, миттєво масштабуючись без повторного збирання будь-чого. -
Сценарій: Молодший розробник налаштовує контейнеризовану платформу для блогів так, щоб завантажені фотографії профілів користувачів зберігалися безпосередньо в директорії
/var/www/uploadsвсередині запущеного контейнера. Пізніше тієї ж ночі контейнер аварійно завершує роботу і автоматично перезапускається. Запитання: Що стається з фотографіями профілів користувачів і чому?Відповідь
Фотографії профілів будуть назавжди втрачені. За замовчуванням контейнери є ефемерними. Будь-які дані, записані у внутрішню файлову систему контейнера, існують лише протягом життєвого циклу цього конкретного екземпляра контейнера. Коли контейнер аварійно завершує роботу і перезапускається, створюється новий чистий екземпляр з оригінального образу тільки для читання. Для збереження даних вони повинні записуватися на зовнішній том, примонтований до контейнера. -
Сценарій: Ви пишете скрипт розгортання, який завантажує та запускає
my-api:latest. У вівторок усе працює добре. У четвер ви запускаєте той самий скрипт на новому сервері, і застосунок не запускається через невідповідність схеми бази даних. Запитання: Припускаючи, що база даних не змінювалася, яка найбільш імовірна причина цього збою?Відповідь
Тег `latest` — це лише вказівник, і розробники, ймовірно, перемістили його на нову версію образу між вівторком і четвергом. Скрипт завантажив зовсім іншу, новішу версію коду застосунку, яка очікує іншу схему бази даних. Це порушує принцип передбачуваного розгортання. У продакшені завжди слід фіксувати конкретні незмінні теги версій (наприклад, `my-api:v1.2.4`), щоб гарантувати виконання одного й того самого коду щоразу.
Практична вправа: Ілюзія ізоляції
Розділ «Практична вправа: Ілюзія ізоляції»Завдання: Доведіть, що контейнер — це просто ізольований процес, що працює на вашому хості, а не магічна окрема машина.
Вимоги: Термінал із встановленим Docker.
Крок 1: Запустіть тривалий процес у контейнері
Запустіть простий контейнер Alpine, який «спить» протягом години. Зверніть увагу, що ми запускаємо його у фоновому режимі (-d).
docker run -d --name isolation-test alpine sleep 3600Крок 2: Перегляньте процес зсередини контейнера Виконайте команду в контейнері, щоб вивести список процесів.
docker exec isolation-test ps auxСпостереження: Процес sleep 3600, швидше за все, має PID (ідентифікатор процесу) 1. Він «думає», що він є найпершим процесом у всій системі.
Крок 3: Розвійте ілюзію (Перегляд з хоста)
Тепер знайдіть той самий процес sleep 3600 на вашій реальній хост-машині.
ps aux | grep "sleep 3600"Спостереження: Процес існує на вашому хості! Але його PID НЕ дорівнює 1. Це буде звичайний великий номер PID, призначений вашою операційною системою.
Крок 4: Доведіть ефемерність (Зникнення даних) Створіть файл у запущеному контейнері:
docker exec isolation-test sh -c "echo 'Important Data' > /secret.txt"Перевірте його наявність:
docker exec isolation-test cat /secret.txtТепер зупиніть і видаліть контейнер, а потім запустіть новий з такою ж назвою:
docker rm -f isolation-testdocker run -d --name isolation-test alpine sleep 3600Спробуйте знову прочитати свій файл:
docker exec isolation-test cat /secret.txtСпостереження: Файл зник. Новий контейнер запустився «з нуля» з образу тільки для читання.
Крок 5: Очищення
docker rm -f isolation-test✅ Критерії успіху
Розділ «✅ Критерії успіху»- Ви переконалися, що процес у контейнері вважає себе PID 1 (ізоляція простору імен).
- Ви знайшли той самий процес, що працює у вашій ОС хоста з іншим PID (що доводить спільне використання ядра хоста).
- Ви відчули втрату даних, знищивши контейнер, що доводить їхню ефемерну природу.
Підсумок
Розділ «Підсумок»Контейнери вирішують проблему узгодженості середовища шляхом пакування:
- Коду застосунку
- Залежностей
- Конфігурації
- Усього необхідного для роботи
Вони досягають цього за допомогою:
- Namespaces: ізоляція процесів
- Control groups: обмеження ресурсів
- Union filesystems: ефективні шаруваті образи
Контейнери:
- Легкі: мегабайти, а не гігабайти
- Швидкі: секунди на запуск, а не хвилини
- Портативні: працюють всюди, де працюють контейнери
- Незмінні: створені один раз, не змінюються
Наступний модуль
Розділ «Наступний модуль»Модуль 1.2: Основи Docker — практична робота зі створення та запуску контейнерів.