TerraformとCodeDeployでイミュータブルなBlue/Greenデプロイ環境を構築する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、中山です。

最近業務の中でAWS CodeDeployを使っています。AWS上でアプリケーションのデプロイを自動化する上でとても便利なサービスです。

私は完全に習うより慣れろ派の頭なので実際に手を動かさないとなかなか新しいサービスを覚えられません。という訳で、今回はCodeDeployを使ったイミュータブルなBlue/Greenデプロイメントの検証環境をTerraformで構築してみました。

構成図

構成図は以下の通りです。

aws

構成図の解説

  1. 2つのASG(Blue/Green)を作成
  2. そのうち1つのASGをELBに紐付ける(最初はBlue)
  3. アプリケーションのリリース時にCodeDeploy経由でGreenへデプロイ
  4. ELBの紐付け先AutoScaling GroupをGreenに付け替える

アプリケーションのリリース毎にASGを作成してデプロイ、その後ELBの振り分け先を変更させるので常にまっさらな状態でデプロイ可能です。いわゆるイミュータブルなデプロイにしてみました。

コード

作成したコードはGitHubに上げておきました。ディレクトリ構造は以下の通りです。

blue-green-deployment-with-codedeploy-and-terraform
├── README.md
├── assume_role_policy_codedeploy.json
├── assume_role_policy_fleet.json
├── blue
│   ├── appspec.yml
│   ├── blue.html
│   └── scripts
│       ├── application_start.sh
│       ├── application_stop.sh
│       └── before_install.sh
├── cloud_config.yml
├── codedeploy.tf
├── compute.tf
├── green
│   ├── appspec.yml
│   ├── green.html
│   └── scripts
│       ├── application_start.sh
│       ├── application_stop.sh
│       └── before_install.sh
├── keys
├── main.tf
├── modules
│   └── asg
│       ├── blue
│       │   └── blue.tf
│       └── green
│           └── green.tf
├── network.tf
├── output.tf
├── security_group.tf
└── variables.tf

環境の構築

デプロイ環境を構築するためにELBの下にASGが1つだけ紐付いている環境を構築しましょう。まずリポジトリのcloneとKeyPairの作成を実施します。

$ git clone https://github.com/knakayama/blue-green-deployment-with-codedeploy-and-terraform.git
$ cd blue-green-deployment-with-codedeploy-and-terraform
$ ssh-keygen -f keys/site_key -N ''

続いてTerraformを実行します。最初はBlue用のASGのみ作成します。 -target オプションを指定することで特定のResourceのみ作成することができます。また、 -target= を最後に指定すると「Root module」を意味します。これとModuleのパスを同時に指定することで、特定のModuleを除く全てのResourceを作成することが可能です。

$ terraform get
$ terraform plan -target=module.blue -target=
<snip>
Plan: 24 to add, 0 to change, 0 to destroy.
$ terraform apply -target=module.blue -target=
<snip>
Outputs:

  elb_dns_name = codedeploy-demo-elb-2035282467.ap-northeast-1.elb.amazonaws.com
  s3_bucket    = 84644917456e693bbecadd58ac7daae7

CodeDeployを使ったデプロイ

では実際にCodeDeployを使ってデプロイしてみましょう。今回はお馴染みのawscliを使った手順でデプロイしてみます。S3のバケット名は適宜置き換えてください。まずコードをPushします。

$ aws deploy push \
  --application-name codedeploy-demo \
  --s3-location s3://84644917456e693bbecadd58ac7daae7/blue \
  --source blue
aws deploy create-deployment --application-name codedeploy-demo --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=blue,bundleType=zip,eTag=e9860c7c9681ec05ff9afc78f7e1bdeb --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>

続いてデプロイします。

$ aws deploy create-deployment \
  --application-name codedeploy-demo \
  --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=blue,bundleType=zip,eTag=e9860c7c9681ec05ff9afc78f7e1bdeb \
  --deployment-group-name Blue \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --description "Deploy Blue"
{
    "deploymentId": "d-G5YRR9GDF"
}

デプロイの結果を確認します。 Succeeded と表示されたらOKです。

$ aws deploy get-deployment \
  --deployment-id d-G5YRR9GDF \
  --query 'deploymentInfo.status' \
  --output text
