envoy、docker和websockets - 调试和配置

Websockets是一项令人兴奋的技术,它允许你将HTTP连接升级为一个长期持久的二进制连接,你可以用它来发送双向信息。

作为一个旁观者,MQTT协议可以使用websockets进行传输--例如,对于网站交付的JavaScript客户端,这是唯一(?

在任何情况下,由于websockets运行在与正常HTTP和HTTPS流量相同的端口上(80 / 443),企业网络更有可能让它们通过。

Envoy和websockets

Envoy支持websockets。有一些小问题。

无法将JSON解析为proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].route[0].route) use_websocket。无法找到字段.):

Envoy曾经用一个旧的指令 "use_websocket "支持websockets。在当前的envoy安装中(例如,我目前使用envoy 1.10.0-dev进行测试),这个指令已经消失了,它已经被取代。

这个想法是为了有更多的通用升级请求/可能性。

现在正确的语法是使用 升级_configs.请看我的envoy.yaml。

静态_资源。
   听众。
   - 地址。
       socket_address:
         地址。0.0.0.0
         port_value: 80
     filter_chains。
     - 滤波器。
       - 名称: envoy.http_connection_manager
         配置。
           upgrade_configs。
             - 升级_类型: websocket

           codec_type: auto
           stat_prefix: ingress_http
           use_remote_address: true
           xff_num_trusted_hops。0
           route_config。
             virtual_hosts。
             - 名称:调试
               域。["debug.local:80"]
               航线。
                 - 匹配。{前缀。"/" }
                   路线。
                     群组:target_dwebsocket
             - 名称: 后台
               域。["morpheus.local"]
               航线。
               - 匹配。{前缀。"/" }
                 重新定向。
                   path_redirect: "/"
                   https_redirect: true

(snip)

- 地址。
     socket_address:
       地址。0.0.0.0
       port_value:443
   filter_chains。
   - tls_context。
       common_tls_context。
         tls_certificates。
         - certificate_chain: { filename: "/etc/envoy/example.crt" }
           private_key: { filename: "/etc/envoy/example.key" }
         alpn_protocols:[ "h2,http/1.1" ]
     滤波器。
     - 名称: envoy.http_connection_manager
       配置。
         upgrade_configs。
           - 升级_类型: websocket
         stat_prefix: ingress_https
         use_remote_address: true
         xff_num_trusted_hops。0
         route_config。
           virtual_hosts。
           - 名称:调试
             域。["debug.local:443"]
             航线。
               - 匹配。{前缀。"/" }
                 路线。
                   群组:target_dwebsocket


(snip)

集群。
- 名称: target_dwebsocket
   connect_timeout:0.25s
   类型:strict_dns
   lb_policy: round_robin
   主持人。
   - socket_address:
       地址: dwse
       port_value: 8080

根据文档,你可以在两个地方使用这个upgrade_configs指令。在http_connection_manager上(如我上面的例子),以及在单个路由上。

          upgrade_configs。
            - 升级_类型: websocket

路线不匹配

还有一件重要的事情要注意,如果你得到 "404"(错误:意外的服务器响应:404):由于一些奇怪的原因,:authority头将被设置为包括端口 - 因此,如果你只提供域名,将不匹配。

看看这条路线。

["debug.local:80"]

在这个例子中,端口是在域名之后指定的。在我的测试中,只有在指定端口的情况下,我才能通过envoy连接到我设置的websocket服务器。这是一个主要的绊脚石。

接下来,我将讨论,在这样的情况下如何调试envoy。

调试envoy、websockets、Docker。

让envoy的输出变得粗略

这是我的docker-compose.yaml。

版本: '3.7

服务。
   使者。
     建立。
       背景:./
       dockerfile。Dockerfile
     container_name: penvoyage-morpheus-envoy
     港口。
       – “80:80”
       – “443:443”
     量。
       - 类型:体积
         来源:《笔仙》_卷宗
         目标。/etc/envoy
     网络。
       - 使者网
     1TP3用户。"2000:2000"
     用户。"root:root"
     重新开始:除非停止
     环境。
       日志级别: debug

量。
   penvoyage_volume。
     外部。
       名称:笔仙_卷

网络。
   envoy_net。
     外部。
       名称:我的桥梁网络

请注意,环境变量 "loglevel: debug "已被设置。

loglevel可以是以下之一。

  • 痕迹
  • 调试
  • 信息
  • 警告

现在启动容器将产生更多的输出。

docker-compose up

注意,我们并没有从容器中脱附,以直接看到它的输出。(一旦你满意了,你可以用以下方式运行它 docker-compose up -d)

你的输出现在看起来应该是这样的。

penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [source/server/server.cc:143] 冲洗统计资料

在一个来自websocket客户端的连接上,你将得到以下输出。

penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source/server/connection_handler_impl.cc:257] [C0] new connection
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source/common/http/conn_manager_impl.cc:210] [C0] new stream
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][http] [source/common/http/conn_manager_impl.cc:548] [C0][S4222457489265630919] 请求头信息完成(end_stream=false)。
鹏程万里-莫菲斯-恩沃伊 | ':authority', 'debug.local:80'
鹏程万里-莫菲斯-恩沃伊 | ':path', '/mqtt_drinks_are_free'.
penvoyage-morpheus-envoy | ':method', 'GET' 。
penvoyage-morpheus-envoy | 'sec-websocket-version', '13' 。
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | '连接', '升级'。
鹏程万里-莫菲斯-恩沃伊 | '升级', 'websocket'
penvoyage-morpheus-envoy | 'sec-websocket-extensions', 'permessage-deflate; client_max_window_bits' 。
鹏程万里-莫菲斯-恩沃伊 |
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source/common/router/router.cc:321] [C0][S4222457489265630919] cluster 'target_dwebsocket' match for URL '/mqtt_drinks_are_free' 。
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source/common/router/router.cc:379] [C0][S4222457489265630919] router decoding headers。
鹏程万里-莫菲斯-恩沃伊 | ':authority', 'debug.local:80'
penvoyage-morpheus-envoy | ':path', '/mqtt_drinks_are_free' 。
penvoyage-morpheus-envoy | ':method', 'GET' 。
penvoyage-morpheus-envoy | ':scheme', 'http' 。
penvoyage-morpheus-envoy | 'sec-websocket-version', '13' 。
penvoyage-morpheus-envoy | 'sec-websocket-key', 'HQFiCTWiFMktGDPFXwzrjQ=='
penvoyage-morpheus-envoy | '连接', '升级'。
penvoyage-morpheus-envoy | '升级', 'websocket'.
penvoyage-morpheus-envoy | 'sec-websocket-extensions', 'permessage-deflate; client_max_window_bits' 。
penvoyage-morpheus-envoy | 'content-length', '0' 。
penvoyage-morpheus-envoy | 'x-forwarded-for', '192.168.1.2'
penvoyage-morpheus-envoy | 'x-forwarded-proto', 'http' 。
penvoyage-morpheus-envoy | 'x-envoy-internal', 'true' 。
penvoyage-morpheus-envoy | 'x-request-id', 'ca8a765c-e549-4c45-988c-58b6c853df7b'
penvoyage-morpheus-envoy | 'x-envoy-expected-rq-timeout-ms', '15000'
鹏程万里-莫菲斯-恩沃伊 |
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][pool] [source/common/http/http1/conn_pool.cc:82] 创建一个新连接
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][defug]

[source/common/http/codec_client.cc:26] [C1] 正在连接。
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:638] [C1] 连接到172.18.0.8:8080
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source/common/network/connection_impl.cc:647] [C1] 连接进行中
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/conn_pool_base.cc:20] 由于没有可用连接,正在排队请求。
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connecting] [source/common/network/connection_impl.cc:516] [C1] connected
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][defug] [source/common/http/codec_client.cc:64] [C1] 已连接。
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source/common/http/http1/conn_pool.cc:236] [C1] 连接到下一个请求
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source/common/router/router.cc:1122] [C0][S4222457489265630919] pool ready
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source/common/router/router.cc:669] [C0][S4222457489265630919] upstream headers complete: end_stream=false
penvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][http] [source/common/http/conn_manager_impl.cc:1234] [C0][S4222457489265630919] 通过codec(end_stream=false)编码头文件。
penvoyage-morpheus-envoy | ':status', '101' 。
penvoyage-morpheus-envoy | '升级', 'websocket'.
penvoyage-morpheus-envoy | '连接', '升级'。
penvoyage-morpheus-envoy | 'sec-websocket-accept', ' 2W9caJQU0JKW3MhWV6T8FHychjk='
penvoyage-morpheus-envoy | 'date', 'Sat, 18 May 2019 14:21:14 GMT'
penvoyage-morpheus-envoy | 'server', 'envoy' 。
penvoyage-morpheus-envoy | 'content-length', '0' 。
鹏程万里-莫菲斯-恩沃伊 |

注意我上面提到的 :authority header。

':authority', 'debug.local:80'

下面是一个普通网站请求的授权标头。

penvoyage-morpheus-envoy | ':method', 'GET' 。
penvoyage-morpheus-envoy | ':authority', 'morpheus.local' 。
penvoyage-morpheus-envoy | ':scheme', 'https' 。

注意第一种情况(通过websocket请求)中存在端口,而第二种情况中没有端口。

测试容器

请注意,这 https://hub.docker.com/r/dataferret/websocket-echo dataferret/websocket-echo的图像似乎是 坏了!

对于所使用的特定版本,扭曲/高速公路似乎有一些不兼容,因此我决定直接推出自己的版本。

我们将建立一个测试容器,它将呼应我们以websocket方式发送给它的输入。

