Squashでコンテナ化マイクロサービスのデバッグ

# Squashとは

Squashは「The debugger for microservices」をテーマにしたプロダクトです。

<img width="143" alt="スクリーンショット 2019-05-19 2.42.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/79f9f1e8-6079-f333-4ce2-253e3d115f6e.png">

CNCFのLandscapeにも載っていますが、開発元の[solo.io](https://www.solo.io/)がCNCFのSliver memberで、Sandboxやincubatorなどには入っていないようです。

<img width="418" alt="スクリーンショット 2019-05-19 2.45.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/498f40bd-5f63-f4b7-416c-59183099c27a.png">

[Telepresence](https://www.telepresence.io/)さんの隣にいることから察せますが、
ローカルからリモートのKubernetesクラスタをデバッグできる強力なツールです。

# リンク一覧

おそらく本記事がまとめに日本語でSquashに触れた最初の記事です...
英語ですらまともにDocumentがなく、非常に苦戦しました。
(公式Documentもそれだけだと動かない...CNCFプロダクトあるあるです)

古い資料も混ざっているのでご注意ください(特にいつの間にかアーキテクチャが大きく変わっています)

公式サイト: https://squash.solo.io/
GitHub: https://github.com/solo-io/squash
Demo: https://www.youtube.com/watch?v=i5_eacXkw3w
Demo: https://www.youtube.com/watch?v=5aNPfwVvLvA  ※アーキテクチャが古いので注意
Squash: Microservices Debugger:  ※アーキテクチャが古いので注意
 https://medium.com/solo-io/squash-microservices-debugger-5023e27533de
Squash, the definitive cloud-native debugging tool:
 https://itnext.io/squash-the-definitive-cloud-native-debugging-tool-89614650cc94

# Squashでできること

端的に言うとKubernetesクラスタ上のアプリケーションをリモートデバッグすることができます。
公式ホームページでは次の機能を謳っています。

・マイクロサービスのデバッグ
・Pod内のコンテナのデバッグ
・サービスのデバッグ
・ブレークポイントの設定
・コードのステップ処理
・変数の評価と修正
・その他通常のデバッガーでできること

サーバーサイドでエラーが発生したり予想外の挙動をしたりすることは、決して珍しいことではありません。

コンテナアプリは通常、外部から隔離された状態です。
[CKA](https://www.cncf.io/certification/cka/)の出題でもそうですが、たいていKubernetes上のコンテナに問題があった際は「kubectl describe」や「kubectl logs」でコンテナの状態やログの確認を行います。
しかし、もし問題に関する情報がログに出ていなければ、その問題を探ることは非常に難しく、原因を推測することくらいしかできません。

Squashはコードをデバッグすることで問題の特定をサポートすることができます。

> 念のため書きますが本番利用を推奨しているわけではありません

Squashは現在次のデバッガーをサポートしています。

・dlv
・Java
・gdb(2019)
・Nodejs(2019)
・Python - ptvsd(2019)

また、サポートしているIDEは次の通りです。

・VS Code
・Intellij (2019)
・Eclipse (2019)

RoadMapとして2019年にService Mesh([Istio](https://istio.io/), [Envoy](https://www.envoyproxy.io/))のデバッグも可能にするようです。

本記事ではGo言語のサンプルアプリをデプロイして、そのコードをIDE(Visual Studio Code)でデバッグする過程を記載します。

# Squashのアーキテクチャ

Squashのアーキテクチャは下記の構成になっています。
デバッグ対象のPodの他に「Plank」と呼ばれるデバッガー用のPodが生成されます。

![Squash.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/4c6ff83b-6c23-97e9-5714-3b77b1bf9cdd.png)

> 引用元: https://itnext.io/squash-the-definitive-cloud-native-debugging-tool-89614650cc94

なお、セキュアモードもあるようですが、本記事では説明を省きます。
公式ドキュメントをご参照ください。
https://squash.solo.io/secure_mode/

# 環境

下記は既にインストールされているものとします

・Mac Pro(2018) OS Mojave 10.14.1
・Docker for Mac(Docker Engine 18.09)
・Kubernetesクラスタ(Docker for Macの機能)
・Homebrew
・Visual Studio Code

# 必要ソフトウェアのインストール

Squashのインストールを説明します。
Go言語のデバッグをするために必要なものは下記です。

・[Go](https://golang.org/)
・[Delve](https://github.com/go-delve/delve)(Goのデバッガー)
・Visual Studio CodeのGoの拡張機能
・squashctl(Squashのコマンドラインツール)
・Visual Studio CodeのSquashの拡張機能

GoとDelve、VSCodeのGo拡張機能のインストール方法は本記事では省略します。
ただし、インストール後に$GOPATHと「dlv」(delveコマンド)がVisual Studio Code上でも利用可能な状態にする必要があります。

## squashctl

ではまず、squashctlをインストールします。
[公式Document](https://squash.solo.io/overview/)に従い、Homebrewを使ってインストールします。

```
$ brew install solo-io/tap/squash
Error: No available formula with the name "solo-io/tap/squash"
==> Searching for a previously deleted formula (in the last month)...
Error: No previously deleted formula found.
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
```
インストールに失敗しました...
Homebrewのコマンドを次のように打って回避します。

```
brew search squash
brew tap solo-io/tap
brew install squashctl
```

これでインストールができます。

### squashctl - 別のインストール方法

Homebrew以外のインストール方法も記載します。

次のページで最新版のファイルをダウンロードします

https://github.com/solo-io/squash/releases
squashctl-darwin

```
mv squashctl-darwin squashctl
chmod +x squashctl
mv squashctl /usr/local/bin/
```

Homebrewかこちらの方法でインストールしてください。

## Visual Studio CodeのSquash拡張機能

Visual Studio Codeを立ち上げて、拡張機能のインストール画面からSquashをインストールします。

<img width="876" alt="スクリーンショット 2019-05-17 0.32.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/3564086d-80fa-a566-e429-fe2fb5e020b6.png">

インストールと活性化が完了したら拡張機能用の設定画面を開きます。

Code > Preferences > Settingsから開けます。
SettingでSquashで検索をかけて、Squash: Pathに「/usr/local/bin/squashctl」を設定します。

<img width="868" alt="スクリーンショット 2019-05-17 0.49.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/1f2c6dbb-7c6b-fa02-35eb-4b60f0d69b9a.png">

# デモアプリの準備

デモアプリのソースをローカルにダウンロードします。

```
go get -v github.com/solo-io/squash
```

$GOPATH/src/github.com/solo-io/squash/contrib/example/配下のソースがデモアプリのソースです。
service1とservice2のファイルが今回のデモの対象です。

```
example/
├── service1      ※デモ対象
├── service2      ※デモ対象
└── service2-java ※今回は対象外
```

## ソースコードマッピングの設定

拡張機能用の設定画面からSquash: Remote Pathを「/home/yuval/go/src/github.com/solo-io/squash/contrib/example/」に設定します。

Remoete Pathの設定は非常に重要です。
Squashでデバッグをする際には「ソースコードマッピング」を行う必要があります。

https://squash.solo.io/configuration/

Remote Pathには実行対象のアプリの絶対パスを記載します。
重要なのがローカルにダウンロードした実行ファイルの位置ではなく、リモートで実施している実行ファイルの位置を記載することです。
今回のデモアプリのコンテナは「/home/yuval/go/src/github.com/solo-io/squash/contrib/example/」配下のファイルが実行されるため、事前に設定しておきます。

<img width="860" alt="スクリーンショット 2019-05-19 3.38.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/f85f81b1-5a37-6c5a-6703-e2ed2c699bf2.png">

# デモアプリのデプロイ

デモアプリをKubernetesクラスタにデプロイします。
「squash deploy demo」というコマンドでデモアプリをデプロイすることができます。

デモアプリは下記の構成のアプリケーションです。
「example-service1」と「example-service2」という二つのコンテナを実行します。
「example-service2」はGo言語用とJava用のソースがあり、どちらをデプロイするか選ぶことができます。
今回は二つともGo言語用をデプロイします。

<img width="986" alt="スクリーンショット 2019-05-20 21.18.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/e2160750-fa71-1952-cff1-72a65b418a44.png">

```
$ squashctl deploy demo
? Select a namespace for service 1. default
? Select a namespace for service 2. default
? Choose a demo microservice to deploy go-go
```

デプロイ後にPodをデプロイすると、二つのPodが確認できます。

```
$ kubectl get pod
NAME                                READY     STATUS              RESTARTS   AGE
example-service1-d476946c6-2fznz    0/1       ContainerCreating   0          4s
example-service2-6bb5ff4965-xqgr2   0/1       ContainerCreating   0          4s
```

サービスリソースも二つ作成されています。

```
$ kubectl get svc
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
example-service1   ClusterIP   10.103.19.149   <none>        80/TCP    8s
example-service2   ClusterIP   10.98.58.75     <none>        80/TCP    8s
kubernetes         ClusterIP   10.96.0.1       <none>        443/TCP   25d
```

ローカルKubernetesにClusterIPとしてデプロイしているため、ローカル端末からアクセスできるようにポートフォワーディングをします。
(フォアグラウンドで実行されるため、別途ターミナルを開きましょう)

```
kubectl --namespace default port-forward service/example-service1 8080:80
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
```

ブラウザで次のURLにアクセスすると、デモアプリのUIが確認できます。

http://localhost:8080/

<img width="1352" alt="スクリーンショット 2019-05-17 0.17.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/62020ec7-dd89-24c2-abcc-2b0f8b0bd2cb.png">

見ての通り、二つの入力値を加算または減算するアプリです。

試しに「2 + 2」の計算を行います。

<img width="830" alt="スクリーンショット 2019-05-20 21.25.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/2aaa16fd-0530-5253-5ae5-c0ed03ddf7af.png">

「2 - 2」の計算結果になっています。
明らかにアプリケーションのバグを起こしています(というデモアプリの設定です)。
このバグの原因をデバッグして探っていきます。

なお、デモアプリのソースは次のリンクから確認できます。

example-service1:
 https://github.com/solo-io/squash/blob/master/contrib/example/service1/main.go

example-service2:
 https://github.com/solo-io/squash/blob/master/contrib/example/service2/main.go

# デモアプリのデバッグ

F1キーで拡張機能を「Squash」で検索します。
検索すると「Squash - debug pod」が表示されるので選択します。

<img width="725" alt="スクリーンショット 2019-05-17 0.38.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/8d2247f7-1563-cbcf-bcc8-e3714a939cdd.png">

続けて、Namespaceを選択します。
この時点でKubernetesのNamespaceやPodの情報をSquashが収集していることが理解できると思います。

今回はNamespace: defaultにデプロイしたため、defaultを選択します。

<img width="624" alt="スクリーンショット 2019-05-17 0.39.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/efbfd261-db40-ac83-2068-5760d7ab72fb.png">

続いて、デバッグ対象のPodを選択します。
今回は計算ロジックのバグなので、計算ロジックを実装している「example-service2」をデバッグします。

<img width="608" alt="スクリーンショット 2019-05-20 21.38.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/d578ebbb-d01d-c521-5808-222e55d23d5d.png">

続いて、デバッガーを選択します。
Go言語用のデバッガーとして「dlv」を選択します。

<img width="627" alt="スクリーンショット 2019-05-17 0.39.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/2597483f-88d1-22ab-b238-5a659907566b.png">

ここまで選択すると、裏側でデバッガー実行用のPodとして「plank」が立ち上がります。

$ kubectl get pods -n squash-debugger
NAME         READY     STATUS    RESTARTS   AGE
plank76sr6   1/1       Running   0          11s

同時にバッググラウンドのプロセスとして「kubectl port-forward plank76sr6 :38291 -n squash-debugger」が走っています。
※ポートは毎回ランダムです

このポートフォワーディングによってPlankコンテナ内のデバッガー(delve)とローカル端末のデバッガー(delve)を同期させているのだと思います(ドキュメント見当たらなかったので推測)。

Podが立ち上がると、VSCodeが自動でデバッグモードで立ち上がります。

<img width="571" alt="スクリーンショット 2019-05-20 21.49.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/a3e923bb-5da1-893d-9a6f-269328e29761.png">

デバッグ対象となるブレークポイントを設定します。

<img width="525" alt="スクリーンショット 2019-05-20 21.51.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/59eb39ce-8796-aa0c-3d7f-4688cb8be509.png">

それではデバッグを開始するために画面で操作を行います。

前回同様、「2 + 2」を入力し、「Calculate」を実行します。

<img width="787" alt="スクリーンショット 2019-05-20 21.52.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/41cf290b-eb32-63c3-77d4-21fd89cd7bd4.png">

すると、ブレークポイントでアプリの処理が止まります。

<img width="1170" alt="スクリーンショット 2019-05-20 21.53.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/899dda2c-92dd-75be-7164-7f1d1632a595.png">

あとは通常のデバッガーと同じです。
変数の中身を確認したり、ステップイン・ステップオーバーして処理を進めることができます。

<img width="1186" alt="スクリーンショット 2019-05-20 21.55.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/3e4bcdae-2393-cc99-0713-67568dc2a6dd.png">

デモアプリの説明に戻ると、次のソースの箇所で「isadd: true」の時に減算、「isadd: false」の時に加算するというアプリのバグが確認できます。

```:main.go
# 「isadd」はtrue

if isadd {
   fmt.Fprintf(w, "%d", op1-op2)
} else {
  fmt.Fprintf(w, "%d", op1+op2)
}
```

試しに「isadd」をfalseに変更してみると、正しく「2 + 2」が加算されることが分かります。
(本当はバグ部分のソースの修正が必要です)

<img width="300" alt="スクリーンショット 2019-05-20 21.59.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/70796b3f-92f7-bc5a-9fd4-9b766ac75a06.png">

<img width="786" alt="スクリーンショット 2019-05-20 22.01.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314463/19123940-1015-5a7f-ffec-ca91d8743133.png">

以上でデバッグのデモは終了です。
Plank Podはデバッグが終わっても残り続けるため、デバッグが終わったら掃除しましょう。

> デバッグ後にClean Upさせる、というIssueは上がっています
> https://github.com/solo-io/squash/issues/178

```
$ kubectl delete pods/plank76sr6 -n squash-debugger
pod "plank76sr6" deleted
```

バックグラウンドで起動しているポートフォワードも起動しっぱなしなので、プロセスも停止します。

```
$ ps aux | grep port-forward
$ kill <PID>
```

# Squashの注意事項

・デバッグが終わった後に、PlankのPodが削除されないので自分でClean Upする
・デバッグの度にソースマッピングの設定を行う必要がある

# 終わりに

Kubernetes環境をよく触るため、個人的にSquashは非常に興味を持っています。
Istioと組み合わせたマイクロサービスのデバッグや他言語のデバッグなどができないか今後も追っていきたいところです。