こんにちは。最近神奈川県藤沢市に引っ越してQOLが上がりつつある、DS課の古川です。
はじめに
Infrastructure as Code(以下IaC)が当たり前の世の中になってきました。 この流れでAmazon ECS (以下ECS)もAWS CloudFormaition(以下CFn)でIaC化して構築する機会が増えました。
ECS on Fargateはデプロイタイプとして、ローリングアップデートとBlue/Geenデプロイを選択できます。
docs.aws.amazon.com docs.aws.amazon.com
今まで、ECSのローリングアップデートをCFnにてIaC化する機会はあったのですが、Blue/GeenデプロイをIaC化したことはなかったので、今回挑戦してみました。
結論
いきなり結論から述べますと、IaC化には以下の2パターンがあります。
CloudFormaionからBlue/Greenデプロイを実行するなら可能
100%IaC化は無理で、一部手動によるリソースの用意が必要
本記事では1. CloudFormaionからBlue/Greenデプロイを実行するなら可能
を実践してみました。
テンプレート
今回の検証は、AWS公式ページを参考にしております。
公式ページを参考に、自身で用意したyamlを以下に記載します。※ネットワーク関連のリソースは予め用意しております。
この中で気になった箇所をピックアップしてきます。
AWSTemplateFormatVersion: 2010-09-09 Description: ecs_poc_blue_green Parameters: System: Type: String Default: ecs Stage: Type: String Default: poc Vpc: Type: AWS::EC2::VPC::Id ALBSubnet1a: Type: AWS::EC2::Subnet::Id ALBSubnet1c: Type: AWS::EC2::Subnet::Id ECSSubnet1a: Type: AWS::EC2::Subnet::Id ECSSubnet1c: Type: AWS::EC2::Subnet::Id Transform: - AWS::CodeDeployBlueGreen Hooks: CodeDeployBlueGreenHook: Type: AWS::CodeDeploy::BlueGreen Properties: TrafficRoutingConfig: Type: AllAtOnce Applications: - Target: Type: AWS::ECS::Service LogicalID: ECSPocService ECSAttributes: TaskDefinitions: - ECSPocBlueTaskDefinition - ECSPocGreenTaskDefinition TaskSets: - ECSPocBlueTaskSet - ECSPocGreenTaskSet TrafficRouting: ProdTrafficRoute: Type: AWS::ElasticLoadBalancingV2::Listener LogicalID: ECSPocALBListenerProdTraffic TestTrafficRoute: Type: AWS::ElasticLoadBalancingV2::Listener LogicalID: ECSPocALBListenerTestTraffic TargetGroups: - ECSPocALBTargetGroupBlue - ECSPocALBTargetGroupGreen Resources: ECSPocALBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for ec2 access 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 ECSPocContainerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for ecs VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 ECSPocALBTargetGroupBlue: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub "${System}-${Stage}-blue-tg" HealthCheckIntervalSeconds: 15 HealthCheckPath: /healthcheck.php HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthyThresholdCount: 3 UnhealthyThresholdCount: 2 Matcher: HttpCode: 200 Port: 80 Protocol: HTTP Tags: - Key: System Value: !Ref System - Key: Environment Value: !Ref Stage TargetType: ip VpcId: !Ref Vpc ECSPocALBTargetGroupGreen: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub "${System}-${Stage}-green-tg" HealthCheckIntervalSeconds: 15 HealthCheckPath: /healthcheck.php HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthyThresholdCount: 3 UnhealthyThresholdCount: 2 Matcher: HttpCode: 200 Port: 80 Protocol: HTTP Tags: - Key: System Value: !Ref System - Key: Environment Value: !Ref Stage TargetType: ip VpcId: !Ref Vpc ECSPocALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub "${System}-${Stage}-alb" Scheme: internet-facing SecurityGroups: - !Ref ECSPocALBSecurityGroup Subnets: - !Ref ALBSubnet1a - !Ref ALBSubnet1c Tags: - Key: System Value: !Ref System - Key: Environment Value: !Ref Stage Type: application IpAddressType: ipv4 ECSPocALBListenerProdTraffic: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward ForwardConfig: TargetGroups: - TargetGroupArn: !Ref ECSPocALBTargetGroupBlue Weight: 1 LoadBalancerArn: !Ref ECSPocALB Port: 80 Protocol: HTTP ECSPocALBListenerTestTraffic: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward ForwardConfig: TargetGroups: - TargetGroupArn: !Ref ECSPocALBTargetGroupBlue Weight: 1 LoadBalancerArn: !Ref ECSPocALB Port: 8080 Protocol: HTTP ECSPocTaskExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${System}-${Stage}-task-execution-role" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole ECSPocBlueTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: ExecutionRoleArn: !GetAtt ECSPocTaskExecutionRole.Arn ContainerDefinitions: - Name: !Sub "${System}-${Stage}-app" Image: ******.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-poc-ecr:v3 Essential: true PortMappings: - HostPort: 80 Protocol: tcp ContainerPort: 80 RequiresCompatibilities: - FARGATE NetworkMode: awsvpc Cpu: 256 Memory: 512 Family: !Sub "${System}-${Stage}-task" ECSPocCluster: Type: "AWS::ECS::Cluster" Properties: ClusterName: !Sub "${System}-${Stage}-cluster" Tags: - Key: System Value: !Ref System - Key: Environment Value: !Ref Stage ECSPocService: Type: AWS::ECS::Service Properties: Cluster: !Ref ECSPocCluster DesiredCount: 1 DeploymentController: Type: EXTERNAL ECSPocBlueTaskSet: Type: AWS::ECS::TaskSet Properties: Cluster: !Ref ECSPocCluster LaunchType: FARGATE NetworkConfiguration: AwsVpcConfiguration: AssignPublicIp: ENABLED SecurityGroups: - !Ref ECSPocContainerSecurityGroup Subnets: - !Ref ECSSubnet1a - !Ref ECSSubnet1c PlatformVersion: 1.4.0 Scale: Unit: PERCENT Value: 1 Service: !Ref ECSPocService TaskDefinition: !Ref ECSPocBlueTaskDefinition LoadBalancers: - ContainerName: !Sub "${System}-${Stage}-app" ContainerPort: 80 TargetGroupArn: !Ref ECSPocALBTargetGroupBlue PrimaryTaskSet: Type: AWS::ECS::PrimaryTaskSet Properties: Cluster: !Ref ECSPocCluster Service: !Ref ECSPocService TaskSetId: !GetAtt ECSPocBlueTaskSet.Id
TransformとHooks
Transformプロパティはいわゆるマクロで、AWS公式が用意しているAWS::CodeDeployBlueGreenを適用します。 AWS::CodeDeployBlueGreenによって、Hooksプロパティに設定しているCodeDeployBlueGreenHookが呼び出されます。
今回CodeDeployBlueGreenHookで定義するものは、Blue/Greenデプロイで使用する以下のリソースです。
プロパティ | 説明 | |
---|---|---|
TrafficRoutingConfig | 一括デプロイの場合はAllAtOnce、カナリアデプロイはTimeBasedCanary、線形デプロイはTimeBasedLinearを選択します。 | |
Applications | TaskDefinitions | Blue用、Green用タスク定義のリソースの論理ID |
TaskSets | Blue用、Green用タスクセットのリソースの論理ID | |
TrafficRouting | ALBのリスナーポートを選択します。公式ページのサンプルではProdTrafficRouteのみの記載ですが、テスト用ポートが必要な場合はTestTrafficRoute を用意します。 |
公式ページを見ると、ServiceRoleの設定が必須になっていますが、記載しなくてもデフォルトで用意してくれるようです。
他にもAdditionalOptionsプロパティやLifecycleEventHooksプロパティを記載することで、CodeDeployの設定ができます。
Resources
ECS関連のリソースは、Blue用を用意するだけで十分です。
Green用のリソースは?と思うかもしれませんが、Hooksで設定したECSPocGreenTaskDefinition
やECSPocGreenTaskSet
によりGreen用のリソースを用意してくれます。
リスナーやターゲットグループ等のALB関連のリソースは、Blue用とGreen用を両方用意する必要があります。
以下のDeploymentControllerにて、ローリングアップデートかCodeDeployによるBlue/Greenデプロイ、もしくはその他のデプロイタイプとしてEXTERNAL
が選択できます。
Type:code_deployを設定するかと思ったのですが、ここではType: EXTERNALを選択します。
ECSPocService: Type: AWS::ECS::Service Properties: Cluster: !Ref ECSPocCluster DesiredCount: 1 DeploymentController: Type: EXTERNAL
まずはリソースをデプロイしてみる
今回はAWS CLIではなく、CFnのマネージメントコンソールからデプロイします。
問題なくデプロイが完了しました。
ECSサービスのステータス画面を確認してみると、ECSタスクは問題なく実行されています。
しかし画面上ではデプロイタイプはBlue/Greenデプロイメントと認識はされていないようです。
マネージメントコンソールからCodeDeployによるBlue/Greenデプロイを設定した場合、本来ならば以下の画像のように表示されます。
Greenをデプロイする
仕様として、ECSタスク定義もしくはECSタスクセットの変更を実施した場合、Blue/Greenデプロイが実行されるようです。 今回はコンテナイメージをv3からv4に変更します。
ContainerDefinitions: - Name: !Sub "${System}-${Stage}-app" Image: ******.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-poc-ecr:v4
CFnのコンソール画面から、先ほど作成したスタックを更新します。
CodeDeployのコンソール画面を確認すると、デプロイの開始を確認できました。
Greenのデプロイが完了しました。切り替えも完了しております。
最後に
本記事では、CloudFormaitionからAmazon ECS on FargateのBlue/Greenデプロイを行いました。
ただこの方法はいくつか制約があるのと、Hooksプロパティの内容がブラックボックス化されているので、注意が必要です。
気になる制約としては以下が上げられます。
- コンテナイメージのURIはParametersから参照できない
- Parametersから動的に参照させようと試みたのですが、Resoursesに直接URIを記載しないといけないようです
- ECSタスク定義もしくはECSタスクセットと同時に、他のリソースの変更を行い、スタックを更新するとエラーが出る
- NoEcho 属性で定義されたパラメータは参照できない
- Parameter StoreやSecrets Managerなどの外部リソースから取得する値は参照できない
他にも制約がありそうなので、用法・用量を守って正しくお使いください。
次回は2. 100%IaC化は無理で、一部手動によるリソースの用意が必要
について検証してみます。