关于容器:深度复盘重启-etcd-引发的异常

2次阅读

共计 8229 个字符,预计需要花费 21 分钟才能阅读完成。

作者信息:

唐聪、王超常,腾讯云原生产品核心技术专家,负责腾讯云大规模 TKE 集群和 etcd 管制面稳定性、性能和老本优化工作。

王子勇,腾讯云专家级工程师,腾讯云计算产品技术服务专家团队负责人。

详情

作为以后中国宽泛应用的云视频会议产品,腾讯会议已服务超过 3 亿用户,能高并发撑持千万级用户同时散会。腾讯会议数百万外围服务都部署在腾讯云 TKE 上,通过寰球多地区多集群部署实现高可用容灾。在去年用户应用最高峰期间,为了撑持更大规模的并发在线会议的人数,腾讯会议与 TKE 等各团队进行了一轮新的扩容。

然而,在这过程中,一个简略的 etcd 过程重启操作却触发了一个的诡异的 K8s 故障(不影响用户散会,影响新一轮后盾扩容效率),本文介绍了咱们是如何从问题景象、到问题剖析、大胆猜想排除、再次复现、谨严验证、根治隐患的,从 Kubernetes 到 etcd 底层原理,从 TCP RFC 草案再到内核 TCP/IP 协定栈实现,一步步定位并解决问题的具体流程(最终定位到是非凡场景触发了内核 Bug)。

心愿通过本文,让大家对 etcd、Kubernetes 和内核的简单问题定位有一个较为深刻的理解,把握相干方法论,同时也能让大家更好的理解和应用好 TKE,通过分享咱们的故障处理过程,晋升咱们的透明度。

背景常识

首先给大家简要介绍下腾讯会议的简要架构图和其应用的外围产品 TKE Serverless 架构图。

腾讯会议极简架构图如下:

腾讯会议重度应用的 TKE Serverless 架构如下:

腾讯会议简直全副业务都跑在 TKE Serverless 产品上,Master 组件部署在咱们 metacluster 中(K8s in K8s),超大集群可能有 10 多个 APIServer,etcd 由服务化的 etcd 平台提供,APIServer 拜访 etcd 链路为 svc -> cluster-ip -> etcd endpoint。业务各个 Pod 独占一个轻量级的虚拟机,安全性、隔离性高,业务无需关怀任何 Kubernetes Master、Node 问题,只须要专一业务畛域的开发即可。

问题景象

在一次资源扩容的过程中,腾讯会议的研发同学早晨忽然在群里反馈他们上海一个最大集群呈现了业务扩容失败,收到反馈后研发同学,第一工夫查看后,还看到了如下异样:

● 局部 Pod 无奈创立、销毁

● 某类资源 Get、list 都是超时

● 个别组件呈现了 Leader Election 谬误

● 大部分组件失常,少部分控制器组件有如下 list pvc 资源超时日志

k8s.io/client-go/informers/factory.go:134: Failed to watch 
*v1.PersistentVolumeClaim: failed to list 
*v1.PersistentVolumeClaim: the server was unable to return a 
response in the time allotted, but may still be processing the 
request (get persistentvolumeclaims)

然而 APIServer 负载并不高,过后一线研发同学疾速给出了一个管制面呈现未知异样的论断。随后 TKE 团队疾速进入攻艰模式,开始深入分析故障起因。

问题剖析

研发团队首先查看了此集群相干变更记录,发现此集群在几个小时之前,进行了重启 etcd 操作。变更起因是此集群规模很大,在之前的屡次扩容后,db size 使用率曾经靠近 80%,为了防止 etcd db 在业务新一轮扩容过程中被写满,因而零碎进行了一个通过审批流程后的,一个惯例的调大 etcd db quota 操作,并且变更后零碎自检 etcd 外围指标失常。

咱们首先剖析了 etcd 的接口延时、带宽、watch 监控等指标,如下图所示,
etcd P99 延时毛刺也就 500ms,节点带宽最大的是均匀 100MB/s 左右,初步看并未发现任何异样。

随后又回到 APIServer,一个在申请在 APIServer 外部经验的各个外围阶段(如下图所示):

