squashでKubernetesのPod内のGoアプリをデバッグ実行する

はじめに

最近、アプリの実行プラットフォームとしてk8sを使うことが多いのですが、ローカルで動いているアプリがk8s上で動かないみたいな事象にハマることがありました(もちろん、なるべく環境は合わせるようにしていたのでですが、ローカルでは外部APIを一部モックしたりしている箇所がありそこの周りで)。
そんなとき、「Kubernetesで実践するクラウドネイティブDevOps」を読んでるときに kubesquashというツールの名前と概要が紹介されていました。どうもこいつを使うと、k8s上で動いているアプリのデバッグを行なうことが可能になるとのことで、興味が湧いたので使ってみたいと思います。

.....と、思ったのですが、どうもkubesquashはsquashにマージされいました。なのでそちらを今回は使ってみたいと思います。
このブログのゴールは以下の通りです。

  • k8sのpodで動作しているGo製(Ginを利用)のアプリをデバッグ実行できるようになる

Squashとは

SquashはKubernetesで動作中のアプリに対して、ターミナルやIDEからデバッグ実行を可能とするOSSです。
Squashを利用することで以下のようなことが可能になります。

  • 動作するマイクロサービスをDebuggingする
  • contenier内のPodをDebuggingする
  • ブレークポイントを置く
  • ステップ単位でコードを実行する

squashは現在(2020/4/6)以下のDebuggerをサポートしているます。

また、プラットフォームは以下のものをサポートしているようです。

IDEプラグインもいくつかサポートされています。

私は普段IntelliJ Ideaを使ってコーディングすることが多いのですが、dlvのサポートが無いらしいので、今回はVS Codeプラグインを使います。

Goのアプリをデバッグする

Gin製のアプリをminikubeにデプロイして、デバッグをしてみます。

動作環境

インストールして動かす環境は以下です。

$ uname -srvmpio
Linux 5.3.0-51-generic #44~18.04.2-Ubuntu SMP Thu Apr 23 14:27:18 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

$ docker -v
Docker version 19.03.8, build afacb8b7f0

$ minikube version
minikube version: v1.5.2
commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad-dirty

$ dlv version
Delve Debugger
Version: 1.4.0
Build: $Id: 67422e6f7148fa1efa0eac1423ab5594b223d93b

今回はk8sクラスタはminikube上に構築します。
また、goのDebaggerであるdlvのインストールも必要なようなので事前にしておいてください。(Linuxでのdlvのインストるはここから)

下準備

もろもろのセットアップとかを行なうまえに以下のことをやっておきます。

  • Ginでサンプルアプリを作成
  • minikube 上にデプロイ(ServiceとDeploymentの作成)
  • ローカルでの疎通確認

Ginでサンプルアプリを作成

さくっとアプリを作ってしまします。

$ mkdir sample-app
$ cd sample-app
$ go mod init sample-app
$ go get -u github.com/gin-gonic/gin

作成するアプリはGin公式のquick-startを丸パクリします。

main.go

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}

実行してみます。

$  ls
go.mod  go.sum  main.go  sample-app

$ go run main.go

# 別ターミナルで
$ curl http://localhost:8080/ping
{"message":"pong"}

アプリは完成しました。

minikubeにデプロイ

作成したアプリをminikubeにデプロイしてみます。
まずは、作ったアプリをビルドします。

$ GOOS=linux CGO_ENABLED=0 go build -gcflags "-N -l" -o sample-app

-gcflags "-N -l"ははDebuggerに必要な情報を出力するためのオプションです。

次にはイメージを作成します。 以下のDokcerfileを使用します。

FROM alpine
COPY sample-app /sample-app
ENTRYPOINT ["/sample-app"]

EXPOSE 8080

ビルドします。

 $ docker build -t sample-app:1.0.0 .

次にdeployment.ymlservice.ymlを作成します。

$ kubectl create deployment sample-app --image=sample-app:1.0.0 --dry-run -o yaml > deployment.yml

$ cat deployment.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: sample-app
  name: sample-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-app
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: sample-app
    spec:
      containers:
      - image: sample-app:1.0.0
        name: sample-app
        ports:
        - containerPort: 8080
        resources: {}
status: {}


$ kubectl create service nodeport sample-app --tcp=8080:8080 --dry-run -o yaml > service.yml

$ cat service.yml 
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: sample-app
  name: sample-app
spec:
  ports:
  - name: 8080-8080
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: sample-app
  type: NodePort
status:
  loadBalancer: {}

作成したymlを使ってリソースを作成します。

$ kubectl apply -f deployment.yml
$ kubectl apply -f service.yml

また、localhostのアクセスでServiceにつながるようにするためにport-fowordをしておきます。

$ kubectl --context=minikube port-forward service/sample-app 8080:8080
$ curl localhost:8080/ping
{"message":"pong"}

これで下準備は完了です。

squashのインストール

Mac OS用にはBrewでパッケージが用意されているようですがLinuxでは無いので、ここから最新のリリースを落として来て任意のディレクトリに起きPATHを通すようにします。

# ダウンロード
$ wget https://github.com/solo-io/squash/releases/download/v0.5.18/squashctl-linux
$ wget https://github.com/solo-io/squash/releases/download/v0.5.18/squashctl-linux.sha256

$ sha256sum squashctl-linux
071406a1eadcf78014c1b6c629c46e3145a1f150d40555a4be0410747160b82b  squashctl-linu
$ cat squashctl-linux.sha256 
071406a1eadcf78014c1b6c629c46e3145a1f150d40555a4be0410747160b82b squashctl-linux

# 今回はPATHの通っているところに配置
$  sudo mv squashctl-linux /usr/local/bin/squashctl
$ sudo chmod +x /usr/local/bin/squashctl 

$ squashctl --version
squashctl version 0.5.18

これでインストールは完了です。

VS codeからSquashを実行する

前述の通り、VS Codeプラグインを用いて、Pluginの検索ボックスに"squash"と入力してプラグインをインストールします。

f:id:yuya_hirooka:20200507004759p:plain

プラグインにはsquashctlのPathを設定してやる必要があります。

[Setting]の検索ボックスにsquashと入力してSpuash: Pathの項目に/user/local/bin/squashctlを指定してやります。

f:id:yuya_hirooka:20200507141038p:plain

これで、インストールは完了です。

デバッグを実行する

それでは実際にデバッグをしてみましょう。
作成したmain.goのソースファイルを開き、ctrl + shift + pでコマンドパレットを開きます。
検索ボックスにsquashと入力し、[debug pod]⇨[namespace]⇨[sample-app-デバッグしたいpodID]⇨[dlv]の順番でデバッグを実行したいNamespace、PodとDebeggerの種類を選択します。

そうすると、Debeggerが立ち上がります。
f:id:yuya_hirooka:20200507142335p:plain

コードの左側(コードの行数が書かれている左側)をクリックするとブレークポイントを置くことができます。

f:id:yuya_hirooka:20200507142716p:plain

この状態でcurlを叩くと置いたブレークポイントの位置で実行が停止します。

$ curl localhost:8080/ping

f:id:yuya_hirooka:20200507142745p:plain

後は煮るなり焼くなりって感じですね。

感想

はじめは、アプリをマルチステージビルドでやっていたのですが、どうもビルドしたバイナリが、ローカルに無いとうまく動かないっぽいですね(この辺の理解がまだ甘いですが...)。それに気がつくまでにめっちゃ時間がかかりました。
それと、単一Podのデバッグ実行はできたのですが、ReplicaSetのスケールが1以上の場合は、少しやり方を考えないと行けないかもなと感じました。