这里是 码头文件(Dockerfile:

FROM python:stretch
COPY assets/websocketd /usr/bin/websocketd
运行 chmod +x /usr/bin/websocketd
COPY assets/run.py /opt/run.py
工作目录/opt

EXPOSE 8080
ENTRYPOINT ["websocketd", "-port=8080", "python3", "/opt/run.py"]

注意,使用ENTRYPOINT,当容器从docker-compose.yml中用CMD启动时,CMD值将作为参数传递给ENTRYPOINT命令。

websocketd是一个websocket守护程序,你可以从这里获得独立的可执行文件。

http://websocketd.com/

我下载了 https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_amd64.zip 并将其解压到我为docker容器创建的assets文件夹中。

这个漂亮的应用程序的总体思路是为你的应用程序提供一个包装,以便能够提供websockets。

你的应用程序所要做的就是在stdin上监听,并在stdout上写入响应/任何通信。

run.py - 这是一个简单的脚本,它将对写入它的所有内容进行回音。

#!/usr/bin/python
输入sys

虽然是真的。
     read_co = sys.stdin.readline()
     print(read_co, end="")
     sys.stdout.flush()

注意readline()的使用,它将在换行时立即返回字符串。否则你必须处理缓冲区/等待输入的累积。

还注意到sys.stdout.flush()。

最后,这里是 docker-compose.yml:

版本:'3.6

服务。
   dwse。
     建立。
       背景:./
       dockerfile。Dockerfile
     container_name: dwse
     主机名: dwse
     港口。
       – “8078:8080”
     网络。
       - 使者网
     重新启动:总是
     命令。"80"
     #user。"root:root"

网络。
   envoy_net。
     外部。
       名称:我的桥梁网络

注意,我把它放在与上面的envoy相同的网络 "my-bridge-network "中。

这里的命令是一个随机的 "80",不会被应用程序解释,但你可以用它来创建更复杂的命令行等。

排排解

最后,我们希望客户端能够连接并能够看到我们发送给测试Websocket服务的消息,该服务是通过envoy代理的。

我建议 wscat 为这个。

wscat是一个npm(node.js)包。因此,你必须先安装npm。

apt install npm

然后你可以安装wscat。

npm install -g wscat

用一个公共的websocket回声服务测试wscat。

wscat -c ws://echo.websocket.org

形象

获取wscat的命令行选项列表。

wscat -help

形象

要通过envoy检查一个带有自签名证书的安全websocket连接,请执行以下操作。

wscat -c ws://debug.local/mqtt_drinks_are_free -no-check

形象

提示

不合预期的服务器响应

错误。未预期的服务器响应。404

看一下你的路由:你是否像上面讨论的那样,把端口放在域中进行匹配?显然,对于websocket客户端来说,这似乎是必要的。

当有疑问时,在你的虚拟主机的末尾包括一个匹配所有域名,以使调试更容易。我建议在此设置一个固定的路径匹配,而不是前缀--通过改变你在wscat中使用的路径,这种方式,你可以验证哪个规则被匹配。

- 姓名:Matchverything
   域。["*"]
   航线。
     - 匹配。{ path:"/mqtt" }
       路线。{ 集群: target_dwebsocket }

无法解析JSON/无法找到字段。

无法将JSON解析为proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].route[0].route) use_websocket。无法找到字段.):

如上所述,这是一种传统的配置风格,已被新的配置所取代。

          upgrade_configs。
            - 升级_类型: websocket

没有群组匹配的URL

[source/common/router/router.cc:278] [C0][S5067510418337690596] URL'/'没有集群匹配。

见上文--URL的匹配显然包括Websockets的端口(在我的测试中),因此包括一个匹配所有域的规则,并从那里进行调试/使用我上面提供的语法。

          route_config。
            virtual_hosts。
            - 名称:调试
              域。["debug.local:80"]
              航线。
                - 匹配。{前缀。"/" }
                  路线。
                    群组:target_dwebsocket

自签证书

错误:自我签名的证书

用-no-check命令行参数运行wscat(这是一个双破折号,WordPress会把它弄乱)。

形象

Websocket连接失败。

pcp-code.js:15 WebSocket连接到'wss://key:[email protected]/mqtt'失败。在WebSocket握手过程中出错。意外的响应代码。403

见上面的信息:很可能,这个javascript错误信息是由于域名匹配的错误配置造成的(见上文)。

高速公路错误。

"要使用txaio,你必须先选择一个框架" exceptions.RuntimeError:要使用taxaio,你必须先选择一个带有.use_twisted()或.use_txaio()的框架。

这是我在运行dataferret/websocket-echo图像时得到的错误。我建议使用我上面提供的代码作为替代。

参考文献。 https://stackoverflow.com/questions/34157314/autobahn-websocket-issue-while-running-with-twistd-using-tac-file

参考文献。