Golang を使用した Knative での WebSocket アプリケーションの構築 ¶
公開日: 2024-09-11
Golang を使用した Knative での WebSocket アプリケーションの構築¶
著者:Matthias Weßendorf、Red Hat シニアプリンシパルソフトウェアエンジニア
Knative Serving は、サーバーレスワークロードを実行するための `HTTP`、`HTTP/2`、`gRPC`、`WebSocket` などの複数のプロトコルをサポートしています。 WebSocket プロトコル は、クライアントアプリケーションとサーバー間の単一の TCP 接続を介した全二重通信を可能にします。 WebSocket プロトコルは、HTTP「アップグレード」リクエストとして開始され、接続を HTTP から WebSocket プロトコルに切り替えることで、ピア間のリアルタイムの双方向通信を可能にします。この記事では、Knative を使用してスケーラブルな WebSocket サーバーを構築する方法を説明します。
注記
ここで説明するサンプルアプリケーションは、Knative ドキュメントリポジトリ にあります。
HTTP アップグレード¶
すべての WebSocket アプリケーションは HTTP アップグレードリクエストで開始されるため、従来の HTTP リクエストを処理する Golang ベースの Web サーバーを定義する必要があります。 WebSocket の実装には、`gorilla/websocket` パッケージが使用されます。これは、Golang 用の高速で十分にテストされ、広く使用されている WebSocket 実装を提供します。以下は、サンプルアプリケーションの HTTP 部分です。
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
"github.com/knative/docs/code-samples/serving/websockets-go/pkg/handlers"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
`main` 関数は `/ws` コンテキストで Web サーバーを起動し、`handleWebSocket` ハンドラ関数を登録します。
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
`handleWebSocket` はプロトコルアップグレードを実行し、`OnOpen` や `OnMessage` などのさまざまな WebSocket ハンドラ関数を割り当てます。
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
注記
関心の分離を明確にするために、異なる `On...` ハンドラは別々の `handlers` パッケージにあります。
WebSocket ハンドラ¶
WebSocket アプリケーションロジックは `pkg/handlers/handlers.go` ファイルにあり、各 WebSocket イベントのコールバックが含まれています。
func OnOpen(conn *websocket.Conn) {
log.Printf("WebSocket connection opened: %v", conn.RemoteAddr())
}
func OnMessage(conn *websocket.Conn, messageType int, message []byte) {
log.Printf("Received message from %v: %s", conn.RemoteAddr(), string(message))
if err := conn.WriteMessage(messageType, message); err != nil {
log.Printf("Error sending message: %v", err)
}
}
func OnClose(conn *websocket.Conn) {
log.Printf("WebSocket connection closed: %v", conn.RemoteAddr())
conn.Close()
}
func OnError(conn *websocket.Conn, err error) {
log.Printf("WebSocket error from %v: %v", conn.RemoteAddr(), err)
}
各ハンドラでは、接続されたピアのリモートアドレスと、`OnMessage` ハンドラを介して受信したメッセージをログに記録し、送信者にエコーバックします。
WebSocket サーバーのテスト¶
注記
Golang アプリケーションをビルドしてデプロイする最も簡単な方法は、実際の サンプルアプリケーション で説明されているように、ko を使用することです。
Knative Serving アプリケーションが Kubernetes クラスターにデプロイされたら、それをテストできます。そのためには、WebSocket アプリケーションのサービスの実際の URL を取得する必要があります。
kubectl get ksvc
NAME URL LATESTCREATED LATESTREADY READY REASON
websocket-server http://websocket-server.default.svc.cluster.local websocket-server-00001 websocket-server-00001 True
次に、サービスに接続するためのクライアントが必要です。そのためには、Kubernetes 内で Linux コンテナを起動し、wscat を実行して WebSocket サーバーアプリケーションに接続します。
kubectl run --rm -i --tty wscat --image=monotykamary/wscat --restart=Never -- -c ws://websocket-server.default.svc.cluster.local/ws
その後、次のように WebSocket サーバーとチャットできます。
```If you don't see a command prompt, try pressing enter.
```connected (press CTRL+C to quit)
```> Hello
```< Hello
```>
上記は、1 つのクライアントのみが接続されているため、正確に 1 つのポッドにスケーリングされます。 Knative Serving では動的スケーリングが可能なため、一定数の同時接続があると、ポッドの数が増えます。
以下は、多数の同時リクエストを実行した場合にスケールアウトされたアプリケーションの例です。
k get pods
NAME READY STATUS RESTARTS AGE
websocket-server-00001-deployment-f785cbd65-42mrn 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-76bjr 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-98cwb 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-9fdbl 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-bpvjj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-c6f47 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-fl6kk 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-gnffw 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-hqpfx 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-j4v9d 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-j72vk 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-k856w 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-kmmng 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-l4f2v 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-lpfr9 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-mn26w 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-mr98h 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-prjmj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-v696v 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-ws5k9 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-xmszx 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-znhrr 2/2 Running 0 20s
注記
ターゲットアノテーション(`autoscaling.knative.dev/target`)に応じて、接続数に基づいてスケーリングできます。