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を使うを参照のこと。
(追記 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
本番環境で動作中のアプリケーションの内部状態を直接デバッグできるという夢のようなツールです。
現時点での対応プラットフォームは以下のようになっております。
ref. https://cloud.google.com/debugger/docs/before-you-begin?hl=ja#supported_platforms
表の通り、いまのところGoについてはGAEでもサポートされておらず(!)、GCE、GKEだけになります。
GCEについては、公式の導入ドキュメントが存在しており、この通りにagentをGCEにダウンロードして、agent経由でgoバイナリを起動してあげればよいです。
しかしながら、GKEについては表で○がついている割にはドキュメントは存在してません…
GCE直に使うことはまずない(?)ので、GKEでも使えるようにしてみましたよ、という記事になります。
Clusterの作成
gcloud
の設定とプロジェクトの作成は済んでいるものとして話を進めます。
まず以下のようなコマンドでClusterを作成します。
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アプリを使うことにします。
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しておきます。
FROM golang
WORKDIR /go/src/hello
COPY main.go .
RUN go build
CMD ["./hello"]
EXPOSE 3000
gcloud docker -- build -t gcr.io/$PROJECT_ID/hello:latest
gcloud docker -- push gcr.io/$PROJECT_ID/hello:latest
$PROJECT_ID
やrepositoryへのパス1は適当に自分の環境に合わせてください。
k8sへのデプロイ
GCRにpushしたimageを、k8s上にデプロイします。
---
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
---
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で置き換えてください。
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が実行されます。
Google Container Builder(GCB)によるデプロイ
Containerのビルドとk8sへのデプロイにGCBを使います。
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用のドキュメントに書かれている手順を、GCBを使って実現していけばよい、ということになります。
以下、順を追って説明していきます。
見出しはid
です。
gen-repo-info
- name: gcr.io/cloud-builders/gcloud
args:
- debug
- source
- gen-repo-info-file
id: gen-repo-info
ソースコンテキストファイルの作成です。
ローカルで実行して生成されるファイルの中身をみてみれば分かりますが、repositoryの情報とrevision情報が入っています。
debuggerの起動に使用します。
wget-debugger-script
- 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
- 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、とする場合が多いのでこうしています。
またREADMEを読むと分かるように、PROJECT_ROOT
を指定することでGOPATH
を解決しています。5
docker-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
Build Step内では置換される変数がいくつかあります。
これらを使って、tag名を動的に決定します。
ここではrevisionつきのtagとlatest tagの2つを作っています。
push-image
- 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
- 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は以下のように修正します。
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
公式ドキュメントの要件のところに
- 64 ビット Debian Linux イメージ
と書かれています。
ブートストラップスクリプトを実行すると、最新のDebugger agentがダウンロードされるのですが、このagentはgoバイナリ6になっており、これを実行するための環境要件がこれになります。
これを満たすためと、ここでは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
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
を実行します。
パラメータはドキュメントを参照してください。
このバイナリは実行すべきコマンド文字列を返すので、これをバッククォート8で実行してあげる必要があります。
これらをgithubとかにpushします。
Container Registry Trigger
まだベータ版ですがGCRにはtriggerを設定できます。
github9と連携してmasterブランチにpushされたらbuildが開始するように設定します。
連携は簡単でConsole上から行います。
- Container Registry -> トリガーを作成 -> GitHub認証
- 連携したいリポジトリを選択 -> ブランチやビルドファイルなどの設定
IAMで権限付与
GCBでGKEへのdeployを実行するために、cloudbuildサービスアカウントにGKEの権限を付与します。
- IAMと管理 -> IAM -> cloudbuildアカウント選択
- 役割で「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の設定
- Stackdriver Debugger for Golang on GKE
- 手元にDockerいらずGCP上でコンテナビルドして、そのままGKEにデプロイする
-
ここでは
hello
というパスにしました。 ↩ -
EXTERNAL-IP
が割り振られるまでにはしばらく時間がかかります ↩ -
今回は
main.go
しかないので無意味ですが ↩ -
将来的にはMulti-stage buildsでしょうか ↩
-
ここでは他と揃えるためにhelloにしています。 ↩
-
どうやらこのバイナリのソースは https://godoc.org/cloud.google.com/go/cmd/go-cloud-debug-agent にあるようなので、これを自前でbuildすればalpineでも動作しそうです。しかし公式でサポートしていない方式なので今回は採用しません ↩
-
いま思うとwgetをインストールしちゃうのでここで普通にwgetしてダウンロードでいい気もする ↩
-
CMDにバッククォートを使うのはセキュリティ的によくないように思うけど仕方なし ↩
-
github以外にもCloud Source Repository, BitBucketの連携にも対応 ↩