添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

当使用Firefox和Safari时,关闭WebSocket可以正常工作,但Istio和Chrome的组合会导致WebSocket关闭时返回 "wasclean": false 和返回码 1006 。本文介绍如何通过EnvoyFilter解决Websocket关闭时返回码不一致的问题。

前提条件

步骤一:部署示例应用

您可以使用本示例镜像或者自行构建Docker镜像部署应用,具体操作如下:

方式一:使用阿里云镜像部署应用

  1. 使用以下内容,创建 sample.yaml 文件。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: websocket-test
      namespace: default
      labels:
        app: websocket-test
        version: current
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: websocket-test
          version: current
      template:
        metadata:
          labels:
            app: websocket-test
            version: current
        spec:
          containers:
          - name: websocket-test
            image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/asm-websocketsample:v1
            imagePullPolicy: Always
            command: ["node", "ws.js"]
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: websocket-test
      name: websocket-test
      namespace: default
    spec:
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: 9900
      selector:
        app: websocket-test
      type: ClusterIP
  2. 执行以下命令,在default命名空间下部署WebSocket服务端示例应用。

    命名空间default需启用Sidecar自动注入,具体操作,请参见 启用自动注入

    kubectl apply -f sample.yaml

方式二:自行构建Docker镜像部署应用

  1. 此处以Node应用为例, package.json 文件示例如下。

    {
      "name": "wssample",
      "version": "0.0.1",
      "main": "ws.js",
      "license": "UNLICENSED",
      "scripts": {
        "start": "node --trace-warnings ./ws.js"
      "dependencies": {
        "ws": "^8.0.0"
    }
  2. 执行以下代码,部署WebSocket服务端示例应用。

    const WebSocket = require("ws");
    const http = require("http");
    const wss = new WebSocket.Server({ noServer: true });
    const server = http.createServer()
    server.on("upgrade", async (request, socket, head) => {
      const handleAuth = (ws) => {
        wss.emit("connection", ws, request);
      wss.handleUpgrade(request, socket, head, handleAuth);
    wss.on("connection", (conn, req) => {
      // 默认应返回1005,但是有Sidecar的情况下,客户端收到的是1006。
      // conn.close()
      // 自定义的返回码4321。
      // 但是有Sidecar的情况下, 客户端收到的仍然是1006。
      // 当没有Sidecar 时, 客户端收到4321。
      conn.close(4321, "test")
    server.listen({ host: '0.0.0.0', port: 9900 });
                            
  3. 使用以下内容,构建镜像的Dockerfile。

    FROM node:16.7.0-alpine3.14
    WORKDIR /root/app
    COPY . .
    RUN yarn install

步骤二:配置网格规则

  1. 登录 ASM控制台

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

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

  4. 创建网关规则。

    1. 在网格详情页面左侧导航栏,选择 ASM网关 > 网关规则 ,然后在右侧页面,单击 使用YAML创建

    2. 设置 命名空间 default ,选择任意场景模板,将以下内容粘贴到 YAML 文本框,然后单击 创建

      apiVersion: networking.istio.io/v1beta1
      kind: Gateway
      metadata:
        name: websocket-test
        namespace: default
      spec:
        selector:
          istio: ingressgateway
        servers:
          - hosts:
              - '*'
            port:
              name: http
              number: 80
              protocol: HTTP
  5. 创建目标规则。

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

    2. 设置 命名空间 default ,选择任意场景模板,将以下内容粘贴到 YAML 文本框,然后单击 创建

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: websocket-test
        namespace: default
      spec:
        host: websocket-test
        subsets:
        - name: current
          labels:
            version: current
  6. 创建虚拟服务。

    1. 在网格详情页面左侧导航栏,选择 流量管理中心 > 虚拟服务 ,然后在右侧页面,单击 使用YAML创建

    2. 设置 命名空间 default ,选择任意场景模板,将以下内容粘贴到 YAML 文本框,然后单击 创建

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: websocket-test
        namespace: default
      spec:
        gateways:
        - websocket-test
        hosts:
        - '*'
        http:
        - name: default
          route:
          - destination:
              host: websocket-test
              subset: current

步骤三:访问WebSocket

本文以直接访问WebSocket和使用EnvoyFilter访问WebSocket两种方式进行对比,通过返回码(本文以自定义返回码4321为例)验证WebSocket的访问效果。

方式一:直接访问WebSocket

  1. 使用以下内容,创建 client.html 文件。

    请将YAML中入口网关地址修改为实际的入口网关IP。

    <!DOCTYPE html>
        <title>WebSocket example</title>
    </head>
        <script>
            var ws = new WebSocket('ws://{替换为实际的入口网关IP地址}');
            ws.onopen = function (ev) {
                console.log(ev)
            ws.onmessage = function (ev) {
                console.log("on msg", ev)
            ws.onclose = function (ev) {
                console.log("on close", ev)
            ws.onerror = function (ev) {
                console.log("on error", ev)
        </script>
    </body>
    </html>
  2. 使用Chrome浏览器打开 client.html 文件,在键盘上按F12键,开启Developer Tools。

  3. 刷新页面,在 Console 页签下,查看返回码。

    如下图所示,返回码始终为1006,而不是自定义的返回码4321。 1006

方式二:通过EnvoyFilter访问WebSocket

  1. 使用以下内容,创建EnvoyFilter。

    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      labels:
        asm-system: 'true'
        provider: asm
      name: hack-to-fix-delayedclosetimeout-istio-upper-version
      namespace: istio-system
    spec:
      configPatches:
        - applyTo: NETWORK_FILTER
          match:
            listener:
              filterChain:
                filter:
                  name: envoy.filters.network.http_connection_manager
            proxy:
              proxyVersion: ^1.*.*
          patch:
            operation: MERGE
            value:
              typed_config:
                '@type': >-
                  type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                delayed_close_timeout: 0s
                    
  2. 将EnvoyFilter绑定至指定的工作负载或命名空间。具体操作,请参见 步骤二:将Envoy过滤器模板绑定至工作负载或命名空间

  3. 使用Chrome浏览器打开 client.html 文件,在键盘上按F12键,开启Developer Tools。

  4. 刷新页面,在 Console 页签下,查看返回码。

    如下图所示,返回码变为4321,符合预期。因此,您可以通过EnvoyFilter解决WebSocket关闭时返回码不一致的问题。 4321