Stackdriver Debugger で簡単に本番環境をデバッグする

# はじめに

本番環境にデプロイしたらバグが出て、デバッグしたいけど本番環境のソースコードにログを仕込んで再起動を繰り返さなきゃみたいなこと、経験したことないでしょうか。
本番環境にブレークポイントや変数を見るためにログを仕込む、そんなことを簡単に行えるのが Stackdriver Debugger です。
今回は Node.js を使って、Google Compute Engine（GCE）にデプロイして Stackdriver Debugger がどんな風に使えるか試してみます。
本記事の執筆時点（2019/03）では、Node.js の Stackdriver Debugger エージェントがベータ版です。
変更になる可能性があることに注意してください。

# 環境

- macOS High Sierra Version 10.13.6
- Node.js v10.15.3
- npm 6.9.0
- Google Cloud SDK 240.0.0
- express 4.16.4
- @google-cloud/debug-agent 3.1.0

# Stackdriver Debugger とは

Stackdriver Debugger は、実行中のアプリケーションに対して現時点のアプリケーションの状態を確認できるサービスです。
アプリケーションの処理速度は低下せず、ユーザーに影響しないと言われています。
用途としては、本番でしか再現しないコードを調査したい場合などに大きな威力を発揮します。

# Google Cloud SDK をインストールする

