Лабораторна робота № 1. Контейнер розробника: перша капсула
Мета роботи
Навчитися запускати ізольований Docker-контейнер розробника, спостерігати результат ізоляції процесів на конкретних значеннях, свідомо прокидати порти для доступу з хоста та автоматизувати розгортання середовища ідемпотентним bash-скриптом. Робота формує базове розуміння того, що контейнер — це процес із підміненою видимістю, на якому будуються всі наступні лабораторні першого модуля. Механізм цієї підміни (простори імен) розглядається на лекції 3 і практично — у лабораторній роботі № 3.
📚Теоретичні відомості335 слів · ~2 хв
1. Контейнер як процес (стисло)
Контейнер — процес операційної системи, якому ядро підмінило видимість частини системних
ресурсів. Власного ядра, емульованого обладнання та завантажувача в контейнера немає; ядро
спільне з хостом (перевіряється командою uname -r усередині контейнера й на хості —
значення однакові). Детальний розбір — лекція 1, розділ «Контейнер як процес»; тут наведено
мінімум, потрібний для виконання роботи.
2. Результат ізоляції, який спостерігається в цій роботі
Механізм ізоляції (простори імен Linux) вивчається на лекції 3; у цій роботі використовується лише результат його дії, який спостерігається двома вимірюваннями:
- Нумерація процесів. Оболонка, запущена як початковий процес контейнера, має
PID = 1(перевіряєтьсяecho $$), тоді як на хості цей самий процес має інший номер. - Обсяг списку процесів.
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) на початку скрипту.
Обладнання та програмне забезпечення
- GitHub Codespaces або локальний devcontainer із попередньо встановленим Docker Engine.
- Термінал bash.
- Утиліти для огляду процесів і мереж:
ps,lsns,iproute2(ip), доступ до/proc. - Базовий образ
ubuntu:24.04. Для рівня 1 він обов'язковий: автотест читає вdocker inspectназву образу контейнера й вимагає самеubuntu:24.04. Для ручних дослідів рівнів 2–3 дозволено інший дистрибутив Ubuntu/Debian.
Порядок виконання
- Перевірити доступність Docker Engine:
docker version. Очікуваний результат: блокиClient:іServer:виводяться без помилок з'єднання з демоном. - Запустити базовий контейнер розробника в інтерактивному режимі:
docker run -it --name devbox ubuntu:24.04 bash. Очікуваний результат: інтерактивне запрошення командного рядка всередині контейнера. - Усередині контейнера виконати
ps auxтаecho $$. Очікуваний результат: видно лише кілька процесів самого контейнера; PID оболонки —1. - У другому терміналі на хості виконати
ps aux | wc -lі порівняти з кількістю, побаченою в контейнері. Очікуваний результат: на хості кількість процесів суттєво більша. - Оформити спостереження кроків 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. - Зупинити й прибрати перший контейнер (
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 без прапорця
-pна новому контейнері (інше ім'я) і переконатися, що звернення з хоста завершується помилкою з'єднання. Очікуваний результат:curlповертаєConnection refused— доказ, що порт контейнера недоступний без явного проброшування. - Оформити всі кроки 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, автотести). Запустити базовий контейнер розробника й оглянути
простори імен — переконатися, що процеси хоста приховані від контейнера, а PID
оболонки всередині дорівнює 1. Відповідає крокам 1–5 порядку виконання.
Здається скриптом level1.sh у корені репозиторію завдання: спостереження, яке не
можна відтворити командою, не є результатом роботи. Контракт скрипту:
Шапка
#!/usr/bin/env bashіset -euo pipefail; запуск без аргументів.Підняти контейнер з іменем
vtfk-lab01з образуubuntu:24.04і лишити його працювати після завершення скрипту.Записати значення змінної оточення
$VTFK_NONCEу файл/tmp/vtfk-nonceвсередині контейнера. Змінну передає перевірка; щоразу вона інша.Надрукувати в стандартний вивід три рядки (порядок вільний, інший вивід дозволено):
CONTAINER_PID=<PID головного процесу всередині контейнера> CONTAINER_PROCS=<скільки процесів видно всередині> HOST_PROCS=<скільки процесів видно на хості>Завершитися кодом
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, захист). Прокинути порт для доступу з хоста до сервісу всередині контейнера. На захисті — усно пояснити механізм NAT-переадресації та різницю мережевих просторів контейнера й хоста, наживо продемонструвати робочий проброшений порт. Відповідає крокам 6–7 порядку виконання.
Рівень 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 поверне помилку з'єднання —
переконливий доказ того, що проброс порту є явною, окремою дією, а не побічним ефектом
запуску контейнера.
- Чому процес усередині контейнера бачить себе як
PID 1, хоча на хості в нього інший, реальний PID? - Чому ядро в контейнера те саме, що на хості, — і як це перевірити однією командою для процесу?
- Що станеться, якщо звернутися до
http://localhost:8080з хоста, а контейнер запущено без прапорця-p? - Чим прапорець
-p 8080:80відрізняється від-p 80:80з погляду безпеки локальної машини? - Значення
echo $$усередині контейнера і номер того самого процесу на хості відрізняються. Це один процес чи два? Обґрунтувати, спираючись на виводиps auxв обох середовищах. (Перелік просторів імен — лабораторна робота № 3.) - Чому автоматизація розгортання bash-скриптом надійніша за ручний набір команд у терміналі щоразу?
- Що покаже
ps auxна хості для процесу, запущеного всередині контейнера? - Чому для ідемпотентності скрипту потрібно прибирати або перевикористовувати
контейнер із заданим іменем перед повторним
docker run?
Парні теми СРС
- Рівень 2: srs01 — «Публікація портів у Docker:
-p,EXPOSEі чому без них хост не бачить контейнера». - Рівень 3: srs02 — «Ідемпотентність скрипта розгортання: запустити двічі й не зламати».
Критерії оцінювання та форма звіту
Здача рівня 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.