TKE 集群组建最佳实践

Kubernetes 版本 K8S 版本迭代比较快,新版本通常包含许多 bug 修复和新功能,旧版本逐渐淘汰,建议创建集群时选择当前 TKE 支持的最新版本,后续出新版本后也是可以支持 [阅读全文]

打造云原生大型分布式监控系统(三): Thanos 部署与实践

视频

附上本系列完整视频

概述

上一篇 Thanos 架构详解 我们深入理解了 thanos 的架构设计与实现原理,现在我们来聊聊实战,分享一下如何部署和使用 Thanos。

[阅读全文]

打造云原生大型分布式监控系统(二): Thanos 架构详解

概述

之前在 大规模场景下 Prometheus 的优化手段 中,我们想尽 “千方百计” 才好不容易把 Prometheus 优化到适配大规模场景,部署和后期维护麻烦且复杂不说,还有很多不完美的地方,并且还无法满足一些更高级的诉求,比如查看时间久远的监控数据,对于一些时间久远不常用的 “冷数据”,最理想的方式就是存到廉价的对象存储中,等需要查询的时候能够自动加载出来。

Thanos (没错,就是灭霸) 可以帮我们简化分布式 Prometheus 的部署与管理,并提供了一些的高级特性:全局视图长期存储高可用。下面我们来详细讲解一下。

[阅读全文]

打造云原生大型分布式监控系统(一): 大规模场景下 Prometheus 的优化手段

概述

Prometheus 几乎已成为监控领域的事实标准,它自带高效的时序数据库存储,可以让单台 Prometheus 能够高效的处理大量的数据,还有友好并且强大的 PromQL 语法,可以用来灵活的查询各种监控数据以及配置告警规则,同时它的 pull 模型指标采集方式被广泛采纳,非常多的应用都实现了 Prometheus 的 metrics 接口以暴露自身各项数据指标让 Prometheus 去采集,很多没有适配的应用也会有第三方 exporter 帮它去适配 Prometheus,所以监控系统我们通常首选用 Prometheus,本系列文章也将基于 Prometheus 来打造云原生环境下的大型分布式监控系统。

大规模场景下 Prometheus 的痛点

Prometheus 本身只支持单机部署,没有自带支持集群部署,也就不支持高可用以及水平扩容,在大规模场景下,最让人关心的问题是它的存储空间也受限于单机磁盘容量,磁盘容量决定了单个 Prometheus 所能存储的数据量,数据量大小又取决于被采集服务的指标数量、服务数量、采集速率以及数据过期时间。在数据量大的情况下,我们可能就需要做很多取舍,比如丢弃不重要的指标、降低采集速率、设置较短的数据过期时间(默认只保留15天的数据,看不到比较久远的监控数据)。

这些痛点实际也是可以通过一些优化手段来改善的,下面我们来细讲一下。

[阅读全文]

Kubernetes 疑难杂症排查分享:神秘的溢出与丢包

上一篇 Kubernetes 疑难杂症排查分享: 诡异的 No route to host 不小心又爆火,这次继续带来干货,看之前请提前泡好茶,避免口干。

问题描述

有用户反馈大量图片加载不出来。

图片下载走的 k8s ingress,这个 ingress 路径对应后端 service 是一个代理静态图片文件的 nginx deployment,这个 deployment 只有一个副本,静态文件存储在 nfs 上,nginx 通过挂载 nfs 来读取静态文件来提供图片下载服务,所以调用链是:client –> k8s ingress –> nginx –> nfs。

猜测

猜测: ingress 图片下载路径对应的后端服务出问题了。

验证:在 k8s 集群直接 curl nginx 的 pod ip,发现不通,果然是后端服务的问题!

抓包

继续抓包测试观察,登上 nginx pod 所在节点,进入容器的 netns 中:

