AWS
ECS
Fargate

ECSで運用していた社内ツールをFargate化したときに、ハマりやすかった3つのポイント

AWS Fargate Advent Calendar 2017の25日目の記事になります。

他の方が詳細な見解や調査など行われているので、大トリの記事がこれぐらいの内容で大丈夫なのかビビりながら書いています。
ひとまず、年末年始でFargateを試してみようかなあという方の参考になれば。

1.どんな環境をFargate化したか

fargate.png

上記のような構成でECSで運用していた社内ツールのprpr(※)をFargate化しました。
現在のFargateの制限としては、

  1. 東京リージョンがない
  2. SLAがない
  3. 知見が少ない

ということで、production環境にいきなり入れるというよりは、こうしたサービスレベルの低い社内ツールから移行するのがよいかと思います。

※1 SLA設定されてました Amazon Compute サービスレベルアグリーメントを Amazon ECS および AWS Fargate に拡張

※2 prprについては、下記のブログを参照
prprでGithubのPullRequestレビュー依頼をSlack通知する

2.ハマったところ

2-1.FargateがECRのコンテナイメージをpullできない

デプロイしたECSのステータスが、延々とSTOPPEDを繰り返して、ECSのログを見ると、下記のようなエラーが出力され続けているときがありました。残念ながら、Fargate化されても、デプロイ失敗したときなど、ECSが再起動しまくるのは、自前で何とか検知する仕組みを作らないといけなさそう...
ss 2017-12-24 11.51.14.png

解決例:Fargate側にPublicIPを付与する

Fargate: CannotPullContainer located on ECS registry
にもあるように、FargateはVPC内部で起動してくるため、VPC外部への通信経路を確保しておかないと、FargateがECRからコンテナイメージをおとしてくることができません。特にセキュリティ上などで問題なければ、AssignPublicIpを有効化しておきましょう。

CFn例
Service:
    Type: AWS::ECS::Service
    Properties:
        ServiceName: !Ref RoleName
        Cluster: !Ref ECSCluster
        DesiredCount: 1
        LaunchType: FARGATE
        TaskDefinition: !Ref ECSTask
        LoadBalancers:
            -
                ContainerName: !Sub ContainerName
                ContainerPort: 3000
                TargetGroupArn: !Ref ALBTargetGroup
        NetworkConfiguration:
            AwsvpcConfiguration:
                AssignPublicIp: ENABLED
                SecurityGroups:
                    - !Ref ECSSecurityGroup
                Subnets: !Ref SubnetIds

2-2.ECSの動的ポートマッピングは使えない

Fargateのデプロイ中は、下記の画像のような感じで、同一ポートでプライベートIPが異なるという状況になる。Fargateが使用するサブネットでプライベートIPが枯渇したときにどうなるかは未検証。Fargateの起動コンテナ数の制限とかないのであれば、サブネットマスクの設計とかはちょっと注意しておいた方がよさそう。
ss 2017-12-25 7.30.10.png

解決例:ホスト側のポートを固定する

CFn例
ECSTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
        Family: !Ref FamilyName
        NetworkMode: awsvpc
        RequiresCompatibilities:
            - FARGATE
        Cpu: 256
        Memory: 512
        ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
        ContainerDefinitions:
            -
                Name: !Ref TaskName
                Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RegistoryName}:${ImageTag}"
                PortMappings:
                    -
                        ContainerPort: 3000
                        HostPort: 3000
                Essential: "true"
                Ulimits:
                    -
                        Name: nofile
                        SoftLimit: 65535
                        HardLimit: 65535
                Environment:
                    -
                        Name: PORT
                        Value: 3000
                    -
                        Name: RACK_ENV
                        Value: production
                LogConfiguration:
                    LogDriver: awslogs
                    Options:
                        awslogs-group: !Ref CloudWatchLogGroup
                        awslogs-region: !Sub ${AWS::Region}
                        awslogs-stream-prefix: !Ref ImageTag

2-3.CodePipelineでFargateのデプロイを行う、CFnの記述方法がわからない

AWS CodePipeline に Amazon ECS および AWS Fargate のサポートを追加
CodePipeline で ECS にデプロイできるようになり、Docker 環境の継続的デリバリも簡単になりました
にもあるのですが、12/12に、CodePipeline上でFargateのデプロイがサポートされています。
ss 2017-12-25 16.47.19.png
ただ、上記のようなイメージで、CodePipelineとFargateを連携させようとしたときに、CFnのドキュメントからだとCFnでのサンプルが見つけられませんでした。
ひとまず、下記のように書いたら、CFnでも何とか通ったけど、合ってるのかしら(どこかに公式チュートリアルとか準備されてるかな)

解決例:CodeBuildでimagedefinitions.jsonを出力して、CodePipelineのDeployフェーズと連携させる

buildspec.yml例
version: 0.2

phases:
    pre_build:
        commands:
            - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
            - REPOSITORY_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
            - IMAGE_TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}
    build:
        commands:
            - echo Build started on `date`
            - docker build -t "${IMAGE_REPO_NAME}:${IMAGE_TAG}" .
            - docker tag "${IMAGE_REPO_NAME}:${IMAGE_TAG}" "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
    post_build:
        commands:
            - echo Build completed on `date`
            - docker push "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
            - printf '[{"name":"container-name-sample","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files:
        - imagedefinitions.json
    discard-paths: yes

imagedefinitions.jsonをCodeBuildで生成したフォルダ直下においておけば、
あとは下記のようなCodePipelineの書き方で、Fargateでもデプロイが可能になる。

CFn例
CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CodePipelineS3
    Properties:
        Name: codepipeline-sample
        ArtifactStore:
            Type: S3
            Location: !Ref S3BucketName
        RoleArn: !Ref RoleArn 
        Stages:
            -
                Name: Source
                Actions:
                    -
                        Name: Source
                        RunOrder: 1
                        ActionTypeId:
                            Category: Source
                            Owner: ThirdParty
                            Version: 1
                            Provider: GitHub
                        Configuration:
                            Owner: hoge
                            Repo: fuga
                            Branch: master
                            OAuthToken: xxxxxxxxxxxx
                        OutputArtifacts:
                            - Name: Source
            -
                Name: Build
                Actions:
                    -
                        Name: CodeBuild
                        RunOrder: 1
                        InputArtifacts:
                            - Name: Source
                        ActionTypeId:
                            Category: Build
                            Owner: AWS
                            Version: 1
                            Provider: CodeBuild
                        Configuration:
                            ProjectName: !Ref CodeBuild
                        OutputArtifacts:
                            - Name: Build
            -
                Name: Deploy
                Actions:
                    -
                        Name: Deploy
                        ActionTypeId:
                            Category: Deploy
                            Owner: AWS
                            Version: 1
                            Provider: ECS
                        InputArtifacts:
                            - Name: Build
                        Configuration:
                            ClusterName: !Ref ClusterName
                            ServiceName: !Ref ServiceName

3.参考記事

他の方のアドベントカレンダーがすごく参考になったので、CFnまわりの実装で参考にさせていただいた記事をいくつか紹介させていただこうと思います。

Fargate を試した感想と ecs-deploy で Fargate にデプロイできるようにする話
AWS CloudFormationを使ってAWS Fargateの環境を作成してみる
ECS+EC2で動いているサービスをFargateにのせ替える

4.まとめ

社内で使っていたECSをFargate化したことで、EC2の管理(障害対応、およびセキュリティアップデート対応)をなくすことができました。VPCのサブネット設計など、Fargateにしてもインフラ面を意識しないといけないところはありそうなので、また知見がたまれば共有させていただこうと思います。

それでは、皆様メリークリスマス!