ECS + GitLab CIでカナリアリリースみたいなことを実現する

ECS + GitLab CIでカナリアリリースみたいなことを実現する:

この記事はiRidge Advent Calendar 2018 の7日目の記事です。


tl;dr;

私の運用しているシステムではGitLab CI + ansibleを利用して、リリースの度にEC2を半自動+手順書で作り変える運用をしているんですが、どうしてもリリースに時間がかかってしまうため、アプリケーションのコンテナ化 + コンテナ向けのデプロイの仕組みづくりを推進することになりました。

その中でECSとGitLab CIを用いたカナリアリリースの仕組み等を考えて検証を行ったのでこの記事ではそれについて紹介していきます。


はじめに

デプロイの全体の流れ的なところを中心に書いていきます。ECSよりGitLab CIの要素が強めです。

AWSのECSクラスタの構築の仕方等いろいろ省略してたりするので読みにくいかもしれませんが、ご容赦ください。


カナリアリリースとは

本番環境に部分的に新しいバージョンのアプリケーションをデプロイし、カナリア環境のアプリケーションに問題ないことを確認してから全体の更新を行なうリリース手法のことです。

下記にカナリアリリースのコンセプト等が書かれています。
https://cloudplatform-jp.googleblog.com/2017/04/how-release-canaries-can-save-your-bacon-CRE-life-lessons.html


作りたい仕組みの全体像



カナリアデプロイ計画.jpg



AWS側の構成

検証として、ECSクラスタ(EC2)を作っておきます。検証としてnginxのTaskを利用するのでALBとそれに紐づくTargetGroupも用意しました。

ECSへTaskをデプロイするためのツールとしてはecs-cliを利用することにしました。採用の経緯はdocker-composeのような形でECSのServiceの更新ができ、個人的に使いやすいと思ったためです。


GitLab CIで準備するJob

GitLab CIに次のbuild,canary-deploy,deployの3つのjobを定義したパイプラインを作ります。それぞれの役割は下記のような内容になっています。(商用に適用する際はアプリケーションのユニットテストやら別のjobも入ってくると思います。)

パイプラインはリポジトリでタグを作成したタイミングで生成され、build jobまでは自動実行、deploy系のjobはマニュアル実行という形を想定しています。

  • build


    • docker buildによるimageの生成、Registryへのpushを行なう。
    • 自動実行
  • canary-deploy

    • ECSのカナリア用のServiceにデプロイを行なう
    • まず、こっちのjobを実行して、カナリア用のServiceに新しいバージョンのタスクを配置
    • 実行はGitLab CI の when: manualを利用した手動実行
  • deploy

    • ECSの本番用のServiceにデプロイを行なう
    • カナリアリリースの内容が問題なかったら実行
    • 実行はGitLab CI の when: manualを利用した手動実行


ディレクトリの構成

. 
├── .gitignore 
├── .gitlab-ci.yml 
├── Dockerfile # テスト用のnginxファイル 
├── conf 
│   └── test.conf # テスト用のnginx.conf 
├── deploy 
│   ├── docker-compose.yml 
│   └── ecs-params.yml 
└── html 
    └── index.html # nginxで表示させるindex.html 


ecs-cliに渡すファイルの設定

docker-compose.yml
version: '3' 
services: 
  canary-test: 
    container_name: canary-test 
    image: <Registryのpath>:${CI_COMMIT_TAG} 
    environment: 
      - TZ=Asia/Tokyo 
    ports: 
      - "80" 
    restart: always 
    logging: 
      driver: awslogs 
      options: 
        awslogs-region: ap-northeast-1 
        awslogs-group: /ecs/canary-test 
ecs-params.yml
version: 1 
task_definition: 
  task_execution_role: TaskExecRole 
  ecs_network_mode: bridge 
  task_size: 
    cpu_limit: 256 
    mem_limit: 512 
  services: 
    canary-test: 
      essential: True 
      repository_credentials: 
        credentials_parameter: "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxxx:secret:ecs-canary-poc2-1ax2py" 
今回はGitLabのDocker Registryを使ったので、Registryにアクセスするための情報をSecretsManagerに登録しました。


