在ASM中使用WebSocket协议访问服务

WebSocket是基于RFC 6455标准,用于客户端和服务器端之间进行双向通信的协议。Istio Sidecar Proxy提供了开箱即用方式使用WebSocket协议,便于您使用WebSocket协议实现服务访问。本文介绍如何在ASM中使用WebSocket over HTTP/1.1和HTTP/2协议访问服务。

背景信息

WebSocket与HTTP协议不同,WebSocket协议使用HTTP Upgrade标头来建立各方之间的连接。Istio无法识别WebSocket协议,但Istio Sidecar Proxy提供了开箱即用方式支持WebSocket协议。关于Istio支持WebSocket协议详细介绍,请参见HTTP upgradesHTTP connection managerProtocol Selection

您可以在ASM中使用WebSocket over HTTP/1.1和HTTP/2协议访问服务,区别如下:

  • WebSocket over HTTP/1.1协议:一次请求和响应,建立一个连接,用完关闭连接。

  • WebSocket over HTTP/2协议:多个请求可同时在一个连接上并行执行,某个请求任务耗时严重,不会影响到其它连接的正常执行。

前提条件

部署WebSocket客户端和服务端

  1. 部署WebSocket服务端。

    说明

    本文使用WebSocket社区提供的Python库中WebSocket服务端为例。如果您想修改WebSocket服务端的部署配置,请参见容器化应用程序

    1. 使用以下内容,创建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: 80
    2. 执行以下命令,在default命名空间下部署WebSocket服务端。

      kubectl apply -f websockets-server.yaml -n default
  2. 部署WebSocket客户端。

    1. 使用以下内容,创建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"]
    2. 在default命名空间下部署WebSocket客户端。

      kubectl apply -f websockets-client.yaml -n default

使用WebSocket over HTTP/1.1协议

使用WebSocket客户端向服务端发送了多个请求,Sidecar Proxy日志只会包含一个WebSocket连接的访问日志条目。 Envoy将WebSocket连接视为TCP字节流,并且代理只能理解HTTP升级请求或响应,因此Envoy只会在连接关闭后创建该访问日志条目,并且也不会看到每个TCP请求的任何消息。

  1. 任选以下方式,打开Shell命令行窗口。

    • 方式一:

      1. 登录容器服务管理控制台,在左侧导航栏单击集群

      2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择工作负载 > 容器组

      3. 容器组页面单击websockets-client右侧操作列下终端,单击websockets-client

    • 方式二:

      执行以下命令,打开Shell命令行窗口。

      kubectl exec -it -n <namespace> websockets-client-sleep...  -c websockets-client -- sh
  2. 执行以下命令,访问WebSocket服务端。

    python3 -m websockets ws://websockets-server.default.svc.cluster.local:8080

    预期输出:

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

    输入hello和word,可以看到返回hello和word。

    > hello
    < hello
    > world
    < world
    Connection closed: 1000 (OK).
  3. 查看Sidecar Proxy日志。

    • 查看WebSocket客户端的Sidecar Proxy日志。

      1. 容器组页面单击WebSocket客户端容器名称。

      2. 单击日志页签,设置容器istio-proxy

        可以看到日志中包含使用WebSocket over HTTP/1.1协议记录。

        {...."upstream_host":"10.208.0.105:80","bytes_sent":23,"protocol":"HTTP/1.1",....}
    • 查看WebSocket服务端的Sidecar Proxy日志。

      1. 容器组页面单击WebSocket服务端容器名称。

      2. 单击日志页签,设置容器istio-proxy

        可以看到日志中包含使用WebSocket over HTTP/1.1协议记录。

        {...."downstream_local_address":"10.208.0.105:80","upstream_local_address":"127.0.**.**:53983","protocol":"HTTP/1.1",....}

使用WebSocket over HTTP/2协议

低于1.12版本的Istio使用WebSocket over HTTP/2协议,将不能正常连接到WebSocket服务器端, 并返回503错误。

如果您已经为低于1.12版本的Istio设置HTTP/2升级,并发生报错,您可以通过在目标规则中定义h2UpgradePolicyDO_NOT_UPGRADE的方式,使低于1.12版本的Istio可以正常使用WebSocket over HTTP/1.1协议。

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

Istio 1.12或以上版本使用WebSocket over HTTP/2协议的操作如下:

  1. 创建目标规则。

    1. 登录ASM控制台

    2. 在左侧导航栏,选择服务网格 > 网格管理

    3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理

    4. 在网格详情页面左侧导航栏,选择流量管理中心 > 目标规则,然后在右侧页面,单击使用YAML创建

    5. 设置命名空间default,复制以下内容到文本框中,然后单击创建

      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        labels:
          provider: asm
        name: websockets-server
      spec:
        host: websockets-server
        trafficPolicy:
          connectionPool:
            http:
              h2UpgradePolicy: UPGRADE

      h2UpgradePolicy:设置为UPGRADE,表示为websockets-server启用HTTP/2协议。

  2. 创建EnvoyFilter。

    默认情况下,WebSocket不支持HTTP/2协议,但Envoy却支持WebSocket在HTTP/2流上进行隧道传输,以便在整个部署过程中可以使用统一的HTTP/2网络。您可以通过EnvoyFilter针对目标工作负载设置allow_connect: true,从而使WebSocket服务端允许HTTP/2协议连接。

    1. 登录ASM控制台

    2. 在左侧导航栏,选择服务网格 > 网格管理

    3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理

    4. 在网格详情页面左侧导航栏选择插件扩展中心 > 插件市场

    5. 插件市场页面,单击设置allow_connect为true允许升级的协议连接

    6. 插件详情页面,单击新建插件实例页签,在插件生效范围区域,选中工作负载生效,然后单击添加工作负载到生效范围

    7. 添加工作负载到生效范围对话框中,设置命名空间default工作负载类型Deployment,在选择负载区域选中websockets-server,单击添加图标,将websockets-server添加到已选择区域中,然后单击确定

    8. 插件配置区域的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
  3. 执行以下命令,访问WebSocket服务端。

    python3 -m websockets ws://websockets-server.<namespace>.svc.cluster.local:8080

    预期输出:

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

    输入hello和word,可以看到返回hello和word。

    > hello
    < hello
    > world
    < world
    Connection closed: 1000 (OK).
  4. 查看Sidecar Proxy日志。

    • 查看WebSocket客户端的Sidecar Proxy日志。

      1. 容器组页面单击WebSocket客户端容器名称。

      2. 单击日志页签,设置容器istio-proxy

        可以看到日志中包含使用WebSocket over HTTP/1.1协议记录,说明客户端发起请求使用的是HTTP/1.1协议。

        {...."authority":"websockets-server.default.svc.cluster.local:8080","upstream_service_time":null,"protocol":"HTTP/1.1",....}
    • 查看WebSocket服务端的Sidecar Proxy日志。

      1. 容器组页面单击WebSocket服务端容器名称。

      2. 单击日志页签,设置容器istio-proxy

        可以看到日志中包含使用WebSocket over HTTP/2协议记录,说明WebSocket服务端的接收到的协议已经升级为HTTP/2。

        {...."method":"GET","upstream_local_address":"127.0.**.**:34477","protocol":"HTTP/2",....}