Курс ЛР1 · Місія 1
Академічна↔ Сюжет С.І.Д.СРС до ЛР⌨️ команди 14 · 4 нових

Лабораторна робота № 1. Контейнер розробника й огляд просторів імен

Мета роботи

Навчитися запускати ізольований Docker-контейнер розробника, оглядати простори імен процесів усередині нього, свідомо прокидати порти для доступу з хоста та автоматизувати розгортання середовища ідемпотентним bash-скриптом. Робота формує базове розуміння механізму контейнерної ізоляції в Linux, на якому будуються всі наступні лабораторні першого модуля.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Теоретичні відомості

1. Контейнер як процес, а не як віртуальна машина

Контейнер Docker принципово відрізняється від віртуальної машини. Гіпервізор (VMware, KVM, VirtualBox) виконує окрему копію ядра гостьової ОС поверх емульованого або паравіртуалізованого обладнання — це важка, але повна ізоляція на рівні заліза. Контейнер натомість — це звичайний процес хостового ядра Linux, якому надано підмінену видимість системних ресурсів через простори імен (namespaces). Ядро одне на всіх контейнерів хоста; ізолюється не апаратне середовище, а те, що процес бачить про систему. Наслідок — контейнери запускаються за мілісекунди й мають накладні витрати, близькі до нуля, тоді як віртуальна машина завантажується секундами-хвилинами і тримає постійні витрати на емуляцію.

2. Простори імен Linux

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

Простір Що ізолює Наслідок для контейнера
PID Дерево процесів Перший процес контейнера має PID 1; процеси хоста невидимі зсередини
NET Мережевий стек Власні інтерфейси, таблиця маршрутизації, localhost; порти хоста й контейнера не перетинаються без явного проброшування
MNT Дерево монтувань Контейнер бачить лише свою файлову систему (шари образу + томи), а не файлову систему хоста
UTS Хостнейм і доменне ім'я NIS hostname усередині контейнера відрізняється від хоста
IPC Об'єкти міжпроцесної взаємодії (семафори, черги повідомлень, спільна пам'ять) Контейнер не бачить і не втручається в IPC-об'єкти хоста
USER Відображення UID/GID UID 0 усередині контейнера може відповідати непривілейованому UID на хості

Технічно кожен простір імен створюється системним викликом clone(2) з відповідним прапорцем (CLONE_NEWPID, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWUSER) або пізніше приєднується процесом через unshare(2)/setns(2). Це документується в namespaces(7). Docker (через containerd і runc) виконує ці виклики за розробника автоматично при docker run.

Важливо для розуміння асиметрії ізоляції. Простори імен ховають хост від контейнера і контейнер від сторонніх процесів (та інших контейнерів), але не ховають контейнер від хоста: із хоста звичайний ps aux покаже процес контейнера як рядок зі своїм, хостовим PID — просто це інший номер, ніж той, що бачить сам процес зсередини свого PID-простору. Ізоляція — не невидимість ззовні, а підміна картини світу зсередини.

3. Проброс портів (-p) як правило NAT

Оскільки контейнер має власний NET-простір імен, служба, що слухає порт усередині нього, за замовчуванням недосяжна ні з хоста, ні з зовнішньої мережі. Прапорець docker run -p <host_port>:<container_port> створює правило трансляції мережевих адрес (NAT) на рівні мережевого простору імен, у якому працює Docker-демон: вхідний трафік на <host_port> хоста перенаправляється на <container_port> усередині мережевого простору контейнера. Без цього правила порт лишається закритим ззовні незалежно від того, що відбувається всередині контейнера.

4. Автоматизація розгортання

Ручний набір docker run з десятком прапорців щоразу — джерело помилок і невідтворюваних середовищ. Фіксація точного набору команд у bash-скрипті — мінімальна практична форма принципу «інфраструктура як код»: середовище описується і відтворюється однаково на будь- якій машині. Ключова вимога до такого скрипту — ідемпотентність: повторний запуск без попереднього ручного прибирання не повинен завершуватися помилкою (типовий кандидат — docker: Error response from daemon: Conflict. The container name "..." is already in use). Ідемпотентність досягається перевіркою наявності ресурсу перед створенням або примусовим видаленням старого екземпляра (docker rm -f) на початку скрипту.

