open atlas
↑ К треку
AWS на практике AWS · 02 · 02

Деплой контейнера от начала до конца

Собери образ, запушь его в ECR, потом запусти — либо сервис ECS/Fargate за ALB, либо управляемый сервис App Runner. Сеньорские детали — две роли, health check и ловушка arm64/amd64.

AWS Middle ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

Образ собрался на ноутбуке, запустился локально, ты его запушил. Через пару минут сервис ECS застрял: desired count 1, running count 0, задачи падают с exec format error. С образом всё в порядке — просто не тот CPU. Ты собрал его на Mac с Apple Silicon (arm64), а задача Fargate — amd64. Ничто в docker push не предупреждает; несовпадение всплывает только когда контейнер пытается запуститься. Деплой от начала до конца — это ровно про такое: каждый переход — сборка, push, запуск, маршрутизация трафика — это место, где счастливый путь тихо ломается.

Собери образ и запушь в ECR

Почему это важно знать до того, как ты касаешься ECS или App Runner? Потому что каждый режим отказа в пайплайне деплоя молчит до рантайма — инструментарий не остановит тебя от отгрузки неправильной архитектуры, push’а в несуществующий репозиторий или аутентификации с просроченным токеном. Знать тихий сбой каждого перехода — это то, что отличает первый деплой от надёжного CI-пайплайна.

ECR (Elastic Container Registry) — приватный Docker-реестр AWS. Перед docker push ты аутентифицируешься: aws ecr get-login-password выдаёт короткоживущий токен, который ты передаёшь в docker login. Затем тегируешь локальный образ полным именем реестра и пушишь.

# 1. Аутентифицируем Docker в приватном реестре ECR (токен живёт ~12ч)
aws ecr get-login-password --region eu-central-1 \
  | docker login --username AWS \
      --password-stdin 123456789012.dkr.ecr.eu-central-1.amazonaws.com

# 2. Собираем под ту архитектуру, которую ждёт runtime — это и есть ловушка.
#    На Mac с Apple Silicon --platform обязателен, иначе отгрузишь arm64 в amd64-Fargate.
docker build --platform linux/amd64 -t myapp:1.0 .

# 3. Тегируем полным URI ECR-репозитория, потом пушим
docker tag myapp:1.0 123456789012.dkr.ecr.eu-central-1.amazonaws.com/myapp:1.0
docker push   123456789012.dkr.ecr.eu-central-1.amazonaws.com/myapp:1.0

Здесь кусают две вещи. Первая — репозиторий уже должен существовать: aws ecr create-repository --repository-name myapp, потому что push его не создаёт. Вторая — архитектура: образ собирается под конкретный CPU, и docker push с радостью загрузит arm64-образ в репозиторий, который amd64-задача потом будет тянуть. Падение откладывается до рантайма в виде exec format error. Собирай с явным --platform или используй docker buildx для мульти-арх манифеста и выбери у Fargate runtimePlatform (X86_64 или ARM64), который совпадает.

ПереходКоманда / объектТихий сбой, если пропустить
Аутентификацияecr get-login-password | docker loginno basic auth credentials при push
Сборкаdocker build —platform linux/amd64arm64-образ → exec format error при запуске
Репо существуетecr create-repositoryname unknown / репозиторий не существует
Pushdocker tag && docker push <uri>пушишь голый тег, а не URI реестра

Вместе эти четыре перехода образуют единую цепочку отказов: пропусти любой — и ошибка молчит вплоть до момента, когда контейнер пытается запуститься. Несовпадение архитектуры — самое коварное, потому что проходит все шаги валидации до того, как ядро отвергает бинарник.

Маршрут A — ECS на Fargate за ALB

Fargate запускает твой контейнер, не заставляя тебя управлять серверами, но несколько объектов ты собираешь сам. Определение задачи (task definition) — это чертёж: какой образ, сколько CPU/памяти, какой порт, какие роли, куда идут логи.

{
  "family": "myapp",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "512",
  "memory": "1024",
  "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" },
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn":      "arn:aws:iam::123456789012:role/myappTaskRole",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789012.dkr.ecr.eu-central-1.amazonaws.com/myapp:1.0",
      "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "eu-central-1",
          "awslogs-stream-prefix": "myapp"
        }
      }
    }
  ]
}

Две роли — самая путаная часть ECS, и они не взаимозаменяемы:

  • Execution role (executionRoleArn) — используется агентом ECS при запуске, ещё до того как заработает твой код, чтобы вытянуть образ из ECR и писать логи в CloudWatch. AWS-managed политика AmazonECSTaskExecutionRolePolicy покрывает ровно это. Если её нет, задача не стартует: ты видишь CannotPullContainerError или отсутствие лог-группы.
  • Task role (taskRoleArn) — личность, которую принимает на себя код твоего приложения в рантайме. Именно её используют вызовы твоего SDK, чтобы читать бакет S3, таблицу DynamoDB или секрет. Давай ей минимум привилегий — лишь те действия, что приложению реально нужны; к вытягиванию образа она отношения не имеет.

Затем сервис держит N копий запущенными и связывает их с балансировщиком:

