Azure Function Proxy を使って静的サイトをカスタムドメインかつHTTPSのホスティングする

キーワードを詰め込んだら長いタイトルになりました。

静的サイトのホスティング方法

Azure で静的サイトをホスティングするにはいくつか方法があります。

  • Azure Web App
  • Azure CDN + Blob Storage
  • Azure Function Proxy + Blob Storage

大まかに3つあります。

Azure Web App

オーソドックスな方法です。
カスタムドメインを適用するには Shared プラン以上が必要なので、日本リージョンで運用するには大体月1000円以上かかります。(公式:App Service 料金)
このパターンについてはギョームで使っているのでそのうち別媒体で書きます。

Azure CDN + Blob Storage

費用はアクセスが少なければ月数十円で済むでしょう。

なぜ Blob Storage の手前に CDN を挟む必要があるかというと、以下の記事が大変よくまとまっています。

Azure BlobでのWebサイト公開がつらい - Qiita

Blob Storage のツラみを CDN でどうにかしてやろうということです。
CDNSSL 証明書の面倒を見てくれるということで大変ありがたいです。
スケーラビリティも言うことなしでしょう。

しかし CDN で URL Rewrite するルールの適用が4時間以上かかるということで、待つのがかったるいです。
この記事を書いている間は絶賛待機中なのですが、待っている間に Function で試したところ動いたのでもういいかというところが今ココです。

Azure Function Proxy + Blob Storage

こちらも費用はアクセス次第ですが数十円から運用可能です。

Blob Storage で管理しやすいように管理するコンテンツの URL を Function Proxy でいい感じに整えるという役割分担です。
カスタムドメインSSL 終端は Azure Function が担当します。
なお、今回の例では SSL証明書 は Let's Encrypt を使っているので無料です。

実現方法

ということで本題。

完成品

まずは成果物です。

kheiakiyama's CV

コンテンツは何でもいいですが、Jekyll で作成したレジュメっぽいものを置いています。

GitHub - kheiakiyama/kheiakiyama.github.com at 12dc107f19cdea04a73fd72ec877c2e1f57acf13

余談ですが Octopress はどこに行ったんでしょうね。

GitHub から Blob Storage へのデプロイ

Web App には GitHub からの継続的デプロイ方法が提供されていますが、Blob ではもちろんありません。
そこで Travis CI を使っています。

kheiakiyama.github.com/.travis.yml at 12dc107f19cdea04a73fd72ec877c2e1f57acf13 · kheiakiyama/kheiakiyama.github.com · GitHub

デバッグ用コード多めです。
(上のリンクを開いた前提で)やっていることをざっくり書くと以下のとおりです。

  • Jekyll のビルド
  • .NET Core のインストール
  • AzCopy のインストール
  • AzCopy で Jekyll の成果物を Blob にコピー

Travis CI の権限周りがよくわからなかったので .NET Core のインストールはコピーしてきて手元で動かすだけという雑な作りになっています。

また、AzCopy で set-content-type しないと Blob 側の配信がすべて application/octet-stream になるという悲しい現象が起きるため、拡張子ごとにコピーするというイケてないコードになっています。
Function でヘッダーを上書きするのもありますが、Blob の時点で動作確認できる方が原因切り分けの面で有利なので Blob で実現しています。
コンテンツの一部ファイルが削除された場合など考慮することが他にもありますが、まずはこれで。

Azure Function の実装

proxies.json は以下のとおりです。

{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "blob-route": {
            "matchCondition": {
                "route": "/"
            },
            "backendUri": "http://{your_storage}.blob.core.windows.net/{container}/index.html"
        },
        "blob-index": {
            "matchCondition": {
                "route": "/index.html"
            },
            "backendUri": "http://{your_storage}.blob.core.windows.net/{container}/index.html"
        },
        "blob-asserts": {
            "matchCondition": {
                "route": "/assets/{*file}"
            },
            "backendUri": "https://{your_storage}.blob.core.windows.net/{container}/assets/{file}"
        },
        "letsencrypt": {
            "matchCondition": {
                "route": "/.well-known/acme-challenge/{*rest}"
            },
            "backendUri": "https://%WEBSITE_HOSTNAME%/api/letsencrypt/{rest}"
        }
    }
}

Blob Storage から取得したいファイルを Proxy する以外では Let's Encrypt の承認プロセスのための API を Proxy しています。
このあたりは 公式の Wiki に書いてある通りに書いただけです。

Proxy 以外にも api/letsencrypt/{rest} の部分を実装する必要があります。
上述の公式の Wiki を参考に C# か NodeJS の内容をコピペしましょう。

余談ですが Azure ポータルと App Service Editor を両方開きながら相互に読み書きしたところ proxies.json が消えるハプニングが起きました。ローカルに適宜バックアップしながら作業を進めるのが無難です。

Azure Function でカスタムドメインSSL の設定

カスタムドメイン公式 でも見てください。

Let's Encrypt の導入については Azure Function でも Web App でも上記の Proxy 部分以外に差はありません。
How to install · sjkp/letsencrypt-siteextension Wiki · GitHub

公式の手順を参考に粛々と作業を進めましょう。
英語がニガテな方は以下サイトを参考にするとよさそうです。

Azure Web Apps に Let's Encrypt を使って簡単に SSL を導入する - えむにわリソース

無事完了すると SSL 証明書が割り当てられ、カスタムドメインSSL バインディングされた状態になります。

f:id:khei-fuji:20180212164429p:plain

https://{your_site}.scm.azurewebsites.net/.well-known/acme-challenge/{*rest} のリダイレクトでエラーになった場合は Function Proxy の実装が上手くいっていないと思われます。
一つずつ見直しましょう。

参考にした記事

最後に

Proxy は書き換え直後にすぐ動くので使っていて気持ちいいです。
最初に上げた通り、実現方法のパターンはいくつかありますが、要件に合わせて適切に使っていきたいものです。

Azureテクノロジ入門 2018

Azureテクノロジ入門 2018