WebSocket is a communications protocol that enables interaction between a client and a server. The WebSocket protocol is standardized by RFC 6455. Istio sidecar proxies support the WebSocket protocol out of the box and allow you to access services by establishing WebSocket connections with ease. This topic describes how to access services over WebSocket connections initiated by using HTTP/1.1 or HTTP/2 in Alibaba Cloud Service Mesh (ASM).

Prerequisites

Background information

A WebSocket connection is established by using the HTTP Upgrade header. This is different from the way to establish an HTTP connection. Istio cannot recognize the WebSocket protocol. However, Istio sidecar proxies provide out-of-the-box support for the WebSocket protocol. For more information about how to use the WebSocket protocol in Istio, see HTTP upgrades, HTTP connection manager, and Protocol Selection.

WebSocket connections initiated by using HTTP/1.1 and those initiated by using HTTP/2 have the following differences:
  • WebSocket connections initiated by using HTTP/1.1: A connection is established to process only one request. After a response is returned for the request, the connection is closed.
  • WebSocket connections initiated by using HTTP/2: Multiple requests can be processed in parallel over a connection. If a single request is time-consuming, other requests over the same connection are not affected.

Deploy a WebSocket client and a WebSocket server

  1. Connect to ACK clusters by using kubectl.
  2. Deploy a WebSocket server.
    In this example, the WebSocket server for Python provided by the WebSocket community is used. For more information about how to modify the configurations for deploying the WebSocket server, see Deploy to Kubernetes.
    1. Create the websockets-server.yaml file that contains the following content:
      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: 80
    2. Deploy the WebSocket server in the default namespace.
      kubectl apply -f websockets-server.yaml -n default
  3. Deploy a WebSocket client.
    In this example, a Dockerfile is used to build an image for a WebSocket client. The websockets-client.yaml file contains the configurations for using the image of the WebSocket client to deploy the WebSocket client to a cluster.
    FROM python:3.9-alpine
    RUN pip3 install websockets
    1. Create the websockets-client.yaml file that contains the following content:
      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"]
    2. Deploy the WebSocket client in the default namespace.
      kubectl apply -f websockets-client.yaml -n default

Use WebSocket connections initiated by using HTTP/1.1

Even if the WebSocket client sends multiple requests to the WebSocket server, the logs of sidecar proxies contain only one access log entry for the WebSocket connection. Envoy treats WebSocket connections as TCP byte streams and can recognize only requests and responses that contain the HTTP Upgrade header. Therefore, Envoy generates an access log entry only after the connection is closed. In addition, the log entry does not contain details of each TCP request.

  1. Open the CLI shell in the container of the WebSocket client.
    1. Log on to the ACK console.
    2. In the left-side navigation pane of the ACK console, click Clusters.
    3. On the Clusters page, find the cluster that you want to manage and click the name of the cluster or click Details in the Actions column. The details page of the cluster appears.
    4. In the left-side navigation pane of the details page, choose Workloads > Pods.
    5. On the Pods page, find websockets-client and click Terminal in the Actions column. After that, click Container: websockets-client.
    6. Run the following command to open the CLI shell.
      kubectl exec -it -n <namespace> websockets-client-sleep...  -c websockets-client -- sh
  2. Run the following command to access the WebSocket server:
    python3 -m websockets ws://websockets-server.<namespace>.svc.cluster.local:8080

    Expected output:

    Connected to ws://websockets-server.default.svc.cluster.local:8080.

    If you enter "hello" and "world", the two words are returned as expected.

    > hello
    < hello
    > world
    < world
    Connection closed: 1000 (OK).
  3. Query the logs of sidecar proxies.
    • Query the logs of the sidecar proxy for the WebSocket client.
      1. On the Pods page, click the name of the container of the WebSocket client.
      2. Click the Logs tab and select istio-proxy from the Container drop-down list.
        You can see that the logs contain an entry about the WebSocket connection initiated by using HTTP/1.1.
        {...."upstream_host":"10.208.0.105:80","bytes_sent":23,"protocol":"HTTP/1.1",....}
    • Query the logs of the sidecar proxy for the WebSocket server.
      1. On the Pods page, click the name of the container of the WebSocket server.
      2. Click the Logs tab and select istio-proxy from the Container drop-down list.
        You can see that the logs contain a record of the WebSocket connection initiated by using HTTP/1.1.
        {...."downstream_local_address":"10.208.0.105:80","upstream_local_address":"127.0.**.**:53983","protocol":"HTTP/1.1",....}

Use WebSocket connections initiated by using HTTP/2

If you use Istio of a version earlier than 1.12, WebSocket connections cannot be established to the WebSocket server by upgrading HTTP/2 connections. In this case, the HTTP status code 503 is returned.

