HugoをS3+CloudFront上にSSLでホストする

はじめに

随分と更新を放置していましたが、今回ブログの構築方法を大幅に刷新したので備忘録も兼ねて変更点などを記します。

最初にこのブログを始めたときはVPS上でWordPressを使っていましたが、しばらくしてPHPとMySQLの管理が面倒になったのでGhostを試しました。WordPressもそうですが、基本的に両者ともブラウザ上で下書きを作成するためレスポンスがあまり良くない場合があります。また当時Ghostは開発初期だったので機能が乏しくカスタマイズも難しいところがありました。

そんなとき耳にしたのが静的サイトジェネレーターです。MarkdownなどのテキストファイルからHTMLを生成しインターネット上に公開するだけなのでデータベースの作成も必要ありません。さらに、プレーンテキストで下書きが保存されるためGitなどを用いたバージョン管理も容易です。

そうしてしばらくPelicanを使っていましたが、それでもWebサーバーの保守は避けられません。なにか楽な方法はないものかと調べているとAmazon S3を使って静的サイトをホスティングできるという記事[1, 2]を目にしました。また、CloudFrontを併用することで自分の用意したSSL証明書を使ってセキュアな通信も可能とあり魅力的です。

そして先日、Amazonから無料でSSL証明書を発行できるサービスが発表されました(Amazon Web Services ブログ: AWS Certificate Manager – AWS上でSSL/TLSベースのアプリケーションを構築)。Let’s Encryptを利用して無料で証明書を入手することも可能ですが、AWS Certificate Manager(ACM)の優れた点は、証明書の有効期限が近づくと自動で更新してくれるということです。これでサーバーの管理や証明書の更新といった煩わしさから開放されます。

こうしてAWS Certificate Managerの発表がきっかけとなりブログ環境を改めることにしました。また今回、静的サイトジェネレーターとしてPelicanではなく、高速な記事の生成速度で評判のあるHugoを使うことにしました。

移行手順

注: 以下は2017年4月25日時点での操作方法です。AWSの仕様変更などにより手順が変わる場合が予想されます。

ソフトウェアのインストール

まず必要となるソフトウェアをインストールします。環境はMac OS Xで行い、Homebrewを使っています。他OSでも各ディストリビューションのパッケージマネージャーやバイナリなどから適宜インストールできます。

$ brew install hugo pandoc git awscli

hugoはサイトジェネレーターです。Pelicanでは原稿をreStructuredText形式(.rst)で書いていたため、Hugoを利用するにあたりMarkdown形式(.md)に変換する用途にpandocを使いました。gitはバージョン管理やテーマのインストールの際に必要です。awscliはAmazon Web Seviceの各サービスにコマンドラインからアクセスするためのツールで、hugoで生成されたファイルをS3にアップロードするときに使います。

Hugoを使った静的サイトの作成

続いてローカル環境でブログを構築します。ref: Hugo Quickstart Guide

$ hugo new site path/to/blog
$ cd path/to/blog 
$ tree .
.
├── archetypes
├── config.toml
├── content
├── data
├── layouts
└── static

Pandocを使ってMarkdown形式に変換した原稿をcontentディレクトリ内にコピーします。画像などのツリー関係はリンクが切れないように元の状態を維持しておきます。PelicanとHugoでは原稿の日付やタイトルといったメタデータの書き方が異なるため、それらは手動で書き換えました。ref: Pandoc - Getting started with pandoc, Hugo - Front Matter

// .rstを.mdに変換。.rstファイルのあるディレクトリにて
$ pandoc input.rst -f rst -t markdown -s -o output.md

// コピー後のcontentディレクトリ(一例)
$ tree content/
content/
├── images
│   ├── 2013
│   │   ├── 09
│   │   │   ├── 20130924-1.jpg
...            ...
│ 
└── posts
    ├── 2013-09-16_hello-world.md
...

テーマをインストールします。テーマ一覧があるのでそこから自分の好きなテーマを選びます。Viennaというテーマが良さそうだったので今回はこれに決めました。

Hugoのトップディレクトリにて、

$ mkdir themes
$ cd themes
$ git clone https://github.com/keichi/vienna

テーマがインストールされました。

Hugoの基本的な設定を指定するconfig.tomlを編集します。このファイルはYAML形式やJSON形式にも対応していますが、デフォルトのTOML形式で記述しました。ref: Hugo - Configuring Hugo

$ cd ..
$ cat config.toml
baseurl = "https://blog.yujinakao.com/"
languageCode = "ja-jp"
title = "UGarchive"
theme = "vienna"

[permalinks]
  posts = "/:year/:month/:day/:slug/"

[params]
  twitter = "nakaoyuji"
  github = "ynakao"
  ...