以下のドキュメントを参考に Google Cloud SDK をインストールし、プロジェクトの設定をしてください。
[Google Cloud SDK documentation](https://cloud.google.com/sdk/docs/)

# サンプルアプリケーションの作成

Node.js が事前にインストールされている前提で話を進めます。
インストールされていない場合は、事前に Node.js のインストールを実施してください。

まずは、作成するサンプルアプリケーションを Git で管理します。
今回は、GCP で完結させるために Cloud Source Repositories を利用します。
バージョン管理システムとしては、Cloud Source Repositories の他に GitHub や Bitbucket、GitLab などに対応しています。
それではリポジトリを作成してローカルに clone します。
サンプルとして `stackdriver-debugger-sample` という名前で作成しています。

```bash
$ gcloud source repos create stackdriver-debugger-sample
$ gcloud source repos clone stackdriver-debugger-sample
```

まずは express を利用した Node.js のアプリケーションを作成します。
プロジェクトを作成して express をインストールします。

```bash
$ cd stackdriver-debugger-sample
$ npm init -y
$ npm install --save express
```

以下のような 0 から 10 までの数値をランダム生成して、5 より大きい、または小さいを判断する index.js を作成します。

```js:index.js
const express = require('express');

const PORT = 80;
const HOST = '0.0.0.0';

const app = express();

const getRandomNumber = (min, max) =>
  Math.floor(Math.random() * (max - min + 1)) + min;

app.get('/', (req, res) => {
  const num = getRandomNumber(0, 10);

  if (num > 5) {
    res.send('5 より大きい');
  } else {
    res.send('5 より小さい');
  }
});

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
```

サンプルアプリケーションの確認をしましょう。
index.js を起動します。

```bash
$ sudo node index.js
```

別の shell でリクエストすると以下のようなレスポンスであることを確認します。

```bash
$ curl http://127.0.0.1
5 より小さい
```

サンプルアプリケーションを Git で管理していきましょう。
不要なファイルをコミットしないように、`.gitignore` ファイルを作成します。
gitignore.io というサイトから Node.js 用の `.gitignore` ファイルの設定を利用します。

```bash
$ curl https://www.gitignore.io/api/node > .gitignore
```

最初のコミットをしましょう。

```bash
$ git add .
$ git commit -m 'initial commit'
```

次に Node.js 用の Stackdriver Debugger のパッケージをインストールします。

```bash
$ npm install --save @google-cloud/debug-agent
```

インストールしたパッケージを利用するために、アプリケーションのエントリポイントである index.js の先頭で Stackdriver Debugger のエージェントを有効にします。

```diff:index.js
+require('@google-cloud/debug-agent').start({
+  allowExpressions: true,
+  serviceContext: {
+    service: process.env.SERVICE,
+    version: process.env.VERSION,
+  }
+});
+
 const express = require('express');

 const PORT = 80;
 const HOST = '0.0.0.0';
```

`allowExpressions` は、後述するログポイントでメッセージ形式を指定するに当たって必要なため有効にします。
`service` には、アプリケーションの名前、`version` には、アプリケーションのバージョンを設定します。
これらの値をデプロイするたびに変えられるように環境変数から設定できるようにしておきます。

再度コミットしてプッシュします。

```bash
$ git commit -am 'Add Stackdriver Debugger agent'
$ git push origin master
```

# GCE へデプロイ

それでは GCE のインスタンスにソースコードを配置しデバッグを行うため、GCE のインスタンスを作成します。
スコープに Cloud Source Repositories の読み取り権限と Stackdriver Debugger の権限を付与しておきます。
サンプルとして `example-instance` という名前で作成しています。
作成すると以下のような情報が表示されます。

```bash
$ gcloud compute instances create example-instance \
    --zone asia-northeast1-a \
    --machine-type g1-small \
    --image-family ubuntu-1804-lts \
    --image-project ubuntu-os-cloud \
    --scopes https://www.googleapis.com/auth/source.read_only,https://www.googleapis.com/auth/cloud_debugger \
    --tags http-server
Created [https://www.googleapis.com/compute/v1/projects/stackdriver-debugger-235505/zones/asia-northeast1-a/instances/example-instance].
NAME              ZONE               MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP   STATUS
example-instance  asia-northeast1-a  g1-small                   10.146.0.7   34.85.116.83  RUNNING
```

作成した GCE のインスタンスに SSH 接続します。

```bash
$ gcloud compute ssh example-instance
```

Node.js のアプリケーションなので、Node.js と npm をインストールします。

```bash
$ sudo apt update && sudo apt install -y nodejs npm
```

Cloud Source Repositories のリポジトリから作成したアプリケーションをインスタンス内に clone して、npm module のインストールを行います。

```bash
$ gcloud source repos clone stackdriver-debugger-sample
$ cd stackdriver-debugger-sample
$ npm install
```

ソースコードが自動で選択されるようにソースコンテキスト情報を含んだファイル `source-context.json` を生成します。
アプリケーションと一緒にバージョン管理する必要はありませんが、このファイルを含めた状態でデプロイする必要があります。
今回はサンプルのためインスタンス内で生成します。

```bash
$ gcloud debug source gen-repo-info-file
```

サンプルアプリケーションを起動します。
起動時に、環境変数から設定できるようにした Stackdriver Debugger エージェントの変数を指定します。

```bash
$ sudo SERVICE=sample VERSION=1.0.0 node index.js
```

試しに別の shell でリクエストすると正常にレスポンスされることが分かります。

```bash
$ curl 34.85.116.83
5 より小さい
```

# デバッガを設定する

Google Cloud Console の左メニューから「デバッグ」メニューを開きます。
すると以下のようにデバッグ画面が開きます。

![Stackdriver Debugger の画面](https://qiita-image-store.s3.amazonaws.com/0/129299/19bc488e-e26d-1919-6705-76a6d92bb18b.png "Stackdriver Debugger の画面")

デバッグ画面が表示されたところで、スナップショットの表示から 22 行目をクリックしてブレークポイントを設定します。
この時点で変数 `num` にどんな値が入っているか確認できることを期待します。
再びリクエストを行うとブレークポイントの時点の変数やコールスタックが確認できます。
そして変数 `num` は 4 であることが分かりました。

![ブレークポイントを設定して確認](https://qiita-image-store.s3.amazonaws.com/0/129299/4cbb422a-ed73-ae67-7fa6-89d966f54286.png "ブレークポイントを設定して確認")

# ログポイントを設定する

ログポイントはデプロイしたアプリケーションのままログ出力を設定できる機能です。
これによって、本番で確認したいがために本番へログインしてソースコードを書き換え再起動するという手間が無くなります。

先ほどの画面からログポイントの画面に切り替えます。

![ログポイントの画面](https://qiita-image-store.s3.amazonaws.com/0/129299/f3bd2473-77a1-7926-b52d-12c8d9def36d.png "ログポイントの画面")

再び 22 行目をクリックすると、ログポイントの設定が表示されます。
ログポイントにはログが出力される条件とログのメッセージ形式が指定できます。
`true` の部分が出力条件、`var = {var}` の部分がメーセージ形式を指定する項目です。
またログレベルも指定でき、「情報」・「警告」・「エラー」の 3 種類から選択できます。

![ログポイントの設定](https://qiita-image-store.s3.amazonaws.com/0/129299/3763c085-f9df-ae65-80ae-07fbddb19633.png "ログポイントの設定")

今回は、メッセージ形式に `num = {num}` を指定し追加をクリックしましょう。
追加をすると 22 行目に `num = {num}` が追加されたことが分かります。

![ログポイントの追加](https://qiita-image-store.s3.amazonaws.com/0/129299/22f540e8-05dc-d1ef-7fa8-4f01613b7120.png "ログポイントの追加")

追加後、サンプルアプリケーションに何度かリクエストすると、サンプルアプリケーションを起動した標準出力にログポイントのログが出力されていることが分かります。
GCE の場合は、ログの出力先がアプリケーションの標準出力になります。
ログビューア上で見る場合は、別途ロギングエージェントなどを利用した転送が必要です。
GAE や GKE であれば、標準で Stackdriver Logging に対応しているため Stackdriver Logging のリクエストログから確認できます。

```bash
$ sudo SERVICE=sample VERSION=1.0.0 node index.js
Running on http://0.0.0.0:80
LOGPOINT: num = 8
LOGPOINT: num = 7
LOGPOINT: num = 10
LOGPOINT: num = 8
LOGPOINT: num = 4
```

次にログポイントの条件を指定してみましょう。
先ほど作成したログポイントを編集して、条件に `num > 5` と入力し適用をクリックすると編集完了です。

![ログポイントに条件を追加](https://qiita-image-store.s3.amazonaws.com/0/129299/25f3adfa-5523-f9a0-cb87-bf9a8513051a.png "ログポイントに条件を追加")

再びサンプルアプリケーションに何度かリクエストすると、 `num > 5` の条件の場合にのみログ出力されることが分かります。

```bash
$ sudo SERVICE=sample VERSION=1.0.0 node index.js
Running on http://0.0.0.0:80
LOGPOINT: num = 10
LOGPOINT: num = 9
LOGPOINT: num = 8
LOGPOINT: num = 8
LOGPOINT: num = 6
```

# お掃除

以下のコマンドを実行し、`y` でリポジトリを削除します。

```bash
$ gcloud source repos delete stackdriver-debugger-sample
```

以下のコマンドを実行し、`y` でインスタンスを削除します。

```bash
$ gcloud compute instances delete example-instance
```

# さいごに

ここまで Stackdriver Debugger を実際に使ってアプリケーションのデバッグを試しました。
Stackdriver Debugger を使えば簡単かつ便利に本番環境でもデバッグできることがお分かりいただけたのではないでしょうか。
是非有効にして、アプリケーションの調査に役立てましょう。

# 参考

[【GCP 入門編・第 24 回】 Stackdriver Debugger で本番環境のデバッグを行おう！ | 株式会社トップゲート](https://www.topgate.co.jp/gcp24-stackdriver-debugger)
[Stackdriver Debugger documentation | Stackdriver Debugger](https://cloud.google.com/debugger/docs/)