If the HTTP status code 503 is returned after you send a request to upgrade an HTTP/2 connection to a WebSocket connection, you can set the h2UpgradePolicy parameter to DO_NOT_UPGRADE in the destination rule. This way, you can establish WebSocket connections by upgrading HTTP/1.1 connections without the need to update the version of Istio.
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  labels:
    provider: asm
  name: websockets-server
spec:
  host: websockets-server
  trafficPolicy:
    connectionPool:
      http:
        h2UpgradePolicy: DO_NOT_UPGRADE

If you use Istio 1.12 or later, you can perform the following steps to establish WebSocket connections by using HTTP/2.

  1. Create a destination rule.
    1. Log on to the ASM console.
    2. In the left-side navigation pane, choose Service Mesh > Mesh Management.
    3. On the Mesh Management page, find the ASM instance that you want to configure. Click the name of the ASM instance or click Manage in the Actions column.
    4. On the details page of the ASM instance, choose Traffic Management > DestinationRule in the left-side navigation pane. On the DestinationRule page, click Create from YAML.
    5. On the Create page, select default from the Namespace drop-down list and copy the following content to the code editor. Then, click Create.
      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        labels:
          provider: asm
        name: websockets-server
      spec:
        host: websockets-server
        trafficPolicy:
          connectionPool:
            http:
              h2UpgradePolicy: UPGRADE

      h2UpgradePolicy: A value of UPGRADE indicates that HTTP/2 is enabled for the WebSocket server.

  2. Create an Envoy filter.
    By default, WebSocket does not work with the HTTP/2 protocol. However, Envoy supports tunnel transmission for WebSocket data over HTTP/2. This way, all communication can be performed over HTTP/2 in the ASM instance. You can set the allow_connect parameter of an Envoy filter to true for the destination workload. After that, HTTP/2 connections are supported by the WebSocket server.
    1. Create an Envoy filter template.
      1. Log on to the ASM console.
      2. In the left-side navigation pane, choose Service Mesh > Mesh Management.
      3. On the Mesh Management page, find the ASM instance that you want to configure. Click the name of the ASM instance or click Manage in the Actions column.
      4. In the left-side navigation pane, choose Plugin Center > Market Place.
      5. On the Market Place page, select the setting allow_connect true for HTTP protocol upgrade template and click Apply this template.
      6. In the Fill template parameters step, select SIDECAR_INBOUND from the drop-down list and click Next.
      7. Enter the name of the template in the Name field and click OK.
    2. Bind the Envoy filter template to a workload.
      1. In the left-side navigation pane, choose Plugin Center > EnvoyFilter Template.
      2. On the EnvoyFilter Template page, find the Envoy filter template that you create in the previous step and click Edit template in the Actions column.
      3. Click the Bind template to workloads tab and click Bind EnvoyFilter to Workloads.
      4. In the Bind EnvoyFilter to Workloads dialog box, set the Namespace parameter to default and the Workload Type parameter to Deployment. In the Not bound section, click Bind in the Actions column for websockets-server. After that, click OK.
        After you bind the Envoy filter template to a workload, ASM automatically creates an Envoy filter. The following content describes the YAML code of the Envoy filter:
        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
  3. Run the following command to access the WebSocket server:
    python3 -m websockets ws://websockets-server.<namespace>.svc.cluster.local:8080

    Expected output:

    Connected to ws://websockets-server.default.svc.cluster.local:8080.

    If you enter "hello" and "world", the two words are returned as expected.

    > hello
    < hello
    > world
    < world
    Connection closed: 1000 (OK).
  4. Query the logs of sidecar proxies.
    • Query the logs of the sidecar proxy for the WebSocket client.
      1. On the Pods page, click the name of the container of the WebSocket client.
      2. Click the Logs tab and select istio-proxy from the Container drop-down list.
        You can see that the logs contain an entry about the WebSocket connection initiated by using HTTP/1.1. This indicates that the WebSocket client sends requests by using HTTP/1.1.
        {...."authority":"websockets-server.default.svc.cluster.local:8080","upstream_service_time":null,"protocol":"HTTP/1.1",....}
    • Query the logs of the sidecar proxy for the WebSocket server.
      1. On the Pods page, click the name of the container of the WebSocket server.
      2. Click the Logs tab and select istio-proxy from the Container drop-down list.
        You can see that the logs contain an entry about the WebSocket connection initiated by using HTTP/2. This indicates that the WebSocket server receives requests whose protocols are upgraded to HTTP/2.
        {...."method":"GET","upstream_local_address":"127.0.**.**:34477","protocol":"HTTP/2",....}