T-CREATOR

Kubernetes で WebSocket:Ingress(NGINX/ALB) 設定とスティッキーセッションの実装手順

Kubernetes で WebSocket:Ingress(NGINX/ALB) 設定とスティッキーセッションの実装手順

Kubernetes 環境で WebSocket を使ったリアルタイム通信を実装する際、Ingress の設定で躓いた経験はありませんか?

WebSocket は HTTP とは異なる特性を持つため、適切な設定を行わないと接続が切断されたり、セッションが維持できなかったりする問題が発生します。特に Kubernetes のような分散環境では、複数の Pod 間でのセッション管理が課題となるでしょう。

本記事では、NGINX Ingress と AWS ALB Ingress の両方における WebSocket 設定と、スティッキーセッションの実装手順を詳しく解説します。実際の YAML ファイルやコード例を交えながら、初心者の方でも実装できるように段階的に説明していきますね。

背景

WebSocket とは

WebSocket は、クライアントとサーバー間で双方向のリアルタイム通信を可能にするプロトコルです。

従来の HTTP 通信では、クライアントがリクエストを送信し、サーバーがレスポンスを返すという一方向の通信でした。しかし、WebSocket を使うことで、一度接続を確立すれば、サーバーからクライアントへ自発的にデータを送信できるようになります。

#項目HTTPWebSocket
1通信方向単方向(リクエスト → レスポンス)双方向(相互通信)
2接続維持都度切断長時間接続
3オーバーヘッド大きい(毎回ヘッダー送信)小さい(初回のみ)
4リアルタイム性低い(ポーリング必要)高い(即座に送信)
5用途例Web ページ表示、API 呼び出しチャット、通知、ゲーム

Kubernetes における WebSocket の課題

Kubernetes 環境では、複数の Pod がロードバランサーの背後に配置されます。

WebSocket は長時間接続を維持する必要があるため、通常の HTTP トラフィックとは異なる考慮が必要になるんです。特に、Ingress を経由する場合、適切な設定を行わないと接続が予期せず切断されてしまいます。

以下の図で、Kubernetes における WebSocket 通信の基本的な流れを確認しましょう。

mermaidflowchart LR
  client["クライアント<br/>(ブラウザ)"] -->|"1. WebSocket接続要求<br/>(HTTP Upgrade)"| ingress["Ingress<br/>(NGINX/ALB)"]
  ingress -->|"2. 接続振り分け"| pod1["Pod 1<br/>(WebSocketサーバー)"]
  ingress -.->|"振り分け候補"| pod2["Pod 2<br/>(WebSocketサーバー)"]
  ingress -.->|"振り分け候補"| pod3["Pod 3<br/>(WebSocketサーバー)"]
  pod1 -->|"3. 双方向通信<br/>(長時間接続)"| ingress
  ingress -->|"4. データ転送"| client

図で理解できる要点:

  • クライアントは HTTP Upgrade リクエストで WebSocket 接続を開始する
  • Ingress が複数の Pod から 1 つを選択して接続を確立する
  • 確立後は同じ Pod との間で双方向通信が継続される

課題

接続の切断問題

Kubernetes で WebSocket を使う際、最も頻繁に遭遇するのが「接続が突然切断される」という問題です。

この問題は主に以下の原因で発生します。

  1. タイムアウト設定が短すぎる:Ingress やロードバランサーのアイドルタイムアウトが短いと、通信が発生しない時間が続いた際に接続が切られてしまいます。
  2. HTTP Upgrade ヘッダーが正しく処理されない:WebSocket は HTTP から WebSocket プロトコルへの「アップグレード」を行いますが、この処理が適切に設定されていないと接続に失敗するでしょう。
  3. プロキシのバッファリング:一部のプロキシは応答をバッファリングするため、リアルタイム性が損なわれます。

セッションアフィニティの必要性

WebSocket 接続では、クライアントが常に同じ Pod に接続し続ける必要があります。

なぜなら、WebSocket のセッション状態(接続情報、メッセージ履歴など)は各 Pod のメモリ内に保持されるため、接続先の Pod が変わってしまうとセッションが失われてしまうからです。

以下の図で、スティッキーセッションがない場合の問題を可視化しました。

