Google Cloud Functionsとは
- HTTPリクエスト(POST,PUT, GET, DELETE, OPTIONS)
- GCS の状態変更
- Cloud Pub / Sub
をトリガーにして、事前に作成しておいたスクリプトを実行できます。
(ちなみにFirebaseにも対応したCloud Functions for Firebaseでは、上記に加え以下のものもトリガー可能です。データベースへのwrite、所持ドメイン内の任意パスへのアクセス、ユーザの作成・削除、アナリティクスのイベント、モバイルアプリのクラッシュ)
環境構築
- Cloud Functionsのインストール
- ここを参考に。
- gcloudからbetaのcomponents をinstall
gcloud components update beta
- Emulatorインストール
- ここを参考に。
- nodeはnodebrewとかで入れたほうがいいかも。
- Emulatorのインストールが完了したら、consoleで
which functions
できることを確認。
- 注意
- 2017/12現在、Google Cloud Functionsはnode v6.11.5しか対応していないので注意。
- 2018/8現在、Google Cloud Functionsはnode v8に対応しました。v8では関数の引数が異なるのでご注意ください。 https://cloud.google.com/functions/docs/writing/background#functions_background_parameters-node8
簡単なサンプルをローカルで動かす
- HTTP Trriger
- 以下のようなjsを用意し、コマンドを実行
exports.helloWorld = (req, res) => res.send("Hello, World!");
functions start
functions deploy helloWorld --trigger-http
functions call helloWorld
- EmulatorでホストされているFunctionsを確認
functions list
- GCS Trriger
- 以下のようなjsを用意し、コマンドを実行
exports.index = function (event, callback) {
const file = event.data;
if (file.resourceState === 'not_exists') {
console.log('File ' + file.name + ' not_exists.');
} else {
if (file.metageneration === '1') {
// この判定を入れることで、ファイルの新規作成なのかそれ以外(変名、削除)なのかの
// 判別が可能です。
console.log('File + ' + file.name + ' created.');
// ファイルが作成されたらやりたい処理 //
} else {
console.log('File + ' + file.name + ' metadata updated.');
}
}
callback();
};
functions start
functions deploy index --trigger-bucket=my-bucket-project.appspot.com
functions call index --data='{"name":"upload.zip", "bucket":"my-bucket-project.appspot.com", "metageneration": "1"}'
ちなみに、GCSのBucket内にディレクトリ構造を持たせている場合、
以下のようにして指定すれば再現可能です。
functions call index --data='{"name":"functions_files/upload.zip", "bucket":"my-bucket-project.appspot.com", "metageneration": "1"}'
なお、Deployのtargetはindex.js
内でexportsしているmodule名称で指定します。
index.js
内に複数のexportしても、それぞれDeployすれば問題ありません。
逆にindex.js
以外の名称のjsを読み込むことはできなさそうです。
実行結果のログは functions logs read
で確認できます。
GoogleCloudStrage, CloudFunctions, サーバーサイドAPIの連携
まずはStorageの準備
GCSに適当なバケットを作成し、そこに適当なファイルを配置。
以下はサンプル。
- GCP Project
- sample-project
- バケット
- artifacts.sample-project.appspot.com
- 階層
- functions_files /
- test.zip
- functions_files /
Cloud Functionsでhookされるjs
- このjsでは、バックエンドサーバーへPOSTリクエストを行います。
- パラメータとして、ファイル名とバケット名を渡します。
var request = require('request');
exports.index = function (event, callback) {
const file = event.data;
if (file.resourceState === 'not_exists') {
console.log('File ' + file.name + ' not_exists.');
callback();
return
} else {
if (file.metageneration === '1') {
var data = {
'file_name': file.name,
'bucket_name': file.bucket
};
var options = {
url: 'http://localhost:8000',
method: 'POST',
headers: {'Content-Type':'application/json'},
json: data
};
request(options, function(err, response, body) {
if (response && response.statusCode === 200 && body) {
console.log('backend request success.');
} else {
console.log('backend error.');
}
callback();
});
} else {
console.log('File + ' + file.name + ' metadata updated.');
callback();
return
}
}
};
バックエンドAPI
- バックエンドAPIでは、
localhost:8000
にてPythonサーバーを起動して、パラメータで指定されたファイルをGCSから取得してローカルサーバーに保存します。
# -*- coding: utf-8 -*-
import json
from bottle import request, HTTPResponse
from google.cloud import storage
file_types = {
'zip' : 'application/zip'
}
class Sample():
@classmethod
def main(cls):
try:
file_name = request.json.get('file_name')
bucket_name = request.json.get('bucket_name')
if '.' in file_name:
file_ext = file_name.split('.')[len(file_name.split('.')) - 1]
else:
file_ext = ''
result, validated_result = get_validated_file(file_name, bucket_name, file_ext)
if result:
body = { 'result': 'ok' }
else:
body = { 'result': validated_result }
# レスポンスを返却する
response = HTTPResponse(status=200, body=json.dumps(body))
response.set_header('Content-Type', 'application/json')
return response
def get_file(file_name, bucket_name):
"""GCSからファイルを取得
"""
storage_client = storage.Client(project='sample-project')
bucket = storage_client.get_bucket(bucket_name)
return bucket.get_blob(file_name)
def is_accept_type(file, file_ext):
"""想定ファイルかどうかチェック
"""
if file_ext not in file_types:
return False
else:
return file.content_type == file_types[file_ext]
def get_validated_file(file_name, bucket_name, file_ext):
"""対象ファイルのバリデーション
:return: バリデーションの成否, バリデーション結果(成功ならファイルパス)
"""
file = get_file(file_name, bucket_name)
if not file or not file.exists():
return False, 'is not exist'
if not is_accept_type(file, file_ext):
return False, 'content_type'
outfile_name = '/hoge/hoge/text.zip'
# GCSのBlobオブジェクトをローカルのファイルオブジェクトとして保存
file.download_to_filename(outfile_name)
return True, outfile_name
CloudFunctions Emulatorで試す
functions deploy index --trigger-bucket=artifacts.sample-project.appspot.com
functions call index --data='{"name":"functions_files/test.zip", "bucket":"artifacts.sample-project.appspot.com", "metageneration": "1"}'
あらかじめ localhost:8000
でPythonサーバーを起動しておき、上記コマンドでfunctionsをローカルから叩いてPythonへリクエストが通れば成功。
Python側で指定ファイルがGCSに存在するかなどのチェックを行っているため、無い場合はちゃんとエラーが返ります。
これで動作確認が出来れば、GCSにファイル配置をトリガーにしてマスタインポートなどの処理が可能となります。
GCP上にCloudFunctionsをデプロイする
- index.js があるディレクトリで以下のコマンドを実行すればDeploy可能です。
gcloud beta functions deploy index --stage-bucket test-stage-bucket --trigger-bucket=artifacts.sample-project.appspot.com
※GCFはバケットに展開されるため、stage-bucket はあらかじめ作成しておくこと。
上記にてDeploy完了したら、以後GCSをトリガーにしてFunctionsが実行されます。