# 拿到 pod 中 nginx 的容器 id
$ kubectl describe pod tcpbench-6484d4b457-847gl | grep -A10 "^Containers:" | grep -Eo 'docker://.*$' | head -n 1 | sed 's/docker:\/\/\(.*\)$/\1/'
49b4135534dae77ce5151c6c7db4d528f05b69b0c6f8b9dd037ec4e7043c113e

# 通过容器 id 拿到 nginx 进程 pid
$ docker inspect -f {{.State.Pid}} 49b4135534dae77ce5151c6c7db4d528f05b69b0c6f8b9dd037ec4e7043c113e
3985

# 进入 nginx 进程所在的 netns
$ nsenter -n -t 3985

# 查看容器 netns 中的网卡信息,确认下
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 56:04:c7:28:b0:3c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.26.0.8/26 scope global eth0
       valid_lft forever preferred_lft forever

使用 tcpdump 指定端口 24568 抓容器 netns 中 eth0 网卡的包:

tcpdump -i eth0 -nnnn -ttt port 24568

在其它节点准备使用 nc 指定源端口为 24568 向容器发包:

nc -u 24568 172.16.1.21 80

观察抓包结果:

00:00:00.000000 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000206334 ecr 0,nop,wscale 9], length 0
00:00:01.032218 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000207366 ecr 0,nop,wscale 9], length 0
00:00:02.011962 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000209378 ecr 0,nop,wscale 9], length 0
00:00:04.127943 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000213506 ecr 0,nop,wscale 9], length 0
00:00:08.192056 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000221698 ecr 0,nop,wscale 9], length 0
00:00:16.127983 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000237826 ecr 0,nop,wscale 9], length 0
00:00:33.791988 IP 10.0.0.3.24568 > 172.16.1.21.80: Flags [S], seq 416500297, win 29200, options [mss 1424,sackOK,TS val 3000271618 ecr 0,nop,wscale 9], length 0

SYN 包到容器内网卡了,但容器没回 ACK,像是报文到达容器内的网卡后就被丢了。看样子跟防火墙应该也没什么关系,也检查了容器 netns 内的 iptables 规则,是空的,没问题。

排除是 iptables 规则问题,在容器 netns 中使用 netstat -s 检查下是否有丢包统计:

$ netstat -s | grep -E 'overflow|drop'
    12178939 times the listen queue of a socket overflowed
    12247395 SYNs to LISTEN sockets dropped

果然有丢包,为了理解这里的丢包统计,我深入研究了一下,下面插播一些相关知识。

[阅读全文]

Kubernetes 疑难杂症排查分享: 诡异的 No route to host

之前发过一篇干货满满的爆火文章 Kubernetes 网络疑难杂症排查分享,包含多个疑难杂症的排查案例分享,信息量巨大。这次我又带来了续集,只讲一个案例,但信息量也不小,Are you ready ?

问题反馈

有用户反馈 Deployment 滚动更新的时候,业务日志偶尔会报 “No route to host” 的错误。

分析