上に自分の設定したconfig.tomlの一部を示します。ほとんどは設定項目の名称からどのような内容か推察できると思います。[permalinks]の項目は記事URLの表示形式を決めるもので、content/posts/以下の原稿のメタデータと対応します。

例えば、content/posts/2013-09-16_hello-world.mdのメタデータは以下のようになっています。

$ cat content/posts/2013-09-16_hello-world.md 
+++
date = "2013-09-16"
title = "なにか書く"
tags = [ "announcement" ]
slug = "hello-world"
+++
...

これにより:yearは2013、:monthは09、:dayは16、:slugはhello-worldとなり、この記事のURLはbaseurlと合わせてhttps://blog.yujinakao.com/2013/09/16/hello-world/となります。

[params]の項目群はテーマによって設定できる値が変わるので注意が必要です。テーマのREADMEなどをよく読みましょう。

config.tomlの編集が終わったらいよいよHTMLファイルを生成します。

$ hugo

新たにpublicディレクトリが作られ、その中にHTMLファイルなどが入っています。これらを丸ごとインターネット上に公開するとサイトが表示されます。

S3の設定

publicディレクトリ内のファイルをS3にアップロードするため、まずアップロード先となるS3のバケットを準備します。ref: 例: 独自ドメインを使用して静的ウェブサイトをセットアップする - Amazon Simple Storage Service

  • S3コンソールに適切な権限を持つユーザーでログインし、Create Bucketをクリック。
  • ここで言う「適切な権限を持つユーザー」とはS3へのアクセスやバケットの作成、設定の変更が許可されたユーザーのことである。AWSではユーザーごとに与えられる権限を細かく決めることが可能なためこのような表現をとっている。全権限を付与されたadminユーザーならば権限の有無を考慮する必要はないが、セキュリティの観点からAWSでは特定のサービス毎に必要最低限の権限を持ったユーザーの作成が推奨されている。この後も同様の表現をとった箇所があり、意味は同じである。ref: IAM のベストプラクティス - AWS Identity and Access Management
  • ポップアップウインドウが現れるのでBucket Nameにはブログのドメイン名を入力(今回はblog.yujinakao.com)。適当なRegionを選択、Copy settings from an existing bucketは空欄のままNextをクリック。
  • Set propertiesでは特に変更を加えずNextをクリック。
  • Set permissionsでも同様に変更を加えずNextをクリック。
  • Reviewで設定した項目を確認し、Create bucketをクリック。
  • バケット一覧に作成したバケットが表示される。作成したバケットを選択し、Propertiesのエリアをクリックし内容を編集していく。
  • Bucket Policyのボックスをクリック。
  • Bucket policy editorのテキストエリアに以下の内容をペーストする。blog.yujinakao.comの箇所は運用するサイトURLに合わせて適宜変更する。
{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"AddPerm",
        "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::blog.yujinakao.com/*"
      ]
    }
  ]
}
  • Saveをクリック。
  • 次にPropertiesのタブをクリック、Static website hostingの項目をクリックし内容を編集する。
  • Use this bucket to host a websiteのラジオボタンをクリック。Index documentにはindex.htmlError documentには404.htmlを入力しSaveをクリックする。
  • Static website hostingに表示されるEndpointのURL(http://{バケット名}.s3-website-{バケットのリージョン}.amazonaws.com)にアクセスすることでウェブサイトのテストが可能。今回の場合はhttp://blog.yujinakao.com.s3-website-ap-northeast-1.amazonaws.com

続いてS3にコマンドラインからアクセスするための設定を行います。まず、アクセスキーを発行します。ref: IAM ユーザーのアクセスキーの管理 - AWS Identity and Access Management

  • 適切な権限を持つユーザーでIAMコンソールにログインする。
  • Userをクリックし、S3にアクセス権限を持つユーザー→Security CredentialsCreate Access Keysの順にクリック。
  • 鍵の作成に成功の旨のポップアップウインドウが表示されたら、Secret access keyの項目内のShowをクリックし、Access Key IDSecret Access Keyの内容をメモしておく。Download .csv fileからダウンロードすることも可能。ウインドウを閉じるとSecret Access Keyは二度と見ることができなくなる。また、忘れると再発行しなければならないことに注意。
  • ローカル環境のコマンドラインでIDとKeyの設定を行う。ref: configure — AWS CLI 1.10.1 Command Reference
$ aws configure
AWS Access Key ID [None]: "Access Key ID"
AWS Secret Access Key [None]: "Secret Access Key"
Default region name [None]: ap-northeast-1
Default output format [None]:
  • 対話形式のウィザードが始まるので先程のIDとKeyを入力する。Default region nameには自分の利用しているAWSのリージョンを指定する。日本のユーザーであればほとんどがap-northeast-1Default output formatは何も入力せずエンターキーを押した。

一通りの準備が整ったのでCloudFrontの設定を行う前に、S3のみでサイトがホストされるか確認しました。publicディレクトリ内のファイルを作成したS3のバケットにアップロードします。下のblog.yujinakao.comには作成したバケットの名前を指定します。ref: sync — AWS CLI 1.10.1 Command Reference

$ aws s3 sync public/ s3://blog.yujinakao.com

バケットをEnable website hostingに設定した際のEndpointのURL(http://{バケット名}.s3-website-{バケットのリージョン}.amazonaws.com)にアクセスしてサイトが表示されれば成功しています。ただし、HTMLヘッダ内のスタイルシートなどのリンクがこのURLだとアクセスできずレイアウトが崩れている場合もあります。

ACMにて証明書の発行

AWS Certificate Managerで自分のドメインのSSL証明書を発行します。ref: Request a Certificate - AWS Certificate Manager

  • ACMコンソールに適切な権限を持つユーザーでログイン。Get startedをクリックする。
  • Domain nameのテキストフィールドに証明書を発行したいドメインを入力する。ワイルドカードも使用可能で*.yujinakao.comとすればyujinakao.com以下すべてのサブドメインで証明書が有効となる。ここではblog.yujinakao.comと入力した。
  • Review and requestをクリックすると確認画面に遷移する。
  • 確認画面でConfirm and requestをクリックすると、申請したドメインの正式な保有者であるか確認のメールが送られてくる。自分の場合は、admin, webmaster, postmasterとWhois情報に載っているアドレス宛にメールが届いた。
  • メール内のリンクを開くと最終確認の画面が表示される。内容をよく読み、I Approveをクリックする。
  • ACMコンソールに戻ると申請したドメインが一覧に加わっている。

CloudFrontの設定

CloudFrontを使ってSSL通信に対応したコンテンツデリバリーの設定を行います。

  • CloudFrontコンソールに適切な権限を持つユーザーでログイン。Create Distributionをクリックする。
  • デリバリー方式の選択を求められるので、Webの項目のGet Startedをクリックする。
  • 各種項目を以下のように設定した。特に明記していなければ空白のままか、もしくは変更を行ってない場合である。
    • Origin Domain Name: S3 Endpoint URLのドメイン箇所のみ({バケット名}.s3-website-{バケットのリージョン}.amazonaws.com)を入力(http://は入力しない)。テキストエリアをクリックするとプルダウンにリージョン名を含まない候補({バケット名}.s3.amazonaws.com)がサジェストされることがあるが、これを選択するとうまく動作しない。必ずリージョン名を含んだエンドポイントを入力するよう注意する。
    • Origin ID: 上のOrigin Domain Nameを入力すると自動で割り振られる。
    • Viewer Protocol Policy: Redirect HTTP to HTTPSを選択。HTTPでアクセスしてもHTTPSのサイトにリダイレクトされるようにする。
    • Alternate Domain Names(CNAMEs): blog.yujinakao.comを入力。
    • SSL Certificate: Custom SSL Certificateを選択。さらにプルダウンから先に作成したblog.yujinakao.comのSSL証明書を選択する。
    • Custom SSL Client Support: Only Clients that Support Server Name Indication (SNI)を選択。
  • Create Distributionをクリックして設定を終了。ディストリビューション一覧でStatusProgessからDeployに変わるまで15分ほどかかる。
  • リストから作成したディストリビューションを選択し、Distribution Settingsをクリック。Generalタブ内のDomain Nameの値(******.cloudfront.net)をメモしておく。
  • Amazon Route 53blog.yujinakao.com******.cloudfront.netをCNAMEで参照するようにDNSを設定した。

これでhttps://blog.yujinakao.com/にアクセスするとHugoで生成したブログが表示されるようになりました。セキュアな通信を示す緑の鍵マークもアドレス欄で確認でき、証明書の詳細を見るとAmazonが発行していることが分かります。

さいごに

他にもHugoのテーマのCSSをいじったりバージョン管理の設定を行ったりと瑣末な点はありましたがおおまかな手順は以上の通りです。AWSにおけるユーザー・グループの作成やRoute 53の細かな操作は少し本題から離れそうでしたので軽く触れる程度にとどめました。

セキュリティ的な不備やもっとこうした方が良いといった改善点などあればご教授ください。ブログが新しくなったことで更新回数が増えればいいなと自分のことながら思います。

追記(2016/07/05)

参考までに次のエントリの末尾にAWSで実際にかかった料金について触れています。上記のような運用でだいたい月$0.15-0.2くらいでした。ただ、アクセス数や通信量、S3に占めるファイルの容量が増えるとその分料金も上がります。