aws ecs create-service \
  --cluster prod \
  --service-name myapp \
  --task-definition myapp \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration 'awsvpcConfiguration={
      subnets=[subnet-aaa,subnet-bbb],
      securityGroups=[sg-app],
      assignPublicIp=DISABLED }' \
  --load-balancers 'targetGroupArn=arn:...:targetgroup/myapp/abc,
      containerName=myapp,containerPort=8080'

Разнеси подсети по двум зонам доступности (AZ), чтобы отказ одной AZ не уронил тебя целиком. Спереди стоит Application Load Balancer (ALB) с target group, чья проверка здоровья (health check) опрашивает путь (например, GET /healthz). ALB шлёт трафик только тем целям, что проходят проверку, и на это опираются rolling-деплои: ECS поднимает задачи новой версии, ждёт, пока они пройдут health check, регистрирует их, потом сливает (drain) и останавливает старые. Слишком строгая, слишком быстрая или указывающая на несуществующий путь проверка заставит каждый деплой откатываться — новые задачи «падают», ECS держит старые, а ты гадаешь, почему твоё изменение так и не выехало.

Маршрут B — App Runner: управляемый короткий путь

Если тебе не нужен контроль ECS, App Runner сворачивает почти весь маршрут A в один сервис. Ты указываешь ему образ в ECR, а он сам разворачивает балансировщик, TLS-сертификат, автомасштабирование и публичный HTTPS-URL — без JSON определения задачи, без target group, без подсетей, которые надо проводить.

aws apprunner create-service \
  --service-name myapp \
  --source-configuration '{
    "ImageRepository": {
      "ImageIdentifier": "123456789012.dkr.ecr.eu-central-1.amazonaws.com/myapp:1.0",
      "ImageRepositoryType": "ECR",
      "ImageConfiguration": { "Port": "8080" }
    },
    "AutoDeploymentsEnabled": true,
    "AuthenticationConfiguration": {
      "AccessRoleArn": "arn:aws:iam::123456789012:role/AppRunnerECRAccessRole"
    }
  }' \
  --health-check-configuration '{ "Protocol": "HTTP", "Path": "/healthz" }'

У App Runner та же идея двух личностей: access role, чтобы тянуть из ECR (аналог execution role), и опциональная instance role для собственных AWS-вызовов приложения (аналог task role). Он по-прежнему гоняет health check. И по-прежнему заботится об архитектуре — App Runner запускает образ как есть, поэтому arm64-образ не стартует. Размен — контроль на удобство: ты отдаёшь тонкую настройку сети, кастомные правила балансировщика и точное размещение задач, а получаешь рабочий HTTPS-сервис одним вызовом. Тянись к ECS/Fargate, когда нужна интеграция с VPC, сайдкары или точное масштабирование; тянись к App Runner, когда просто хочешь контейнер в интернете.

Почему это работает

Почему образ тянет execution role, а не task role? Потому что pull происходит до того, как твой контейнер существует — агенту ECS (инфраструктуре AWS) нужны права на ECR и CloudWatch, чтобы вообще запустить задачу. Task role принимается внутри запущенного контейнера твоим кодом через SDK. Смешать их — классический баг ECS: люди вешают права на S3 на execution role (так агент мог бы читать S3, но приложение — нет) и недоумевают, почему их код получает AccessDenied, хотя задача стартует нормально.

Викторина

Задача ECS/Fargate сразу падает с 'exec format error' и ни разу не обслуживает запрос. Самая вероятная причина?

Викторина

Контейнер стартует нормально, но его код получает AccessDenied на вызове s3:GetObject. В определении задачи есть обе роли. Куда вешать права на S3?

Вспомните перед уходом
  1. 01
    Пройди весь путь от локального Docker-образа до живого трафика на ECS/Fargate за ALB.
  2. 02
    Объясни execution role против task role и ловушку arm64/amd64 — почему каждая тихо ломается до самого рантайма.
Итог

Деплой контейнера в AWS — это четыре перехода, и каждый прячет тихий сбой. Ты собираешь образ — под правильный CPU, потому что docker push отгрузит arm64-образ в amd64-задачу, и узнаешь об этом только в рантайме через exec format error. Пушишь в ECR, предварительно создав репозиторий и аутентифицировавшись через ecr get-login-password в docker login, тегируя полным URI реестра. Потом запускаешь. На ECS/Fargate ты собираешь определение задачи (образ, cpu/память, порт, runtimePlatform, конфигурация логов в CloudWatch через драйвер awslogs) с двумя разными ролями — execution role, которой агент ECS тянет образ и пишет логи, и task role с минимумом привилегий, которую твой код принимает для собственных вызовов AWS, — затем сервис с desired count, подсетями по двум AZ, security group и target group у ALB, чья проверка здоровья пропускает трафик и ведёт rolling-деплои. App Runner сворачивает балансировщик, TLS, автомасштабирование и публичный URL в один вызов create-service, нацеленный на образ в ECR; он сохраняет тот же раздел access-role/instance-role и по-прежнему заботится об архитектуре. Повторяющийся урок: собирай под правильную архитектуру, разделяй две роли и правильно настраивай health check — именно на этих трёх вещах деплои от начала до конца ломаются. Теперь, когда увидишь деплой, застрявший при desired count 1 / running count 0, ты знаешь: сначала проверяешь exec format error, потом execution role, потом путь health check — именно в таком порядке, прежде чем тянуться к любому другому инструменту.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.