Docker
Docker
Docker кардинально меняет подход: у нас общее ядро (хост), а каждое приложение
получает свой контейнер, где описаны все нужные зависимости. В итоге:
1/2
Но Docker не ограничивается docker run. Чтобы реально использовать
возможности контейнеризации, нужно разбираться в слоях образа, правильном
кэшировании, сетевых драйверах, томах для хранения данных, безопасности и
многом другом. Именно это и делает курс полноценным.
Итог
2/2
1.3 Подготовка окружения
VirtualBox (бесплатно)
VMware Workstation Player (бесплатно для личного использования)
Hyper-V (встроен в Windows 10/11 Pro, Enterprise и Education)
1. Запустите VirtualBox
2. Нажмите кнопку "Создать" или "New"
1/7
3. Настройте параметры новой ВМ:
Имя: Ubuntu (или любое другое)
Тип: Linux
Версия: Ubuntu (64-bit)
Нажмите "Далее"
4. Выделите память (RAM):
Рекомендуется минимум 2048 МБ (2 ГБ)
Оптимально 4096 МБ (4 ГБ)
Нажмите "Далее"
5. Создайте виртуальный жесткий диск:
Выберите "Создать новый виртуальный жесткий диск"
Нажмите "Создать"
6. Выберите тип жесткого диска:
Выберите VDI (VirtualBox Disk Image)
Нажмите "Далее"
7. Выберите формат хранения:
Рекомендуется "Динамический виртуальный диск"
Нажмите "Далее"
8. Укажите размер и расположение диска:
Минимум 20 ГБ, рекомендуется 30-50 ГБ
Выберите расположение диска
Нажмите "Создать"
2/7
5. В разделе "Updates and other software":
Выберите "Normal installation"
Отметьте "Download updates while installing Ubuntu"
Нажмите "Continue"
6. В разделе "Installation type":
Выберите "Erase disk and install Ubuntu" (не беспокойтесь, это относится
только к виртуальному диску)
Нажмите "Install Now"
Подтвердите изменения, нажав "Continue"
7. Выберите часовой пояс и нажмите "Continue"
8. Настройте учетные данные:
Введите ваше имя
Придумайте имя компьютера
Укажите имя пользователя
Создайте пароль
Нажмите "Continue"
9. Дождитесь завершения установки
10. Нажмите "Restart Now", когда появится соответствующее сообщение
11. Когда система попросит извлечь установочный носитель, просто нажмите Enter
sudo reboot
3/7
1. Откройте терминал в Ubuntu (Ctrl+Alt+T)
2. Запустите скрипт установки:
sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release
sudo apt update && sudo apt install -y docker-ce docker-ce-cli [Link]
docker-compose-plugin
sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release
curl -fsSL [Link] | sudo gpg --dearmor -o
/etc/apt/keyrings/[Link]
echo "deb [arch=$(dpkg --print-architecture)]
[Link] $(lsb_release -cs) stable" | sudo tee
/etc/apt/[Link].d/[Link] > /dev/null
sudo apt update && sudo apt-get install -y docker-ce docker-ce-cli [Link]
docker-compose-plugin
sudo gpasswd -a $USER docker
newgrp docker
4/7
Проверка установки
После установки проверьте, что Docker работает корректно:
Дополнительные настройки
docker --version
docker compose version
5/7
2. Перетащите иконку Docker в папку Applications
3. Закройте окно установщика
docker --version
Дополнительные советы
6/7
Для виртуальной машины Linux
1. Для удобной работы используйте общие папки между Windows и VM:
7/7
2.1 Контейнеризация vs Виртуализация
1/4
Минусы:
Чуть менее жёсткая изоляция, так как одно общее ядро. Хотя
namespaces и seccomp дают хорошую безопасность, формально VM-
контейнеры — более глубоко изолированная среда.
Контейнеры обычно зависят от ОС хоста (Linux/Windows). Но для
macOS/Windows есть Docker Desktop, который под капотом всё равно
запускает Linux VM.
Как эти технологии работают вместе в Docker — при запуске контейнера Docker
создаёт набор namespaces для изоляции, настраивает cgroups для ограничения
ресурсов и назначает минимальный набор capabilities для безопасной работы.
2/4
3. Сравнительная таблица
3/4
Банковские/государственные требования, когда узаконена конкретная
модель безопасности с VM.
Итог
Виртуализация (VM) — полноценная гостевая ОС внутри гипервизора,
надёжная, но тяжёлая по ресурсам.
Контейнеризация (Docker) — общий ядро-хост, лёгкое и быстрое, идеально
для DevOps, микросервисов, CI/CD.
Технологии в основе — namespaces изолируют ресурсы, cgroups
контролируют потребление, capabilities обеспечивают безопасность.
4/4
2.2 Что такое Docker?
Docker — это не просто программа для контейнеров. Это целая экосистема, которая
упрощает:
Идея в том, что вы один раз описываете, какое окружение нужно вашему
приложению, а Docker создаёт среду (контейнер), которая точно совпадает и на
вашем ноутбуке, и на тестовом сервере, и в продакшене. Это даёт уверенность:
“Если работает здесь, значит будет работать везде” (есть случаи, когда это не
так).
Простая аналогия
1/2
Можно представить, что каждое приложение — это чемодан (контейнер), в котором
сложены все вещи (зависимости). Docker помогает сформировать этот чемодан и
перевезти его, куда нужно, не разбирая заново и не забывая ничего важного.
Вывод
Docker — это инструмент контейнеризации, который упростил процесс создания и
запуска контейнеров в Linux (а через Docker Desktop и на macOS и Windows). Он
решает проблему: как упаковать приложение в лёгкую, но изолированную среду и
как гарантировать одинаковую работу кода и зависимостей в разных окружениях.
Поэтому о Docker говорят все — это базис для современных DevOps-практик и
микросервисной архитектуры.
2/2
10 апреля 2025 г.
Общая схема
Можно представить Docker как систему из нескольких основных компонентов:
1. Docker Daemon
2. Docker Client (CLI или API)
3. Docker Engine (часто подразумевается как объединение Daemon + механизмы
работы с контейнерами)
4. Registry (реестр образов)
Когда мы вводим команду вроде docker run, она идёт к Docker Daemon, который, в
свою очередь, управляет контейнерами, образами, сетью и т. д.
Если Daemon упал или не запущен, команды docker не будут работать, потому
что некому их исполнять.
1/3
2. Docker Client (CLI) и API
CLI — это командная строка, где мы вводим docker run, docker ps, docker
images и прочие команды.
Под капотом все эти команды идут по Docker API. То есть CLI просто
оборачивает HTTP-запросы, которые отправляются Daemon.
3. Docker Engine
2/3
Однако, в базовом понимании достаточно знать, что Docker Engine — это общий
движок Docker, отвечающий за взаимодействие клиента, ядра системы и
контейнеров.
Когда мы делаем docker pull, Daemon скачивает образ из реестра. Когда делаем
docker push, мы, наоборот, отправляем образ (собранный у нас) в реестр, чтобы
другие могли его использовать.
3/3
Мы уже знаем, что Docker помогает быстро собирать и запускать контейнеры. Но из
чего состоит эта контейнерная магия? В Docker есть несколько ключевых
сущностей, каждая со своей ролью. Сейчас их кратко рассмотрим, а в следующих
уроках углубимся в детали.
1. Образы (Images)
Образ — это шаблон контейнера. Он содержит:
2. Контейнеры (Containers)
1/3
Если образ — это чертёж, то контейнер — это уже построенный дом (и, кстати, дом
можно перестраивать, не затрагивая чертёж).
Главная фишка: один и тот же образ может быть использован для запуска многих
контейнеров (параллельно или по очереди).
3. Сети (Networks)
В Docker каждый контейнер, по умолчанию, получает собственный изолированный
сетевой стек. Это значит, что:
2/3
4. Тома (Volumes) и хранение данных
По умолчанию, файлы внутри контейнера живут только в слое записи и
пропадают, когда контейнер удалён. Если нужно сохранять данные дольше
(например, база данных), Docker предлагает:
Это позволяет:
Dockerfile: это шаблон для создания образа: скопируй вот этот файл,
поставь такую-то библиотеку, запусти команду...
docker-compose: инструмент для описания (в YAML) нескольких сервисов,
сетей и томов. Полезно, когда нужно поднимать, например, веб-приложение и
базу данных одновременно.
Registry (реестр): хранилище образов (Docker Hub, GitHub Container Registry,
локальный registry).
Docker CLI: набор команд (run, ps, build, pull, push).
3/3
5. Первая практика: пробуем Docker в деле
Пора перейти от теории к практике — пусть и самой базовой. Идея в том, чтобы
убедиться, что Docker установлен и работает, и почувствовать, как выглядят
простейшие операции. В этом задании не нужно писать Dockerfile или понимать все
тонкости. Главное — запустить пару команд и увидеть, что у нас живой Docker.
Подготовка
docker version
Что происходит?
1/3
docker run --name mynginx -d -p 8080:80 nginx
Проверка
(Остановит контейнер)
docker rm mynginx
docker ps -a
docker images
Итог
Что мы сделали:
2/3
Таким образом вы убедились, что Docker у вас работает, и что команда docker run
действительно поднимает контейнер. В следующих практиках мы начнём
разбираться глубже: как строится образ (Dockerfile), как конфигурируется
контейнер (сеть, тома), и прочие “хитрости” Docker. Но этот небольшой шаг уже
показывает основной принцип: одна команда — и у вас в считанные секунды
запущен полноценный сервис.
3/3
2.6 Ключевые команды Docker
1. docker run
Пример:
Если образ nginx не найден локально, Docker подтянет его из реестра (Docker
Hub).
Запустит контейнер, выполняя внутри него основной процесс (у nginx это веб-
сервер).
Полезные флаги:
1/5
-d (detached):
Запуск в фоновом режиме. Это значит, что ваш терминал сразу вернётся к
приглашению командной строки, а контейнер продолжит работать в фоне.
Пример: docker run -d nginx
После такого запуска nginx не будет занимать ваш терминал, и вы сможете
продолжить вводить другие команды.
Если бы вы не использовали -d, то при запуске nginx вы видели бы логи прямо
в консоли, и контейнер захватил бы терминал до тех пор, пока вы не
остановите процесс (Ctrl+C или docker stop).
--name <имя>:
Позволяет задать контейнеру понятное имя вместо случайного ID.
Пример: docker run -d --name mynginx -p 8080:80 nginx
Так мы будем знать, что именно этот контейнер называется mynginx, и сможем
удобнее обращаться к нему (остановить, посмотреть логи и т.д.).
2. docker ps
Отображает список запущенных контейнеров.
Пример:
docker ps
2/5
Результатом будет таблица с колонками (CONTAINER ID, IMAGE, COMMAND,
CREATED и т. д.), чтобы видеть, какие контейнеры в данный момент живут и
работают.
4. docker pull
Команда, которая явно скачивает образ из реестра (Docker Hub или другого
реестра, если указаны соответствующие настройки):
Если нужного тега не указано, Docker подтянет latest (при наличии). Например:
3/5
Обычно docker run сам делает pull, если образа нет локально. Но docker pull
полезен, когда вы заранее хотите убедиться, что нужная версия образа у вас есть (к
примеру, в скриптах или CI/CD-процессах).
5. docker images
Чтобы посмотреть, какие образы локально хранятся, используйте:
docker images
Если образ вам больше не нужен, можно удалить командой docker rmi <image>.
(Осторожно: если этот образ используется каким-то контейнером, Docker не
даст просто так его удалить.)
6. docker rmi
4/5
8. Дополнительные команды в контексте
1. docker kill:
Резко завершает контейнер (в отличие от stop, который посылает SIGTERM).
kill сразу посылает SIGKILL, не давая приложению времени на корректное
закрытие.
2. docker rename:
Переименовывает контейнер. Не часто используется, но бывает полезно, если
дали неудобное имя.
Пример: docker rename old_name new_name
4. docker logs:
Выводит логи контейнера: всё, что контейнер напечатал (stdout / stderr) с
момента запуска.
Пример: docker logs mynginx
Можно использовать флаг -f (follow), чтобы подписаться на логи в режиме
реального времени:
docker logs -f mynginx.
Итог
Вот тот минимальный набор команд, без которых невозможно прожить день в
Docker:
5/5
4 марта 2025 г.
2.7 Практика
Задание
2. Запуск контейнера
docker run -d \
--name checker \
-e KEY=devops2025 \
prostodevops/docker-basics-checker:1.0
docker ps
1/2
4. Логи
5. Остановка и удаление
6. Ввод кода
Введите, например, «1234-ABCD» в форму ответа.
Результат
2/2
2.7 Практика
Давайте немного усложним задачу: теперь контейнер будет поднимать простой веб-
сервис на порту 80, а мы «пробросим» этот порт на хост (например, на 9999), чтобы
получить доступ к секретному коду по адресу [Link] Также
необходимо передать переменную окружения KEY=supersecret, без которой код не
отобразится.
Задание
1. Скачайте (pull) образ из публичного Docker-репозитория —
prostodevops/docker-basics-checker:2.0.
2. Запустите контейнер с именем secretserver. Нужно:
работать в фоновом режиме (-d),
пробросить порт 9999 (хост) на 80 (контейнер),
передать переменную окружения KEY=supersecret (иначе код не
появится),
дать контейнеру читаемое имя (--name secretserver).
3. Убедитесь, что контейнер действительно запущен — посмотрите список
активных контейнеров.
4. Проверьте работу веб-сервиса, открыв [Link] (или через
curl). Если контейнер правильно поднялся и переменная окружения задана,
вы увидите строку вида: «Поздравляю! Ваш код: 1111-AAAA».
5. Остановите контейнер, затем удалите его, чтобы не оставалось «мусора».
6. Вставьте секретный код (тот, который вы увидели в сообщении) в форму
ответа.
1/2
2. Запуск контейнера (пример, чтобы открыть на localhost:9999):
docker run -d \
--name secretserver \
-p 9999:80 \
-e KEY=supersecret \
prostodevops/docker-basics-checker:2.0
-d — фоновый режим
-p 9999:80 — проброс порта (хост:контейнер)
-e KEY=supersecret — переменная окружения, без неё сервис не
покажет код
--name secretserver — задаём читаемое имя контейнера
3. Список контейнеров:
docker ps
curl [Link]
5. Остановка и удаление:
Результат
2/2
2.7 Практика
Задание
1. Скачайте (pull) образ из публичного Docker-репозитория
prostodevops/docker-basics-checker:3.0.
2. Запустите контейнер под определённым именем в фоновом режиме,
пробросив соответствующие порты и передав переменную окружения
KEY=inspecttest.
3. Убедитесь, что контейнер действительно запущен — проверьте список
активных контейнеров.
4. Переименуйте контейнер (потренируйтесь в использовании docker rename) и
убедитесь, что новое имя отобразилось в списке.
5. Проверьте проброшенные порты при помощи docker port, чтобы
убедиться, что внутренняя портовая конфигурация контейнера соответствует
вашей настройке.
6. Изучите метаданные контейнера через docker inspect, чтобы найти
секретный код. Он может быть спрятан в Labels или среди переменных
окружения.
7. Посмотрите логи контейнера (хотя кода там нет, стоит удостовериться, что
контейнер запущен корректно и не даёт ошибок).
8. Остановите контейнер и удалите его, чтобы не оставался мусор.
9. При желании, удалите образ, если он больше не нужен.
10. Вставьте секретный код, найденный при docker inspect, в форму ответа.
2. Запуск контейнера:
docker run -d \
--name meta-checker \
-p 7777:80 \
-e KEY=inspecttest \
prostodevops/docker-basics-checker:3.0
3. Переименование контейнера:
1/2
4. Просмотр портов:
6. Логи контейнера:
7. Остановка и удаление:
или:
2/2
2.8 Работа внутри контейнера
Ранее мы рассмотрели базовые команды docker run, docker ps, docker stop,
docker rm, docker pull и т. д. Теперь поговорим о том, как взаимодействовать с уже
запущенным контейнером: заходить внутрь, выполнять команды, а также
копировать файлы между хостом и контейнером.
Пример:
Часто нужно зайти в контейнер, чтобы выполнить несколько команд подряд в shell –
чем-то похоже на SSH. Для этого добавляем флаги -i (interactive) и -t (pseudo-
TTY):
1/3
Важно: docker exec – это запуск дополнительного процесса внутри контейнера,
который не мешает основному.
docker exec, напротив, создаёт новый процесс (shell, утилиту), что безопаснее и
гибче для отладки. В большинстве случаев сейчас используют именно docker exec
-it для интерактивного входа.
2/3
docker cp [Link] mynginx:/usr/share/nginx/html/[Link]
docker cp mynginx:/usr/share/nginx/html/[Link]
./index_from_container.html
Ограничение: если контейнер удалён, файлы в нём (если они не были в volume)
пропадают. Поэтому docker cp — удобно для быстрого обмена, но для постоянного
хранения данных надо использовать тома (volumes). Мы поговорим о них в
следующих уроках.
Итог
docker exec -it [container] bash — интерактивный доступ внутрь
контейнера (shell).
docker cp — копирование файлов в/из контейнера.
docker attach — «подключение» к главному процессу (используется реже).
3/3
11 марта 2025 г.
2.9 Практика
Задание
docker run -d \
--name puzzle \
prostodevops/cp-puzzle:1.0
1/2
echo "Puzzle solved!" > [Link]
8. Остановка и удаление:
2/2
12 марта 2025 г.
2.9 Практика
Сценарий
Предположим, образ prostodevops/python-runner:1.0 умеет проверять, есть ли
/opt/app/[Link]. Если есть, он даёт возможность запустить скрипт, который
обращается к скрытой функции (внутри образа), которая выдаёт сообщение вида
«Поздравляю! Ваш код: ...». Без [Link] контейнер просто выводит: «No [Link]
found, I'm idle...». Вам нужно «разбудить» контейнер, передав ему этот скрипт и
запустив его.
Задание
1. Сделайте pull образа — prostodevops/python-runner:1.0. По легенде, он
содержит Python и скрытую логику выдачи кода.
2. Запустите контейнер (фоновый режим, имя pycontainer) и посмотрите логи.
Скорее всего, увидите «No [Link] found, I'm idle...».
3. Создайте локально файл [Link]. В нём может быть, например:
#!/usr/bin/env python3
print("Активируем скрытый код...")
import secret
secret.show_code()
1/3
Подсказки (раскрывайте при необходимости)
1. Pull образа:
2. Запуск контейнера:
Скорее всего, «No [Link] found, I'm idle...». Или что-то подобное.
Если всё ок, “скрытый код” внутри образа среагирует и выведет: «Поздравляю!
Ваш код: ЕУУУУ-211231 (пример).
6. Проверьте логи:
7. Остановка и удаление:
Результат
2/3
Впишите секретный код в форму ответа, и вы успешно завершите практику!
3/3
16 марта 2025 г.
2.9 Практика
Только если все три условия выполнены, приложение (зашитое в образ) генерирует
код и выводит его в логах (и/или при перезапуске). Иначе оно сообщает, чего не
хватает. Ваша задача — удовлетворить эти три условия: передать переменную,
смонтировать или скопировать файлы — и получить код
Сценарий
SECRET_KEY (ENV)
наличие /app/config/[Link]
наличие /app/license
Если всё найдено, контейнер радуется и выводит, к примеру: «All checks passed!
SuperCode: 2023-FULL-PASS» (или аналогичную строку). Иначе ругается, указывая,
что отсутствует.
Задание
1. Сделайте pull образа prostodevops/triple-check:1.0.
2. Подготовьте локально:
Файл [Link] (например, secret_data: "Some config info"),
Файл license (к примеру, Licensed to devops-user).
3. Запустите контейнер (имя tripleapp) так, чтобы:
Передать переменную окружения SECRET_KEY (например, mysuperkey).
Примонтировать (bind mount) ваш [Link] внутрь
/app/config/[Link]. (Или используйте volume, если хотите.)
Пока без файла /app/license — посмотрим, что скажет логи.
4. Проверьте логи контейнера. Скорее всего, увидите фразу вроде: «License
missing» или «File /app/license not found».
5. Скопируйте лицензию (файл license) в контейнер через docker cp —
положите её в /app/license. Возможно, нужно перезапустить контейнер,
чтобы скрипт заново проверил условия.
1/3
6. Снова гляньте логи. Теперь должно появиться «All checks passed! SuperCode:
2023-FULL-PASS» (пример). Это значит, что все три условия выполнились.
7. Зайдите (при желании) внутрь контейнера (через docker exec -it tripleapp
bash), чтобы посмотреть, действительно ли файлы на месте.
8. Остановите и удалите контейнер.
9. Впишите код (например, 2023-FULL-PASS) в форму ответа.
4. Копирование license:
5. Снова логи:
Если всё три условия (ENV, config, license) соблюдены, «All checks passed!
SuperCode: ???»
6. Остановка и удаление:
Результат
Таким образом, вы применили сразу несколько приёмов:
-e SECRET_KEY=… (ENV),
-v (bind mount) или volumes,
docker cp (копирование файла license),
2/3
docker restart (чтобы логика пересканировала условия),
docker logs и docker exec для проверки.
Когда все три сигнала собрались, контейнер отдал код. Вставьте этот код в форму
ответа, подтверждая успех!
3/3
2.10 Логи
Пример: docker logs mynginx – вы увидите все записи, которые Nginx вывел с
момента запуска.
Если контейнер говорлив, лог может быть очень длинным.
Полезные флаги:
Важно: если контейнер завершился, docker logs всё равно доступен, пока вы не
удалили контейнер. После docker rm логи тоже исчезают.
1/4
2. Анализ логов: кратко о продвинутых возможностях
docker stats
Когда это нужно: если контейнер тормозит систему или падает из-за переполнения
памяти, docker stats быстро подскажет, где проблема. Для более сложного
мониторинга существуют инструменты вроде cAdvisor, Prometheus/Grafana.
2/4
5. docker events: отслеживание событий Docker
docker events
Итог
docker logs <container>: смотрим текущие или прошлые логи приложения, -
f для живого режима.
docker stats: базовые метрики ресурсов (CPU, RAM, сети, диска).
docker top: какие процессы внутри контейнера?
docker events: мониторинг событий Docker.
3/4
4/4
31 марта 2025 г.
2.11 Практика
Сценарий задания
Что сдавать?
Подсказка
Для запуска контейнера с пробросом порта можно использовать -d и -p.
Логи контейнера удобно смотреть командой docker logs, при необходимости с
флагом -f.
Чтобы узнать о потреблении ресурсов, примените docker stats.
Список процессов доступен через docker top.
Для отслеживания событий Docker запустите docker events, а затем
остановите или перезапустите контейнер.
1/1
3.1 Что такое Image?
Слои (layers)
Представьте, что у вас есть:
1/5
Почему это удобно?
Copy-on-Write
2/5
Когда вы запускаете контейнер, Docker добавляет поверх всех слоёв ещё один слой
записи (COW layer). Представьте, что все нижние слои — read-only, а любые
изменения (создание файла, удаление, изменение) фактически записываются в
этот верхний слой записи. Так достигается:
Пример
В итоге Docker хранит слои, и каждый слой хранится один раз в локальном кэше.
Если вы собрали ещё один образ, но с тем же базовым Alpine, то Docker
переиспользует старый базовый слой. При запуске контейнера ко всем слоям
добавляется верхний слой записи.
3/5
Базовые образы и официальные репозитории
FROM ubuntu:20.04
Это значит: «Возьми базовый слой с именем ubuntu:20.04 из Docker Hub (если нет
локально) и стройся поверх него». Пользуясь такими официальными образами, вы
можете быть уверены в минимальном наборе необходимых инструментов
(например, для Ubuntu — базовая система), а далее уже делаете RUN, COPY и т. д.
для вашего приложения.
4/5
1. Docker проверяет, есть ли локальный образ (image). Если нет — скачивает
слои из реестра (Docker Hub).
2. Собирает эти слои вместе (read-only) и добавляет слой записи COW.
3. Запускает ваш процесс (ENTRYPOINT/CMD) внутри сформированной
файловой системы.
Итог
Docker Image — это собрание слоёв, каждый шаг сборки формирует новый
слой.
Copy-on-Write позволяет контейнерам не портить исходные слои.
Официальные и базовые образы (ubuntu, alpine, node и др.) — фундамент,
на котором вы строите свою среду.
При docker run, Docker складывает слои и добавляет слой записи для
контейнера.
5/5
21 марта 2025 г.
3.2 Практика
1/1
3 апреля 2025 г.
3.3 Dockerfile
Если ранее мы выяснили, что Docker Image — это набор слоёв (layers), то
Dockerfile — это инструкция, как эти слои собрать. В этом уроке подробно разберём
ключевые инструкции Dockerfile (FROM, RUN, COPY/ADD, CMD, ENTRYPOINT, ENV,
EXPOSE) и поймём, как они совместно формируют ваш образ.
FROM <image>[:<tag>]
Важно: выбирайте базовый образ с умом: ubuntu:22.04 или alpine — это влияет на
размер, набор утилит, совместимость.
Синтаксис:
RUN <команда>
1/4
3. COPY и ADD — перенесём файлы в образ
3.1. COPY
Пример:
3.2. ADD
Когда мы делаем docker run yourimage, Docker ищет, какую команду внутри
контейнера нужно стартовать.
CMD
Пример:
ENTRYPOINT
Пример:
2/4
Совет: Имеет смысл использовать ENTRYPOINT для «главного» запуска, а CMD — для
«дефолтных» аргументов. Например:
EXPOSE <port>
3/4
FROM alpine:3.17
# Команда по умолчанию
CMD ["npm", "start"]
Итог
FROM — стартовый слой (базовый образ).
RUN — команды во время сборки (установка пакетов, компиляция и т. д.).
COPY / ADD — перенос файлов в образ (COPY обычно предпочтительнее).
CMD / ENTRYPOINT — как контейнер будет запускаться; CMD можно
перезаписать внешней командой, ENTRYPOINT — менее гибок, но даёт
добавление аргументов.
ENV — переменные окружения внутри образа.
EXPOSE — порт, который приложение слушает (для информации/
документации).
С этими инструкциями вы опишете, как собрать свой образ, добавите свой код и
зависимости, и определите, как запускать контейнер. В следующем уроке мы
обсудим оптимизацию, чтобы ваши образы были лёгкими и быстрыми:
объединение команд, multi-stage builds и прочие тонкости.
4/4
27 марта 2025 г.
FROM python:3.9
WORKDIR /app
COPY [Link] /app/
RUN pip install --no-cache-dir -r [Link]
COPY . /app
EXPOSE 8000
1/4
Сценарий: У вас есть приложение на [Link] ([Link], [Link] или
React/Vue/Angular), где нужно установить зависимости и запустить сервер/
собранный бандл.
FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
FROM openjdk:17-jdk
WORKDIR /app
EXPOSE 8080
CMD ["java", "-jar", "/app/[Link]"]
2/4
4. Подключение статических файлов и настройка Nginx
Сценарий: У вас есть статический сайт или фронтенд (React/Vue/Angular) после
сборки, и вы хотите отдать эти файлы через Nginx. Также может потребоваться
сконфигурировать Nginx, чтобы работать с бэкендом на другом сервисе.
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Почему удобно: Один образ с Nginx можно быстро запускать в любом окружении
(стейдж, прод), всё уже упаковано внутри.
FROM python:3.9
WORKDIR /app
COPY [Link] /app/[Link]
Пример запуска: docker run <image> --mode debug — аргумент --mode debug
будет добавлен к команде python [Link].
3/4
Сценарий: Ваше приложение нуждается в базе данных или стороннем API, и вы
хотите задать URL и режим работы при сборке, но при запуске на проде
переопределить эти переменные.
FROM node:16
WORKDIR /app
ENV APP_MODE=production
ENV API_URL=[Link]
COPY [Link] .
RUN npm install
COPY . .
EXPOSE 3000
Подведём итоги
Выбирайте базовый образ с учётом задач (Node, Python, OpenJDK, Nginx) и
версий языка/технологии.
Устанавливайте нужные пакеты через RUN, чтобы окружение было
консистентным для всех разработчиков и сред.
Копируйте и собирайте проект (npm install, pip install, gradle/mvn package)
ещё на этапе сборки образа.
Используйте ENTRYPOINT/CMD и ENV для гибкости запуска и конфигурации.
EXPOSE полезен, чтобы явно сообщать, какой порт слушает контейнер.
4/4
3.5 Продвинутые инструкции Dockerfile
Обратите внимание, что все эти инструкции относятся к сборке (build time).
То есть они влияют на образ и на то, как контейнер будет работать.
1. ARG
Сценарий: Вам нужно передать некий параметр только на этапе сборки, который не
должен утекать в контейнер как переменная окружения. Например, версию пакета
или секретный ключ для скачивания зависимостей (который потом стирается).
FROM python:3.9
ARG MY_PACKAGE_VERSION=1.2.3
RUN echo "Installing my_package version $MY_PACKAGE_VERSION"
RUN pip install my_package==$MY_PACKAGE_VERSION
Запуск сборки:
Зачем это нужно? Часто для более гибкого build-процесса, чтобы не жёстко
прописывать версии прямо в Dockerfile.
2. LABEL
Сценарий: Вы хотите добавить метаданные к образу: автора, версию приложения,
контактную информацию или ссылку на репозиторий.
1/4
FROM node:16
LABEL maintainer="prosto@[Link]" \
version="1.0.0" \
description="My [Link] Service"
При просмотре образа (например, docker image inspect <image>) эти поля будут
видны в разделе Labels.
3. USER
Сценарий: По умолчанию многие базовые образы запускаются под root. Но в
продакшене или в средах с повышенными требованиями к безопасности бывает
нужно запускать процессы от непривилегированного пользователя.
FROM alpine:3.17
WORKDIR /home/myuser
COPY app ./
CMD ["./app"]
4. VOLUME
Сценарий: Ваше приложение пишет логи или хранит данные, и вы хотите вынести
их во вне (host machine / Docker volume), чтобы не терять данные при пересоздании
контейнера.
FROM postgres:15
VOLUME /var/lib/postgresql/data
5. ONBUILD
2/4
Сценарий: Вы делаете базовый образ, который в будущем будут наследовать
другие Dockerfile с FROM your-image. И хотите, чтобы при следующей сборке (в
дочернем образе) автоматически выполнилась некая команда (например, COPY или
RUN).
FROM node:16
ONBUILD COPY package*.json /app/
ONBUILD RUN npm install
FROM your-node-base:latest
COPY . /app
CMD ["npm", "start"]
То при сборке этого дочернего образа сработают ONBUILD COPY и ONBUILD RUN,
заложенные в родительском образе.
6. STOPSIGNAL
Сценарий: При остановке контейнера Docker отправляет SIGTERM (по умолчанию)
главному процессу. Если приложение ожидает другой сигнал или вы хотите
изменить поведение — используйте STOPSIGNAL.
FROM ubuntu:22.04
STOPSIGNAL SIGQUIT
CMD ["/usr/bin/my-daemon"]
Теперь, когда вы сделаете docker stop, Docker пошлёт SIGQUIT вместо обычного
SIGTERM. Это полезно, если ваше приложение не понимает стандартный сигнал или
обрабатывает другой.
7. HEALTHCHECK
Сценарий: Ваш контейнер должен сигнализировать Docker, жив ли он. Для этого
можно настроить healthcheck — периодическую проверку состояния (например,
HTTP-запрос к эндпоинту /health).
3/4
FROM nginx:alpine
8. SHELL
Сценарий: По умолчанию Dockerfile интерпретирует RUN, CMD, ENTRYPOINT как запуск
через sh -c (в Linux-образах) или cmd /S /C (в Windows-образах). Но если вы
хотите использовать другой shell (например, powershell в Windows или bash в
Linux), можно изменить поведение с помощью SHELL.
FROM ubuntu:22.04
Подведём итоги
Теперь вы знаете о более тонких возможностях Dockerfile:
4/4
13 апреля 2025 г.
3.6 Практика
Техническое задание
1/3
9. Итог: При запуске контейнера (например, docker run -d -p 3000:3000 my-
advanced-node) ваше [Link]-приложение должно работать, а в образе будут
метаданные LABEL, объявленный VOLUME и непривилегированный
пользователь.
Структура папки
{
"name": "advanced-node-practice",
"version": "1.0.0",
"scripts": {
"start": "node [Link]"
},
"dependencies": {
"express": "^4.18.2"
}
}
[Link](3000, () => {
[Link]("Server running on port 3000");
});
2/3
FROM
ARG
LABEL
RUN
USER
VOLUME
WORKDIR
COPY
CMD
FROM node:16
ARG APP_VERSION=1.0
RUN echo "Building version $APP_VERSION"
LABEL maintainer="you@[Link]" \
version=$APP_VERSION
VOLUME /home/nodeuser/data
COPY package*.json ./
RUN npm install
COPY [Link] ./
Напишите текст
3/3
Задействуем продвинутые инструкции в Dockerfile
В этом задании вы создадите Dockerfile, в котором будут использоваться не только
базовые (FROM, RUN, COPY, CMD, ENV), но и дополнительные инструкции (ARG, LABEL,
USER, VOLUME, ONBUILD, STOPSIGNAL, HEALTHCHECK, SHELL). Мы объединим их в один
образ, чтобы на практике увидеть, как каждая инструкция влияет на процесс сборки
и работу контейнера.
1/3
Структура папки
app = Flask(__name__)
@[Link]("/")
def index():
return "Hello from Advanced Dockerfile!"
if __name__ == "__main__":
port = int([Link]("PORT", "8080"))
[Link](host="[Link]", port=port)
FROM
ARG
LABEL
RUN
USER
VOLUME
ONBUILD
STOPSIGNAL
HEALTHCHECK
SHELL
COPY
ENV (или ARG)
EXPOSE
CMD (или ENTRYPOINT)
2/3
FROM python:3.9
# Переключаемся на bash
SHELL ["/bin/bash", "-c"]
# Добавляем метаданные
LABEL maintainer="you@[Link]" \
version="1.0" \
description="Advanced Dockerfile Example"
# Объявляем volume
VOLUME /home/myapp/data
# ONBUILD инструкция
ONBUILD COPY . /home/myapp/extra
# STOPSIGNAL
STOPSIGNAL SIGQUIT
# Копируем приложение
COPY [Link] /home/myapp/
# Устанавливаем зависимости
RUN pip install flask
# EXPOSE
EXPOSE 8080
3/3
2 апреля 2025 г.
3.6 Практика
Зачем?
Структура папки
1/3
Dockerfile — вы создаёте по требованиям выше.
[Link] — простой HTML:
<!DOCTYPE html>
<html>
<head><title>Nginx Advanced Demo</title></head>
<body>
<h1>Hello from Nginx with custom SHELL & StopSignal!</h1>
</body>
</html>
FROM
RUN
SHELL
STOPSIGNAL
HEALTHCHECK
COPY
CMD
FROM nginx:alpine
# Задаём StopSignal
STOPSIGNAL SIGQUIT
2/3
3/3
6 марта 2025 г.
3.6 Практика
Структура папки
Dockerfile
1/3
[Link] (простой скрипт, что-то вроде):
#!/bin/sh
echo "Running with $TEXT_TOOL on $1"
$TEXT_TOOL "ERROR" $1
FROM
ARG
RUN
USER
WORKDIR
COPY
VOLUME
CMD
2/3
FROM alpine:3.17
ARG TEXT_TOOL=grep
RUN apk add --no-cache $TEXT_TOOL
WORKDIR /home/myuser/app
COPY [Link] .
RUN chmod +x [Link]
VOLUME /data
3/3
3.6 Практика
FROM my-java-base:latest
COPY . .
RUN mvn clean package
CMD ["java", "-jar", "/app/[Link]"]
Dockerfile — родительский.
1/2
Дополнительно ничего не нужно. Вы просто собираете этот образ командой:
FROM my-java-base:latest
# Ваши шаги: например, COPY кода, mvn package, CMD и т. д.
FROM
LABEL
ENV
ONBUILD
CMD
FROM openjdk:17-jdk
LABEL maintainer="you@[Link]" \
version="1.0" \
description="Base Spring Boot Dockerfile with ONBUILD"
ENV SPRING_PROFILES_ACTIVE=default
ENV SERVER_PORT=8080
2/2
Создаём Python-приложение с HEALTHCHECK, USER и
VOLUME
В этом задании вы настроите Dockerfile для Python-приложения (Flask / FastAPI /
любой другой фреймворк), где продемонстрируете использование нескольких
продвинутых инструкций:
Сценарий
1/3
9. Итог:
При запуске контейнера (например, -p 5000:5000), открываете
[Link] — видите вашу главную страницу, а
[Link] возвращает 200 OK для HealthCheck.
docker ps показывает статус healthy или unhealthy.
В /data/logs ваши логи живут независимо от пересоздания контейнера,
если вы смонтируете том.
Структура папки
Flask==2.2.3
app = Flask(__name__)
@[Link]("/")
def index():
return "Hello from a health-checked Flask!"
@[Link]("/health")
def health():
return "OK", 200
if __name__ == "__main__":
# Логи можно писать в /data/logs, если хотите
port = 5000
[Link](host="[Link]", port=port)
2/3
FROM
RUN
USER
HEALTHCHECK
VOLUME
COPY
EXPOSE
CMD
FROM python:3.9
WORKDIR /home/webuser/app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY [Link] .
EXPOSE 5000
VOLUME /data/logs
USER webuser
3/3
27 марта 2025 г.
Сценарий
Допустим, у вас есть Rust-приложение (файл [Link]), которое при запуске выводит
информацию и обрабатывает входящие аргументы. При остановке контейнера вы
хотите отправлять не стандартный сигнал SIGTERM, а SIGINT, чтобы Rust-
приложение корректно завершалось.
Также вы хотите иметь ARG BUILD_MODE (например, debug или release), чтобы
определять, как компилировать приложение. Плюс вы любите пользоваться bash
вместо sh, поэтому настройте это через SHELL.
1/3
8. Итог:
При запуске контейнера (например, docker run --rm my-rust-cli) ваш
CLI-приложение будет работать. Если вы сделаете docker stop, Docker
пошлёт SIGINT, и приложение может корректно завершиться.
Если захотите собрать в релизном режиме:
Структура папки
[package]
name = "mycli"
version = "0.1.0"
edition = "2021"
[dependencies]
fn main() {
println!("Hello from Rust CLI!");
let args: Vec<String> = std::env::args().collect();
if [Link]() > 1 {
println!("Got arguments: {:?}", &args[1..]);
}
// Here you might simulate a loop or do some work until SIGINT is
received
}
3. Проверить запуск:
2/3
FROM
SHELL
ARG
RUN
COPY
LABEL
STOPSIGNAL
CMD
FROM rust:1.68
# Переключаемся на bash
SHELL ["/bin/bash", "-c"]
ARG BUILD_MODE=debug
STOPSIGNAL SIGINT
WORKDIR /app
COPY [Link] [Link] ./
COPY [Link] src/
# Выбираем CMD
# Если релиз, у нас /app/target/release/mycli
# Иначе debug
CMD if [ "$BUILD_MODE" = "release" ]; then \
/app/target/release/mycli ; \
else \
/app/target/debug/mycli ; \
fi
3/3
3.7 Best Practices
В Dockerfile каждая инструкция (FROM, RUN, COPY и т. д.) порождает новый слой.
Docker кэширует эти слои, чтобы не пересобирать всё при каждом изменении
файлов. Правильное расположение (упорядочение) инструкций может существенно
ускорить сборку и уменьшить итоговый размер образа. Ниже — основные приёмы и
советы.
# Плохая практика:
COPY . /app
RUN npm install
Здесь, если вы меняете любой файл в проекте, при сборке Docker увидит, что
инструкция COPY . /app изменилась, и заново запустит RUN npm install. Это
теряет кэш.
# Хорошая практика:
COPY package*.json /app
RUN npm install
COPY . /app
1/5
Каждая RUN создаёт слой. Чем больше слоёв, тем больше размер образа (иногда
незначительно, но всё же). Часто объединяют несколько команд в один RUN:
# Вместо:
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Делают так:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Теперь это всё — один слой, и кэш Docker проверяет результат целиком.
Если что-то поменялось в этих командах, пересобирается только один слой, а
не несколько.
FROM node:14-alpine
WORKDIR /app
# (3) настройки
ENV NODE_ENV=production
2/5
Если вы сделаете rm -rf в отдельном RUN, то «мусор» уже запечатается в
предыдущем слое и останется в образе.
6. Multi-stage builds
Ещё один приём: multi-stage build. Вы можете сначала собрать (скомпилировать,
сделать npm install) в одном этапе, а потом скопировать результат в финальный
образ. Это помогает убрать компиляторы и лишние зависимости.
FROM alpine:3.17
COPY --from=build /app/myapp /usr/local/bin/myapp
CMD ["myapp"]
7. Используйте .dockerignore
Если в корне проекта лежит большой объём ненужных для сборки файлов (логов,
документации, temp-файлов) — их желательно исключить из контекста сборки. Для
этого создайте файл .dockerignore и перечислите там всё, что не нужно копировать
в образ:
# .dockerignore
.git
node_modules
logs
*.log
*.tmp
3/5
# Вместо:
FROM ubuntu:latest
# Можно:
FROM alpine:3.17
FROM node:16-alpine
WORKDIR /app
COPY . .
Таким образом вы снижаете риски, если кто-то получит доступ к контейнеру или
найдёт уязвимость в приложении.
4/5
Итог
Правильная структура Dockerfile и набор бестпрактис позволяет максимально
использовать кэш Docker, избегать лишних слоёв и временных файлов, защищать
образ от уязвимостей и сохранять секреты. В результате вы получаете более
быстрые сборки, меньшие по размеру образы и повышенную безопасность. Эти
приёмы особенно важны в CI/CD, где контейнеры пересобираются часто.
5/5
24 марта 2025 г.
3.8 Практика
Сломанный Dockerfile
FROM python:3.9-sli
WORKDR /app
COPY . /ap
EXPOSE 80
Контекст
[Link]
1/2
from flask import Flask
import os
app = Flask(__name__)
@[Link]("/")
def index():
# Считываем значение переменной окружения, заданной в Dockerfile
message = [Link]("HELLO_MSG", "No message found")
return f"App says: {message}"
if __name__ == "__main__":
# По умолчанию Flask слушает порт 5000
# Если хотите слушать 80, измените порт либо используйте gunicorn
[Link](host="[Link]", port=5000)
[Link]
Flask==2.2.3
Что сдаем
FROM python:3.9-slim
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
ENV HELLO_MSG=Hello_from_Docker
EXPOSE 80
CMD ["python","[Link]"]
2/2
9 апреля 2025 г.
3.8 Практика
Сломанный Dockerfile
FROM node:14-silm
WORKDR /src
COPY . /src
EXPOSE 3000
CMD ["npm","[Link]"]
Контекст
[Link]
1/2
const express = require("express");
const app = express();
[Link](port, () => {
[Link]("App is running on port " + port);
});
[Link]
{
"name": "node-docker-demo",
"version": "1.0.0",
"description": "A simple [Link] app for Docker demo",
"main": "[Link]",
"scripts": {
"start": "node [Link]"
},
"dependencies": {
"express": "^4.18.0"
}
}
Что сдаем
FROM node:14-slim
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node","[Link]"]
2/2
3.8 Практика
Сломанный Dockerfile
FROM golang:1.19-lin
RUN cd /app
COPY [Link] /go/src/
EXPOSE 80
Контекст
1/2
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := [Link]("PORT")
if port == "" {
port = "8080"
}
[Link]("/", handler)
[Link]("Starting server on port %s...", port)
[Link]([Link](":"+port, nil))
}
Что сдаем
# FROM golang:1.19-alpine
# ENV PORT=8080
# WORKDIR /app
# COPY [Link] [Link] ./
# RUN go mod download
# COPY . .
# EXPOSE 8080
# CMD ["go", "run", "[Link]"]
2/2
3.8 Практика
Сломанный Dockerfile
WORKDIR /usr/src/app
COPY . .
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Контекст
1/2
Для тестирования можете взять базовый React-проект, созданный командой npx
create-react-app my-app, а затем положить исходники в директорию с Dockerfile.
Что сдаем
# Первый этап:
# FROM node:16 as build-stage
# WORKDIR /usr/src/app
# COPY package*.json ./
# RUN npm install
# COPY . .
# RUN npm run build
# Второй этап:
# FROM nginx:alpine
# COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html
# EXPOSE 80
# CMD ["nginx","-g","daemon off;"]
2/2
3.9 Multi-stage
Пример: Go
# СТАДИЯ 1: сборка
FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
1/7
Более продвинутый пример Go: использование scratch и статическая
компиляция
В Node-проектах, при production build (React, Vue, Angular) можно собрать статику
в одной стадии, а раздавать её через Nginx в другой.
Первая стадия (build): node + npm install + npm run build => создаёт финальный
dist.
Вторая: minimal Nginx-образ. Копируем результат /app/dist прямо в
/usr/share/nginx/html.
Финальный образ в итоге не содержит Node или npm — только файлы статики
и Nginx.
2/7
Для улучшения производительности сборки и более эффективного использования
кэша Docker можно разделить установку зависимостей и сборку кода:
3/7
# СТАДИЯ 1: сборка с Maven и полным JDK
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
# Копируем только [Link] для кэширования зависимостей
COPY [Link] .
RUN mvn dependency:go-offline
Также можно использовать индекс стадии вместо имени (не рекомендуется, так как
это менее наглядно):
# СТАДИЯ 1
FROM alpine:3.17
# Копирование из стадии 0 по индексу
COPY --from=0 /app/myapp /usr/local/bin/myapp
4/7
# Глобальный ARG (перед первым FROM)
ARG VERSION=latest
# СТАДИЯ 1
FROM golang:1.18-alpine AS builder
# Нужно снова объявить ARG внутри стадии
ARG VERSION
RUN echo "Building version $VERSION"
# ...
# СТАДИЯ 2
FROM alpine:3.17
# И здесь снова объявляем, если нужно использовать
ARG VERSION
RUN echo "Version: $VERSION" > /[Link]
# ...
Важно: каждый FROM создаёт новый контекст, поэтому аргумент нужно объявлять
заново в каждой стадии
Если у вас есть несколько стадий, вы можете указать, какую именно нужно собрать,
с помощью флага --target:
Это полезно при создании образов для разработки, тестирования или отладки.
Часто при сборке нужны секреты (токены доступа к приватным репозиториям и т.д.),
но их нельзя хранить в образе. Multi-stage builds помогают решить эту проблему:
5/7
# СТАДИЯ 1: используем секреты
FROM node:16-alpine AS builder
WORKDIR /app
COPY . .
# Используем секрет для доступа к приватному репозиторию
RUN --mount=type=secret,id=npm_token \
cat /run/secrets/npm_token > .npmrc && \
npm install && \
rm .npmrc # Удаляем файл с секретом
Общие рекомендации
6/7
2. Запустите интерактивную оболочку для исследования: docker run -it debug-
image sh
3. Проверьте размер каждого слоя: docker history myapp:latest
4. Используйте docker image ls для просмотра всех образов, включая
промежуточные
Итог
Multi-stage builds — мощный способ сократить размер образа и отделить этап
разработки/сборки от этапа запуска. Особенно полезно для языков с компиляцией
(Go, C++, Rust) или больших JS-фреймворков (React/Vue) где сборка статических
файлов происходит в одном этапе, а раздача — в другом. Рекомендуется
использовать для «production» образов, где важно, чтобы в финале остался только
«runtime».
7/7
2 апреля 2025 г.
3.10 Практика
Техническое задание
1. Стадия 1 (сборка):
Базовый образ — node:16-alpine
Рабочая директория — /app
Скопировать сначала только [Link] и [Link] для
оптимизации кэширования
Установить зависимости через npm ci
Скопировать остальные файлы проекта
Запустить сборку через npm run build
Назвать эту стадию build
2. Стадия 2 (production):
Базовый образ — nginx:1.21-alpine
Скопировать готовую сборку из первой стадии (/app/build) в директорию
Nginx (/usr/share/nginx/html)
Скопировать кастомный конфиг Nginx (опционально)
Открыть порт 80
Структура проекта
1/3
[Link] — базовый файл конфигурации React-приложения:
{
"name": "react-docker-example",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --passWithNoTests",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
server {
listen 80;
location / {
root /usr/share/nginx/html;
index [Link] [Link];
try_files $uri $uri/ /[Link];
}
}
2/3
3. Дополнительно: проверить запуск только до стадии тестирования:
# Стадия сборки
FROM node:16-alpine AS build
WORKDIR /app
...
# Стадия production
FROM nginx:1.21-alpine
...
# Стадия сборки
FROM node:16-alpine AS build
WORKDIR /app
# Запускаем сборку
RUN npm run build
3/3
9 апреля 2025 г.
3.10 Практика
Техническое задание
1. Стадия 1 (сборка):
Базовый образ — golang:1.18-alpine
Рабочая директория — /app
Скопировать файлы [Link] и [Link] (если есть) для кэширования
зависимостей
Выполнить go mod download для загрузки зависимостей
Скопировать остальные файлы проекта
Собрать статический бинарник без CGO с помощью флагов:
CGO_ENABLED=0
GOOS=linux
go build -a -ldflags '-extldflags "-static"'
Назвать эту стадию builder
2. Стадия 2 (production):
Базовый образ — scratch (пустой контейнер)
Скопировать SSL-сертификаты из builder-стадии для поддержки HTTPS
Скопировать собранный бинарный файл из builder-стадии
Открыть порт 8080
Настроить точку входа (ENTRYPOINT) для запуска вашего API
3. Бонусное задание: Добавить дополнительную стадию для проверки
безопасности:
Стадия после сборки, но перед финальной стадией
Использовать инструмент gosec для проверки кода на уязвимости
Назвать эту стадию security-check
Структура проекта
1/4
[Link] — файл зависимостей:
module api-service
go 1.18
require (
[Link]/gin-gonic/gin v1.8.1
[Link]/stretchr/testify v1.8.0
)
package main
import (
"[Link]/gin-gonic/gin"
"net/http"
"os"
)
func main() {
r := [Link]()
port := [Link]("PORT")
if port == "" {
port = "8080"
}
[Link](":" + port)
}
2/4
2. Проверить сборку и запуск:
curl [Link]
curl [Link]
Вы должны увидеть, что финальный образ имеет размер всего несколько мегабайт,
в отличие от сотен мегабайт стандартного golang-образа.
3/4
# Стадия сборки
FROM golang:1.18-alpine AS builder
WORKDIR /app
4/4
Создаем базовый Multi-stage Dockerfile для Spring Boot
приложения
В этом задании вы создадите простой Multi-stage Dockerfile для Java Spring Boot
приложения с двумя стадиями: сборка и запуск.
Техническое задание
1. Стадия 1 (сборка):
Базовый образ — maven:3.8-openjdk-17
Рабочая директория — /build
Скопировать [Link] для кэширования зависимостей Maven
Запустить mvn dependency:go-offline для загрузки зависимостей
Скопировать исходный код приложения
Собрать проект с помощью mvn package -DskipTests
Назвать эту стадию builder
2. Стадия 2 (запуск):
Базовый образ — eclipse-temurin:17-jre-alpine (только JRE без
полного JDK)
Создать рабочую директорию /app
Скопировать собранный JAR-файл из первой стадии
Открыть порт 8080
Запустить JAR-файл с помощью java -jar
Структура проекта
Предположим, что у вас есть стандартный Spring Boot проект со следующей
структурой:
1/5
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.6</version>
</parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<[Link]>17</[Link]>
</properties>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2/5
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@SpringBootApplication
@RestController
public class DemoApplication {
@GetMapping("/")
public String hello() {
return "Hello from Spring Boot!";
}
@GetMapping("/info")
public String info() {
return "Spring Boot application built with multi-stage Docker";
}
}
Образ на базе только JRE должен быть примерно на 300-400 МБ меньше, чем образ
с полным JDK и Maven.
3/5
# Стадия сборки
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /build
# Копирование и сборка...
# Стадия запуска
FROM eclipse-temurin:17-jre-alpine
# Копирование JAR и запуск...
# Стадия сборки
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /build
# Собираем проект
RUN mvn package -DskipTests
# Стадия запуска
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Открываем порт
EXPOSE 8080
# Запускаем приложение
ENTRYPOINT ["java", "-jar", "[Link]"]
4/5
Повышение безопасности за счет минимизации компонентов в runtime-образе
Ускорение сборки благодаря кэшированию зависимостей Maven
Образ содержит только необходимый рантайм (JRE) без инструментов
разработки
Дополнительные задачи
5/5
Создаем оптимизированный Multi-stage Dockerfile для Python
Flask приложения
В этом задании вы разработаете эффективный Multi-stage Dockerfile для Python
Flask приложения, который разделит процесс разработки, тестирования и
эксплуатации.
Техническое задание
1. Стадия 1 (base):
Базовый образ — python:3.10-slim
Рабочая директория — /app
Установить только базовые зависимости, необходимые для всех стадий
Назвать эту стадию base
2. Стадия 2 (builder):
Базироваться на стадии base
Установить дополнительные пакеты, необходимые для компиляции
(build-essential, python3-dev)
Скопировать [Link]
Преобразовать зависимости в wheel-пакеты с помощью pip wheel
Назвать эту стадию builder
3. Стадия 3 (development):
Базироваться на стадии base
Установить как основные, так и dev-зависимости из wheel-пакетов
Включить дополнительные инструменты для разработки (pytest, black)
Настроить переменную среды FLASK_ENV=development
Назвать эту стадию development
4. Стадия 4 (production):
Базироваться на стадии base
Установить только основные зависимости из wheel-пакетов, без dev-
инструментов
Создать непривилегированного пользователя flask и назначить
правильные разрешения
Скопировать исходный код приложения
Настроить переменные среды для production (FLASK_ENV=production,
GUNICORN_WORKERS=4)
Открыть порт 5000
Запускать приложение с помощью gunicorn
Структура проекта
1/5
[Link] — зависимости проекта:
flask==2.2.3
werkzeug==2.2.3
gunicorn==20.1.0
SQLAlchemy==2.0.7
psycopg2-binary==2.9.5
marshmallow==3.19.0
-r [Link]
pytest==7.3.1
black==23.3.0
flake8==6.0.0
isort==5.12.0
app = Flask(__name__)
@[Link]('/api/status')
def status():
return jsonify({
'status': 'ok',
'service': 'flask-demo',
'environment': [Link]('ENV', 'production')
})
@[Link]('/api/data')
def data():
return jsonify({
'message': 'This is a multi-stage Flask application',
'items': [
{'id': 1, 'name': 'Item 1'},
{'id': 2, 'name': 'Item 2'},
{'id': 3, 'name': 'Item 3'}
]
})
if __name__ == '__main__':
[Link](host='[Link]', debug=True)
#!/bin/bash
set -e
WORKERS=${GUNICORN_WORKERS:-4}
PORT=${PORT:-5000}
2/5
Что нужно сдать?
1. Dockerfile с правильно настроенными multi-stage инструкциями.
2. Проверить сборку и запуск для разработки:
3/5
# Базовая стадия с общими зависимостями
FROM python:3.10-slim AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
COPY requirements*.txt ./
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r [Link] -r
[Link]
ENV FLASK_ENV=development \
FLASK_DEBUG=1
VOLUME /app
EXPOSE 5000
CMD ["python", "[Link]"]
COPY [Link] ./
COPY [Link] ./
RUN chmod +x [Link]
ENV FLASK_ENV=production \
GUNICORN_WORKERS=4
USER flask
EXPOSE 5000
ENTRYPOINT ["./[Link]"]
4/5
Дополнительные задачи для продвинутого уровня
1. Добавьте стадию test, которая запускает автоматические тесты перед
созданием production-образа
2. Настройте систему health-checks для Docker с использованием инструкции
HEALTHCHECK
3. Оптимизируйте размер слоев, объединив связанные команды RUN
4. Реализуйте передачу номера версии приложения через ARG и отображение её
в API
5/5
Оптимизируем сборку C++ приложения с помощью Multi-stage
Dockerfile
В этом задании вы создадите Multi-stage Dockerfile для сборки и запуска простого
C++ приложения.
Техническое задание
1. Стадия 1 (сборка):
Базовый образ — gcc:11.2
Установить необходимые инструменты для сборки: cmake, make,
библиотеки
Скопировать исходный код и [Link]
Создать директорию для сборки и перейти в неё
Запустить cmake и make для сборки приложения
Назвать эту стадию builder
2. Стадия 2 (runtime):
Базовый образ — debian:bullseye-slim (минимальный образ)
Установить только необходимые библиотеки для запуска (runtime
dependencies)
Скопировать исполняемый файл из стадии сборки
Настроить команду запуска приложения
Структура проекта
У вас есть простое C++ приложение со следующей структурой:
Содержимое [Link]:
1/4
#include <iostream>
#include <vector>
#include <string>
#include <ctime>
int main() {
std::cout << "=== C++ Application ===" << std::endl;
Содержимое [Link]:
cmake_minimum_required(VERSION 3.10)
project(cpp_docker_demo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(cpp_app src/[Link])
2/4
Для сравнения, соберите также версию с одной стадией:
# Стадия сборки
FROM gcc:11.2 AS builder
# Установка инструментов, сборка...
# Стадия runtime
FROM debian:bullseye-slim
# Установка только необходимых библиотек, копирование исполняемого файла...
# В стадии сборки
WORKDIR /app
COPY . .
RUN mkdir -p build && cd build && \
cmake .. && \
make
# В стадии runtime
COPY --from=builder /app/build/cpp_app /usr/local/bin/
3/4
# Стадия сборки
FROM gcc:11.2 AS builder
# Сборка приложения
RUN mkdir -p build && cd build && \
cmake .. && \
make
# Стадия runtime
FROM debian:bullseye-slim
# Запуск приложения
CMD ["/usr/local/bin/cpp_app"]
4/4
Оптимизация Docker-образа для Ruby on Rails приложения с
multi-stage сборкой
В этом задании вы создадите эффективный Multi-stage Dockerfile для Rails-
приложения, который отделит этап сборки зависимостей и ассетов от этапа запуска.
Техническое задание
1. Стадия 1 (builder):
Базовый образ — ruby:3.1
Установить необходимые системные зависимости (nodejs, yarn, build-
essential)
Установить гемы Rails через Bundler
Скомпилировать ассеты с помощью rails assets:precompile
Очистить временные файлы после компиляции ассетов
Назвать эту стадию builder
2. Стадия 2 (runtime):
Базовый образ — ruby:3.1-slim (меньший размер за счёт отсутствия
компиляторов)
Установить минимальные необходимые системные зависимости
Скопировать установленные гемы из стадии builder
Скопировать код приложения и скомпилированные ассеты из builder
Создать непривилегированного пользователя для запуска приложения
Открыть порт 3000
Настроить запуск Rails-приложения в production-режиме
Структура проекта
1/5
source '[Link]
git_source(:github) { |repo| "[Link] }
ruby '3.1.2'
group :development do
gem 'web-console'
end
group :test do
gem 'capybara'
gem 'selenium-webdriver'
end
Пример [Link]:
{
"name": "rails_app",
"private": true,
"dependencies": {
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.2.0",
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.2.2",
"esbuild": "^0.15.12",
"sass": "^1.55.0"
},
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --
outdir=app/assets/builds --public-path=assets",
"build:css": "sass
./app/assets/stylesheets/[Link]:./app/assets/builds/applicatio
[Link] --no-source-map --load-path=node_modules"
}
}
2/5
2. Проверить сборку и запуск:
# Стадия сборки
FROM ruby:3.1 AS builder
# Установка системных зависимостей...
# Установка гемов...
# Компиляция ассетов...
# Стадия запуска
FROM ruby:3.1-slim
# Установка минимальных зависимостей...
# Копирование установленных гемов и приложения...
# В стадии builder
ENV BUNDLE_PATH=/usr/local/bundle
COPY Gemfile [Link] ./
RUN bundle install --jobs=4 --without development test
# В стадии runtime
COPY --from=builder /usr/local/bundle /usr/local/bundle
3/5
# Стадия сборки
FROM ruby:3.1 AS builder
# Установка гемов
ENV BUNDLE_PATH=/usr/local/bundle
COPY Gemfile [Link] ./
RUN bundle config set --local without 'development test' && \
bundle install --jobs=4
# Стадия запуска
FROM ruby:3.1-slim
# Переменные среды
ENV RAILS_ENV=production \
RAILS_SERVE_STATIC_FILES=true \
RAILS_LOG_TO_STDOUT=true
# Открытие порта
EXPOSE 3000
4/5
# Запуск приложения
CMD ["bundle", "exec", "rails", "server", "-b", "[Link]"]
Дополнительные задачи
1. Добавить многоступенчатый кэш для зависимостей:
Отдельное копирование Gemfile и [Link]
Разделение установки гемов и Node-пакетов
2. Добавить аргументы сборки для настройки:
Версии Ruby
Окружения Rails (development/production)
3. Добавить механизм ожидания запуска базы данных перед запуском
приложения:
Создать entrypoint-скрипт, который проверяет доступность базы данных
Запускать приложение только после готовности базы данных
5/5
Создаем Multi-stage Dockerfile для веб-приложения
В этом задании вы создадите Multi-stage Dockerfile с тремя стадиями для полного
цикла разработки, тестирования и развертывания веб-приложения.
Техническое задание
1. Стадия 1 (deps):
Базовый образ — node:16-alpine
Рабочая директория — /app
Скопировать только файлы зависимостей ([Link] и package-
[Link])
Установить все зависимости через npm ci
Назвать эту стадию deps
2. Стадия 2 (test):
Базироваться на стадии deps
Скопировать исходный код приложения
Запустить линтинг и тесты (команды npm run lint и npm run test)
Назвать эту стадию test
3. Стадия 3 (production):
Снова начать с базового образа node:16-alpine
Создать непривилегированного пользователя nodeuser
Скопировать установленные зависимости из стадии deps (только
production зависимости)
Скопировать исходный код приложения
Переключиться на непривилегированного пользователя
Открыть порт 3000
Запустить приложение через npm start
Структура проекта
Dockerfile — файл, который вы создадите
1/5
[Link] — файл конфигурации [Link] приложения:
{
"name": "three-stage-demo",
"version": "1.0.0",
"description": "Demo for multi-stage build",
"main": "[Link]",
"scripts": {
"start": "node [Link]",
"lint": "eslint .",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"eslint": "^8.28.0",
"jest": "^29.3.1"
}
}
[Link](PORT, () => {
[Link](`Server running on port ${PORT}`);
});
2/5
[Link] — простой тест для приложения:
{
"env": {
"node": true,
"jest": true
},
"extends": "eslint:recommended",
"rules": {
"no-console": "off"
}
}
3/5
Стадия deps: Оптимизирует кэширование зависимостей. При изменении
только кода приложения (без изменения зависимостей) Docker переиспользует
кэш этой стадии.
Стадия test: Гарантирует, что код проходит тесты и соответствует стандартам,
прежде чем попадет в production-образ.
Стадия production: Создает минимальный образ без dev-зависимостей и
инструментов тестирования/линтинга.
# Стадия зависимостей
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Стадия тестирования
FROM deps AS test
COPY . .
RUN npm run lint && npm run test
# Стадия production
FROM node:16-alpine
# ...инструкции для production...
# В production стадии
WORKDIR /app
COPY package*.json ./
COPY --from=deps /app/node_modules /app/node_modules
# Удаляем dev-зависимости, оставляя только production
RUN npm prune --production
4/5
# Стадия зависимостей
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Стадия тестирования
FROM deps AS test
COPY . .
RUN npm run lint && npm run test
# Стадия production
FROM node:16-alpine
# Создание непривилегированного пользователя
RUN addgroup -g 1001 -S nodeuser && \
adduser -S -u 1001 -G nodeuser nodeuser
WORKDIR /app
# Установка только production зависимостей
COPY package*.json ./
COPY --from=deps /app/node_modules /app/node_modules
RUN npm prune --production
# Назначение пользователя
USER nodeuser
# Переменные среды
ENV NODE_ENV=production \
PORT=3000
# Открытие порта
EXPOSE 3000
# Запуск приложения
CMD ["npm", "start"]
5/5
Создаем Dockerfile для машинного обучения
В этом задании вы разработаете Multi-stage Dockerfile с тремя стадиями для
полного цикла создания и развертывания модели машинного обучения.
Техническое задание
1. Стадия 1 (data-prep):
Базовый образ — python:3.9-slim
Установка необходимых библиотек для обработки данных (pandas, scikit-
learn)
Копирование скрипта предобработки данных
Предварительная обработка набора данных и сохранение в формате для
обучения
Назвать эту стадию data-prep
2. Стадия 2 (model-training):
Базовый образ — python:3.9 (полная версия Python с инструментами
сборки)
Установка библиотек машинного обучения (scikit-learn, joblib)
Копирование скрипта обучения модели
Копирование подготовленных данных из первой стадии
Обучение модели и сохранение в файл
Оценка метрик качества обучения
Назвать эту стадию model-training
3. Стадия 3 (model-serving):
Базовый образ — python:3.9-slim
Установка минимальных библиотек для API (Flask, joblib, scikit-learn)
Копирование обученной модели из второй стадии
Копирование скрипта веб-сервера для API
Открытие порта 5000
Запуск Flask API сервера для получения предсказаний модели
Структура проекта
1/9
Скачать датасет можно по ссылке - [Link]
Что внутри?
sepal_length,sepal_width,petal_length,petal_width,species
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
...
7.0,3.2,4.7,1.4,versicolor
...
6.3,3.3,6.0,2.5,virginica
...
2/9
import pandas as pd
from sklearn.model_selection import train_test_split
from [Link] import StandardScaler, LabelEncoder
import os
import joblib
# Загружаем данные
df = pd.read_csv('/data/raw/[Link]')
print(f"Loaded dataset with {[Link][0]} rows and {[Link][1]} columns")
# Сохраняем энкодер
[Link](le, '/data/processed/label_encoder.joblib')
print("Saved label encoder")
# Масштабируем признаки
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Сохраняем скейлер
[Link](scaler, '/data/processed/[Link]')
print("Saved scaler")
3/9
from [Link] import RandomForestClassifier
from [Link] import accuracy_score, classification_report
import joblib
import os
# Оцениваем модель
y_pred = [Link](X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model accuracy: {accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(y_test, y_pred))
# Сохраняем модель
[Link](model, '/model/iris_model.joblib')
print("Model saved to /model/iris_model.joblib")
4/9
from flask import Flask, request, jsonify
import joblib
import numpy as np
import os
app = Flask(__name__)
@[Link]('/predict', methods=['POST'])
def predict():
# Получаем данные из запроса
data = [Link]
# Масштабируем данные
features_scaled = [Link](features)
# Получаем предсказание
prediction = [Link](features_scaled)
# Декодируем результат
species = encoder.inverse_transform(prediction)[0]
# Возвращаем результат
return jsonify({
"prediction": species,
"prediction_code": int(prediction[0]),
"probability": float([Link](model.predict_proba(features_scaled)))
})
@[Link]('/health', methods=['GET'])
def health():
return jsonify({"status": "ok"})
if __name__ == '__main__':
[Link](host='[Link]', port=5000)
5/9
1. Dockerfile с тремя стадиями (data-prep, model-training, model-serving).
2. Проверить сборку и запуск:
Дополнительные преимущества:
6/9
# Создайте отдельные директории для каждого этапа
WORKDIR /app
# В стадии 2:
COPY --from=data-prep /data/processed /data/processed
VOLUME /model
# В стадии 3:
COPY --from=model-training /model /model
COPY --from=data-prep /data/processed /data/processed
7/9
# Стадия 1: Предобработка данных
FROM python:3.9-slim AS data-prep
WORKDIR /app
# Запускаем предобработку
RUN python [Link]
WORKDIR /app
# Обучаем модель
RUN python [Link]
WORKDIR /app
# Открываем порт
EXPOSE 5000
8/9
Реальное применение в индустрии
Такой подход особенно полезен для ML-проектов, потому что:
9/9
3.11 Типовые ошибки
# Пример .dockerignore:
.git
node_modules
__pycache__
*.env
1/4
Каждая команда = новый слой. Избыточно, плюс в результате остаются временные
файлы.
Таким образом создаётся только один слой, и кэш Docker проверяет его разом.
COPY . /app
RUN npm install
Решение:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
2/4
Проблема: Копируете .env или [Link] с паролями / API ключами прямо в
образ — могут утечь важные данные.
Решение:
Проблема: Сначала COPY . ., а потом RUN npm install. Любое изменение кода
ломает кэш для npm install.
Решение:
COPY package*.json ./
RUN npm install
COPY . .
FROM alpine:3.17
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]
3/4
Второй этап получает лишь бинарник, всё dev мусор остаётся в первом.
Вывод
4/4
2 апреля 2025 г.
1. Bridge Network
Bridge — это наиболее часто используемый и дефолтный тип сети при запуске
контейнера без особых флагов. Ваш контейнер получает виртуальный Ethernet-
интерфейс и IP-адрес внутри этой сети. Docker настраивает NAT, чтобы вы могли
публиковать порты наружу.
Плюсы:
Минусы:
2. Host Network
1/4
Host network означает, что контейнер разделяет сетевой стек хостовой машины, без
отдельного виртуального интерфейса. Проще говоря, никаких iptables/NAT для
контейнера — всё напрямую на хосте.
docker run --network=host ... (на Linux) — тогда контейнер слушает тот же
интерфейс, что и хост. Если внутри контейнера приложение слушает 80 порт,
то это будет 80 порт самого хоста (без -p).
В Windows/macOS режим host network практически не работает в том же
смысле (ограничения виртуализации), так что эта опция по-настоящему
полезна на Linux.
Плюсы:
Минусы:
2/4
3. None Network
None network означает вообще никакой сети (кроме lo внутри контейнера).
Контейнер не подключается ни к какой внешней сети, не получает IP.
Плюсы:
Минусы:
4. Overlay Network
Overlay используется для контейнеров, раскиданных по разным хостам, например в
Docker Swarm или Kubernetes (там свои аналоги overlay). Docker Swarm, например,
создает VXLAN-туннели между хостами, чтобы контейнеры в одной overlay-сети
видели друг друга по IP, даже если физически они на разных узлах.
Плюсы:
Минусы:
3/4
На хосте создаётся виртуальный интерфейс (docker0).
Каждый контейнер получает внутренний IP из пула (172.17.x.x или 192.168.x.x
— в зависимости от сети).
При -p 80:80, Docker прописывает iptables-правило, перенаправляющее
трафик с хоста:80 на IP/порт контейнера.
Вывод
4/4
4.2 Практика
Сценарий задания
1. Создайте пользовательскую сеть (bridge). Назовите ее, например, mynet.
2. Запустите два контейнера в этой сети (любой образ, который позволяет что-то
запустить, например alpine или nginx на ваше усмотрение).
3. Проверьте, что один контейнер видит другой по DNS-имени (то есть сможет
пингануть второй по его имени). Например, если один контейнер называется
app1, а другой app2, то app1 должен обращаться к app2.
Итог: Если все сделано правильно, один контейнер сможет обращаться к другому
по имени, благодаря встроенному DNS Docker в user-defined bridge-сети.
1/1
4.2 Практика
Сценарий задания
1. Важно: Данный вариант сети полноценно работает только на Linux (на
Windows/Mac он ведет себя иначе).
2. Запустите любой контейнер (например, nginx или httpd), используя режим --
network=host. То есть:
Без -p флагов, так как порты контейнера сразу «совпадают» с портами
хоста.
3. Убедитесь, что если контейнер слушает внутри на 80 порту, то на хосте также
занят порт 80. Можете проверить, нет ли конфликта с другими сервисами
(например, Apache, Nginx), которые слушают 80 порт на хосте.
4. По желанию, попробуйте зайти в браузере на [Link] и увидеть
страницу, отдаваемую контейнером (если ничего не мешает). Либо вы можете
проверить с помощью curl [Link] или curl [Link].
Итог: Вы оцените, как «host network» позволяет контейнеру работать без NAT, а
также увидите риск конфликтов портов и сниженную изоляцию.
1/1
4.2 Практика
Сценарий задания
1/1
4.2 Практика
Сценарий задания
1. Создайте две пользовательские сети с драйвером bridge. Назовем их
front_net и back_net.
2. Запустите три контейнера:
frontend: подключен только к front_net
db: подключен только к back_net
api: подключен к обеим сетям (front_net и back_net)
3. Убедитесь, что:
frontend не может напрямую видеть db (так как они в разных сетях).
frontend может «достучаться» (ping или curl) до api, так как оба в
front_net.
db может достучаться до api, так как оба в back_net.
Итог: Вы узнаете, как использовать несколько сетей для разделения доступа, и как
один «пограничный» контейнер (api) может участвовать в обеих сетях, общаясь и со
frontend, и с db.
1/1
4.2 Практика
Сценарий задания
1. Запустите контейнер publicweb, используя дефолтный bridge и параметр -p
8080:80.
Убедитесь, что curl [Link] (или браузер) возвращает
страницу Nginx.
2. Создайте пользовательскую сеть, назовите её isolated_net. Затем запустите
контейнер hiddenweb в этой сети, но без публикации порта
3. Попробуйте зайти [Link] или :8080 — вы не увидите ответ
hiddenweb, так как он в отдельной сети без публикации порта.
4. Дополнительно (по желанию): убедитесь, что publicweb и hiddenweb не
видят друг друга по DNS-имени (ведь они в разных сетях). Можете проверить
через docker exec, попытавшись «ping hiddenweb» из publicweb или наоборот
(вероятнее всего, не получится).
1/1
4.2 Практика
Сценарий задания
1/1
4.2 Практика
Сценарий задания
1. Host network:
Запустите контейнер myhost с использованием --network=host (только на
Linux работает полноценно). Например, возьмите nginx или httpd,
который слушает 80 порт.
Убедитесь, что сервис внутри контейнера совпадает с портом хоста (если
внутри контейнера 80, то снаружи тоже 80).
Проверьте, что curl [Link] (или браузер) открывает страницу,
отдаваемую контейнером (если порт 80 не занят чем-то другим).
2. None network:
Запустите контейнер mynone с --network=none. Например, alpine с sleep.
Убедитесь, что нет доступа к сети внутри контейнера (попробуйте ping
или curl — не работает), и что на docker inspect видите нет IP-адреса
в NetworkSettings, кроме lo.
3. User-defined bridge:
Создайте сеть (например, mybridge): docker network create --driver
bridge mybridge.
Запустите контейнер mybridge1 (любой образ, например nginx или
alpine) с флагом --network mybridge.
При желании, запустите второй контейнер mybridge2 в той же сети и
убедитесь, что mybridge1 может пинговать или подключиться к mybridge2
(DNS-имя mybridge2), если у него есть нужный инструмент (e.g. ping).
4. Наблюдения:
С контейнером myhost (host network) — не нужно -p, порты совпадают с
хостом.
С контейнером mynone — полная изоляция, он не имеет IP и не виден
снаружи.
С контейнерами в mybridge — они видят друг друга по DNS (если в одной
сети), но извне доступ не откроется, пока не используете -p (если нужно).
1/2
Список команд, которые вы использовали, чтобы:
Запустить myhost (--network=host) и протестировать его доступ с хоста
Запустить mynone (--network=none) и проверить отсутствие сети
Создать сеть mybridge и запустить контейнер(ы) mybridgeX в этой сети (и
при желании проверить взаимодействие между ними)
2/2
4.3 DNS
Итог: контейнер db может быть достигнут по имени db или [Link]-net, без ручного
прописывания IP.
Важная разница:
Вывод: Для простого сетевого взаимодействия (по имени) лучше всегда создавать
user-defined bridge (или overlay). Default «bridge» весьма ограничен.
1/3
Любые DNS-запросы (ping, curl по hostname) идут к этому DNS, который
решает: db → [Link], web → [Link] и т. д.
Кроме --name, можно задавать --network-alias при docker run, чтобы контейнер
имел несколько DNS-имен. В [Link] (в service’ах) есть aliases и т. д.
Суть та же: Docker (или оркестратор) хранит таблицу сервис → IP, и встроенный
DNS отвечает на запросы контейнеров.
7. Основные советы
2/3
Итог
Система DNS в Docker встроена в daemon. Контейнеры в user-defined bridge или
overlay сети получают автоматический DNS, разрешая имена контейнеров/сервисов.
Это упрощает service discovery и избавляет от ручного прописывания IP. В итоге,
db, redis, web и другие контейнеры обращаются по имени, а Docker сам под капотом
переназначает IP при перезапусках. Понимание этой механики поможет вам легче
связывать контейнеры в микросервисах и избежать проблем с DNS.
3/3
4.4 Практика
Сценарий задания
1. Контейнеры в default bridge (сеть по умолчанию, когда не указываем --
network):
Запустите два контейнера, дайте им имена (например, def1 и def2), но не
используйте флаг --network.
Попробуйте обратиться из одного контейнера к другому по имени
(например, def2), посмотрите результат.
2. Создаём user-defined bridge:
Создайте новую сеть (например, mydnsnet) с драйвером bridge.
Запустите два контейнера (например, dns1 и dns2), указав, что они
входят в эту сеть (через --network).
Попробуйте обратиться из одного контейнера к другому по имени
(например, dns2), проверьте, что теперь DNS работает.
Посмотрите, что внутри контейнера может быть /etc/[Link] с
nameserver [Link].
1/1
9 апреля 2025 г.
4.4 Практика
Сценарий задания
1. Создайте user-defined сеть (например, aliasnet).
2. Запустите контейнер (назовите его, например, app) с двумя разными alias:
Основное имя (через --name app).
Альтернативный alias (через --network-alias alt1).
Можно добавить ещё один (через --network-alias alt2), если хотите
3. Запустите второй контейнер (в той же сети aliasnet), назовите его tester.
Внутри него попробуйте «постучаться» к первому контейнеру по разным
именам (app, alt1, alt2), чтобы убедиться, что Docker DNS резолвит все
варианты.
1/1
4.4 Практика
Сценарий задания
1. Создайте user-defined сеть, например fallbacknet.
2. Запустите контейнер с именем db (или backend) в этой сети (используйте
alpine с sleep, nginx или любой другой образ). Назовите контейнер, чтобы --
name db.
3. Запустите второй контейнер tester в сети fallbacknet, чтобы из него можно
было проверить обращение как к db, так и к внешнему домену (например,
[Link]).
4. В контейнере tester проверьте:
ping db или ping backend — запрос разрешается внутренним Docker
DNS.
ping [Link] (или curl [Link]), видите, что это уходит во
внешний мир. Docker DNS «фолбэчит» на системный DNS хоста.
1/2
Итог: Вы увидите, что Docker DNS знает внутренние имена контейнеров в сети, а
для обычных интернет-доменов прокидывает запрос к DNS хоста, делая работу
контейнеров прозрачной как для локальных, так и для внешних адресов.
2/2
10 апреля 2025 г.
4.4 Практика
Сценарий задания
1. Создайте user-defined сеть, назовите её testdns.
2. Запустите контейнер (назовите его db) в сети testdns. Можно взять alpine
или любой другой образ (чтобы он жил, используйте sleep или tail -f
/dev/null).
3. Запустите контейнер tester в той же сети testdns. Изнутри tester —
проверьте, что ping db действительно резолвится и отвечает.
4. Остановите и удалите контейнер db, а затем запустите другой контейнер
(другой образ или тот же), но с тем же именем db в сети testdns.
Теперь IP-адрес у нового db может отличаться.
5. Всё ещё находясь в tester, опять попробуйте ping db. Убедитесь, что DNS
«подхватил» новый контейнер. Вы должны увидеть, что запросы идут уже на
новый IP — а не «зависают» на старом.
1/2
2/2
4.4 Практика
Сценарий задания
1. Создайте две user-defined сети: frontend_net и backend_net.
2. Запустите три контейнера (в любом порядке):
frontend — подключите к frontend_net и дайте ему alias web (через --
network-alias).
backend — подключите к backend_net, alias api (или другое название).
Возможно, вы захотите подключить этот контейнер и к frontend_net
(если ему надо общаться с frontend), но можно сделать это
динамически (см. пункт о docker network connect).
db — подключите (сначала) к backend_net и дайте alias database.
Если образам нужно жить в фоне, используйте tail -f /dev/null или sleep
в Alpine.
3. Проверьте DNS-обращения:
В frontend (или web alias) попробуйте пинг/обращение к api, если
backend оказался в той же сети frontend_net.
Если нет — используйте docker network connect для backend и
frontend_net.
В backend (или api) попробуйте пинг/обращение к database (alias для db)
— так как они оба в backend_net.
4. Удалите и пересоздайте один из контейнеров (скажем, db), сохранив прежний
alias (database). Убедитесь, что другие контейнеры теперь видят
новый контейнер под тем же DNS-именем database — Docker DNS
обновляется.
5. (Дополнительно, если хотите) добавьте ещё один alias к одному из
контейнеров (например, backend пусть ответит и на oldapi, и на api), и
проверьте, что обращение по обоим именам срабатывает.
1/2
Что сдать в качестве ответа?
Список команд:
Создание сетей frontend_net и backend_net
Запуск frontend, backend, db (каждого с нужными --network / --network-
alias)
Любые docker network connect (если вы динамически подключаете
контейнер ко второй сети)
Проверка (ping или curl) между контейнерами по алиасам
Удаление и пересоздание одного контейнера (с тем же alias),
подтверждая, что DNS обновился
Короткое описание, как всё в итоге работает: frontend пингует api,
backend пингует database и т. д., а после пересоздания контейнера всё ещё
работает по тому же DNS-имени.
Итог: Если всё сделано, вы убедитесь, что Docker DNS позволяет гибко связывать
контейнеры разными именами (alias), подключать контейнеры к нескольким сетям, и
автоматически обновляет IP, если контейнер удаляется и создаётся заново под тем
же именем (или alias).
2/2
4.5 Траблшутинг сетей
Когда контейнеры не могут увидеть друг друга, или сеть ведёт себя странно, нужно
уметь быстро проверить, где сбой. В этом уроке разберём три полезных
инструмента: ping, nslookup, и docker network inspect. С их помощью вы сможете
разобраться, есть ли у контейнеров сеть вообще, как имена резолвятся в IP, и какие
порты доступны.
1. ping
Что проверяет? Достучится ли контейнер A до контейнера B по IP или hostname, а
также проверяет задержку (latency). Это самое базовое сетевое средство.
ping db
ping [Link]
Если «db» отвечает, значит DNS внутри сети работает, и трафик идёт.
На некоторых минималистичных образах (Alpine, BusyBox) ping может
отсутствовать по умолчанию. При необходимости можно «apk add iputils» или
apt-get install iputils-ping в контейнере.
Ограничения: ping не всегда работает в host сети или firewall может блокировать
ICMP. Но для user-defined bridge обычно полезен.
Что проверяет? Как Docker DNS (или системный DNS) резолвит имя в IP. Если db
не резолвится, приложение не увидит db.
nslookup db
или
dig db
(в зависимости от образа).
Результат покажет IP-адрес, если Docker DNS знает контейнер db.
1/3
Если ошибка server can’t find db — значит DNS не в курсе этого имени. Может,
вы не используете user-defined network? Или контейнер db вышел из строя?
{
"Name": "mynetwork",
"Id": "...",
"IPAM": { "Config": [ { "Subnet": "[Link]/16" } ] },
"Containers": {
"container_id": {
"Name": "mycontainer",
"IPv4Address": "[Link]/16",
...
}
}
}
Дополнительные инструменты
telnet или curl: проверить порт (e.g. curl db:5432 — получится ошибка, но
если соединение установилось, значит порт открыт).
traceroute / tracepath: для более глубокого пути пакетов, обычно реже нужно в
локальном Docker-контейнере.
2/3
При микросервисной архитектуре, где много контейнеров общаются через user-
defined bridge (или overlay), когда что-то «не пингуется» или «не резолвится», эти
команды быстро помогают локализовать проблему:
Итог
ping проверяет маршрут и отклик, nslookup (или dig) проверяет DNS-резолв, а
docker network inspect даёт полную картину сети (IP-адреса, subnet, какие
контейнеры подключены). Сочетая их, вы сможете быстро диагностировать сетевые
проблемы в Docker.
3/3
4.6 Практика
Сценарий задания
1. Создайте user-defined сеть, назовём её, например, diag_net.
2. Запустите два контейнера в этой сети (например, mydb и tester). Позаботьтесь
о том, чтобы в tester были доступные инструменты для диагностики (либо
установите их).
3. Используя доступные инструменты, проверьте:
docker network inspect для вашей сети, чтобы увидеть, какие
контейнеры подключены и под какими IP-адресами.
ping внутри контейнера tester в адрес mydb (по имени или IP) —
убедитесь, что пакеты доходят.
nslookup (или dig) для mydb внутри tester, чтобы удостовериться, что
Docker DNS корректно резолвит имя.
Итог: Вы научитесь использовать ping и nslookup (или dig) внутри контейнера для
проверки DNS и доступности, а также docker network inspect для просмотра
конфигурации сети и IP-адресов контейнеров.
1/1
4.6 Практика
Сценарий задания
1. Создайте user-defined сеть (например, portcheck_net).
2. Запустите контейнер с простым приложением, которое слушает на каком-то
порту. Например:
[Link] или Python, включающее в себя HTTP-сервер,
или nc -lkp 8080 (netcat в режиме сервер),
или любой другой образ, где сервис висит на порту и пишет логи при
входящих соединениях.
Назовём контейнер server и подключим его к portcheck_net.
3. Запустите второй контейнер client в той же сети (portcheck_net), в котором
есть nc (netcat) или telnet. С его помощью попробуйте подключиться к порту
серверного контейнера по имени server (например, nc server 8080),
отправьте какие-то символы.
4. Убедитесь, что:
соединение устанавливается;
server контейнер что-то пишет в логи (например, входящее соединение,
полученные данные);
client может отправлять и получать ответ (зависит от того, как настроен
server).
5. (Опционально) Откройте ещё один порт server или используйте -p
HOSTPORT:CONTAINERPORT, чтобы проверить доступ с хоста. Но основная идея
— тест взаимодействия контейнер-контейнер по имени в user-defined сети.
1/2
Создайте сеть (драйвер bridge), запустите контейнер «server» с каким-нибудь
сервисом, который слушает на 8080 или 1234.
Например, nc -lkp 8080 (в Alpine нужно apk add busybox-extras или apk
add netcat-openbsd).
Запустите «client» с nc (или telnet) установленным, подключитесь командой
nc server 8080.
Посмотрите логи «server» (docker logs server): если сервис что-то выводит
при подключении, вы увидите это.
2/2
14 апреля 2025 г.
4.6 Практика
Сценарий задания
location / {
proxy_pass [Link]
}
1/2
Список команд, которыми вы:
Создали сеть proxy_net
Запустили backend, настроив его на порт (скажем, 5000)
Подготовили proxy (Nginx), подключили к proxy_net, пробросили порт -p
8080:80 наружу
Убедились, что [Link] отдаёт контент от backend
location / {
proxy_pass [Link]
}
Итог: Вы настроите reverse proxy внутри Docker-сети, убедитесь, что Nginx ходит по
имени backend (через DNS), и всё работает прозрачно для внешнего пользователя
на localhost:8080.
2/2
14 апреля 2025 г.
4.6 Практика
Сценарий задания
1. Создайте две сети (например, front_net и back_net), обе user-defined (driver
= bridge).
2. Запустите три контейнера:
frontend в front_net, дайте ему alias web (через --network-alias),
backend в back_net, alias api,
joiner, который должен быть мостом: подключен и к front_net, и к
back_net. Чтобы это сделать:
Запустите joiner сначала в одной сети,
потом добавьте к другой сети через docker network connect.
Этот контейнер (joiner) может быть простым alpine с sleep — главное,
чтобы он числился в обеих сетях.
3. Проверьте через joiner (зайдя docker exec внутрь):
ping web (из front_net, alias web) — должно работать, ведь joiner и
frontend в одной сети.
ping api — должно работать в back_net, так как joiner и backend в
одной сети.
4. Добавьте временный контейнер (например, ephemeral) в back_net с alias
temp. Из joiner сделайте ping temp, убедитесь, что DNS резолвит новое имя.
Теперь удалите ephemeral (docker rm -f ephemeral).
Снова запустите ephemeral (другой образ или тот же) с alias temp в
back_net. Проверьте, что joiner видит новый IP.
5. По желанию: если хотите, подключите frontend тоже к back_net, чтобы
проверить, как несколько сетей у одного контейнера сочетаются, и проверяйте
DNS-имена с разных сторон. Но это опционально.
1/2
Список команд, которыми вы:
Создали front_net и back_net
Запустили frontend (alias web), backend (alias api), joiner (добавив
вторую сеть через docker network connect)
Добавили ephemeral (alias temp) в back_net, протестировали доступ,
удалили, пересоздали — увидели новый IP
Проверяли DNS через ping или nslookup внутри joiner (и, при желании,
в frontend/backend)
2/2
4.7 Частые ошибки
1. Конфликты портов
Симптом: Could not bind to port 80, или Address already in use при
запуске docker run -p 80:80.
Причина: На хост-машине уже кто-то слушает этот порт (Nginx, Apache, другой
контейнер).
Решение:
1. Освободить порт: остановить процесс, который его занимает.
2. Или выбрать другой порт хоста, например -p 8080:80, если 80 уже занят.
3. Проверить, нет ли уже запущенных контейнеров, которые занимают этот
порт.
2. Непробиваемый firewall
3. Забыл указать -p
1/3
Решение:
1. Запустить контейнер заново с -p (или -P для автопубликации).
2. Проверить, что сервис внутри контейнера слушает именно тот порт,
который вы мапите снаружи.
2/3
Причина: SELinux или AppArmor могут блокировать доступ к файлам, если не
настроены соответствующие профили. Также некоторые контейнеры пытаются
менять /etc/hosts, что Docker не разрешает по умолчанию.
Решение:
1. Проверить SELinux-режим (Permissive, Enforcing). Выполнить chcon -Rt
svirt_sandbox_file_t /path при bind mount.
2. AppArmor-профили настроить или отключить для конкретного
контейнера, если нужно.
3. Не пытаться напрямую менять /etc/hosts; используйте Docker DNS или
--add-host для дополнительных хостов.
Итог
Конфликты портов — следите, чтобы -p X:Y не пересекался с уже занятой
службой.
Firewall — откройте нужные порты, убедитесь, что внешние запросы не
блокируются.
Bind to [Link] — если приложение слушает только localhost, наружу его не
видно.
Сети на разных хостах — для межхостовых связей нужна либо Swarm
overlay, либо VPN, либо Kubernetes.
SELinux/AppArmor — могут мешать монтированию папок, проверяйте
контексты и профили.
3/3
4.8 Macvlan и Ipvlan
Плюсы:
Минусы:
1/3
1. Определите физический интерфейс хоста, с которым хотите
связать контейнеры, например eth0.
2. Узнайте подсеть, шлюз и т. д. (например, у вас LAN: [Link]/24, шлюз
[Link]).
3. Создайте сеть командой docker network create с драйвером macvlan:
2/3
Вместо того чтобы у контейнеров были собственные MAC, они делят MAC с
хостом, а различаются IP-адресом. Это упрощает ситуацию, когда свитчи не
любят дополнительные MAC-адреса.
Команда создания сети похожа, только -d ipvlan вместо -d macvlan. Опции те
же (parent, --subnet, --gateway).
Так же, как и с macvlan, есть нюанс: хост по умолчанию не сможет влезть в эту
же L3-сеть без ручного маршрута.
Не используйте, если:
У вас нет прав (root) или доступа на хосте, чтобы менять сетевые интерфейсы
/ iptables. Macvlan требует соответствующих прав.
Не хотите заморачиваться с маршрутизацией хост ↔ контейнер. Bridge-сеть
или host network проще.
Свитч или сетевые администраторы не любят появление новых MAC-адресов
(тогда ipvlan может помочь, но всё равно нужна продвинутая настройка).
3/3
5.1 Volumes и Bind Mounts
Когда контейнер работает, любые данные внутри него живут только до момента
удаления или пересоздания контейнера (Copy-on-Write слой). Если вы хотите
сохранить данные дольше, нужно использовать либо тома (volumes), которые
управляет Docker, либо bind mounts, которые напрямую привязаны к директории на
хосте. Рассмотрим эти подходы и их различия.
3. Принципиальные отличия
1/3
Критерий Volume Bind Mount
4. Примеры запуска
Volume
Bind Mount
5. Плюсы/минусы
2/3
Volume:
+ Не завязаны на конкретный путь хоста, удобно переносить.
+ Docker командой docker volume ls показывает все тома, легко
управлять.
– Менее прозрачно, где именно лежат файлы, если вдруг нужно вручную
внести правки.
Bind Mount:
+ Полный контроль: прописали точный путь хоста, видим файлы,
редактируем в реальном времени.
– Менее переносимо, нужно помнить о путях, правах. На другой машине
папки могут отличаться.
6. Как выбрать?
Итог
Volume — управляемая Docker'ом область хранения. Отличная
переносимость, простое управление командами docker volume.
Bind Mount — монтируете конкретную папку/файл хоста в контейнер, полная
прозрачность.
3/3
5.2 Практика
Сценарий задания
1. Создайте именованный том (например, myvol).
2. Запустите контейнер (назовём его container_vol), который будет
использовать этот том для хранения данных (например, пишите логи или
файлы в какую-то директорию внутри контейнера). Убедитесь, что данные
сохраняются в myvol.
3. Запустите второй контейнер (назовём его container_bind), который вместо
тома использует bind mount (например, монтируете локальную папку
/tmp/testbind в /app/data внутри контейнера). Убедитесь, что при записи в
/app/data внутри контейнера файлы оказываются на хосте в /tmp/testbind.
4. Сравните:
Как Docker отображает контейнер c volume (посмотрите docker volume
ls, docker volume inspect).
Где физически лежат данные при bind mount (вы сразу видите их на хосте
в /tmp/testbind), а не через docker volume inspect.
5. Попробуйте удалить контейнеры и посмотреть, что произойдёт с данными:
Данные из именованного тома (myvol) не исчезнут.
Файлы на хосте (в /tmp/testbind) тоже останутся, так как bind mount
напрямую привязан к папке.
1/2
Затем создайте локальную папку (например, /tmp/testbind), запустите второй
контейнер с -v /tmp/testbind:/app/data. При записи в /app/data внутри
контейнера файлы появятся в /tmp/testbind на хосте.
Посмотрите docker volume ls — увидите myvol, а для bind mount Docker
volume не создаётся, потому что это просто монтирование папки хоста.
2/2
15 апреля 2025 г.
5.2 Практика
Сценарий задания
1/1
5.2 Практика
Сценарий задания
1. Создайте именованный том (например, mydatavol), чтобы хранить
данные приложения.
2. Подготовьте локальную папку на хосте (например, /tmp/myapplogs), в которую
приложение будет писать логи (через bind mount).
3. Запустите контейнер (назовём его myapp), который:
Использует mydatavol как том для данных (например, в /var/lib/myapp).
Одновременно монтирует /tmp/myapplogs (локальная папка на хосте) в
/var/log/myapp (для логов).
При необходимости, используйте любой образ (например, alpine или
ubuntu) и простую команду, которая периодически пишет что-то в лог и
создаёт данные (можно скрипт на bash или Python).
4. Проверьте:
Что в /var/lib/myapp (внутри контейнера) находятся данные (и они
сохраняются в mydatavol).
Что в /var/log/myapp контейнер действительно пишет логи, которые
видны прямо в /tmp/myapplogs на хосте.
5. Удалите контейнер (или пересоздайте его). Убедитесь, что:
«Данные» в mydatavol не пропали (при запуске нового контейнера с тем
же -v том снова «подхватит»).
«Логи» в /tmp/myapplogs остались (так как это bind mount).
1/2
Запуск контейнера: docker run с -v mydatavol:/var/lib/myapp и -v
/tmp/myapplogs:/var/log/myapp. Используйте любой образ, где можно
записывать в /var/log/myapp и /var/lib/myapp.
После удаления контейнера проверьте, что docker volume ls всё ещё
показывает mydatavol, а /tmp/myapplogs не пустая.
2/2
5.2 Практика
Сценарий задания
1. Создайте именованный том (например, mysqldata) для хранения файлов
MySQL.
2. Подготовьте локальную папку (например, /tmp/mysqlconf), где будет лежать
[Link]. В этом файле можно указать любые нужные настройки (например,
bind-address=[Link], sql_mode=NO_ENGINE_SUBSTITUTION, и т. д.).
3. Запустите контейнер MySQL (назовём его mysqlserver):
Используйте mysqldata как том: подключив его к /var/lib/mysql или
подходящей директории внутри контейнера.
Подключите [Link] с хоста (через bind mount) к директории, куда MySQL
читает кастомные конфиги (например, /etc/mysql/conf.d).
При первом запуске, если используете mysql:latest, задайте -e
MYSQL_ROOT_PASSWORD=secret (или другой), чтобы MySQL
инициализировался.
По желанию, опубликуйте порт -p 3306:3306, чтобы проверять доступ к
базе извне (или используйте другой подход, например, Docker network +
клиент). Но это не обязательно.
4. Сделайте какую-то тестовую базу или таблицу, чтобы было что бэкапить
(через docker exec + mysql -u root -psecret).
5. Одноразовый контейнер (назовём его backup):
Создайте (при необходимости) локальную папку (например, /tmp/backups)
на хосте, куда бэкап будет сохраняться.
Запустите контейнер «backup» без сохранения (флаг --rm), где
используете mysqldump (или копирование файлов) и складываете
результат в /tmp/backups на хосте (через bind mount).
Если SELinux блокирует запись в /tmp/backups, нужно будет chcon -Rt
svirt_sandbox_file_t /tmp/backups или иное решение. В случае
«Permission denied» — это вероятно SELinux/доступность папки.
В итоге появится (например) /tmp/backups/[Link] или
/tmp/backups/[Link], смотря какую стратегию бэкапа вы выбрали.
6. Восстановление (или проверка сохранённого архива):
Можно аналогичным способом запустить restore контейнер:
монтировать /tmp/backups и выполнить mysql -u root SOURCE
/backups/[Link] или распаковать файлы.
7. Удалите и пересоздайте mysqlserver, проверив, что данные остались в
mysqldata, так как это именованный том.
1/2
Краткий список действий, как вы:
Создали том (mysqldata), подготовили папку /tmp/mysqlconf (с [Link]) и
(при желании) /tmp/backups.
Запустили mysqlserver (используя mysqldata и bind mount для [Link]).
Создали тестовую таблицу, вставили данные.
Запустили одноразовый контейнер (backup) с mysqldump (или
архивированием), проверили SELinux (если нужно).
Опционально, сделали восстановление или пересоздание контейнера,
убедились, что данные не пропали.
2/2
5.3 docker volume
Мы уже обсудили, что такое Volume (том) в Docker: это специальная область для
хранения данных, которую Docker управляет самостоятельно. Теперь давайте
посмотрим на конкретные команды — docker volume create и docker volume
inspect, с помощью которых вы можете создавать, настраивать и изучать тома.
docker run -d \
--name mycontainer \
-v mydata:/path/in/container \
some-image
1/3
docker volume inspect <имя_тома>
Пример:
[
{
"CreatedAt": "2023-05-01T10:15:24Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mydata/_data",
"Name": "mydata",
"Options": {},
"Scope": "local"
}
]
2/3
3. Запускаем контейнер с этим томом:
docker run -d \
--name some-mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-v mydata:/var/lib/mysql \
mysql:5.7
docker run -d \
--name some-other-mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-v mydata:/var/lib/mysql \
mysql:5.7
Итог
docker volume create позволяет явно создать том (именованный), который
потом используете в -v volumeName:/path.
docker volume inspect даёт детальную инфу (где хранится, драйвер, дата
создания и т. д.).
Вместе они помогают упорядочить хранение данных, не теряя их при
пересоздании контейнеров.
3/3
5.4 Практика
Сценарий задания
1. Создайте именованный том (например mydata).
2. Проверьте, что том создан.
3. Запустите контейнер (назовем его mycontainer), используя том mydata. Пусть
внутри контейнера будет директория, в которую вы запишите файлы.
4. Остановите и удалите контейнер.
5. Запустите новый контейнер, снова используя том mydata, и убедитесь, что
данные остались.
6. Посмотрите подробную информацию о томе mydata.
Подсказка
Сначала можно выполнить docker volume create mydata
Посмотреть список томов через docker volume ls
При запуске контейнера используйте -v mydata:/someDir, создайте там файл
После удаления контейнера и повторного запуска с тем же -v
mydata:/someDir данные сохранятся
docker volume inspect mydata покажет информацию о томе
1/1
5.4 Практика
Сценарий задания
1. Создайте именованный том (например mydbdata).
2. Создайте сеть Docker (например mynet), чтобы связать контейнеры.
3. Запустите контейнер (назовем его mydb) с образом MySQL, монтируя том
mydbdata в директорию для данных, и установите переменные среды
(например пароль root). Подключите контейнер к сети mynet.
4. Проверьте, что mydb работает.
5. Запустите второй контейнер (например myclient) в сети mynet, чтобы
подключиться к mydb и создать (или изменить) таблицу в базе.
6. Остановите и удалите mydb.
7. Запустите mydb заново с тем же томом mydbdata и убедитесь, что таблица
сохранилась.
8. Посмотрите информацию о томе mydbdata.
Подсказка
Можно создать том через docker volume create mydbdata.
Сеть можно создать через docker network create mynet.
Для MySQL используйте -e MYSQL_ROOT_PASSWORD и монтируйте -v
mydbdata:/var/lib/mysql, а также подключайте к сети --network mynet.
Чтобы проверить таблицу, можно подключиться к базе через docker exec или
запустить второй контейнер myclient с тем же --network.
docker volume inspect mydbdata покажет подробную информацию.
1/1
5.4 Практика
Сценарий задания
1. Подготовьте образ (можно с помощью Dockerfile или используя готовый,
например nginx), чтобы веб-сервер записывал логи в папку (например
/app/logs).
2. Создайте именованный том (например mylogs) для хранения логов.
3. Запустите контейнер с веб-сервером, смонтировав том mylogs в директорию
логов внутри контейнера.
4. Сделайте несколько http-запросов (или отправьте какие-то сообщения), чтобы
веб-сервер сгенерировал логи.
5. Остановите и удалите контейнер.
6. Запустите новый контейнер с тем же томом mylogs и проверьте, что логи
сохранились.
7. Посмотрите информацию о томе mylogs.
Подсказка
Для создания тома можно использовать docker volume create mylogs.
Для запуска веб-сервера монтируйте папку логов: -v mylogs:/app/logs (если
в образе веб-сервера логи идут в /app/logs).
Посмотреть информацию о томе: docker volume inspect mylogs.
После удаления контейнера данные в томе сохраняются.
1/1
5.4 Практика
Сценарий задания
1. Создайте два именованных тома (например mysql_data и redis_data).
2. Создайте сеть (например myapp_network) для соединения сервисов.
3. Запустите контейнер MySQL (назовем его mysqldb), используя том mysql_data
для директории данных и указав переменные среды (например пароль).
Подключите его к сети myapp_network.
4. Запустите контейнер Redis (назовем его myredis), используя том redis_data
для директории данных. Подключите его к сети myapp_network.
5. Запустите еще один контейнер (например myclient) в myapp_network,
проверьте подключение к mysqldb и myredis, создайте (или измените) записи,
чтобы убедиться, что данные сохраняются.
6. Остановите и удалите контейнеры mysqldb и myredis.
7. Снова запустите mysqldb и myredis с теми же томами mysql_data и redis_data
и проверьте, что данные остались.
8. Посмотрите информацию о томах (например docker volume inspect).
Подсказка
Тома можно создать через docker volume create mysql_data и docker volume
create redis_data.
Сеть — docker network create myapp_network.
Для MySQL можно использовать -e MYSQL_ROOT_PASSWORD=... -v
mysql_data:/var/lib/mysql --network myapp_network.
Для Redis — -v redis_data:/data --network myapp_network.
С помощью контейнера myclient (или docker exec) можно проверить записи в
MySQL и Redis, а затем убедиться, что после удаления контейнеров данные не
пропадают.
docker volume inspect покажет путь и драйвер тома.
1/1
5.5 Продвинутые вещи
1. tmpfs-тома
tmpfs — это том, который хранится в оперативной памяти (RAM) и не записывается
на диск. Он полезен для временных файлов или кэша, которые не нужно сохранять
после завершения контейнера. Это обеспечивает высокую скорость доступа и
гарантированное стирание данных при остановке.
docker run -d \
--name mycontainer \
--tmpfs /app/tmp:rw,size=64m \
alpine
Плюсы/Минусы tmpfs
Плюсы:
Высокая скорость чтения/записи (RAM).
Безопасность: данные исчезают при остановке, не остаются на диске.
Минусы:
Ограничено объёмом оперативной памяти.
Если приложение или база активно использует много памяти, можно
столкнуться с OOM (Out Of Memory).
2. Шифрованные тома
1/3
Для дополнительной безопасности, иногда нужно хранить данные в
зашифрованном виде (например, в случае конфиденциальных данных). Docker
сам по себе не умеет из коробки шифровать тома, но есть несколько подходов:
Общая схема
2/3
В новых версиях Docker есть возможность docker volume export VOLUME, но она
экспериментальная в некоторых сборках. Аналогично docker volume import. Это
упрощает извлечение/загрузку содержимого тома.
Подведём итоги
tmpfs-тома: храним данные в памяти (RAM), без записи на диск. Полезно для
кэша/временных файлов, но не для долгосрочного хранения.
Шифрованные тома: обычно реализуются через внешние драйверы или
шифрование на уровне хоста. Позволяют защитить данные при утечке
физического диска.
Бэкапы:
Традиционный метод через docker run tar (архивируем содержимое
тома в .[Link]).
docker volume export/import (при необходимости).
Использование внешних инструментов, если том на NFS или облаке.
3/3
5.6 Практика
Сценарий задания
1. Запустите контейнер с помощью --tmpfs, чтобы папка (например /app/tmp)
была в оперативной памяти.
2. Создайте именованный том (например securedata) с опциями шифрования
(можно указать любой драйвер или флаг, симулирующий шифрование).
3. Запустите контейнер, монтируя том securedata в директорию (например
/data) внутри контейнера. Запишите туда любые файлы.
4. Сделайте бэкап содержимого тома, запаковав файлы (например с помощью
tar) и сохранив архив в локальную папку на хосте.
5. Остановите и удалите контейнер.
6. Запустите контейнер снова, используя тот же том securedata, убедившись, что
файлы на месте.
7. Посмотрите информацию о томе securedata.
Подсказка
Для tmpfs можно использовать --tmpfs /app/tmp:rw,size=64m.
Чтобы симулировать шифрованный том, можно указать драйвер и опцию
(например --driver blockbridge --opt encryption=on).
Для бэкапа воспользуйтесь tar, смонтировав том в контейнере и
перенаправив вывод архива на хост.
docker volume inspect securedata покажет путь к данным.
1/1
5.6 Практика
Сценарий задания
1. Создайте именованный том (например secure_db) с опцией шифрования (или
любым фиктивным драйвером, который это поддерживает).
2. Запустите контейнер (например mydb) с базой данных (MySQL или любая
другая), монтируя том secure_db в директорию данных.
3. Настройте, чтобы логи базы писались во временную папку (например /logs) и
сделайте её tmpfs (в оперативной памяти).
4. Сделайте несколько операций в базе, чтобы сгенерировать логи и данные.
5. С помощью бэкапа (например через tar или другой способ) сохраните
содержимое тома secure_db на хосте.
6. Остановите и удалите контейнер.
7. Запустите контейнер снова, используя тот же том secure_db, и убедитесь, что
данные остались (а логи были временными и пропали).
8. Посмотрите информацию о томе (например docker volume inspect
secure_db).
Подсказка
Создать том можно через docker volume create --driver blockbridge --opt
encryption=on --name secure_db (или любую другую комбинацию).
Чтобы логи оказались в памяти, используйте --tmpfs /logs:size=64m при
запуске контейнера.
Для бэкапа можно собрать архив с помощью tar, запустив временный
контейнер и перенаправив вывод в файл на хосте.
После удаления контейнера том сохраняется, что позволяет поднять снова тот
же контейнер и проверить, что данные остались.
docker volume inspect secure_db даст детали о томе.
1/1
5.6 Практика
Сценарий задания
1. Создайте том (например encrypted_db), указав драйвер или опцию
шифрования.
2. Запустите контейнер базы данных (например mydb), монтируя том
encrypted_db в папку для данных и установив переменную среды для пароля.
3. Настройте, чтобы контейнер приложения (например myapp) имел директорию
(например /app/cache) в tmpfs. Подключите его к той же сети, что и mydb, и
убедитесь, что он может взаимодействовать с БД.
4. Создайте еще один том (например db_backup), предназначенный для
сохранения резервных копий.
5. Запустите временный контейнер (например backup_runner), чтобы сделать
бэкап содержимого тома encrypted_db и положить архив в том db_backup.
Используйте любой инструмент (например tar).
6. Остановите и удалите контейнеры mydb и myapp.
7. Запустите их снова, используя тот же том encrypted_db, убедившись, что
данные БД остались.
8. Проверьте информацию о томе encrypted_db, а при желании и о томе
db_backup.
Подсказка
Том можно создать так: docker volume create --driver blockbridge --opt
encryption=on --name encrypted_db.
Для tmpfs: --tmpfs /app/cache:size=64m в параметрах docker run.
Сеть (если нужно) создается через docker network create mynet, потом
указывать --network mynet при запуске контейнеров.
Для бэкапа: docker run --rm -v encrypted_db:/source:ro -v
db_backup:/backup alpine tar -czf /backup/[Link] /source.
docker volume inspect encrypted_db покажет информацию о томе.
1/1
6.1 Основы
1/6
2. Детали синтаксиса [Link]
2/6
version: "3.8" # версия синтаксиса Compose
services: # блок, где мы перечисляем все сервисы
servicename: # имя сервиса (например, web или db)
image: ...
container_name: ...
ports:
- "хост_порт:контейнер_порт"
environment:
- KEY=value
volumes:
- ...
depends_on:
- ...
volumes:
...
networks:
...
3/6
version: "3.8"
services:
db:
image: postgres:14
container_name: my_db
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=user
volumes:
- dbdata:/var/lib/postgresql/data
networks:
- mynetwork
web:
image: nginx:latest
container_name: my_web
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db
networks:
- mynetwork
volumes:
dbdata:
networks:
mynetwork:
В этом примере:
4/6
Однако depends_on не гарантирует, что db успеет полностью
инициализироваться и быть готовым к подключению (для этого есть
специальные механизмы healthcheck, wait-for-it, и т.д.). Но для большинства
базовых случаев, когда нужно просто сначала поднять базу, потом
приложение, depends_on достаточно.
Если у вас более сложные требования (дождаться готовности сервиса), нужно
дополнительно использовать healthcheck или иные инструменты (например,
wait-for-it скрипты), чтобы приложение не попыталось подключаться к базе
слишком рано.
Вместо длинных ручных команд (куча флагов -p, -v, -e, --network) вы всё
описываете в YAML.
Легко расти и поддерживать: сегодня у вас 2 контейнера, завтра 5 — просто
добавляете новые секции services: в Compose.
5/6
Управление версионностью: [Link] можно хранить в
репозитории, чтобы вся команда видела общий способ запуска проекта.
Итог
Docker Compose — инструмент и формат (YAML) для описания нескольких
контейнеров как сервисов.
[Link] позволяет задать образы, порты, тома, переменные
окружения, зависимости (depends_on) и многое другое.
docker-compose up/down (или docker compose up/down) запускают и
останавливают все сервисы за один шаг.
Это мощный способ декларативно (то есть мы описываем, что должно быть
сделано, не расписывая детально, как это сделать) указать, как ваш проект (из
нескольких контейнеров) должен подниматься, вместо запуска каждого контейнера
вручную с помощью docker run.
6/6
6.2 Практика
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello from Docker Compose</title>
</head>
<body>
<h1>Hello from Docker Compose!</h1>
<p>Это наш тестовый сайт, который будет раздаваться Nginx через Docker Compose.
</p>
</body>
</html>
1/3
version: "3.8" # Версия синтаксиса Docker Compose
# (3.8 - актуальная и часто используемая)
ports:
- "8080:80" # Пробрасываем порт 80 из контейнера
# на 8080 на хостовой машине
volumes:
- ./mywebsite:/usr/share/nginx/html
# Монтируем локальную папку mywebsite
# в директорию /usr/share/nginx/html,
# откуда nginx раздает статические файлы
3. Запускаем и проверяем
4. Остановка и удаление
2/3
Если нужно оставить контейнер работающим — не выполняйте down, тогда
веб-сервис продолжит раздавать сайт.
Итого:
3/3
6.2 Практика
Техническое задание
1. Сервис (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере
Структура проекта
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
version: "3"
services:
web:
image: nginx:alpine
ports:
- "8080:80"
Полное решение
version: "3"
services:
web:
image: nginx:alpine
container_name: simple_web
ports:
- "8080:80"
1/1
13 апреля 2025 г.
6.2 Практика
Техническое задание
1. Сервис (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./html к /usr/share/nginx/html в
контейнере
Структура проекта
1/3
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Моя первая страница через Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h1>Привет из Docker Compose!</h1>
<p>Если вы видите эту страницу, значит:</p>
<ul>
<li>Вы успешно создали [Link] файл</li>
<li>Правильно настроили volumes для HTML-контента</li>
<li>Корректно пробросили порты</li>
</ul>
<p>Поздравляем с успешным выполнением задания!</p>
</div>
</body>
</html>
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
Требования к решению
2/3
Версия docker-compose файла должна быть не ниже 3.0
Контейнер должен иметь имя web-server
version: "3"
services:
web:
image: nginx:alpine
container_name: web-server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
3/3
11 апреля 2025 г.
6.2 Практика
Техническое задание
1. Сервис 1 (веб-приложение):
Базовый образ — php:7.4-apache
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./app к /var/www/html в контейнере
Установка расширения PHP для работы с MySQL
Зависимость от сервиса базы данных
2. Сервис 2 (база данных):
Базовый образ — mysql:5.7
Настройка переменных окружения для инициализации БД
Использование именованного тома для хранения данных
Проброс порта 3306 (опционально)
Структура проекта
1/4
<?php
$host = 'db'; // Имя сервиса в docker-compose
$user = 'devuser'; // Имя пользователя из переменных окружения
$password = 'devpass'; // Пароль из переменных окружения
$db = 'test_db'; // Имя базы данных из переменных окружения
// Создаем соединение
$conn = new mysqli($host, $user, $password, $db);
// Проверяем соединение
if ($conn->connect_error) {
die("Ошибка подключения к базе данных: " . $conn->connect_error);
}
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "<tr><td>" . $row["id"]. "</td><td>" . $row["visit_time"]. "</td>
</tr>";
}
} else {
echo "<tr><td colspan='2'>Нет записей о посещениях</td></tr>";
}
echo "</table>";
$conn->close();
?>
2/4
1. [Link] — файл, который запускает оба сервиса.
2. Проверить работу композа:
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
Требования к решению
Для веб-сервиса вам нужно установить расширение mysqli для PHP. Это можно
сделать с помощью следующего блока команд в Docker Compose:
web:
image: php:7.4-apache
# другие настройки...
command: >
bash -c "docker-php-ext-install mysqli &&
apache2-foreground"
3/4
web:
image: php:7.4-apache
container_name: web_app
ports:
- "8080:80"
volumes:
- ./app:/var/www/html
depends_on:
- db
command: >
bash -c "docker-php-ext-install mysqli &&
apache2-foreground"
db:
image: mysql:5.7
container_name: mysql_db
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: test_db
MYSQL_USER: devuser
MYSQL_PASSWORD: devpass
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
4/4
21 марта 2025 г.
6.2 Практика
Веб-сервер и время
В этом задании вы создадите Docker Compose файл для запуска двух
взаимосвязанных сервисов: веб-сервер и сервис текущего времени.
Техническое задание
1. Сервис 1 (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./html к /usr/share/nginx/html в
контейнере
Зависимость от сервиса времени
Должен быть в общей сети app-network
2. Сервис 2 (сервис времени):
Базовый образ — busybox:latest
Должен выполнять скрипт, который каждые 10 секунд обновляет время в
файле
Подключение к общему тому shared-data
Должен быть в общей сети app-network
Структура проекта
1/5
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose Практика</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 600px;
margin: 0 auto;
}
h1 {
color: #333;
text-align: center;
}
.time-container {
margin-top: 20px;
padding: 15px;
background-color: #e9f7fe;
border-radius: 5px;
text-align: center;
}
#current-time {
font-size: 24px;
font-weight: bold;
color: #0066cc;
}
button {
margin-top: 10px;
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>Docker Compose Демонстрация</h1>
<p>Это пример работы Docker Compose с двумя связанными сервисами:</p>
<ul>
<li><strong>Веб-сервер (Nginx)</strong> - отображает эту страницу</li>
<li><strong>Сервис времени (Busybox)</strong> - обновляет текущее
время</li>
2/5
</ul>
<div class="time-container">
<p>Текущее время (обновляется каждые 10 секунд):</p>
<div id="current-time">Загрузка...</div>
<button onclick="checkTime()">Обновить сейчас</button>
</div>
<script>
function checkTime() {
fetch('/[Link]')
.then(response => [Link]())
.then(time => {
[Link]('current-time').innerText = time;
})
.catch(error => {
[Link]('current-time').innerText = 'Ошибка:
время не доступно';
[Link]('Ошибка:', error);
});
}
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
3. Проверка работы:
Откройте в браузере [Link] — вы должны увидеть
страницу с текущим временем, которое обновляется каждые 10 секунд.
3/5
Требования к решению
version: "3.8"
services:
web:
image: nginx:alpine
container_name: web_server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
- shared-data:/usr/share/nginx/html
depends_on:
- time-service
networks:
- app-network
time-service:
image: busybox:latest
container_name: time_service
command: sh -c "while true; do date > /shared-data/[Link]; sleep 10; done"
volumes:
- shared-data:/shared-data
networks:
- app-network
volumes:
shared-data:
networks:
app-network:
Полное решение
4/5
version: "3.8"
services:
web:
image: nginx:alpine
container_name: web_server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
- shared-data:/usr/share/nginx/html
depends_on:
- time-service
networks:
- app-network
time-service:
image: busybox:latest
container_name: time_service
command: sh -c "while true; do date > /shared-data/[Link]; sleep 10; done"
volumes:
- shared-data:/shared-data
networks:
- app-network
volumes:
shared-data:
networks:
app-network:
5/5
21 марта 2025 г.
6.2 Практика
Техническое задание
Компания разрабатывает веб-приложение и вам необходимо настроить локальное
окружение для разработки, которое должно включать следующие компоненты:
Структура проекта
Подготовьте следующую структуру файлов:
1/7
<!DOCTYPE html>
<html>
<head>
<title>Проект компании</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.panel {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-left: 4px solid #007bff;
}
.links a {
display: inline-block;
margin: 10px;
padding: 10px 15px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
}
.links a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>Тестовое окружение для разработки</h1>
<div class="panel">
<h2>Доступные сервисы:</h2>
<ul>
<li><strong>Веб-сервер:</strong> [Link] (текущая
страница)</li>
<li><strong>База данных PostgreSQL:</strong> dev-db:5432</li>
<li><strong>Adminer (управление БД):</strong>
[Link]
</ul>
2/7
</div>
<div class="links">
<a href="[Link]">Проверка подключения к БД</a>
<a href="[Link] target="_blank">Открыть Adminer</a>
</div>
<div class="panel">
<h3>Данные для подключения к БД:</h3>
<ul>
<li><strong>Сервер:</strong> dev-db</li>
<li><strong>База данных:</strong> development</li>
<li><strong>Пользователь:</strong> developer</li>
<li><strong>Пароль:</strong> devpassword</li>
</ul>
</div>
</div>
</body>
</html>
3/7
<!DOCTYPE html>
<html>
<head>
<title>Проверка подключения к БД</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
}
.note {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin: 20px 0;
}
.back {
display: inline-block;
margin-top: 20px;
padding: 10px 15px;
background-color: #6c757d;
color: white;
text-decoration: none;
border-radius: 4px;
}
.back:hover {
background-color: #5a6268;
}
</style>
</head>
<body>
<div class="container">
<h1>Проверка подключения к БД</h1>
<div class="note">
<p><strong>Примечание:</strong> На текущем этапе мы не можем выполнить
реальную проверку подключения к базе данных из браузера напрямую из-за ограничений
безопасности. В рабочем проекте такая проверка обычно выполняется через серверный
код (PHP, [Link], Python и т.д.).</p>
4/7
<a href="[Link]" class="back">← Вернуться на главную</a>
</div>
</body>
</html>
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
3. Проверка работы:
Откройте в браузере [Link] — должна открыться
тестовая веб-страница
Откройте в браузере [Link] — должен открыться
интерфейс Adminer
В Adminer подключитесь к базе данных, используя следующие
параметры:
Система: PostgreSQL
Сервер: dev-db
Пользователь: developer
Пароль: devpassword
База данных: development
Требования к решению
Все сервисы должны быть в одной сети для обеспечения связи между ними.
База данных должна сохранять данные между перезапусками контейнеров
(использовать именованный том).
Используйте depends_on для указания зависимостей между сервисами.
Версия docker-compose файла должна быть не ниже 3.0.
5/7
version: "3.8"
services:
web:
image: nginx:latest
container_name: dev-web
ports:
- "8000:80"
volumes:
- ./src:/usr/share/nginx/html
depends_on:
- db
networks:
- dev-network
db:
image: postgres:13
container_name: dev-db
environment:
- POSTGRES_DB=development
- POSTGRES_USER=developer
- POSTGRES_PASSWORD=devpassword
volumes:
- db-data:/var/lib/postgresql/data
networks:
- dev-network
adminer:
image: adminer:latest
container_name: dev-adminer
ports:
- "8080:8080"
depends_on:
- db
networks:
- dev-network
volumes:
db-data:
networks:
dev-network:
Полное решение
6/7
version: "3.8"
services:
web:
image: nginx:latest
container_name: dev-web
ports:
- "8000:80"
volumes:
- ./src:/usr/share/nginx/html
depends_on:
- db
networks:
- dev-network
db:
image: postgres:13
container_name: dev-db
environment:
- POSTGRES_DB=development
- POSTGRES_USER=developer
- POSTGRES_PASSWORD=devpassword
volumes:
- db-data:/var/lib/postgresql/data
networks:
- dev-network
adminer:
image: adminer:latest
container_name: dev-adminer
ports:
- "8080:8080"
depends_on:
- db
networks:
- dev-network
volumes:
db-data:
networks:
dev-network:
7/7
6.3 Внешние .env файлы
DB_PASS=supersecret
IMAGE_VERSION=1.2
version: "3.8"
services:
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASS}
- MYSQL_DATABASE=appdb
web:
image: "myorg/myweb:${IMAGE_VERSION}"
ports:
- "${HOST_PORT}:80"
environment:
- DB_HOST=db
- DB_PASS=${DB_PASS}
depends_on:
- db
1/3
image "myorg/myweb:${IMAGE_VERSION}" — позволяет менять версию образа
(например «1.2», «latest», «test-rc») без правки самого YAML.
3. Использование env_file
services:
web:
image: myorg/myweb
env_file:
- ./[Link]
ports:
- "8080:80"
2/3
.env по умолчанию можно коммитить в git, если вы не настроили исключения.
Если там пароли/токены — утечка.
Лучше хранить секреты отдельно (gitignore .env), использовать Vault, Docker
Secrets, или хотя бы private repo.
Любой, кто запускает Compose, увидит эти переменные. Также их можно
подсмотреть через docker inspect (в среде runtime) или docker-compose
config.
services:
db:
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_DATABASE: appdb
image: mysql:5.7
...
web:
image: "myorg/myweb:1.2"
ports:
- "8080:80"
...
Итог
.env файл — хранит пары KEY=value, которые Docker Compose считывает и
подставляет в [Link] (прямо в места с ${KEY}).
env_file: — ещё один способ указать, какие переменные окружения
передавать непосредственно внутрь контейнера.
docker-compose config — проверка, как итоговый YAML после подстановок
выглядит.
3/3
6.4 Практика
Техническое задание
Необходимо создать простую конфигурацию Docker Compose, которая запускает
веб-сервер, используя переменные из файла .env для настройки порта и версии
образа.
Структура проекта
Требования к решению
Проверка работы
1/2
1. Проверьте конфигурацию командой:
docker-compose config
docker-compose up -d
NGINX_VERSION=1.21-alpine
HOST_PORT=8000
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
container_name: web-server
ports:
- "${HOST_PORT}:80"
Полное решение
Файл .env:
NGINX_VERSION=1.21-alpine
HOST_PORT=8000
Файл [Link]:
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
container_name: web-server
ports:
- "${HOST_PORT}:80"
2/2
24 марта 2025 г.
6.4 Практика
Техническое задание
1. Создать веб-приложение, которое будет отображать информацию из
переменных окружения:
Базовый образ — nginx:alpine
Порт для проброса — должен настраиваться через переменную
окружения
Версия образа — должна настраиваться через переменную окружения
2. Настроить файл .env для хранения конфигурационных параметров
Структура проекта
1/4
<!DOCTYPE html>
<html>
<head>
<title>Тестирование .env в Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
.info {
margin-top: 20px;
padding: 10px;
background-color: #e7f3fe;
border-left: 3px solid #2196F3;
}
</style>
</head>
<body>
<div class="container">
<h1>Добро пожаловать!</h1>
<p>Эта страница обслуживается контейнером с Nginx.</p>
<div class="info">
<p><strong>Информация о контейнере:</strong></p>
<p>Вы успешно запустили Nginx через Docker Compose с использованием
.env файла!</p>
<p>Этот веб-сервер доступен через порт, указанный в .env.</p>
</div>
</div>
</body>
</html>
2/4
Проверка результата
1. Запустите сервис:
docker-compose up -d
docker-compose config
# Версия Nginx
NGINX_VERSION=alpine
# Имя контейнера
CONTAINER_NAME=env_web_server
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
container_name: ${CONTAINER_NAME}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
Полное решение
Файл .env:
# Версия Nginx
NGINX_VERSION=alpine
# Имя контейнера
CONTAINER_NAME=env_web_server
Файл [Link]:
3/4
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
container_name: ${CONTAINER_NAME}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
Проверка конфигурации:
docker-compose config
Запуск сервиса:
docker-compose up -d
docker-compose down
docker-compose up -d
4/4
6.4 Практика
Техническое задание
Структура проекта
1/5
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose и переменные окружения</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
text-align: center;
}
.info {
border-left: 4px solid #2980b9;
padding: 10px;
background-color: #ebf5fb;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Успешно настроено!</h1>
<div class="info">
<p>Если вы видите эту страницу, значит:</p>
<ul>
<li>Веб-сервер успешно запущен</li>
<li>Переменные окружения корректно настроены</li>
<li>Порты проброшены правильно</li>
</ul>
</div>
<p>Теперь вы можете проверить подключение к базе данных MySQL.</p>
</div>
</body>
</html>
Требования к решению
2/5
1. В .env файле должны быть определены следующие переменные:
NGINX_VERSION=1.21-alpine — версия Nginx
WEB_PORT=8080 — порт для веб-сервера
MYSQL_VERSION=5.7 — версия MySQL
MYSQL_PORT=3306 — порт для MySQL
MYSQL_ROOT_PASSWORD=rootpassword — пароль root
MYSQL_DATABASE=appdb — имя базы данных
MYSQL_USER=appuser — имя пользователя
MYSQL_PASSWORD=apppassword — пароль пользователя
2. В [Link] необходимо:
Настроить сервис web на базе образа nginx:${NGINX_VERSION}
Пробросить порт ${WEB_PORT}:80 для сервиса web
Монтировать локальную директорию ./html в /usr/share/nginx/html
контейнера web
Настроить сервис db на базе образа mysql:${MYSQL_VERSION}
Пробросить порт ${MYSQL_PORT}:3306 для сервиса db (опционально)
Передать переменные окружения MySQL, используя подстановку из .env
Настроить именованный том mysql-data для сохранения данных
3. Версия docker-compose должна быть не ниже 3.0
1. Файл [Link]
2. Файл .env
Проверка работы
docker-compose config
docker-compose up -d
3/5
NGINX_VERSION=1.21-alpine
WEB_PORT=8080
MYSQL_VERSION=5.7
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db
db:
image: mysql:${MYSQL_VERSION}
ports:
- "${MYSQL_PORT}:3306"
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
Полное решение
Файл .env:
NGINX_VERSION=1.21-alpine
WEB_PORT=8080
MYSQL_VERSION=5.7
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword
Файл [Link]:
4/5
version: "3"
services:
web:
image: nginx:${NGINX_VERSION}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db
db:
image: mysql:${MYSQL_VERSION}
ports:
- "${MYSQL_PORT}:3306"
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
5/5
6.5 Сети в Compose
1. Сеть по умолчанию
Если вы ничего не указываете, Compose создаёт сеть в стиле <проект>_default и
все сервисы автоматически к ней подключаются. Внутри этой сети сервисы видят
друг друга по DNS-именам, совпадающим с названием сервиса.
version: "3.8"
services:
db:
image: postgres:14
web:
image: nginx:latest
1/5
version: "3.8"
services:
frontend:
image: nginx:latest
networks:
- frontend_net
backend:
image: myorg/backend:1.0
networks:
- backend_net
- frontend_net
db:
image: postgres:14
networks:
- backend_net
networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge
Это полезно для изоляции сервисов: сервисы, которым не нужно общаться друг с
другом, можно подключить к разным сетям.
version: "3.8"
services:
app:
image: myorg/app
networks:
- existing_net
networks:
existing_net:
external: true
2/5
Compose не будет создавать или удалять её, а только подключит контейнер
app к сети.
Зачем это может быть нужно? Например, у вас есть внешний reverse proxy (Traefik
или Nginx) в другом Compose, и вы хотите, чтобы ваши контейнеры были в той же
сети, дабы автоматически обнаруживаться прокси.
4. Связь по DNS-названиям
Docker Compose поднимает встроенный DNS-сервер для сети. Если контейнеры
находятся в одной сети, они могут обращаться друг к другу по имени сервиса.
Пример:
services:
db:
image: mongo:4
api:
image: myorg/api
environment:
- DB_URL=mongodb://db:27017/mydb
Здесь переменная DB_URL указывает db как hostname. Работает, потому что db и api
в одной сети.
5. Настройка IP-адресов
3/5
networks:
custom_net:
driver: bridge
ipam:
config:
- subnet: [Link]/24
version: "3.8"
services:
frontend:
image: myorg/frontend
ports:
- "80:80"
networks:
- net_front
backend:
image: myorg/backend
networks:
- net_front
- net_back
db:
image: postgres:14
networks:
- net_back
networks:
net_front:
driver: bridge
net_back:
driver: bridge
7. Вывод
4/5
По умолчанию, Compose создаёт одну default сеть. Это удобно для быстрых
стартов, но для более сложных случаев — объявляйте и настраивайте
networks.
Множественные сети позволяют изолировать сервисы и организовать
архитектуру: одни видны только в private сети (DB, backend), другие в
public (frontend).
external сети подключают контейнеры к уже существующей network, что
полезно для интеграции с внешними системами или общим reverse proxy.
DNS-резолвинг внутри сети идёт по имени сервиса, если они в одной сети.
5/5
6.6 Практика
Техническое задание
Необходимо создать конфигурацию Docker Compose с тремя сервисами: веб-
сервер, API и база данных. Сервисы должны быть распределены по двум разным
сетям в соответствии со схемой безопасности.
Структура проекта
#!/bin/bash
1/4
2. Настройте подключение сервисов к соответствующим сетям согласно
требованиям.
3. Добавьте к сервисам команду, чтобы контейнеры не завершались сразу после
запуска:
Для web и db это не требуется (они работают в фоне)
Для api добавьте команду tail -f /dev/null
Проверка результата
1. Запустите сервисы:
docker-compose up -d
chmod +x [Link]
./[Link]
version: "3"
services:
web:
image: nginx:alpine
networks:
- frontend_network
api:
image: alpine
command: tail -f /dev/null
networks:
- ...
- ...
db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: example
networks:
- ...
networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge
2/4
Полное решение
Файл [Link]:
version: "3"
services:
web:
image: nginx:alpine
networks:
- frontend_network
api:
image: alpine
command: tail -f /dev/null
networks:
- frontend_network
- backend_network
db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: example
networks:
- backend_network
networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge
Объяснение:
3/4
Проверка сетевых подключений...
1. Проверка подключения web → api...
PING api ([Link]): 56 data bytes
64 bytes from [Link]: seq=0 ttl=64 time=0.116 ms
64 bytes from [Link]: seq=1 ttl=64 time=0.094 ms
Проверка завершена.
4/4
Настройка внешней сети и межсервисного взаимодействия в
Docker Compose
В этом задании вы научитесь настраивать несколько сетей в Docker Compose,
включая внешнюю сеть, и организуете правильное взаимодействие между
сервисами.
Техническое задание
Необходимо создать конфигурацию Docker Compose для веб-приложения,
состоящего из четырех сервисов: веб-сервер, API, база данных и кэш. Сервисы
должны быть размещены в разных сетях с соблюдением принципов безопасности.
Структура проекта
1/6
#!/bin/bash
Проверка результата
2/6
1. Запустите скрипт проверки:
chmod +x [Link]
./[Link]
docker network ls
networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge
3/6
version: "3"
services:
web:
image: nginx:alpine
networks:
- public_network
- app_network
api:
image: python:3.9-alpine
command: tail -f /dev/null
networks:
- ...
- ...
db:
image: postgres:13-alpine
environment:
POSTGRES_PASSWORD: example
networks:
- ...
cache:
image: redis:alpine
networks:
- ...
networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge
Полное решение
Файл [Link]:
4/6
version: "3"
services:
web:
image: nginx:alpine
networks:
- public_network
- app_network
api:
image: python:3.9-alpine
command: tail -f /dev/null
networks:
- app_network
- db_network
db:
image: postgres:13-alpine
environment:
POSTGRES_PASSWORD: example
networks:
- db_network
cache:
image: redis:alpine
networks:
- db_network
networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge
Объяснение:
5/6
Каждый сервис находится в правильных сетях
6/6
Создание многоуровневого приложения с изолированными
сетями
В этом задании вы научитесь настраивать изолированные сети в Docker Compose
для многоуровневого приложения, реализуя принцип разделения доступа.
Техническое задание
1. Сервис 1 (фронтенд):
Базовый образ — nginx:alpine
Должен быть доступен извне по порту 8080
Должен иметь доступ только к API-серверу, но не к базе данных
2. Сервис 2 (API-сервер):
Базовый образ — python:3.9-slim
Должен иметь доступ к базе данных
Должен быть виден фронтенду
Не должен быть доступен извне напрямую
3. Сервис 3 (база данных):
Базовый образ — postgres:13-alpine
Должна быть доступна только для API-сервера
Не должна быть доступна извне или для фронтенда
Структура проекта
Создайте следующую структуру файлов:
1/9
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
root /usr/share/nginx/html;
index [Link];
}
location /api/ {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
2/9
<!DOCTYPE html>
<html>
<head>
<title>Тестирование сетей в Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
.success {
color: green;
}
.error {
color: red;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>Тестирование сетей в Docker Compose</h1>
<div>
<button onclick="testApi()">Проверить API</button>
<button onclick="testDb()">Проверить прямой доступ к БД</button>
</div>
<script>
function testApi() {
[Link]('result').innerHTML = '<p>Проверка соединения
3/9
с API...</p>';
fetch('/api/status')
.then(response => [Link]())
.then(data => {
✅
[Link]('result').innerHTML =
`<p class="success"> Соединение с API успешно!</p>
<p>API ответил: ${[Link](data)}</p>`;
})
.catch(error => {
❌
[Link]('result').innerHTML =
`<p class="error"> Ошибка соединения с API:
${[Link]}</p>`;
});
}
function testDb() {
[Link]('result').innerHTML = '<p>Проверка прямого
соединения с БД...</p>';
fetch('/db-test')
.then(response => [Link]())
.then(data => {
❌
[Link]('result').innerHTML =
`<p class="error"> Прямое соединение с БД не должно быть
доступно, но получен ответ!</p>`;
})
.catch(error => {
✅
[Link]('result').innerHTML =
`<p class="success"> Правильно! Прямое соединение с БД
недоступно, что и ожидалось.</p>`;
});
}
</script>
</body>
</html>
4/9
from flask import Flask, jsonify
import psycopg2
import socket
import os
app = Flask(__name__)
@[Link]('/')
def index():
return jsonify({"message": "API работает!"})
@[Link]('/status')
def status():
# Получаем имя хоста для идентификации контейнера
hostname = [Link]()
return jsonify({
"status": "ok",
"container": hostname,
"db_status": db_status
})
if __name__ == '__main__':
[Link](host='[Link]', port=5000)
flask==2.0.1
psycopg2-binary==2.9.1
5/9
1. Создайте [Link] файл, в котором:
Определите три сервиса (frontend, api, db) согласно требованиям
Создайте две сети: frontend_network и backend_network
Подключите сервисы к сетям по схеме:
frontend → только к frontend_network
api → к обеим сетям
db → только к backend_network
Настройте проброс портов только для frontend (порт 8080)
2. Запустите Docker Compose и проверьте, что:
Фронтенд доступен по адресу [Link]
API доступен через фронтенд по адресу
[Link]
База данных не доступна напрямую из фронтенда
Проверка результата
1. Запустите сервисы:
docker-compose up -d
6/9
version: "3"
services:
frontend:
# настройка фронтенда...
networks:
- frontend_network
api:
# настройка API...
networks:
- frontend_network
- backend_network
db:
# настройка базы данных...
networks:
- backend_network
networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge
frontend:
image: nginx:alpine
volumes:
- ./frontend/[Link]:/etc/nginx/[Link]:ro
- ./frontend/html:/usr/share/nginx/html
ports:
- "8080:80"
networks:
- frontend_network
api:
build: ./api
# или можно использовать готовый образ python
# image: python:3.9-slim
# volumes, command и т.д.
networks:
- frontend_network
- backend_network
Полное решение
Файл [Link]:
7/9
version: "3"
services:
frontend:
image: nginx:alpine
container_name: network_frontend
volumes:
- ./frontend/[Link]:/etc/nginx/[Link]:ro
- ./frontend/html:/usr/share/nginx/html
ports:
- "8080:80"
networks:
- frontend_network
api:
image: python:3.9-slim
container_name: network_api
working_dir: /app
volumes:
- ./api:/app
command: sh -c "pip install -r [Link] && python [Link]"
environment:
- DB_HOST=db
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWORD=postgres
networks:
- frontend_network
- backend_network
db:
image: postgres:13-alpine
container_name: network_db
environment:
- POSTGRES_PASSWORD=postgres
volumes:
- db_data:/var/lib/postgresql/data
networks:
- backend_network
networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge
volumes:
db_data:
8/9
3. База данных будет изолирована в backend_network и доступна только для API-
сервиса.
Дополнительные задания
1. Добавьте еще один сервис adminer или pgadmin для управления базой данных,
который будет находиться в backend_network, но также будет доступен извне
через порт 8081.
2. Создайте третью сеть monitoring_network и добавьте сервис мониторинга
(например, Prometheus), который сможет собирать метрики со всех сервисов.
3. Настройте использование network_mode: host для одного из сервисов и
проверьте, как это влияет на изоляцию.
9/9
6.7 Рестарт-политики
version: "3.8"
services:
web:
image: nginx:latest
ports:
- "8080:80"
restart: always
1/3
3. Краткие примеры
on-failure:
services:
db:
image: postgres:14
restart: on-failure
Если Postgres аварийно упадёт из-за ошибки, Compose/ Docker попробует его
перезапустить. Но если вы вручную остановили контейнер, он не будет заново
запускаться.
unless-stopped:
2/3
services:
app:
build: .
restart: unless-stopped
5. Резюме
3/3
Настройка рестарт-политик для повышения
отказоустойчивости
В этом задании вы научитесь настраивать различные рестарт-политики в Docker
Compose для обеспечения стабильности работы сервисов.
Техническое задание
Структура проекта
#!/bin/sh
1/5
Сделайте скрипт исполняемым:
chmod +x app/[Link]
Проверка результатов
docker-compose up -d
docker-compose ps
docker-compose stop
docker-compose ps
2/5
Дополнительное задание (опционально)
Модифицируйте скрипт [Link] таким образом, чтобы он непрерывно наращивал
потребление памяти до тех пор, пока контейнер не будет остановлен из-за нехватки
ресурсов. Проверьте, как работает рестарт-политика on-failure в этом случае.
Подсказка ([Link])
version: "3.8"
services:
web:
image: nginx:alpine
restart: always
ports:
- "8080:80"
app:
image: alpine:latest
restart: on-failure
volumes:
- ./app:/app
working_dir: /app
command: ./[Link]
db:
image: mysql:5.7
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: testdb
Полное решение
Файл [Link]:
3/5
version: "3.8"
services:
web:
image: nginx:alpine
restart: always
ports:
- "8080:80"
container_name: restart-web
app:
image: alpine:latest
restart: on-failure
volumes:
- ./app:/app
working_dir: /app
command: ./[Link]
container_name: restart-app
db:
image: mysql:5.7
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: testdb
container_name: restart-db
Выполнение тестов:
docker-compose up -d
docker-compose ps
4/5
4. Тест 2: Проверка политики always и unless-stopped
Останавливаем все сервисы:
docker-compose stop
Перезапускаем Docker:
docker-compose ps
5/5
Тестирование различных рестарт-политик в Docker Compose
В этом задании вы научитесь настраивать и тестировать различные рестарт-
политики в Docker Compose, чтобы обеспечить отказоустойчивость ваших
сервисов.
Техническое задание
Вы создадите несколько сервисов с разными рестарт-политиками и проверите их
поведение в различных сценариях.
Структура проекта
1/8
#!/bin/sh
echo "Запуск сервиса с успешным завершением..."
echo "Сервис будет работать 10 секунд, затем завершится с кодом 0 (успех)"
# Работаем 10 секунд
sleep 10
#!/bin/sh
echo "Запуск сервиса с аварийным завершением..."
echo "Сервис будет работать 10 секунд, затем завершится с кодом 1 (ошибка)"
# Работаем 10 секунд
sleep 10
#!/usr/bin/env python3
import [Link]
import socketserver
import os
import time
PORT = 8000
class Handler([Link]):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
message = f"""
2/8
FROM python:3.9-slim
WORKDIR /app
COPY [Link] .
RUN chmod +x [Link]
EXPOSE 8000
CMD ["./[Link]"]
План тестирования
3/8
version: "3"
services:
# Сервис с политикой no restart
no-restart-success:
image: alpine:latest
container_name: no-restart-success
volumes:
- ./test-success:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=no
restart: "no"
no-restart-failure:
image: alpine:latest
container_name: no-restart-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=no
restart: "no"
on-failure-failure:
image: alpine:latest
container_name: on-failure-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=on-failure
restart: on-failure
4/8
command: sh [Link]
environment:
- RESTART_POLICY=always
restart: always
always-failure:
image: alpine:latest
container_name: always-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=always
restart: always
Проверка результатов
Выполните следующие действия для проверки работы рестарт-политик:
1. Запустите контейнеры:
docker-compose logs -f
docker-compose ps
5/8
4. Ожидаемый результат:
no-restart-success — запустится, отработает и остановится
on-failure-success — запустится, отработает и остановится (НЕ
перезапустится, так как код возврата 0)
always-success — будет постоянно перезапускаться после успешного
завершения
1. Запустите контейнеры:
docker-compose logs -f
docker-compose ps
4. Ожидаемый результат:
no-restart-failure — запустится, отработает и остановится с ошибкой
on-failure-failure — будет перезапускаться после аварийного
завершения
always-failure — будет перезапускаться после аварийного завершения
1. Запустите веб-серверы:
6/8
6. Перезапустите Docker:
# На Linux
sudo systemctl restart docker
# На Windows/Mac
# Перезапустите Docker Desktop
docker-compose ps
8. Ожидаемый результат:
always-server — должен запуститься автоматически после перезапуска
Docker
unless-stopped-server — НЕ должен запуститься автоматически, так как
был остановлен вручную
Таблица результатов
Дополнительные задания
7/8
3. Настройте мониторинг перезапусков контейнеров с помощью команды docker
events
8/8
6.9 Профили в Compose
1. Профили (profiles)
С версии 3.9+ формата Docker Compose (и Docker Compose V2) можно
использовать профили (profiles), чтобы избирательно запускать некоторые
сервисы. Это удобно, когда у вас в [Link] прописано много сервисов,
но не все нужны в каждой среде.
version: "3.9"
services:
db:
image: postgres:14
profiles:
- default
cache:
image: redis:latest
profiles:
- dev
web:
image: nginx:latest
profiles:
- default
- dev
Это значит, что Compose поднимет все сервисы у которых profiles: [dev]
ИЛИ profiles: [default] (профиль default включен всегда).
1/2
Таким образом, вы можете выключить или включить часть сервисов, не правя
постоянно YAML. Удобно для разных окружений (dev, test, staging), или для
вспомогательных сервисов (логирование, мониторинг), которые не нужны во
всех средах.
2/2
6.10 Практика
Техническое задание
Необходимо создать Docker Compose конфигурацию для веб-приложения, которое
будет работать в трех разных режимах:
Структура проекта
Требования к решению
1. Используйте Docker Compose формата версии 3.9 или выше
2. Настройте профили для каждого сервиса в соответствии с требованиями
3. Сервис app и db должны запускаться во всех профилях
4. Сервис adminer должен запускаться только в профилях dev и test
5. Сервис cache должен запускаться в профилях dev и prod
6. Сервис test-runner должен запускаться только в профиле test
Проверка результатов
1/5
1. Запуск всех сервисов (режим разработки):
docker-compose ps
docker-compose down
docker-compose ps
docker-compose down
docker-compose ps
app ✅ ✅ ✅
db ✅ ✅ ✅
adminer ✅ ✅ ❌
cache ✅ ❌ ✅
test-runner ❌ ✅ ❌
Подсказка (структура [Link])
2/5
version: "3.9"
services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
# другие настройки...
db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
# другие настройки...
# Остальные сервисы...
Полное решение
3/5
version: "3.9"
services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
ports:
- "8080:80"
db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: app_db
adminer:
image: adminer:latest
profiles:
- dev
- test
ports:
- "8081:8080"
cache:
image: redis:alpine
profiles:
- dev
- prod
ports:
- "6379:6379"
test-runner:
image: alpine:latest
profiles:
- test
command: echo "Running tests..." && sleep 10 && echo "Tests completed!"
4/5
Напишите текст
Верно решили 10 учащихся
15 баллов за решение.
5/5
6.10 Практика
Техническое задание
Необходимо создать Docker Compose конфигурацию для веб-приложения, которое
будет работать в трех разных режимах:
Структура проекта
Требования к решению
1. Используйте Docker Compose формата версии 3.9 или выше
2. Настройте профили для каждого сервиса в соответствии с требованиями
3. Сервис app и db должны запускаться во всех профилях
4. Сервис adminer должен запускаться только в профилях dev и test
5. Сервис cache должен запускаться в профилях dev и prod
6. Сервис test-runner должен запускаться только в профиле test
Проверка результатов
1/5
1. Запуск всех сервисов (режим разработки):
docker-compose ps
docker-compose down
docker-compose ps
docker-compose down
docker-compose ps
app ✅ ✅ ✅
db ✅ ✅ ✅
adminer ✅ ✅ ❌
cache ✅ ❌ ✅
test-runner ❌ ✅ ❌
Подсказка (структура [Link])
2/5
version: "3.9"
services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
# другие настройки...
db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
# другие настройки...
# Остальные сервисы...
Полное решение
3/5
version: "3.9"
services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
ports:
- "8080:80"
db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: app_db
adminer:
image: adminer:latest
profiles:
- dev
- test
ports:
- "8081:8080"
cache:
image: redis:alpine
profiles:
- dev
- prod
ports:
- "6379:6379"
test-runner:
image: alpine:latest
profiles:
- test
command: echo "Running tests..." && sleep 10 && echo "Tests completed!"
4/5
5/5
6.11 Override, секция build
version: "3.8"
services:
api:
build: .
ports:
- "5000:5000"
version: "3.8"
services:
app:
build:
context: ./app
dockerfile: [Link]
args:
ENVIRONMENT: "production"
image: myorg/app:latest
ports:
- "8080:8080"
1/4
context: директория, передаваемая в качестве контекста (аналог docker build
./app).
dockerfile: имя файла Dockerfile (если не Dockerfile по умолчанию).
args: build-аргументы (через ARG внутри Dockerfile). Здесь
ENVIRONMENT=production.
image: имя образа (например, myorg/app:latest). Полезно, если вы
планируете потом docker push.
2.2 Пример
[Link] (базовый):
version: "3.8"
services:
web:
image: myorg/webapp:1.0
container_name: webapp
ports:
- "8080:80"
[Link]:
2/4
version: "3.8"
services:
web:
# Вместо готового образа — build-секция (например, для локальной разработки)
build:
context: .
dockerfile: [Link]
environment:
- APP_ENV=development
3. Итоги
3/4
[Link] (и вообще несколько YAML-файлов) позволяют
хранить разные конфигурации (prod/dev) без постоянного редактирования
одного файла.
При docker-compose up файлы автоматически объединяются (если
override-файл назван точно [Link]).
Явный флаг -f помогает комбинировать несколько файлов или
переопределить имя override-файла.
4/4
Расширенная конфигурация Docker Compose
В этом задании вы научитесь использовать расширенные возможности Docker
Compose, включая секцию build для сборки образов и разделение конфигурации на
несколько YAML-файлов.
Техническое задание
Вам необходимо разработать конфигурацию Docker Compose для простого веб-
приложения, которое будет работать в двух режимах:
Структура проекта
project/
├── app/
│ ├── [Link]
│ └── [Link]
├── [Link]
├── [Link]
├── [Link]
└── [Link]
1/6
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose Demo</title>
<link rel="stylesheet" href="[Link]">
</head>
<body>
<div class="container">
<h1>Привет из Docker!</h1>
<p>Это демонстрация расширенной конфигурации Docker Compose.</p>
<p>Режим: <span id="environment">неизвестно</span></p>
</div>
<script>
// Читаем переменную окружения из мета-тега
const env = [Link]('meta[name="environment"]');
if (env) {
[Link]('environment').textContent =
[Link]('content');
}
</script>
</body>
</html>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
h1 {
color: #333;
}
#environment {
font-weight: bold;
}
2/6
FROM nginx:alpine
ENTRYPOINT ["/[Link]"]
CMD ["nginx", "-g", "daemon off;"]
FROM nginx:alpine
EXPOSE 80
#!/bin/bash
echo '<meta name="environment" content="development">' >>
/usr/share/nginx/html/[Link]
exec "$@"
Требования к решению
1. Создайте базовый файл [Link] для продакшен-режима,
который:
Использует секцию build для сборки образа с [Link]
Указывает тег для собранного образа: myapp:prod
Пробрасывает порт 80 контейнера на порт 8080 хоста
2. Создайте файл [Link] для режима разработки,
который:
Переопределяет секцию build, чтобы использовать [Link]
Монтирует локальную директорию ./app в контейнер на путь
/usr/share/nginx/html
Изменяет тег образа на myapp:dev
Пробрасывает порт 80 контейнера на порт 8000 хоста (вместо 8080)
3/6
Проверка результатов
docker-compose up -d
1. Остановите контейнеры:
docker-compose down
docker-compose -f [Link] up -d
Проверка конфигурации
docker-compose config
4/6
version: "3.8"
services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:prod
ports:
- "8080:80"
version: "3.8"
services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:dev
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html
Полное решение
[Link]:
version: "3.8"
services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:prod
ports:
- "8080:80"
[Link]:
version: "3.8"
services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:dev
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html
5/6
При запуске с командой docker-compose up будет использоваться конфигурация из
обоих файлов, причем настройки из [Link] перекроют
настройки из [Link]. В результате будет использоваться
[Link], порт 8000 и монтирование локальной директории.
6/6
6.13 Балансировка и --scale
services:
web:
image: myorg/webapp
ports:
- "8080:80"
Один контейнер web слушает внутри на 80 порту, а наружу отдаёт 8080. Всё
ок.
Если вы делаете docker-compose up --scale web=3, у вас будет web_1, web_2,
web_3. Но только один из них сможет занять 8080 на хосте. Остальные либо
не запустятся (ошибка), либо Compose назначит им случайные порты
(например 49153, 49154), что запутает маршрутизацию.
1/3
Решение: использовать балансировщик (reverse proxy) в качестве входной точки. В
этом случае вы не публикуете порт у каждого контейнера, а даёте прокси доступ к
контейнерам по внутренней сети. Он может раздавать запросы равномерно по
разным web.
version: "3.8"
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./[Link]:/etc/nginx/[Link]:ro
depends_on:
- webapp # Нужен, чтобы запустить Nginx после webapp
networks:
- appnet
webapp:
image: myorg/webapp
networks:
- appnet
networks:
appnet:
driver: bridge
http {
upstream webapp_cluster {
# тут может быть dns-резолвинг "webapp" => нескольких IP
server webapp:80;
}
server {
listen 80;
location / {
proxy_pass [Link]
}
}
}
2/3
Будет один контейнер nginx (публикующий порт 8080 на хосте), и три
контейнера webapp.
Все они в сети appnet.
Если Nginx умеет резолвить webapp в несколько IP, то запросы будут
распределяться.
5. Выводы
3/3
Практика: Настройка балансировки трафика и
масштабирование сервисов в Docker Compose
В этой практике вы научитесь масштабировать сервисы с помощью опции --scale в
Docker Compose и настраивать балансировку трафика через Nginx.
Техническое задание
Структура проекта
project/
├── app/
│ ├── Dockerfile
│ └── [Link]
├── nginx/
│ └── [Link]
└── [Link]
Шаги выполнения
1. Создание веб-приложения
1/7
from flask import Flask
import socket
import os
import time
app = Flask(__name__)
@[Link]('/')
def hello():
global request_count
request_count += 1
return f"""
<html>
<head>
<title>Docker Scaling Demo</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; background-
color: #f8f9fa; }}
.container {{ background-color: white; padding: 20px; border-radius:
5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; }}
.info {{ background-color: #e2f2ff; padding: 15px; border-radius: 5px;
margin-top: 20px; }}
.counter {{ background-color: #ffeeba; padding: 15px; border-radius:
5px; margin-top: 20px; }}
</style>
</head>
<body>
<div class="container">
<h1>Привет из контейнера!</h1>
<div class="info">
<p><strong>Хостнейм (ID контейнера):</strong> {hostname}</p>
<p><strong>Время сервера:</strong> {[Link]('%Y-%m-%d
%H:%M:%S')}</p>
</div>
<div class="counter">
<p><strong>Счетчик запросов к этому контейнеру:</strong>
{request_count}</p>
</div>
</div>
</body>
</html>
"""
if __name__ == '__main__':
[Link](host='[Link]', port=5000)
2/7
Создайте файл app/Dockerfile со следующим содержимым:
FROM python:3.9-slim
WORKDIR /app
COPY [Link] .
EXPOSE 5000
3/7
user nginx;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /etc/nginx/[Link];
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream app_servers {
server webapp:5000;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
3. Создание [Link]
4/7
version: "3.8"
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx/[Link]:/etc/nginx/[Link]:ro
depends_on:
- webapp
networks:
- app_network
webapp:
build:
context: ./app
networks:
- app_network
networks:
app_network:
driver: bridge
Запуск и тестирование
docker-compose up -d
2. Масштабирование веб-приложения
3. Проверка балансировки
docker-compose ps
5/7
Для наблюдения за распределением запросов можно выполнить команду:
Дополнительные задания
1. Измените стратегию балансировки: отредактируйте [Link], добавив
алгоритм round-robin или ip_hash.
2. Добавьте sticky sessions: настройте Nginx так, чтобы один клиент всегда
попадал на один и тот же экземпляр webapp.
3. Добавьте health-check: модифицируйте конфигурацию Nginx, добавив
проверку здоровья для сервисов webapp.
Ожидаемые результаты
Подсказки и советы
upstream app_servers {
ip_hash; # Или можно использовать: least_conn; или weight
server webapp:5000;
}
6/7
upstream app_servers {
ip_hash; # Простой способ реализации sticky sessions по IP
server webapp:5000;
}
7/7
7.1 Что такое Docker Registry
Определение и назначение
Проще говоря, Registry для Docker-образов — это то же самое, что GitHub для кода:
место, где вы храните свои артефакты и делитесь ими с другими.
Типы реестров
1. Публичные реестры
1/4
GitHub Container Registry — реестр от GitHub, интегрированный с GitHub
Actions
2. Приватные реестры
2/4
Облачные провайдеры
Локальные реестры — собственный регистри на базе open-source Docker
Registry или Harbor
Именование образов
Структура полного имени образа в реестре выглядит так:
registry-host:port/username/repository:tag
Например:
3/4
Для работы с приватными образами нужно авторизоваться в реестре:
После ввода учётных данных Docker сохраняет ваш токен авторизации в файле
~/.docker/[Link], и последующие команды pull и push будут работать с этим
реестром автоматически.
Итог
Docker Registry — это хранилище Docker-образов, позволяющее
распространять и версионировать образы
Docker Hub — основной публичный реестр, но есть и множество альтернатив
С реестрами работают через docker pull, docker push и docker tag
Локальные реестры полезны для корпоративных окружений и CI/CD
интеграции
4/4
7.2 Практика
Техническое задание
Структура проекта
Подготовьте следующую структуру файлов:
1/4
<!DOCTYPE html>
<html>
<head>
<title>Мой первый опубликованный Docker-образ</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px 30px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #2474b5;
border-bottom: 2px solid #eaeaea;
padding-bottom: 10px;
}
.success {
background-color: #d4edda;
color: #155724;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Образ успешно опубликован в Docker Hub!</h1>
<p>Это страница из образа, который вы опубликовали в Docker Hub.</p>
<div class="success">
<strong>Поздравляем!</strong> Теперь вы умеете работать с Docker Hub и
управлять образами в реестре.
</div>
</div>
</body>
</html>
2/4
FROM nginx:alpine
COPY app/[Link] /usr/share/nginx/html/[Link]
LABEL maintainer="Ваше имя <ваш.email@[Link]>"
LABEL description="Простой образ для демонстрации работы с Docker Hub"
EXPOSE 80
Задание
1. Зарегистрируйтесь на Docker Hub
2. Авторизуйтесь в Docker CLI
3. Соберите Docker-образ на основе предоставленного Dockerfile
4. Загрузите собранный образ в свой репозиторий на Docker Hub
5. Проверьте доступность образа через веб-интерфейс Docker Hub
6. Удалите локальный образ
7. Загрузите образ из Docker Hub
8. Запустите контейнер из загруженного образа на порту 8080
9. Проверьте работу запущенного контейнера
Требования к решению
# Публикация образа
docker push username/my-first-repo:latest
# Запуск контейнера
docker run -d -p 8080:80 username/my-first-repo:latest
3/4
4/4
7.3 Локальный registry
1/6
3. Загрузить (pull) образ из вашего реестра:
{"repositories":["my-nginx"]}
Наш базовый реестр работает без шифрования (HTTP), что подходит только для
тестирования на локальной машине. Docker по умолчанию блокирует push/pull
операции с небезопасными реестрами, кроме localhost.
1. Подготовка сертификатов
Вам понадобятся SSL-сертификат и приватный ключ. Для тестовой среды
можно создать самоподписанный сертификат:
2/6
2. Запуск Registry с поддержкой HTTPS
Ограничение доступа
3/6
3. Аутентификация для push/pull
Теперь перед работой с реестром нужно аутентифицироваться:
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true" # Включает возможность удаления
образов
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always
4/6
3. Удаление образа (требует включения
REGISTRY_STORAGE_DELETE_ENABLED)
Расширенные возможности
Docker Registry поддерживает множество дополнительных опций, которые могут
быть полезны в продакшн-окружении:
-e REGISTRY_PROXY_REMOTEURL=[Link]
Производительные альтернативы
5/6
Официальный Docker Registry хорош для небольших и средних команд, но для
крупных организаций существуют более функциональные альтернативы:
Итог
Docker Registry позволяет создать собственное хранилище образов с полным
контролем
Для продакшн-использования обязательно настройте TLS и
аутентификацию
Постоянное хранение обеспечивается с помощью volumes или внешних
хранилищ
Docker Registry предоставляет API для управления образами и интеграции с
другими системами
Для корпоративного использования стоит рассмотреть альтернативы с
расширенными возможностями
6/6
7.4 Практика
Техническое задание
1. Локальный реестр:
Развернуть Docker Registry с помощью docker-compose
Обеспечить постоянное хранение данных
Настроить базовую HTTP-аутентификацию
Настроить TLS с самоподписанным сертификатом
2. Работа с реестром:
Авторизоваться в реестре
Загрузить образ в реестр
Скачать образ из реестра
Просмотреть список доступных образов через API
Подготовка инфраструктуры
registry-project/
├── [Link]
├── auth/ # Будет содержать файлы аутентификации
├── certs/ # Будет содержать SSL сертификаты
└── data/ # Будет содержать данные реестра
Задания
1/3
3. Создание конфигурации docker-compose
4. Работа с реестром
Требования к решению
Подсказки
# Linux/macOS
sudo sh -c 'echo "[Link] [Link]" >> /etc/hosts'
2/3
Создание сертификатов
mkdir -p certs
cd certs
openssl genrsa -out [Link] 2048
openssl req -new -x509 -sha256 -key [Link] -out [Link] -days 365 -subj
"/CN=[Link]"
Пример [Link]
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always
3/3
7.5 API Registry
Аутентификация в API
1/10
# Сначала получаем токен
TOKEN=$(curl -s -H "Authorization: Basic $(echo -n 'username:password' | base64)"
\
"[Link]
service=[Link]&scope=repository:nginx:pull" \
| jq -r '.token')
Для работы с образом часто нужно получить его манифест и digest (хэш):
Удаление образа
2/10
#!/bin/bash
# [Link]
# Переменные
REGISTRY_URL="[Link]
USERNAME="admin"
PASSWORD="password"
REPO="nginx"
TAG="old-tag"
# Получение digest
DIGEST=$(curl -s -H "Accept: application/[Link].v2+json"
\
--user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$TAG | jq -r '.[Link]')
# Удаление образа
curl -X DELETE --user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$DIGEST
3/10
stages:
- build
- test
- deploy
variables:
REGISTRY_URL: [Link]
IMAGE_NAME: $REGISTRY_URL/myapp
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
# Сборка образа
build:
stage: build
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker push $IMAGE_NAME:$IMAGE_TAG
after_script:
- docker logout $REGISTRY_URL
# Тестирование образа
test:
stage: test
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker run --rm $IMAGE_NAME:$IMAGE_TAG ./run_tests.sh
after_script:
- docker logout $REGISTRY_URL
# Деплой на продакшн
deploy:
stage: deploy
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
- docker push $IMAGE_NAME:latest
- curl -X POST [Link]
after_script:
- docker logout $REGISTRY_URL
4/10
only:
- main
В этом примере:
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Deploy
if: [Link] == 'refs/heads/main'
run: |
curl -X POST [Link]
5/10
Автоматизация очистки старых образов
#!/bin/bash
# [Link]
REGISTRY_URL="[Link]
USERNAME="admin"
PASSWORD="password"
REPO="myapp"
KEEP_LAST=5
# Удаление образа
curl -X DELETE --user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$DIGEST
done
Этот скрипт можно запускать как отдельный job в CI/CD пайплайне или по
расписанию через cron.
6/10
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_NAME: webhook
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_URL:
[Link]
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_HEADERS_Authorization: "Bearer
secrettoken"
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_TIMEOUT: 500ms
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_THRESHOLD: 5
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_BACKOFF: 1s
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always
7/10
// Простой пример на [Link]
const express = require('express');
const { exec } = require('child_process');
const app = express();
[Link]([Link]());
// Проверка авторизации
if (authHeader !== 'Bearer secrettoken') {
return [Link](401).send('Unauthorized');
}
[Link](200).send('Webhook обработан');
});
[Link](3000, () => {
[Link]('Webhook-сервер запущен на порту 3000');
});
8/10
# Стадия сборки
FROM node:14-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Финальный образ
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# В CI/CD пайплайне
VERSION=$(cat VERSION)
GIT_COMMIT=$(git rev-parse --short HEAD)
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
version: '3'
services:
registry-mirror:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_PROXY_REMOTEURL: [Link]
volumes:
- ./mirror-data:/var/lib/registry
restart: always
9/10
После настройки зеркала нужно добавить его в конфигурацию Docker на клиентских
машинах (/etc/docker/[Link]):
{
"registry-mirrors": ["[Link]
}
10/10
8.1 Ограничение ресурсов и настройка cgroups
Важно понимать, что cgroups — это один из двух основных механизмов изоляции в
Docker (второй — namespaces, отвечающий за изоляцию процессов, сетей,
файловых систем).
Ограничение CPU
Docker предоставляет несколько способов ограничения использования CPU:
1. --cpus
Самый простой и понятный способ — указать, сколько полных ядер CPU может
использовать контейнер:
1/6
2. --cpu-shares
3. --cpu-period и --cpu-quota
Эти два параметра работают в паре для более тонкой настройки ограничений CPU:
4. --cpuset-cpus
Ограничение памяти
1. --memory
2/6
Контейнер в этом примере не сможет использовать больше 1 гигабайта памяти.
Если процессы внутри контейнера попытаются выделить больше памяти, они могут
быть завершены OOM-киллером (Out-Of-Memory Killer).
2. --memory-swap
3. --memory-reservation
4. --memory-swappiness
5. --oom-kill-disable
3/6
Контейнеры могут интенсивно использовать дисковую подсистему, что может
негативно влиять на работу других контейнеров и хоста. Docker позволяет
ограничивать скорость чтения и записи на диск.
1. --device-read-bps и --device-write-bps
2. --device-read-iops и --device-write-iops
Этот контейнер сможет выполнять не более 1000 операций чтения и 500 операций
записи в секунду на устройстве /dev/sda.
Настройка Ulimits
В этом примере:
4/6
nproc — максимальное количество процессов
rss — максимальный размер резидентной памяти (в байтах)
rtprio — максимальный приоритет реального времени
rttime — максимальное время CPU реального времени (в микросекундах)
sigpending — максимальное количество ожидающих сигналов
stack — максимальный размер стека (в байтах)
docker run \
--name limited-container \
--cpus=1.5 \
--cpu-shares=512 \
--memory=1g \
--memory-swap=1.5g \
--memory-reservation=800m \
--device-read-bps /dev/sda:10mb \
--device-write-bps /dev/sda:8mb \
--ulimit nofile=1024:4096 \
--ulimit nproc=50:100 \
nginx
5/6
Команда docker inspect покажет все настройки контейнера, включая
установленные ограничения ресурсов.
Также можно проверить cgroups напрямую в файловой системе (если ваша система
использует cgroupfs):
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.cfs_quota_us
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
version: '3'
services:
webapp:
image: nginx
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
ulimits:
nofile:
soft: 1024
hard: 4096
Итог
6/6
Управление ресурсами контейнеров на практике
В этом практическом задании вы научитесь настраивать и тестировать различные
ограничения ресурсов для Docker-контейнеров, а также создадите окружение с
несколькими контейнерами с разными приоритетами ресурсов.
Техническое задание
Компания разрабатывает микросервисное приложение, и вам необходимо
настроить тестовое окружение с правильным распределением ресурсов между
сервисами, исходя из их важности и характера работы. Вам нужно создать Docker
Compose конфигурацию для следующих сервисов:
Структура проекта
1/8
Подготовьте следующую структуру файлов:
import time
import multiprocessing
def cpu_intensive_task():
"""Функция, создающая нагрузку на CPU."""
start_time = [Link]()
# Бесконечный цикл для загрузки CPU
while True:
# Вычисляем много чисел Фибоначчи
fibonacci(35)
def fibonacci(n):
"""Рекурсивное вычисление числа Фибоначчи (намеренно неэффективное)."""
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
if __name__ == "__main__":
print(f"[{[Link]('%H:%M:%S')}] Starting CPU stress test...")
# Ждем завершения всех процессов (не произойдет, так как они бесконечные)
for p in processes:
[Link]()
2/8
#!/bin/bash
chmod +x load_test.sh
3/8
1. [Link] — файл, который запускает все четыре сервиса с
указанными ограничениями ресурсов.
2. Проверить работу композа:
docker-compose up -d
# Или для более новых версий Docker
docker compose up -d
3. Проверка работы:
Запустите скрипт ./load_test.sh для проверки ограничений ресурсов
Выполните команду docker stats для наблюдения за использованием
ресурсов в реальном времени
Проверьте, что API-сервис получает больше ресурсов CPU, чем сервис
обработки данных
Проверьте, что фоновый сервис получает наименьший приоритет при
конкуренции за CPU
Требования к решению
Все сервисы должны быть в одной сети для обеспечения связи между ними.
База данных должна сохранять данные между перезапусками контейнеров
(использовать именованный том).
Все ограничения ресурсов должны быть правильно настроены согласно
техническому заданию.
Версия docker-compose файла должна быть не ниже 3.0.
Для тестирования I/O ограничений вы должны указать правильное устройство
(например, /dev/sda) в конфигурации.
4/8
version: "3.8"
services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
networks:
- resource-test-net
data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
networks:
- resource-test-net
background:
image: python:3.9-slim
container_name: background-service
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
memory: 128M
# cpu-shares требует особого подхода в новых версиях Docker
networks:
- resource-test-net
db:
image: postgres:13-alpine
container_name: limited-db
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=testuser
- POSTGRES_PASSWORD=testpass
volumes:
- db-data:/var/lib/postgresql/data
# device read/write bps также требуют особого подхода
5/8
networks:
- resource-test-net
volumes:
db-data:
networks:
resource-test-net:
Полное решение
6/8
version: "3.8"
services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
networks:
- resource-test-net
data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
networks:
- resource-test-net
background:
image: python:3.9-slim
container_name: background-service
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
# Для cpu-shares в docker-compose v3 мы используем специальный подход
# Так как [Link] не поддерживает cpu-shares напрямую
# Мы используем командную строку docker вместо docker-compose
# В реальном проекте следует запустить отдельно:
# docker update --cpu-shares=256 background-service
deploy:
resources:
limits:
memory: 128M
networks:
- resource-test-net
db:
image: postgres:13-alpine
container_name: limited-db
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=testuser
7/8
- POSTGRES_PASSWORD=testpass
volumes:
- db-data:/var/lib/postgresql/data
# Для device-read-bps и device-write-bps в docker-compose v3
# также нет прямой поддержки в секции deploy
# В реальном проекте следует запустить отдельно:
# docker update --device-read-bps /dev/sda:20mb --device-write-bps
/dev/sda:10mb limited-db
networks:
- resource-test-net
volumes:
db-data:
networks:
resource-test-net:
version: "2.4"
services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
cpus: 1.0
mem_limit: 512m
networks:
- resource-test-net
data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.
8/8
8.3 Мониторинг использования ресурсов
docker stats
1/12
Нет долгосрочного хранения метрик и их истории
Ограниченная детализация
Отсутствие визуализации и алертинга
Нет информации о хост-системе
Prometheus
Prometheus — это система мониторинга с открытым исходным кодом, которая:
Grafana
2/12
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/[Link]:/etc/prometheus/[Link]
- prometheus_data:/prometheus
command:
- '--[Link]=/etc/prometheus/[Link]'
- '--[Link]=/prometheus'
- '--[Link]=/etc/prometheus/console_libraries'
- '--[Link]=/etc/prometheus/consoles'
- '--[Link]-lifecycle'
restart: unless-stopped
networks:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
restart: unless-stopped
depends_on:
- prometheus
networks:
- monitoring
volumes:
prometheus_data:
grafana_data:
networks:
monitoring:
driver: bridge
3/12
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
В Grafana нужно:
4/12
cadvisor:
image: [Link]/cadvisor/cadvisor:latest
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
restart: unless-stopped
networks:
- monitoring
Node Exporter
Загрузка CPU
Использование памяти
Использование дискового пространства
Сетевая статистика
Информация о файловой системе
Температура и другие аппаратные метрики
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--[Link]=/host/proc'
- '--[Link]=/host/sys'
- '--[Link]=/rootfs'
- '--[Link]-mount-points=^/(sys|proc|dev|host|etc)
($$|/)'
restart: unless-stopped
networks:
- monitoring
5/12
Теперь у нас есть полная система мониторинга:
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
ports:
- "9093:9093"
volumes:
- ./alertmanager/[Link]:/etc/alertmanager/[Link]
restart: unless-stopped
networks:
- monitoring
global:
resolve_timeout: 5m
smtp_smarthost: '[Link]'
smtp_from: 'alertmanager@[Link]'
smtp_auth_username: 'username'
smtp_auth_password: 'password'
route:
group_by: ['alertname', 'instance', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'email-notifications'
receivers:
- name: 'email-notifications'
email_configs:
- to: 'admin@[Link]'
send_resolved: true
6/12
Обновим конфигурацию Prometheus для работы с Alertmanager и добавим правила
алертинга:
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
rule_files:
- "/etc/prometheus/rules/*.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
7/12
groups:
- name: container_alerts
rules:
- alert: ContainerCpuUsageHigh
expr: (sum(rate(container_cpu_usage_seconds_total{name!=""}[5m])) by (name) *
100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Container CPU usage high (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} CPU usage is above 80% for 5
minutes\n VALUE = {{ $value }}%"
- alert: ContainerMemoryUsageHigh
expr: (container_memory_usage_bytes{name!=""} /
container_spec_memory_limit_bytes{name!=""} * 100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Container memory usage high (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} memory usage is above 80% for 5
minutes\n VALUE = {{ $value }}%"
- alert: ContainerNearMemoryLimit
expr: (container_memory_usage_bytes{name!=""} /
container_spec_memory_limit_bytes{name!=""} * 100) > 95
for: 5m
labels:
severity: critical
annotations:
summary: "Container near memory limit (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} is using more than 95% of its
memory limit for 5 minutes\n VALUE = {{ $value }}%"
8/12
4. Настройте условия (например, "WHEN avg() OF query(A, 5m, now) IS ABOVE
80")
5. Настройте уведомления (в разделе Notifications)
docker run --rm -it --cpus=0.5 --name=cpu-test alpine sh -c "apk add --no-cache
stress-ng && stress-ng --cpu 4 --timeout 60s"
9/12
Тестирование ограничений I/O
Комплексное тестирование
version: '3.8'
services:
high-cpu:
image: alpine
container_name: high-cpu
command: sh -c "apk add --no-cache stress-ng && stress-ng --cpu 2 --timeout
1h"
deploy:
resources:
limits:
cpus: '0.7'
memory: 100M
high-memory:
image: python:alpine
container_name: high-memory
command: python -c "import numpy as np; data = [Link](80 * 1024 * 1024,
dtype=np.uint8); import time; [Link](3600)"
deploy:
resources:
limits:
cpus: '0.3'
memory: 100M
balanced:
image: alpine
container_name: balanced
command: sh -c "apk add --no-cache stress-ng && stress-ng --cpu 1 --vm 1 --vm-
bytes 50M --timeout 1h"
deploy:
resources:
limits:
cpus: '0.5'
memory: 70M
10/12
Запустите этот compose-файл и наблюдайте за поведением системы через docker
stats или Prometheus/Grafana. Обратите внимание, как Docker распределяет
ресурсы между контейнерами и как система реагирует, когда контейнеры
приближаются к своим лимитам или превышают их.
Итог
docker stats — простой встроенный инструмент для базового мониторинга
контейнеров
Prometheus и Grafana — мощная связка для профессионального мониторинга
и визуализации
cAdvisor — собирает детальные метрики о контейнерах
Node Exporter — собирает метрики о хост-системе
Alertmanager — управляет оповещениями и их маршрутизацией
Тестирование лимитов — необходимый шаг для проверки поведения
системы в экстремальных условиях
11/12
В продакшн-среде комбинация правильно настроенных ограничений ресурсов,
мониторинга и алертинга — необходимый минимум для обеспечения стабильной и
предсказуемой работы контейнерной инфраструктуры.
12/12
8.4 Docker Secrets
Docker Secrets
При разработке и развертывании приложений в контейнерах возникает важный
вопрос: как безопасно передавать чувствительную информацию (пароли, токены
API, SSL-сертификаты) в контейнеры? Простое добавление этих данных в Dockerfile
или передача через переменные окружения не обеспечивает должного уровня
безопасности. Docker Secrets предлагает решение этой проблемы.
1/14
docker swarm init
Создание секрета
docker secret ls
2/14
Обратите внимание, что вы не сможете увидеть фактическое содержимое секрета,
только метаданные:
[
{
"ID": "2jrhpzf1103560chdmtk0gm4q",
"Version": {
"Index": 17
},
"CreatedAt": "2023-10-25T15:42:56.363Z",
"UpdatedAt": "2023-10-25T15:42:56.363Z",
"Spec": {
"Name": "db_password",
"Labels": {}
}
}
]
Удаление секрета
Docker Compose (начиная с версии 3.1) поддерживает работу с секретами. Вот как
это можно реализовать:
3/14
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "80:80"
secrets:
- [Link]
- [Link]
volumes:
- ./[Link]:/etc/nginx/[Link]:ro
db:
image: postgres:13
environment:
- POSTGRES_USER=postgres
secrets:
- source: db_password
target: postgres_password
uid: '999' # UID пользователя postgres в контейнере
gid: '999' # GID пользователя postgres в контейнере
mode: 0400 # Только чтение для владельца
secrets:
[Link]:
file: ./secrets/[Link]
[Link]:
file: ./secrets/[Link]
db_password:
file: ./secrets/db_password.txt
# или можно использовать внешний секрет, созданный в Docker Swarm
# external: true
В этом примере:
Краткая форма:
services:
myservice:
secrets:
- my_secret
Расширенная форма:
4/14
services:
myservice:
secrets:
- source: my_secret
target: /run/secrets/my_custom_name # Переименование секрета внутри
контейнера
uid: '103' # UID владельца файла секрета
gid: '103' # GID владельца файла секрета
mode: 0440 # Права доступа к файлу секрета
Из файла:
secrets:
my_secret:
file: ./path/to/[Link]
secrets:
my_secret:
external: true
secrets:
my_secret:
external: true
name: actual_secret_name_in_swarm
5/14
# Инициализация Docker Swarm
docker swarm init
# Создание секрета
echo "db_password123" | docker secret create db_password -
Bash:
#!/bin/bash
DB_PASSWORD=$(cat /run/secrets/db_password)
echo "Connecting to database with password: $DB_PASSWORD"
Python:
import os
def get_secret(secret_name):
try:
with open(f'/run/secrets/{secret_name}', 'r') as secret_file:
return secret_file.read().strip()
except IOError:
return None
db_password = get_secret('db_password')
print(f"Connecting to database with password: {db_password}")
[Link]:
const fs = require('fs');
function getSecret(secretName) {
try {
return [Link](`/run/secrets/${secretName}`, 'utf8').trim();
} catch (err) {
return null;
}
}
6/14
Многие официальные образы уже поддерживают Docker Secrets. Например, образ
MySQL может автоматически использовать секрет mysql-root-password:
version: '3.8'
services:
db:
image: mysql:8.0
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=user
secrets:
- mysql-root-password
secrets:
mysql-root-password:
file: ./[Link]
7/14
1. Плавная ротация с периодом перекрытия:
Создайте новый секрет
Обновите ваше приложение для поддержки как старого, так и нового
секрета одновременно
Обновите сервис для доступа к обоим секретам
Постепенно переведите клиентов на новый секрет
Когда старый секрет больше не используется, удалите его
2. Ротация через временные метки:
Используйте схему именования с временными метками (например,
db_password_20231025)
Автоматизируйте создание новых секретов по расписанию
Настройте автоматическое обновление сервисов для использования
новейшего секрета
#!/bin/bash
# Пример скрипта для автоматической ротации секрета
# Обновление сервиса
docker service update \
--secret-rm db_password \
--secret-add source=db_password_new,target=db_password \
my-service
HashiCorp Vault
8/14
HashiCorp Vault — один из самых мощных инструментов для управления
секретами.
version: '3.8'
services:
app:
image: myapp:latest
environment:
- VAULT_ADDR=[Link]
entrypoint:
- /bin/sh
- -c
- |
export VAULT_TOKEN=$(cat /run/secrets/vault-token)
# Получение секрета из Vault
export DB_PASSWORD=$(vault kv get -field=password secret/database)
# Запуск приложения с полученными секретами
exec npm start
secrets:
- vault-token
vault:
image: vault:latest
ports:
- "8200:8200"
environment:
- VAULT_DEV_ROOT_TOKEN_ID=dev-only-token
cap_add:
- IPC_LOCK
secrets:
vault-token:
file: ./[Link]
Ключевые возможности:
9/14
Централизованное хранение и управление секретами
Автоматическая ротация секретов
Интеграция с другими сервисами AWS
Детальный контроль доступа через IAM
Шифрование с использованием AWS KMS
version: '3.8'
services:
app:
image: myapp:latest
environment:
- AWS_REGION=us-east-1
volumes:
- ~/.aws:/root/.aws:ro # Монтирование AWS-конфигурации для доступа к
Secrets Manager
entrypoint:
- /bin/sh
- -c
- |
# Получение секрета из AWS Secrets Manager
export DB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id
database-credentials --query SecretString --output text | jq -r .password)
# Запуск приложения
exec npm start
Особенности:
Kubernetes Secrets
10/14
Интеграция с внешними системами управления секретами через CSI
(Container Storage Interface)
Лучше всего
Решение подходит для Особенности Ограничения
11/14
# Инициализация Docker Swarm
docker swarm init
services:
nginx:
image: nginx:alpine
ports:
- "443:443"
secrets:
- source: tls_cert
target: /etc/nginx/ssl/[Link]
- source: tls_key
target: /etc/nginx/ssl/[Link]
configs:
- source: nginx_config
target: /etc/nginx/conf.d/[Link]
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
networks:
- frontend
- backend
api:
image: myapp/api:latest
environment:
- NODE_ENV=production
secrets:
- source: api_key
target: /run/secrets/app_api_key
- source: db_password
target: /run/secrets/app_db_password
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
networks:
- backend
db:
image: postgres:13
12/14
environment:
- POSTGRES_USER=app
- POSTGRES_DB=appdb
secrets:
- source: db_password
target: /run/secrets/postgres_password
uid: '999'
gid: '999'
mode: 0400
deploy:
placement:
constraints:
- [Link] == manager
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
networks:
frontend:
backend:
volumes:
db-data:
configs:
nginx_config:
content: |
server {
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/[Link];
ssl_certificate_key /etc/nginx/ssl/[Link];
location / {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
secrets:
db_password:
external: true
api_key:
external: true
tls_cert:
external: true
tls_key:
external: true
EOF
# Деплой стека
docker stack deploy -c [Link] myapp
13/14
1. Создаем несколько секретов: пароль для БД, API-ключ и TLS-сертификаты.
2. Настраиваем Docker Compose-файл для использования этих секретов в
разных сервисах.
3. Монтируем секреты в разные локации в зависимости от потребностей
приложения.
4. Устанавливаем специальные права доступа для секрета БД внутри контейнера
PostgreSQL.
14/14
8.5 Docker BuildKit
Docker BuildKit
Docker — невероятно мощная платформа для контейнеризации, но до недавнего
времени процесс сборки образов (docker build) имел некоторые ограничения:
последовательное выполнение этапов сборки, ограниченное кеширование,
сложности с передачей чувствительных данных для сборки и т.д. BuildKit —
следующее поколение системы сборки Docker, которое призвано решить эти
проблемы.
Включение BuildKit
# В Linux/macOS
export DOCKER_BUILDKIT=1
# В Windows PowerShell
$env:DOCKER_BUILDKIT=1
1/12
2. В конфигурационном файле Docker:
{
"features": {
"buildkit": true
}
}
# На Linux
sudo systemctl restart docker
# На Windows
Restart-Service docker
После включения BuildKit вы заметите другой вывод при запуске docker build —
более структурированный и информативный.
2/12
# Многоэтапный Dockerfile с независимыми этапами
FROM golang:1.18 AS build-backend
WORKDIR /app
COPY backend/[Link] backend/[Link] ./
RUN go mod download
COPY backend/ ./
RUN go build -o server .
FROM alpine:3.15
WORKDIR /app
COPY --from=build-backend /app/server ./
COPY --from=build-frontend /app/dist ./static
EXPOSE 8080
CMD ["./server"]
В этом примере:
3/12
3. Распределенное кеширование — возможность использовать удаленный кеш,
доступный всем членам команды или CI-серверам
4. Пропуск ненужных этапов — BuildKit может пропустить этапы, результаты
которых не используются в итоговом образе
FROM node:16
WORKDIR /app
BuildKit поддерживает новую опцию --link для команды COPY, которая улучшает
кеширование:
4/12
docker build --cache-from myapp:latest -t myapp:latest .
# В Dockerfile
FROM alpine
В этом примере:
5/12
Практические примеры использования секретов
# В Dockerfile
FROM node:16
WORKDIR /app
COPY [Link] [Link] ./
COPY . .
RUN npm run build
CMD ["npm", "start"]
# В Dockerfile
FROM alpine
RUN apk add --no-cache git
6/12
1. Базовый пример с SSH-агентом
# В Dockerfile
FROM alpine
RUN apk add --no-cache openssh-client git
В этом примере:
# В Dockerfile
FROM alpine
RUN apk add --no-cache openssh-client git
RUN --mount=type=ssh,id=gitlab \
git clone git@[Link]:username/[Link]
7/12
# В Dockerfile
FROM golang:1.18 AS builder
RUN apt-get update && apt-get install -y openssh-client git
WORKDIR /app
FROM alpine:3.15
COPY --from=builder /app/app /usr/local/bin/
CMD ["app"]
# Условная инструкция с IF
RUN --mount=type=bind,source=.,target=/src \
if [ -f /src/[Link] ]; then \
pip install -r /src/[Link]; \
fi
8/12
FROM python:3.9
WORKDIR /app
COPY [Link] .
ARG ENVIRONMENT=development
COPY . .
# syntax=docker/dockerfile:1.3
ARG ARCH=amd64
9/12
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
FROM alpine:3.15
COPY --from=builder /app/app /usr/local/bin/
CMD ["app"]
# syntax=docker/dockerfile:1.3
FROM alpine:3.15
ARG BUILD_TYPE=release
COPY --from=builder-${BUILD_TYPE} /app/app /usr/local/bin/
CMD ["app"]
10/12
FROM node:16
WORKDIR /app
COPY [Link] ./
ARG USE_PRIVATE_REGISTRY=false
COPY . .
CMD ["npm", "start"]
Итог
BuildKit — это современный инструмент сборки Docker, который значительно
улучшает производительность, безопасность и гибкость процесса создания
образов.
Параллельное выполнение этапов сборки ускоряет процесс, особенно для
сложных многоэтапных сборок.
11/12
Продвинутое кеширование минимизирует время повторных сборок
благодаря умному определению изменений.
Секреты позволяют безопасно использовать чувствительную информацию во
время сборки без ее включения в образ.
SSH-агент дает возможность использовать SSH-ключи хост-машины для
доступа к приватным ресурсам.
Условные инструкции делают Dockerfile более гибким и адаптируемым к
разным ситуациям.
12/12
8.6 Docker Plugins
Docker Plugins
Docker plugins (плагины Docker) — это расширения, которые добавляют
дополнительные возможности демону Docker. Они могут обеспечивать
дополнительные функциональные возможности в области сетевых драйверов,
драйверов хранения (storage), средств мониторинга и многого другого.
Использование плагинов упрощает интеграцию Docker с разнообразными внешними
сервисами и инфраструктурными решениями, позволяя расширять базовый
функционал Docker без модификации ядра Docker.
Типы Docker-плагинов
1/6
Other plugins — любые другие плагины, реализующие необходимую вам
функциональность (например, секреты, шифрование томов и т.д.).
Установка плагина
Обновление плагина
2/6
В процессе обновления плагин может быть временно отключен и перезапущен в
новой версии.
Структура плагина
3/6
Упрощённый пример [Link] для плагина:
{
"Description": "Example Volume Plugin",
"Documentation": "[Link]
"Interface": {
"Types": ["[Link]/1.0"],
"Socket": "[Link]"
},
"Entrypoint": ["/usr/bin/example-plugin"],
"WorkDir": "",
"Rootfs": "rootfs",
"Env": [
{
"Name": "LOG_LEVEL",
"Value": "info"
}
],
"Linux": {
"Capabilities": ["CAP_SYS_ADMIN"],
"AllowAllDevices": false,
"Devices": null
}
}
docker plugin ls
Чтобы опубликовать плагин в реестре Docker Hub (или другом реестре), нужно
войти в учётную запись Docker (docker login) и выполнить:
4/6
Настройка привилегий и безопасность
Каждый плагин имеет набор привилегий, которые определяют, к каким ресурсам он
получает доступ. При установке или включении плагина Docker показывает список
требуемых привилегий, и администратор должен их подтвердить.
Практические примеры
5/6
{
"authorization-plugins": [
"twistedogic/docker-authz-plugin"
]
}
Итоги
6/6