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

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

Мета роботи

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

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

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

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

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

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

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

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

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

4. Секрети: змінні середовища, --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 · «3» · ПЕРШОКУРСНИК

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

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

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

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

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

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

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

Приклад 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

Резолвінг storage172.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 містить: вивід резолвінгу agentstorage за іменем, вміст файлу зі спільного тому, записаного одним контейнером і прочитаного іншим, фрагмент docker inspect agent (зі значенням секрету, видаленим уручну зі звіту, якщо воно там присутнє в незашифрованому вигляді), і відповіді на контрольні питання.

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

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

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

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 — розв'язує ім'я в адресу тим самим шляхом, що й програми.

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