AWS
CloudFormation
ECS
CodeBuild
Fargate

ECS向けのCloudFormationテンプレートを割と簡単にAWS Fargateで動くようにする

はじめに

  • AWS Fargateが東京リージョンに対応しましたね。
  • 別件ですが、本日Amazon EFSが東京リージョンに対応しましたね。
  • もろもろ胸熱です。(因みに、現時点ではFargateからEFSのmountは行なえません。
  • 本記事では、 一生懸命作った ECS向けのCloudFormationテンプレートが割と簡単にFargateに対応できたので手順を書いておきます。

概要

対応手順

テンプレート取得

  1. ベースとなるテンプレートをCloneします。

    $ git clone git@github.com:aws-samples/ecs-refarch-cloudformation.git
    $ cd ecs-refarch-cloudformation
    $ code .
    

テンプレート修正

/service/product-service/service.yamlと/service/website-service/service.yamlの修正

AWS::ECS::Serviceセクション

修正後は以下の内容になります。

service.yaml
Service: 
    Type: AWS::ECS::Service
    # Role: !Ref ServiceRole
    DependsOn: ListenerRule
    Properties: 
        Cluster: !Ref Cluster
        DesiredCount: !Ref DesiredCount
        TaskDefinition: !Ref TaskDefinition
        LoadBalancers: 
            - ContainerName: "product-service"
              ContainerPort: 8001
              TargetGroupArn: !Ref TargetGroup
        LaunchType: FARGATE
        NetworkConfiguration:
          AwsvpcConfiguration:
            AssignPublicIp: DISABLED
            SecurityGroups: !Ref ServiceSecurityGroup
            Subnets: !Ref Subnets
  1. サービスのLaunchTypeにFARGATEの指定を追加します。

    LaunchType: FARGATE
    
  2. Fargateタスクはawsvpcネットワーキングモードを利用することになるのでNetworkConfigurationの指定を追加します。ENIが付くのでサービス毎にセキュリティグループが指定出来ます。ここ重要ですね。

    NetworkConfiguration:
      AwsvpcConfiguration:
        AssignPublicIp: DISABLED
        SecurityGroups: !Ref ServiceSecurityGroup
        Subnets: !Ref Subnets
    
  3. You cannot specify an IAM role for services that require a service linked role. と怒られるので、ServiceRoleの指定をコメントアウトします。

    # Role: !Ref ServiceRole
    

AWS::ECS::TaskDefinitionセクション

修正後は以下の内容になります。

service.yaml
TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
        Family: product-service
        Cpu: 256
        Memory: 512
        ContainerDefinitions:
            - Name: product-service
              Essential: true
              Image: 275396840892.dkr.ecr.us-east-1.amazonaws.com/ecs-refarch-cloudformation/product-service:1.0.0
              Cpu: 256
              Memory: 512
              PortMappings:
                - ContainerPort: 8001
              LogConfiguration:
                LogDriver: awslogs
                Options:
                    awslogs-group: !Ref AWS::StackName
                    awslogs-region: !Ref AWS::Region
                    awslogs-stream-prefix: product-service
        TaskRoleArn:
            Fn::If:
            - 'HasCustomTaskRole'
            - !Ref 'TaskRole'
            - !Ref "AWS::NoValue"
        ExecutionRoleArn: !Ref ExecutionRole
        NetworkMode: awsvpc
        RequiresCompatibilities:
            - FARGATE
  1. 起動タイプにFARGATEの指定を追加します。

    RequiresCompatibilities:
        - FARGATE
    
  2. NetworkModeにawsvpcの指定を追加します。

    NetworkMode: awsvpc
    
  3. 必須ではないですが、TaskRoleとExecutionRoleの指定を追加します。

    TaskRoleArn:
        Fn::If:
        - 'HasCustomTaskRole'
        - !Ref 'TaskRole'
        - !Ref "AWS::NoValue"
    ExecutionRoleArn: !Ref ExecutionRole
    
  4. Fargate requires log configuration options to include awslogs-stream-prefix と怒られるので 適当な値で awslogs-stream-prefix の指定を追加します。

    LogConfiguration:
      LogDriver: awslogs
      Options:
          awslogs-group: !Ref AWS::StackName
          awslogs-region: !Ref AWS::Region
          awslogs-stream-prefix: product-service
    
  5. Fargate requires that 'cpu' be defined at the task level.と怒られるので、以下のURLを参考にCPUとついでにメモリの指定を追加します。

    https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html

    TaskDefinition:
        Type: AWS::ECS::TaskDefinition
        Properties:
            Family: product-service
            Cpu: 256
            Memory: 512
    

AWS::ElasticLoadBalancingV2::TargetGroupセクション

修正後は以下の内容になります。

service.yaml
TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
        VpcId: !Ref VPC
        Port: 80
        Protocol: HTTP
        Matcher: 
            HttpCode: 200-299
        HealthCheckIntervalSeconds: 10
        HealthCheckPath: /products
        HealthCheckProtocol: HTTP
        HealthCheckTimeoutSeconds: 5
        HealthyThresholdCount: 2
        TargetType: ip
  1. awsvpc ネットワークモードを使用する場合、ターゲットグループのターゲットタイプとして ip を指定する必要があります。こちらを参照。そのため、TargetTypeに ip の指定を追加します。

    TargetType: ip
    
  2. 最後に必要なParameters、Conditionsの定義を追加します。

    Parameters:
    ・・・
        ServiceSecurityGroup:
            Description: The ServiceSecurityGroup
            Type: List<AWS::EC2::SecurityGroup::Id>
    
        Subnets:
            Description: Choose which subnets this ECS cluster should be deployed to
            Type: List<AWS::EC2::Subnet::Id>
    
        TaskRole:
            Description: The ECS task role ARN
            Type: String
            Default: ""
    
        ExecutionRole:
            Description: The ECS Fargate Execution role ARN
            Type: String
    
    Conditions:
        HasCustomTaskRole: !Not [ !Equals [!Ref 'TaskRole', ''] ]
    

/infrastructure/ecs-cluster-fargate.yamlの追加

ecs-cluster.yamlから大きく変わるのでecs-cluster-fargate.yamlという新しいファイルを作ります。修正後は、以下リンク先の内容になります。

https://github.com/ukitiyan/ecs-refarch-cloudformation/blob/future/support-fargate/infrastructure/ecs-cluster-fargate.yaml

  1. ECSClusterを指定します。ecs-cluster.yamlと同じ内容です。

    ECSCluster:
        Type: AWS::ECS::Cluster
    
  2. ECSServiceAutoScalingRoleを指定します。ecs-cluster.yamlと同じ内容です。

    ECSServiceAutoScalingRole:
        Type: AWS::IAM::Role
    
  3. タスク定義で設定するためのECSTaskExecutionRole、ECSProductTaskRole、ECSWebsiteTaskRoleを指定します。

    ECSTaskExecutionRole:
        Type: AWS::IAM::Role
    
    ECSProductTaskRole:
        Type: AWS::IAM::Role
    
    ECSWebsiteTaskRole:
        Type: AWS::IAM::Role
    

/master.yamlの修正

修正後は、以下リンク先の内容になります。

https://github.com/ukitiyan/ecs-refarch-cloudformation/blob/future/support-fargate/master.yaml

  1. ECSクラスター用のスタックのパスをecs-cluster-fargate.yamlに変更します。あと不要なParametersを削除します。

    TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/ecs-cluster-fargate.yaml
    
  2. ProductService、WebsiteService用のスタックに引き渡す必要のあるParametersを追加します。

    ServiceSecurityGroup: !GetAtt SecurityGroups.Outputs.ECSHostSecurityGroup
    Subnets: !GetAtt VPC.Outputs.PrivateSubnets
    TaskRole: !GetAtt ECS.Outputs.ECSProductTaskRole
    ExecutionRole: !GetAtt ECS.Outputs.ECSTaskExecutionRole
    
  3. AutoScaleでホストのDrainingを気にする必要が無くなるので、そこら辺を設定しているLifecycleHookのスタックしていをコメントアウトします。

    # LifecycleHook:
    #     Type: AWS::CloudFormation::Stack
    #     Properties:
    #         TemplateURL: https://s3.amazonaws.com/ecs-refarch-cloudformation/infrastructure/lifecyclehook.yaml
    #         Parameters:
    #             Cluster: !GetAtt ECS.Outputs.Cluster
    #             ECSAutoScalingGroupName: !GetAtt ECS.Outputs.ECSAutoScalingGroupName
    

ビルド&デプロイ

  1. CodeBuildで検証とビルドを行うためのbuildspec.ymlが用意されているので、CodeBuildします。
  2. ついでにCodeBuildの環境変数の S3_BUCKET に当該テンプレートを配置するS3バケット名を指定して、ビルド実行すれば当該テンプレートがS3に配置されるよう対応しておきました。こちら
  3. 上記で配置されたテンプレートのmaster.yamlのURLを指定して、CloudFoamationを実行すれば目的の環境が構成されます。
  4. 以下のようなURLがCloudFormationのOutputで確認できるので、アクセスして画面表示されることを確認します。
    http://fargate-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com/
    http://fargate-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com/products

まとめ

  • 割と簡単にFargateに対応できたと思います。
  • このテンプレートを拡張していけば良いわけです。