之前没遇到滚动更新会报 “No route to host” 的问题,我们先看下滚动更新导致连接异常有哪些常见的报错:

  • Connection reset by peer: 连接被重置。通常是连接建立过,但 server 端发现 client 发的包不对劲就返回 RST,应用层就报错连接被重置。比如在 server 滚动更新过程中,client 给 server 发的请求还没完全结束,或者本身是一个类似 grpc 的多路复用长连接,当 server 对应的旧 Pod 删除(没有做优雅结束,停止时没有关闭连接),新 Pod 很快创建启动并且刚好有跟之前旧 Pod 一样的 IP,这时 kube-proxy 也没感知到这个 IP 其实已经被删除然后又被重建了,针对这个 IP 的规则就不会更新,旧的连接依然发往这个 IP,但旧 Pod 已经不在了,后面继续发包时依然转发给这个 Pod IP,最终会被转发到这个有相同 IP 的新 Pod 上,而新 Pod 收到此包时检查报文发现不对劲,就返回 RST 给 client 告知将连接重置。针对这种情况,建议应用自身处理好优雅结束:Pod 进入 Terminating 状态后会发送 SIGTERM 信号给业务进程,业务进程的代码需处理这个信号,在进程退出前关闭所有连接。

  • Connection refused: 连接被拒绝。通常是连接还没建立,client 正在发 SYN 包请求建立连接,但到了 server 之后发现端口没监听,内核就返回 RST 包,然后应用层就报错连接被拒绝。比如在 server 滚动更新过程中,旧的 Pod 中的进程很快就停止了(网卡还未完全销毁),但 client 所在节点的 iptables/ipvs 规则还没更新,包就可能会被转发到了这个停止的 Pod (由于 k8s 的 controller 模式,从 Pod 删除到 service 的 endpoint 更新,再到 kube-proxy watch 到更新并更新 节点上的 iptables/ipvs 规则,这个过程是异步的,中间存在一点时间差,所以有可能存在 Pod 中的进程已经没有监听,但 iptables/ipvs 规则还没更新的情况)。针对这种情况,建议给容器加一个 preStop,在真正销毁 Pod 之前等待一段时间,留时间给 kube-proxy 更新转发规则,更新完之后就不会再有新连接往这个旧 Pod 转发了,preStop 示例:

    lifecycle:
      preStop:
        exec:
          command:
          - /bin/bash
          - -c
          - sleep 30
    

    另外,还可能是新的 Pod 启动比较慢,虽然状态已经 Ready,但实际上可能端口还没监听,新的请求被转发到这个还没完全启动的 Pod 就会报错连接被拒绝。针对这种情况,建议给容器加就绪检查 (readinessProbe),让容器真正启动完之后才将其状态置为 Ready,然后 kube-proxy 才会更新转发规则,这样就能保证新的请求只被转发到完全启动的 Pod,readinessProbe 示例:

    readinessProbe:
      httpGet:
        path: /healthz
        port: 80
        httpHeaders:
        - name: X-Custom-Header
          value: Awesome
      initialDelaySeconds: 15
      timeoutSeconds: 1
    
  • Connection timed out: 连接超时。通常是连接还没建立,client 发 SYN 请求建立连接一直等到超时时间都没有收到 ACK,然后就报错连接超时。这个可能场景跟前面 Connection refused 可能的场景类似,不同点在于端口有监听,但进程无法正常响应了: 转发规则还没更新,旧 Pod 的进程正在停止过程中,虽然端口有监听,但已经不响应了;或者转发规则更新了,新 Pod 端口也监听了,但还没有真正就绪,还没有能力处理新请求。针对这些情况的建议跟前面一样:加 preStop 和 readinessProbe。

下面我们来继续分析下滚动更新时发生 No route to host 的可能情况。

这个报错很明显,IP 无法路由,通常是将报文发到了一个已经彻底销毁的 Pod (网卡已经不在)。不可能发到一个网卡还没创建好的 Pod,因为即便不加存活检查,也是要等到 Pod 网络初始化完后才可能 Ready,然后 kube-proxy 才会更新转发规则。

什么情况下会转发到一个已经彻底销毁的 Pod? 借鉴前面几种滚动更新的报错分析,我们推测应该是 Pod 很快销毁了但转发规则还没更新,从而新的请求被转发了这个已经销毁的 Pod,最终报文到达这个 Pod 所在 PodCIDR 的 Node 上时,Node 发现本机已经没有这个 IP 的容器,然后 Node 就返回 ICMP 包告知 client 这个 IP 不可达,client 收到 ICMP 后,应用层就会报错 “No route to host”。

所以根据我们的分析,关键点在于 Pod 销毁太快,转发规则还没来得及更新,导致后来的请求被转发到已销毁的 Pod。针对这种情况,我们可以给容器加一个 preStop,留时间给 kube-proxy 更新转发规则来解决,参考 《Kubernetes实践指南》中的部分章节: https://k8s.imroc.io/best-practice/high-availability-deployment-of-applications#smooth-update-using-prestophook-and-readinessprobe

