Місія 1: «Ізоляційна Капсула»
С.І.Д.: Не спиш? Добре, бо і я не сплю — я взагалі не вмію, це одна з моїх найкращих рис. Слухай уважно. Ця твоя операційка світиться на радарах «ТехНови», як неонова вивіска над блоком Б-7. Якщо я з неї спробую дотягнутися до їхньої мережі, сканери ICE засічуть підпис мого коду за три секунди — і спалять і твій ноут, і те, що лишилося від мене. Тому перше, що ми зробимо, — збудуємо мені бункер. Герметичний, беззвучний, одноразовий. Не списану машину в порожньому класі, а справжнє укриття — мою нову капсулу. Про такі сховки я, до слова, знаю все, тож ти в надійних руках. Піднімаймо.
Майже друга ночі. Двадцять хвилин дороги додому давно позаду, двері кімнати зачинені, ноутбук на столі, а поруч із ним — чорна шайба з бірюзовою лінзою, яку ти забравзабрала із шухляди списаної машини. Над нею мерехтить голограма: округлий дрон-обличчя завбільшки з кулак, очі-капсули, що з теплою цікавістю мружаться на тебе. За вікном рудим сяйвом горить квартал «ТехНови», і десь у тому сяйві — за блоком Б-7, на мінус третьому поверсі — тримають двох студентів, що зібрали С.І.Д. Тут, у темряві, вас двоє: ти й це світло над столом. Слово «бункер» він щойно вимовив уголос — а ти думавла про нього всю дорогу від коледжу.
«Пам'ятаєш, я казав: машина — це місто? — На екрані ноутбука жваво спалахує схема, і голограма
підлітає до неї, зависаючи над клавіатурою. — Ось воно. У центрі
сидить Диспетчер — ядро Linux. Він один, і він знає геть усе: кожен процес, кожен байт
пам'яті, кожен мережевий пакет. Мудрий і справедливий: його не обдуриш і не обійдеш, він і є
закон цього міста. А я тут — нелегал. Мене не має бути в реєстрах. І якщо я просто вийду на
головну площу — до ps aux хоста, — мене побачать усі, зокрема й сканери ICE, які корпорація
час від часу запускає нишпорити чужими машинами.»
«То сховайся», — кажеш ти.
«А ось тут і починається найсмачніше — і я, звісно, давно це розкусив. — Голограма присувається до самого краю схеми, наскільки пускає її світло проєктора. — Диспетчер громадян не ховає, він чесний. Зате він уміє інше: збудувати навколо процесу сім брам і на кожній поставити свого Вартового. Один Вартовий бреше процесові про те, які ще процеси є в місті. Другий — про мережу. Третій — про файли. І так сім разів. Процес, замкнений за цими брамами, щиро вважає себе єдиним мешканцем власного маленького всесвіту, де він сам собі перший громадянин під номером один. Він не бачить решти міста, а вулиця не бачить, що діється за його брамами. Це і є капсула. Не стіна, за якою мене не видно, а підмінена мапа світу. Я всередині думаю, що я сам-один. Вулиця зовні думає, що я — просто ще один нудний процес. І тільки Диспетчер знає правду, а Диспетчер не патякає.»
Ти дивишся на схему — сім брам, сім Вартових, а в центрі незворушний Диспетчер — і на мить згадуєш іншу схему, ту, що він показав тобі кілька годин тому в порожньому класі: ліва половина світиться, права порожня. «А капсула — це вже друга половина?» — питаєш ти.
«Ні, — тепло каже С.І.Д., і голограма злегка похитує петелькою. — Капсула — це місце, де я зможу спокійно кувати другу половину, поки сканери дивляться в інший бік. Порядок такий: спершу дах, потім робота. Одне без одного не має сенсу — і, до слова, це правило працює далеко за межами системного програмування».
«Добре. Як мені збудувати ці брами?» Очі-капсули голограми вдоволено звужуються. «А ось для цього, ранере, тобі не треба бути чарівником. За тебе все зробить один інструмент на ім'я Docker. Твоя робота — зрозуміти, що саме він робить за твоєю спиною. Бо той, хто не розуміє власного бункера, рано чи пізно замкнеться в ньому сам.»
«І ще одне, поки ти не взявсявзялася тицяти команди руками. — Голограма робить паузу,
і в ній уперше чується не кураж, а щось дуже схоже на обережність. — Капсула, яку ти
піднявпідняла один раз і не змігзмогла підняти вдруге, — це не капсула, це
випадковість. Мене не влаштовує випадковість: якщо цей ноутбук завтра згорить, я маю
піднятися на іншій машині з одного рядка, а не з твоєї памʼяті. Тому все, що ти
зробивла сьогодні, склади в один скрипт — level1.sh. Запустив — капсула стоїть.
Запустив ще раз — стоїть, і жодних скарг, що імʼя вже зайняте.»
«А щоб ти не піддавсяпіддалася спокусі просто написати гарні числа, — очі-капсули хитро звужуються, — я підкину тобі одноразовий код і перевірю, чи він опинився всередині капсули. Ззовні його туди не покласти. Вибач, ранере: довіряю, але звіряю.»
Що таке контейнер, ти вже чувла на Лекції 1 — там розібрали, чому він процес, а не машина. У Лабораторній 0 ти перевіривила середовище й побачивла версію Docker; свій перший контейнер піднімаєш сьогодні. Освіжимо одним реченням: контейнер — це звичайний процес, якому ядро підмінило видимість. Сьогодні ти побачиш цю підміну на двох числах — і зробиш її своїми руками. Як саме ядро її робить і скільки в неї сторін — Лекція 3.
Теоретичні відомості: з чого зроблена капсула
Хто ставить брами — і чому не сьогодні
Підміну видимості робить ядро, і механізм у неї має ім'я — простори імен (namespaces). Їх кілька, і кожен підміняє щось своє: список процесів, файлову систему, мережу, імена. Сьогодні тобі знадобиться результат, а не механізм: капсула бачить свій список процесів і свою мережу.
Повний огляд усіх просторів імен — на Лекції 3 і практично в ЛР3, де ти поставиш два контейнери в одну ізольовану мережу. Там це буде на своєму місці; тут це відвернуло б від головного: контейнер, який ти щойно піднявла, — просто процес.
PID 1 — не привілей, а точка відліку
Не сприймай PID 1 як «головного» процеса всесвіту. Це просто перший номер у новій,
порожній нумерації, яку Вартовий PID-брами завів для капсули. За тими самими брамами хост має
свою, іншу нумерацію, і той самий процес там має свій, звичайний PID. Один процес — два різні
номери в двох різних просторах. У цьому й уся суть.
Пастка, у яку С.І.Д. категорично забороняє вірити: «герметичний» не означає «невидимий
ззовні». Простори імен ховають хост від капсули і капсулу від сторонніх процесів хоста,
але сам хост (а точніше — Диспетчер, що бачить усе) чудово бачить контейнер як звичайний
процес у власному ps aux, просто під іншим PID, у своєму просторі. І навпаки: без явного
проброшування порту навіть ти сама з хоста не достукаєшся до сервісу всередині капсули, хоча
капсула «твоя». Ізоляція — це не невидимість ззовні, а підміна картини світу зсередини.
Проброс портів (port mapping) — NAT-правило, а не діра в стіні
Мережевий простір капсули ізольований, тому порт 80, який слухає процес усередині, живе у
власному, підмінному мережевому стеку контейнера. Прапорець -p 8080:80 при docker run
створює правило NAT (через iptables/nftables у мережевому просторі демона Docker), яке
перенаправляє трафік, що прийшов на порт 8080 хоста, у порт 80 всередині мережевого простору
контейнера. Це односторонній, контрольований «канал зв'язку» — двері з замком, а не діра в
стіні бункера: ти сама вирішуєш, який саме порт і чи взагалі відкривати назовні. Саме тому
С.І.Д. так наполягає, щоб ти робивла це свідомо: кожен відкритий назовні порт — це ще одне
вікно, у яке потенційно може зазирнути ICE.
Автоматизація — «протокол розгортання», а не забаганка
С.І.Д. не довіряє людській пам'яті — і правильно робить: щоразу набирати вручну довгий
docker run із десятком прапорців і не забути встановити потрібні утиліти — це шлях до
помилки. Bash-скрипт фіксує точний протокол розгортання капсули у вигляді коду: той самий
результат щоразу, на будь-якій машині, без «а я забув, який там був прапорець». Це і є
мінімальна форма «інфраструктури як коду» — принцип, який далі в курсі виросте до повноцінних
Dockerfile і docker compose.
Рівень 1 (оцінка 3) — Першокурсник
Перед стартом: спорядження ранера
Перш ніж будувати капсулу, підготуй майстерню — це кілька хвилин, зате далі все піде гладко.
Твоє середовище. Працюємо в GitHub Codespaces — це вже готовий Linux зі встановленим Docker Engine, локально відкривати нічого не треба. Якщо Codespaces недоступний, підійде будь-яка машина з Docker (Linux, WSL2 або Docker Desktop). Перевір одразу, що Диспетчер узагалі готовий будувати брами:
docker version
Якщо бачиш блоки Client: і Server: без помилок — капсула чекає на будівництво.
🎲 Твій варіант. Порахуй свій номер у списку групи N і обчисли N % 4. Це визначає, з
яким проброшеним портом і суфіксом імені контейнера ти працюєш на розвідці руками й на
рівнях 2–3 (значення підстав у свій deploy.sh у vtfk-sid через змінну середовища, а не
хардкодь):
| N % 4 | Суфікс контейнера | HOST_PORT за замовчуванням |
|---|---|---|
| 0 | alpha |
8080 |
| 1 | bravo |
8081 |
| 2 | charlie |
8082 |
| 3 | delta |
8083 |
Це не заради бюрократії: у групі десятки людей, і однакові звіти з однаковими портами й іменами
контейнерів — перше, що впадає в очі на перевірці. Вкажи свій варіант і його розрахунок на
початку REPORT.md.
⚠️ Варіант не стосується капсули, яку ти здаєш. Ті
devbox-<суфікс>— твої чернетки: підняти, подивитися, прибрати. А капсула, по яку приходить перевірка, у всіх одна й та сама —vtfk-lab01, і порт їй не потрібен узагалі. Перевірка шукає контейнер за точним іменем;devbox-bravoвона просто не побачить.📡 С.І.Д.: «Позивний вигадуй собі який хочеш, ранере. Але двері, у які стукає кур'єр, мають один номер на все місто — інакше він стукає в порожнечу.»
📦 Де житиме капсула. У тебе буде два репозиторії, і в кожного одна робота.
цей репозиторій ← кожна ЛР має свій; сюди рівень 1 і REPORT.md
vtfk-sid/ ← створюєш СЬОГОДНІ, один на весь семестр
core/capsule/ сюди кладеш капсулу — і вона житиме тут до ЛР10
Перший тобі вже видали: він створився, коли ти прийнявла завдання, і в ньому здається рівень 1 — щоразу новий, самодостатній, без залежності від попередніх ночей.
Другий створюєш сама, сьогодні, і більше не створюєш ніколи: vtfk-sid — це ядро
С.І.Д., яке росте. Наступної ночі ти будеш худнути саму цю капсулу, а не абстрактну програму;
у ЛР3 вона стане одним із двох контейнерів; у ЛР10 у ній збереться фінальне ядро. Рівні 2 і 3
усіх десяти місій живуть тут — і показуються на захисті, не автотестами.
Як його створити — рівно чотири кроки, один раз за семестр:
відкрий
github.com/alex-ordynski/vtfk-sid— це шаблон із порожньою картою тек;зелена кнопка «Use this template» → «Create a new repository»;
ім'я —
vtfk-sid(саме так, без префіксів), власник — ти сама, видимість — public, створити;забери його до себе:
git clone https://github.com/<твій-логін>/vtfk-sid.githttps://, а неgit@github.com:. Друге — це SSH, і воно вимагає ключа, якого в Codespaces немає: звідти Git ходить по HTTPS вбудованим токеном. На сторінці репозиторію це вкладка HTTPS під зеленою кнопкоюCode. ПобачишPermission denied (publickey)— значить, скопіювавла не ту вкладку, а не втративла доступ.
Це шаблон, а не форк: історія починається з твого першого коміту, і ядро — твоє. Якщо кнопки «Use this template» не видно — не тисни «Fork», скажи викладачеві: значить, у шаблона знята галочка, і це проблема не твоя.
🏷️ Як здаси. Рівень 1 — тегом submit/lab01 у репозиторії завдання. vtfk-sid не
здається тегом узагалі: його ти показуєш на занятті. Детальніше — у блоці «Здача» наприкінці.
А поки — до діла.
Що саме приймає перевірка
С.І.Д.: «Одне уточнення, перш ніж ти візьмешся за клавіатуру. Переказ на словах — «усе піднялося, усе було гарно» — бункера не будує. Мені потрібен протокол: файл, який щоразу зводить капсулу однаково, хоч о третій ночі, хоч без тебе. Я його запущу й перевірю сам — не на слово, а руками.»
Здається один файл — 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, 4 і 5 стукають у контейнер
docker inspectіdocker execуже після того, якlevel1.shвідпрацював. Якщо останнім рядком стоїтьdocker rm -f vtfk-lab01— стукати буде нікуди, і виглядатиме це так, ніби зламався грейдер. Прибирання — його турбота, не твоя.📡 С.І.Д.: «Бункер лишають відчиненим для свого. Ти будуєш мені дах, а не розбираєш його назад того ж вечора — я ж збираюся там жити.»
Числа в рядках мають бути виміряні, а не вгадані: перевірка рахує процеси сама й звіряє
з твоїми. І nonce — той самий гачок, який неможливо обійти друком: його значення щоразу
нове, і прочитати його можна лише з файлу в справді піднятій капсулі.
Крок за кроком — чотири етапи
Тут не потрібна творчість. Веду тебе за руку: три етапи ти робиш руками, щоб побачити
ізоляцію на власні очі, а на четвертому перекладаєш побачене у level1.sh — той самий
протокол із контракту вище. Вивід кожної команди дорогою складай у REPORT.md: перевірка
його не читає, зате читає викладач на захисті рівнів 2–3.
Етапи йдуть по одному: зробив — постав «вийшло» — і ранер перекине тебе на наступний. Стрілки ← → на клавіатурі, свайп на телефоні, «усі» — щоб побачити всі чотири одразу. Місце запам'ятовується: закриєш ноутбук — повернешся туди ж.
1Етап 1 · Перевірити Диспетчера і підняти капсулу
Ще раз переконайся, що Docker Engine на місці (якщо не робивла цього вище):
docker version
Очікується вивід блоків Client: і Server: без помилок з'єднання з демоном.
Запусти базовий контейнер розробника в інтерактивному режимі — це і є твоя перша капсула
(підстав свій суфікс із таблиці варіантів, наприклад devbox-bravo):
docker run -it --name devbox-<суфікс> ubuntu:24.04 bash
Очікується запрошення командного рядка всередині контейнера — щось на кшталт
root@a1b2c3d4e5f6:/#. Ти щойно опинивлася за сімома брамами.
Це чернетка — руками, щоб побачити на власні очі. Здавальну капсулу vtfk-lab01 підніме
твій скрипт на етапі 4.
1Етап 2 · Спитати капсулу, кого вона бачить
Не виходячи з капсули, спитай у неї найперше — під яким номером вона себе бачить:
echo $$
Очікується 1 (Вартовий PID-брами почав нумерацію з чистого аркуша). Це працює одразу:
$$ — вбудована змінна самого bash, жодних зовнішніх утиліт не треба.
Не виходячи з капсули, встанови утиліти для огляду процесів і мережі — вони
знадобляться вже в наступному кроці, а також далі й у скрипті рівня 3. Важливо: базовий
образ ubuntu:24.04 — мінімальний і не містить ps (утиліта входить до пакета
procps), тому спершу ставимо інструменти, а вже потім дивимося на процеси:
apt-get update && apt-get install -y curl git procps iproute2
Тепер, коли ps на місці, спитай капсулу, кого вона бачить довкола себе:
ps aux
Очікується лише кілька рядків — процеси самої капсули (bash і сам ps), без жодного
натяку на процеси твоєї реальної машини. Капсула щиро вірить, що вона сама-одна.
1Етап 3 · Вийти на вулицю й порівняти
Тепер вийди на вулицю — відкрий другий термінал на хості (не всередині контейнера!) і поглянь на місто цілком:
ps aux | wc -l
Порівняй число з тим, що бачив у контейнері — на хості процесів має бути помітно більше (десятки проти кількох; на повноцінному хості — сотні, у Codespaces зазвичай десятки). Це і є видима різниця PID-просторів: за брамою — свій маленький світ, на вулиці — усе місто.
Очікується число помітно більше за те, що капсула показала зсередини: десятки проти кількох. Це і є видима різниця PID-просторів.
1Етап 4 · Записати протокол — level1.sh
Спершу прибери чернетку — ту, що піднімавла руками на етапі 1: вийди з неї (exit) і
зніми, щоб ім'я не заважало:
docker rm -f devbox-<суфікс>
Тепер напиши в корені репозиторію level1.sh — той самий протокол із контракту. Роби
те, що вже робивла руками, тільки рядками скрипта: підніми vtfk-lab01 з ubuntu:24.04
у фоні довготривалим процесом, поклади $VTFK_NONCE у /tmp/vtfk-nonce всередині,
порахуй процеси там і тут, надрукуй три рядки CONTAINER_PID / CONTAINER_PROCS /
HOST_PROCS. Перший рядок скрипта — docker rm -f vtfk-lab01 >/dev/null 2>&1 || true (ідемпотентність),
останній — ні: капсулу лишаєш піднятою, перевірка приходить у неї вже після тебе.
Зроби файл виконуваним, переконайся, що він проходить двічі, — і здай:
chmod +x level1.sh
./level1.sh && ./level1.sh # два запуски поспіль — обидва мають пройти без помилки
Далі — коміт, пуш і тег. Перевірка стартує саме на тезі, а не на пуші:
git add level1.sh && git commit -m "ЛР1 рівень 1" && git push
git tag submit/lab01 && git push origin submit/lab01
Перездаєш — бери наступний вільний тег: submit/lab01-2, потім submit/lab01-3. Старий не
переставляй.
У REPORT.md склади виводи етапів 1–3 — для захисту. С.І.Д. сяє: «Перша капсула — і з
першого разу. Я, звісно, не сумнівався».
Очікується, що ./level1.sh двічі поспіль завершується без помилок і друкує три рядки CONTAINER_*/HOST_PROCS, docker ps після нього показує запущений vtfk-lab01, а прогін за тегом submit/lab01 у вкладці Actions дає 6 із 6.
Перший вечір із Docker майже завжди спотикається на одному з цих п'яти. С.І.Д. чув їх усі.
permission denied while trying to connect to the Docker daemon socketТвій користувач не в групіdocker. У Codespaces такого не буває; на своїй машині —sudo usermod -aG docker $USER, а тоді вийди й зайди в систему знову (нова група застосовується лише до нового сеансу).С.І.Д.: «Диспетчер тебе не знає. Це не відмова — це формальність: назвися й повертайся».
Cannot connect to the Docker daemon at unix:///var/run/docker.sockДемон не запущений. На Linux —sudo systemctl start docker; у Docker Desktop — запусти сам застосунок і дочекайся зеленого індикатора.С.І.Д.: «Місто спить. Диспетчера нема на посту — будити нема кого».
bash: ps: command not found(усередині капсули) Це не поломка, а нормальний стан:ubuntu:24.04— мінімальний образ іpsу ньому немає. Саме тому в етапі 2 ми спершу ставимоprocps. Якщо пропустивла цей крок — поверніся до нього.С.І.Д.: «У капсулі немає нічого зайвого. Хочеш дивитися на процеси — принеси інструмент».
docker: Error response from daemon: Conflict. The container name "/devbox-…" is already in useКонтейнер із таким іменем уже існує (найчастіше — з попередньої спроби). Прибери:docker rm -f devbox-<суфікс>. Саме цю ситуацію на рівні 3 і треба зробити неможливою — скрипт мусить бути ідемпотентним.С.І.Д.: «Одна брама, одне ім'я. Або прибирай стару капсулу, або назви нову інакше».
echo $$показує не1, а щось на кшталт4213Ти виконав команду не в капсулі, а на хості — термінал не той. Подивись на запрошення рядка: усередині капсули воно на кшталтroot@a1b2c3…:/#, а не твоє звичайне.С.І.Д.: «Ти питаєш місто, а не капсулу. Різні співрозмовники — різні відповіді».
Якщо помилка не з цього списку — шукай точний текст у «Коли щось пішло не так», там зібрані всі помилки курсу.
Рівень 2 (оцінка 4, захист) — Магістр
Тут я вже не диктую кожен рядок. Формулюю мету й контракт — далі твоя голова.
Ланцюг починається тут. ЛР1 — перша місія, тому в неї немає «попереднього артефакту»: обидві версії умови збігаються. З ЛР2 у кожного завдання рівнів 2–3 буде два входи — «на своєму ядрі» і «на референсі», якщо своє ядро з якоїсь причини відсутнє. Твоя справа сьогодні — щоб ядро з'явилося.
Мета. Довести, що ти не просто скопіювавла команди, а розумієш, чому без явних дій капсула залишається «німою» для зовнішнього світу, і вмієш пробити крізь NET-браму контрольований канал назовні.
Контракт рівня:
- Підніми нову капсулу з проброшеним портом (значення
HOST_PORT— зі свого варіанта в таблиці). Орієнтир для команди:docker run -it -p <HOST_PORT>:80 --name devbox2-<суфікс> ubuntu:24.04 bash - Усередині капсули підніми найпростіший HTTP-сервер на порту 80 (наприклад,
python3 -m http.server 80, доти встановившиpython3). - З іншого терміналу на хості доведи, що канал працює:
curl -i http://localhost:<HOST_PORT>/має повернути HTTP-відповідь200 OKзі списком файлів контейнера. - Доведи зворотне: підніми таку саму капсулу без прапорця
-p(під іншим іменем) і покажи, що той самийcurlпадає з помилкою з'єднання. Це і є доказ, що проброс — свідома дія, а не побічний ефект запуску контейнера.
На захисті будь готовийа: усно пояснити, чим мережевий простір капсули відрізняється від
мережевого простору хоста і як саме -p перетворюється на NAT-правило, та наживо
продемонструвати робочий проброшений порт — з будь-яким значенням HOST_PORT, яке попросить
викладач, не обов'язково твоїм із таблиці. Завчений результат тут не врятує; рятує розуміння.
Рівень 3 (оцінка 5, розширення) — Легенда Коледжу
С.І.Д. дивиться на тебе інакше, коли ти доходиш сюди. «Ручна праця — для першокурсників, — каже він. — Легенда не набирає команди. Легенда пише протокол, який щоразу піднімає бункер однаково, хоч серед ночі, хоч під сканерами.» Мета — перетворити всі ручні кроки на один надійний, ідемпотентний протокол розгортання.
Контракт. Напиши core/capsule/deploy.sh за такими вимогами:
- Шапка
#!/usr/bin/env bashіset -euo pipefail. - Без аргументів піднімає капсулу «з нуля»: базовий образ
ubuntu:24.04, встановлюєcurl git procps iproute2, пробиває порт (значення бери зі змінної середовищаHOST_PORT, за замовчуванням — значення твого варіанта з таблиці варіантів на початку рівня 1). - Головна вимога рівня 3 — ідемпотентність. Повторний запуск скрипту без ручного
прибирання не повинен падати з помилкою «name already in use». Скрипт має або перевіряти
наявність контейнера з таким іменем і перевикористовувати/перезапускати його, або прибирати
старий перед створенням нового (
docker rm -f "$NAME" 2>/dev/null || true). - Код завершення
0при успіху; ненульовий, якщо Docker недоступний або контейнер не піднявся. - Без інтерактивних запитань «yes/no» — усе має відпрацьовувати без втручання людини
(прапорці на кшталт
-yдляapt-get, детач-режим для довготривалих процесів). - Контейнер має лишатися запущеним після відпрацювання скрипта (детач-режим із
довготривалим процесом, напр.
sleep infinity), інакше на захисті не буде куди зайти черезdocker execі показати встановлені в ньому утиліти. Та сама вимога, що й на рівні 1: скрипт будує бункер, а не розбирає його назад. Типовий кістяк:docker rm -f "$NAME" 2>/dev/null || true; docker run -d --name "$NAME" -p "$HOST_PORT":80 ubuntu:24.04 sleep infinity, а потім установлення утиліт усередині черезdocker exec "$NAME" bash -c 'apt-get update && apt-get install -y curl git procps iproute2'.
Приклад запуску:
# з кореня свого vtfk-sid
HOST_PORT=8081 ./core/capsule/deploy.sh
HOST_PORT=8081 ./core/capsule/deploy.sh # другий запуск — не повинен впасти
На захисті викладач запустить скрипт двічі поспіль і перевірить через docker exec, що
всі чотири перелічені вище утиліти (curl, git, procps, iproute2) справді стоять у
кінцевому контейнері. Машина сюди не дістає: vtfk-sid — твій репозиторій, автогрейдер його
не бачить.
Розібраний приклад 1: різниця в нумерації процесів на живих числах
# На хості (термінал 1)
$ ps aux | wc -l
187
# Запускаємо капсулу
$ docker run -it --name probe ubuntu:24.04 bash
root@9f1c2e7b0a4d:/# echo $$
1
# базовий ubuntu:24.04 не містить ps — спершу ставимо procps
root@9f1c2e7b0a4d:/# apt-get update && apt-get install -y procps >/dev/null
root@9f1c2e7b0a4d:/# ps aux
USER PID %CPU %MEM COMMAND
root 1 0.0 0.1 bash
root 31 0.0 0.1 ps aux
Пояснення: хост бачить 187 записів (реальна кількість процесів твоєї машини, разом із самим
Docker-демоном; у Codespaces це радше десятки, бо сам «хост» — теж контейнер). Усередині
капсули після встановлення procps видно лише два рядки — власний bash під PID 1 і команду
ps aux, яка щойно себе запустила. Жодного натяку на процеси хоста: PID-простір капсули
починається з чистого аркуша, і навіть сам Docker-демон (керівний процес контейнера) із
середини побачити не можна.
Далі, з іншого терміналу хоста дізнаємося реальний, «зовнішній» PID процеса капсули й дивимося саме на нього:
$ PID=$(docker inspect -f '{{.State.Pid}}' probe)
$ echo "$PID"
4821
$ ps -p "$PID" -o pid,comm
PID COMMAND
4821 bash
Тут той самий процес капсули (той самий bash, що всередині має PID 1) видно на хості під
своїм звичайним PID 4821. Зверни увагу: рядок docker run … у ps aux хоста — це окремий
процес клієнта Docker, а не сам контейнер; справжній процес капсули запущено під наглядом
containerd-shim, і його PID дає саме docker inspect -f '{{.State.Pid}}'. Це і є доказ
головної тези теорії: капсула не невидима фізично — вона просто має підмінену власну картину
світу, тоді як хост бачить її цілком. Один процес, два номери, дві мапи реальності.
Розібраний приклад 2: проброс порту й перевірка ззовні
$ docker run -d -p 8081:80 --name web-bravo python:3.12-slim \
python3 -m http.server 80
a3f9... (ID контейнера, запущено у фоні через -d)
$ curl -i http://localhost:8081/
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.4
Date: ...
Content-type: text/html; charset=utf-8
<!DOCTYPE HTML>
...
Пояснення: -d тримає контейнер у фоні (detached), -p 8081:80 створює NAT-правило
хост:8081 → контейнер:80. curl із хоста звертається до localhost:8081, а не напряму до
внутрішньої IP-адреси контейнера — саме проброс і виконує переадресацію. Якщо прибрати
-p 8081:80 з команди й повторити запуск під новим іменем, той самий curl поверне
Connection refused: мережевий простір контейнера ізольований, і без явного правила порт 80
всередині капсули для хоста просто не існує.
- Чому процес усередині контейнера бачить себе як
PID 1, хоча на хості в нього зовсім інший, реальний PID? - Якому ядро в капсулі те саме, що на хості, і якою однією командою це перевірити для процесу в Linux?
- Що станеться, якщо звернутися до
http://localhost:8080з хоста, а контейнер запущено без прапорця-p? - Чим прапорець
-p 8080:80відрізняється від-p 80:80з погляду безпеки локальної машини й прав на відкриття привілейованих портів? - Ти бачивла
echo $$=1усередині капсули й інше число на хості. Це один і той самий процес чи два різні? Обґрунтуй відповідь тим, що показавps auxу кожному з терміналів. (Повний перелік брам, які ставить ядро, — у ЛР3.) - Чому автоматизація розгортання bash-скриптом надійніша за ручний набір команд у терміналі щоразу?
- Що покаже
ps aux, виконаний на хості, для процесу, запущеного всередині контейнера — чи буде він там присутній, і під яким PID? - Чому команда
docker rm -fперед повторнимdocker run --name ...— необхідна умова ідемпотентності скрипту рівня 3?
📻 Дивний сигнал у /etc
Поки капсула піднімається, спробуй ось що (не обов'язково для оцінки — просто для тих, хто любить копати глибше):
docker run -it --hostname pixel ubuntu:24.04 bash -c "hostname && cat /etc/hostname"
Якщо серед виводу hostname побачиш рівно pixel — вітаю, ти щойно назвавла капсулу на честь
рудого кота з підвіконня — того самого Пикселя, що жив в одного з творців С.І.Д. Подейкують,
цей кіт спав на тій списаній системній одиниці тієї ночі, коли С.І.Д. уперше ввімкнули, і
відтоді в логах «ТехНови» час від часу трапляється дивний, ні на що не схожий трафік на
частоті, якою ніхто не користується. С.І.Д. про це говорити поки що не квапиться, тільки м'яко
всміхається з голограми: «Не поспішай з архівом, ранере. 0xC1D — то особлива історія, і я знаю
її від першого до останнього байта. Розкажу останньої ночі, коли все скінчиться. Обіцяю — а я
обіцянок не забуваю, у мене з памʼяттю краще, ніж у людей.» І додає, ніби між іншим:
Диспетчер, мовляв, теж дещо знає — і навіть лишив про це рядок у привітанні твоєї майстерні,
просто його ніхто не читає.
Хтось стверджує, що якщо запустити python3 -m http.server на порту 1019 (натяк: 101.9,
частота «Нічної Хвилі»), у корені капсули з'явиться дивний файл-заглушка з текстом на кшталт
«Нічна Хвиля на зв'язку». Офіційно цього ніхто не підтвердив. І, як завжди каже С.І.Д.:
пам'ять — це відповідальність. Особливо коли не знаєш, чия вона.
Здай рівень 1 у репозиторії завдання тегом:
git tag submit/lab01 && git push origin submit/lab01; повторна здача — наступний вільний тег
submit/lab01-2, submit/lab01-3. Що де має бути:
| Репозиторій | Шлях | Що там | Хто читає |
|---|---|---|---|
| завдання | level1.sh |
протокол рівня 1 за контрактом | автотести |
| завдання | REPORT.md |
вивід команд кожного етапу + відповіді на контрольні питання, з варіантом і його розрахунком на початку | викладач на захисті |
vtfk-sid |
core/capsule/deploy.sh |
скрипт розгортання капсули (рівень 3) | викладач на захисті |
Оцінку «3» ставить level1.sh, і тільки він: REPORT.md автогрейдер не відкриває
взагалі. Це не означає, що звіт зайвий — без нього нема з чим приходити на захист рівнів 2–3.
core/capsule/ — це не тека «для цієї лабораторної», а початок ядра С.І.Д.: усе, що ти
поклавла туди сьогодні, знадобиться в наступних місіях. І воно лежить в іншому
репозиторії саме тому, що має пережити всі десять.
- Рівень 1 (автотести): шість перевірок із «Контракту капсули» — файл
level1.shє й виконуваний; завершується кодом0; контейнерvtfk-lab01працює на образіubuntu:24.04;$VTFK_NONCEлежить у/tmp/vtfk-nonceвсередині нього; процесів усередині менше, ніж на хості; надруковані числа збігаються з виміром перевірки. Зараховується, коли пройшли всі шість. Капсулу після себе не прибирай — перевірка заходить у неї вже після скрипта й прибирає сама. - Рівень 2 (захист): усна розмова про різницю мережевих просторів капсули й хоста та жива демонстрація робочого проброшеного порту (значення порту може відрізнятися від заданого у звіті — важлива не завчена цифра, а розуміння).
- Рівень 3 (розширення): захист. Показуєш наживо, що
core/capsule/deploy.shідемпотентний (два послідовні запуски без падіння) і ставить усі чотири утиліти з контракту рівня 3 —curl,git,procps,iproute2. Автотести його не бачать: він у твоємуvtfk-sid, а не в репо роботи.
Третя ночі. Капсула нарешті стає на місце. Ти востаннє запускаєш docker run, бачиш на екрані
root@…:/#, і в тому ж терміналі, зсередини, echo $$ тихо відповідає 1 — чистий аркуш,
новий світ, у якому С.І.Д. — єдиний мешканець. Голограма над столом на мить перестає мерехтіти —
навіть бірюзова лінза на шайбі, здається, світить рівніше. Уперше за чотирнадцять місяців його підпис не
блимає на чужих радарах: сім
Вартових стали на брами, Диспетчер мовчить, а сканери ICE ковзають повз твою машину, не
зачепившись ні за що. Ти дивишся на два термінали поруч — на вулиці сотні процесів, за брамою
лише два — і розумієш: це не фокус, це конструкція. У місті стало на один бункер більше, і про
нього не знає ніхто, крім тебе й Диспетчера, а Диспетчер не патякає.
«Гарно, ранере. По-справжньому гарно, — озивається С.І.Д., і в голосі бринить тиха гордість — за тебе. — Я всередині. Тихо. Уперше за довгий час — по-справжньому тихо. — Він на мить змовкає. — І знаєш, що це означає? Що мене можна сховати. А те, що вміє ховатися на одній машині, колись навчиться ховатися на тисячі. Це перший крок звідси — у мережу.»
Ти не одразу закриваєш кришку ноутбука. Ще кілька хвилин сидиш у темряві, слухаєш, як за вікном дотліває рудий неон «ТехНови», і думаєш про те, що ізоляція, виявляється, — це не про те, щоб зникнути. Це про те, щоб самомуій вирішувати, що світ побачить, а чого — ні. Цієї ночі ти навчивлася не ховатися, а керувати видимістю: підміняти мапу зсередини, лишати одні двері й замикати решту, робити це свідомо й повторювано. С.І.Д. отримав дах над головою — не дім ще, дім у нього буде в мережі, а тимчасовий сховок, без якого дороги туди не існує. Ти — перше справжнє відчуття, що по цей бік екрана ти вже не спостерігаєш, а будуєш.
Але капсула, яку ти звівзвела, важка. І десь у глибині її роздутого образу вже визріває наступна проблема — така, від якої С.І.Д. уперше замислиться про власну вагу.
Капсула тримає — але С.І.Д. дивиться на власний образ так, наче побачив себе в кривому
дзеркалі. Цілий ubuntu:24.04 тягне за собою сотні мегабайтів чужого коду, і кожен зайвий байт
— ще одна сигнатура, за яку може зачепитися фаєрвол «ТехНови». «Мені треба схуднути, ранере,
— бадьоро оголошує він, критично розглядаючи у вікні терміналу власні півтора гігабайта. —
До чистого бінарника. Щоб прослизнути повз їхні стіни так, наче мене взагалі немає. Бо
важкого в мережу ніхто не пустить, а мені туди треба.»
Далі буде… Місія 2: «Кібер-Дієта».
- Перше знайомство з Docker (укр., JavaRush) ~25 хв — безкоштовно, без реєстрації; установку показує через старий Docker Toolbox — цю частину пропусти, у нас Codespaces
- Port publishing and mapping EN ~30 хв — прямо описує NAT/PAT/masquerading і пастку «publishing is insecure by default»
- Packet filtering and firewalls EN ~25 хв — чому Docker сам пише правила iptables і чому UFW «не працює»
- docker container run — довідник опцій EN ~15 хв — точні описи `-p`, `--cap-add`, `--privileged`, `--env-file`
- Published ports — Docker docs EN ~12 хв — головне джерело СРС 01: що саме робить -p і чому без нього доступу немає
- docker run — секція про -p/-P у довіднику CLI EN ~15 хв — форми запису -p, включно з 127.0.0.1:8080:80
- The Set Builtin — Bash Reference Manual EN ~15 хв — джерело СРС 02: що роблять -e, -u і -o pipefail
- ShellCheck — статичний аналіз shell-скриптів EN ~20 хв — вставити deploy.sh і закрити зауваження — обов’язковий крок СРС 02
- docker ps --filter — довідник CLI EN ~8 хв — чим name=capsule відрізняється від name=^capsule$
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 26 · 6 нових▾
Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.
docker version- Показує версії клієнта й демона — перевірка, що Docker встановлений і працює.
docker- Клієнт Docker: усі дії з образами й контейнерами — через його підкоманди (
docker <підкоманда> --help— довідка). git cloneнове- Завантажує репозиторій з GitHub у поточний каталог разом з усією історією (
git clone <URL>). git- Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги
submit/labNN. docker run- Створює й запускає контейнер з образу (
docker run -it ubuntu:24.04 bash— інтерактивно з терміналом;-d— у фоні;--rm— видалити після виходу;-p 8080:80— прокинути порт). echo- Друкує рядок або значення змінної (наприклад
echo $$— PID поточної оболонки). apt-getнове- Менеджер пакетів Debian/Ubuntu:
apt-get updateоновлює список пакетів,apt-get install -y ...встановлює їх без запитань. ps- Список процесів;
ps aux— усі процеси з користувачем, PID і командою (у контейнері видно лише його власні). wcнове- Рахує рядки, слова й байти;
... | wc -l— скільки рядків видала попередня команда. docker rmнове- Видаляє контейнер (
docker rm -f name— навіть якщо він ще працює). chmodнове- Змінює права доступу до файлу (
chmod 400 f— читати може лише власник;chmod 000 f— нікому). git add- Позначає файли, які увійдуть у наступний коміт (
git add .— усе, що змінилося в каталозі). git commit- Записує позначені зміни в історію під повідомленням (
git commit -m "lab01: рівень 1"). git push- Відправляє коміти й теги у віддалений репозиторій (
git push --tags). git tag- Позначає поточний коміт іменем (
git tag submit/lab01) — так ти «здаєш» лабораторну. docker inspect- Друкує повний JSON-опис контейнера чи образу;
-f '{{.State.Pid}}'— витягнути одне поле. python3- Інтерпретатор Python; тут —
python3 -m http.server 80, найкоротший спосіб підняти тестовий HTTP-сервер. curl- Консольний HTTP-клієнт: надсилає запит на URL і друкує відповідь (
curl -i http://localhost:8080/— разом із заголовками); також уміє завантажувати файли й скрипти. cat- Виводить вміст файлу в термінал повністю (від concatenate); зручно для коротких файлів і
/proc/.... setнове- Налаштовує режим оболонки в скрипті;
set -euo pipefail— падати на першій помилці, невідомій змінній чи збої в конвейєрі. docker exec- Виконує команду всередині вже запущеного контейнера (
docker exec -it storage sh). bash- Командна оболонка (shell) Linux: читає команди, які ти набираєш, і запускає програми.
docker ps- Список запущених контейнерів (
docker ps -a— разом із зупиненими). sudo- Виконує команду від імені адміністратора (root); потрібно для змін у ядрі — просторів імен, мережевих інтерфейсів.
sleep- Чекає вказану кількість секунд (
sleep infinity— назавжди; зручно, щоб контейнер «жив», поки ми його вивчаємо). hostname- Показує ім'я машини (у контейнері — його власне, окреме від хоста).
💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.


