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

Лабораторна робота № 3. Два контейнери разом і ізольована мережа

Мета роботи

Навчитися піднімати кілька взаємодіючих контейнерів у власній ізольованій мережі Docker з DNS-резолвінгом за іменем, обмінюватися даними через спільний том (volume), незалежний від життєвого циклу контейнерів, і безпечно передавати секретні ключі між контейнерами через змінні середовища, мінімізуючи слід секрету в місцях, доступних стороннім.

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

1. Простори імен: що використовується в цій роботі

Повний перелік просторів імен, прапорці clone() і механізм підміни видимості розглянуті на лекції 3 (того самого дня). У цій роботі використовується один простір — мережевий (NET): кожен контейнер отримує власні інтерфейси, таблицю маршрутизації та діапазон портів.

Належність процесу до простору імен перевіряється через каталог /proc/<pid>/ns/, у якому кожен простір представлений символьним посиланням вигляду net:[4026532008]. Число в дужках — inode простору: однаковий inode означає той самий простір. Це дає пряму перевірку, яка виконується в роботі: два контейнери в спільній user-defined мережі мають однаковий inode net, а контейнер і хост — різні.

2. Мережева ізоляція контейнерів і потреба у спільній мережі

Кожен контейнер за замовчуванням отримує власний NET-простір імен (лабораторна робота № 1), тому два довільні контейнери не бачать мереж один одного, доки не приєднані до спільної мережі Docker. Дефолтна мережа bridge, у яку контейнери потрапляють без явного --network, не веде записів DNS для контейнерів за іменем — доступ можливий лише за внутрішньою IP-адресою, яка змінюється при кожному перезапуску контейнера.

3. User-defined bridge мережа і вбудований DNS

Користувацька (user-defined) bridge-мережа, створена командою docker network create, дає контейнерам, приєднаним до неї з іменем (--name), вбудований DNS-резолвінг: звернення до імені контейнера всередині мережі автоматично резолвиться у поточну IP-адресу того контейнера, без ручного оновлення /etc/hosts чи будь-якого зовнішнього DNS-сервера. Це принципова відмінність від дефолтної мережі bridge, де такого сервісного DNS немає.

4. Том (volume) як незалежна одиниця зберігання

Файлова система контейнера — тимчасова: видалення контейнера (docker rm) знищує все, що контейнер записав понад базові шари образу. Том (volume), створений командою docker volume create, — окрема сутність, керована Docker і не прив'язана до життєвого циклу конкретного контейнера. Кілька контейнерів можуть монтувати той самий том одночасно (-v <ім'я-тому>:<шлях>) і обмінюватися файлами через файлову систему, не відкриваючи для цього мережевий порт. На відміну від bind mount (монтування конкретної теки хоста), том керується самим Docker і не залежить від структури файлів на конкретному хості, що робить його переносним.

5. Секрети: змінні середовища, --env-file, межі приховування

Секрети (ключі доступу, паролі) не повинні лишати слідів у місцях, доступних стороннім: у команді запуску контейнера (видима через docker inspect, поле Config.Cmd), в історії процесів термінала, у логах CI/CD чи безпосередньо в Dockerfile. Передача через змінні середовища при запуску контейнера можлива двома способами:

  • docker run -e KEY=value — значення потрапляє і в Config.Env контейнера, і дублюється в команді запуску, видимій в історії шелла та журналах.
  • docker run --env-file secrets.env — значення потрапляє в Config.Env, але не з'являється в команді запуску й не лишається в історії шелла; сам файл секретів не комітиться в репозиторій (.gitignore).

