Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

CodePipelineでCloudFormationを利用してECSをデプロイ

自分のメモとして残します。

手動でECSを立ち上げる

サンプルアプリ

今回利用するアプリをローカルで実行してみます。

app.js
const http = require('http')

var app = function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello\n');
};

http.createServer(app).listen(3000);
Dockerfile
FROM node:12

WORKDIR /usr/src/app

COPY app.js .

EXPOSE 3000
CMD [ "node", "app.js" ]

試しに実行してみます。

実行
$ docker build -t ecs_node:latest .
$ docker run -p 3000:3000 -d ecs_node:latest

$ curl localhost:3000
Hello

CloudFormationテンプレート

各種テンプレートです。

ecr.yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Repository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: deploy-test-repository

Outputs:
  Repository:
    Description: Test Repository
    Value: !Ref Repository
    Export: 
      Name: DeployTestRepository
ecs-cluster.yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: deploy-test-cluster

Outputs:
  Cluster:
    Description: ECS Cluster
    Value: !Ref Cluster
    Export: 
      Name: DeployTestCluster

ECSサービスのテンプレートファイルです。
DockerImageは更新されることが想定されるため、パラメータとして渡せるようにしています。

ecs-service.yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  SecurityGroup:
    Type: String
  Subnet:
    Type: String
  DockerImage:
    Type: String

Resources:

  # Fargate の起動に必要な権限
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: ecs-tasks.amazonaws.com
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      Path: /

  # Fargate(アプリケーション)に付与する権限
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: ecs-tasks.amazonaws.com
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      Path: /

  # Task定義
  ApTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      # Family: !Ref AWS::StackName
      ExecutionRoleArn: !GetAtt ExecutionRole.Arn
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: app
          Image: !Ref DockerImage
          Essential: true
          PortMappings:
            - ContainerPort: 3000
      Cpu: 256
      Memory: 512
      NetworkMode: awsvpc
      TaskRoleArn: !GetAtt TaskRole.Arn

  # Service定義
  FargateService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !ImportValue DeployTestCluster
      LaunchType: FARGATE
      DesiredCount: 1
      TaskDefinition: !Ref ApTaskDefinition
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref SecurityGroup
          Subnets:
            - !Ref Subnet

スタック作成

まずリポジトリとECSクラスターを作成します。

$ aws cloudformation deploy --template ecr.yaml --stack-name create-ecr

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - create-ecr

$ aws cloudformation deploy --template ecs-cluster.yaml --stack-name create-ecs-cluster

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - create-ecs-cluster

続いて作成したECRにサンプルアプリのDockerイメージをpushします。
詳細はこちらをどうぞ Dockerイメージをレジストリに登録する(ECR含む)

$ $(aws ecr get-login --no-include-email)
$ aws ecr describe-repositories
{
    "repositories": [
        {
            "repositoryArn": "arn:aws:ecr:ap-northeast-1:XXXXXXXX:repository/deploy-test-repository",
            "registryId": "XXXXXXXX",
            "repositoryName": "deploy-test-repository",
            "repositoryUri": "XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository",
            "createdAt": 1592618305.0
        }
    ]
}

$ docker image tag ecs_node:latest XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init
$ docker push XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init

デプロイ

$ aws cloudformation deploy --template ecs-service.yaml --stack-name create-ecs-service --capabilities CAPABILITY_IAM --parameter-overrides DockerImage=XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init

デプロイ結果の確認
IPアドレスはECSをコンソールで開いて確認しました。
なぜかaws ecs describe-tasksだと表示されませんでした・・・

$ curl http://XXX.XXX.XXX.XXX:3000/
Hello

CodePiepelineの作成

作業の前にGithubでアクセストークンを設定してください。

私は以下参考にして設定しました。
CFnでGitHub + Fargate + CodePipelineを構築してみる

テンプレートファイルの作成

code-pipeline-yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  SecurityGroup:
    Type: String
  Subnet:
    Type: String
  GitHubRepositoryName:
    Type: String
  GitHubAccountName:
    Type: String
  GitHubOAuthToken:
    Type: String

