TerraformとCodeDeployでイミュータブルなBlue/Greenデプロイ環境を構築する
はじめに
こんにちは、中山です。
最近業務の中でAWS CodeDeployを使っています。AWS上でアプリケーションのデプロイを自動化する上でとても便利なサービスです。
私は完全に習うより慣れろ派の頭なので実際に手を動かさないとなかなか新しいサービスを覚えられません。という訳で、今回はCodeDeployを使ったイミュータブルなBlue/Greenデプロイメントの検証環境をTerraformで構築してみました。
構成図
構成図は以下の通りです。
構成図の解説
- 2つのASG(Blue/Green)を作成
- そのうち1つのASGをELBに紐付ける(最初はBlue)
- アプリケーションのリリース時にCodeDeploy経由でGreenへデプロイ
- 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」にアクセスしてください。以下の画像が表示されと思います。
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」にアクセスしてください。以下の画像が表示されと思います。
新アプリケーションのリリース
新しいアプリケーションをリリースしなければいけないと仮定して、デプロイをしてみましょう。説明を簡易化させるために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」にアクセスしてください。以下の画像が表示されと思います。
まとめ
いかがだったでしょうか。
今回は使用しなかったですがASGのLifeCycle Hookを使えばデプロイの処理自体も自動化可能です。夢がひろがりんぐですね。
本エントリがみなさんの参考になれば幸いです。