Adjust のリアルタイムコールバックを Cloud Logging に流す

# 1. はじめに
Adjust のリアルタイムコールバックを利用すると、 Adjust の計測結果を指定のサーバーに送信する事ができます。
この記事では、 Adjust のデータを Cloud Functions に送信して Cloud Logging へ流すための設計をまとめます。


# 2. アーキテクチャ
Adjust でリアルタイムコールバックを利用する際、受け口となるサーバーが必要となります。
今回は Cloud Logging へ送る事が目的なので、同じ GCP の Cloud Functions を使います。

アーキテクチャの完成予想図はこちらです。
![adjust-architect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66083/74c86b93-98e3-d6eb-84f6-efe1ff68ee10.png)


# 3. サーバーを建てる

アーキテクチャで記述した通り、サーバーには Cloud Functions を利用します。
以下のパラメータで Cloud Functions を作成します。

__Cloud Functions の設定__

|項目|設定内容|
|:--:|:--:|
|関数名|adjust-server|
|リージョン|asia-northeast1|
|トリガーのタイプ|HTTP|
|認証|認証が必要|
|ランタイム|Go 1.13|
|エントリポイント|GetRawData|

Cloud Functions は複数言語のランタイムに対応していますが、今回は Go 1.13 で実行します。
ログの出力は構造を変更しやすいように [uber-go/zap](https://github.com/uber-go/zap) を利用します。

``` go
package p

import (
	"fmt"
	"net/http"

	"go.uber.org/zap"
)

var zlogger *zap.Logger

func init() {
	var err error
	zlogger, err = zap.NewProduction()
	if err != nil {
		fmt.Printf("request parse failed. %v", err)
		return
	}
}

func GetRawData(_ http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		fmt.Printf("request parse failed. %v", err)
		return
	}

	fields := make([]zap.Field, 0, len(r.Form)+1)
	fields = append(fields, zap.Namespace("body"))

	for k, v := range r.Form {
		fields = append(fields, zap.String(k, v[0]))
	}

	zlogger.Info("AdjustLogger", fields...)
}
```

デプロイが完了したらトリガーURLを叩いてみましょう。

```
$ curl https://asia-northeast1-${project_id}.cloudfunctions.net/adjust-server
```
クエリパラメータを追加して叩くと、パラメータ情報もログとして出力されます。
​

# 4. Adjust のリアルタイムコールバック設定
Adjust 側で行う設定は以下の公式ドキュメントに書かれています。
[データの管理 > ローデータエクスポート > リアルタイムコールバック](https://help.adjust.com/ja/article/callbacks)

抜粋すると、以下の手順で設定できます。

>
① 該当アプリの下部に表示されている（▲）ボタンを選択します。
② その他の設定 (All Settings)​> ローデータエクスポート (Raw Data Export)​ > リアルタイムコールバック (Real-Time Callbacks)​を選択します。
③ グローバルコールバック（GLOBAL CALLBACK）​を選択する。
④ コールバックURLを入力します（コールバックURLのテンプレートおよび推奨のプレースホルダーを参照）。 
⑤ UPDATE​を選択します。

今回は例としてグローバルコールバックを使用します。
コールバックURLは、先ほど作成した Cloud Functions のトリガーURLに必要なクエリパラメータを追加したURLになります。

```
https://asia-northeast1-${project_id}.cloudfunctions.net/adjust-server?app_id={app_id}&app_name={app_name}
```

app_id, app_name 以外の情報が欲しい場合は [Adjust Placeholders](https://partners.adjust.com/placeholders/) にプレースホルダーのドキュメントがあるので、必要なクエリパラメータを探して設定して下さい。
​
コールバックURLが設定できたら Adjust のイベントを発火させてみましょう。
正しく疎通できていれば以下のようなログが Cloud Logging に出力され、 `jsonPayload.body` の中にプレースホルダーの情報が表示されます。

``` json
{
	"insertId": "000000-...",
	"jsonPayload": {
		"msg": "AdjustLogger",
		"body": {
			"app_id": "foo",
			"app_name": "bar"
		},
		"level": "info",
		"caller": "serverless_function_source_code/function.go:33",
	},
	"resource": {
		"type": "cloud_function",
		"labels": {
			"project_id": "xxx",
			"function_name": "adjust-server",
			"region": "asia-northeast1"
		}
	},
	...
}
```


# 5. IP 制限をかける
これまでの設定で、 Adjust から送られるリアルタイムコールバックの情報を Cloud Logging に流すことができました。
ただ、今のままだと外部から指定のURLに直接アクセスできるので Adjust 以外の情報が混入する可能性があります。

この問題を解決するため、 Cloud Armor で Adjust の IP をホワイトリストに設定します。

### Cloud Functions の設定を変更する
まずは作成した Cloud Functions リソースを外部からアクセスできないようにします。

adjust-server の編集を行い、
`ランタイム、ビルド、接続の設定` > `接続` > `内部トラフィックと Cloud Load Balancing からのトラフィックを許可する`
を設定して更新しましょう。

正しく更新されると、トリガーAPIを叩く際に403エラーが返るようになります。


### Cloud Load Balancing を作成する
次に、 Cloud Load Balancing を作成します。

以下の手順でロードバランサーの設定画面に移動します。
`Cloud Load Balancing` > `ロードバランサを作成` > `HTTP(S)負荷分散`

インターネット接続の設定は `インターネットから自分のVMへ` を選択します。
ロードバランサーの名前は 「adjust-server-lb」 としましょう。

Cloud Load Balancing を作成するためには、バックエンドとフロントエンドの設定をする必要があります。また、今回は Cloud Functions のエンドポイントを設定するため、サーバーレス NEG を設定する必要があります。
バックエンド、フロントエンド、サーバーレス NEG を以下のように設定し、 Cloud Load Balancing を作成します。

__バックエンドの設定__

|項目|設定内容|
|:--:|:--:|
|名前|adjust-server-backend|
|バックエンドタイプ|サーバーレスネットワークエンドポイントグループ|
|プロトコル|HTTP|
|タイムアウト|30秒|
|バックエンド|adjust-server-neg (新規作成)|
|セキュリティ|なし|


__サーバーレス NEG の設定__

|項目|設定内容|
|:--:|:--:|
|名前|adjust-server-neg|
|リージョン|asia-northeast1|
|サーバーレスネットワークエンドポイントグループの種類|Cloud Functions|
|関数名を選択|adjust-server|

__フロントエンドの設定__

|項目|設定内容|
|:--:|:--:|
|名前|adjust-server-frontend|
|プロトコル|HTTP|
|ネットワークサービス階層|プレミアム|
|IPバージョン|IPv4|
|IPアドレス|エフェメラル ( or 静的IPアドレス)|
|ポート|80|

Cloud Load Balancing リソースが作成されたら、作成された IP アドレスにアクセスしてみましょう。
Cloud Functions のトリガーURLにアクセスした時と同様にログが出力されていればOKです。

```
$ curl http://${lb_ip_address}
```

### Cloud Armor のポリシーを作成する
最後に、 Cloud Armor で Adjust の IP アドレスをホワイトリストに登録します。

`Cloud Armor` > `ポリシーを作成` から、ポリシーの設定を行います。
Adjust の IP アドレスは以下の公式ドキュメントを参照してください。
[参考資料 > リスト > Adjustサーバー IP](https://help.adjust.com/ja/article/adjust-server-ips)

__ポリシーの設定__

|項目|設定内容|
|:--:|:--:|
|名前|adjust-server-policy|
|デフォルトのルールアクション|拒否|
|拒否ステータス|403 (アクセス拒否)|
|関数名を選択|adjust-server|

__ルールの追加__

|項目|設定内容|
|:--:|:--:|
|モード|基本モード|
|一致|x.x.x.x/24,x.x.x.x/24,...|
|アクション|許可|
|優先度|0|
※ IPアドレスは最大で10個までしか指定できないので、複数のルールを追加します。
※ 優先度は重複できないので、新しいルールを追加する際は優先度をインクリメントしてください。

__ターゲットへのポリシーの適用__

|項目|設定内容|
|:--:|:--:|
|タイプ|ロードバランサ バックエンドサービス|
|ターゲット|adjust-server-backend|

ポリシーが作成されたら、ロードバランサーのセキュリティ設定にadjust-server-policyを追加します。

`Cloud Load Balancing` から adjust-server-lb の編集画面に遷移します。
`バックエンドの構成` から adjust-server-backend を選択し、 `セキュリティ` に adjust-server-policy を設定します。

これで Cloud Functions へのアクセスを Adjust の IP からのみ許可する設定ができました。

### Adjust のコールバックURLを変更する
各リソースの作成が完了したら、 Adjust のリアルタイムコールバックの設定を更新しましょう。

```
http://${lb_ip_address}?app_id={app_id}&app_name={app_name}
```

今回は省略していますが、運用する際は以下の設定もやっておくと良いでしょう。

* Cloud DNS を使ってドメインを設定する
* 静的 IP を取得してロードバランサーのフロントエンドに設定する
* SSL/TLS 通信の設定


# 6. まとめ
Adjust のデータを Cloud Functions に送信して Cloud Logging へ流すための設計をまとめました。
簡易的に作るだけであれば Cloud Functions を使うだけで良いですが、実戦運用する際には IP 制限や負荷分散を考慮する必要があるので、 Cloud Load Balancing や Cloud Armor を併用する設計が良いと思います。