● 鉴权(校验用户 token、证书是否正确)

● 审计

● 受权(查看是否用户是否有权限拜访对应资源)

● 限速模块(1.20 后是优先级及偏心治理)

● mutating webhook

● validating webhook

● storage/cache

● storage/etcd

申请在发往 APIServer 前,client 可能导致申请慢的起因:
● client-go 限速,client-go 默认 qps 5, 如果触发限速,则日志级别 4 以上(高版本 client-go 日志级别 3 以上)能够看到客户端日志中有打印 Throttling request took 相干日志

那到底是哪个阶段呈现了问题,导致 list pvc 接口超时呢?

● 依据 client QPS 很高、并且通过 kubectl 连贯异样实例也能复现,排除了 client-go 限速和 client 到 apiserver 网络连接问题

● 通过审计日志搜寻到不少 PVC 资源的 Get 和 list 5XX 谬误,汇集在其中一个实例

● 通过 APIServer Metrics 视图和 trace 日志排除 webhook 导致的超时等

● 基于 APIServer 拜访 etcd 的 Metrics、Trace 日志确定了是 storage/etcd 模块耗时很长,然而只有一个 APIServer 实例、某类资源有问题

那为什么 etcd 侧看到的监控延时很低,而 APIServer 拜访 etcd 延时很高,而且只是某类资源呈现问题,不是所有资源呢?

首先,咱们查看下 APIServer 的 etcd 延时统计上报代码 (以 Get 接口为例):

它统计的是整个 Get 申请 (理论调用的是 etcd Range 接口) 从 client 收回到收到后果的耗时,包含了整个网络链路和 etcd RPC 逻辑解决耗时

而 etcd 的 P99 Range 延时是基于 gRPC 拦截器机制实现的,etcd 在启动 gRPC Server 的时候,会注册一个一元拦截器实现延时统计,在 RPC 申请入口和执行 RPC 逻辑实现时上报延时,也就是它并不包含 RPC 申请在数据接管和发送过程中的耗时,相干逻辑封装在 monitor 函数中,简要逻辑如下所示:

最初一个疑难为什么是某类资源呈现问题?

APIServer 在启动的时候,会依据 Kubernetes 中的每个资源和版本创立一个独立 etcd client,并依据配置决定是否开启 watch cache,每个 client 个别 1 个 TCP 连贯,一个 APIServer 实例会高达上百个 etcd 连贯。etcd client 与 etcd server 通信应用的是 gRPC 协定,而 gRPC 协定又是基于 HTTP/2 协定的。

PVC 资源超时,Pod、Node 等资源没超时,这阐明是 PVC 资源对应的底层 TCP 连贯 / 应用层 HTTP/2 连贯出了问题。

在 HTTP/2 协定中,音讯被合成独立的帧(Frame),交织发送,帧是最小的数据单位。每个帧会标识属于哪个流(Stream),流由多个数据帧组成,每个流领有一个惟一的 ID,一个数据流对应一个申请或响应包。如上图所示,client 正在向 server 发送数据流 5 的帧,同时 server 也正在向 client 发送数据流 1 和数据流 3 的一系列帧。一个连贯上有并行的三个数据流,HTTP/2 可基于帧的流 ID 将并行、交织发送的帧从新组装成残缺的音讯。

也就是,通过 HTTP/2 的多路复用机制,一个 etcd HTTP/2 连贯,能够满足高并发状况下各种 client 对 PVC 资源的查问、创立、删除、更新、Watch 申请。

那到底是这个连贯出了什么问题呢?

明确是 APIServer 和 etcd 的网络链路呈现了异样之后,咱们又有了如下猜想:

● 异样实例 APIServer 所在节点出现异常

● etcd 集群 3 个节点底层网络异样

● etcd HTTP/2 连贯最大并发流限度(单 HTTP/2 连贯最大同时关上的并发流是有限度的)

● TCP 连贯触发了内核未知 bug,连贯疑似 hang 住一样

● …..

