アットランタイム

dev ドメインで作成したブログのパイプラインを作成する

概要

前の記事でブログをホスティングするための環境を作成しました。 この記事ではブログを継続的にデプロイするためのパイプライラインを作成します。

前提

  • ブログ記事の管理は Github

テンプレート

作成するリソースは大体次です。

  • ブログをビルドするための CodeBuild
  • ソースが Github、ビルドが前述の CodeBuild、デプロイが S3 の CodePipeline
  • Github からの変更を通知する WebHook
  • WebHook のシークレット

入力パラメーター

この内、DistributionID ですが、これは、CodeBuild 内で、CloudFront のキャッシュを Invalidation するために使用します。 buildspec.yml ファイルも添付します。hugo でビルドしています。

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  GitHubRepo:
    Type: String
  GitHubBranch:
    Type: String
  GitHubToken:
    Type: String
    NoEcho: true
  GitHubUser:
    Type: String

  SourceArtifact:
    Type: String
    Default: SourceArtifact
  BuildArtifact:
    Type: String
    Default: BuildArtifact

  DeployBucket:
    Type: String

  DistributionID:
    Type: String

Resources:
  ArtifactBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      LifecycleConfiguration:
        Rules:
          - Id: delete all objects per a day
            Status: Enabled
            ExpirationInDays: 1

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:1.0
        Type: LINUX_CONTAINER
        PrivilegedMode: True
        EnvironmentVariables:
          - Name: AWS_ACCOUNT_ID
            Value: !Ref AWS::AccountId
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: DISTRIBUTION_ID
            Value: !Ref DistributionID
          - Name: DEPLOY_BUCKET
            Value: !Ref DeployBucket
      Name: !Ref AWS::StackName
      # does not work on CodePipeline
      # BadgeEnabled: True
      ServiceRole: !Ref CodeBuildServiceRole

  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: root
          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
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/PowerUserAccess
        - arn:aws:iam::aws:policy/IAMReadOnlyAccess

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: Github
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Version: 1
                Provider: GitHub
              Configuration:
                Owner: !Ref GitHubUser
                Repo: !Ref GitHubRepo
                Branch: !Ref GitHubBranch
                OAuthToken: !Ref GitHubToken
                PollForSourceChanges: false
              OutputArtifacts:
                - Name: !Ref SourceArtifact
              RunOrder: 1
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: !Ref SourceArtifact
              OutputArtifacts:
                - Name: !Ref BuildArtifact
              RunOrder: 1
        - Name: Deploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: S3
                Version: 1
              InputArtifacts:
                - Name: !Ref BuildArtifact
              Configuration:
                BucketName: !Ref DeployBucket
                Extract: true
              RunOrder: 1

  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: root
          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:
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
              - Resource:
                  - !Sub arn:aws:s3:::${DeployBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl

  GitHubSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        SecretStringTemplate: "{}"
        GenerateStringKey: "SecretToken"
        ExcludePunctuation: true
        PasswordLength: 40

  PipelineWebhook:
    Type: AWS::CodePipeline::Webhook
    Properties:
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: !Sub "{{resolve:secretsmanager:${GitHubSecret}:SecretString:SecretToken}}"
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: refs/heads/{Branch}
      TargetPipeline: !Ref Pipeline
      TargetAction: Github
      TargetPipelineVersion: !GetAtt Pipeline.Version
      RegisterWithThirdParty: true

Outputs:
  PipelineUrl:
    Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}
version: 0.2

env:
  variables:
    HUGO_VERSION: "0.59.1"

phases:
  install:
    runtime-versions:
      golang: 1.13
    commands:
      - curl -Ls https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz -o /tmp/hugo.tar.gz
      - tar xf /tmp/hugo.tar.gz -C /tmp
      - mv /tmp/hugo /usr/bin/hugo
  build:
    commands:
      - hugo
  post_build:
    commands:
      #      - aws s3 sync --delete public/ s3://${DEPLOY_BUCKET}
      - aws cloudfront create-invalidation --distribution-id ${DISTRIBUTION_ID} --paths "/*" --region ${AWS_DEFAULT_REGION}

artifacts:
  files:
    - "**/*"
  base-directory: public
  name: page-$(date +%Y-%m-%d)

備考

S3 への Deploy ステージ使わずに、CodeBuild 内で aws s3 sync すれば良いのではないかと思われるかもしれませんが、個人的にはその通りかなと思います。 CodeBuild で S3 にデプロイする方法のメリットは、1. aws s3 sync –delete オプションで不要なファイルを削除できる。2. CloudFront のキャッシュを Invalidation するタイミングがベストである点が挙げられます。 特に、Invalidation はデプロイステージ後に行うことで、確実となりますが、この例では、デプロイ前に行うことになるため、タイミング的に最新のデプロイが反映されない可能性が挙げられます。 ではなぜデプロイステージで S3 をするかですが、それはやってみたかったからとなります。その他、メリットがあればご指摘ください。