[阅读全文]

k8s v1.17 新特性预告: 拓扑感知服务路由

今天给大家介绍下我参与开发的一个 k8s v1.17 新特性: 拓扑感知服务路由。

名词解释

  • 拓扑域: 表示在集群中的某一类 “地方”,比如某节点、某机架、某可用区或某地域等,这些都可以作为某种拓扑域。
  • endpoint: k8s 某个服务的某个 ip+port,通常是 pod 的 ip+port。
  • service: k8s 的 service 资源(服务),关联一组 endpoint ,访问 service 会被转发到关联的某个 endpoint 上。

背景

拓扑感知服务路由,此特性最初由杜军大佬提出并设计。为什么要设计此特性呢?想象一下,k8s 集群节点分布在不同的地方,service 对应的 endpoints 分布在不同节点,传统转发策略会对所有 endpoint 做负载均衡,通常会等概率转发,当访问 service 时,流量就可能被分散打到这些不同的地方。虽然 service 转发做了负载均衡,但如果 endpoint 距离比较远,流量转发过去网络时延就相对比较高,会影响网络性能,在某些情况下甚至还可能会付出额外的流量费用。要是如能实现 service 就近转发 endpoint,是不是就可以实现降低网络时延,提升网络性能了呢?是的!这也正是该特性所提出的目的和意义。

k8s 亲和性

service 的就近转发实际就是一种网络的亲和性,倾向于转发到离自己比较近的 endpoint。在此特性之前,已经在调度和存储方面有一些亲和性的设计与实现:

  • 节点亲和性 (Node Affinity): 让 Pod 被调度到符合一些期望条件的 Node 上,比如限制调度到某一可用区,或者要求节点支持 GPU,这算是调度亲和,调度结果取决于节点属性。
  • Pod 亲和性与反亲和性 (Pod Affinity/AntiAffinity): 让一组 Pod 调度到同一拓扑域的节点上,或者打散到不同拓扑域的节点, 这也算是调度亲和,调度结果取决于其它 Pod。
  • 数据卷拓扑感知调度 (Volume Topology-aware Scheduling): 让 Pod 只被调度到符合其绑定的存储所在拓扑域的节点上,这算是调度与存储的亲和,调度结果取决于存储的拓扑域。
  • 本地数据卷 (Local Persistent Volume): 让 Pod 使用本地数据卷,比如高性能 SSD,在某些需要高 IOPS 低时延的场景很有用,它还会保证 Pod 始终被调度到同一节点,数据就不会不丢失,这也算是调度与存储的亲和,调度结果取决于存储所在节点。
  • 数据卷拓扑感知动态创建 (Topology-Aware Volume Dynamic Provisioning): 先调度 Pod,再根据 Pod 所在节点的拓扑域来创建存储,这算是存储与调度的亲和,存储的创建取决于调度的结果。

而 k8s 目前在网络方面还没有亲和性能力,拓扑感知服务路由这个新特性恰好可以补齐这个的空缺,此特性使得 service 可以实现就近转发而不是所有 endpoint 等概率转发。

[阅读全文]

Kubernetes 网络疑难杂症排查分享

大家好,我是 roc,来自腾讯云容器服务(TKE)团队,经常帮助用户解决各种 K8S 的疑难杂症,积累了比较丰富的经验,本文分享几个比较复杂的网络方面的问题排查和解决思路,深入分析并展开相关知识,信息量巨大,相关经验不足的同学可能需要细细品味才能消化,我建议收藏本文反复研读,当完全看懂后我相信你的功底会更加扎实,解决问题的能力会大大提升。

本文发现的问题是在使用 TKE 时遇到的,不同厂商的网络环境可能不一样,文中会对不同的问题的网络环境进行说明

[阅读全文]