Amazon ECSのコンテナインスタンスからFargateへ移行した話
この記事は iRidge Advent Calendar 2018 の15日目の記事です。
はじめに
Amazon ECSのコンテナインスタンスでWebアプリケーションを運用していたのですが、ものすごいトイルを感じたのでFargateへ移行を決意しました。
ものすごいトイル
- コンテナインスタンスAMI更新作業
- 度重なる深夜対応 (
Fargate移行を見越して自動化しなかった)
- 度重なる深夜対応 (
- 2ステップスケーリング
- リソース不足の場合に
コンテナインスタンス数スケール
からのTask数スケール
を実施する必要がある。
- リソース不足の場合に
現状の構成
将来の構成
前提条件
- 既存のECS Clusterを利用する
- 既存のELB, FQDNを利用する
- 新規にECS Service, TargetGroupを作成する
- ゼロダウンタイムで移行する
Fargate移行によって得たもの・失ったもの
開発リリースフロー
- GitLabへソースコードをPush
- GitLabでMR作成 + マージ
- GitLab CIによるパイプライン実行
- Unit Testの実行
- Radonによる複雑度確認
- Flake8による規約確認
- Docker Imageのビルド
- ecs-cliによるデプロイ
移行作業
.gitlab-ci.yml
を修正docker-compose.yml
を作成ecs-params.yml
を作成- GitLab CIのSecret Variablesを修正
- ELBのリスナーに新規TargetGroupを関連付ける
- GitLab CIで ECRへPush + ecs-cli によるデプロイ
- ELBのリスナーのデフォルトアクションを変更
前処理
- 新規のECS Serviceに設定するSecurityGroupが作成済み
- 1.で作成したSecurityGroupからの通信をRDSのSecurityGroupから許可済み
- 新規のECS Serviceに関連づけるTargetGroupを作成済み (TargetTypeが
IP
なので注意)
具体的な作業
1. .gitlab-ci.yml
を修正
下記はデプロイ部だけ抜き出したもの
.deploy_template: &deploy_definition image: python:3.6 stage: deploy before_script: - export AWS_ACCESS_KEY_ID=$ECS_CLI_AWS_ACCESS_KEY_ID - export AWS_SECRET_ACCESS_KEY=$ECS_CLI_AWS_SECRET_ACCESS_KEY - export AWS_DEFAULT_OUTPUT=json - curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest - chmod 755 /usr/local/bin/ecs-cli - echo "$(curl -s https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest.md5) /usr/local/bin/ecs-cli" | md5sum -c - - cd deploy script: - ecs-cli compose --debug -p $AWS_PROJECT_NAME -f app/docker-compose.yml --ecs-params app/ecs-params.yml service up --target-group-arn $AWS_TARGET_GROUP_ARN --cluster $AWS_ECS_CLUSTER_NAME --launch-type FARGATE --container-name web --container-port 80 --force-deployment --deployment-max-percent 200 --deployment-min-healthy-percent 50 --timeout 10 - ecs-cli compose --debug -p $AWS_PROJECT_NAME -f app/docker-compose.yml service scale $AWS_ECS_CLUSTER_SCALE --cluster $AWS_ECS_CLUSTER_NAME --deployment-max-percent 200 --deployment-min-healthy-percent 50 --timeout 10 deploy_to_stg: <<: *deploy_definition variables: AWS_APP_IMAGE: $AWS_APP_IMAGE_STG AWS_ECS_CLUSTER_NAME: $AWS_ECS_CLUSTER_NAME_STG AWS_ECS_CLUSTER_SCALE: $AWS_ECS_CLUSTER_SCALE_STG AWS_PROJECT_NAME: $AWS_PROJECT_NAME_STG AWS_SECURITY_GROUP: $AWS_SECURITY_GROUP_STG AWS_SUBNET_A: $AWS_SUBNET_A_STG AWS_SUBNET_C: $AWS_SUBNET_C_STG AWS_TARGET_GROUP_ARN: $AWS_TARGET_GROUP_ARN_STG AWS_LOGS_GROUP: $AWS_LOGS_GROUP_STG only: - master
2. docker-compose.yml
を作成
version: "3" services: web: image: $AWS_WEB_IMAGE ports: - "80:80" logging: driver: awslogs options: awslogs-group: $AWSLOGS_GROUP awslogs-region: $AWS_DEFAULT_REGION awslogs-stream-prefix: "ecs" app: image: $AWS_APP_IMAGE ports: - "8000:8000" environment: hoge: $HOGE command: gunicorn -b 0.0.0.0:8000 --access-logfile=- --error-logfile=- -w=8 -t=600 -k=gevent --threads=8 --worker-connections=1024 app.wsgi working_dir: /app logging: driver: awslogs options: awslogs-group: $AWSLOGS_GROUP awslogs-region: $AWS_DEFAULT_REGION awslogs-stream-prefix: "ecs" x-ray: image: $AWS_XRAY_IMAGE ports: - "2000:2000" logging: driver: awslogs options: awslogs-group: $AWSLOGS_GROUP awslogs-region: $AWS_DEFAULT_REGION awslogs-stream-prefix: "ecs"
3. ecs-params.yml
を作成
version: 1 task_definition: ecs_network_mode: awsvpc task_role_arn: $AWS_TASK_ROLE_ARN task_execution_role: ecsTaskExecutionRole task_size: cpu_limit: 1024 mem_limit: 2.0GB services: web: essential: true cpu_shares: 256 mem_reservation: 0.5GB app: essential: true cpu_shares: 512 mem_reservation: 1.0GB x-ray: essential: true cpu_shares: 256 mem_reservation: 0.5GB run_params: network_configuration: awsvpc_configuration: subnets: - $AWS_SUBNET_A - $AWS_SUBNET_C security_groups: - $AWS_SECURITY_GROUP assign_public_ip: ENABLED
4. GitLab CIのSecret Variablesを修正
事前準備で作成したSecurityGroup, TargetGroup等の情報を格納する
5. ELBのリスナーに新規TargetGroupを関連付ける
新規ルールで、ホスト: example.com, 転送先: 新規作成したTargetGroup
とし、保存する。*4
6. GitLab CIで ECRへPush + ecs-cli によるデプロイ
パイプラインを実行し、イメージビルド + ECRへPush
+ ecs-cliによるデプロイ
を実施
7. ELBのリスナーのデフォルトアクションを変更
5.
で作成したルールを削除し、デフォルトアクションの転送先を新規に作成したTargetGroupへ変更し、保存する。
これで、バチッと切り替わる。
疎通確認して、旧ECS ServiceのTask数・コンテナインスタンス数を0へ変更する。
おわりに
- vCPUに対するメモリの制限をなくしてほしい。 *5
- 🎉祝トイル撲滅 🎉
*1:1コンテナインスタンスに配置されるTaskの組み合わせによってはリソースをフル活用できていなかったり、TaskScheduleで定期実行するTask用のコンテナインスタンスのリソースを余らせておかないといけなかったりと、過剰にコンテナインスタンス料金を支払っていた。リソース(vCPU, メモリ)を使った分だけの課金であるFargateを使った方が結果的に安くなった。
*2:Fargateの場合、Runningまで3~5分ほどかかる。つまりecs-cliによるデプロイも時間がかかる。TaskScheduleでTaskを定期的に実行している場合は、起動が遅くなるで実装によっては意図しない振る舞いになる可能性がある。
*3:普段からやっているわけではない。運用としてほしい情報はすべて外出している。最終奥義として使ったこともある。
*4:TargetGroupが関連していないとデプロイに失敗するため。
*5:vCPU:1, メモリ:1GBとかできると嬉しい。