Обладнання та програмне забезпечення

Порядок виконання

  1. Перевірити доступність Docker Engine: docker version. Очікуваний результат: блоки Client: і Server: виводяться без помилок з'єднання з демоном.
  2. Запустити базовий контейнер розробника в інтерактивному режимі: docker run -it --name devbox ubuntu:24.04 bash. Очікуваний результат: інтерактивне запрошення командного рядка всередині контейнера.
  3. Усередині контейнера виконати ps aux та echo $$. Очікуваний результат: видно лише кілька процесів самого контейнера; PID оболонки — 1.
  4. У другому терміналі на хості виконати ps aux | wc -l і порівняти з кількістю, побаченою в контейнері. Очікуваний результат: на хості кількість процесів суттєво більша.
  5. Зупинити й прибрати перший контейнер (docker rm -f devbox), запустити новий із проброшеним портом: docker run -it -p 8080:80 --name devbox2 ubuntu:24.04 bash, усередині встановити й запустити простий HTTP-сервер (apt-get install -y python3 && python3 -m http.server 80). Очікуваний результат: з хоста curl -i http://localhost:8080/ повертає 200 OK.
  6. Повторити крок 5 без прапорця -p на новому контейнері (інше ім'я) і переконатися, що звернення з хоста завершується помилкою з'єднання. Очікуваний результат: curl повертає Connection refused — доказ, що порт контейнера недоступний без явного проброшування.
  7. Оформити всі кроки 2–6 у вигляді єдиного bash-скрипту scripts/deploy.sh (#!/usr/bin/env bash, set -euo pipefail), який розгортає середовище без ручного втручання і коректно поводиться при повторному запуску (ідемпотентність). Очікуваний результат: два послідовні запуски скрипту завершуються кодом 0, без помилок про зайняте ім'я контейнера.

Завдання за рівнями

РІВЕНЬ 1 · «3» · ПЕРШОКУРСНИК

Рівень 1 (оцінка 3, автотести). Запустити базовий контейнер розробника й оглянути простори імен — переконатися, що процеси хоста приховані від контейнера, а PID оболонки всередині дорівнює 1. Відповідає крокам 1–4 порядку виконання.

РІВЕНЬ 2 · «4» · МАГІСТР

Рівень 2 (оцінка 4, захист). Прокинути порт для доступу з хоста до сервісу всередині контейнера. На захисті — усно пояснити механізм NAT-переадресації та різницю мережевих просторів контейнера й хоста, наживо продемонструвати робочий проброшений порт. Відповідає крокам 5–6 порядку виконання.

РІВЕНЬ 3 · «5» · ЛЕГЕНДА

Рівень 3 (оцінка 5, розширення). Написати автоматизований, ідемпотентний bash-скрипт scripts/deploy.sh, що розгортає середовище з усіма потрібними утилітами без ручного втручання. Відповідає кроку 7 порядку виконання.

🧪 РОЗІБРАНИЙ ПРИКЛАД

Розібраний приклад

Приклад 1. Огляд PID-простору зсередини й ззовні

# Хост, термінал 1
$ ps aux | wc -l
187

$ docker run -it --name probe ubuntu:24.04 bash
root@9f1c2e7b0a4d:/# echo $$
1
root@9f1c2e7b0a4d:/# ps aux
USER   PID %CPU %MEM  COMMAND
root     1  0.0  0.1  bash
root     9  0.0  0.1  ps aux

Усередині контейнера видно лише два процеси — власний PID-простір починається з чистого аркуша. З іншого терміналу хоста той самий процес контейнера видно як звичайний рядок ps aux під іншим, реальним PID хоста (наприклад, 4821). Це ілюструє асиметрію ізоляції, описану в теоретичних відомостях: підміна видимості діє лише зсередини простору імен.

Приклад 2. Проброс порту та перевірка ззовні

$ docker run -d -p 8080:80 --name web python:3.12-slim python3 -m http.server 80
$ curl -i http://localhost:8080/
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.4
...

-d тримає контейнер у фоновому режимі; -p 8080:80 створює правило NAT хост:8080 → контейнер:80. Якщо повторити запуск без -p, ідентичний curl поверне помилку з'єднання — переконливий доказ того, що проброс порту є явною, окремою дією, а не побічним ефектом запуску контейнера.

❓ КОНТРОЛЬНІ ПИТАННЯ

Контрольні питання

  1. Чому процес усередині контейнера бачить себе як PID 1, хоча на хості в нього інший, реальний PID?
  2. Який системний виклик і які прапорці лежать в основі створення нового простору імен для процесу?
  3. Що станеться, якщо звернутися до http://localhost:8080 з хоста, а контейнер запущено без прапорця -p?
  4. Чим прапорець -p 8080:80 відрізняється від -p 80:80 з погляду безпеки локальної машини?
  5. Які саме простори імен активуються за замовчуванням у docker run без додаткових прапорців — перелічити всі шість і пояснити, що кожен ізолює?
  6. Чому автоматизація розгортання bash-скриптом надійніша за ручний набір команд у терміналі щоразу?
  7. Що покаже ps aux на хості для процесу, запущеного всередині контейнера?
  8. Чому для ідемпотентності скрипту потрібно прибирати або перевикористовувати контейнер із заданим іменем перед повторним docker run?

Парні теми СРС

Критерії оцінювання та форма звіту

Здача відбувається тегом submit/lab01 у навчальному репозиторії, разом зі скрипт-файлом scripts/deploy.sh і звітом REPORT.md, що містить вивід команд кожного кроку та відповіді на контрольні питання.

Автотести (рівень 1) перевіряють: контейнер успішно запускається; усередині PID оболонки дорівнює 1; у виводі ps aux контейнера відсутні процеси хоста; контейнер коректно прибирається після перевірки.

Захист (рівень 2) — усне пояснення різниці між мережевим простором контейнера і хоста та жива демонстрація робочого проброшеного порту (значення порту може відрізнятися від заданого у звіті — важливо продемонструвати розуміння, а не завчений результат).

Розширення (рівень 3) перевіряється автотестами на ідемпотентність і повноту bash-скрипту: два послідовні запуски scripts/deploy.sh не повинні завершуватися помилкою через уже наявний контейнер, а сам скрипт має встановлювати всі утиліти, перелічені в starter/README.md (curl, git, procps, iproute2).

⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 14 · 4 нових

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

ps
Список процесів; ps aux — усі процеси з користувачем, PID і командою (у контейнері видно лише його власні).
wcнове
Рахує рядки, слова й байти; ... | wc -l — скільки рядків видала попередня команда.
docker run
Створює й запускає контейнер з образу (docker run -it ubuntu:24.04 bash — інтерактивно з терміналом; -d — у фоні; --rm — видалити після виходу; -p 8080:80 — прокинути порт).
docker
Клієнт Docker: усі дії з образами й контейнерами — через його підкоманди (docker <підкоманда> --help — довідка).
echo
Друкує рядок або значення змінної (наприклад echo $$ — PID поточної оболонки).
curl
Консольний HTTP-клієнт: надсилає запит на URL і друкує відповідь (curl -i http://localhost:8080/ — разом із заголовками); також уміє завантажувати файли й скрипти.
hostname
Показує ім'я машини (у контейнері — його власне, окреме від хоста).
docker rmнове
Видаляє контейнер (docker rm -f name — навіть якщо він ще працює).
lsnsнове
Перелічує простори імен процесу (lsns -p <PID>) — показує, у яких «кімнатах» ядра він живе.
ip
Керує мережею Linux: інтерфейси, адреси, маршрути, мережеві простори імен (ip netns add demo).
docker version
Показує версії клієнта й демона — перевірка, що Docker встановлений і працює.
setнове
Налаштовує режим оболонки в скрипті; set -euo pipefail — падати на першій помилці, невідомій змінній чи збої в конвейєрі.
git
Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги submit/labNN.
ls
Показує список файлів у каталозі; ls -la — з правами, власником, розміром і прихованими файлами.

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.