然而咱们对 APIServer 和 etcd 节点进行了具体的系统诊断、网络诊断,除了发现 etcd 节点呈现了大量毛刺丢包,并未发现其余显著问题,以后 etcd 节点也仅应用了 1/3 的节点带宽,然而申请仍然巨慢,因而根本能够排除带宽超限导致的申请超时。

etcd HTTP/2 连贯最大并发流限度的特点是此类资源含有较大的并发申请数、同时应有局部成功率,不应全副超时。然而通过咱们一番深刻排查,通过审计日志、Metrics 监控发现 PVC 资源的申请绝大部分都是 5XX 超时谬误,简直没有胜利的,同时咱们发现了一个 CR 资源也呈现了连贯异样,然而它的并发申请数很少。基于以上剖析,etcd HTTP/2 连贯最大并发流限度猜想也被排除。

问题再次陷入未知,此刻,就要寄出终极杀器——抓包大法,来剖析到底整个 TCP 连贯链路产生了什么。

要通过抓包来剖析具体申请,首先咱们就要面临一个问题,以后单个 APIServer 到 etcd 同时存在上百个连贯,咱们该如何放大范畴,定位到具体异样的 TCP 连贯呢?要定位到具体的异样连贯,次要会面临以下几个问题:

  1. 数据量大:APIServer 大部分连贯都会不停的向 etcd 申请数据,而且局部申请的数据量比拟大,如果抓全量的包剖析起来会比拟艰难。
  2. 新建连贯无奈复现:该问题只影响个别的资源申请,也就是只影响存量的几个长链接,增量连贯无奈复现。
  3. APIServer 和 etcd 之间应用 https 通信,解密艰难,无奈无效剖析包的内容:因为长链接曾经建设,曾经过了 tls 握手阶段,同时节点平安管控限度,短时间不容许应用 ebpf 等 hook 机制,因而无奈拿到解密后的内容。

为了定位到具体的异样连贯,咱们做了以下几个尝试:

  1. 首先针对响应慢的资源,不通过 Loadbalancer,间接申请 APIServer 对应的 RS,将范畴放大到具体某一个 APIServer 正本上
  2. 针对异样的 APIServer 正本,先将它从 Loadbalancer 的后端摘掉,一方面能够尽快恢复业务,另一方面也能够防止有新的流量进来,能够升高抓包数据量(PS:摘掉 RS 的同时,Loadbalancer 反对发双向 RST,能够将客户端和 APIServer 之间的长链接也断掉)。
  3. 对异样的 APIServer 正本进行抓包,抓取 APIServer 申请 etcd 的流量,同时通过脚本对该异样的 APIServer 发动并发查问,只查问响应慢的资源,而后对抓包数据进行剖析,同一时间点 APIServer 对 etcd 有大量并发申请的长连贯即为异样连贯。

定位到异样连贯后,接下来就是剖析该连贯具体为什么异样,通过剖析咱们发现 etcd 回给 APIServer 的包都很小,每个 TCP 包都是 100 字节以下:

通过 ss 命令查看连贯的 TCP 参数,发现 MSS 竟然只有 48 个字节:

这里简略介绍下 TCP MSS(maximum segment size)参数, 中文名最大分段大小,单位是字节,它限度每次网络传输的数据包的大小,一个申请由多个数据包组成,MSS 不包含 TCP 和 IP 协定包头局部。TCP 中还有一个跟包大小的参数是 MTU(maximum transmission unit),中文名是最大传输单位,它是互联网设施(路由器等)能够接管的最大数据包的值,它包含 TCP 和 IP 包头,以及 MSS。

受限于 MTU 值大小(最大 1500),MTU 减去 TCP 和 IP 包头,云底层网络转发所应用的协定包头,MSS 个别在 1400 左右。然而在咱们这里,如下图所示,对 ss 统计分析能够看到,有 10 几个连贯 MSS 只有 48 和 58。任意一个申请尤其是查问类的,都会导致申请被拆分成大量小包发送,应用层必定会呈现各类超时谬误,client 进而又会触发各种重试,最终整个连贯呈现齐全不可用。

在确定是 MSS 值过小导致下层各种诡异超时景象之后,咱们进一步思考,是什么中央改掉了 MSS。然而 MSS 协商是在三次握手中建设的,存量的异样连贯比拟难找到相干信息。