Важливо розуміти межу цього підходу: значення все одно залишається доступним через docker inspect у полі Config.Env — повністю приховати його від будь-якого доступу цей механізм не може. Мета навчального завдання — не лишити слід у Dockerfile, історії команд збірки/запуску й репозиторії, а не досягти криптографічно строгого приховування (для цього існують docker secret у режимі Swarm або секрети, змонтовані як файли в Kubernetes/Compose — тема розширення поза межами базового завдання).

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

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

  1. Створити user-defined bridge-мережу: docker network create mission-net. Очікуваний результат: мережа mission-net з'являється у списку docker network ls.
  2. Підняти контейнер-сховище в цій мережі з іменем (--name storage --network mission-net). Очікуваний результат: стан контейнера running.
  3. Підняти контейнер-сервіс у тій самій мережі (--name agent --network mission-net) і перевірити резолвінг імені storage (ping storage або getent hosts storage). Очікуваний результат: успішна відповідь за іменем, без явного вказання IP.
  4. Створити том: docker volume create mission-logs і змонтувати його в обидва контейнери (-v mission-logs:/data). Очікуваний результат: файл, записаний у /data одним контейнером, видно в іншому.
  5. Записати тестовий «зашифрований лог» у спільний том із контейнера-сервісу і прочитати його з контейнера-сховища. Очікуваний результат: вміст файлу ідентичний у обох контейнерах.
  6. Видалити обидва контейнери й підняти їх заново з тим самим томом. Очікуваний результат: дані в томі збереглися, незважаючи на видалення контейнерів.
  7. Передати секретний ключ у контейнер-сервіс через --env-file secrets.env (файл не комітиться в репозиторій), перевірити доступність змінної всередині контейнера й переконатися, що ключ не видно в docker inspect agent як частина команди запуску. Очікуваний результат: усередині контейнера echo "$STORAGE_ACCESS_KEY" виводить значення; docker inspect agent --format '{{.Config.Cmd}}' значення не містить.

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

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

Тег ставить скрипт здачі, а не студент. У робочому просторі — Ctrl+Shift+B (меню Terminal → Run Build Task), або те саме з терміналу: bash .devcontainer/submit.sh. Скрипт комітить незбережене, показує, який тег створить, і чекає підтвердження; відмова нічого не здає. Ручні git tag / git push лишаються запасним шляхом — вони працюють і поза Codespaces.

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

Рівень 1 (оцінка 3, автотести). Підняти два контейнери (сервіс і сховище) й налаштувати ізольовану user-defined мережу між ними з резолвінгом за іменем. Відповідає крокам 1–3 порядку виконання.

Здається скриптом scripts/up.sh у корені репозиторію завдання — кроки 1–3, записані так, щоб їх можна було відтворити однією командою. Контракт скрипту:

  1. Шапка #!/usr/bin/env bash і set -euo pipefail; запуск без аргументів (bash scripts/up.sh), без інтерактивного вводу.
  2. Створити user-defined bridge-мережу mission-net.
  3. Підняти в ній контейнер storage — HTTP-сервер на порту 8000, що віддає свою робочу теку (python3 -m http.server 8000 на образі python:3.12-slim).
  4. Підняти в ній контейнер agent довготривалим процесом (-d … sleep infinity, не -it); в образі мають бути getent і HTTP-клієнт (python3, curl або wget) — python:3.12-slim має обидва.
  5. Не прописувати адресу сховища явно: жодних --add-host, --ip чи записів в /etc/hosts — ім'я storage резолвить вбудований DNS мережі.
  6. Бути ідемпотентним: повторний запуск поспіль завершується кодом 0 попри вже наявні мережу й контейнери.
  7. Лишити обидва контейнери працювати після завершення скрипту.

Спільного тому й секретів (secrets.env) у скрипті рівня 1 немає — це рівні 2–3.

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

# Перевірка
1 scripts/up.sh існує і завершується кодом 0
2 контейнери agent і storage працюють
3 обидва приєднані до спільної user-defined bridge-мережі mission-net
4 getent hosts storage усередині agent повертає IP сховища в mission-net, і ім'я не прописане в /etc/hosts
5 HTTP-запит з agent на http://storage:8000/<файл> повертає вміст файлу, який перевірка поклала в робочу теку storage
6 повторний запуск scripts/up.sh теж завершується кодом 0

