GAミント至上主義

Web Monomaniacal Developer@ビズオーシャン

Firebase HostingからFirebase Functions を使ってGKEのアプリケーションにプロキシする

Firebase Hostingを使ったサイトで、一部のパス(/api以下等)へのリクエストを別サーバーに投げられる仕組みを考えた。

別サーバーでCloud Functions(Node.js)で出来ないことをしたかった。

結果として動いてしまったけどなぜこんなことしてるのか分からなくなってきたのでまず整理。

1、アプリケーション概要

フロントエンド

Vue.jsでビルドしたSPA(Firebase Hostingで配信)

バックエンド

GKEで動いているヘッドレスChromeDjango等を使ったAPIサーバー。Cloud Functionsじゃむりぽ。

2、動機

上記構成を普通につくるとホスト名やSSL証明を分ける必要があり、下記のデメリットが発生する

  • バックエンドではHTTPS Load Balancerを立てる必要があり、SSL証明書の設定が必要
  • フロントエンドからAPIにリクエストする際、別ドメインだとCORSの設定が必要になるなど面倒が多い

なのでhttps://{firebase hostingのURL}/api等に来たリクエストを、そのままバックエンドにプロキシすることができたらいいなぁと思った。

3、手順

1、Firebase Functionsでリクエストをプロキシする関数を作る

まずFunctionsのinitは公式通りに行う。

https://firebase.google.com/docs/functions/get-started?hl=ja


今回はHTTPがトリガーなので下記を理解する
HTTP リクエスト経由で関数を呼び出す  |  Firebase

で、さっそく作り始める。
expressでプロキシを作るのは、ぐぐったら今回にぴったりなパッケージが見つかった。
www.npmjs.com

とりあえずindex.jsはこんな感じになった.
最後でexposeしているapiProxyが名前として使われ、URLの一部にもなる。
functions/index.js

const functions = require('firebase-functions');
const proxy = require('express-http-proxy');
const app = require('express')();
const apiHost = 'your.api.example.com' 
app.use('/', proxy(apiHost));
exports.apiProxy = functions.https.onRequest(app);

あとでいろいろ必要になるとも思うけど6行でできてしまった

npmのインストール情報
package.json

]
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "dependencies": {
    "express": "^4.16.3",
    "express-http-proxy": "^1.2.0",
    "firebase-admin": "~5.12.1",
    "firebase-functions": "^1.0.3"
  },
  "devDependencies": {
    "eslint": "^4.12.0",
    "eslint-plugin-promise": "^3.6.0"
  },
  "private": true
}

これで

firebase deploy

して成功すると下記のようなURLが表示されるのでアクセスしてみる

https://{region}-{project-id}.cloudfunctions.net/apiProxy/

まだGKEではDjangoをデバックで動かしているのでこんな感じで出た。
f:id:uyamazak:20180525122837p:plain

ちゃんとつながった。

あとはこれをFirebase Hosting側に設定する

firebase.json

{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/api/**",
        "function": "apiProxy"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint"
    ]
  }
}

/api/**をapiProxyにリライトする設定を追加した。

これをまたdeployして、今度はhosting側のURLに/apiをつけてアクセスしてみる

https://{project-id}.firebaseapp.com/api/

同じくつながってた。
f:id:uyamazak:20180525125206p:plain

こちらは/apiもリクエストに含まれる模様。
とりあえずここまでできることを確認できた。

デメリット

レスポンスが遅くなる

簡略化して書くと

GKE → ロードバランサー → ユーザー
で済んでいたのが

GKE →  ロードバランサー → Firebase Functions → Firebase Hosting → ユーザー
となるので、遅い。

しかもやっかいなのがFirebase Functionsのリージョンは今のところ選択できないため、usになってしまい、さらに遅くなる。

上記のテストでもレスポンスが600ms程度かかっていた。

結果としてこの遅さは致命的なので、今回は使うことはなさそう。

レスポンスの遅さが気にならないものや、何としても同一ドメインで別サーバーで処理をさせたい場合には使い道があるかも。