HugoとAWSを使ってブログを構築したので、その方法を書いていきます。
目次
- Hugo
- Route53でドメインを取得
- AWS Certificate ManagerでSSL/TLS証明書を発行
- S3とCloudFrontを作成
- CodeCommit + CodeBuild + CodePipelineで自動化
- Lambda@Edgeを設定
Hugo
Quick Start を参考にしながら進めていきます。
インストール
バイナリを使用してHogoをインストールします。Hugo Releases から自分の環境に合ったものをダウンロードします。
私の場合は hugo_extended_0.65.3_Windows-64bit.zip をダウンロードしました。ダウンロード後、フォルダを解凍し、解凍したフォルダの名前をbinに変更します。新しいフォルダ(Hugo)をローカルディスク(C:)に作成し、先ほど解凍したフォルダ(bin)をHugoフォルダに入れます。後は、C:¥Hugo¥binを環境変数のPathに追加すれば、インストールは完了です。
サイトを作成
$ cd C:¥Hugo
$ hugo new site quickstart
コマンドプロンプトで上記のコマンドを実行します。実行するとconfigファイル等が入ったquickstartフォルダが作成されます。
コマンドの quickstart の部分は、フォルダ名になるので、任意の名前を入力します。
テーマを追加
$ cd quickstart
$ git clone https://github.com/dim0627/hugo_theme_robust.git themes/hugo_theme_robust
$ git init
$ echo theme = "hugo_theme_robust" >> config.toml
テーマを追加することにより、Hugo Themes のような、様々なテンプレートを使用することができます。 今回は、私のブログでも使用している hugo_theme_robust を追加します。
記事を追加
$ hugo new post/my-first-post.md
Markdownファイルが、quickstartフォルダ内のpostフォルダに作成されます。Markdownファイルを編集することにより、記事の内容を変更できます。
draft: true
↓
draft: false
記事をウェブサイトで公開するために、Markdownファイル先頭部分を上記に変更します。
ローカルで確認
$ hugo server -D
Hugoサーバーを起動し、ブラウザから http://localhost:1313/ にアクセスして動作を確認します。特に問題がなければ、Hugoの準備は一旦完了です。
Route53でドメインを取得
下記を参考にして、ウェブサイトを公開するためのドメインを取得します。
Route 53 でドメインを取得・購入する(2019版) | Developers.IO
ドメインを取得したら、Hugoに戻ってquickstartフォルダ内のconfig.tomlファイルを編集します。
baseURL = "http://example.org/"
↓
baseURL = "https://取得したドメイン名/"
AWS Certificate ManagerでSSL/TLS証明書を発行
CloudFrontに設定するSSL/TLS証明書を発行します。
マネージメントコンソールから Certificate Manager を選択 → バージニア北部リージョンであることを確認 → 証明書のプロビジョニング「今すぐ始める」をクリック → 「パブリック証明書のリクエスト」を選択 → 「証明書のリクエスト」をクリック → ドメイン名を入力 → 「次へ」をクリック → 「DNSの検証」を選択 → 「次へ」をクリック → 「確認」をクリック → 「確認とリクエスト」をクリック → 「Route53でのレコードの作成」をクリック → 「作成」をクリック
S3とCloudFrontを作成
S3とCloudFrontは、CloudFormationで一気に作ってしまいます。
AWSTemplateFormatVersion: 2010-09-09
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: S3 and CloudFront Configuration
Parameters:
- S3BucketName
- WebsiteDomainName
- AcmCertificateArn
Parameters:
# バケット名を入力
S3BucketName:
Type: String
# ドメイン名を入力
WebsiteDomainName:
Type: String
# AcmCertificateArnを入力
AcmCertificateArn:
Type: String
Resources:
# ログ用のバケットを作成
AccessLogS3Bucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub ${S3BucketName}-log
AccessControl: LogDeliveryWrite
LifecycleConfiguration:
Rules:
- Id: !Sub ${S3BucketName}-log-lifecycle-rule
Status: Enabled
ExpirationInDays: 30
# Hugo用のバケットを作成
HugoS3Bucket:
Type: "AWS::S3::Bucket"
Properties:
AccessControl: PublicRead
BucketName: !Ref S3BucketName
LoggingConfiguration:
DestinationBucketName: !Ref AccessLogS3Bucket
# バケットポリシーを作成
S3BucketPolicy:
Type: "AWS::S3::BucketPolicy"
Properties:
Bucket: !Ref HugoS3Bucket
PolicyDocument:
Statement:
-
Action:
- s3:GetObject
Effect: Allow
Resource: !Sub arn:aws:s3:::${HugoS3Bucket}/*
Principal: "*"
# CloudFrontを設定
CloudFront:
Type: "AWS::CloudFront::Distribution"
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt HugoS3Bucket.DomainName
Id: !Sub S3-Website-${HugoS3Bucket.DomainName}
CustomOriginConfig:
OriginProtocolPolicy: http-only
Enabled: true
DefaultRootObject: index.html
Logging:
IncludeCookies: false
Bucket: !GetAtt AccessLogS3Bucket.DomainName
Prefix: cloudfront/
Aliases:
- !Ref WebsiteDomainName
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
DefaultTTL: 604800
MaxTTL: 1209600
MinTTL: 0
Compress: true
TargetOriginId: !Sub S3-Website-${HugoS3Bucket.DomainName}
ForwardedValues:
QueryString: false
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_200
ViewerCertificate:
AcmCertificateArn: !Ref AcmCertificateArn
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.1_2016
HttpVersion: http2
Outputs:
# バケット名を表示
BucketName:
Value: !Ref HugoS3Bucket
# CloudFrontのドメイン名を表示
DomainName:
Value: !GetAtt CloudFront.DomainName
スタック作成時、パラメータでは3つの値を入力します。
- S3BucketName = S3のバケット名
- WebsiteDomainName = Route53で取得したドメイン名
- AcmCertificateArn = AWS Certificate Managerで発行したSSL/TLS証明書のARN
出力では2つの値を表示します。
- BucketName = Hugo用S3のバケット名
- DomainName = CloudFrontのドメイン名
出力で表示されたDomainNameは、Route53でエイリアスレコードとして登録する必要があります。
マネージメントコンソールからRoute 53を選択 → ホストゾーンを選択 → ドメイン名を選択 → 「レコードセットに移動」をクリック → 「レコードセットの作成」をクリック → タイプは「A - IPv4アドレス」を選択、エイリアスは「はい」を選択、エイリアス先は「CloudFrontディストリビューション」を選択 → 「作成」をクリック
CodeCommit + CodeBuild + CodePipelineで自動化
- AWS CodeCommit(AWSが提供するフルマネージド型のGitリポジトリサービス)
- AWS CodeBuild(クラウドで動作する完全マネージド型のビルドサービス)
- AWS CodePipeLine(完全マネージド型の継続的デリバリーサービス)
3つのサービスを利用して、Hugoで記事を作成したら、自動で公開できるようにします。
CodeCommit
まず、リポジトリを作成します。
次に、リポジトリに接続します。
リポジトリに接続するためには、CodeCommitのGit認証情報を生成する必要があります。
マネージメントコンソールからIAMユーザーにアクセスし、「AWS CodeCommit の HTTPS Git 認証情報」の「認証情報を生成」ボタンをクリックします。
リモートリポジトリを追加し、後は add → commit → push の流れです。pushするとWindowsの場合、Git Credential Manager for Windows の画面が表示されます。そこに先ほど生成した認証情報を入力すると、リモートリポジトリに接続できます。
CodeBuild + CodePipeline
buildspec.ymlを作成します。
テンプレート内の bucket_name Distribution_ID に値を入力します。
- bucket_name = Hugo用S3のバケット名
- Distribution_ID = CloudFrontのID
version: 0.2
env:
variables:
hugo_version: "0.65.3"
bucket_name: "***"
Distribution_ID: "***************"
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
- rm -rf /tmp/hugo*
build:
commands:
- hugo
post_build:
commands:
- aws s3 sync --delete public/ s3://${bucket_name}
- aws cloudfront create-invalidation --distribution-id ${Distribution_ID} --paths "/*"
作成したら、quickstartフォルダに配置し、CodeCommitにpushしておきます。
└─quickstart
│ buildspec.yml
│ config.toml
│
├─archetypes
│
├─content
│
├─data
│
├─layouts
├─resources
├─static
└─themes
次に、CloudFormationでCodeBuildとCodePipelineを作成します。
AWSTemplateFormatVersion: 2010-09-09
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Build and Pipeline Configuration
Parameters:
- BuildProjectName
- PipelineName
- HugoS3BucketName
- CodeCommitRepositoryName
Parameters:
# ビルドプロジェクト名を入力
BuildProjectName:
Type: String
# パイプライン名を入力
PipelineName:
Type: String
# Hugo用S3バケット名を入力
HugoS3BucketName:
Type: String
# CodeCommitのリポジトリ名を入力
CodeCommitRepositoryName:
Type: String
Resources:
# CodeBuild用IamRoleを作成
BuildIamRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudFrontFullAccess
Policies:
- PolicyName: CodeBuild-Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource:
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${BuildProjectName}
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${BuildProjectName}:*
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Effect: Allow
Resource:
- !Sub arn:aws:s3:::codepipeline-${AWS::Region}-*
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- s3:DeleteObject
- Effect: Allow
Resource:
- !Sub arn:aws:s3:::${HugoS3BucketName}
- !Sub arn:aws:s3:::${HugoS3BucketName}/*
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- s3:DeleteObject
- s3:ListBucket
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
Resource:
- !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${BuildProjectName}-*
# CodePipeline用IamRoleを作成
PipelineIamRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CodePipeline-Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource: '*'
- Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
Resource: '*'
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:ListFunctions
Resource: '*'
- Effect: Allow
Action:
- iam:PassRole
Resource: '*'
- Effect: Allow
Action:
- cloudwatch:*
- s3:*
Resource: '*'
# CloudWatchEvent用IamRoleを作成
CloudWatchEventIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: cwe-pipeline-execution
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Join
- ''
- - 'arn:aws:codepipeline:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref BlogPipeline
# CodeBuildを設定
BlogBuild:
Type: "AWS::CodeBuild::Project"
Properties:
Source:
Type: CODEPIPELINE
Name: !Ref BuildProjectName
Artifacts:
Type: CODEPIPELINE
LogsConfig:
CloudWatchLogs:
Status: ENABLED
ServiceRole: !GetAtt BuildIamRole.Arn
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:3.0
Type: LINUX_CONTAINER
# CloudWatchEventルールを設定
CloudWatchEventRule:
Type: "AWS::Events::Rule"
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- CodeCommit Repository State Change
resources:
- !Join
- ''
- - 'arn:aws:codecommit:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref CodeCommitRepositoryName
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- master
Targets:
- Arn: !Join
- ''
- - 'arn:aws:codepipeline:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref BlogPipeline
RoleArn: !GetAtt CloudWatchEventIamRole.Arn
Id: codepipeline-BlogPipeline
# アーティファクト保存用バケットを作成
PipelineArtifactStoreBucket:
Type: "AWS::S3::Bucket"
# バケットポリシーを作成
PipelineArtifactStoreBucektPolicy:
Type: "AWS::S3::BucketPolicy"
Properties:
Bucket: !Ref PipelineArtifactStoreBucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: DenyUnEncryptedObjectUploads
Effect: Deny
Principal: '*'
Action: s3:PutObject
Resource: !Sub arn:aws:s3:::${PipelineArtifactStoreBucket}/*
Condition:
StringNotEquals:
's3:x-amz-server-side-encryption': aws:kms
- Sid: DenyInsecureConnections
Effect: Deny
Principal: '*'
Action: s3:*
Resource: !Sub arn:aws:s3:::${PipelineArtifactStoreBucket}/*
Condition:
Bool:
'aws:SecureTransport': false
# CodePipelineを設定
BlogPipeline:
Type: "AWS::CodePipeline::Pipeline"
Properties:
Name: !Ref PipelineName
RestartExecutionOnUpdate: false
RoleArn: !GetAtt PipelineIamRole.Arn
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
OutputArtifacts:
- Name: SourceOutput
Configuration:
BranchName: master
RepositoryName: !Ref CodeCommitRepositoryName
PollForSourceChanges: false
RunOrder: 1
- Name: Build
Actions:
- Name: CodeBuild
InputArtifacts:
- Name: SourceOutput
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref BuildProjectName
RunOrder: 1
ArtifactStore:
Location: !Ref HugoS3BucketName
Type: S3
スタック作成時、パラメータで4つの値を入力します。
- BuildProjectName = CodeBuildのプロジェクト名
- PipelineName = CodePipelineのパイプライン名
- HugoS3BucketName = S3とCloudFrontを作成で入力したS3バケット名
- CodeCommitRepositoryName = CodeCommitに作成したリポジトリ名
Lambda@Edgeを設定
最後にLambda@Edgeを設定します。こちらを設定しないと、サブフォルダにアクセスできません。
バージニア北部リージョンであることを確認 → ランタイムは「Node.js 10.x」を選択 → 「AWSポリシーテンプレートから新しいロールを作成」を選択 → ポリシーテンプレート - オプションから「基本的なLambda@Edgeのアクセス権限(CloudFrontトリガーの場合)」を選択 → 「関数の作成」をクリック
コードを下記のように編集し、「保存」をクリックします。
'use strict';
exports.handler = (event, context, callback) => {
// Extract the request from the CloudFront event that is sent to Lambda@Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var newuri = olduri.replace(/\/$/, '\/index.html');
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);
// Replace the received URI with the URI that includes the index page
request.uri = newuri;
// Return to CloudFront
return callback(null, request);
};
アクションから「Lambda@Edgeへのデプロイ」を選択します。
「Lambda@Edgeへのデプロイを確認」にチェックし、「デプロイ」をクリックすれば完了です。