🚦

【AWS】CodePipeline+ECSでBlue/Greenデプロイしてみた

2023/10/07に公開

概要

会社で aws を触ることになり、基本から学んでいこうと思ったため備忘録として記事を書き始めました。
今回は 有名な Blue/Green デプロイ に関する概要説明の後 CodePipeline と ECS を使用して 自分の PC 内の Next アプリケーションを Blue/Green デプロイして Web 上に公開してみます 🌀
もし理解が違うよというところ等ありましたら優しく教えて頂けると幸いです 🙇‍♀️

Blue/Green デプロイ とは

Blue/Green デプロイとは、アプリケーションのデプロイをダウンタイム無しに行うことができるデプロイ手法のことです。
下の図をご覧ください。

旧アプリケーションから新アプリケーションにアプリケーションの移行を行いたかったとします。
もし Blue/Green デプロイを行わなかった場合旧アプリケーションを新アプリケーションに移行する際に本番サーバーを一度停止することでダウンタイムが発生します。
しかし、Blue/Green デプロイは違います。
以下は Blue/Green デプロイの一例ですが

  • 旧アプリケーションと新アプリケーションの二つにユーザーの通信を分配するロードバランサーを用意
  • 旧アプリケーションと新しく立ち上げた新アプリケーションを用意
  • 初めの段階で全て旧アプリケーションに流れていた通信を新アプリケーションに徐々に流す
  • 最終的に全ての通信を新アプリケーションに流す
  • 旧アプリケーションを停止

という流れをなぞることでダウンタイム無しで新アプリケーションへの移行が行えます。

CodePipeline、ECS とは

CodePipeline とは AWS 上で CI/CD を行う際に使用されるサービスです。
ECS とは、AWS 上でコンテナオーケストレーションを行う際に使用されるサービスです。
こちらの記事で CodePipeline と ECS の概要・使用方法の説明を行なっていますので、詳細気になる方は是非読んでみてください 😌

https://zenn.dev/alichan/articles/fce0f352402a6d

CodePipeline + ECS で Next アプリケーションを Blue/Green デプロイして Web 上に公開してみる

では、Next アプリケーションを CodePipeline + ECS で Blue/Green デプロイして Web 上に公開するハンズオンを行ってみます。
ここでは、以下の様な構成の パイプラインを 2 本組んでみます。

黒い連番の数字で書かれているのは Next アプリケーションを CodePipeline + ECS で Blue/Green デプロイして Web 上に公開する手順です。
以下手順が各数字と対応します。

  1. 自分の PC で作成した Next アプリケーションを CodeCommit 上のリポジトリに push
  2. CodePipeline が CodeCommit のリポジトリの変更を検知して リポジトリ内の buildspec.yaml ファイルをもとに Next アプリケーションのイメージ化を行う
  3. CodeBuild が作成された Next アプリケーションのイメージを ECR に保存する
  4. CodeDeploy が CodeCommit 上のリポジトリに格納されたタスク定義を行う taskdef.json とデプロイ設定を行う appspec.yaml を参照する
  5. CodeDeploy が Blue/Green デプロイをスタートする。まず、新 Next アプリケーションの起動を始める
  6. タスク定義ファイルが ECR の指定した URI を元にコンテナ化するイメージを参照する
  7. サービスがイメージとタスク定義ファイルを参照する
  8. サービスがタスクを Fargate 上に立ち上げる
  9. 立ち上がったタスクが新 Next アプリケーションとして起動される
  10. CodeDeploy がロードバランサーによる Blue/Green デプロイを開始する
  11. 旧 Next アプリケーションに流れている通信を徐々に新 Next アプリケーションに流し、最終的に全ての通信を新 Next アプリケーションに流す
  12. 全ての通信を流したあと、旧 Next アプリケーションを停止する
  13. インターネットからはロードバランサーを介して新アプリケーションが閲覧される
ロードバランサー・アプリケーション周りのインフラ構成の補足

ロードバランサーは外部に対して 80 番ポートを開放しており、ここから流れてきた通信が後続のコンテナに割り振られます。
ロードバランサーではターゲットグループという通信を流す対象を含ませるグループに対して通信が割り当てられる様になっており、各ターゲットグループは旧新それぞれのコンテナを含みます。
旧 Next アプリケーションと新 Next アプリケーションは別コンテナとして立ち上がっており、それぞれに別 IP アドレスが割り振られています。
それぞれ 80 番ポートが開放されており、ここから通信が流れる様になっています。

では、上記手順を以下から行っていきます。

  1. Fargate がコンテナを立ち上げるために必要な VPC 他ネットワークリソースを作成

まず、最終的にコンテナが立ち上がる環境を作成します。
マネジメントコンソールで VPC 画面を表示し、VPC を作成ボタンを押下してください。

