Please enable Javascript to view the contents

请定量证明这是网络的锅 —— 我思,故 Envoy sidecar 在

 ·  ☕ 5 分钟

File:René Descartes i samtal med Sveriges drottning, Kristina.jpg

Queen Christina of Sweden (left) and René Descartes(瑞典克里斯蒂娜女王(左)和笛卡尔(右))

SRE / Supporting / Performance test teams 问题定位遇事不决三件宝:

  • 网络问题
  • 重启
  • 硬件故障

这时如果遇上一个精明校真,又不遵守儒家职场尊老爱幼,凡事留一线,日后好相见的"优良传统"的对手,他/她会追问:"请用数据证明你的说法"。如果你说是个网络问题,怎么证明?

直线思维当然是找网络问题测量工具(如 iPerf)去测试网络质量和丢包率。如果报告能证明当然很好,但我的经验是只有在运维犯低级错误如 MTU 配置错误时,才有明显的问题。我叫这种不直接测量业务有效流量的工具为 offline 测量工具。其最大的问题是与现实场景差并过大,很难保证测量结果与现实问题一致。在 k8s 环境,网络拓扑更复杂,offline 测量更难靠谱。

相反的当然就是直接测量业务流量的 online 测量工具了。注意,online 不等于只在生产线上,也可以是测试环境,可以是压力测试,Chaos 混沌测试,干扰/破坏性 disruptive 测试环境。有很多这类型的 TCP 连接质量测量/可视察性工具:

不过本文打算用我比较熟识的 原生 Envoy 作为 connection 级别的 tcp stats inspect 工具。(注意,是 原生 Envoy ,不是 Istio Proxy,后面会解释为何)。

大概的架构如下:

[client(Traffic Generator)] --> [Envoy Proxy] -----external network may drop packets-----> [Application Cluster Gateway]

在测试环境中,Envoy 比起以上工具,在一些情况下有一些优点:

  • 自带 L7(HTTP) 层的成熟多样的监控 metrics
    • 作为 client 端(测试时的流量产生端,如 JMeter)。我们时常怀疑它的实际并发数,TPS等等。有了专业 http 的 sidecar metrics,一切指标都显得更透明和可控了。
  • 自带 L4/L3(TCP/IP) 层的 metrics,tcp-stats,这是本文的重点
  • 自带各种成熟的流量控制技术

失落的 Envoy sidecar 可视察性初心

Istio 当年其中一个吹上天的特性就是 Observability(可视察性) 。但从我这几年的观察来看,在现实环境中,可视察性很少被深度使用和研究。因为很多指标其实不是阅读一行说明文字就可以理解的。

更失落的 tcp_stats

下面以原生 Envoy 的 TCP statistics 为例:

Name Type Description
cx_tx_segments Counter Total TCP segments transmitted
cx_rx_segments Counter Total TCP segments received
cx_tx_data_segments Counter Total TCP segments with a non-zero data length transmitted
cx_rx_data_segments Counter Total TCP segments with a non-zero data length received
cx_tx_retransmitted_segments Counter Total TCP segments retransmitted
cx_rx_bytes_received Counter Total payload bytes received for which TCP acknowledgments have been sent.
cx_tx_bytes_sent Counter Total payload bytes transmitted (including retransmitted bytes).
cx_tx_unsent_bytes Gauge Bytes which Envoy has sent to the operating system which have not yet been sent
cx_tx_unacked_segments Gauge Segments which have been transmitted that have not yet been acknowledged
cx_tx_percent_retransmitted_segments Histogram Percent of segments on a connection which were retransmistted
cx_rtt_us Histogram Smoothed round trip time estimate in microseconds
cx_rtt_variance_us Histogram Estimated variance in microseconds of the round trip time. Higher values indicated more variability.

可以看到, Envoy 有能力获取 upstream/downstream 的 TCP 级别的一些与网络质量相关的指标。

它是由一个 TCP Stats Transport Socket wrapper 实现的。如果你对实现有兴趣,可见:源码。需要注意的是,使用这个功能需要 linux kernel >= 4.6 。这也是 Istio Proxy 默认构建不带 tcp stats 的原因:

https://github.com/istio/proxy/blob/2320d000121a42ac5e423c0b29e4ae210174a474/bazel/extension_config/extensions_build_config.bzl#L505

ISTIO_DISABLED_EXTENSIONS = [
    # ISTIO disable tcp_stats by default because this plugin must be built and running on kernel >= 4.6
    "envoy.transport_sockets.tcp_stats",
]

可能上面一下说得太深入,赶跑了部分读者了。下面还是说说简单的使用示例吧。

简单使用 TCP Stats Transport Socket wrapper

以下以这个拓扑为示例:

[curl(to www.example.com:80) --(redirect to 8080)--> Envoy Proxy:8080(L7 proxy to www.example.com:443)] -----external network may drop packets-----> [www.example.com:443]

Envoy 的配置文件

首先看看 Envoy 的配置文件 envoy-demo-simple-http-proxy-tcp-stats.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"admin": {
     "address": {
      "socket_address": {
       "address": "127.0.0.1",
       "port_value": 15000
      }
     }
}

static_resources:

  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: www.example.com
      transport_socket:
        name: envoy.transport_sockets.tcp_stats
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tcp_stats.v3.Config
          update_period: 5s            
          transport_socket:
            name: envoy.transport_sockets.raw_buffer
            typed_config: 
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer


  clusters:
  - name: www.example.com
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    connect_timeout: 1000s
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options:
            max_concurrent_streams: 100
    transport_socket:
      name: envoy.transport_sockets.tcp_stats
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tcp_stats.v3.Config
        update_period: 5s            
        transport_socket:
          name: envoy.transport_sockets.tls
          typed_config: 
            "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
            common_tls_context:
            sni: www.example.com
    load_assignment:
      cluster_name: www.example.com
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.example.com
                port_value: 443

本地 Linux 与网络环境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export ENVOY_PORT=8080

# create a new user u202406 to label the TCP traffic
sudo useradd -u 202406 u202406
sudo --user=u202406 id

# Redirect all connections which target at 80 to 8080. Limit redirect TCP connection only from user u202406
sudo iptables -t nat -A OUTPUT  -m owner --uid-owner 202406 -p tcp --dport 80 -j REDIRECT --to-ports "$ENVOY_PORT"

curl -v www.example.com
# success

sudo --user=u202406 time curl www.example.com
# connection refuse

启动 Envoy 和简单测试

开启三个终端,分别执行:

1
./envoy-1.30.2-linux-x86_64  -c ./envoy-demo-simple-http-proxy-tcp-stats.yaml -l debug
1
watch -d -n 0.5 "curl http://localhost:15000/stats | grep tcp"
1
sudo --user=u202406 time curl -v www.example.com

其中,watch 的终端会输出类似这样的数据:

cluster.www.example.com.tcp_stats.cx_rx_bytes_received: 5616
cluster.www.example.com.tcp_stats.cx_rx_data_segments: 10
cluster.www.example.com.tcp_stats.cx_rx_segments: 13
cluster.www.example.com.tcp_stats.cx_tx_bytes_sent: 548
cluster.www.example.com.tcp_stats.cx_tx_data_segments: 4
cluster.www.example.com.tcp_stats.cx_tx_retransmitted_segments: 0
cluster.www.example.com.tcp_stats.cx_tx_segments: 12
cluster.www.example.com.tcp_stats.cx_tx_unacked_segments: 0
cluster.www.example.com.tcp_stats.cx_tx_unsent_bytes: 0
cluster.www.example.com.tcp_stats.cx_rtt_us: P0(nan,200000) P25(nan,202500) P50(nan,205000) P75(nan,207500) P90(nan,209000) P95(nan,209500) P99(nan,209900) P99.5(nan,209950) P99.9(nan,209990) P100(nan,210000)
cluster.www.example.com.tcp_stats.cx_rtt_variance_us: P0(nan,59000) P25(nan,59250) P50(nan,59500) P75(nan,59750) P90(nan,59900) P95(nan,59950) P99(nan,59990) P99.5(nan,59995) P99.9(nan,59999) P100(nan,60000)
cluster.www.example.com.tcp_stats.cx_tx_percent_retransmitted_segments: P0(nan,0) P25(nan,0) P50(nan,0) P75(nan,0) P90(nan,0) P95(nan,0) P99(nan,0) P99.5(nan,0) P99.9(nan,0) P100(nan,0)
listener.0.0.0.0_8080.tcp_stats.cx_rtt_us: P0(nan,19) P25(nan,19.25) P50(nan,19.5) P75(nan,19.75) P90(nan,19.9) P95(nan,19.95) P99(nan,19.99) P99.5(nan,19.995) P99.9(nan,19.999) P100(nan,20)
listener.0.0.0.0_8080.tcp_stats.cx_rtt_variance_us: P0(nan,8) P25(nan,8.025) P50(nan,8.05) P75(nan,8.075) P90(nan,8.09) P95(nan,8.095) P99(nan,8.099) P99.5(nan,8.0995) P99.9(nan,8.0999) P100(nan,8.1)
listener.0.0.0.0_8080.tcp_stats.cx_tx_percent_retransmitted_segments: P0(nan,0) P25(nan,0) P50(nan,0) P75(nan,0) P90(nan,0) P95(nan,0) P99(nan,0) P99.5(nan,0) P99.9(nan,0) P100(nan,0)

