Перейти к содержимому
TelegramGitHub

Контейнеры в продакшене и облаке

Образ, который работает на ноутбуке, работает и в облаке. Это главная причина, почему контейнеры любят в продакшене. Образ собирают один раз. Запускают где угодно.

код в Git → CI собирает образ → образ в реестре → облако запускает контейнер
  1. Вы пушите код в Git.
  2. CI (например, GitHub Actions) выполняет docker build.
  3. CI отправляет образ в реестр (docker push).
  4. Облачный сервис берёт образ из реестра и запускает контейнер.

Сам образ на пути не меняется. Меняются только переменные окружения: адрес базы, пароль, имя бакета.

Реестр — хранилище образов. docker pull берёт образ оттуда.

РеестрГде
Docker HubОбщий публичный реестр
GHCR (ghcr.io)GitHub. Удобен, если код уже на GitHub
ECRAWS
Artifact RegistryGoogle Cloud
ACRAzure

Обычно берут реестр того облака, где работает контейнер. Так быстрее и проще с правами.

ОблакоСервисДля чего
AWSECS на FargateСервисы и задачи без управления серверами
AWSAWS BatchБольшие пакетные задачи с очередью
AWSLambda (образ контейнера)Короткие задачи по событию, до 15 минут. Образ должен быть на базе public.ecr.aws/lambda/python или содержать runtime interface client. Обычный python:3.12-slim не подойдёт
AWSECS Express ModeПростой веб-сервис из образа. App Runner с 30.04.2026 закрыт для новых клиентов
Google CloudCloud Run (services)HTTP-сервис, масштабируется до нуля
Google CloudCloud Run (jobs)Пакетная задача: запустилась, отработала, остановилась
AzureContainer Apps (apps)Сервисы и API
AzureContainer Apps (jobs)Задачи по расписанию или по событию

Для данных чаще всего нужна пакетная задача. Это контейнер, который запускается по расписанию, грузит данные и завершается. Именно так работает наш load_orders.py.

Кто запускает задачу по расписанию:

СервисРасписание
ECS на FargateAmazon EventBridge Scheduler
Cloud Run jobsCloud Scheduler
Container Apps jobsВстроенный cron в настройках задачи
Любой из нихAirflow, если между задачами есть зависимости
  • DockerOperator. Задача DAG запускает контейнер на хосте. Окружение задачи не зависит от окружения Airflow.
  • KubernetesPodOperator. То же самое, но контейнер запускается в Kubernetes.
  • Управляемый Airflow. MWAA (AWS), Cloud Composer (Google Cloud), Astronomer. Облако само поддерживает Airflow. Вам не нужно запускать Kubernetes.

Про Kubernetes — в следующем уроке.

ПравилоЧто значит
Конфиг через переменные окруженияОдин образ для dev и prod. Меняются только переменные
Секреты не в образеПароли и ключи берите из менеджера секретов (Secrets Manager, Secret Manager, Key Vault). Не пишите их в Dockerfile и не коммитьте .env
Логи в stdout и stderrПишите через print или logging в консоль. Облако соберёт их само
Неизменяемые тегиТегируйте образ хешем коммита: orders:9554b77. Не используйте latest
Не rootUSER appuser в Dockerfile
Лимиты ресурсов--memory и --cpus. Один плохой контейнер не должен занять весь сервер
Health checkОркестратор проверяет, что контейнер жив, и перезапускает его
Сканирование образовРеестр или CI ищет известные уязвимости
Маленькие образы-slim. Быстрее скачать, меньше уязвимостей. alpine хорош для утилит, но не для Python: многие пакеты под него не собраны
Один процесс на контейнерБаза, приложение и планировщик — разные контейнеры
Контейнер без состоянияДля приложений и задач: данные живут в базе или S3, не в файлах контейнера. Саму базу держите в управляемом сервисе или в томе с бэкапами

Ограничьте память и процессор:

Окно терминала
docker run -d --name orders-limited --memory 256m --cpus 1 alpine:3.22 sleep 300
docker stats --no-stream --format '{{.Name}} {{.MemUsage}} {{.CPUPerc}}' orders-limited

Ответ:

orders-limited 1.344MiB / 256MiB 0.00%

После / стоит лимит: 256MiB. Если контейнер превысит память, Docker остановит его (OOM kill). Лучше узнать об этом сейчас, чем в продакшене.

Удалите контейнер: docker rm -f orders-limited.

Окно терминала
git rev-parse --short HEAD

Ответ — короткий хеш, например 9554b77. Тег показывает, из какого коммита собран образ. Откат — это запуск старого тега.

Окно терминала
docker build -t orders:9554b77 .

Файл .github/workflows/docker.yml. Мы его не запускали. Версии действий мы проверили на день написания (ноябрь 2026). Проверьте актуальные версии на страницах действий.

name: docker
on:
push:
branches: [main]
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

Что здесь важно:

  • permissions: packages: write разрешает токену GITHUB_TOKEN писать образы в GHCR. Без него push вернёт ошибку доступа.
  • Тег — полный хеш коммита github.sha. Это неизменяемый тег.
  • Пароль не написан в файле. Токен выдаёт GitHub.
  • Имя образа должно быть в нижнем регистре. Если в имени репозитория есть заглавные буквы, добавьте шаг docker/metadata-action@v6. Он сам делает имя маленькими буквами и готовит теги.

Создайте Git-репозиторий в ~/docker-practice и сделайте коммит. Возьмите короткий хеш и поставьте его тегом на образ alpine:3.22 с именем orders-demo.

Проверка: docker images orders-demo показывает тег, равный git rev-parse --short HEAD. У вас хеш будет другой.

Решение
Окно терминала
cd ~/docker-practice
git init -q
git config user.email you@example.com
git config user.name you
git add .
git commit -qm "init"
SHA=$(git rev-parse --short HEAD)
docker tag alpine:3.22 orders-demo:$SHA
docker images orders-demo

Уберите за собой: docker rmi orders-demo:$SHA. Это удалит только тег. Образ alpine:3.22 останется.

Запустите alpine:3.22 с командой sleep 300, лимитом памяти 128 МБ и одним процессором. Покажите использование памяти.

Проверка: в колонке памяти после / стоит 128MiB.

Решение
Окно терминала
docker run -d --name limit-test --memory 128m --cpus 1 alpine:3.22 sleep 300
docker stats --no-stream --format '{{.Name}} {{.MemUsage}}' limit-test
docker rm -f limit-test

В Dockerfile есть шесть строк. Какие из них плохие для продакшена и почему?

FROM python:latest
COPY . /app
ENV DB_PASSWORD=secret123
RUN pip install psycopg
USER root
CMD ["python", "/app/load_orders.py"]

Проверка: найдите пять проблем.

Решение
СтрокаПроблемаКак исправить
FROM python:latestlatest меняется. Образ тяжёлыйFROM python:3.12-slim
COPY . /appКопирует всё, даже .env и .git. Ломает кэшСначала requirements.txt, потом код. Добавьте .dockerignore
ENV DB_PASSWORD=secret123Пароль остаётся в образе и в истории слоёвПередавайте пароль при запуске из менеджера секретов
RUN pip install psycopgНет версии. Нет --no-cache-dir. Нет requirements.txtpip install --no-cache-dir -r requirements.txt
USER rootЗапуск от rootUSER appuser

Строка CMD в порядке. Это шестая строка, и к ней нет замечаний.

Дальше: Нужен ли Kubernetes.