k8sのpodへのリクエスト回数を可視化する

# はじめに
k8s の勉強に何かを作ろうと思い、 k8s の pod へのリクエスト回数を可視化するアプリを作った。

レプリカセットが4の状態でリクエストを実行している様子
![k8s.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/d6411f34-4cf9-a305-00ab-c0cc1052ad57.gif)

また、レプリカセットによる pod の停止 => 起動 もこんな感じで眺めることができる。
（死んだ pod は赤くなって、新しい pod が下の方に追加される）
![delete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/5cd13c57-c7e4-2438-1750-cec222d22b59.gif)



pod 上で動いているサーバーの起動、停止、API 実行をクライアント側が検知できるようにするため、以下のライブラリを利用した。
redisのpub/subの機能とwebsocket、redisを組み合わせて、サーバーからクライアントへのpush通知ができるようにした。

- socket.io サーバー(Next.js) 

```
    "socket.io": "^3.1.0",
    "socket.io-emitter": "^3.2.0",
    "socket.io-redis": "^6.0.1"
```

- クライアント(Next.js)

```
    "socket.io-client": "^3.1.0",
```

- API が実行されると redis のパブリッシュを実行するサーバー(golang)

このサーバーがレプリカセットで複数起動される

```
	"github.com/go-redis/redis/v8"
```

- redis

```
        - image: redis:6.0
```

# コード

- フロントエンド / socket.io サーバー (Next.js)
https://github.com/pokotyan/k8s-request-counter

- redis サーバーと、 redis のパブリッシュを実行するサーバー (golang)
https://github.com/pokotyan/k8s-request-counter-emitter

# 流れ
APIの実行をクライアント側へpush通知する流れは以下の通り。

1) 複数の pod 上で起動される go のサーバーの API が実行されると、 redis のパブリッシュが行われる

```golang
	if err := rdb.Publish(ctx, "EXEC_API", os.Getenv("POD_NAME")).Err(); err != nil {
		fmt.Println(err)
	}
```

2) redis イベントを受け取った socket.io サーバーは websocket のイベントを発火

```typescript
  redis.on("message", (channel, message) => {
    switch (channel) {
      case "EXEC_API":
        emitter.broadcast.emit("NOTICE_EXEC_API", message);
        break;
```

3) websocket のイベントを subscribe しているクライアント側で、リクエスト回数を描画する

```typescript
  useEffect(() => {
    const socket = io();
    socket.on("connect", () => {
      console.log(`connected, socket_id: ${socket.id}`);
    });
    socket.on("disconnect", () => {
      console.log("socket disconnected!!");
    });
    socket.on("NOTICE_EXEC_API", (podName: string) => {
      // リクエスト回数を更新する処理
    });
```

これと同じ要領で、サーバーの停止、起動時にも redisのイベントをpublishし、同様の流れでクライアント側へイベント通知している。

サーバー起動時

```go
		if err := rdb.Publish(ctx, "READY_ON", os.Getenv("POD_NAME")).Err(); err != nil {
			fmt.Println(err)
		}
```

サーバー停止時

```go
	if err := rdb.Publish(ctx, "SHUTDOWN", os.Getenv("POD_NAME")).Err(); err != nil {
		fmt.Println(err)
	}
```


# k8s
こんな感じの構成。
単一の redis の pod と、レプリカセットで複数起動される golang の pod を k8s で用意した。

![screencapture-localhost-8080-applications-k8s-request-counter-emitter-2021-01-30-14_04_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/ef30ff06-6e9b-0c33-38dc-8c54f8932f32.png)


## redis
redisは NodePort のサービスで起動した後、 `kubectl port-forward svc/redis-service 6379:6379` を実行し、localhost上でlistenされるようにする。

これにより socket.io のサーバーからは localhost で redis にアクセスできるようになる。

```typescript
import redis from "redis";

const client = redis.createClient(6379, "localhost");

client.subscribe("EXEC_API");
client.subscribe("SHUTDOWN");

export { client };
```

go のサーバーからは `サービス名:port番号` で redis にアクセスする。

```golang
	rdb = redis.NewClient(&redis.Options{
		Addr: "redis-service:6379",
	})
```

## redis の publish をする go のサーバー
勉強のために、 ingress(nginx) => service（NodePort） => deployment(nginx、golang) の構成で作った。

# 終わりに
やはり、実際に手元で触ってみることで理解が進んだ気がする。（作るものをどうするかでいつも悩む）
今回はローカルで動かしただけなので、これを次はちゃんとした環境にデプロイしてみる