コンテンツにスキップ

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`)に応じて、接続数に基づいてスケーリングできます。

サイトのトラフィックを理解するために、アナリティクスと Cookie を使用しています。サイトの使用に関する情報は、その目的のために Google と共有されます。 詳細はこちら。