GitLab CIの設定

.gitlab-ci.ymlに下記のようにjobの定義をしました。

.gitlab-ci.yml
image: docker:latest 
services: 
   - docker:dind 
 
stages: 
  - build 
  - deploy 
 
tag-build: 
  stage: build 
  only: 
    - tags 
    - triggers 
  before_script: 
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY 
  script: 
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG . 
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG 
  tags: 
    - docker 
 
canary-deploy: 
  stage: deploy 
  variables: 
    AWS_REGION: ap-northeast-1 # AWS region 
    AWS_ACCOUNT_ID: xxxxxxxxxxxx # AWSのアカウントID 
    AWS_ACCESS_KEY_ID: $POC_AWS_ACCESS_KEY_ID # GitLab CIのSecret variableから取得 
    AWS_SECRET_ACCESS_KEY: $POC_AWS_SECRET_ACCESS_KEY # GitLab CIのSecret variableから取得 
    AWS_TARGET_GROUP_NAME: ecs-canary-test-poc # target groupの名前 
    AWS_TARGET_GROUP_ID: yyyyyyyyyyyyyyyy # target groupのID 
    AWS_ECS_CLUSTER_NAME: ecs-canary-test-poc-svc-cluster # ECSのクラスタ名 
    AWS_TASK_ROLE_NAME: TaskExecRole # Taskに割り当てるIAMRole名 
    ENVIRONMENT: poc 
    CONTAINER_NAME: canary-test 
    CONTAINER_PORT: 80 
    DEPLOYMENT_MAX_PARCENT: 100 
    DEPLOYMENT_MIN_PARCENT: 0 
    SERVICE_NAME: canary-release # ECSのService名 
  before_script: &deploy_before_script 
    - apk -v --update add python py-pip groff less mailcap curl 
    - pip install --upgrade awscli s3cmd python-magic 
    - aws sts get-caller-identity # 正しいIAMを読み込めているかを確認 
    - 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 
    - ecs-cli -v 
  script: &deploy_script 
    - echo ecs-cli compose -p ${SERVICE_NAME} -f deploy/docker-compose.yml --ecs-params deploy/ecs-params.yml --task-role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/${AWS_TASK_ROLE_NAME} service up --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:${AWS_ACCOUNT_ID}:targetgroup/${AWS_TARGET_GROUP_NAME}/${AWS_TARGET_GROUP_ID} --container-name ${CONTAINER_NAME} --container-port ${CONTAINER_PORT} --cluster ${AWS_ECS_CLUSTER_NAME} --deployment-max-percent ${DEPLOYMENT_MAX_PARCENT} --deployment-min-healthy-percent ${DEPLOYMENT_MIN_PARCENT} 
    - ecs-cli compose -p ${SERVICE_NAME} -f deploy/docker-compose.yml --ecs-params deploy/ecs-params.yml --task-role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/${AWS_TASK_ROLE_NAME} service up --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:${AWS_ACCOUNT_ID}:targetgroup/${AWS_TARGET_GROUP_NAME}/${AWS_TARGET_GROUP_ID} --container-name ${CONTAINER_NAME} --container-port ${CONTAINER_PORT} --cluster ${AWS_ECS_CLUSTER_NAME} --deployment-max-percent ${DEPLOYMENT_MAX_PARCENT} --deployment-min-healthy-percent ${DEPLOYMENT_MIN_PARCENT} 
  tags: 
    - docker 
  environment: 
    name: canary-poc 
  only: 
    - tags 
  when: manual 
 