mermaidsequenceDiagram
  participant Client as クライアント
  participant Ingress as Ingress
  participant Pod1 as Pod 1
  participant Pod2 as Pod 2

  Client->>Ingress: 初回接続要求
  Ingress->>Pod1: 接続確立
  Pod1->>Pod1: セッション作成(メモリ)
  Pod1-->>Client: 接続成功

  Note over Client,Pod1: 一定時間通信

  Client->>Ingress: 再接続要求
  Ingress->>Pod2: 別Podへ振り分け
  Pod2->>Pod2: セッション情報なし❌
  Pod2-->>Client: セッションエラー

図で理解できる要点:

  • スティッキーセッションがないと、再接続時に別の Pod へ振り分けられる可能性がある
  • 新しい Pod にはセッション情報が存在しないため、エラーが発生する
  • 一貫した接続先を維持する仕組みが必要

NGINX Ingress と ALB Ingress の違い

Kubernetes では、Ingress Controller として様々な選択肢があります。

特に一般的なのが NGINX Ingress と AWS ALB Ingress ですが、それぞれ設定方法や機能が異なるため、環境に応じた適切な設定が必要になりますね。

#項目NGINX IngressAWS ALB Ingress
1提供元Kubernetes コミュニティAWS
2動作場所クラスタ内 PodAWS マネージドサービス
3設定方法AnnotationAnnotation + TargetGroup
4スティッキーセッションCookie ベースTargetGroup 設定
5タイムアウト設定ConfigMap / AnnotationAnnotation
6適用環境あらゆる環境AWS EKS 専用

解決策

NGINX Ingress での WebSocket 設定

NGINX Ingress で WebSocket を有効にするには、いくつかの Annotation を追加する必要があります。

これらの設定により、HTTP から WebSocket へのアップグレードが正しく処理され、長時間接続が維持されるようになるんです。

以下の図で、NGINX Ingress における WebSocket 処理の流れを確認しましょう。

mermaidflowchart TB
  request["HTTP Upgradeリクエスト"] --> nginx["NGINX Ingress"]

  nginx --> check1{"Upgrade<br/>ヘッダー<br/>確認"}
  check1 -->|"存在する"| upgrade["WebSocket<br/>プロトコルへ<br/>アップグレード"]
  check1 -->|"存在しない"| http["通常のHTTP<br/>処理"]

  upgrade --> sticky{"スティッキー<br/>セッション<br/>設定"}
  sticky -->|"有効"| cookie["Cookie発行<br/>(route)"]
  sticky -->|"無効"| random["ランダムに<br/>Pod選択"]

  cookie --> pod_select["同一Podへ<br/>接続維持"]
  random --> pod_random["毎回異なる<br/>Podの可能性"]

  pod_select --> success["接続成功✓"]
  pod_random --> fail["セッション<br/>エラー❌"]
  http --> success

図で理解できる要点:

  • NGINX は Upgrade ヘッダーの有無で処理を分岐する
  • スティッキーセッションが有効な場合、Cookie で接続先を記憶する
  • 適切な設定がないとセッションエラーが発生する

基本的な Annotation の設定

まず、WebSocket 接続を有効にするための基本的な Annotation を見ていきましょう。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # WebSocket 接続を有効化
    nginx.ingress.kubernetes.io/proxy-read-timeout: '3600'
    nginx.ingress.kubernetes.io/proxy-send-timeout: '3600'

これらのタイムアウト設定は秒単位で指定します。3600 秒(1 時間)に設定することで、長時間のアイドル状態でも接続が切断されにくくなりますね。

HTTP Upgrade ヘッダーの処理

WebSocket は HTTP から WebSocket プロトコルへの「アップグレード」を必要とします。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # WebSocket の Upgrade ヘッダーを適切に処理
    nginx.ingress.kubernetes.io/websocket-services: 'websocket-service'

この設定により、NGINX は指定されたサービスへの接続時に WebSocket プロトコルへのアップグレードを自動的に処理してくれます。

スティッキーセッションの実装

同じクライアントを常に同じ Pod に接続させるには、スティッキーセッション(アフィニティ)の設定が必要です。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # スティッキーセッションを有効化
    nginx.ingress.kubernetes.io/affinity: 'cookie'
    # Cookie の名前を指定
    nginx.ingress.kubernetes.io/session-cookie-name: 'route'
    # Cookie の有効期限(秒)
    nginx.ingress.kubernetes.io/session-cookie-max-age: '3600'

