CFnでGitHub + Fargate + CodePipelineを構築してみる

おはようございます、もきゅりんです。

CFnを使用してGitHub CodePipelineを作成してみたので、まとめておきます。

テンプレートの再利用等で役立てば幸いです。

先日、CodeCommitで構築したFargate+RDS(MySQL5.7)+FlaskをCFnで構築してみるを使った、GitHubでのケースになります。

実際に確認したい場合は、先にこの環境を構築する必要があります。

なお、やってから気付きましたが、こんなのも2年以上前にありました。

そして、上記内容を弊社ブログ記事が丁寧に補足説明しています。

CodePipeline, CodeBuildを使ってAmazon ECSへの継続的デプロイメントを試してみた

前提条件

注意として、今回はCodeCommitではなく、利用するGitHubに今回使うファイルをPushしておくことが変更点となります。

やること

  • GitHubの個人用のアクセストークンを取得する
  • CFnテンプレートの作成とスタック作成

GitHubの個人用のアクセストークンを取得する

個人用のアクセストークンを使用するようにパイプラインを設定する (GitHub と CLI)に記載されているように、以下の手順でGitHub 個人用アクセストークンを作成します。

  1. GitHub で、プロフィール写真のドロップダウンオプションから、[Settings] を選択します。
  2. [Developer settings] を選択してから [Personal access tokens] を選択します。
  3. [Generate new token] を選択します。
  4. [Select scopes] で、[admin:repo_hook] および [repo] を選択します。

こんな感じですね。

generate access token

[Generate token]をクリックすると、トークンが生成されてるので保持しておきます。

ドキュメントにも記載されていますが、下記注意です。

この時点で、生成されたトークンを確実にコピーしてください。このページを閉じた後でトークンを表示することはできません

※ 注 以下ではトークンをそのままテンプレート内でパラメータとして扱っています。後日トークンをSecretsManagerで秘匿するやり方をまとめていますので、必要あればご参照下さい。

[小ネタ] SecretsManagerを使ってCFnのGitHubTokenをシークレットにする

CFnテンプレートの作成とスタック作成

あとは適当なローカルでテンプレートを作成して実行するだけです。

# create-stackコマンドで作成
aws cloudformation create-stack --stack-name github-fargate-pipeline \
--template-body file://`pwd`/sample-pipeline.yml \
--parameters file://`pwd`/paramas.json \
--capabilities CAPABILITY_NAMED_IAM
# deployコマンドで作成
aws cloudformation deploy --template-file sample-pipeline.yml \
--stack-name github-fargate-pipeline \
--parameter-overrides $(jq -r '.[] | [.ParameterKey, .ParameterValue] | join("=")' paramas.json) \
--capabilities CAPABILITY_NAMED_IAM

テンプレートは下記です。

# sample-pipeline.yml

AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate with GitHub

Parameters:
  Cluster:
    Type: String
  Service:
    Type: String
  ContainerName:
    Type: String
  ECRName:
    Type: String
  GitHubRepositoryName:
    Type: String
  GitHubAccountName:
    Type: String
  GitHubSecret:
    Type: String
  GitHubOAuthToken:
    Type: String
  Branch:
    Type: String
  PipelineName:
    Type: String

Resources:
  # CodeBuildに適用するIAMRole
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SampleCodeBuildAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
              - Resource: "*"
                Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:GetRepositoryPolicy
                  - ecr:DescribeRepositories
                  - ecr:ListImages
                  - ecr:DescribeImages
                  - ecr:BatchGetImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                  - ecr:PutImage

  # 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: SamplePipeline
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Resource: "*"
                Effect: Allow
                Action:
                  - codecommit:GetRepository
                  - codecommit:ListBranches
                  - codecommit:GetUploadArchiveStatus
                  - codecommit:UploadArchive
                  - codecommit:CancelUploadArchive
                  - codedeploy:CreateDeployment
                  - codedeploy:GetApplication
                  - codedeploy:GetApplicationRevision
                  - codedeploy:GetDeployment
                  - codedeploy:GetDeploymentConfig
                  - codedeploy:RegisterApplicationRevision
                  - codebuild:StartBuild
                  - codebuild:StopBuild
                  - codebuild:BatchGet*
                  - codebuild:Get*
                  - codebuild:List*
                  - codecommit:GetBranch
                  - codecommit:GetCommit
                  - s3:*
                  - ecs:*
                  - elasticloadbalancing:*
                  - autoscaling:*
                  - iam:PassRole

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

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

  # CodeBuild
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - $(aws ecr get-login --no-include-email)
                - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
                - IMAGE_TAG=${COMMIT_HASH:=latest}
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...
                - docker build -t $REPOSITORY_URI:latest .
                - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker images...
                - docker push $REPOSITORY_URI:latest
                - docker push $REPOSITORY_URI:$IMAGE_TAG
                - echo Writing image definitions file...
                - echo "[{\"name\":\"${ContainerName}\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]" > imagedefinitions.json
          artifacts:
            files: imagedefinitions.json
      Environment:
        PrivilegedMode: true
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0-1.7.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
          - Name: ContainerName
            Value: !Ref ContainerName
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildServiceRole

  # CodePipeLine
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      Name: !Ref PipelineName
      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: !Ref Branch
                OAuthToken: !Ref GitHubOAuthToken
              RunOrder: 1
              OutputArtifacts:
                - Name: App
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              RunOrder: 1
              InputArtifacts:
                - Name: App
              OutputArtifacts:
                - Name: BuildOutput
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: ECS
              Configuration:
                ClusterName: !Ref Cluster
                ServiceName: !Ref Service
                FileName: imagedefinitions.json
              RunOrder: 1
              InputArtifacts:
                - Name: BuildOutput
# paramas.json
[
  {
    "ParameterKey": "Cluster",
    "ParameterValue": "demo-flask-cluster"
  },
  {
    "ParameterKey": "Service",
    "ParameterValue": "demo-flask-service"
  },
  {
    "ParameterKey": "ContainerName",
    "ParameterValue": "demo-flask-container"
  },
  {
    "ParameterKey": "ECRName",
    "ParameterValue": "ECRName"
  },
  {
    "ParameterKey": "GitHubRepositoryName",
    "ParameterValue": "RepositoryName"
  },
  {
    "ParameterKey": "GitHubAccountName",
    "ParameterValue": "GitHubAccountName"
  },
  {
    "ParameterKey": "GitHubSecret",
    "ParameterValue": "secret"
  },
  {
    "ParameterKey": "GitHubOAuthToken",
    "ParameterValue": "xxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  {
    "ParameterKey": "Branch",
    "ParameterValue": "master"
  },
  {
    "ParameterKey": "PipelineName",
    "ParameterValue": "cfn-github-pipeline"
  }
]

ファイルを適当に変更してGitHubリポジトリにプッシュしてみます。

$ git add .
$ git commit -m "index changed"
$ git push

pipeline1

pipeline2

しばらくすると、変更が反映されました。

index changed

以上です、どなたかのお役に立てば幸いです。

参考

個人用のアクセストークンを使用するようにパイプラインを設定する (GitHub と CLI)

CodePipeline パイプライン構造のリファレンス

AWS::CodePipeline::Webhook

例 3: AWS CloudFormation を使用して GitHub パイプラインを作成する