Cilium 源码解析:Node 之间的健康探测(health probe)机制
更多奇技淫巧欢迎订阅博客:https://fuckcloudnative.io
前言
排查问题时研究了一下 Cilium health probe 相关的代码,本文略作整理,仅供参考。代码基于 1.8.4
。
流程图:
调用栈:
runDaemon // daemon/cmd/daemon_main.go|-initHealth // daemon/cmd/health.go| |-health.Launch // cilium-health/launch/launcher.go| | |-ch.server = server.NewServer()| | | |-newServer| | | |-api := restapi.NewCiliumHealthAPI| | | |-srv := healthApi.NewServer(api)| | |-ch.client = client.NewDefaultClient()| | |-go runServer() // cilium-health/launch/launcher.go| | |-go ch.server.Serve()| | | |-for { go tcpServer.Serve() }| | | |-go runActiveServices()| | | |-s.FetchStatusResponse| | | | |-getAllNodes| | | | | |-s.Daemon.GetClusterNodes| | | | | |-"GET /cluster/nodes" to cilium-agent API| | | | |-prober.Run| | | | | |-p.Pinger.Run| | | | | |-p.runHTTPProbe| | | | | |-for node in nodes:| | | | | for ip in [nodeIP, healthIP]:| | | | | for port in ports:| | | | | httpProbe(node, ip, port) // port=4240| | | | |-updateCluster(prober.Results)| | | |-s.getNodes| | | |-prober.OnIdle = func() {| | | | updateCluster(prober.Results)| | | | nodesAdded, nodesRemoved := getNodes()| | | | |-s.Daemon.GetClusterNodes| | | | |-"GET /cluster/nodes" to cilium-agent API| | | | prober.setNodes(nodesAdded, nodesRemoved)| | | | }| | | |-prober.RunLoop| | | |-s.Server.Serve() // api/v1/health/server/server.go| | | |-s.Listen() // listen at unix://xxx/health.sock| | | |-if unix sock:| | | |-domainSocket.Serve(l)| | |-for {| | ch.client.Restapi.GetHealthz()| | ch.setStatus(status)| | }| || |-pidfile.Remove("/var/run/cilium/state/health-endpoint.pid")| |-UpdateController("cilium-health-ep", func() {| DoFunc: func() {| LaunchAsEndpoint| RunInterval: 60 * time.Second,| }| })||-startAgentHealthHTTPService // daemon/cmd/agenthealth.go|- mux.Handle("/healthz")
1. 设计
Full-mesh 健康探测
在 Cilium 的设计中,每个 node 都可以主动探测(probe)其他 node 的健康状态, 这样它们就能拿到第一手的全局健康状态信息(global health status of all nodes)。
默认情况下,任何两个 node 之间都会互相 probe,因此最终形成一张 full-mesh probe 网络,如下图所示:
Probe 行为由 cilium-agent 的两个开关控制,默认都是开的,
enable-health-checking
:probe 其他 node的健康状态。enable-endpoint-health-checking
:probe 其他 node 上的cilium-health-ep
的健康状态。稍后会介绍cilium-health-ep
是什么。
四种 probe 类型
从网络层级的角度,probe 分两个维度:
三层(L3)探测:
ping
(ICMP)七层(L7)探测:
GET
API。
再结合以上两个开关,总共就有四种 probe:
enable-health-checking=true
:ICMP probe (L3):
ping <NodeIP>
HTTP probe (L7):
GET http://<NodeIP>:4240/hello
enable-endpoint-health-checking=true
:ICMP probe (L3):
ping <HealthIP>
HTTP probe (L7):
GET http://<HealthIP>:4240/hello
Probe results
Probe 结果会缓存到 cilium-agent 中,可以通过下面命令查看(#
开头的注释是后加的):
(node1) $ cilium-health status
Probe time: 2020-12-29T15:17:02Z
Nodes:cluster1/node1 (localhost):Host connectivity to 10.5.6.60: # <-- NodeIPICMP to stack: OK, RTT=9.557967msHTTP to agent: OK, RTT=405.072µsEndpoint connectivity to 10.6.2.213: # <-- HealthIPICMP to stack: OK, RTT=9.951333msHTTP to agent: OK, RTT=468.645µscluster1/node2:...cluster2/node100:Host connectivity to 10.6.6.100: # <-- NodeIPICMP to stack: OK, RTT=10.164048msHTTP to agent: OK, RTT=694.196µsEndpoint connectivity to 10.22.1.3: # <-- HealthIPICMP to stack: OK, RTT=11.282117msHTTP to agent: OK, RTT=765.092µs
如果启用了 clustermesh[1],那 cilium-agent 也会对其他集群的 node 进行探测,所以我们看到上面的输出中有其他集群的 node 信息。
cilium-health-ep
: cilium-health endpoint
简单来说,cilium-agent 会为每个 Pod 创建一个它所谓的 Endpoint 对象。而在这里, cilium-health-ep
是个特殊的 endpoint:
(node) $ cilium endpoint list
ENDPOINT POLICY (ingress) POLICY (egress) IDENTITY LABELS (source:key[=value]) IPv4 STATUS
...
2399 Disabled Disabled 4 reserved:health 10.6.2.213 ready
...
它并不是一个 Pod,但可以看到,它有自己独立的
Endpoint ID:随机分配,每台节点内唯一。
Identity:reserved identity,固定值 4,也就是说每台节点上的 cilium-health identity 都是 4。
IP address:cilium-agent 随机分配。
Veth pair:
lxc_health@<peer>
也可以用下面的方式查看 cilium-health 使用的 IP 地址:
$ cilium status --all-addresses | grep health10.6.2.213 (health)
小结
由以上内容可知,Cilium health probe 的整体设计还是非常简单直接的,并没有很高深的东西。
但到了实现层面,就要复杂很多了。
2. 实现
完整的流程图和调用关系见本文开篇。接下来分步介绍这个过程。
初始化流程
从 cilium-agent
(daemon)初始化代码开始。
大致步骤:
调用
initHealth()
完成 prober 的初始化工作,大部分工作都在这里面完成。初始化 prober,顺序对其他所有 node 执行 probe。
创建
cilium-health-ep
,这一步不依赖上面 probe 的结果,二者是独立进行的。
注册 cilium-agent
/healthz
API 并开始提供服务。
这个 API 用于检测 cilium-agent 是否正常。
cilium status --brief
返回的就是这个 API 的结果。
但这里要注意,以上两个步骤是异步进行的,initHealth()
中会创建很多 goroutine 异步执行。也就是说,cilium-agent 的 /healthz
很快会进入 ready 状态,而并不会等待 initHealth()
对所有 node 执行完 health probe,因为后者可能需要几秒钟、几分钟,甚至几个小时。
大部分工作都在 initHealth()
中完成,接下来看这里的实现。
initHealth() -> Launch() -> runServer() -> server.Serve()
initHealth()
做的事情:
调用
health.Launch()
,后者初始化
ch.server
初始化
ch.client
go ch.runServer()
,接下来的大部分逻辑,都在这里面。
清理之前的 cilium-health pid 文件(
/var/run/cilium/state/health-endpoint.pid
)创建一个名为
cilium-health-ep
的 controller(定时任务),这里面会创建
cilium-health
endpoint,定期将该
cilium-health
endpoint 状态同步到 K8s。
接下来看 go ch.runServer()
。
runServer()
代码见 cilium-health/launch/launcher.go
。
逻辑:
等待 cilium-agent 启动成功(
GET /healthz
返回成功),然后转步骤 2删除之前的
/var/run/cilium/health.sock
文件。本地执行cilium-health
命令时会用到这个 socket 文件。go ch.server.Serve()
:创建一个 goroutine,在里面启动 cilium-health API server,主逻辑在这里面,包括:创建 TCP servers
运行
runActiveServices()
,这里面会创建 prober 和 unix servers,其中 unix server 在Listen()
时会创建新的health.sock
文件。
等待,直到新的
health.sock
文件 ready,然后给其设置合适的文件权限以
statusProbeInterval
的间隔,定时向 cilium-agent 发起GET /healthz
,并将结果保存
ch.server.Serve()
实现:
// pkg/health/server/server.go// Serve spins up the following goroutines:
// * TCP API Server: Responders to the health API "/hello" message, one per path
// * Prober: Periodically run pings across the cluster, update server's connectivity status cache.
// * Unix API Server: Handle all health API requests over a unix socket.
func (s *Server) Serve() (err error) {for i := range s.tcpServers {srv := s.tcpServers[i]go func() {errors <- srv.Serve()}()}go func() {errors <- s.runActiveServices()}()err = <-errors // Block for the first error, then return.return err
}
这里面最重要的是 runActiveServices()
。
runActiveServices()
主要步骤:
执行
FetchStatusResponse()
,这会用一个三层 for 循环对所有 node 顺序进行 probe;设置 prober
OnIdle()
handler,然后启动prober.RunLoop()
,定期更新 node 集 合;执行
s.Server.Seve()
,开始接收 Unix、HTTP、HTTPS 请求。
注意其中的第一步,
对所有 node 的 probe 操作是顺序进行的。
每次 probe 如果不通,需要过
30s
超时退出。
因此,如果有大量 node 不通,这里就会花费大量时间,导致后面的 UNIX server 迟 迟无法启动,具体表现就是宿主机执行 cilium-health
命令报以下错误:
$ cilium-health status
Error: Cannot get status: Get "http://%2Fvar%2Frun%2Fcilium%2Fhealth.sock/v1beta/status": dial unix /var/run/cilium/health.sock: connect: no such file or directory
因为这个文件是在第三步 s.Server.Serve() -> Listen()
里面才创建的。
代码
// pkg/health/server/server.go// Run services that are actively probing other hosts and endpoints over ICMP and HTTP,
// and hosting the health admin API on a local Unix socket.
func (s *Server) runActiveServices() error {s.FetchStatusResponse()nodesAdded, _, _ := s.getNodes()prober := newProber(s, nodesAdded)prober.OnIdle = func() {// Fetch results and update set of nodes to probe every ProbeIntervals.updateCluster(prober.getResults())if nodesAdded, nodesRemoved, err := s.getNodes(); err != nil {log.WithError(err).Error("unable to get cluster nodes")} else {prober.setNodes(nodesAdded, nodesRemoved)}}prober.RunLoop()return s.Server.Serve()
}
最后一行 s.Server.Serve()
调用到下面这里:
// api/v1/server/server.go// Serve the api
func (s *Server) Serve() (err error) {if !s.hasListenerss.Listen() // net.Listen(s.SocketPath) -> create sock fileif s.handler == nil // set default handler, if none is sets.SetHandler(s.api.Serve(nil))if s.hasScheme(schemeUnix) { // "Serving cilium at unix://%s", s.SocketPathgo func(l net.Listener) {domainSocket.Serve(l)}(s.domainSocketL)}if s.hasScheme(schemeHTTP) {...}if s.hasScheme(schemeHTTPS) {...}return nil
}
创建 cilium-health-ep
如上图所示,创建 cilium-health-ep
过程和 2.2~2.4
是异步的(从 go runServer()
开始就是异步了),因此不用等待后者的完成。
cilium-health-ep
也是一个 Endpoint,因此会经历:
分配 IP
创建 netns
创建 veth pair(
lxc_health
)创建 Endpoint
分配 Identity:注意 Cilium 里面都是先创建 Endpoint,再为 Endpoint 分配 Identity
Regenerate BPF
等等过程,代码见 cilium-health/launch/endpoint.go。
3. CLI cheat sheet
一些相关的命令。
Check cilium agent status
cilium status --brief
: brief output, which internally performs"GET /healthz"
cilium status
: normal outputcilium status --verbose
: verbose output
Check connectivity results
$ cilium-health status
...$ cilium status --verbose
...
Cluster health: 265/268 reachable (2020-12-31T09:00:03Z)Name IP Node Endpointscluster1/node1 (localhost) 10.5.6.60 reachable reachablecluster2/node2 10.6.9.132 reachable reachable...
Check health info in CT/NAT tables
ICMP records in Conntrack (CT) table and NAT table:
$ cilium bpf ct list global | grep ICMP |head -n4
ICMP IN 10.4.9.12:0 -> 10.5.8.4:518 expires=1987899 RxPackets=1 RxBytes=50 RxFlagsSeen=0x00 LastRxReport=1987839 TxPackets=1 TxBytes=50 TxFlagsSeen=0x00 LastTxReport=1987839 Flags=0x0000 [ ] RevNAT=0 SourceSecurityID=2 IfIndex=0
ICMP OUT 10.5.2.101:47153 -> 10.4.9.11:0 expires=1987951 RxPackets=0 RxBytes=0 RxFlagsSeen=0x00 LastRxReport=0 TxPackets=1 TxBytes=50 TxFlagsSeen=0x00 LastTxReport=1987891 Flags=0x0000 [ ] RevNAT=0 SourceSecurityID=0 IfIndex=0$ cilium bpf nat list | grep ICMP |head -n4
ICMP OUT 10.5.2.101:65204 -> 10.4.6.9:0 XLATE_SRC 10.5.2.101:65204 Created=1987884sec HostLocal=1
ICMP IN 10.4.4.11:0 -> 10.5.2.101:39843 XLATE_DST 10.5.2.101:39843 Created=1987884sec HostLocal=1
参考资料
[1]
启用了 clustermesh: http://arthurchiao.art/blog/cilium-clustermesh/
你可能还喜欢
点击下方图片即可阅读
容器网络一直在颤抖,罪魁祸首竟然是 ipvs 定时器
云原生是一种信仰 ????
扫码关注公众号
后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!
点击 "阅读原文" 获取更好的阅读体验!
❤️给个「在看」,是对我最大的支持❤️
Cilium 源码解析:Node 之间的健康探测(health probe)机制相关推荐
- Spring源码解析:自定义标签的解析过程
2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...
- JAVA并发容器-ConcurrentHashMap 1.7和1.8 源码解析
HashMap是一个线程不安全的类,在并发情况下会产生很多问题,详情可以参考HashMap 源码解析:HashTable是线程安全的类,但是它使用的是synchronized来保证线程安全,线程竞争激 ...
- ConcurrentHashMap 1.7和1.8 源码解析
HashMap是一个线程不安全的类,在并发情况下会产生很多问题,详情可以参考HashMap 源码解析:HashTable是线程安全的类,但是它使用的是synchronized来保证线程安全,线程竞争激 ...
- (Nacos源码解析五)Nacos服务事件变动源码解析
Nacos源码解析系列目录 Nacos 源码编译运行 (Nacos源码解析一)Nacos 注册实例源码解析 (Nacos源码解析二)Nacos 服务发现源码解析 (Nacos源码解析三)Nacos 心 ...
- Aspects源码解析
Aspects使用方法 Aspects源码只有两个文件:Aspects.h和Aspects.m文件.使用的方式就是对NSObject添加了一个Category,其中有两个方法分别为类和对象添加切面bl ...
- 【dubbo源码解析】--- dubbo中Invoker嵌套调用底层原理
本文对应源码地址:https://github.com/nieandsun/dubbo-study 文章目录 1 dubbo中Invoker的重要性 2 dubbo RPC链条中代理对象的底层逻辑 2 ...
- Cilium创建pod network源码解析
01 Overview 我们生产K8s使用容器网络插件 Cilium 来创建 Pod network,下发 eBPF 程序实现 service 负载均衡来替换 kube-proxy,并且使用 BGP ...
- btcd源码解析——peer节点之间的区块数据同步 (3) —— 非headersFirstMode模式
文章目录 1. 写在前面 2. 非headersFirstMode模式下的数据同步过程 2.1 peer A 发送"获取区块哈希"的请求 2.2 peer B 响应"获取 ...
- JUC.Condition学习笔记[附详细源码解析]
JUC.Condition学习笔记[附详细源码解析] 目录 Condition的概念 大体实现流程 I.初始化状态 II.await()操作 III.signal()操作 3个主要方法 Conditi ...
最新文章
- 基于人工智能方法的手写数字图像识别_【工程分析】基于ResNet的手写数字识别...
- 汇编语言--转移指令
- Linux常用的基本命令head、tail、tar、grep、date、cal(二)
- 网站如何从http升级成https
- 我犯的错误--关于数据库类型不对
- SAP C4C计价(Pricing)中折扣(Discount)的使用
- linux中设备文件的主要内容包括什么,LINUX期末考试复习题.doc
- [USACO4.1]麦香牛块Beef McNuggets By cellur925
- Python采集3000条北京二手房数据,看我都分析出了啥?
- java基础知识简化
- python输入一个整数、输出该整数的所有素数因子_【401】Python 求合数的所有质数因子...
- SqlServer修改密码后登陆不上
- oracle11g服务项及其启动顺序
- 小众绿软|媒体:myPlayer 2.1
- 小白量化彩票实战(5)彩票号码快速生成组合及利用数据库生成彩票号码组合
- ACM学习历程—HDU 5025 Saving Tang Monk(广州赛区网赛)(bfs)
- 【路径规划】A*三维全局路径规划(附Python实现源码)
- 档案重要吗有什么作用(转载记录避免以后麻烦)
- 「我们只投这两种AI公司」, 三位局内人首次公开AI投资的技术与产业标准
- 全概率公式与贝叶斯公式-机器学习