Firebaseを使って「NoOps」でWebサイト運営する話

こんにちは!Future of Workプロジェクト Webサイト掲載担当のホリです。今日は、新しいWebサイトを掲載する基盤としてFirebaseを選んであれこれ工夫したことについて書きたいと思います。

背景・経緯

プロモーション用のWebサイト作ったからサクッと公開したい!というニーズは多々ありますが、サクッとは言え非機能要件のハードルは意外と高いです。

  • サイトをいつでも見れるようにして欲しい
  • ページ描画待ちのストレスを極力排して欲しい
  • サイト更新内容を本番に近い環境で事前確認したい
  • サイト更新したらなるはやで本番公開して欲しい

そこで、サイトを公開してから頻繁に更新したいことも分かっていたので、機能的にできて欲しいことを洗い出しました。

  • 開発環境を簡単に構築できるようにしたい
  • プレビュー環境を構築したい
  • Ajaxページのメタタグを静的に設定したい
  • CI/CD環境からデプロイしたい

まずは、サーバー運用せず安定的にサイトが公開されている状態を作るために コンテンツ配信ネットワーク(CDN) を使うべきだろうと考えました。その中で開発&リリースを円滑にする機能を持つものを探していくと、Firebase がまさに全てを満たすすごいヤツだという結論にたどり着きました!

機能的にできて欲しいこと

開発環境を簡単に構築できるようにしたい

開発環境構築&デプロイツールとして、 Firebase CLI が用意されていて、インストール&セットアップは超簡単です。

※事前に、Firebaseプロジェクト登録が必要です

npm install -g firebase-tools
firebase login

ローカルサーバー(デフォルト localhost:5000)を起動します。

(Firebaseプロジェクトのディレクトリに移動して)

npm install
firebase serve

プレビュー環境を構築したい

社内IPアドレスからのみアクセス可能な環境を用意したい、ということです。FirebaseにIPフィルター機能はありませんが、 Cloud Functions for Firebase を使った下記方法で実現可能です。

※事前に、Firebase Hostingサイト登録が必要です

functions/index.js にはこのようなプログラムを書いておきます。

exports.filter = functions.https.onRequest((req, res) => {
  // クライアントIPアドレスを取得して社内IP範囲に収まっているかチェック
  // 収まっていなかったらステータス403でエラーhtmlファイルを返し
  // それ以外だったらステータス200でリクエストパスに対応するhtmlファイルを返そう
});

※fs, ip-range-check, cheerioなどnode.jsで使えるモジュールは問題なく動作しました

次に、Firebase CLI用の設定ファイルを変更します。

.firebaserc(一部)

  "targets": {
    "【プロジェジェクト名】": {
      "hosting": {
        "production": [
          "【本番用サイト名】"
        ],
        "staging": [
          "【プレビュー用サイト名】"
        ]
      }
    }
  }

firebase.json(一部)

  "hosting": [
    {
      "target": "staging",
      "rewrites": [ {
        "source": "**", "function": "filter"
      } ],
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ]
    }
  ]

あとはgulpなどタスクランナーで、functions/public以下のディレクトリに、html, css, jsなどリソースファイルを 移動 させます。

準備が整ったらプレビュー用サイトにデプロイしましょう。

firebase deploy --only hosting:staging,functions:filter

Ajaxページのメタタグを静的に設定したい

クライアントサイドのテンプレートエンジンなどで動的に生成しているページを後から、SEOやOGP対応したいことがあると思います!この場合サーバーサイドでメタタグを埋めてクラアントに渡すようページ生成処理を大掛かりに変更する必要がありますが、先ほどと同様の方法で比較的簡単に実現可能です。

firebase.json(追加要素)

  "hosting": [
    {
      "target": "production",
      "rewrites": [ {
        "source": "/【転送パス】/**", "function": "server"
      } ],
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ]
    }
  ]

この場合だと、指定パスのhtmlファイルを読み取ってメタタグを書き換える server 関数を functions/index.js に追加することになります。先ほど作成した filter 関数も修正して server 関数呼び出しを間に挟むようにしましょう。

exports.server = functions.https.onRequest((req, res) => {
  // リクエストパスから変数を取得してメタタグの元となるデータをロード
  // メタタグを書き換えたいhtmlファイルを読み取ってDOMツリーを解析
  // 所定のメタタグcontentを上書きしてHTML文字列を返そう
});
exports.filter = functions.https.onRequest((req, res) => {
  // IPチェックがOKだったら上記server相当の関数を呼ぶ
});

参考までに、本番用サイトのデプロイコマンドも書いておきます。

firebase deploy --only hosting:production,functions:server

CI/CD環境からデプロイしたい

まず、Firebaseにアクセスするためのトークンを発行します。

firebase login:ci

※ここに表示されたトークンをCIツールの環境変数設定で登録します(以下例だと FIREBASE_TOKEN

今まで見てきてお分かりかと思いますが、Firebase CLIデプロイコマンドの引数を変更することだけで、環境やロードされるモジュールを切り替えています。CircleCIであれば、こんな感じになります。

.circleci/config.yml(一部)

  deploy_staging:
    <<: *container_for_js_build
    steps:
    - run:
        name: Install Firebase CLI
        command: |
          sudo npm install -g firebase-tools
    - attach_workspace:
        at: worksplace
    - run:
        name: Deployment to Staging
        command: |
          cd worksplace
          cp -pr public functions && rm -rf public/*
          firebase deploy --only hosting:staging,functions:filter --token "$FIREBASE_TOKEN"
        no_output_timeout: 30m

  deploy_production:
    <<: *container_for_js_build
    steps:
    - run:
        name: Install Firebase CLI
        command: |
          sudo npm install -g firebase-tools
    - attach_workspace:
        at: worksplace
    - run:
        name: Deployment to Production
        command: |
          cd worksplace
          firebase deploy --only hosting:production,functions:server --token "$FIREBASE_TOKEN"
        no_output_timeout: 30m

ほとんど違いないですね。

CircleCIに関しては、こちらの記事が詳しいです。

Firebaseを使ってみて

最初に掲げた非機能要件は実現したでしょうか?

  • サイトをいつでも見れるようにして欲しい
  • ページ描画待ちのストレスを極力排して欲しい

自動スケールのフルマネージドCDNなので理論的には達成してますし、体感でも快適な配信速度です!

  • サイト更新内容を本番に近い環境で事前確認したい

node.js実装することで、(ちょっと力技ですが笑)実現できました。

Firebase CLIを使ってプロジェクトを管理することで、全てのプログラム・設定ファイルが一箇所に集まる効果があります。これは、例えばGitHubのリポジトリを見るだけで、Hosting, Functionsの動作を見通せる状態であり、開発者にとって大きなメリットだと思います。

  • サイト更新したらなるはやで本番公開して欲しい

GitHubとFirebase CLIをCI/CD環境から呼び出すように設定しておけば、いつでも/どこからでもリリース作業可能です!

今後の展望

Firebase(そもそもmBaaSクラウド)には今回紹介したHosting, Functions以外にもたくさんの機能(Authentication, Database など)があります。

「サーバーレス」SPAで本格的なアプリケーションを立ち上げられる時代がやってきたとしか思えないので、フロントエンジニアに鞍替えしてサービスをガシガシ作っていこうと考えている今日この頃です!!