VPC を作成画面で以下の様に入力して VPC を作成ボタンを押下してください。

次に ロードバランサーに付与するセキュリティグループと立ち上がるタスクに付与するセキュリティグループを作成します。
VPC 画面でセキュリティ > セキュリティグループ画面からセキュリティグループを作成ボタンを押下します。

まずロードバランサーに付与するセキュリティグループを作成します。
セキュリグループを作成画面で以下の様に入力してセキュリティグループを作成ボタンを押下します。

次にタスクに付与するセキュリティグループを作成します。
同じ様にセキュリグループを作成画面で以下の様に入力してセキュリティグループを作成ボタンを押下します。

次に、Blue/Green デプロイを行う際に通信を振り分けるロードバランサーを作成します。
マネジメントコンソールで EC2 > ロードバランサー 画面を表示し、ロードバランサー の作成ボタンを押下してください。

ロードバランサーのタイプではApplication Load Balancerで作成を押下し、Application Load Balancer を作成画面では以下の様に入力してロードバランサーを作成してください。

途中でターゲットグループの作成リンクを押下するとターゲットグループを作成できるので、以下の様に入力してターゲットグループを 2 つ作成してください。

1 つ目のターゲットグループを作成する際にグループの詳細の指定で以下項目を入力して次へを押下してください。

ターゲットの登録で以下項目を入力してターゲットグループの作成を押下してください。

2 つ目のターゲットグループを作成する際にグループの詳細の指定で以下項目を入力して次へを押下してください。

ターゲットの登録で以下項目を入力してターゲットグループの作成を押下してください。

  1. ECR でイメージを保存するリポジトリを作成

マネジメントコンソールで ECR と検索し検索結果のリポジトリを押下してください。

リポジトリ画面でリポジトリを作成を押下してください。

リポジトリを作成画面で以下項目を入力してリポジトリを作成ボタンを押下してください。

  1. ECS 他コンテナ公開用リソース作成

タスクが ECR に保存されたイメージを扱えるようにタスクに定義するロールを作成します。
マネジメントコンソールで IAM を表示し、ロール画面からロールを作成ボタンを押下してください。

信頼されたエンティティを選択で以下項目を入力して次へボタンを押下してください。

許可を追加で何も選択せず次へボタンを押下してください。
名前、確認、および作成画面で以下項目を入力してロールを作成ボタンを押下してください。

同じように以下設定値で blue-green-execute-task-role を作成してください。

項目名
ロール名 blue-green-execute-task-role
信頼ポリシー 上と同じ
許可ポリシー AmazonECSTaskExecutionRolePolicy

ECS でタスクを作成します。
マネジメントコンソールで ECS 画面を表示し、タスク定義から新しいタスク定義の作成 > JSON を使用した新しいタスク定義の作成ボタンを押下してください。

タスクを以下の様に定義して作成ボタンを押下し、タスク定義を作成してください。

{
  "containerDefinitions": [
    {
      "name": "blue-green-task",
      "image": "<ECR のリポジトリ blue-green-image の URI>:latest",
      "portMappings": [
        {
          "name": "blue-green-port",
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "essential": true
    }
  ],
  "family": "blue-green-family",
  "taskRoleArn": "<blue-green-task-roleのARN>",
  "executionRoleArn": "<blue-green-execute-task-roleのARN>",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "3072",
  "runtimePlatform": {
    "cpuArchitecture": "ARM64"
  }
}

クラスター 画面を表示し、クラスターの作成ボタンを押下してください。

クラスターの作成画面で以下項目を入力し、作成ボタンを押下してください。

クラスター作成後、クラスター内で立ち上げるサービスを AWS CLI で作成します。
CLI で作成する理由としては、立ち上げるサービスに設定する項目に CLI でないと設定できない値があるからです。
もし AWS CLI が自分の PC にインストールされていなかった場合、以下記事を参考に CLI をインストールしてください。
2-3. CLI で接続のaws configureでアクセスキーとシークレットアクセスキーを入力するところまで行ってください。

https://dev.classmethod.jp/articles/lim_aws_cli_start/

これで作成した IAM ユーザーとして AWS にログインできたので、サービスを作成します。
まず、下の記載をしたcreate-service.jsonという json ファイルを作成してください。

{
  "taskDefinition": "blue-green-family:1",
  "cluster": "blue-green-cluster",
  "loadBalancers": [
    {
      "targetGroupArn": "<blue-green-targetgroup-1のARN>",
      "containerName": "blue-green-task",
      "containerPort": 80
    }
  ],
  "desiredCount": 0,
  "launchType": "FARGATE",
  "schedulingStrategy": "REPLICA",
  "deploymentController": {
    "type": "CODE_DEPLOY"
  },
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": ["<blue-green-subnet-public1-ap-northeast-1aのサブネットID>"],
      "securityGroups": ["<blue-green-task-sgのセキュリティグループID>"],
      "assignPublicIp": "ENABLED"
    }
  }
}

