1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者・IaC入門】第3回 - 前編:CloudFormationはCREATE_COMPLETEまでたどり着くのは大変だが堅牢

1
Posted at

※本記事は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)の「向き先」を一瞬で切り替えることでリリースを行います。

image.png

【準備完了時(まだ本番は青)】
[ 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が出たとき、感動しました)
image.png

image.png

image.png

image.png

構築完了後、AWSマネジメントコンソールで「EC2」→「ロードバランサー」を開き、作成されたALBのDNS名(URL)をブラウザで開きます。
Nginx の画面が表示されれば成功です!

image.png

今日はAIと壁打ちしながら試行錯誤しまくった結果、ここまででめちゃくちゃ時間使ってしまい、力尽きたのでここまでとさせてください。


【次回予告】

「ブルーグリーンデプロイ」完結編!(明日完了目標)としたいです

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?