deploy: 
  stage: deploy 
  variables: 
    AWS_REGION: ap-northeast-1 # AWS region 
    AWS_ACCOUNT_ID: xxxxxxxxxxxx # AWSのアカウントID 
    AWS_ACCESS_KEY_ID: $POC_AWS_ACCESS_KEY_ID # GitLab CIのSecret variableから取得 
    AWS_SECRET_ACCESS_KEY: $POC_AWS_SECRET_ACCESS_KEY # GitLab CIのSecret variableから取得 
    AWS_TARGET_GROUP_NAME: ecs-canary-test-poc # target groupの名前 
    AWS_TARGET_GROUP_ID: yyyyyyyyyyyyyyyy # target groupのID 
    AWS_ECS_CLUSTER_NAME: ecs-canary-test-poc-svc-cluster # ECSのクラスタ名 
    AWS_TASK_ROLE_NAME: TaskExecRole # Taskに割り当てるIAMRole名 
    ENVIRONMENT: poc 
    CONTAINER_NAME: canary-test 
    CONTAINER_PORT: 80 
    DEPLOYMENT_MAX_PARCENT: 200 
    DEPLOYMENT_MIN_PARCENT: 100 
    SERVICE_NAME: release # ECSのService名 
  before_script: *deploy_before_script 
  script: *deploy_script 
  tags: 
    - docker 
  environment: 
    name: poc 
  only: 
    - tags 
  when: manual 
個人的なポイントとしては、GitLab CIのEnvironmentsを利用している点です。deploy系のjobにそれぞれenvironmentを定義しておくことで、カナリア用にデプロイしたアプリケーションに問題があった場合、1つ前のバージョンに戻すことが容易になります。
https://docs.gitlab.com/ce/ci/environments.html

上記を設定して、リポジトリにタグを作成すると下記のようなパイプラインが作成されます。

デプロイするときは、canary-deployのjobを実行し様子を見て問題ないことが確認できたらdeployのjobを実行していく想定です。

※ 初回デプロイ時はTaskが1つしか作成されないので手動でスケールさせる必要があります。2回目以降はその時定義されているTask数を維持したまま更新する挙動になっています。



スクリーンショット 2018-12-06 19.44.38.png



更新をしてみる

nginxのv1のコンテナがデプロイされた状況から、カナリアデプロイ -> 通常デプロイ の順で更新をしてみようと思います。ALBにアクセスすると下記のようなページが表示される状況です。

スクリーンショット 2018-12-06 19.58.43.png

ECSは下記のような状態で、canary用のServiceは1 Task,通常用のServiceは3 Taskという状況で更新してみます。


スクリーンショット 2018-12-06 20.00.58.png


  1. nginxで表示させるindex.html内のアプリケーションバージョンをv2に変更し、pushします。
  2. バージョン2.0.0のタグを作成します。するとタグの内容をデプロイするパイプラインが構築されます。


    スクリーンショット 2018-12-06 20.07.57.png

  3. canary-deployをマニュアル実行して、カナリア用のサービスを2.0.0に更新します。



    • 完了後にALBにアクセスすると、4回に1回(タスクが全部で4つあるので)は下記のような新しいバージョンが表示されるようになります。
    スクリーンショット 2018-12-06 20.12.31.png



  4. deployをマニュアル実行して、全体のバージョンを2.0.0に更新します。



    • 完了後にALBにアクセスすると、新しいバージョンのみが表示される様になります。


ロールバックをしてみる

ロールバックには先程書いたように、GitLab CIのEnvironmentsを使います。CI/CD > Environmentsに移動すると下記のようなページが表示されます。



スクリーンショット 2018-12-06 20.20.11.png


Environmentの詳細に移動すると下記のようにデプロイの履歴が表示されます。

この一覧の戻したいバージョンの右側にあるRollbackボタンを押すと、そのバージョンをデプロイするためのjobが実行され、特定のバージョンに戻すことができます。



スクリーンショット 2018-12-06 20.22.34.png



まとめ

ECSのServiceを分離して、それぞれにデプロイするjobをGitLab CIで準備することで、カナリアリリースっぽいこと実現することができました。GitLab CIの仕組みをうまく活用することで、ロールバック等も比較的容易に行えそうかなと思っています。今回は同一のALBのTargetGroupを利用してしまいましたが、分離しておくことでカナリア部分のみの応答状況をCloudWatchで見れそうなので、そういった細かい点の見直しをしていきたいです。

今後はカナリアリリース後、エラーレートとかを見て問題なければ自動でリリースを続ける仕組みとか、新しいバージョンの割合を徐々に増やしていくような仕組みがあったらいいなーとか考えています。以上です。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)