Resources:

  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: codebuild.amazonaws.com
      Policies:
        - PolicyName: code-build-service
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:ValidateTemplate
                Resource: "*"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - ecr:GetAuthorizationToken
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetObjectVersion
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - ecr:BatchCheckLayerAvailability
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                Resource: "*"
      Path: /

  # アプリケーションのテストやビルドの定義(開発環境)
  CodeBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: deploy-test-build
      ServiceRole: !Ref CodeBuildServiceRole
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Type: LINUX_CONTAINER
        Image: aws/codebuild/docker:18.09.0-1.7.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: REPOSITORY_URI
            Value: !Sub 
              - ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}
              - Repository: !ImportValue DeployTestRepository
          - Name: Subnet
            Value: !Ref Subnet
          - Name: SecurityGroup
            Value: !Ref SecurityGroup
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: ecs_deploy/application/deploy/buildspec.yaml

  # CodePipelineに適用するIAMRole
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: code-pipeline-service
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - codecommit:GetRepository
                  - codecommit:ListBranches
                  - codecommit:GetUploadArchiveStatus
                  - codecommit:UploadArchive
                  - codecommit:CancelUploadArchive
                  - cloudformation:CreateChangeSet
                  - cloudformation:CreateStack
                  - cloudformation:CreateUploadBucket
                  - cloudformation:DeleteStack
                  - cloudformation:Describe*
                  - cloudformation:List*
                  - cloudformation:UpdateStack
                  - cloudformation:ValidateTemplate
                  - cloudformation:ExecuteChangeSet
                  - codebuild:StartBuild
                  - codebuild:StopBuild
                  - codebuild:BatchGet*
                  - codebuild:Get*
                  - codebuild:List*
                  - codecommit:GetBranch
                  - codecommit:GetCommit
                  - s3:*
                  - iam:PassRole

  # S3Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket

  # 外部イベント発生のwebhook
  PipelineWebhook:
    Type: "AWS::CodePipeline::Webhook"
    Properties:
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: "secret"
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: "refs/heads/master"
      TargetPipeline: !Ref Pipeline
      TargetAction: SourceAction
      Name: GitHubPipelineWebhook
      TargetPipelineVersion: !GetAtt Pipeline.Version
      RegisterWithThirdParty: "true"

  # 継続的デプロイに必要な権限
  CloudFormationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: cloudformation.amazonaws.com
      Policies:
        - PolicyName: code-pipeline-service
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - iam:*
                  - ecs:RegisterTaskDefinition
                  - ecs:DeregisterTaskDefinition
                  - ecs:CreateService
                  - ecs:Delete*
                  - logs:Delete*
                  - cloudwatch:Delete*
                Resource: "*"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole
      Path: /

  # CodePipeLine
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      Name: pipeline-test-build
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Version: 1
                Provider: GitHub
              Configuration:
                Owner: !Ref GitHubAccountName
                Repo: !Ref GitHubRepositoryName
                PollForSourceChanges: false
                Branch: master
                OAuthToken: !Ref GitHubOAuthToken
              RunOrder: 1
              OutputArtifacts:
                - Name: SourceCode
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuild
              RunOrder: 1
              InputArtifacts:
                - Name: SourceCode
              OutputArtifacts:
                - Name: BuildOutput
        - Name: Deploy
          Actions:
          Actions:
            - Name: CreateEdgeChangeSet
              ActionTypeId:
                Category: Deploy
                Provider: CloudFormation
                Owner: AWS
                Version: 1
              Configuration:
                StackName: create-ecs-service
                ActionMode: CHANGE_SET_REPLACE
                ChangeSetName: ChangeSet
                RoleArn: !GetAtt CloudFormationRole.Arn
                Capabilities: CAPABILITY_NAMED_IAM
                TemplatePath: SourceCode::ecs_deploy/application/deploy/ecs-service.yaml
                TemplateConfiguration: BuildOutput::config.json
              InputArtifacts:
                - Name: SourceCode
                - Name: BuildOutput
              OutputArtifacts:
                - Name: CreatedEdgeChangeSet
              RunOrder: 1
            - Name: ExecuteEdgeChangeSet
              ActionTypeId:
                Category: Deploy
                Provider: CloudFormation
                Owner: AWS
                Version: 1
              Configuration:
                StackName: create-ecs-service
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: ChangeSet
              InputArtifacts:
                - Name: CreatedEdgeChangeSet
              OutputArtifacts:
                - Name: EdgeDeployed
              RunOrder: 2

スタック作成します。
各種パラメータもここで設定して実行します。

$ aws cloudformation deploy --template code-pipeline.yaml --stack-name create-code-pipeline --parameter-overrides SecurityGroup=sg-XXXXXX Subnet=subnet-XXXXXXXX GitHubRepositoryName=XXXX GitHubAccountName=XXXXX GitHubOAuthToken=XXXXXXXXXXXXXXXXXXXXXXX --capabilities CAPABILITY_IAM

buildSpecの作成

buildSpecを作成します。

途中でDEFAULT変数にパスを格納して戻っていますが、最後にconfig.jsonを出力する際に、初期の場所で吐き出さないとエラーになってしまうからです。
ここで出力したファイルは、CloudFormationを更新するときのパラメータとして利用されます。

buildspec.yaml
version: 0.2

phases:
  pre_build:
    commands:
      - IMAGE_NAME="${REPOSITORY_URI}:$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)"
      - $(aws ecr get-login --no-include-email)
  build:
    commands:
      - echo Build started on $(date)
      - DEFAULT=`pwd`
      - cd ./ecs_deploy/application
      - docker build --tag ${IMAGE_NAME} .
      - docker push ${IMAGE_NAME}
      - cd ${DEFAULT}
  post_build:
    commands:
      - echo Build completed on $(date)
      - printf '{"Parameters":{"Subnet":"%s","SecurityGroup":"%s","DockerImage":"%s"}}' ${Subnet} ${SecurityGroup} ${IMAGE_NAME} > config.json

artifacts:
  files: config.json

結果確認

対象リポジトリを修正してpushすれば修正した内容がデプロイされるようになります!!

app.js
const http = require('http')

var app = function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Goodmorning\n');
};

http.createServer(app).listen(3000);
$ curl http://YYY.YYY.YYY.YYY:3000/
Goodmorning

今回は省きましたが、ECS Service Discoveryを利用することで新しく作成されたIPをRoute53に登録可能です。

参考情報

Jawsug Fargate アプリケーションの継続的デリバリー
Classmethod CloudFormationのスタック間でリソースを参照する

AWSユーザーガイド ECS リソースタイプのリファレンス

今回のコードは以下に配置しております。
https://github.com/uc4w6c/aws-test/tree/master/ecs_deploy

YutaSaito1991
事業会社でWebアプリケーションエンジニアをしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away