Succeeded

ELBへのひも付け

この段階ではまだASGをELBにひも付けていないので、設定しましょう。 modules/asg/blue/blue.tf でコメントアウトされている load_balancers をアンコメントしてください。

  • modules/asg/blue/blue.tf
resource "aws_autoscaling_group" "asg" {
  name                      = "${var.name}"
  launch_configuration      = "${var.launch_configuration_name}"
  vpc_zone_identifier       = ["${var.public_subnet_id}"]
  desired_capacity          = "${var.desired_capacity}"
  max_size                  = "${var.max_size}"
  min_size                  = "${var.min_size}"
  health_check_grace_period = 300
  health_check_type         = "ELB"
  force_delete              = true
  load_balancers            = ["${var.load_balancer_id}"]

ファイルの修正後以下のコマンドでELBにひも付けます。

$ terraform plan -target=module.blue -target=
<snip>
~ module.blue.aws_autoscaling_group.asg
    load_balancers.#:          "0" => "1"
    load_balancers.4183476207: "" => "codedeploy-demo-elb"


Plan: 0 to add, 1 to change, 0 to destroy.
$ terraform apply -target=module.blue -target=

ASGがELBにひも付いたか確認しましょう。以下のように codedeploy-demo-elb と出力されていれば成功です。

$ aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[?AutoScalingGroupName==`codedeploy-demo-blue`].LoadBalancerNames' \
  --output text
codedeploy-demo-elb

インスタンスがELBにInServiceした後「http://<ELB-endpoint>/blue.html」にアクセスしてください。以下の画像が表示されと思います。

blue_v1.0

Green環境のデプロイ

今度はGreen環境をデプロイしましょう。まずGreen用ASGを作成します。以下のコマンドを実行してください。

$ terraform plan -target=module.green -target=
<snip>
+ module.green.aws_autoscaling_group.asg
    availability_zones.#:               "" => "<computed>"
    default_cooldown:                   "" => "<computed>"
    desired_capacity:                   "" => "1"
    force_delete:                       "" => "true"
    health_check_grace_period:          "" => "300"
    health_check_type:                  "" => "ELB"
    launch_configuration:               "" => "codedeploy-demo-fleet-yvfcri3ckff6xadk4hmw67jk7y"
    max_size:                           "" => "1"
    metrics_granularity:                "" => "1Minute"
    min_size:                           "" => "1"
    name:                               "" => "codedeploy-demo-green"
    tag.#:                              "" => "1"
    tag.2523385023.key:                 "" => "Name"
    tag.2523385023.propagate_at_launch: "" => "true"
    tag.2523385023.value:               "" => "Green"
    vpc_zone_identifier.#:              "" => "1"
    vpc_zone_identifier.4120503239:     "" => "subnet-bbb9a2cc"
    wait_for_capacity_timeout:          "" => "10m"

+ module.green.aws_codedeploy_deployment_group.group
    app_name:               "" => "codedeploy-demo"
    autoscaling_groups.#:   "" => "<computed>"
    deployment_config_name: "" => "CodeDeployDefault.OneAtATime"
    deployment_group_name:  "" => "Green"
    service_role_arn:       "" => "arn:aws:iam::************:role/codedeploy-demo-codedeploy-role"


Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply -target=module.green -target=

続いてGreen用のコードをpushします。

$ aws deploy push \
  --application-name codedeploy-demo \
  --s3-location s3://84644917456e693bbecadd58ac7daae7/green \
  --source green
To deploy with this revision, run:
aws deploy create-deployment --application-name codedeploy-demo --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=green,bundleType=zip,eTag=aec927b5d1ee30f406fbf58f160fd2c8 --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>

デプロイします。

$ aws deploy create-deployment \
  --application-name codedeploy-demo \
  --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=green,bundleType=zip,eTag=aec927b5d1ee30f406fbf58f160fd2c8 \
  --deployment-group-name Green \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --description "Deploy Green"
{
    "deploymentId": "d-4K2OAGIDF"
}

結果を確認してみましょう。

$ aws deploy get-deployment \
  --deployment-id d-4K2OAGIDF \
  --query 'deploymentInfo.status' \
  --output text
Succeeded

ELBの振り分け先をGreenに変更する

Green環境の準備ができたのでELBの振り分け先をBlueからGreenに変更しましょう。 modules/asg/blue/blue.tf でアンコメントした箇所を再度コメントアウトしてください。逆に、 modules/asg/green/green.tf のコメントアウトしてある箇所をアンコメントさせてください。

  • modules/asg/blue/blue.tf
resource "aws_autoscaling_group" "asg" {
  name                      = "${var.name}"
  launch_configuration      = "${var.launch_configuration_name}"
  vpc_zone_identifier       = ["${var.public_subnet_id}"]
  desired_capacity          = "${var.desired_capacity}"
  max_size                  = "${var.max_size}"
  min_size                  = "${var.min_size}"
  health_check_grace_period = 300
  health_check_type         = "ELB"
  force_delete              = true
  #load_balancers            = ["${var.load_balancer_id}"]
  • modules/asg/green/green.tf
resource "aws_autoscaling_group" "asg" {
  name                      = "${var.name}"
  launch_configuration      = "${var.launch_configuration_name}"
  vpc_zone_identifier       = ["${var.public_subnet_id}"]
  desired_capacity          = "${var.desired_capacity}"
  max_size                  = "${var.max_size}"
  min_size                  = "${var.min_size}"
  health_check_grace_period = 300
  health_check_type         = "ELB"
  force_delete              = true
  load_balancers            = ["${var.load_balancer_id}"]

ファイルの修正後以下のコマンドを実行してください。今回は全てを対象にするので -target オプションは指定しません。

$ terraform plan
<snip>
~ module.blue.aws_autoscaling_group.asg
    load_balancers.#:          "1" => "0"
    load_balancers.4183476207: "codedeploy-demo-elb" => ""

~ module.green.aws_autoscaling_group.asg
    load_balancers.#:          "0" => "1"
    load_balancers.4183476207: "" => "codedeploy-demo-elb"


Plan: 0 to add, 2 to change, 0 to destroy.
$ terraform apply

ELBの振り分け先が切り替わったことを確認してみましょう。

$ aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[?AutoScalingGroupName==`codedeploy-demo-blue`].LoadBalancerNames' \
  --output json
[
    []
]
$ aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[?AutoScalingGroupName==`codedeploy-demo-green`].LoadBalancerNames' \
  --output text
codedeploy-demo-elb

Green用ASGのインスタンスがELBにInServiceした後「http://<ELB-endpoint>/green.html」にアクセスしてください。以下の画像が表示されと思います。

green_v1.0

新アプリケーションのリリース

新しいアプリケーションをリリースしなければいけないと仮定して、デプロイをしてみましょう。説明を簡易化させるためにHTMLの修正のみとします。 blue/blue.html を以下のように修正してください。

  • blue/blue.html
<!DOCTYPE html>
<html>
  <head>
    <title>Blue</title>
    <style type="text/css">

    body {
      background-color: #00FFFF;
    }
    </style>
  </head>
  <body>
    <h1>This is Blue v1.1.</h1>
  </body>
</html>

ファイル修正後コードをpushしておきます。

$ aws deploy push \
  --application-name codedeploy-demo \
  --s3-location s3://84644917456e693bbecadd58ac7daae7/blue \
  --source blue
To deploy with this revision, run:
aws deploy create-deployment --application-name codedeploy-demo --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=blue,bundleType=zip,eTag=b26167f8cf582612fbf060eafbb793bf --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>

Blue用ASGを再度作成し直します(これくらいならローリングアップデートでも全然問題無いですが)。 taint サブコマンドでBlue用ASGのResourceを「汚染」させることで、次回 apply 時に強制的にResourceを再作成させることができます。

$ terraform taint -module=blue aws_autoscaling_group.blue
The resource aws_autoscaling_group.blue in the module root.blue has been marked as tainted!

この状態で plan を実行すると以下のように再作成されることが分かります。

$ terraform plan
<snip>
-/+ module.blue.aws_autoscaling_group.blue
    availability_zones.#:              "" => "<computed>"
    default_cooldown:                  "" => "<computed>"
    desired_capacity:                  "" => "1"
    force_delete:                      "" => "true"
    health_check_grace_period:         "" => "300"
    health_check_type:                 "" => "ELB"
    launch_configuration:              "" => "codedeploy-demo-fleet-qykjoc6aurbctawu6gw2tginc4"
    max_size:                          "" => "1"
    metrics_granularity:               "" => "1Minute"
    min_size:                          "" => "1"
    name:                              "" => "codedeploy-demo-blue"
    tag.#:                             "" => "1"
    tag.833643807.key:                 "" => "Name"
    tag.833643807.propagate_at_launch: "" => "true"
    tag.833643807.value:               "" => "Blue"
    vpc_zone_identifier.#:             "" => "1"
    vpc_zone_identifier.1684201939:    "" => "subnet-98a5beef"
    wait_for_capacity_timeout:         "" => "10m"

~ module.blue.aws_codedeploy_deployment_group.blue
    autoscaling_groups.#: "" => "<computed>"


Plan: 1 to add, 1 to change, 0 to destroy.

準備が整ったところで apply しましょう。

$ terraform apply

新Blue用ASG作成後インスタンスが起動したら、新しいコードをデプロイします。

$ aws deploy create-deployment \
  --application-name codedeploy-demo \
  --s3-location bucket=84644917456e693bbecadd58ac7daae7,key=blue,bundleType=zip,eTag=b26167f8cf582612fbf060eafbb793bf \
  --deployment-group-name Blue \
  --deployment-config-name CodeDeployDefault.OneAtATime \
  --description "Deploy Blue"
{
    "deploymentId": "d-L4KM4SRDF"
}

結果を確認します。

$ aws deploy get-deployment \
  --deployment-id d-L4KM4SRDF \
  --query 'deploymentInfo.status' \
  --output text
Succeeded

デプロイが完了したら再度同じようにELBの振り分け先を変更します。

  • modules/asg/blue/blue.tf
resource "aws_autoscaling_group" "blue" {
  name                      = "${var.name}"
  launch_configuration      = "${var.launch_configuration_name}"
  vpc_zone_identifier       = ["${var.public_subnet_id}"]
  desired_capacity          = "${var.desired_capacity}"
  max_size                  = "${var.max_size}"
  min_size                  = "${var.min_size}"
  health_check_grace_period = 300
  health_check_type         = "ELB"
  force_delete              = true
  load_balancers            = ["${var.load_balancer_id}"]
  • modules/asg/green/green.tf
resource "aws_autoscaling_group" "green" {
  name                      = "${var.name}"
  launch_configuration      = "${var.launch_configuration_name}"
  vpc_zone_identifier       = ["${var.public_subnet_id}"]
  desired_capacity          = "${var.desired_capacity}"
  max_size                  = "${var.max_size}"
  min_size                  = "${var.min_size}"
  health_check_grace_period = 300
  health_check_type         = "ELB"
  force_delete              = true
  #load_balancers            = ["${var.load_balancer_id}"]

ファイルの修正後以下のコマンドを実行してELBの振り分けを変更します。

$ terraform plan
~ module.green.aws_autoscaling_group.green
    load_balancers.#:          "1" => "0"
    load_balancers.4183476207: "codedeploy-demo-elb" => ""

~ module.blue.aws_autoscaling_group.blue
    load_balancers.#:          "0" => "1"
    load_balancers.4183476207: "" => "codedeploy-demo-elb"
$ terraform apply

ASGがELBにひも付いたか確認します。

$ aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[?AutoScalingGroupName==`codedeploy-demo-blue`].LoadBalancerNames' \
  --output text
codedeploy-demo-elb
$ aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[?AutoScalingGroupName==`codedeploy-demo-green`].LoadBalancerNames' \
  --output json
[
    []
]

インスタンスがELBにInServiceした後「http://<ELB-endpoint>/blue.html」にアクセスしてください。以下の画像が表示されと思います。

blue_v1.1

まとめ

いかがだったでしょうか。

今回は使用しなかったですがASGのLifeCycle Hookを使えばデプロイの処理自体も自動化可能です。夢がひろがりんぐですね。

本エントリがみなさんの参考になれば幸いです。