Tomorrow Will Be A Better Day

Published

- 7 min read

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

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

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

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

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

fargate.png

上記のような構成でECSで運用していた社内ツールのprprをFargate化しました。
prprについては、下記のブログを参照
prprでGithubのPullRequestレビュー依頼をSlack通知する

2017/12時点のFargateの制限としては、

  • 東京リージョンがない
  • SLAがない1
  • 知見が少ない

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

2.ハマったところ

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

fargate_error.png

デプロイしたECSのステータスが、延々とSTOPPEDを繰り返して、ECSのログを見ると、下記のようなエラーが出力され続けているときがありました。
残念ながら、Fargate化されても、デプロイ失敗したときなど、ECSが再起動しまくるのは、自前で何とか検知する仕組みを作らないといけなさそう…
解決例:Fargate側にPublicIPを付与する
Fargate: CannotPullContainer located on ECS registryにもあるように、FargateはVPC内部で起動してくるため、VPC外部への通信経路を確保しておかないと、FargateがECRからコンテナイメージをおとしてくることができません。特にセキュリティ上などで問題なければ、AssignPublicIpを有効化しておきましょう。

   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.png

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

   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の記述方法がわからない

codepipeline.png

AWS CodePipeline に Amazon ECS および AWS Fargate のサポートを追加 CodePipeline で ECS にデプロイできるようになり、Docker 環境の継続的デリバリも簡単になりました にもあるのですが、12/12に、CodePipeline上でFargateのデプロイがサポートされています。

ただ、上記のようなイメージで、CodePipelineとFargateを連携させようとしたときに、CFnのドキュメントからだとCFnでのサンプルが見つけられませんでした。 ひとまず、下記のように書いたら、CFnでも何とか通ったけど、合ってるのかしら(どこかに公式チュートリアルとか準備されてるかな)

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

   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でもデプロイが可能になる。

   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まわりの実装で参考にさせていただいた記事をいくつか紹介させていただこうと思います。

4.まとめ

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

Footnotes

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