はじめに
k8s の勉強に何かを作ろうと思い、 k8s の pod へのリクエスト回数を可視化するアプリを作った。
また、レプリカセットによる pod の停止 => 起動 もこんな感じで眺めることができる。
(死んだ pod は赤くなって、新しい pod が下の方に追加される)
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通知する流れは以下の通り。
- 複数の pod 上で起動される go のサーバーの API が実行されると、 redis のパブリッシュが行われる
if err := rdb.Publish(ctx, "EXEC_API", os.Getenv("POD_NAME")).Err(); err != nil {
fmt.Println(err)
}
- redis イベントを受け取った socket.io サーバーは websocket のイベントを発火
redis.on("message", (channel, message) => {
switch (channel) {
case "EXEC_API":
emitter.broadcast.emit("NOTICE_EXEC_API", message);
break;
- websocket のイベントを subscribe しているクライアント側で、リクエスト回数を描画する
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し、同様の流れでクライアント側へイベント通知している。
サーバー起動時
if err := rdb.Publish(ctx, "READY_ON", os.Getenv("POD_NAME")).Err(); err != nil {
fmt.Println(err)
}
サーバー停止時
if err := rdb.Publish(ctx, "SHUTDOWN", os.Getenv("POD_NAME")).Err(); err != nil {
fmt.Println(err)
}
k8s
こんな感じの構成。
単一の redis の pod と、レプリカセットで複数起動される golang の pod を k8s で用意した。
redis
redisは NodePort のサービスで起動した後、 kubectl port-forward svc/redis-service 6379:6379
を実行し、localhost上でlistenされるようにする。
これにより socket.io のサーバーからは localhost で redis にアクセスできるようになる。
import redis from "redis";
const client = redis.createClient(6379, "localhost");
client.subscribe("EXEC_API");
client.subscribe("SHUTDOWN");
export { client };
go のサーバーからは サービス名:port番号
で redis にアクセスする。
rdb = redis.NewClient(&redis.Options{
Addr: "redis-service:6379",
})
redis の publish をする go のサーバー
勉強のために、 ingress(nginx) => service(NodePort) => deployment(nginx、golang) の構成で作った。
終わりに
やはり、実際に手元で触ってみることで理解が進んだ気がする。(作るものをどうするかでいつも悩む)
今回はローカルで動かしただけなので、これを次はちゃんとした環境にデプロイしてみる