AWSのLambdaは高機能だが、GCPのCloud Functionsは機能が少なめでシンプル。ただ微妙なクセがある。
目次
Google Cloud FunctionsとCloud Functions for Firebaseの関係
Cloud Functions for FirebaseはGoogle Cloud Functionsを簡素化してFirebaseで使えるようにしたもの
- Node.jsのバージョン8のみ
- Google Cloud Functionsは独自ドメインが使えないが、Cloud Functions for Firebaseでは使える(独自ドメインSSLのWeb APIが可能)。
- 複数のFunctionsを使う構造になっていない(index.jsのみ)→工夫が必要
- 管理画面からのデプロイができない
- Cloud Functions for Firebaseで登録した関数は同じプロジェクトのGCPの管理画面のCloud Functions for Firebaseから確認編集できる。
AWSのLambdaとは違い、Firebase CLIだけでデプロイできるので簡単ではある。
Google Cloud Functionの豆知識とハマるポイント
Firebaseでは関数だけのデプロイが可能
firebase deploy
だけだとHostingなどのデプロイも同時に行ってしまう。
firebase deploy --only functions
とすると無駄なデプロイがなくなる
Cloud Functions for FirebaseではNode.js以外の言語も扱える
Cloud Functions for FirebaseではNode.jsしか使えないと思っている人が多いかもしれないが、実はPythonやGoも使える。Firebase CliからはNode.jsしかデプロイできないが、同じプロジェクトのGCPの画面で関数を登録し、Firebase Hostingのrewritesの設定でその関数を指示すればOK。
my-project/firebase.json
{
:
"hosting": {
:
"rewrites": [{
"source": "/エンドポイント名",
"function": "pythonの関数名"
}],
:
}
:
}
これで
https://独自ドメイン/エンドポイント名
へのHTTPアクセスでGoogle Cloud Functionで登録したPythonの関数が実行できる。
Cloud Functions for FirebaseはGoogle Cloud Functionsの(機能を制限した)ラッパーである。上記のように先にGoogle Cloud FunctionsにPythonの関数を登録して後でFirebaseから参照する使い方もできるし、逆にFirebase側で登録した関数をGCPのGoogle Cloud Functionsの管理画面で操作できる。ログもGCPのStackDriverで扱うことができる。
Cloud Functions for Firebaseのロケーション設定
Cloud Functions for Firebaseのロケーション設定ではデフォルトのus-central1
を使わないほうがちょっとだけいい。
というのもストレージを使う場合にus-central1を選択できないから。us-east1
とus-east4
なら同じロケーションになって転送時間と料金をちょっとだけ節約できる。
Google Cloud Functionではcookieが使えない
Google Cloud Functions(Cloud Functions for Firebaseも)ではセッション以外のcookieが使えない。ブラウザに保存したデータを使うことが不可能で、ITP対策の実装はGoogle Cloud Functionsではできない(AWS Lambdaとの大きな違い)
自ドメイン割り当て時のreq.hostname
Google Cloud Functions for Firebaseで自ドメインを割り当てていても、req.hostnameは自ドメインではなくロケーション名-プロジェクト名.cloudfunctions.net
になる。
Node.jsを使ったときのロギング
標準/エラー出力がStackDriverのログに出ず、ログレベルを設定して簡単に出し分けるlogger.debug()
のようなことするにはbunyanかwinstonが必要。一般的なlog4jsは使えない。
bunyan一式のインストール
cd my-project/functions/
npm install bunyan @google-cloud/logging-bunyan express-bunyan-logger --save
しかしbunyanはexpressとの連携が微妙で、結局WebAPI用途でのログを出すのが難しい。ここがLambdaとの大きな違い。
Google Cloud Functions for Firebaseの細かい使い方
ESLintのインストール
firebase init
でESLintを使う設定にした場合
cd my-project/functions/
npm install eslint eslint-plugin-promise@latest --save-dev
vi package.json
my-project/functions/package.json
"scripts": {
:
"lint": "./node_modules/.bin/eslint ."
},
独自ドメインのエンドポイントを設定する
単純に初期状態(firebase.json
を未編集の状態)でfirebase deploy
から関数をデプロイするだけでは独自ドメインは使えない。まずこの状態では
https://ロケーション名-プロジェクト名.cloudfunctions.net/関数名
が関数のエンドポイントになる。独自ドメインからアクセスできるようにするためにはFirebase Hostingでトラフィックを関数に流すrewritesの設定が必要。
my-project/firebase.json
{
:
"hosting": {
:
"rewrites": [{
"source": "/エンドポイント名",
"function": "関数名"
}],
:
}
:
}
これでデプロイすると
https://独自ドメイン/エンドポイント名
がエンドポイントとして使えるようになる。Firebase HostingがLambdaでいうAPI Gatewayのような役割を果たすわけである。
(この設定をした直後はfunctionsのデプロイだけではなく、hostingのデプロイも必要になる)
Expressを使う
my-project/functions/index.js
が関数を記述するファイルになる
const express = require('express');
const app = express();
//app.use(require('cookie-parser')); // 結局cookie自体使えない
//app.use(require('express-bunyan-logger')({logger})); // うまくいかない
// メインロジック
app.get('/my-method-1', (req, res)=>{
:
});
app.get('/my-method-2', (req, res)=>{
:
});
const functions = require('firebase-functions');
module.exports = functions.https.onRequest(app);
関数ファイルを複数扱う
デフォルトでは関数は1ファイルmy-project/functions/index.js
しか設定できない。そこで一工夫。my-project/functions/src/
というディレクトリを作り、その中に個別の関数のファイルを格納する。
my-project/functions/src/my-func1.js
const functions = require('firebase-functions');
module.exports = functions.https.onRequest((req, res)=>{
:
});
Expressを使ってもいいし、この要領で複数の関数のファイルを配置していく。
my-project/functions/index.js
const funcs = {
func1: './src/my-func1.js',
func2: './src/my-func2.js',
:
}
loadFunctions = (funcsObj) => {
for(let name in funcsObj){
if(! process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
exports[name] = require(funcsObj[name])
}
}
}
console.log('process.env.FUNCTION_NAME:', process.env.FUNCTION_NAME)
loadFunctions(funcs)
console.log('exports:', exports)
定数funcs
で{関数名: ファイル名}
の形式で個別の関数のスクリプトファイルを指定する。
https://uyamazak.hatenablog.com/entry/2018/10/22/113000
関数のURL(パス)を自由に設定する
上記の「Expressを使う」と「関数ファイルを複数扱う」を組み合わせた設定
my-project/functions/index.js
const funcs = {
func1: './src/my-func1.js',
func2: './src/my-func2.js',
:
}
loadFunctions = (funcsObj) => {
for(let name in funcsObj){
if(! process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
exports[name] = require(funcsObj[name])
}
}
}
console.log('process.env.FUNCTION_NAME:', process.env.FUNCTION_NAME)
loadFunctions(funcs)
console.log('exports:', exports)
my-project/functions/src/my-func1.js
const express = require('express');
const app = express();
// メインロジック
app.get('/my-method-1', (req, res)=>{
:
});
app.get('/my-method-2', (req, res)=>{
:
});
const functions = require('firebase-functions');
module.exports = functions.https.onRequest(app);
では
https://ロケーション名-プロジェクト名.cloudfunctions.net/func1/my-method-1
が各メソッドにアクセスするURLとなる。これを独自ドメイン
https://独自ドメイン/apipath/func1/my-method-1
でアクセスできるようにしたい。、まず挿入されたパス/apipath
を扱うには
my-project/functions/src/my-func1.js
const express = require('express');
const app = express();
// メインロジック
app.get('/my-method-1', (req, res)=>{
:
});
app.get('/my-method-2', (req, res)=>{
:
});
const functions = require('firebase-functions');
// ここから先が違う
const main = express();
main.use('/apipath', app); // パス名を入れる
exports.main = functions.https.onRequest(main);
URL処理のロジックを1段階追加する。これで
https://ロケーション名-プロジェクト名.cloudfunctions.net/apipath/func1/my-method-1
でメソッドにアクセスできるようになる。ここで独自ドメインのrewrites設定を追加する。
my-project/firebase.json
{
:
"hosting": {
:
"rewrites": [{
"source": "/apipath/**",
"function": "func1"
}],
:
}
:
}
これで
https://独自ドメイン/apipath/func1/my-method-1
から指定のメソッドを呼び出すことができる。
https://stackoverflow.com/questions/44959652/firebase-hosting-with-dynamic-cloud-functions-rewrites
GCP/Firebase の記事一覧