Google Cloud Functions(Beta)の基本的な仕組み、使い方を学ぶ

# 概要

Google Cloud Functions(Beta)の基本的な仕様や使い方を、オフィシャルドキュメントの入門ガイド、チュートリアルをもとに学習したまとめの記事です。
下記に引用した通りGoogle Cloud Functionsはベータ版(2018年4月現在)です。将来のリリースでこの記事の内容が役に立たなくなるかもしれませんのでご注意ください。

>★ Beta
これは、Google Cloud Functionsのベータ版リリースです。このAPIは、下位互換性のない方法で変更される可能性があり、SLAサポート終了予定ポリシーの対象ではありません。

**参考**

* [Google Cloud Functionsに関するドキュメント] (https://cloud.google.com/functions/docs/?hl=ja)

# Google Cloud Functionsの概要

## 関数の実行環境

~~Google Cloud Functionsで利用しているNode.jsのバージョンは下記に引用したとおり`v6.11.5`です。~~

> ~~The Cloud Functions Node.js execution environment follows the Node "LTS" releases, starting with the v6 
LTS release published on 2016-10-18. The current Node.js version running in Cloud Functions is Node v6.11.5.~~

Google Cloud Functionsの実行環境としてNode.js v6とNode.js v8およびPythonがサポートされました。

> [Cloud Functions Execution Environment] (https://cloud.google.com/functions/docs/concepts/exec?hl=ja)
> 
> Cloud Functions supports multple languages and runtimes. Updates to the language runtimes are generally done automatically (unless otherwise notified), and include any changes in the base image's definition. For more information, see the runtimes pages for Node.js 6, Node.js 8, or Python.

[Node.js Foundation Release Working Group] (https://github.com/nodejs/Release)

### 実行時サービスアカウント

[Cloud Functions > Documentation > Access Control] (https://cloud.google.com/functions/docs/concepts/iam?hl=ja)

関数は次のサービスアカウントの権限で実行されます。(関数を作成すると自動的にサービスアカウントが作成されます。)
このサービスアカウントはデフォルトではプロジェクトの編集者というロールを持っていますが、最適な権限を割り当てることもできます。

```text
PROJECT_ID@appspot.gserviceaccount.com
```

#### Google APIサービスアカウント

実行時サービスアカウントの他に、関数の作成、更新、削除を行うためのサービスアカウントが作成されます。
このサービスアカウントの権限は基本的に編集しません。

```text
PROJECT_NUMBER@cloudservices.gserviceaccount.com
```

## 関数の種類

Google Cloud Functionsの関数には、HTTP関数とバックグラウンド関数という2つの種類があります。
それぞれの関数には以下のような特徴があります。

* HTTP関数
    * HTTPリクエストをトリガーとします
    * 関数の実行は同期的でレスポンス＝関数の実行終了です
* バックグラウンド関数
    * Cloud Pub/SubやCloud Storageなどのインフライベントをトリガーとします
    * 関数の実行は非同期的で処理終了の通知はコールバック関数を使用します

### [HTTP関数] (https://cloud.google.com/functions/docs/writing/http?hl=ja)

HTTP関数はトリガーとなるURLにHTTPリクエストが起きたときに呼び出されます。

関数はrequestとresponseの2つのパラメータを取ります。

```js
exports.helloHttp = (request, response) => {

  // 処理

  response.status(200).end();
};
```

#### HTTPリクエスト ([HTTP Triggers] (https://cloud.google.com/functions/docs/calling/http?hl=ja))

##### デプロイコマンド

```text
gcloud beta functions deploy [FUNCTION_NAME] --trigger-http
```

**--trigger-http**

関数にエンドポイント(HTTPトリガーURL)が割り当てられます。

**HTTPトリガーURL**

デプロイ後にdescribeコマンドで確認できます。

```text
> gcloud beta functions describe [FUNCTION_NAME]
```

##### 関数呼び出しの例

HTTPトリガーURLに対してHTTPリクエスト(GET,POST,PUT,DELETE,OPTIONS)を発行します。
この例はcurlを使ってHTTPトリガーURLへPOSTリクエストを行います。

```text
curl -X POST -H "Content-Type:application/json" -d "{\"message\": \"hello world!\"}" "HTTP_TRIGGER_URL"
```

### [バックグラウンド関数] (https://cloud.google.com/functions/docs/writing/background?hl=ja)

バックグラウンド関数は、Cloud Pub/Subのトピックへメッセージをパブリッシュしたときや、Cloud Storageのバケットにファイルをアップロードしたときに呼び出されます。

関数はeventとオプションのcallbackの2つのパラメータを取ります。

```js
exports.helloBackground = (event, callback) => {

  // 処理

  callback();
};
```

eventオブジェクトは次の2つのプロパティを持っています。

|property      |description        |保持するデータ    |
|:-------------|:------------------|:---|
|event.data    |イベントデータ      |Pub/Subではメッセージ、Storageではオブジェクトのデータ |
|event.context |イベントコンテキスト |イベントIDやイベントタイプなどのメタデータ |

#### Cloud Pub/Subのトピック ([Cloud Pub/Sub Triggers] (https://cloud.google.com/functions/docs/calling/pubsub?hl=ja))

##### デプロイコマンド(1)

```text
> gcloud beta functions deploy [FUNCTION_NAME] --trigger-topic=[TRIGGER_TOPIC]
```

**--trigger-topic=[TRIGGER_TOPIC]**

実行トリガーとするCloud Pub/Subトピックの名を指定します。 このトピックへパブリッシュされたメッセージは、関数のパラメータに渡されます。

##### デプロイコマンド(2)

```text
> gcloud beta functions deploy [FUNCTION_NAME] --trigger-resource [TRIGGER_TOPIC] --trigger-event [EVENT_TYPE]
```

**--trigger-resource [TRIGGER_TOPIC]**

実行トリガーとするCloud Pub/Subのトピック名を指定します。

**--trigger-event [EVENT_TYPE]**

[EVENT_TYPE]に指定できるのは（いまのところ）次の1種類だけなので、上記のデプロイコマンド(1)とデプロイコマンド(2)は同じ結果になります。

|event_type                           |イベント             |
|:------------------------------------|:-------------------|
|google.pubsub.topic.publish          |トピックにメッセージがパブリッシュされたとき |

##### 関数呼び出しの例

関数デプロイ時に実行トリガーとして指定したトピックへパブリッシュすることで関数を呼び出します。

```text
> gcloud beta pubsub topics publish [TOPIC_NAME] --message "なんらかのメッセージ"
```

#### Cloud Storageのオブジェクト ([Cloud Storage Triggers] (https://cloud.google.com/functions/docs/calling/storage?hl=ja))

##### デプロイコマンド(1)

```text
> gcloud beta functions deploy [FUNCTION_NAME] --trigger-bucket=[TRIGGER_BUCKET]
```

**--trigger-bucket=[TRIGGER_BUCKET]**

実行トリガーとするCloud Storageのバケット名を指定します。このバケット内のファイルが変更されるたびに関数が実行されます。

##### デプロイコマンド(2)

```text
> gcloud beta functions deploy [FUNCTION_NAME] --trigger-resource [TRIGGER_BUCKET] --trigger-event [EVENT_TYPE]
```

**--trigger-resource [TRIGGER_BUCKET]**

実行トリガーとするCloud Storageのバケット名を指定します。

**--trigger-event [EVENT_TYPE]**

[EVENT_TYPE]に指定できるイベントは次の通りです。

|event_type                           |イベント             |
|:------------------------------------|:-------------------|
|google.storage.object.finalize       |バケットに新しいオブジェクトが作成されたとき |
|google.storage.object.delete         |バケットのオブジェクトが削除されたとき |
|google.storage.object.archive        |バケットのオブジェクトがアーカイブされたとき |
|google.storage.object.metadataUpdate |バケットのオブジェクトのメタデータが更新されたとき |

* オブジェクトのバージョニングの設定によって挙動が変わります。

##### 関数呼び出しの例

関数デプロイ時に実行トリガーとして指定したバケットのオブジェクトを操作（コピー、削除など）することで関数を呼び出します。
この例はローカルPCからgsutilツールを使ってファイルをバケットへコピーしています。

```text
> gsutil cp test.txt gs://[BUCKET_NAME]
```

## [Direct Triggers] (https://cloud.google.com/functions/docs/calling/direct?hl=ja)

callコマンドでHTTP関数やバックグラウンド関数をイベントを介さずに直接呼び出すことができます。
このコマンドは開発時の動作確認やデバッグ用途に使うためのものです。

### 関数の呼び出し

```text
> gcloud beta functions call [FUNCTION_NAME] --data "{\"message\": \"hello world!\"}"
```

**--data**

JSON文字列を指定します。
HTTP関数の場合はrequestオブジェクトのbodyプロパティにバインドされます。
バックグラウンド関数の場合はeventオブジェクトのdataプロパティにバインドされます。


# 実行環境のセットアップ

## ローカルPC側のセットアップ

Cloud SDKをアップデートします。

```text
> gcloud components update

> gcloud components install beta
```

Node.jsの開発環境を準備します。

[ドキュメント > Node.js > Node.js 開発環境のセットアップ] (https://cloud.google.com/nodejs/docs/setup?hl=ja)

GCP側のNode.jsのバージョンは6.11.5のようなので、ローカルPC側もバージョンを揃えました。
私の環境では既にnodistをインストールしていたのでnodistを利用していますが、ドキュメントではnvm(Windowsはnvm-windows)を利用する方法が紹介されています。

```text
> nodist + 6.11.5

> nodist 6.11.5
```

```text
> npm install --save express
```

## GCP側のセットアップ

Cloud Functions APIを有効にします。

![cf0.png](https://qiita-image-store.s3.amazonaws.com/0/22772/ec2abb88-40cb-3f51-55c4-279433f3199d.png)


# HTTP関数を実行するチュートリアル

[Cloud Functions > Documentation > HTTP Tutorial] (https://cloud.google.com/functions/docs/tutorials/http?hl=ja)

Cloud Functions APIを有効にしたら、続けてHTTP関数を作成しローカルから呼び出すまでのチュートリアルを行います。

![cf1.png](https://qiita-image-store.s3.amazonaws.com/0/22772/de9b6cd3-c72d-6561-fa71-e6aa2784f893.png)

下の図は「関数を作成」ボタンをクリックした後の画面です。

* 関数の名前を”helloWorld”、使用するメモリを最小の128MBに変更しました。
* トリガーは”HTTPトリガー”を指定します。
* 画面に表示されているURLはこの関数のエンドポイント(HTTPトリガーURL)で、このエンドポイントにHTTPリクエストを発行する関数が実行されます。

表示されているソースコードはデフォルトのもので、この画面でソースコードを変更できますがこのチュートリアルではそのまま使用しました。

![cf2b.png](https://qiita-image-store.s3.amazonaws.com/0/22772/9cb41099-a4e1-f379-3686-48cbb4910628.png)

関数が作成されると一覧に表示されます。これで関数を実行する準備ができたのでローカルPCから呼び出してみます。

![cf4.png](https://qiita-image-store.s3.amazonaws.com/0/22772/d0a67216-7bb0-9715-9989-923c9a3c7cbc.png)

### 関数を実行

curlでhttpトリガーにアクセスすると関数が実行され"Success: hello world!"というレスポンスが表示されます。
下記の結果はローカルPCより実行したものです。

```text
> curl -X POST -H "Content-Type:application/json" -d "{\"message\": \"hello world!\"}" "https://us-central1-project-********.cloudfunctions.net/helloWorld"
Success: hello world!
```

gcloudツールで関数を直接呼び出した場合です。

```text
> gcloud beta functions call helloWorld --data="{\"message\": \"hello world\"}"
executionId: sa2mbsm8xv0c
result: 'Success: hello world'
```

### 関数の詳細

GCP Consoleの「関数の詳細」画面で関数の実行状況を確認することができます。
また「ソース」タブの画面からは関数のソースコードをzipファイルでダウンロードすることができます。

![cf5.png](https://qiita-image-store.s3.amazonaws.com/0/22772/7c2d6d2c-1e9d-7249-c4cb-9e7b9d310b79.png)

### gcloud beta functionsコマンドで操作する

[gcloud beta functions] (https://cloud.google.com/sdk/gcloud/reference/beta/functions/?hl=ja)

ソースコードをダウンロードし、少し修正してからgcloudツールでデプロイをしてみます。
下記はダウンロードしたzipファイルをhello_worldというディレクトリに展開した結果です。

```text
/hello_world
  |
  +--- index.js
  |
  +--- package.json
```

index.jsを下記のように修正してGETでもレスポンスを返せるようにしました。

```js
function handle(message, res) {
  if (message === undefined) {
    res.status(400).send('No message defined!');
  } else {
    console.log(message);
    res.status(200).send('Success: ' + message);
  }
}

/**
 * Responds to any HTTP request that can provide a "message" field in the body.
 *
 * @param {!Object} req Cloud Function request context.
 * @param {!Object} res Cloud Function response context.
 */
exports.helloWorld = (req, res) => {
  switch (req.method) {
    case 'GET':
      handle(req.query.message, res);
      break;
    case 'POST':
      handle(req.body.message, res);
      break;
    default:
      res.status(500).send({ error: 'Something blew up!' });
      break;
  }
};
```

#### functionをデプロイする

hello_worldディレクトリで次のコマンドを実行しデプロイを行います。
デプロイ時に引数を指定して ―たとえば使用メモリ量(e.g. `--memory=256MB`)やタイムアウト時間(e.g. `--timeout=300s`)など― 関数の設定を変更することも可能です。

```text
> gcloud beta functions deploy helloWorld --verbosity debug
DEBUG: Running [gcloud.beta.functions.deploy] with arguments: [--verbosity: "debug", NAME: "helloWorld"]
INFO: Refreshing access_token
INFO: Not using a .gcloudignore file.
INFO: Not using a .gcloudignore file.
Deploying function (may take a while - up to 2 minutes)...done.
INFO: Display format: "default"
availableMemoryMb: 128
entryPoint: helloWorld
httpsTrigger:
  url: https://us-central1-project-********.cloudfunctions.net/helloWorld
labels:
  deployment-tool: cli-gcloud
name: projects/project-********/locations/us-central1/functions/helloWorld
serviceAccountEmail: project-********@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/<省略...>
status: ACTIVE
timeout: 60s
updateTime: '2018-03-30T12:27:11Z'
versionId: '2'
```

#### 関数の情報を確認する

functions listコマンドで関数の一覧を確認

```text
> gcloud beta functions list
NAME        STATUS  TRIGGER       REGION
helloWorld  ACTIVE  HTTP Trigger  us-central1
```

functions describeコマンドで関数の詳細を確認

```text
> gcloud beta functions describe helloWorld
availableMemoryMb: 128
entryPoint: helloWorld
httpsTrigger:
  url: https://us-central1-project-********.cloudfunctions.net/helloWorld
labels:
  deployment-tool: cli-gcloud
name: projects/project-********/locations/us-central1/functions/helloWorld
serviceAccountEmail: project-********@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/<省略...>
status: ACTIVE
timeout: 60s
updateTime: '2018-03-30T12:27:11Z'
versionId: '2'
```

#### 動作確認

修正した通りにGETでもレスポンスが返るか確認します。

```text
> curl "https://us-central1-project-********.cloudfunctions.net/helloWorld?message=HELLO%20WORLD!"
Success: HELLO WORLD!
```

#### 関数を削除する

functions deleteコマンドで関数を削除できます。

```text
> gcloud beta functions delete helloWorld
```


# バックグラウンド関数を実行するチュートリアル

## Google Cloud Storageを利用するチュートリアル

[Cloud Functions > Documentation > Cloud Storage Tutorial] (https://cloud.google.com/functions/docs/tutorials/storage?hl=ja)

このチュートリアルではGoogle Cloud Storageを利用します。

## Google Cloud Storage JSON APIの有効化

このAPIはデフォルトで有効になっています。

## バケットの作成

イベントのトリガーになるバケットを用意します。

## バックグラウンド関数の作成

* 関数の名前を”processFile”、使用するメモリを最小の128MBに変更しました。
* トリガーは”Cloud Storage バケット”を指定します。
* 画面にイベントタイプとバケットを選択するリストが表示されます。
    * イベントタイプはファイルが作成されたときのイベントの”ファイナライズ / 作成”を選択しました。
    * バケットには作成したバケットを選択しました。

表示されているソースコードはデフォルトのものです。関数の最後に必ずコールバック関数(この例では`callback();`)を実行する必要があります。

![cf7.png](https://qiita-image-store.s3.amazonaws.com/0/22772/79998264-1b1e-1bb5-3f2a-3316ca914449.png)

関数が作成されると一覧に表示されます。これで関数を実行する準備ができたのでバケットにファイルをアップロードします。

![cf8.png](https://qiita-image-store.s3.amazonaws.com/0/22772/132e340a-1697-0463-a3ae-58a83ed066bd.png)

ファイルのアップロード後にログを確認します。
ログにアップロードしたファイルが出力されていることが確認できました。

![cf9.png](https://qiita-image-store.s3.amazonaws.com/0/22772/e3b8c073-0027-adce-e2d7-1e885f93130e.png)

関数の処理が失敗したことを通知するには、callback関数の第1引数にErrorオブジェクトを渡します。

```js
callback(new Error("processing failed!"));
```

![cf11.png](https://qiita-image-store.s3.amazonaws.com/0/22772/17dbafe7-af99-f3d9-e09a-27e08a0c1899.png)


## Google Cloud Pub/Subを利用するチュートリアル

[Cloud Functions > Documentation > Cloud Pub/Sub Tutorial] (https://cloud.google.com/functions/docs/tutorials/pubsub?hl=ja)

このチュートリアルではGoogle Cloud Pub/Sub APIを利用します。

> Google Cloud Pub/Subはフルマネージドのリアルタイム メッセージング サービスで、個別のアプリケーション間でメッセージを送受信できます。

## Cloud Pub/Sub APIの有効化

Cloud Pub/Sub APIを有効化します。

## トピックの作成

イベントのトリガーになるトピックを用意します。

![cf12.png](https://qiita-image-store.s3.amazonaws.com/0/22772/1e5801b3-ed68-2b8f-26e6-c6f75b68273e.png)

![cf13.png](https://qiita-image-store.s3.amazonaws.com/0/22772/51f2d4dd-dab9-9132-5d5b-4e7b5d6547cf.png)

## バックグラウンド関数の作成

* 関数の名前を”subscribe”、使用するメモリを最小の128MBに変更しました。
* トリガーは”Cloud Pub / Sub トピック”を指定します。
* 画面にトピックを選択するリストが表示されます。

表示されているソースコードはデフォルトのものです。関数の最後に必ずコールバック関数(この例では`callback();`)を実行する必要があります。

![cf14.png](https://qiita-image-store.s3.amazonaws.com/0/22772/d8ace891-37fb-6a01-ba1c-def62433867d.png)

関数が作成されると一覧に表示されます。これで関数を実行する準備ができたのでトピックにメッセージをパブリッシュします。

![cf15.png](https://qiita-image-store.s3.amazonaws.com/0/22772/4f2d4f1a-335d-4485-ec16-e59295600ac5.png)

メッセージのパブリッシュ後にログを確認します。
ログにメッセージが出力されていることが確認できました。

![cf16.png](https://qiita-image-store.s3.amazonaws.com/0/22772/18f5bb80-8cb6-32f6-7eb7-3d5e82140db5.png)


gcloudツールで関数を直接呼び出した場合です。
メッセージはbase64エンコードされている必要があります。下記のメッセージは"hello world"をエンコードしたものです。

```text
> gcloud beta functions call subscribe --data="{\"data\": \"aGVsbG8gd29ybGQ=\"}"
executionId: 7fjpocvlean9
```
![cf17.png](https://qiita-image-store.s3.amazonaws.com/0/22772/1cfb1c1c-2eef-81b7-7e3a-8b54aea0ba59.png)