この設定により、NGINX は「route」という名前の Cookie を発行し、その Cookie を持つクライアントを常に同じ Pod に振り分けるようになります。

バッファリングの無効化

リアルタイム性を確保するため、プロキシのバッファリングを無効にしましょう。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # レスポンスのバッファリングを無効化
    nginx.ingress.kubernetes.io/proxy-buffering: 'off'
    # リクエストのバッファリングを無効化
    nginx.ingress.kubernetes.io/proxy-request-buffering: 'off'

バッファリングを無効にすることで、メッセージが即座にクライアントへ届くようになり、リアルタイム性が向上します。

AWS ALB Ingress での WebSocket 設定

AWS ALB Ingress を使う場合は、NGINX とは異なる設定方法を使います。

ALB は AWS のマネージドロードバランサーであるため、一部の設定は AWS の機能を直接活用する形になるんです。

基本的な Annotation の設定

ALB で WebSocket を有効にする基本設定を見ていきましょう。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # ALB Ingress Controller を使用
    kubernetes.io/ingress.class: alb
    # インターネット向け ALB を作成
    alb.ingress.kubernetes.io/scheme: internet-facing

これらは ALB Ingress を使用するための基本的な設定です。

ターゲットタイプの指定

ALB がバックエンドの Pod にどのように接続するかを指定します。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # IP モードでターゲットを指定(Pod の IP に直接接続)
    alb.ingress.kubernetes.io/target-type: ip

ip モードを指定することで、ALB が Pod の IP アドレスに直接接続するため、より効率的な通信が可能になります。

アイドルタイムアウトの設定

ALB のアイドルタイムアウトを長めに設定することで、WebSocket 接続が維持されやすくなります。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # アイドルタイムアウトを 3600 秒(1 時間)に設定
    alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=3600

この設定により、1 時間までアイドル状態が続いても接続が切断されません。

スティッキーセッションの設定

ALB でスティッキーセッションを有効にするには、TargetGroup の設定を行います。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # スティッキーセッションを有効化
    alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=3600

この設定により、ALB が発行する Cookie を使って同じターゲット(Pod)への接続が維持されるようになりますね。

ヘルスチェックの設定

WebSocket サービスに対して適切なヘルスチェックを設定することも重要です。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: websocket-ingress
  annotations:
    # ヘルスチェックのパス
    alb.ingress.kubernetes.io/healthcheck-path: /health
    # ヘルスチェックのプロトコル
    alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
    # ヘルスチェックの間隔(秒)
    alb.ingress.kubernetes.io/healthcheck-interval-seconds: '30'

ヘルスチェックを適切に設定することで、異常な Pod へのトラフィック振り分けを防げます。

具体例

NGINX Ingress の完全な設定例

実際のプロダクション環境で使用できる、NGINX Ingress の完全な設定例を見ていきましょう。

この例では、チャットアプリケーションを想定して、WebSocket サーバーと通常の HTTP API サーバーを組み合わせた構成を示します。

Service の定義

まず、WebSocket サーバーを公開するための Service を定義しましょう。

yamlapiVersion: v1
kind: Service
metadata:
  name: websocket-service
  labels:
    app: chat-server
spec:
  # ClusterIP タイプで内部公開
  type: ClusterIP
  ports:
    # WebSocket 接続用のポート
    - port: 8080
      targetPort: 8080
      protocol: TCP
      name: websocket
  selector:
    # Pod のラベルと一致させる
    app: chat-server

Service は ClusterIP タイプで作成し、Ingress を経由してのみアクセス可能にします。

Deployment の定義

次に、WebSocket サーバーを実行する Pod の定義です。

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: chat-server
spec:
  # 3 つの Pod を起動
  replicas: 3
  selector:
    matchLabels:
      app: chat-server
  template:
    metadata:
      labels:
        app: chat-server
    spec:
      containers:
        - name: chat-server
          # WebSocket サーバーのイメージ
          image: your-registry/chat-server:latest
          ports:
            # コンテナのポート
            - containerPort: 8080
              name: websocket

この例では 3 つのレプリカを起動し、負荷分散とスティッキーセッションの動作を確認できるようにしています。

Ingress の完全な定義

