ALBとTerraformによるBlue-Green Deployment

f:id:watass:20141103203916j:plain

最近、AWSからALBというビックなアップデートが発表されました。
【新発表】AWS アプリケーションロードバランサー | Amazon Web Services ブログ

ALBは従来のELBと比べてコンテントベースルーティング、優先度の設定、HTTP/2、WebSocketの対応など、さまざまな変更点を含みます。従来のELBをClassic Load Balancerと呼んでいることからも、今後はこちらへの移行を推進していくものと思われます。

今回のアップデートはかなり衝撃的で、幅広いユースケースが見込めることから、Terraformも早々に対応しています。

特にNginx的な用途が見込めることから、従来のAWS上におけるBlue-Green Deploymentの改善に繋がるのでは?と考えて、ちょっと試してみました。

Blue-Green Deploymentの流れ

使用したTerraformのテンプレートは以下のリポジトリにおいてあります。



ALBはTerraform上ではaws_albリソースとは別に、aws_alb_listener、aws_alb_listener_rule、aws_alb_target_group、aws_alb_target_group_attachmentから構成されています。今回のBlue-Green Deploymentではlistener_ruleの再作成で対応します。具体的には、blue用のtarget_groupとgreen用のtarget_groupを生成して、green用のruleを削除した後にblue用のruleを生成します。

f:id:watass:20160828234919p:plain

ただし、現状、AWS側のAPIの関係でalb_target_group_attachmentはEC2インスタンスとECSのコンテナにしか対応していません。AutoScaling Groupをtarget_groupに紐付ける場合には、以下のようにautoscaling_groupのtarget_group_arnsに指定する必要があります。

resource "aws_autoscaling_group" "green" {
  name = "green-environment"
  max_size = 1
  min_size = 1
  launch_configuration = "${aws_launch_configuration.green.name}"
  vpc_zone_identifier = "${var.subnets}"
  target_group_arns = ["${aws_alb_target_group.green.arn}"]
}

実際の適用処理は、listener_ruleリソースを書きなおしてterraform applyするだけです。

diff --git a/alb.tf b/alb.tf
index 04eeb3f..d24b555 100644
--- a/alb.tf
+++ b/alb.tf
@@ -29,13 +29,13 @@ resource "aws_alb_listener" "test" {
    }
 }

-resource "aws_alb_listener_rule" "green" {
+resource "aws_alb_listener_rule" "blue" {
   listener_arn = "${aws_alb_listener.test.arn}"
   priority = 50

   action {
     type = "forward"
-    target_group_arn = "${aws_alb_target_group.green.arn}"
+    target_group_arn = "${aws_alb_target_group.blue.arn}"
   }

   condition {

重要なのはダウンタイムが発生するかどうかなので、別途1秒ごとにALBのエンドポイントに対してリクエストを投げるスクリプトを走らせながら切替時の処理がどのようになるか見てみました。

f:id:watass:20160828223755p:plain

このように、一瞬502が返ってきてますが、基本的には即座に切り替えが完了しています。まぁこの一瞬の502を許容できないならば、カナリアデプロイ的に両方の環境が共存する瞬間を許容するしかなさそうですね。
ちなみに、aws_alb_target_group_attachmentでインスタンスIDをひも付けた場合はほぼダウンタイム無しで切り替えが完了していました。AutoScaling Groupが原因かわかりませんが、もしかしたらインスタンスの台数が増えたらさらに切り替えに時間がかかるようなこともあるかもしれません。

ライフサイクルを作れるか

実際にはblue環境のインスタンスを常に起動しっぱなしにするのは富豪的なので、max_sizeを0にして起動させないみたいなアプローチが必要そうです。
理想はこの辺の切り替えを最小限の操作で実現できることです。リリースの度にいちいちテンプレートを手動で修正してコミット、レビュー受けてマージみたいな流れを通すとオペレーションミスを誘発しますし、何より面倒です。

大まかな流れとしては

  • blue環境のimage_idを新しいAMIに更新して、max_sizeを適切な数に増やす(standby phase)
  • target_group_listenerを再作成してルーティングを切り替える(switch phase)
  • blue環境をgreen環境に昇格させる(migrate phase)
  • blue環境のmax_sizeを0にする(cleanup phase)

といった感じになるかと思います。自動化が難しいのはmigrate phaseで、コードで管理されたテンプレートをどのように変更するかですね。
まだアイデアレベルですが、blue環境とgreen環境はファイルを分けておいて、migrate phaseではファイル名をswapする感じでいいんじゃないかと思います。この辺の変更やmax_sizeの変更とかはChatOpsベースでPR生成とか仕組み化しておけると良さそうです。なお、ルーティング切替時にリソース名がネックになると思いますが、Terraform v0.7から、state mvコマンドが実装されていますので、これを使うといいでしょう。tfファイル側は変更されませんので、このファイル名の変更タイミングで更新しておきます。

以上のような流れでBlue-Green Deploymentも仕組み化できるかと思います。まだ検証が足りない部分も多いので、また何か知見が得られたら記事を書くことにします。