その json ファイルを格納しているディレクトリを自分の PC のターミナルで開き、以下コマンドを入力してサービスを作成してください。

$ aws ecs create-service --service-name blue-green-service --cli-input-json file://create-service.json

まだタスクは立ち上げていません。
Blue/Green デプロイを行う際に、タスクを立ち上げます。

  1. Next アプリケーションの作成、CodeCommit へ push する

パイプラインで必要になる CodeCommit 内のリポジトリを作成してアプリケーションを push します。
最初に、Next アプリケーションを作成して CodeCommit へ push します。
CodeCommit で以下記事のCodeCommitを使ってみるの章の手順CodeCommitからHTTPSのクローンのクローン URL をクリップボードにコピーするところまでを行ってください。
コピーしたクローン URL はメモしておき、まだクローンはしないでください。
途中でリポジトリを作成しますが、設定するリポジトリ名はblue-green-appにしてください。

https://www.hacknotes.jp/blog/codecommit-guide/#codecommitを使ってみる

以下コマンドを実行して Next アプリケーションを作成してください。
途中の質問には以下の様に答えてください。

$ npx create-next-app@13
...
Need to install the following packages:
  create-next-app
Ok to proceed? (y) y
✔ What is your project named? … next-container
✔ Would you like to use TypeScript with this project? … Yes
✔ Would you like to use ESLint with this project? … Yes
✔ Would you like to use Tailwind CSS with this project? … No
✔ Would you like to use `src/` directory with this project? … Yes
✔ Would you like to use experimental `app/` directory with this project? … No
✔ What import alias would you like configured? … @/*
...

アプリケーションが作成されるので、blue-green-app のルートディレクトリにbuildspec.yamlファイルを配置し、以下の様に記載してください。

version: 0.2

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin <リポジトリURLのリポジトリ名を排除した文字列>
      - REPOSITORY_URI=<リポジトリURI>
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG

このファイルが後に CodeBuild で読み込まれ、記載したコマンドが実行されます。
Docker で Next アプリケーションをイメージ化するために必要なDockerfileもルートディレクトリ配下に配置して、下の様に記載してください。

FROM node:latest

WORKDIR /usr/src

COPY . .

RUN npm install

RUN npm run build

CMD ["npm", "start"]

また、package.json の start コマンドも以下の様に修正してください。

...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start -p 80",
    "lint": "next lint"
  }
...

以下コマンドで CodeCommit 上のリポジトリにコードを push してください。
push 時にユーザー名とパスワードを求められた場合、上記記事を参考に入力してください。

$ git init
$ git remote add origin <blue-green-appのクローンURL>
$ git add .
$ git commit -m "Nextアプリケーション作成"
$ git push origin main
  1. CodePipeline でイメージ作成用パイプラインを作成する

まず、 イメージ作成用の CI/CD パイプラインを構成します。
マネジメントコンソールで CodePipeline 画面を表示し、パイプラインを作成するボタンを押下してください。

パイプラインの設定を選択するで以下項目を入力して次にボタンを押下してください。

ソースステージを追加するで以下項目を入力して次にボタンを押下してください。

ビルドステージを追加するで以下項目を入力してビルドプロジェクトを作成するボタンを押下してプロジェクトを作成してください。

ビルドプロジェクトを作成するで以下項目を入力して CodePipeline に進むを押下して先ほどの画面に戻り、次にボタンを押下してください。

デプロイステージを追加するで導入段階をスキップするボタン → レビューでパイプラインを作成するボタンを押下してパイプラインを作成してください。

パイプラインが作成されたら、CodeBuild に付与されているロールに ECR へのアクセス権限を付与します。

マネジメントコンソールで IAM のロール 画面を表示し、codebuild-blue-green-build-service-roleを押下してください。

許可を追加 > ポリシーをアタッチを押下してください。

AmazonEC2ContainerRegistryPowerUser を選択し、許可の追加ボタンを押下してください。

これでイメージ作成用のパイプラインが作成されました。

  1. CodePipeline で Blue/Green デプロイ用パイプラインを作成する

次に、 Blue/Green デプロイ用の CI/CD パイプラインを構成します。
まず、CodeDeploy が ECS にアクセスするために付与するサービスロールを作成します。
マネジメントコンソールで IAM のロール 画面を表示し、ロールを作成画面を押下し、信頼されたエンティティを選択画面で以下項目を入力して次へボタンを押下して、許可を追加でも次へボタンを押下してください。

名前、確認、および作成画面でロールを作成ボタンを押下してロールを作成してください。

次に、ECS でタスクを立ち上げる際に使用するタスク定義ファイルと CodeDeploy がデプロイ時に使用する appspec.yaml ファイルを CodeCommit のリポジトリに格納します。
4. Next アプリケーションの作成、CodeCommit へ push するの手順を参考に、CodeCommit にblue-green-deploy-artifactというリポジトリを作成し、以下 2 ファイルを作成して格納してください。

taskdef.json

{
  "containerDefinitions": [
    {
      "name": "blue-green-task",
      "image": "<ECR のリポジトリ blue-green-image の URI>:latest",
      "portMappings": [
        {
          "name": "blue-green-port",
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "essential": true
    }
  ],
  "family": "blue-green-family",
  "taskRoleArn": "<blue-green-task-roleのARN>",
  "executionRoleArn": "<blue-green-task-execute-roleのARN>",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "3072",
  "runtimePlatform": {
    "cpuArchitecture": "ARM64"
  }
}

appspec.yaml

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "blue-green-task"
          ContainerPort: 80

次に、デプロイステージ用に CodeDeploy でのデプロイ設定を行います。
マネジメントコンソールで CodeDeploy 画面を表示し、CodeDeploy → アプリケーションでアプリケーションの作成ボタンを押下してください。

アプリケーションの作成で以下項目を入力し、アプリケーションの作成ボタンを押下してください。

アプリケーションの画面でデプロイグループの作成ボタンを押下してください。

デプロイグループの作成画面でデプロイグループの作成ボタンを押下してください。
ここのデプロイ設定で今回は CodeDeployDefault.ECSAllAtOnce を設定していますが、これは新アプリケーションに通信を置き換える時にどの様に置き換えていくかを設定する項目です。
今回は時間短縮のため一度に全ての通信を置き換える様に設定していますが、1 分ごとに 1 割の通信を置き換えるという様な置き換え方を選ぶこともできます。

最後に、Blue/Green デプロイ用のパイプラインを作成します。
パイプラインを作成するボタンを押下し、パイプラインの設定を選択するで以下項目を入力して次にボタンを押下してください。

ソースステージを追加するで以下項目を入力して次にボタンを押下してください。

ビルドステージをスキップボタンを押下し、デプロイステージを追加する画面で以下項目を入力して次にボタンを押下してください。

レビュー画面でパイプラインを作成するボタンを押下してください。
これで Blue/Gree デプロイ用のパイプラインが作成されました。

  1. Blue/Green デプロイしてみる

Blue/Green デプロイをする準備が整ったので、実際にデプロイを行ってみます。
まず、旧アプリケーションとする Next アプリケーションを立ち上げます。

ECS の画面 > クラスター > blue-green-cluster > サービス > blue-green-service を開き、サービスを更新ボタンを押下してください。

必要なタスクを 1 に修正し、更新ボタンを押下してください。
これでサービスが立ち上がりました。

では、新アプリケーションとする Next アプリケーションを以下の様に自分の PC で文言を追記して作成します。
自分の PC 内のblue-green-appの src > pages > index.tsx に以下の様に追記してください。

...
<h1>新アプリケーションです</h1>
<p>
  Get started by editing&nbsp;
  <code className={styles.code}>src/pages/index.tsx</code>
</p>
...

その後、以下コマンドで修正を CodeCommit のリポジトリに反映させます。

$ git add .
$ git commit -m "新アプリケーションです"
$ git push origin main

これで修正が反映されたはずです。
CodePipeline > パイプライン > blue-green-image-pipeline で開いたパイプラインが以下の様に全ての段階が成功していたら完璧です 🙆
最後の Build でも新アプリケーションですのコメントが付与されたコミットがビルドされていることがわかります。

次に、Blue/Green デプロイを行います。
EC2 > ロードバランサー > blue-green-loadbalancer > リスナーとルールで現在ターゲットグループ 1 に対して通信が流れていることが分かります。

これを Blue/Green デプロイしてターゲットグループ 2 に流していきます。
CodePipeline > パイプライン > blue-green-deploy-pipeline でパイプラインを開き、変更をリリースするを押下します。
以下の様に全ての段階が成功していたら完璧です 🙆

Deploy ステージの詳細で、以下の様に Blue/Green デプロイの過程が見れますが、最終的に以下の様に置換が 100%になっていたら Blue/Green デプロイが成功しているということです 👍

先ほどのロードバランサーの画面を見てみると、ターゲットグループ 2 に対して通信が流れていることが分かります。

終わりに

ハンズオンお疲れ様でした。
前回はもう少し手軽に CodePipeline + ECS で CI/CD パイプラインを組む記事を書いたので、よかったら読んでみてください。

https://zenn.dev/alichan/articles/fce0f352402a6d

ここまで読んでいただき本当にありがとうございます 🙇‍♀️

参照

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/welcome.html
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html

Discussion