Firebase Hosting と Cloud Run で GitHub の芝生を植える

              · ·

はじめに

Firebase Hosting や Netlify 等、静的サイトのホスティングサービスは多数あります。 この2つは FaaS (Function as a Service) 機能により、動的なコンテンツの配信にも対応しています。

それぞれ、次の機能が利用できます。

Service FaaS CaaS
Firebase Hosting Firebase Functions (Cloud Functions) Cloud Run
Netlify Netlify Functions (AWS Lambda)

今回は、このサイトが Firebase Hosting で稼働していること、また Cloud Run という Container as a Service が利用できることから、Firebase Hosting を例にして紹介します。

対象読者

TL;DR

GitHub の芝生とは

GitHub Contributions Graph といいます。 GitHub のプロフィールページにある、緑色の芝生です。

GitHub の芝生を自分のサイトにも植えたいという物好きな人は中々いないと思うので、一般の方は Firebase Hosting と Cloud Run を統合して動的なコンテンツを生成する際の参考情報としてご活用ください。

ちなみに、今回植える芝生はこちらです。Cloud Run で GitHub から取得してきている SVG 画像なので、毎日内容が更新されます。

芝生

Cloud Run が必要な理由

Same-Origin Policyによりフロントエンドで GitHub から直接取得することはできません。 そこで BFF (Backends For Frontends) が必要になります。

BFF とはドメインロジックを担うバックエンドとは別に、フロントエンドのために API の集約、データ整形、プロトコル変換、SSR などの役割を担う、バックエンドとフロントエンドの橋渡しのような存在です。

今回は、BFF のために Cloud Run を採用し、CDN 対応のための Cache-Control ヘッダー付与と、データ整形を行う API を稼働させます。 Cloud Functions ではなく Cloud Run を選ぶ利点としては、Docker Image としての可搬性、再利用性が大きく、ローカル環境でも容易に動作確認ができる事です。さらにベンダーロックインも、ランタイムの制限もないため、好きな言語を採用できます。

今回は、Cloud Functions や Netlify Functions でも使える Go 言語で実装します。

全体図

Architecture

図の通り、Client は Firebase Hosting 経由で Cloud Run を実行します。これにより CDN によるキャッシュが利用できるので、すでにキャッシュに存在する場合は Cloud Run を起動せず、Firebase Hosting から直接返却されます。

Cloud Run で稼働させるアプリケーションから、GitHub へ HTTP Request を送信します。

Cloud Run を利用するには Docker image を事前に Docker Hub や Cloud Container Registry に登録する必要があるので、今回は Cloud Container Registry を利用しています。

GitHub の芝生を取得するプログラム

コードを見ていただいた方が早いと思うのでリポジトリへのリンクを掲載します。誰も使わないと思いますが、MIT ライセンスの下で利用できます。

https://github.com/ww24/lawn

やっていることはシンプルです。

GitHub から GitHub Contributions Graph の SVG 画像を取得

次の URL から GitHub Contributions Graph を含む HTML が取得できます。

https://github.com/users/${github_username}/contributions

golang.org/x/net/html を用いて svg 要素のみを抽出する parser を実装します。

抽出した svg 要素には style が当たっていない、xmlns 属性が足りない等、単体の SVG 画像として扱うには不都合があるため、修正します。

HTTP Server で配信

Firebase Hosting は CDN によるキャッシュが効くので、Cache-Control header を返却することは、コストやパフォーマンスの面で重要です。今回は1日キャッシュされるように設定しています。

Firebase Hosting のキャッシュの動作については、 こちら に記載されています。

Deploy

Cloud Container Registry への push

API を有効化します。

$ gcloud services enable containerregistry.googleapis.com

Cloud Container Registry へ docker image を push できるように、認証を済ませておきます。
cf. https://cloud.google.com/sdk/gcloud/reference/auth/configure-docker

$ gcloud auth configure-docker

docker image を build & push します。
${PROJECT_ID} の箇所は実際の GCP Project ID で置き換えてください。

$ docker build -t asia.gcr.io/${PROJECT_ID}/lawn:latest .
$ docker push asia.gcr.io/${PROJECT_ID}/lawn:latest

Cloud Run への deploy

API を有効化します。

$ gcloud services enable run.googleapis.com

gcloud command で deploy する場合

${PROJECT_ID} の箇所は実際の GCP Project ID に、 ${GITHUB_USERNAME} の箇所は実際の GitHub ユーザー名で置き換えてください。

$ gcloud run deploy lawn \
	--image="asia.gcr.io/${PROJECT_ID}/lawn:latest" \
	--region=asia-northeast1 \
	--platform=managed \
	--max-instances=1 \
	--memory=128Mi \
	--allow-unauthenticated \
	--set-env-vars \
	GITHUB_USERNAME=${GITHUB_USERNAME}

terraform で deploy する場合

こちらを参考にしてください。
https://github.com/ww24/lawn/tree/master/terraform

Firebase Hosting の設定

firebase.json に以下のように rewrite rule を追記し、deploy します。
serviceId には Cloud Run への deploy 時に指定した service id を指定してください。

{
  "hosting": {
    "rewrites": [ {
      "source": "/lawn.svg",
      "run": {
        "serviceId": "lawn",
        "region": "asia-northeast1"
      }
    } ]
  }
}

次のコマンドで deploy します。

$ firebase deploy

注意点

1. Firebase Hosting と Cloud Run を統合すると Firebase のプランは Blaze になる

Spark と無料枠は変わりませんが、無料枠を超過した分は従量課金になります。

2. Cloud Run 作成時のデフォルトでは 403 が返る

Cloud Functions と異なり、Cloud Run では認証済みリクエストのみ受け付ける設定がデフォルトです。 そのため、Firebase Hosting から呼び出す際は未認証リクエストを許可する設定が必要です。

※ Firebase Hosting から呼び出す際のサービスアカウントがあれば、そのサービスアカウントに Cloud Run 起動元ロール (roles/run.invoker) を付与することで認証済みリクエストに対応できるのですが、現在 (2020/3) ドキュメントに記載はなく、未認証リクエストを許可するしかないようです。

3. Firebase Hosting の rewrite rule で path は rewrite できない

たとえば、次のように /lawn というリクエストが Cloud Run にルーティングされるように rewrite rule を記述した場合、Cloud Run にはそのまま /lawn という path でリクエストが飛びます。/lawn/ に rewrite することはできないので、source に指定する path が変わる際は、Cloud Run 側で動かすアプリケーションの実装にも修正が必要になる場合があります。

{
  "hosting": {
    "rewrites": [ {
      "source": "/lawn",
      "run": {
        "serviceId": "lawn",
        "region": "asia-northeast1"
      }
    } ]
  }
}

まとめ

Firebase Hosting と Cloud Run を統合することで、動的なコンテンツも配信できるようになりました。CDN によるキャッシュも利用できるため、ページが開かれる度に Cloud Run が起動することもなく、直接 Cloud Run の URL を公開する場合に比べてコストも抑えられます。

今回取得した GitHub の芝生は トップページ下部 Activities に掲載しています。

参考