※本記事はAIが書いて、人間がファクトチェックしています。
IaC連載企画、第3回 - 前編です。
今日はIaCの試行錯誤で時間切れとなったため、第3回は複数回に分けたいと思います。
こんにちは!「絶対止めない」デプロイ戦略ハンズオン、第3回です。
前回(第2回)は、サーバーを1台ずつ入れ替える「ローリングアップデート」を体験しました。ダウンタイムなしで切り替わる様子は感動的でしたが、現場では 「切り替え時間が長い」 ことによる別の問題が発生することがあります。
今回は、AWS純正のIaCツールである「AWS CloudFormation (CFn)」とコンテナ技術(ECS)を使って、「一瞬で切り替え、バグがあれば1秒で戻す」 という究極のスピード感を持つデプロイ戦略に挑戦します!
1. 【プロローグ】 「バグだ!すぐ前のバージョンに戻せ!」
若手エンジニア(あなた):
「先輩!ローリングアップデートで、新機能(緑アプリ)を無停止でデプロイ完了しました!完璧です!」
先輩エンジニア:
「お疲れ!……あれ? 緑アプリの方、決済ボタンが押せなくなってないか!? 致命的なバグだぞ!」
若手エンジニア:
「えっ!? ほんとだ! す、すぐ前の青アプリに戻します!!」
(カタカタ……ターン!)
「……えーっと、ローリングアップデートで1台ずつ戻していくので、完全に復旧するまであと10分くらいかかります……」
先輩エンジニア:
「10分も決済が止まったら大損害だぞ!! なんで一瞬で戻せるようにしておかなかったんだー!!」
ローリングアップデートは「少しずつ」進むため、切り戻し(ロールバック)にも時間がかかるという弱点があります。
致命的なバグが出た場合、1分1秒でも早く前の正常な状態に戻す必要があります。そこで登場するのが今回の主役です。
2. 【図解インプット】 ブルーグリーンデプロイの仕組み
「ブルーグリーンデプロイ(Blue/Green Deployment)」は、現在動いている本番環境(Blue)とは完全に別の環境(Green)を丸ごと新規に作成します。
そして、ロードバランサー(ALB)の「向き先」を一瞬で切り替えることでリリースを行います。
【準備完了時(まだ本番は青)】
[ ALB (本番:80番ポート) ] ──────> [ ECSコンテナ (青 v1) ]
[ ALB (テスト:8080番ポート) ] ──> [ ECSコンテナ (緑 v2) ] ← 裏側でこっそり準備完了
【デプロイ(一瞬で切り替え!)】
[ ALB (本番:80番ポート) ] ──┬─× [ ECSコンテナ (青 v1) ] ← 待機させる(消さない)
└─> [ ECSコンテナ (緑 v2) ] ← 本番デビュー!
- メリット1: 本番トラフィックを切り替える前に、テスト用のポート(8080など)で緑アプリの動作を本番と同じ環境で事前確認できます。
- メリット2: もし緑アプリにバグがあっても、青アプリをまだ消さずに残しておくため、ALBの向き先を戻すだけで1秒でロールバックできます。
今回は、AWSのコンテナサービス「Amazon ECS (Fargate)」と、デプロイ自動化サービス「AWS CodeDeploy」を使います。
3. 【ハンズオン】 1秒で戻る安心感を体感せよ!(所要時間:約45分)前編
Step 1: CFnでベース環境(青アプリ)の構築
💡 【重要】今回のテンプレートの魔法について
実は、CloudFormation単体でECSのBlue/Greenデプロイを実装するコードは、AWS::CodeDeploy::BlueGreenという特殊なフックを使う必要があり、複雑(数百行の長いコード)になります。
今回はコードの難解さで挫折しないよう、特別な魔法を仕込んだ完成済みのテンプレートを用意しました。中身が難しくても、今は「こういう風に動くんだ!」という体験を優先して楽しんでくださいね!
作業フォルダに、以下のテンプレートをコピペして配置したと仮定します。
blue-green.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: "Hands-on: ECS Blue/Green Deployment Complete Template (Base Environment)"
# ==========================================
# 1. Transform Declaration
# ==========================================
Transform:
- 'AWS::CodeDeployBlueGreen'
# ==========================================
# 2. CodeDeploy Hook Configuration
# ==========================================
Hooks:
CodeDeployBlueGreenHook:
Type: 'AWS::CodeDeploy::BlueGreen'
Properties:
TrafficRoutingConfig:
Type: AllAtOnce # Route 100% of traffic immediately
AdditionalOptions:
TerminationWaitTimeInMinutes: 5 # Wait time before terminating original tasks
Applications:
- Target:
Type: 'AWS::ECS::Service'
LogicalID: ECSService
ECSAttributes:
TaskDefinitions:
- TaskDefinitionBlue
- TaskDefinitionGreen
TaskSets:
- TaskSetBlue
- TaskSetGreen
TrafficRouting:
ProdTrafficRoute:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
LogicalID: ALBListenerProd
TestTrafficRoute:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
LogicalID: ALBListenerTest
TargetGroups:
- ALBTargetGroupBlue
- ALBTargetGroupGreen
Resources:
# ==========================================
# 3. Network Infrastructure (VPC, Subnets, IGW)
# ==========================================
VPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: handson-bg-vpc
InternetGateway:
Type: 'AWS::EC2::InternetGateway'
VPCGatewayAttachment:
Type: 'AWS::EC2::VPCGatewayAttachment'
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: 'AWS::EC2::RouteTable'
Properties:
VpcId: !Ref VPC
PublicRoute:
Type: 'AWS::EC2::Route'
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
PublicSubnet2:
Type: 'AWS::EC2::Subnet'
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Subnet1RouteTableAssociation:
Type: 'AWS::EC2::SubnetRouteTableAssociation'
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
Subnet2RouteTableAssociation:
Type: 'AWS::EC2::SubnetRouteTableAssociation'
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
# ==========================================
# 4. Security Groups
# ==========================================
ALBSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Allow HTTP for ALB
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: 0.0.0.0/0
ECSSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Allow HTTP from ALB
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
# ==========================================
# 5. IAM Roles
# ==========================================
ECSExecutionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# ==========================================
# 6. Load Balancer (ALB) and Target Groups
# ==========================================
ALB:
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
Properties:
Name: handson-bg-alb
Scheme: internet-facing
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref ALBSecurityGroup
ALBTargetGroupBlue:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
Name: tg-blue
Port: 80
Protocol: HTTP
TargetType: ip
VpcId: !Ref VPC
ALBTargetGroupGreen:
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
Properties:
Name: tg-green
Port: 80
Protocol: HTTP
TargetType: ip
VpcId: !Ref VPC
ALBListenerProd:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroupBlue
ALBListenerTest:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
LoadBalancerArn: !Ref ALB
Port: 8080
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroupGreen
# ==========================================
# 7. ECS Cluster & Task Definition (Blue/v1)
# ==========================================
ECSCluster:
Type: 'AWS::ECS::Cluster'
Properties:
ClusterName: handson-bg-cluster
TaskDefinitionBlue:
Type: 'AWS::ECS::TaskDefinition'
Properties:
Family: handson-blue-green-task
RequiresCompatibilities:
- FARGATE
NetworkMode: awsvpc
Cpu: '256'
Memory: '512'
ExecutionRoleArn: !GetAtt ECSExecutionRole.Arn
ContainerDefinitions:
- Name: app-container
Image: nginx:1.23-alpine # Initial Blue Application (v1)
PortMappings:
- ContainerPort: 80
# ==========================================
# 8. ECS Service
# ==========================================
ECSService:
Type: 'AWS::ECS::Service'
DependsOn: ALBListenerProd
Properties:
Cluster: !Ref ECSCluster
ServiceName: handson-bg-service
DesiredCount: 1
LaunchType: FARGATE
TaskDefinition: !Ref TaskDefinitionBlue
DeploymentController:
Type: CODE_DEPLOY
LoadBalancers:
- TargetGroupArn: !Ref ALBTargetGroupBlue
ContainerName: app-container
ContainerPort: 80
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ECSSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
-
ターミナルを開き、以下のコマンドでAWS上に環境を一気に構築します。
# CloudFormationスタックの作成(約5分かかります)
aws cloudformation create-stack \
--stack-name handson-blue-green \
--template-body file://blue-green.yaml \
--capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND
実行結果は以下の通りです(ここにたどり着くまでに3時間もかかった。。。CREATE_COMPLETEが出たとき、感動しました)

構築完了後、AWSマネジメントコンソールで「EC2」→「ロードバランサー」を開き、作成されたALBのDNS名(URL)をブラウザで開きます。
Nginx の画面が表示されれば成功です!
今日はAIと壁打ちしながら試行錯誤しまくった結果、ここまででめちゃくちゃ時間使ってしまい、力尽きたのでここまでとさせてください。
【次回予告】
「ブルーグリーンデプロイ」完結編!(明日完了目標)としたいです




