WebSocket は、クライアントとサーバー間の双方向通信を可能にする通信プロトコルです。WebSocket プロトコルは RFC 6455 で標準化されています。Istio サイドカープロキシは WebSocket プロトコルを標準でサポートしており、WebSocket 接続を簡単に確立することでサービスにアクセスできます。このトピックでは、Alibaba Cloud Service Mesh(ASM)で HTTP/1.1 または HTTP/2 経由で WebSocket 接続を確立してサービスにアクセスする方法について説明します。
前提条件
Service Mesh(ASM)インスタンスが作成されています。ACK クラスタが ASM インスタンスに追加されています。詳細については、「ASM インスタンスを作成する」および「ASM インスタンスにクラスタを追加する」をご参照ください。
指定された名前空間でサイドカーインジェクションが有効になっています。詳細については、「サイドカープロキシインジェクションポリシーを設定する」をご参照ください。この例では、default 名前空間を使用します。
背景情報
WebSocket 接続は、HTTP Upgrade ヘッダーを使用して確立されます。これは、HTTP 接続を確立する方法とは異なります。Istio は WebSocket プロトコルを認識できません。ただし、Istio サイドカープロキシは WebSocket プロトコルを標準でサポートしています。Istio で WebSocket プロトコルを使用する方法の詳細については、「HTTP アップグレード」、「HTTP 接続マネージャー」、および「プロトコルの選択」をご参照ください。
HTTP/1.1 経由の WebSocket 接続と HTTP/2 経由の WebSocket 接続には、次の違いがあります。
HTTP/1.1 経由の WebSocket 接続: 1 つのリクエストのみを処理するために接続が確立されます。リクエストに対するレスポンスが返されると、接続は閉じられます。
HTTP/2 経由の WebSocket 接続: 複数のリクエストを 1 つの接続で並列処理できます。1 つのリクエストに時間がかかる場合でも、同じ接続上の他のリクエストは影響を受けません。
WebSocket クライアントと WebSocket サーバーをデプロイする
kubectl を使用して ACK クラスタに接続します。詳細については、「クラスタの kubeconfig ファイルを取得し、kubectl を使用してクラスタに接続する」をご参照ください。
WebSocket サーバーをデプロイします。
この例では、WebSocket コミュニティによって提供される Python 用の WebSocket サーバーを使用します。WebSocket サーバーをデプロイするための設定を変更する方法の詳細については、「Kubernetes にデプロイする」をご参照ください。
次の内容を含む websockets-server.yaml ファイルを作成します。
apiVersion: v1 kind: Service metadata: name: websockets-server labels: app: websockets-server spec: type: ClusterIP ports: - port: 8080 targetPort: 80 name: http-websocket selector: app: websockets-server --- apiVersion: apps/v1 kind: Deployment metadata: name: websockets-server labels: app: websockets-server spec: selector: matchLabels: app: websockets-server template: metadata: labels: app: websockets-server spec: containers: - name: websockets-test image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/istio-websockets-test:1.0 ports: - containerPort: 80default 名前空間に WebSocket サーバーをデプロイします。
kubectl apply -f websockets-server.yaml -n default
WebSocket クライアントをデプロイします。
この例では、Dockerfile を使用して WebSocket クライアントのイメージをビルドします。websockets-client.yaml ファイルには、WebSocket クライアントのイメージを使用して WebSocket クライアントをクラスタにデプロイするための設定が含まれています。
FROM python:3.9-alpine RUN pip3 install websockets次の内容を含む websockets-client.yaml ファイルを作成します。
apiVersion: v1 kind: Service metadata: name: websockets-client labels: app: websockets-client spec: type: ClusterIP ports: - port: 8080 targetPort: 80 name: http-websockets-client selector: app: websockets-client --- apiVersion: apps/v1 kind: Deployment metadata: name: websockets-client-sleep labels: app: websockets-client spec: selector: matchLabels: app: websockets-client template: metadata: labels: app: websockets-client spec: containers: - name: websockets-client image: registry.cn-beijing.aliyuncs.com/aliacs-app-catalog/istio-websockets-client-test:1.0 command: ["sleep", "14d"]default 名前空間に WebSocket クライアントをデプロイします。
kubectl apply -f websockets-client.yaml -n default
HTTP/1.1 経由で WebSocket 接続を確立する
WebSocket クライアントが 1 つの接続で WebSocket サーバーに複数のリクエストを送信した場合でも、サイドカープロキシのログには、WebSocket 接続のアクセスログエントリが 1 つだけ含まれます。Envoy は WebSocket 接続を TCP バイトストリームとして扱い、HTTP Upgrade ヘッダーを含むリクエストとレスポンスのみを認識できます。そのため、Envoy は接続が閉じられた後にのみアクセスログエントリを生成します。また、ログエントリには各 TCP リクエストの詳細は含まれていません。
次のいずれかの方法を使用して CLI シェルを開きます。
方法 1:
ACK コンソール にログインします。左側のナビゲーションペインで、[クラスタ] をクリックします。
[クラスタ] ページで、管理するクラスタを見つけて、その名前をクリックします。左側のペインで、 を選択します。
[ポッド] ページで websockets-client を見つけ、[アクション] 列の [ターミナル] をクリックします。次に、[websockets-client] をクリックします。
方法 2:
次のコマンドを実行して CLI シェルを開きます。
kubectl exec -it -n <namespace> websockets-client-sleep... -c websockets-client -- sh
次のコマンドを実行して WebSocket サーバーにアクセスします。
python3 -m websockets ws://websockets-server.<namespace>.svc.cluster.local:8080期待される出力:
Connected to ws://websockets-server.default.svc.cluster.local:8080.「hello」と「world」と入力すると、2 つの単語が期待どおりに返されます。
> hello < hello > world < world Connection closed: 1000 (OK).サイドカープロキシのログをクエリします。
WebSocket クライアントのサイドカープロキシのログをクエリします。
[ポッド] ページで、WebSocket クライアントのコンテナの名前をクリックします。
[ログ] タブをクリックし、[コンテナ] ドロップダウンリストから [istio-proxy] を選択します。
ログに HTTP/1.1 経由の WebSocket 接続に関するエントリが含まれていることがわかります。
{...."upstream_host":"10.208.0.105:80","bytes_sent":23,"protocol":"HTTP/1.1",....}
WebSocket サーバーのサイドカープロキシのログをクエリします。
[ポッド] ページで、WebSocket サーバーのコンテナの名前をクリックします。
[ログ] タブをクリックし、[コンテナ] ドロップダウンリストから [istio-proxy] を選択します。
ログに HTTP/1.1 経由の WebSocket 接続に関するエントリが含まれていることがわかります。
{...."downstream_local_address":"10.208.0.105:80","upstream_local_address":"127.0.**.**:53983","protocol":"HTTP/1.1",....}
HTTP/2 経由で WebSocket 接続を確立する
1.12 より前のバージョンの Istio を使用している場合、HTTP/1.1 接続を HTTP/2 接続にアップグレードすることで WebSocket サーバーに WebSocket 接続を確立することはできません。この場合、HTTP ステータスコード 503 が返されます。
1.12 より前のバージョンの Istio の場合、宛先ルールで h2UpgradePolicy を DO_NOT_UPGRADE に設定します。こうすることで、HTTP/1.1 接続は HTTP/2 にアップグレードされず、HTTP/1.1 を使用して WebSocket サーバーに WebSocket 接続を正常に確立できます。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
labels:
provider: asm
name: websockets-server
spec:
host: websockets-server
trafficPolicy:
connectionPool:
http:
h2UpgradePolicy: DO_NOT_UPGRADEIstio 1.12 以降を使用している場合は、次の手順を実行して HTTP/2 を使用して WebSocket 接続を確立できます。
宛先ルールを作成します。
ASM コンソール にログインします。
左側のナビゲーションペインで、 を選択します。
[メッシュ管理] ページで、設定する ASM インスタンスを見つけます。ASM インスタンスの名前をクリックするか、管理[アクション] 列の をクリックします。
ASM インスタンスの詳細ページで、左側のナビゲーションペインの を選択します。表示されるページで、[YAML から作成] をクリックします。
作成ページで、既定[名前空間] ドロップダウンリストから 作成 を選択し、次の内容をコードエディタにコピーします。次に、 をクリックします。
apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: labels: provider: asm name: websockets-server spec: host: websockets-server trafficPolicy: connectionPool: http: h2UpgradePolicy: UPGRADEh2UpgradePolicy: UPGRADE の値は、WebSocket サーバーで HTTP/2 が有効になっていることを示します。
Envoy フィルタを作成します。
デフォルトでは、WebSocket は HTTP/2 プロトコルでは機能しません。ただし、Envoy は HTTP/2 経由の WebSocket のトンネリングをサポートしています。こうすることで、ASM インスタンス内のすべての通信を HTTP/2 経由で実行できます。宛先ワークロードの Envoy フィルタの
allow_connectパラメータを true に設定できます。その後、HTTP/2 接続は WebSocket サーバーでサポートされます。ASM コンソール にログインします。
左側のナビゲーションペインで、 を選択します。
[メッシュ管理] ページで、設定する ASM インスタンスを見つけます。ASM インスタンスの名前をクリックするか、管理[アクション] 列の をクリックします。
ASM インスタンスの詳細ページで、左側のナビゲーションペインの を選択します。
[マーケットプレイス] ページで、[allow_connect パラメータを True に設定して更新されたプロトコル接続を許可するテンプレート] をクリックします。
[プラグインの詳細] ページで、[プラグイン設定] タブをクリックします。[プラグインの有効範囲] セクションで、[ワークロードのスコープ] を選択し、[有効範囲にワークロードを追加] をクリックします。
[有効範囲にワークロードを追加] ダイアログボックスで、[名前空間] パラメータを [default] に、[ワークロードタイプ] を [デプロイメント] に設定します。[ワークロードを選択] セクションで、[websockets-server] を選択し、
アイコンをクリックして [websockets-server] を [選択済み] セクションに追加し、[OK] をクリックします。[プラグイン設定] セクションの YAML コードエディタに
patch_context: SIDECAR_INBOUNDと入力し、[プラグインスイッチ] をオンにして、プラグインが有効になるまで待ちます。プラグインが有効になると、ASM は Envoy フィルタを自動的に作成します。次のコードは、Envoy フィルタの YAML ファイルの例です。
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: h2-upgrade-wss labels: asm-system: 'true' provider: asm spec: workloadSelector: labels: app: websockets-server configPatches: - applyTo: NETWORK_FILTER match: context: SIDECAR_INBOUND proxy: proxyVersion: '^1\.*.*' listener: filterChain: filter: name: "envoy.filters.network.http_connection_manager" patch: operation: MERGE value: typed_config: '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager http2_protocol_options: allow_connect: true
次のコマンドを実行して WebSocket サーバーにアクセスします。
python3 -m websockets ws://websockets-server.<namespace>.svc.cluster.local:8080期待される出力:
Connected to ws://websockets-server.default.svc.cluster.local:8080.「hello」と「world」と入力すると、2 つの単語が期待どおりに返されます。
> hello < hello > world < world Connection closed: 1000 (OK).サイドカープロキシのログをクエリします。
WebSocket クライアントのサイドカープロキシのログをクエリします。
[ポッド] ページで、WebSocket クライアントのコンテナの名前をクリックします。
[ログ] タブをクリックし、[コンテナ] ドロップダウンリストから [istio-proxy] を選択します。
ログに HTTP/1.1 経由の WebSocket 接続に関するエントリが含まれていることがわかります。これは、WebSocket クライアントが HTTP/1.1 を使用してリクエストを送信していることを示します。
{...."authority":"websockets-server.default.svc.cluster.local:8080","upstream_service_time":null,"protocol":"HTTP/1.1",....}
WebSocket サーバーのサイドカープロキシのログをクエリします。
[ポッド] ページで、WebSocket サーバーのコンテナの名前をクリックします。
[ログ] タブをクリックし、[コンテナ] ドロップダウンリストから [istio-proxy] を選択します。
ログに HTTP/2 経由の WebSocket 接続に関するエントリが含まれていることがわかります。これは、WebSocket サーバーがプロトコルが HTTP/2 にアップグレードされたリクエストを受信していることを示します。
{...."method":"GET","upstream_local_address":"127.0.**.**:34477","protocol":"HTTP/2",....}