すべての設定を組み合わせた Ingress の定義を見てみましょう。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: chat-ingress
  annotations:
    # タイムアウト設定(1 時間)
    nginx.ingress.kubernetes.io/proxy-read-timeout: '3600'
    nginx.ingress.kubernetes.io/proxy-send-timeout: '3600'
    nginx.ingress.kubernetes.io/proxy-connect-timeout: '60'

    # WebSocket の有効化
    nginx.ingress.kubernetes.io/websocket-services: 'websocket-service'

    # スティッキーセッション設定
    nginx.ingress.kubernetes.io/affinity: 'cookie'
    nginx.ingress.kubernetes.io/session-cookie-name: 'chat_route'
    nginx.ingress.kubernetes.io/session-cookie-max-age: '3600'
    nginx.ingress.kubernetes.io/session-cookie-path: '/'
    nginx.ingress.kubernetes.io/session-cookie-secure: 'true'
    nginx.ingress.kubernetes.io/session-cookie-samesite: 'Lax'

Cookie に関する詳細な設定により、セキュリティとセッション管理の両立を実現しています。

yaml    # バッファリング無効化
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
    nginx.ingress.kubernetes.io/proxy-request-buffering: "off"

    # 大きなヘッダーサイズを許可
    nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
spec:
  ingressClassName: nginx
  rules:
    - host: chat.example.com
      http:
        paths:
          # WebSocket エンドポイント
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: websocket-service
                port:
                  number: 8080

この設定により、chat.example.com​/​ws へのアクセスが WebSocket サーバーにルーティングされます。

TLS の追加設定

HTTPS/WSS 接続を使う場合の TLS 設定も追加しましょう。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: chat-ingress
  annotations:
    # 前述の annotations に加えて
    # HTTPS へのリダイレクト
    nginx.ingress.kubernetes.io/ssl-redirect: 'true'
    # HTTP/2 の有効化
    nginx.ingress.kubernetes.io/http2-push-preload: 'true'
spec:
  ingressClassName: nginx
  # TLS 証明書の設定
  tls:
    - hosts:
        - chat.example.com
      # Secret に保存された証明書を参照
      secretName: chat-tls-secret
  rules:
    - host: chat.example.com
      http:
        paths:
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: websocket-service
                port:
                  number: 8080

TLS を設定することで、暗号化された安全な WebSocket 接続(WSS)が利用できるようになりますね。

AWS ALB Ingress の完全な設定例

AWS EKS 環境で ALB Ingress を使った WebSocket 設定の完全な例を見ていきます。

Service の定義(ALB 用)

ALB Ingress でも基本的な Service 定義は同じです。

yamlapiVersion: v1
kind: Service
metadata:
  name: websocket-service
  labels:
    app: realtime-server
spec:
  type: NodePort
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    app: realtime-server

ALB の場合、ターゲットタイプを ip にする場合でも、Service のタイプは NodePort または ClusterIP で問題ありません。

Ingress の完全な定義(ALB)

すべての ALB 設定を含んだ Ingress リソースです。

yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: realtime-ingress
  annotations:
    # ALB Ingress Controller の指定
    kubernetes.io/ingress.class: alb

    # インターネット向け ALB
    alb.ingress.kubernetes.io/scheme: internet-facing

    # ターゲットタイプ(Pod の IP に直接接続)
    alb.ingress.kubernetes.io/target-type: ip

    # リスニングポート(HTTPS)
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'

    # HTTP から HTTPS へのリダイレクト
    alb.ingress.kubernetes.io/ssl-redirect: '443'

まずは基本的な ALB の設定と、HTTP/HTTPS のリスニングポートを定義します。

yaml# SSL 証明書の ARN(AWS Certificate Manager)
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxx

# アイドルタイムアウト(3600 秒 = 1 時間)
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=3600

# スティッキーセッション設定
alb.ingress.kubernetes.io/target-group-attributes: |
  stickiness.enabled=true,
  stickiness.type=lb_cookie,
  stickiness.lb_cookie.duration_seconds=3600

スティッキーセッションの設定では、Cookie の有効期限を 3600 秒(1 時間)に設定しています。

yaml# ヘルスチェック設定
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-path: /health
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '30'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5'
alb.ingress.kubernetes.io/success-codes: '200'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'

ヘルスチェックの設定により、異常な Pod を迅速に検出して切り離せます。

yaml    # タグ設定(AWS リソース管理用)
    alb.ingress.kubernetes.io/tags: |
      Environment=production,
      Application=realtime-chat,
      ManagedBy=kubernetes
