CodePipelineのS3へのデプロイを使って、CloudFormation一撃で静的Webサイトの構築をやってみる

GithubにあるWebコンテンツをCodePipelineでS3にデプロイし、CloudFrontで配信するAWS環境を一撃で作るCloudFormationを作ったので公開します
2019.01.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

福岡オフィスの梶原です。先日、CodePipelineでS3へのデプロイがサポートされました。

AWS CodePipeline で Amazon S3 へのデプロイのサポートを開始 https://aws.amazon.com/jp/about-aws/whats-new/2019/01/aws-codepipeline-now-supports-deploying-to-amazon-s3/

ということで、そのままお知らせするのもなんなので勢いあまって

GithubリポジトリにあるWebコンテンツをCodePipelineでS3にデプロイ展開し、CloudFrontで配信するAWS環境を一撃で作るCloudFormation Templateを作ったので公開します (なげーよ

※注意(2019/02/25 追記) S3へのデプロイですが、現時点ではgithub側のコンテンツ削除が同期されません。本番使用する場合はデプロイ前に削除 または 別途syncを実施するなどの対応が必要です。

構成図

いるもの

  • AWSアカウント
  • Githubアカウント
  • Webコンテンツ(とりあえず、index.html)

Github側の準備

リポジトリとコンテンツの準備

Githubにprivateのリポジトリを作成して、直下にwebコンテンツを置きます。 ここでは、単純にindex.htmlを置きます。

OAuthトークンの作成と取得

下記URLにアクセスしてGithubのOAuthのトークンを取得します

https://github.com/settings/tokens

  1. [generate new token]を押下します token descriptionはわかりやすい名前をつけてください
  2. □repo, □admin:repo_hookをチェックします。
  3. [generato token]を押下します。

tokenが生成されるのでメモしておきます

CloudFormationで一撃

使用するCloudFormationのテンプレートはこちらになります。

AWSTemplateFormatVersion: '2010-09-09'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Github Repository Settings"
Parameters:
- GitHubOwner
- GitHubRepo
- GitHubBranch
- Label:
default: "Github OAuth"
Parameters:
- GitHubOAuthToken
ParameterLabels:
GitHubOwner:
default: "Owner"
GitHubRepo:
default: "Repository"
GitHubBranch:
default: "Branch"
GitHubOAuthToken:
default: "OAuthToken"
Parameters:
GitHubOwner:
Type: String
GitHubRepo:
Type: String
GitHubBranch:
Type: String
Default: master
GitHubOAuthToken:
Type: String
NoEcho: true

Resources:
# S3 bucket contains static contents
S3Bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain

CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref AWS::StackName

# S3 bucket policy to allow access from CloudFront OAI
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Statement:
- Sid: "1"
Action: s3:GetObject
Effect: Allow
Resource: !Sub arn:aws:s3:::${S3Bucket}/*
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}

# CloudFront Distribution for contents delivery
S3Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id: !Sub S3-${S3Bucket.DomainName}
DomainName: !GetAtt S3Bucket.DomainName
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
DefaultRootObject: index.html
DefaultCacheBehavior:
TargetOriginId: !Sub S3-${S3Bucket.DomainName}
ViewerProtocolPolicy: allow-all
ForwardedValues:
QueryString: false
PriceClass: PriceClass_200

CodePipelineArtifactStoreBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain

CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /service-role/
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:
- Action:
- iam:PassRole
Resource: '*'
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- cloudformation.amazonaws.com
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
Resource: '*'
Effect: Allow
- Action:
- cloudwatch:*
- s3:*
- cloudformation:*
Resource: '*'
Effect: Allow

Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Type: S3
Location: !Ref CodePipelineArtifactStoreBucket
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: 1
Provider: GitHub
Configuration:
Owner: !Ref GitHubOwner
Repo: !Ref GitHubRepo
Branch: !Ref GitHubBranch
OAuthToken: !Ref GitHubOAuthToken
PollForSourceChanges: false
OutputArtifacts:
- Name: SourceArtifact
RunOrder: 1
- Name: Deploy
Actions:
- Name: DeployAction
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: S3
Version: 1
InputArtifacts:
- Name: SourceArtifact
Configuration:
BucketName: !Ref S3Bucket
Extract: true
# OutputArtifacts: []
RunOrder: 1

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: !Join ['', ['{{resolve:secretsmanager:', !Ref GitHubSecret, ':SecretString:SecretToken}}' ]]
Filters:
-
JsonPath: "$.ref"
MatchEquals: refs/heads/{Branch}
TargetPipeline: !Ref Pipeline
TargetAction: SourceAction
TargetPipelineVersion: !GetAtt Pipeline.Version
RegisterWithThirdParty: true

Outputs:
S3OriginBucket:
Value: !Join [ "", [ "https://s3.console.aws.amazon.com/s3/buckets/", !Ref S3Bucket ]]
CloudFrontUrl:
Value: !Join [ "", [ "https://", !GetAtt [ S3Distribution, DomainName ]]]

ファイルはS3に置いたので、AWSアカウントにログイン後下記URLをクリックしてみるとCloudFormationのコンソールが立ち上がりますので

配布元のGithubリポジトリなどを変更して、先ほど取得したOAuthトークンを設定してください。

こちらをクリック

CloudFormationのポイント

CodePipeline のS3展開部分

- Name: Deploy
Actions:
- Name: DeployAction
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: S3
Version: 1
InputArtifacts:
- Name: SourceArtifact
Configuration:
BucketName: !Ref S3Bucket
Extract: true
# OutputArtifacts: []
RunOrder: 1

S3 のデプロイ部分は上記記載の部分になります。 S3のOAIを使用してCloudFrontコンテンツを配信するので、Extract:true を指定してオリジンのS3バケットにファイルを展開しています。

GithubのWebhook

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: !Join ['', ['{{resolve:secretsmanager:', !Ref GitHubSecret, ':SecretString:SecretToken}}' ]]
Filters:
-
JsonPath: "$.ref"
MatchEquals: refs/heads/{Branch}
TargetPipeline: !Ref Pipeline
TargetAction: SourceAction
TargetPipelineVersion: !GetAtt Pipeline.Version
RegisterWithThirdParty: true

GithubのOAuthの権限でWebHookを作成できるようにしてますので(repo_hookのチェックの部分) CodepipeLineのGithub WebHook連携機能を使って、GithubのWebHookを作成しCommitされた場合に自動でCodePipelineのデプロイが動くようにしています。 また、WebHookを作成する際は、CloudFormationでSecretsManagerにランダムなTokenを生成して動的な参照(Dynamic Reference)してWebHookのパラメータに使用しています。

Github のリポジトリページの[Settings][WebHook]にWebHookが作成されていますので確認してみてください。 また、生成したトークンの値はSecretsManagerでも確認できます。

アクセスしてみる

CloudFormationのOutputにCloudFrontのURLが出力されていますアクセスしてみてください。

※注 CloudFormationの作成が終わってもCloudFrontの展開状況によっては以下のアクセス拒否の下記エラーがでる可能性があります。解消まである程度時間がかかるのでお待ちください。

This XML file does not appear to have any style information associated with it. The document tree is shown below.

コンテンツを更新してみる

githubのリポジトリのコンテンツを更新また、作成してみてください。 CodePipelineのデプロイが走り、S3のコンテンツが更新されます。 再度CloudFrontのURLにアクセスしコンテンツが更新されていれば成功です。 (キャッシュが効いていることもあるのでスーパーリロードなど実施してください)

まとめ

S3のデプロイがサポートされる前は、CodeBuild等を使用してS3へsyncで展開などを行っていましたが、CodePipelineでサポートされたので手軽に静的コンテンツのデプロイが実施できるようになりました。 S3でWeb配信以外にも、単純なgithubのリポジトリのバックアップなどにも使えそうな気がしています。 (容量が大きくなるとS3に展開されるファイルも増えますので実際に使用する際は一時バケット(ArtifactStoreBucket)のLifeCycleなどの設定をお願いします)

おそらく、このテンプレートをそのまま使う事はあまりなく、独自ドメインが使いたいとかはきっとあると思うので、いい感じにカスタマイズして使用してください。 誰かの役に立つと嬉しいです。もっともっとカスタマイズしたい、もう無理ぽ。とかなれば

クラスメソッドの「プレミアムオプション」などもご検討ください。

https://classmethod.jp/services/members/

参考

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-s3deploy.html

https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-webhooks-create-cfn.html

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/GitHub-rotate-personal-token-CLI.html