Перевірки 2–5 виконуються після завершення скрипту (docker inspect, docker exec), тому прибирання контейнерів у кінці up.sh знімає чотири перевірки з шести; прибирає автотест сам. Ім'я й вміст файлу п'ятої перевірки випадкові й щоразу нові, тож пройти її можна лише справжнім запитом агента до сховища за іменем. Рівень зараховується, коли пройшли всі шість перевірок.

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

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

Два рівноцінні варіанти виконання (оцінка однакова):

  • A — на власному наскрізному артефакті: роль контейнера agent виконує образ core/capsule/, зібраний у роботах № 1–2. Том, підключений до нього в цій роботі, використовується далі — у роботі № 4 у ньому зберігається згенерований ключ.
  • B — на референсному знімку: якщо власного образу немає, береться vtfk-sid-reference@after-lab02.

Допуск до захисту. Перед захистом рівня 2 треба залишити три предметні зауваги до роботи № 2 іншого студента — в issue або коментарі до комміта його репозиторію завдання. Кого рецензувати: студента, чий номер у списку групи на 3 більший за власний (по колу — номер цієї роботи задає зсув, тому пари щоразу різні). Заувага предметна, якщо називає конкретний рядок, команду або крок і пояснює, у чому проблема; загальні оцінки («усе правильно», «гарний код») не враховуються. Це елемент ЗК7 — здатності працювати в команді: рівень 1 доводить, що код збирається, рецензія — що студент уміє його читати.

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

Рівень 3 (оцінка 5, розширення). Налаштувати передачу секретних ключів між контейнерами через змінні середовища так, щоб значення не з'являлося в команді запуску контейнера. Відповідає кроку 7 порядку виконання.

Ті самі два варіанти: A — конфігурація секрету лягає в core/capsule/ власного репозиторію; B — той самий результат від референсного знімка.

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

Приклад 1. DNS-резолвінг у user-defined мережі

$ docker network create mission-net
$ docker run -d --name storage --network mission-net nginx:1.27-alpine
$ docker run -it --rm --network mission-net alpine:3.20 sh
/ # getent hosts storage
172.19.0.2      storage

Резолвінг storage → 172.19.0.2 відбувається автоматично через вбудований DNS-сервер Docker у межах мережі mission-net, без попереднього вказання IP. У дефолтній мережі bridge (без явного docker network create) та сама команда завершилася б помилкою — дефолтна мережа не веде записів DNS для контейнерів за іменем.

Приклад 2. -e проти --env-file

$ docker run -d --name bad-agent -e STORAGE_ACCESS_KEY=0xC1D-secret alpine sleep 1000
$ docker inspect bad-agent --format '{{.Config.Env}}'
[PATH=/usr/local/sbin:... STORAGE_ACCESS_KEY=0xC1D-secret]

$ echo "STORAGE_ACCESS_KEY=0xC1D-secret" > secrets.env
$ docker run -d --name agent --env-file secrets.env alpine sleep 1000
$ docker inspect agent --format '{{.Config.Cmd}}'
[sleep 1000]

В обох випадках значення технічно опиняється в Config.Env контейнера — це властивість самого механізму змінних середовища. Різниця в тому, де ще значення лишає слід: -e дублює його в команді запуску (видно в Config.Cmd/аргументах та в історії шелла), тоді як --env-file тримає значення виключно у виключеному з репозиторію файлі.

❓ КОНТРОЛЬНІ ПИТАННЯ
  1. Чому два контейнери в дефолтній bridge-мережі не резолвлять один одного за іменем, а в user-defined bridge — резолвлять?
  2. Що станеться з даними в томі (volume) після видалення обох контейнерів, які його монтували?
  3. Чим том (volume) відрізняється від bind mount з погляду керування Docker?
  4. Чому передавати секрет як аргумент команди в Dockerfile (ARG/ENV із хардкодом) небезпечніше, ніж через --env-file під час запуску?
  5. Як мережева ізоляція (--network mission-net) захищає обмін даними між сервісом і сховищем від зовнішнього доступу?
  6. Що покаже docker inspect agent щодо переданих через -e змінних середовища?
  7. Чому DNS-резолвінг за іменем контейнера важливіший, ніж здається, якщо контейнер може бути перезапущений з новою IP-адресою?
  8. У чому принципова межа приховування секрету через --env-file порівняно з docker secret у режимі Swarm?

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

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