spec:
  rules:
    - host: realtime.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: websocket-service
                port:
                  number: 8080

タグを設定することで、AWS コンソールでのリソース管理やコスト追跡が容易になりますね。

クライアント側の実装例

サーバー側の設定だけでなく、クライアント側でも適切な実装が必要です。

JavaScript/TypeScript での WebSocket 接続

ブラウザから WebSocket に接続する基本的なコード例を見ていきましょう。

typescript// WebSocket クラスのインスタンスを作成
// wss:// は WSS(WebSocket Secure)プロトコルを使用
const ws = new WebSocket('wss://chat.example.com/ws');

HTTPS サイトから接続する場合は、必ず wss:​/​​/​(WebSocket Secure)を使用しましょう。

typescript// 接続が確立された時のイベントハンドラー
ws.addEventListener('open', (event) => {
  console.log('WebSocket 接続が確立されました');

  // サーバーにメッセージを送信
  ws.send(
    JSON.stringify({
      type: 'join',
      userId: 'user123',
      room: 'general',
    })
  );
});

接続確立時に認証情報や初期設定を送信することが一般的です。

typescript// メッセージ受信時のイベントハンドラー
ws.addEventListener('message', (event) => {
  // 受信データを JSON としてパース
  const data = JSON.parse(event.data);

  console.log('メッセージを受信:', data);

  // メッセージタイプに応じて処理を分岐
  switch (data.type) {
    case 'chat':
      displayChatMessage(data);
      break;
    case 'notification':
      showNotification(data);
      break;
  }
});

メッセージの内容に応じて適切な処理を行うことで、柔軟なリアルタイム通信が実現できます。

typescript// エラー発生時のイベントハンドラー
ws.addEventListener('error', (event) => {
  console.error('WebSocket エラーが発生しました:', event);

  // エラー通知を表示
  showErrorNotification('接続エラーが発生しました');
});

エラーハンドリングを適切に行うことで、ユーザー体験が向上しますね。

typescript// 接続が閉じられた時のイベントハンドラー
ws.addEventListener('close', (event) => {
  console.log('WebSocket 接続が閉じられました');
  console.log('クローズコード:', event.code);
  console.log('理由:', event.reason);

  // 意図しない切断の場合は再接続を試みる
  if (!event.wasClean) {
    setTimeout(() => {
      reconnectWebSocket();
    }, 3000); // 3 秒後に再接続
  }
});

予期しない切断が発生した場合に自動的に再接続する仕組みを実装することが重要です。

再接続ロジックの実装

ネットワークの不安定さに対応するため、再接続ロジックを実装しましょう。

typescript// 再接続の最大試行回数
const MAX_RECONNECT_ATTEMPTS = 5;
// 現在の再接続試行回数
let reconnectAttempts = 0;

まず、再接続に関する設定値を定義します。

typescriptfunction reconnectWebSocket() {
  // 最大試行回数に達した場合は終了
  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
    console.error('最大再接続回数に達しました');
    showErrorNotification(
      '接続できませんでした。ページを再読み込みしてください。'
    );
    return;
  }

  reconnectAttempts++;
  console.log(
    `再接続を試行中 (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`
  );

  // 指数バックオフで待機時間を増やす
  const delay = Math.min(
    1000 * Math.pow(2, reconnectAttempts),
    30000
  );

  setTimeout(() => {
    createWebSocketConnection();
  }, delay);
}

指数バックオフアルゴリズムを使うことで、サーバーへの負荷を軽減しながら再接続を試みられます。

typescriptfunction createWebSocketConnection() {
  const ws = new WebSocket('wss://chat.example.com/ws');

  ws.addEventListener('open', () => {
    // 再接続成功時はカウンターをリセット
    reconnectAttempts = 0;
    console.log('再接続に成功しました');
  });

  // 他のイベントハンドラーも設定...
}

再接続が成功したら、試行回数をリセットすることを忘れないようにしましょう。

Node.js サーバー側の実装例

Kubernetes で動作する WebSocket サーバーの実装例も見ていきます。

Express と ws ライブラリを使った実装

まず、必要なパッケージをインストールしましょう。

bash# Express と WebSocket ライブラリをインストール
yarn add express ws
# TypeScript の型定義もインストール
yarn add -D @types/express @types/ws

