GKE/GoでもStackdriver Debuggerを使いたい人生だった

![eye-catch-kubernetes-400x4001.png](https://qiita-image-store.s3.amazonaws.com/0/1847/e123132f-0d79-bfa3-2ae7-caf8443c3d3b.png)![images.png](https://qiita-image-store.s3.amazonaws.com/0/1847/91f81123-1d67-521b-c586-a7d1edb5668d.png)

# tl;dr

- GKE上で動作しているgolangアプリケーションでStackdriver Debuggerを使えるようにする
- 公式のDebugger Agentがgoバイナリで提供されているが動作環境が _64ビットDebian Linuxイメージ_ となっているため嵌りポイントがあって試行錯誤した

(追記 2017/09/29)
Go1.9を使うため、image tagを`go:wheezy`から`go:debian`に更新しました。
詳しくは[GCCBでGo1.9を使う](https://qiita.com/iwata@github/items/d2082205c627f3649bef)を参照のこと。
(追記 2018/03/26)
Go用のエージェントが`cd_go_agent.sh`から`go-cloud-debug`に変わったので修正。

# Stackdriver Debugger

> Stackdriver Debugger を導入すれば、アプリケーションを止めたり、顧客に影響を及ぼしたりせずに、コードの任意の行でアプリケーションの状態や変数、コール スタックを調べることができます。本番環境のコードを直接デバッグできるので、バグの発見や再現に要する時間が大幅に短縮されます。
ref. https://cloudplatform-jp.googleblog.com/2016/10/stackdriver-debugger-ga.html

本番環境で動作中のアプリケーションの内部状態を直接デバッグできるという夢のようなツールです。
現時点での対応プラットフォームは以下のようになっております。
<img width="660" alt="始める前に_ _ _StackDriver_Debugger_ _ _Google_Cloud_Platform.png" src="https://qiita-image-store.s3.amazonaws.com/0/1847/d588ceac-e2b3-7615-202a-0b2f1ed8ffb4.png">
ref. https://cloud.google.com/debugger/docs/before-you-begin?hl=ja#supported_platforms

表の通り、いまのところGoについてはGAEでもサポートされておらず(!)、GCE、GKEだけになります。
GCEについては、公式の[導入ドキュメント](https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#enable_the_stackdriver_debugger_agent)が存在しており、この通りにagentをGCEにダウンロードして、agent経由でgoバイナリを起動してあげればよいです。

しかしながら、GKEについては表で○がついている割にはドキュメントは存在してません…
GCE直に使うことはまずない(?)ので、GKEでも使えるようにしてみましたよ、という記事になります。

# Clusterの作成

`gcloud`の設定とプロジェクトの作成は済んでいるものとして話を進めます。
まず以下のようなコマンドでClusterを作成します。

```bash
gcloud container clusters create $CLUSTER_NAME --scopes compute-rw,storage-rw,https//www.googleapis.com/auth/cloud-platform
```

オプションは必要に応じて設定してもらうとして、`--scopes`は上記の指定が必須になります。

> gcloud compute instances create コマンドを使用して、コマンドラインからインスタンスを作成することもできます。かならず --scopes パラメータを --scopes compute-rw,storage-rw,https://www.googleapis.com/auth/cloud-platform に設定してください。これにより、Cloud Platform バックエンドへのアクセスに必要な Cloud Platform OAuth スコープとその他のスコープが有効化されます。
ref. https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#create_a_compute_engine_instance

GCEについて書かれていますが、GKEもGCE上で動作しているので同様です。

## 初期イメージのGoogle Container Registry(GCR)へのpush

以下のような適当なgoアプリを使うことにします。

```go:main.go
package main

import (
	"log"
	"net/http"
)

func handler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("Hello, world!\n"))
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe("0.0.0.0:3000", nil))
}
```

このアプリを実行するDockerfileを適当に用意して、GCRにpushしておきます。

```Dockerfile:Dockerfile
FROM golang

WORKDIR /go/src/hello
COPY main.go .

RUN go build
CMD ["./hello"]
EXPOSE 3000
```

```bash
gcloud docker -- build -t gcr.io/$PROJECT_ID/hello:latest
gcloud docker -- push gcr.io/$PROJECT_ID/hello:latest
```

`$PROJECT_ID`やrepositoryへのパス[^1]は適当に自分の環境に合わせてください。
[^1]: ここでは`hello`というパスにしました。

## k8sへのデプロイ

GCRにpushしたimageを、k8s上にデプロイします。

```yaml:deployment.yaml
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
        tier: frontend
    spec:
      containers:
        - name: hello
          image: gcr.io/$PROJECT_ID/hello:latest
          ports:
            - containerPort: 3000
```

```yaml:service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: hello
  labels:
    app: hello
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 3000
  selector:
    app: hello
```

Dockerfileで3000番をexportしたので、これに80番でアクセスさせてます。
`$PROJECT_ID`は展開されないので作成したIDで置き換えてください。

```bash
kubectl create -f deployment.yaml -f service.yaml
kubectl get pod
kubectl get deployments
kubectl get service
```

YAMLから諸々作成します。
`kubectl get service`で`EXTERNAL-IP`が確認[^2]できるので、そこにアクセスするとContainerが実行されます。
[^2]: `EXTERNAL-IP`が割り振られるまでにはしばらく時間がかかります

# Google Container Builder(GCB)によるデプロイ

Containerのビルドとk8sへのデプロイに[GCB](https://cloud.google.com/container-builder/)を使います。
cloudbuild.yamlを以下のように記述します。(詳細は後述します)

```yaml:cloudbuild.yaml
---
steps:
  - name: gcr.io/cloud-builders/gcloud
    args:
      - debug
      - source
      - gen-repo-info-file
    id: gen-repo-info
  - name: gcr.io/cloud-builders/wget
    args:
      - -q
      - https://storage.googleapis.com/cloud-debugger/compute-go/go-cloud-debug
    id: wget-debugger-script

  - name: gcr.io/cloud-builders/go:debian
    args:
      - build
    env: ['PROJECT_ROOT=hello']
    id: go-build

  - name: gcr.io/cloud-builders/docker
    args:
      - build
      - -t
      - 'gcr.io/$PROJECT_ID/hello:$BRANCH_NAME-$REVISION_ID'
      - -t
      - 'gcr.io/$PROJECT_ID/hello:latest'
      - '.'
    id: docker-build

  - name: gcr.io/cloud-builders/docker
    args:
      - push
      - 'gcr.io/$PROJECT_ID/hello:latest'
    id: push-image-latest
  - name: gcr.io/cloud-builders/docker
    args:
      - push
      - 'gcr.io/$PROJECT_ID/latest:$BRANCH_NAME-$REVISION_ID'
    id: push-image-revision

  - name: gcr.io/cloud-builders/gcloud
    entrypoint: bash
    args:
      - -c
      - |
        gcloud container clusters get-credentials slack-bot --zone us-west1-a
        kubectl set image deployment/hello hello=gcr.io/$PROJECT_ID/hello:$BRANCH_NAME-$REVISION_ID
    id: deploy-gke
```

基本的には[GCE用のドキュメント](https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#build_test_and_deploy_your_application)に書かれている手順を、GCBを使って実現していけばよい、ということになります。

以下、順を追って説明していきます。
見出しは`id`です。

## gen-repo-info

```yaml
  - name: gcr.io/cloud-builders/gcloud
    args:
      - debug
      - source
      - gen-repo-info-file
    id: gen-repo-info
```
ソースコンテキストファイルの作成です。
ローカルで実行して生成されるファイルの中身をみてみれば分かりますが、repositoryの情報とrevision情報が入っています。
debuggerの起動に使用します。

## wget-debugger-script

```yaml
  - name: gcr.io/cloud-builders/wget
    args:
      - -q
      - https://storage.googleapis.com/cloud-debugger/compute-go/go-cloud-debug
    id: wget-debugger-script
```
Debuggerエージェントをダウンロードします。
このバイナリをwrapperとしてアプリケーションを起動します。

## go-build

```yaml
  - name: gcr.io/cloud-builders/go:debian
    args:
      - build
    env: ['PROJECT_ROOT=hello']
    id: go-build
```

gcr.io/cloud-builders/goを使ってソースコードをbuildします。
前述のDockerfileでは、Dockerfile内でbuildしてそのまま起動していましたが、そうするとソースファイル[^3]をContainer内にCOPYする必要がでてきて、シングルバイナリになるgoアプリの場合、不要になります。
Container imageはなるべく小さくしたいので、GCBでは別stepでバイナリをbuildして、GCRにpushするimageにはそのバイナリをCOPYして起動するだけ[^4]、とする場合が多いのでこうしています。
[^3]: 今回は`main.go`しかないので無意味ですが
[^4]: 将来的にはMulti-stage buildsでしょうか

また[README](https://github.com/GoogleCloudPlatform/cloud-builders/tree/master/go)を読むと分かるように、`PROJECT_ROOT`を指定することで`GOPATH`を解決しています。[^5]
[^5]: ここでは他と揃えるためにhelloにしています。

## docker-build

```yaml
  - name: gcr.io/cloud-builders/docker
    args:
      - build
      - -t
      - 'gcr.io/$PROJECT_ID/hello:$BRANCH_NAME-$REVISION_ID'
      - -t
      - 'gcr.io/$PROJECT_ID/hello:latest'
      - '.'
    id: docker-build
```

Build Step内では[置換される変数](https://cloud.google.com/container-builder/docs/api/build-requests?hl=ja#substitutions)がいくつかあります。
これらを使って、tag名を動的に決定します。
ここではrevisionつきのtagとlatest tagの2つを作っています。

## push-image

```yaml
  - name: gcr.io/cloud-builders/docker
    args:
      - push
      - 'gcr.io/$PROJECT_ID/hello:latest'
    id: push-image-latest
  - name: gcr.io/cloud-builders/docker
    args:
      - push
      - 'gcr.io/$PROJECT_ID/latest:$BRANCH_NAME-$REVISION_ID'
    id: push-image-revision
```

BuildしたimageをGCRにpushしています。
`images`を指定することでbuild成功時に自動的にGCRにpushすることも可能ですが、このbuild stepではGKEへのdeployもやってしまいたいので、その前に自前でpushする必要があります。

## deploy-gke

```yaml
  - name: gcr.io/cloud-builders/gcloud
    entrypoint: bash
    args:
      - -c
      - |
        gcloud container clusters get-credentials hello
        kubectl set image deployment/hello hello=gcr.io/$PROJECT_ID/hello:$BRANCH_NAME-$REVISION_ID
    id: deploy-gke
```

k8s上に新しいimageをdeployします。

# Dockerfileの修正

agent経由でアプリ(`hello`)を起動するため、Dockerfileは以下のように修正します。

```dockerfile:Dockerfile
FROM debian:stretch-slim

RUN apt update && apt install -y wget \
  && apt autoclean && apt clean \
  && rm -rf /var/lib/apt/lists/*

# boot strap for Stackdriver Debugger
COPY go-cloud-debug .
RUN chmod +x go-cloud-debug
COPY source-context.json

COPY hello .

CMD `./go-cloud-debug -sourcecontext=./source-context.json -appmodule=hello -appversion=1.0 -- ./hello`
EXPOSE 3000
```

だいぶ変わったのでひとつずつ説明します。

```
FROM debian:stretch-slim
```

公式ドキュメントの[要件のところ](https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#requirements)に

> - 64 ビット Debian Linux イメージ

と書かれています。
[ブートストラップスクリプト](cd_go_agent.sh)を実行すると、最新のDebugger agentがダウンロードされるのですが、このagentはgoバイナリ[^6]になっており、これを実行するための環境要件がこれになります。
[^6]: どうやらこのバイナリのソースは https://godoc.org/cloud.google.com/go/cmd/go-cloud-debug-agent にあるようなので、これを自前でbuildすればalpineでも動作しそうです。しかし公式でサポートしていない方式なので今回は採用しません

これを満たすためと、ここではgoをbuildする必要がなくなったので、`debian:stretch-slim`を使うように変更します。

実はサラっと流しましたが、`cloudbuild.yaml`の`go-build`のところで`gcr.io/cloud-builders/go`ではなく、`gcr.io/cloud-builders/go:debian`を使っていましたが、これはこの要件を満たすためです。
alpineでbuildしたバイナリは起動しないという…

```
RUN apt update && apt install -y wget \
  && apt autoclean && apt clean \
  && rm -rf /var/lib/apt/lists/*
```

`go-cloud-debug`の起動に`wget`が必要なためインストール。

```
COPY go-cloud-debug .
RUN chmod +x go-cloud-debug
```

`wget-debugger-script`でダウンロードしておいたパイナリをコピーして、実行権限を付与しています。[^7]
[^7]: いま思うとwgetをインストールしちゃうのでここで普通にwgetしてダウンロードでいい気もする

```
COPY source-context.json .
```

`gen-repo-info`で生成したjsonファイルをコピーしておきます。

```
COPY hello .
```

事前にbuildしておいたバイナリをコピー。

```
CMD `./go-cloud-debug -sourcecontext=./source-context.json -appmodule=hello -appversion=1.0 -- ./hello`
```

コピーしておいた`go-cloud-debug`を実行します。
パラメータは[ドキュメント](https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#enable_the_stackdriver_debugger_agent)を参照してください。
このバイナリは実行すべきコマンド文字列を返すので、これをバッククォート[^8]で実行してあげる必要があります。
[^8]: CMDにバッククォートを使うのはセキュリティ的によくないように思うけど仕方なし

これらをgithubとかにpushします。

# Container Registry Trigger

まだベータ版ですがGCRにはtriggerを設定できます。
github[^9]と連携してmasterブランチにpushされたらbuildが開始するように設定します。
[^9]: github以外にもCloud Source Repository, BitBucketの連携にも対応
連携は簡単でConsole上から行います。

1. Container Registry -> トリガーを作成 -> GitHub認証
1. 連携したいリポジトリを選択 -> ブランチやビルドファイルなどの設定

# IAMで権限付与

GCBでGKEへのdeployを実行するために、cloudbuildサービスアカウントにGKEの権限を付与します。

1. IAMと管理 -> IAM -> cloudbuildアカウント選択
1. 役割で「ContainerEngine管理者」を追加

ここまでで設定は完了です。
あとはmasterにpushすればk8sへのdeployまでやってくれるので、適当になにかpushして試してみるとよいでしょう。
無事アクセスできれば完了です。

# Debugger実行

これで晴れてConsole上からLive debugできます。
Consoleにある`デバッグ`メニューにアクセスします。
debuggerが起動していればここでソースファイルを選べるようになっているはずです。
行番号をクリックして、スナップショットを作成すればいま稼動中のアプリの変数やstacktraceが参照できます!

# ちら裏

違う実行環境用にbuildされたバイナリを実行すると、`No such file or directory`っていわれて、ファイルあるのに最初なんのことか分からなかった。

Debianじゃないとダメだと気づいて、Dockerfileを変更したら、今度はgo-buildをalpineでやってるせいで、またNo such..といわれて`go:debian`使うようにしたらようやく動いた。

# See Also

- [Google Compute Engine上のGoアプリケーションに対するStackdriver Debuggerの設定](https://cloud.google.com/debugger/docs/setting-up-go-on-compute-engine#enable_the_stackdriver_debugger_agent)
- [Stackdriver Debugger for Golang on GKE](http://qiita.com/nshmura/items/867d9d560a64c91f7e94)
- [手元にDockerいらずGCP上でコンテナビルドして、そのままGKEにデプロイする](http://qiita.com/zaru/items/80f7b43ef9aec566e50f)