Здача відбувається тегом submit/lab03. Звіт REPORT.md містить: вивід резолвінгу agent → storage за іменем, вміст файлу зі спільного тому, записаного одним контейнером і прочитаного іншим, фрагмент docker inspect agent (зі значенням секрету, видаленим уручну зі звіту, якщо воно там присутнє в незашифрованому вигляді), і відповіді на контрольні питання.

Автотести (рівень 1) перевіряють скрипт scripts/up.sh: він проходить двічі поспіль, обидва контейнери піднято, приєднано до спільної user-defined мережі mission-net, і контейнер-сервіс успішно резолвить та опитує контейнер-сховище за іменем (без явного IP). Захист (рівень 2) — усне пояснення механізму DNS-резолвінгу в мережі Docker і демонстрація запису/читання даних через спільний том обома контейнерами. Розширення (рівень 3) перевіряється на захисті: демонструється відсутність секретного значення у виводі docker inspect (у полі команди запуску чи аргументів) і коректну доступність значення змінної всередині контейнера під час виконання.

🔗 ДЖЕРЕЛА
  • Networking overview EN ~30 хв — усі драйвери в одному місці
  • Bridge network driver EN ~30 хв — головна теза: автоматичний DNS лише в user-defined
  • Volumes EN ~1 год — коли що, життєвий цикл, ризики bind mount
  • Bind mounts EN ~1 год — коли що, життєвий цикл, ризики bind mount
  • Build secrets EN ~25 хв — цитата для слайду: build args і env «inappropriate for passing secrets… because they persist in the final image»
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 19 · 6 нових▾

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

docker networkнове
Керує мережами: docker network create net — своя мережа, де контейнери бачать один одного за іменами.
docker
Клієнт Docker: усі дії з образами й контейнерами — через його підкоманди (docker <підкоманда> --help — довідка).
docker run
Створює й запускає контейнер з образу (docker run -it ubuntu:24.04 bash — інтерактивно з терміналом; -d — у фоні; --rm — видалити після виходу; -p 8080:80 — прокинути порт).
docker inspect
Друкує повний JSON-опис контейнера чи образу; -f '{{.State.Pid}}' — витягнути одне поле.
echo
Друкує рядок або значення змінної (наприклад echo $$ — PID поточної оболонки).
docker rm
Видаляє контейнер (docker rm -f name — навіть якщо він ще працює).
docker secretнове
Керує секретами у режимі Swarm — файли, які монтуються в контейнер у /run/secrets/, а не передаються через змінні середовища.
docker volumeнове
Керує томами (volumes) — сховищем даних, що живе окремо від контейнера й переживає його видалення.
pingнове
Перевіряє, чи досяжний хост у мережі, надсилаючи ICMP-пакети (ping storage).
getentнове
Читає системні бази імен; getent hosts storage — розв'язує ім'я в адресу тим самим шляхом, що й програми.
bash
Командна оболонка (shell) Linux: читає команди, які ти набираєш, і запускає програми.
git tag
Позначає поточний коміт іменем (git tag submit/lab01) — так ти «здаєш» лабораторну.
git
Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги submit/labNN.
git push
Відправляє коміти й теги у віддалений репозиторій (git push --tags).
set
Налаштовує режим оболонки в скрипті; set -euo pipefail — падати на першій помилці, невідомій змінній чи збої в конвейєрі.
python3
Інтерпретатор Python; тут — python3 -m http.server 80, найкоротший спосіб підняти тестовий HTTP-сервер.
curl
Консольний HTTP-клієнт: надсилає запит на URL і друкує відповідь (curl -i http://localhost:8080/ — разом із заголовками); також уміє завантажувати файли й скрипти.
wgetнове
Завантажує файл за URL у поточний каталог.
docker exec
Виконує команду всередині вже запущеного контейнера (docker exec -it storage sh).

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