暇人じゃない

Middleman のブログを CloudFront と Certificate Manager で HTTPS 化する

Middleman で作っているこのブログを HTTPS で配信するようにしました。

当初は無料で利用できる CloudFlare を利用しようと思っていたのですが、 ネームサーバーを書き換えるのに少し抵抗があったので、AWS CloudFront と AWS Certificate Manager を使うことにしました。

以下のツールやサービスを使用します。

すでに S3 でホスティングしていることを前提に、HTTPS 化するまでの手順や Middleman の設定方法を説明します。

AWS Certificate Manager

まずは AWS Certificate Manager (以下 ACM) を使用して証明書を取得します。ACM は SSL/TLS の証明書の発行、管理を行うサービスで、以下のような特徴があります。

CSR を作成して証明書を受け取って Web サーバーに設定して… というのは地味に面倒な作業です。 Elastic Load Balancer や CloudFront を使用しているのが条件ですが、簡単に証明書を利用できます。

ACM を利用するには、設定するドメインに対して所有者の確認のメールを受信できる必要があります。 メールを受信する環境が整っていない場合は、以下のように SES のメール受信を使うとよいでしょう。

まずは「Add domain names」に追加したいドメイン名を記入します。

chocoby.jp*.chocoby.jp を追加しました。 ワイルドカードが使用できるので www.chocoby.jp などのパターンを一括で指定できます。

「Review and request」を押すと、以下のメールアドレス宛に所有者の確認メールが送信されます。

メールに記載されているリンクにアクセスすると、確認が完了します。

AWS S3

特に設定が必要な項目はありませんが、後ほど CloudFront で設定するため Endpoint をメモしておきます。

AWS CloudFront

「Create Distribution」から新しい Distribution を作成します。
以下に設定を記載します。

Origin Settings

Default Cache Behavior Settings

Distribution Settings

AWS Route 53

対象のドメインのレコードを編集します。

AWS IAM

IAM ユーザーは以下のような権限となります。

一つ目は middleman-s3_sync を使用して S3 上のファイルを操作する際の権限です。example.com は適宜書き換えてください。
二つ目は middleman-cdn でファイルを Invalidate する際の権限です。

{
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::example.com",
                "arn:aws:s3:::example.com/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:GetDistribution",
                "cloudfront:CreateInvalidation"
            ],
            "Resource": "*"
        }
    ]
}

Middleman

CloudFront を使うときに気になるのは、変更したファイルの Invalidate (失効) だと思いますが、 middleman-cdn gem を利用することで実現できます。

Gemfile:

gem 'middleman-cdn'

config.rb から S3 と CloudFront に関係する設定を抜き出しました。 アクセスキーやバケットなどの情報は環境変数から読み込んでいます。

config.rb:

# middleman-s3_sync
activate :s3_sync do |sync|
  sync.aws_access_key_id     = aws_access_key_id
  sync.aws_secret_access_key = aws_secret_access_key

  sync.bucket = bucket
  sync.region = region

  # build 後に Invalidate を実行させないように false をセット
  sync.after_build = false
end

# middleman-cdn
activate :cdn do |cdn|
  cdn.cloudfront = {
    access_key_id:     aws_access_key_id,
    secret_access_key: aws_secret_access_key,
    distribution_id:   distribution_id,
  }
end

# 変更されたファイルを Invalidate する
after_s3_sync do |files_by_status|
  cdn_invalidate(files_by_status[:updated])
end

ちなみに files_by_status には以下のようなハッシュが入ります。(blog/index.html を更新した場合)

{:ignored=>
  ["blog/tag/index.html",
   "stylesheets/blog/index-80c33e22.css",
   "stylesheets/variables-da39a3ee.css",
   "stylesheets/reset-33b6f4bf.css"],
 :created=>[],
 :updated=>["blog/index.html"],
 :deleted=>[]}

これで、middleman buildmiddleman deploy を実行すると、S3 にファイルをアップロードした後に、 変更されたファイルに対して CloudFront の Invalidate が実行されるようになります。

cdn  Invalidating 2 files:
cdn   • /blog/index.html
cdn   • /blog/
cdn  cloudfront Invalidating 2 files... ✔
cdn  cloudfront It might take 10 to 15 minutes until all files are invalidated.
cdn  cloudfront Please check the AWS Management Console to see the status of the invalidation.

www ありドメインからのリダイレクト

このブログは Naked Domain を使用しているので、www ありでアクセスされた場合に www なしの URL にリダイレクトさせる必要があります。 今までは S3 の Static Website Hosting のリダイレクト機能を使用していました。 CloudFront でもこの機能を使用します。

S3 で www ありのバケットに対して www なしにリダイレクトするように設定しておきます。

そして CloudFront で www 用の Distribution を作成し、www ありのバケットの Endpoint を Origin Domain Name に設定します。 作成した Distribution の URL を Route 53 のレコードに設定することで、一応リダイレクトされるようになります。

「一応」と書いたのは、ルートディレクトリ (www.example.com/) にアクセスすると example.com/index.html にリダイレクトされてしまうためです。

www.example.com/blog などのサブディレクトリにアクセスした際には example.com/blog に問題なくリダイレクトされます。 ルートディレクトリでもそのままリダイレクト出来れば良いのですが、課題としておきます。

これで HTTPS での配信とリダイレクトが行えるようになりました。 他にも URL をフルパスで指定している箇所の変更や Google Analytics、ウェブマスターツールの設定も忘れないようにしましょう!

費用面

CloudFront は以下のように利用料金がかかります。

転送料金やリクエスト料金については現在のアクセス数であれば気にならない範囲なので、困るほどアクセス数が増えてきたら他のサービスに移行するか、 Let’s Encrypt と組み合わせて自前でホスティングするか、などを考えようと思います。

それよりも、更新が頻繁に発生する場合は Invalidate の料金が問題になるかもしれません。

記事を追加・編集する程度であれば無料範囲内の 1,000 パス内に収まりそうですが、テンプレートを編集した場合は大量の Invalidate が走ることになります。 これを何度も繰り返すと結構な請求になるので、上手くワイルドカードで Invalidate するとか(middleman-cdn はワイルドカードに対応していない)、 急いでいないものについては Invalidate せずにエッジロケーションのキャッシュが切れるまで待つ(今回の設定では 24 時間)などの対応が必要になるかもしれません。