内核剖析过程

抓包剖析

为了进一步搞清楚问题产生的根本原因,咱们在危险可控的状况下,在业务低峰期,凌晨 1 点,被动又做了一次类似的变更,来尝试复现问题。从抓到的包看 TCP 的选项,发现 MSS 协商的都是比拟大,没有特地小的状况:

仅 SYN,SYN+ack 包带有 MSS 选项,并且值都大于 1000,排除底层网络设备篡改了 MSS 造成的问题。

内核剖析

那内核当中,什么中央会批改 MSS 的值?

假如一开始不理解内核代码,然而咱们能晓得这个 MSS 字段是通过 ss 命令输入的,那么能够从 ss 命令代码动手。该命令来自于 iproute2 这个包,搜寻下 MSS 关键词,可知在 ss 程序中,通过内核提供的 sock_diag netlink 接口,查问到的信息。在 tcp_show_info 函数中做解析展现:

可知 MSS 字段来自内核的 tcpi_snd_mss。

之后,从内核外面查找该值赋值的中央:

  2   2749  net/ipv4/tcp.c <<tcp_get_info>>
             info->tcpi_snd_mss = tp->mss_cache;

持续找 mss_cache 的赋值地位:

只有 2 处,第一处是 tcp_init_sock 中调用,当中的赋值是初始值tcp_mss_DEFAULT 536U, 与抓到的现场不匹配,间接可疏忽。

第二处:

这外面可能 2 个中央会影响,一个是 pmtu, 另外一个是 tcp_bound_to_half_wnd.

抓包外面没显著看到 MTU 异样造成的流异样反馈信息。聚焦在窗口局部:

这里有个很可疑的中央。若是窗口很小,那么最初会取窗口与 68 字节 -tcp_header_len 的最大值,tcp_header_len 默认 20 字节的话,刚好是 48 字节。和咱们抓包看到的最小的 MSS 为 48 统一,嫌疑很大。

那什么中央会批改最大窗口大小?

TCP 批改的中央并不多,tcp_rcv_synsent_state_process 中收到 SYN 包批改(状态不合乎咱们以后的 case),另外次要的是在 tcp_ack_update_window 函数中,收 ack 之后去更新:

剖析收到的 ack 包,咱们能发现对方通告的窗口, 除了 SYN 之外,都是 29 字节:

SYN 包外面能看到放大因子是:

按理说,计算出来的窗口依照

   if (likely(!tcp_hdr(skb)->syn))
        nwin <<= tp->rx_opt.snd_wscale;

计算,应该是 14848 字节,然而从 MSS 的体现看,仿佛这个 scale 失落了。实际上,比照失常和异样的连贯,发现的确 TCP 的 scale 选项在内核外面,真的丢了:

从 ss 外面比照失常和异样的连贯看,不仅仅是 window scale 没了,连 timestamp, sack 选项也同时隐没了!很神奇!

咱们来看看 ss 外面获取的这些字段对应到内核的什么值:

ss 代码:

对应到内核 tcp_get_info 函数的信息:

那内核什么中央会清空 window scale 选项?

搜寻把 wscale_ok 改为 0 的中央,实际上并不多,咱们能够比拟轻易确定是 tcp_clear_options 函数干的:

static inline void tcp_clear_options(struct tcp_options_received *rx_opt)
{
    rx_opt->tstamp_ok = rx_opt->sack_ok = 0;
    rx_opt->wscale_ok = rx_opt->snd_wscale = 0;
}

他同时会清空 ts, sack, scale 选项,和咱们的场景再匹配不过。

搜寻 tcp_clear_options 的调用方,次要在 tcp_v4_conn_request 和 cookie_check_timestamp 两个中央,具体调用地位的逻辑都和 SYN cookie 个性有较强关联性。

    if (want_cookie && !tmp_opt.saw_tstamp)
        tcp_clear_options(&tmp_opt);
bool cookie_check_timestamp(struct tcp_options_received *tcp_opt,
            struct net *net, bool *ecn_ok)
{
    /* echoed timestamp, lowest bits contain options */
    u32 options = tcp_opt->rcv_tsecr & TSMASK;