これらのパッケージを使って、シンプルな WebSocket サーバーを構築できます。

typescriptimport express from 'express';
import { WebSocketServer } from 'ws';
import { createServer } from 'http';

// Express アプリケーションを作成
const app = express();

// HTTP サーバーを作成(Express と WebSocket で共有)
const server = createServer(app);

// WebSocket サーバーを作成
const wss = new WebSocketServer({
  server,
  path: '/ws', // WebSocket のパス
});

HTTP サーバーを Express と WebSocket サーバーで共有することで、同じポートで両方のプロトコルを扱えます。

typescript// ヘルスチェック用のエンドポイント
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'healthy',
    connections: wss.clients.size, // 現在の接続数
  });
});

Kubernetes のヘルスチェックに応答するエンドポイントを用意することが重要ですね。

typescript// クライアント接続時の処理
wss.on('connection', (ws, req) => {
  // クライアントの IP アドレスを取得
  const clientIp = req.socket.remoteAddress;
  console.log(`新しい接続: ${clientIp}`);

  // 接続時にウェルカムメッセージを送信
  ws.send(JSON.stringify({
    type: 'welcome',
    message: 'WebSocket サーバーに接続しました'
  }));

接続時にクライアントへ情報を送信することで、接続が正常に確立されたことを確認できます。

typescript// メッセージ受信時の処理
ws.on('message', (data) => {
  try {
    // 受信データを JSON としてパース
    const message = JSON.parse(data.toString());
    console.log('受信メッセージ:', message);

    // すべての接続クライアントにブロードキャスト
    wss.clients.forEach((client) => {
      if (client.readyState === ws.OPEN) {
        client.send(JSON.stringify(message));
      }
    });
  } catch (error) {
    console.error('メッセージ処理エラー:', error);
  }
});

受信したメッセージを全クライアントにブロードキャストする基本的なチャット機能です。

typescript  // 接続が閉じられた時の処理
  ws.on('close', (code, reason) => {
    console.log(`接続が閉じられました: ${clientIp}`);
    console.log(`コード: ${code}, 理由: ${reason}`);
  });

  // エラー発生時の処理
  ws.on('error', (error) => {
    console.error('WebSocket エラー:', error);
  });
});

エラーや切断のログを適切に記録することで、問題の診断が容易になります。

typescript// サーバーを起動
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
  console.log(`サーバーがポート ${PORT} で起動しました`);
});

// グレースフルシャットダウン
process.on('SIGTERM', () => {
  console.log('SIGTERM シグナルを受信しました');

  // 新規接続を拒否
  server.close(() => {
    console.log('サーバーを停止しました');
    process.exit(0);
  });
});

Kubernetes の Pod 終了時に接続を適切に閉じることで、クライアントへの影響を最小限に抑えられますね。

まとめ

Kubernetes 環境で WebSocket を使ったリアルタイム通信を実装するには、Ingress の適切な設定とスティッキーセッションの実装が欠かせません。

本記事では、NGINX Ingress と AWS ALB Ingress の両方について、具体的な設定方法を解説しました。重要なポイントを振り返ってみましょう。

NGINX Ingress のポイント:

  • proxy-read-timeoutproxy-send-timeout でタイムアウトを延長する
  • websocket-services アノテーションで WebSocket を有効化する
  • affinity: cookie でスティッキーセッションを実装する
  • proxy-buffering: off でリアルタイム性を確保する

AWS ALB Ingress のポイント:

  • idle_timeout.timeout_seconds でアイドルタイムアウトを設定する
  • target-type: ip で Pod への直接接続を実現する
  • stickiness.enabled=true でスティッキーセッションを有効化する
  • ヘルスチェックを適切に設定して異常 Pod を検出する

クライアント・サーバー実装のポイント:

  • 再接続ロジックを実装してネットワークの不安定さに対応する
  • エラーハンドリングを適切に行いユーザー体験を向上させる
  • グレースフルシャットダウンで Pod 終了時の影響を最小化する

これらの設定を適切に組み合わせることで、安定した WebSocket 通信が実現できるでしょう。

環境に応じて NGINX Ingress と ALB Ingress を使い分け、要件に合った最適な構成を選択してください。本記事の設定例を参考に、ぜひ実際のプロジェクトで WebSocket を活用してみてくださいね。

関連リンク