はじめに
前回の記事「はてなブログから Hugo に移行した」の続き。
勉強を兼ねて AWS 上にインフラを構築してブログサイト(このサイトのこと)を公開してみた。
本記事ではサイトを公開するまでの手順やコードの解説をする。
構成
- AWS 上のリソースは Terraform で構築
- 静的サイトジェネレーター Hugo を使用してコンテンツを生成
- 静的サイトジェネレーターのソースコードの管理(ブログ記事の管理)は GitHub
- GitHub Actions を構成して、記事をコミットすると GitHub 側でビルドして S3 に自動で配置
- S3 のコンテンツを CloudFront でキャッシュ&デリバリ
- HTTPS で公開したいので、SSL 証明書を ACM(AWS Certificate Manager)で発行
サイト公開までの流れ
環境
- Windows 10 Home
- Chocolatey v1.2.1
- Hugo v0.109.0 extended
- Hugo theme Stack
- aws-cli v2.9.10
- Terraform v1.1.2
Hugo の準備
Hugo のインストール
Hugo をローカル環境にインストールする。
テーマに Stack を使いたいので、Hugo は拡張バージョンをインストールする。
# ChocolateyでHugo拡張版をインストール
choco install -y -f hugo-extended
# hugo versionコマンドを実行して、パスが通っていること&extendedが記載されていることを確認
hugo version
> hugo v0.109.0-47b12b83e636224e5e601813ff3e6790c191e371+extended windows/amd64 BuildDate=2022-12-23T10:38:11Z VendorInfo=gohugoio
サイトの作成
サイトの作成はhugo new site
コマンドで行う。
hugo new site blog-nns7
サイトのバージョン管理
サイトのバージョン管理を Git で行うためgit init
コマンドを実行。
cd blog-nns7
git init
gitignore ファイルはこんな感じにした。
/public/
/resources/
.hugo_build.lock
テーマの追加
Stack ではテーマをサブモジュールとして管理できるため、git submodule add
コマンドでサブモジュールを追加する。
git submodule add https://github.com/CaiJimmy/hugo-theme-stack/ themes/hugo-theme-stack
サンプルページの確認
Stack にはサンプルページが用意されている。
サンプルページを確認したい場合はexampleSite/content
ディレクトリ配下とexampleSite/config.yml
ファイルをコピーする。
cp -r ./themes/hugo-theme-stack/exampleSite/content/* ./content/
cp -r ./themes/hugo-theme-stack/exampleSite/config.yaml ./
# コピーしたconfig.yamlと、もともといるconfig.tomlが競合するため、tomlファイルを削除
rm ./config.toml
Hugo をローカルでサーブする
hugo serve
コマンドを実行すると、ローカルでサーバーを起動する。
http://localhost:1313にアクセスすると、サンプルページをブラウズすることができる。
サーバーを停止する時はCtrl+C
キーを入力する。
hugo serve -D
オリジナルページを作成する
オリジナルページを作成する時はhugo new
コマンドを実行する。
Stack ではページはindex.md
というファイル名で作成する必要がある。
rm -f ./content/* # サンプルページの削除
hugo new post/test/index.md
とりあえずテスト投稿としてindex.md
の内容は以下のようにした。
---
title: "Test"
date: 2022-12-30T17:59:31+09:00
draft: true
---
## テスト投稿
こんにちは
AWS の準備
AWS CLI のインストール
Terraform で使用する AWS 認証情報(クレデンシャル)を設定するため AWS CLI をインストールする。
choco install -y -f awscli
# パスが通っていることを確認
aws --version
> aws-cli/2.9.10 Python/3.9.11 Windows/10 exe/AMD64 prompt/off
AWS 認証情報(クレデンシャル)の設定
以下を参考に、管理者ユーザーを作成し、アクセスキー ID とシークレットアクセスキーを発行しておく。
AWS CLI からアクセスキー ID とシークレットアクセスキーを設定する。
以下の例では料金が安い us-west-2(オレゴン)リージョンを Default にしているが、ここは必要に応じて書き換える。
aws configure
> AWS Access Key ID [None]: <発行したアクセスキーIDを入力>
> AWS Secret Access Key [None]: <発行したシークレットアクセスキーを入力>
> Default region name [None]: us-west-2
> Default output format [None]: json
設定した情報は~/.aws/config
ファイルおよび~/.aws/credentials
ファイルに保存される。
Get-Content ~/.aws/config
> [default]
> region = us-west-2
> output = json
Get-Content ~/.aws/credentials
> [default]
> aws_access_key_id = <アクセスキーID>
> aws_secret_access_key = <シークレットアクセスキー>
Terraform のインストール
続けて Chocolatey から Terraform をインストールする。
choco install -y -f terraform
# パスが通っていることを確認
terraform version
> Terraform v1.1.2
> on windows_amd64
AWS のクレデンシャルは、デフォルトでは~/.aws/config
ファイルおよび~/.aws/credentials
ファイルを参照するようになっている。
The AWS Provider can source credentials and other settings from the shared configuration and credentials files. By default, these files are located at
$HOME/.aws/config
and$HOME/.aws/credentials
on Linux and macOS, and"%USERPROFILE%\.aws\config"
and"%USERPROFILE%\.aws\credentials"
on Windows. If no named profile is specified, thedefault
profile is used. Use theprofile
parameter orAWS_PROFILE
environment variable to specify a named profile.
tfstate 管理用 S3 バケットを作成
Terraform では「tfstate」というファイルを用いて構築状態を管理する。
個人用であればローカル管理でも良いかもしれないが、ベストプラクティスはオンラインストレージに保存するようなので、tfstate 管理用の S3 バケットを作成する。
S3 バケットの作成には Terraform ではなく CloudFormation を用いる。(Terraform で作成すると tfstate 管理用 S3 バケットの構築状態を管理する tfstate ファイルが発生してしまうため)
以下のバケット名を適当な値に変更したら CloudFormation を実行する。
AWS CLI から実行できるかもしれないが、筆者は AWS マネジメントコンソールから yaml ファイルをアップロードして実行した。
AWSTemplateFormatVersion: 2010-09-09
Description: This CloudFormation template to create S3 Bucket
Parameters:
S3BucketName:
Type: String
Default: <バケット名を入力>
Description: Type of this BacketName.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
# バケットに保存するオブジェクトをAES256でサーバーサイド暗号化する
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
# パブリックアクセスを全てブロック
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
Outputs:
S3BucketName:
Value: !Ref S3Bucket
ACM で SSL 証明書の発行
HTTPS で公開したいので、ACM でパブリック証明書(無料)を発行する。
筆者はお名前.com でドメインを取得したため、AWS マネジメントコンソールから証明書をリクエストし、お名前.com の DNS サーバーに CNAME レコードを追加する手順を踏んだ。
CNAME 検証のやり方は以下の記事が詳しい。
なおリンク先の記事にも書いてあるが、CloudFront に使用する証明書は us-east-1(バージニア北部)リージョンで作成する必要があるため注意。

CloudFrontでお名前.comの独自ドメインおよびSSL証明書を使用する
Terraform で AWS リソースを構築
ディレクトリ
.
├── .terraform
├── modules/
│ ├── addIndexFunction.js
│ ├── cloudfront.tf
│ ├── iam.tf
│ ├── s3.tf
│ └── variables.tf
├── .gitignore
├── .terraform.lock.hcl
├── main.tf
├── terraform.tfvars
└── variables.tf
ソースコードの解説
Terraform のソースコードは GitHub で公開しているため、要点だけ解説する。

GitHub - nns7/blog.nns7.work-terraform
Terraform 設定ファイル
7 行目には先の手順で作成した tfstate 管理用 S3 バケット名を指定する。
17 ~ 23 行目では child module に./terraform.tfvars
ファイルで設定した変数を渡している。
|
|
./terraform.tfvars
ファイルの中身はというと、.tfvars
ファイルをバージョン管理の対象外とし、以下のような GitHub で公開したくない情報を記述するようにした。
aws_account_id = <AWSアカウントID>
certificate_id = <ACMで発行した証明書のID>
github_account = <GitHubアカウント>
github_repo = <GitHubリポジトリ>
静的サイト公開用 S3 バケット作成
静的サイト公開用 S3 バケットは CloudFront からのアクセスのみ許可したいため、以下の設定を行う。
- パブリックアクセスを全てブロック
- バケットポリシーに OAC (Origin Access Control)を設定し、CloudFront Distribution からのアクセスのみ許可
|
|
ログ補完用 S3 バケット作成
|
|
CloudFront ディストリビューション作成
オリジンの設定をする際、17 行目で OAC と紐付ける。
|
|
今回は ACM で作成した証明書を使用するため、CloudFront のデフォルト証明書は無効化する。acm_certificate_arn
にて、先の手順で CNAME 検証して発行した ACM の ARN を指定する。
なお、変数aws_account_id
とcertificate_id
は./terraform.tfvars
ファイルに記述している。
|
|
aws_cloudfront_distribution
リソースに紐付ける OAC を作成。
|
|
CloudFront Functions 作成
Hugo ではディレクトリまでの URL(http://example.com/post/test/
)でリクエストするため、index.html
が見つからずコンテンツを表示できない問題が発生する。
CloudFront Functions を使用して URL の末尾が/
で終わるリクエストはindex.html
を補完してあげるようにする。
|
|
CloudFront Functions で実行するスクリプトは AWS 公式ドキュメントに掲載されている JavaScript を使用する。
function handler(event) {
var request = event.request;
var uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith("/")) {
request.uri += "index.html";
}
// Check whether the URI is missing a file extension.
else if (!uri.includes(".")) {
request.uri += "/index.html";
}
return request;
}
GitHub Actions 用 IAM ロール作成
GitHub Actions でビルドしたコンテンツを S3 にアップロードするための IAM ロールを作成する。
AWS の認証には ID フェデレーションを使用し、外部 ID プロバイダで管理している ID(今回は GitHub アカウント)による認証を行う。
|
|
Terraform の実行
terraform init
した後にterraform apply
で実行できる。
なお apply する前にterraform plan
で実行計画が確認でき、ソースコードに不備があれば教えてくれる。
terraform init
terraform apply
GitHub Actions の準備
Hugo ブログサイトのルートディレクトリに、GitHub Actions ワークフローファイルを作成する。
|
|
10 行目で使用する AWS アカウント ID は、あらかじめ GitHub のシークレットに登録しておく。
ワークフローについてはこちらの記事で紹介されているものを参考に作成した。
ワークフローの解説についてはこちらの記事を確認して頂きたい。

Hugo を使った CloudFront + S3 のブログサイトを構築してみた 〜 GitHub Actions で CI/CD 付き | DevelopersIO
36 行目のみ、Warning が発生したためconfigure-aws-credentials@v1
をconfigure-aws-credentials@v1-node16
に変更した。
おわりに
ここまでの設定を順番に行えば、git push すれば GitHub Actions が自動で S3 にデプロイしてくれるようになっている…はず…!!
なお今回 IaC を体感したくて Terraform を初めて使ってみたが、とても便利だと感じた。
Terraform の学習コストはかかるものの、コード化によるメリット(設定の可視化、部品の再利用、インフラのバージョン管理、等)は絶大だと思う。