    if (!tcp_opt->saw_tstamp)  {tcp_clear_options(tcp_opt);
        return true;
    }

两个条件都比拟统一,看起来是 SYN cookie 失效的状况下,对方没有传递 timestamp 选项过去(实际上,依照 SYN cookie 的原理,发送给对端的回包中,会保留有编码进 tsval 字段低 6 位的选项信息),就会调用 tcp_clear_options,清空窗口放大因子等选项。

从系统日志外面,咱们也能察看到的确触发了 SYN cookie 逻辑:

所以,根因终于开始明确,etcd 重启,触发了大量 APIServer 霎时到 etcd 的新建连贯,短时间的大量新建连贯触发了 SYN cookie 爱护查看逻辑。然而因为客户端没有在后续包中将 timestamp 选项传过来,造成了窗口放大因子失落,影响传输性能

客户端为什么不在每一个包都发送 timestamp,而是只在第一个 SYN 包发送?

首先,咱们来看看 RFC 标准,协商了 TCP timestamp 选项后,是应该选择性的发?还是每一个都发?

答案是,后续每一个包 TCP 包都须要带上工夫戳选项。

那么,咱们的内核中为什么 SYN 包带了 TCP timestamp 选项,然而后续的包没有了呢?

搜寻 tsval 关键词:

能够看到 tcp_syn_options 函数中,被动建连时候,会依据 sysctl_tcp_timestmps 配置选项决定是否开启工夫戳选项。

查看客户端零碎,该选项的确是关上的,合乎预期:

net.ipv4.tcp_timestamps = 1

那为什么别的包都不带了呢?客户端角度,发了 SYN,带上工夫戳选项,收到服务端 SYN+ack 以及工夫戳,走到 tcp_rcv_synsent_state_process 函数中,调用 tcp_parse_options 解析 TCP 选项:

这里,就算服务端回包带了 TCP 工夫戳选项,本机也要看两个 sysctl:

● sysctl_tcp_timestamps 这个比拟好了解。内核规范的。

● sysctl_tcp_wan_timestamps 这个看起来是针对外网关上工夫戳的选项?很诡异。

此处就会有坑了,如果 wan_timestamps 选项没关上,saw_tstamp 不会设置为 1,后续也就不会再发送 TCP 工夫戳选项。

查看 wan_timestamps 设置,的确默认是敞开的:

net.ipv4.tcp_wan_timestamps = 0

所以这里假相也就明确了:因为 tcp_timestamps 选项关上,所以内核建连是会发送工夫戳选项协商,同时因为 tcp_wan_timestamps 敞开,在应用非公有网段的状况下,会造成后续的包不带工夫戳(云环境容器管控非凡网段的起因)。

和 SYN cookie 的场景组合在一起,最终造成了 MSS 很小,性能较差的问题。

tcp_wan_timestamps 是外部的个性,是为了解决外网工夫戳不正确而加的个性,TKE 团队发现该问题后已反馈给相干团队优化,以后曾经优化实现。

总结

本文问题表象,APIServer 资源申请慢,看似比较简单,实则在通过 APIServer Metrics 指标、etcd Metrics 指标、APIServer 和 etcd Trace 日志、审计日志、APIServer 和 etcd 源码深入分析后,排除了各种可疑起因,最初发现是一个十分底层的网络问题。

面对底层网络问题,在找到稳固复现的办法后,咱们通过抓包神器 tcpdump,丰盛弱小的网络工具 iproute2 包(iproute2 包中的 ss 命令,可能获取 TCP 的很多底层信息,比方 rtt,窗口因子,重传次数等,对诊断问题很有帮忙),再联合 TCP RFC、linux 源码(代码背后无机密,不论是用户态工具还是内核态),多团队的合作,胜利破案。

通过此案例,更让咱们粗浅领会到,永远要对现网生产环境放弃敬畏之心,任何操作都可能会引发不可预知的危险,监控零碎不仅要检测变更服务外围指标,更要对主调方的外围指标进行深刻检测。

参加本文问题定位的还有腾讯云网络专家赵奇园、内核专家杨玉玺。

正文完
 0