Terraformで極めるECSブルーグリーンデプロイメントの深淵:コードによる「無停止」の完全制御
インフラの自動化を語る際、単に「Terraformでリソースが作れる」ことと、「本番環境のトラフィックをミリ秒単位の損失もなく安全に切り替える」ことの間には、深淵のような隔たりがある。
多くのエンジニアが陥る罠は、Terraformの`aws_ecs_service`を単一の記述で完結させようとすることだ。だが、我々のようなSREにとって、IaCは単なる定義体ではない。それは「システムの安定稼働を定義する、生きている状態遷移図」である。
今回は、CodeDeployを使わずにTerraformとALBのリスナールール操作だけでブルーグリーンデプロイを実現する、極めて「硬派」かつ制御性に優れたアプローチを解説する。
—
1. なぜ「CodeDeploy」ではなく「リスナールール」なのか
AWS CodeDeployは便利だが、ブラックボックス化された挙動が多く、複雑なヘルスチェックやカスタムな切り替えロジックを挟む際に柔軟性を欠くことが多い。
直接ALBのリスナールールを操作することで、以下のメリットが生まれる。
- 可視性: トラフィックの重み付けやルールの切り替えがTerraformのStateと同期される。
- 冪等性: どのような状態からでも、定義した「理想の状態」へ一意に遷移できる。
- 低レイヤ制御: 特定のパスやヘッダーによるトラフィック移行など、高度なカナリアリリースへ即座に応用可能。
—
2. 状態を分離する:Blue/Greenターゲットグループ設計
ECSのサービス定義に依存しすぎると、Terraformは「リソースの置換」を試み、ダウンタイムを誘発する。これを避けるため、ターゲットグループとECSサービスを疎結合にするのが鉄則だ。
実践コード:ALBリスナーとターゲットグループの制御
ターゲットグループを2つ用意し、どちらがアクティブかを識別子で管理する
resource “aws_lb_target_group” “ecs_tg” {
count = 2
name = “app-tg-${count.index}”
port = 80
protocol = “HTTP”
vpc_id = var.vpc_id
# ライフサイクル管理:デプロイ中の変更でリソースを再作成させない
lifecycle {
create_before_destroy = true
}
}
リスナールールを動的に制御するための変数を定義
variable “active_tg_index” {
type = number
default = 0
}
resource “aws_lb_listener_rule” “app_rule” {
listener_arn = var.alb_listener_arn
action {
type = “forward”
target_group_arn = aws_lb_target_group.ecs_tg[var.active_tg_index].arn
}
condition {
path_pattern { values = [“/”] }
}
}
—
3. ECSサービスの制御:Terraformの「罠」を回避する
`aws_ecs_service`リソース内で`load_balancer`ブロックを定義すると、ターゲットグループの変更が「サービス自体の更新」とみなされ、意図しないローリングアップデートが走る可能性がある。
これを防ぐために、`lifecycle { ignore_changes = [load_balancer] }` を駆使し、ターゲットグループへの接続はTerraformの管理外、あるいは明示的なAPI操作によって切り替える戦略を採る。
resource “aws_ecs_service” “main” {
name = “app-service”
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.main.arn
# ターゲットグループの変更でサービス自体を再デプロイさせない
lifecycle {
ignore_changes = [load_balancer]
}
load_balancer {
target_group_arn = aws_lb_target_group.ecs_tg[var.active_tg_index].arn
container_name = “app”
container_port = 80
}
}
—
4. 自動化の核心:トラフィック切り替えスクリプト
Terraformの実行だけで完結しないのがこの設計の「現場感」だ。実際の切り替えは、以下の手順をラップしたCI/CDパイプラインで行う。
1. 待機系(Green)へのデプロイ: `var.active_tg_index` を変更せずに、ECSサービスのタスク定義のみを更新。
2. ヘルスチェック: 待機系ターゲットグループの `aws_lb_target_group_health` をAPIでポーリングし、正常性を確認。
3. トラフィック切り替え:
# 実際は terraform plan/apply で active_tg_index を切り替える
terraform apply -var=”active_tg_index=1″ -auto-approve
エキスパートの知見:メモリとパフォーマンスのハック
- 接続のドレイニング: `deregistration_delay`を極限までチューニングせよ。デフォルトの300秒は長すぎるケースが多い。アプリケーションが処理中のリクエストを安全に捌ききれる最小値(例: 30-60秒)を負荷試験で導き出すこと。
- Stateロックの最適化: `null_resource` を使って「切り替え前」と「切り替え後」に複雑なバリデーション(DBスキーマの整合性チェックなど)を挿入せよ。Terraformのplanが通っても、ビジネスロジック的に切り替えてはならない瞬間がある。
—
5. 終わりに:伝説的なIaCとは
真のSREにとって、Terraformはコードを書く道具ではない。「サービスが停止するリスクを、コードによって消し去るための儀式」である。
今回紹介した「ターゲットグループのインデックス切り替え」手法は、一見シンプルだが、大規模なマイクロサービス構成において最も予測可能性が高く、かつ復旧(ロールバック)が容易なパターンだ。
複雑なツールに頼る前に、AWSのプリミティブなコンポーネントをどう組み合わせれば「不変性」を担保できるか。そこを突き詰めることこそが、クラウドインフラを掌握する者の責務である。
さあ、次は君の環境で、この「完全なる切り替え」を実装してほしい。その先には、アラートに怯えることのない静寂な夜が待っているはずだ。