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

Лабораторна робота № 1. Контейнер розробника: перша капсула

Мета роботи

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

📚Теоретичні відомості335 слів · ~2 хв

1. Контейнер як процес (стисло)

Контейнер — процес операційної системи, якому ядро підмінило видимість частини системних ресурсів. Власного ядра, емульованого обладнання та завантажувача в контейнера немає; ядро спільне з хостом (перевіряється командою uname -r усередині контейнера й на хості — значення однакові). Детальний розбір — лекція 1, розділ «Контейнер як процес»; тут наведено мінімум, потрібний для виконання роботи.

2. Результат ізоляції, який спостерігається в цій роботі

Механізм ізоляції (простори імен Linux) вивчається на лекції 3; у цій роботі використовується лише результат його дії, який спостерігається двома вимірюваннями:

  1. Нумерація процесів. Оболонка, запущена як початковий процес контейнера, має PID = 1 (перевіряється echo $$), тоді як на хості цей самий процес має інший номер.
  2. Обсяг списку процесів. ps aux усередині контейнера показує лише процеси самого контейнера; ps aux | wc -l на хості дає помітно більше рядків.

Обидва спостереження фіксуються у звіті. Повний перелік просторів імен, прапорці clone() і спосіб перевірки належності процесу до простору через /proc/<pid>/ns/ — у лабораторній роботі № 3.

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. Оформити спостереження кроків 1–4 у вигляді скрипту level1.sh за контрактом рівня 1 (контейнер vtfk-lab01 з образу ubuntu:24.04, лишається запущеним після завершення скрипту; $VTFK_NONCE записано у /tmp/vtfk-nonce всередині контейнера; надруковано CONTAINER_PID, CONTAINER_PROCS, HOST_PROCS). Очікуваний результат: скрипт завершується кодом 0, повторний запуск не падає, а прогін автотестів за тегом submit/lab01 дає 6 із 6.
  6. Зупинити й прибрати перший контейнер (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.
  7. Повторити крок 6 без прапорця -p на новому контейнері (інше ім'я) і переконатися, що звернення з хоста завершується помилкою з'єднання. Очікуваний результат: curl повертає Connection refused — доказ, що порт контейнера недоступний без явного проброшування.
  8. Оформити всі кроки 2–7 у вигляді єдиного bash-скрипту core/capsule/deploy.sh (#!/usr/bin/env bash, set -euo pipefail), який розгортає середовище без ручного втручання і коректно поводиться при повторному запуску (ідемпотентність). Очікуваний результат: два послідовні запуски скрипту завершуються кодом 0, без помилок про зайняте ім'я контейнера.

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

Два репозиторії. Рівень 1 виконується в репозиторії завдання — його створює Classroom 50 на прийняття, окремий на кожну роботу, і саме там працюють автотести (здача тегом submit/labNN). Рівні 2 і 3 виконуються в наскрізному репозиторії vtfk-sid, який студент створює один раз у роботі № 1 і веде до кінця семестру; вони оцінюються на захисті, не автотестами. Шляхи виду core/... у тексті нижче — це шляхи в vtfk-sid.

Здача — тегом, а не пушем. Після коміту й пушу ставиться тег: git tag submit/lab01 && git push origin submit/lab01. Повторна здача — наступний вільний нумерований тег (submit/lab01-2, submit/lab01-3), старий тег не переставляється. Яка це робота, перевірка визначає за іменем репозиторію, а не тега.

Репозиторій vtfk-sid створюється із шаблону github.com/AlexOrd/vtfk-sid кнопкою «Use this template» (не форком), під іменем vtfk-sid, з публічною видимістю.

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

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

Здається скриптом level1.sh у корені репозиторію завдання: спостереження, яке не можна відтворити командою, не є результатом роботи. Контракт скрипту:

  1. Шапка #!/usr/bin/env bash і set -euo pipefail; запуск без аргументів.

  2. Підняти контейнер з іменем vtfk-lab01 з образу ubuntu:24.04 і лишити його працювати після завершення скрипту.

  3. Записати значення змінної оточення $VTFK_NONCE у файл /tmp/vtfk-nonce всередині контейнера. Змінну передає перевірка; щоразу вона інша.

  4. Надрукувати в стандартний вивід три рядки (порядок вільний, інший вивід дозволено):

    CONTAINER_PID=<PID головного процесу всередині контейнера>
    CONTAINER_PROCS=<скільки процесів видно всередині>
    HOST_PROCS=<скільки процесів видно на хості>
    
  5. Завершитися кодом 0, бути ідемпотентним (повторний запуск без ручного прибирання не падає через зайняте ім'я) і не вимагати інтерактивного вводу.

Що саме перевіряють автотести:

# Перевірка
1 level1.sh існує і виконуваний
2 level1.sh завершується кодом 0
3 контейнер vtfk-lab01 працює й зібраний з образу ubuntu:24.04
4 $VTFK_NONCE записано у /tmp/vtfk-nonce всередині контейнера
5 процесів усередині контейнера менше, ніж на хості
6 надруковані значення узгоджені з тим, що виміряла сама перевірка

Перевірки 3–5 виконуються після завершення скрипту (docker inspect, docker exec), тому прибирання контейнера в кінці level1.sh (docker rm -f) знімає три перевірки з шести. Прибирає контейнер сам автотест, наприкінці прогону.

Той самий контракт продубльовано в README.md репозиторію завдання — це текст, який студент бачить першим. Автотести вимірюють ізоляцію самостійно, через Docker, а не зчитують надруковане: значення $VTFK_NONCE щоразу інше й читається з файлу в контейнері, тож скрипт, який лише друкує правдоподібні числа, перевірку не проходить. Рівень зараховується, коли пройшли всі шість перевірок.

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

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

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

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

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

Приклад 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. Значення echo $$ усередині контейнера і номер того самого процесу на хості відрізняються. Це один процес чи два? Обґрунтувати, спираючись на виводи ps aux в обох середовищах. (Перелік просторів імен — лабораторна робота № 3.)
  6. Чому автоматизація розгортання bash-скриптом надійніша за ручний набір команд у терміналі щоразу?
  7. Що покаже ps aux на хості для процесу, запущеного всередині контейнера?
  8. Чому для ідемпотентності скрипту потрібно прибирати або перевикористовувати контейнер із заданим іменем перед повторним docker run?

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

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

Здача рівня 1 відбувається тегом submit/lab01 у репозиторії завдання. Предмет здачі — один файл level1.sh у корені цього репозиторію; автотести читають лише його. Звіт REPORT.md (вивід команд кожного кроку й відповіді на контрольні питання) ведеться для захисту рівнів 2–3 і оцінюється викладачем — автотести його не відкривають. Скрипт core/capsule/deploy.sh тегом не здається взагалі: він лежить у наскрізному репозиторії vtfk-sid і показується на захисті.

Автотести (рівень 1) перевіряють шість умов, перелічених у завданні рівня 1 вище. Рівень зараховується, лише коли пройшли всі шість; часткове проходження оцінки не дає.

Окремо наголошується вимога, зворотна до звичного «прибрати за собою»: контейнер vtfk-lab01 має лишитися запущеним після завершення level1.sh. Перевірки 3–5 звертаються до нього вже після того, як скрипт відпрацював (docker inspect, docker exec), тому docker rm -f у кінці скрипту знімає три перевірки з шести. Прибирає контейнер сам автотест, наприкінці прогону.

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

Розширення (рівень 3) перевіряється на захисті — автотести його не бачать, бо скрипт лежить у власному репозиторії vtfk-sid. Демонструються ідемпотентність і повнота bash-скрипту: два послідовні запуски core/capsule/deploy.sh не повинні завершуватися помилкою через уже наявний контейнер, а сам скрипт має встановлювати всі утиліти, перелічені в контракті рівня 3 — curl, git, procps, iproute2.

🔗 ДЖЕРЕЛА
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 18 · 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/ — разом із заголовками); також уміє завантажувати файли й скрипти.
uname
Показує відомості про ядро й архітектуру (uname -m — x86_64 чи aarch64).
docker rmнове
Видаляє контейнер (docker rm -f name — навіть якщо він ще працює).
lsnsнове
Перелічує простори імен процесу (lsns -p <PID>) — показує, у яких «кімнатах» ядра він живе.
ip
Керує мережею Linux: інтерфейси, адреси, маршрути, мережеві простори імен (ip netns add demo).
docker inspect
Друкує повний JSON-опис контейнера чи образу; -f '{{.State.Pid}}' — витягнути одне поле.
docker version
Показує версії клієнта й демона — перевірка, що Docker встановлений і працює.
setнове
Налаштовує режим оболонки в скрипті; set -euo pipefail — падати на першій помилці, невідомій змінній чи збої в конвейєрі.
git tag
Позначає поточний коміт іменем (git tag submit/lab01) — так ти «здаєш» лабораторну.
git
Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги submit/labNN.
git push
Відправляє коміти й теги у віддалений репозиторій (git push --tags).
docker exec
Виконує команду всередині вже запущеного контейнера (docker exec -it storage sh).
ls
Показує список файлів у каталозі; ls -la — з правами, власником, розміром і прихованими файлами.

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