模拟外网丢包

1
2
3
4
export EXAMPLE_COM_IP=93.184.215.14

# drop 50% packet
sudo iptables -D INPUT --src "$EXAMPLE_COM_IP" -m statistic --mode random --probability 0.5 -j DROP
1
watch -d -n 0.5 "curl http://localhost:15000/stats | grep tcp"
1
sudo --user=u202406 time curl -v www.example.com

这时,可见 time curl 的用时比丢包前大。 如果写个脚本 loop curl,从 watch 输出中可见 cx_tx_unacked_segmentscx_tx_unsent_bytes 两上 Gauge 类型的 metrics 有非 0 的情况出现。

数据与可视化图表

如果用 http://localhost:15000/stats?format=prometheus,把数据导入 Prometheus,就可以做时序的网络质量与丢包情况、RTT 的 Dashboard 仪表盘和折线图了。这些图再叠加上其它 API TPS、 Latency 图,就可以印证底层网络质量与 TPS 与 Latency 的影响了。

downstream TCP 监控

上面主要讲 upstream cluster 的 TCP 监控。下面说说 downstream。 其实上面的 Envoy 的配置文件 envoy-demo-simple-http-proxy-tcp-stats.yaml 中,已经加入了 listeners 的 tcp stats 了,所以是可以监控 downstream TCP 的,这个功能对于 Istio Gateway 端的网络质量监控由为实用。不过,可惜的是,Istio Proxy 默认的构建没带 tcp stats 功能,只能自己 build 了。

TCP Proxy

如果你的 client side 应用不是走 plain text http。那么就只能用 Envoy 代理 TCP 层了。这时用 Envoy TCP Proxy Filter 代替上面 Envoy 的配置文件 envoy-demo-simple-http-proxy-tcp-stats.yaml 中的 http_connection_manager 可能也是个可选方案。

我思故我在 - Je pense, donc je suis

学习一个开源项目,有人止于按示例使用,有人止于记录下它的设计理念,有人把这个设计理念融入自己的学习、生活、工作中。在不经意间应用了。当我们学习 Istio 时,其中精彩特性是原有应用无侵入、0 编码情况下,通过透明流量拦截,生成可视化流量指标。那么同样的思想其实可以融会贯通到很多场景中。这种能力或者就是未来架构师或程序员不致于被 AI 轻易取代的基本素质之一。

如果未来有人问你,你为何还能在这岗位上班而未被 AI 取代,回答是,因为 笛卡尔 说了:我思故我在!

伯特兰·罗素在其《西方哲学史》“笛卡尔”一章是这样解释“我思故我在”:据我思考这一事实推断出“我”的存在。 因此,我思考时我也存在,也只有那时我存在。 如果我停止思考,就不会有我存在的证据。

分享

Mark Zhu
作者
Mark Zhu
An old developer