Kubernetes 日常运维中的代码调试场景
案例1:空指针
- 问题描述 Kubenetes 调度器在调度有外挂存储需求的 pod 的时候,在获取节点信息失败时会异常退出
panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x105e283]
- 根因分析 nil pointe 是 Go 语言中最常出现的一类错误,也最容易判断,通常在 call stack 中就会告诉你哪行代码有问题
在调度器 csi.go 中的如下代码,当 node 为 nil 的时候,对 node 的引用 node.Name 就会引发空指针
node := nodeInfo.Node()
if node == nil {
return framework.NewStatus(framework.Error, fmt.Sprintf("node not found: %s" , node.Name))
}
- 解决办法
当指针为空时,不要继续引用。
案例2:Map 的读写冲突
- 问题描述:
程序在遍历 Kubernetes 对象的 Annotation 时异常退出
- 根因分析
Kubernetes 对象中 Label 和 Annotation 是 map[string]string
经常有代码需要修改这两个 Map
同时可能有其他线程 for...range 遍历
- 解决方法:
- 用 sync.RWMutex 加锁
- 使用线程安全 Map,比如 sync.Map{}
案例3:kube-proxy 消耗 10 个 CPU
- 问题描述: 客户汇报问题,kube-proxy 消耗了主机 10 个 CPU
- 根因分析
- 登录问题机器,执行 top 命令查看 cpu 消耗,可以看到 kube-proxy 的 cpu 消耗和 pid 信息
- 对 kube-proxy 进程运行 System profiling tool,发现 10 个 CPU 中,超过 60% 的 CPU 都在做垃圾回收,这说明 GC 需要回收的对象太多了,说明程序创建了大量可回收对象。
- perf top –p <pid>
Overhead Shared Obj Symbol
26.48% kube-proxy [.] runtime.gcDrain
13.86% kube-proxy [.] runtime.greyobject
10.71% kube-proxy [.] runtime.(*lfstack).pop
10.04% kube-proxy [.] runtime.scanobject
通过 pprof 分析内存占用情况
curl 127.0.0.1:10249/debug/pprof/heap?debug=2
1: 245760 [301102: 73998827520] @ 0x11ddcda 0x11f306e 0x11f35f5 0x11fbdce 0x1204a8a 0x114ed76 0x114eacb 0x11
# 0x11ddcd9
k8s.io/kubernetes/vendor/github.com/vishvananda/netlink.(*Handle).RouteListFiltered+0x679
# 0x11f306d k8s.io/kubernetes/pkg/proxy/ipvs.(*netlinkHandle).GetLocalAddresses+0xed
# 0x11f35f4 k8s.io/kubernetes/pkg/proxy/ipvs.(*realIPGetter).NodeIPs+0x64
# 0x11fbdcd k8s.io/kubernetes/pkg/proxy/ipvs.(*Proxier).syncProxyRules+0x47dd
- heap dump 分析
- GetLocalAddresses 函数调用创建了 301102 个对象,占用内存 73998827520
- 如此多的对象被创建,显然会导致 kube-proxy 进程忙于 GC,占用大量 CPU
- 对照代码分析 GetLocalAddresses 的实现,发现该函数的主要目的是获取节点本机 IP 地址,获取的方法是通过 ip route 命令获得当前节点所有 local 路由信息并转换成 go struct 并过滤掉 ipvs0网口上的路由信息
- ip route show table local type local proto kernel
- 因为集群规模较大,该命令返回 5000 条左右记录,因此每次函数调用都会有数万个对象被生成
- 而 kube-proxy 在处理每一个服务的时候都会调用该方法,因为集群有数千个服务,因此,kube-proxy在反复调用该函数创建大量临时对象
- 修复方法
- 函数调用提取到循环外
- 修复方法
案例4:线程池耗尽
- 问题描述: 在 Kubernetes 中有一个控制器,叫做 endpoint controller,该控制器符合生产者消费者模式,默认有5 个 worker 线程作为消费者。该消费者在处理请求时,可能调用的 LBaaS 的 API 更新负载均衡配置。我们发现该控制器会时不时不工作,具体表现为,该做的配置变更没发生,相关日志也不打印了。
- 根因分析: 通过 pprof 打印出该进程的所有 go routine 信息,发现 worker 线程都卡在 http 请求调用处。当worker线程调用 LBaaS API 时,底层是 net/http 包调用,而客户端在发起连接请求时,未设置客户端超时时间。这导致当出现某些网络异常时,客户端会永远处于等待状态。
- 解决方法:修改代码加入客户端超时控制。
课后练习 2.2
- 编写一个 HTTP 服务器,此练习为正式作业需要提交并批改
- 鼓励群里讨论,但不建议学习委员和课代表发满分答案,给大家留一点思考空间
- 大家视个人不同情况决定完成到哪个环节,但尽量把 a 都做完
- 接收客户端 request,并将 request 中带的 header 写入 response header
- 读取当前系统的环境变量中的 VERSION 配置,并写入 response header
- Server 端记录访问日志包括客户端 IP,HTTP 返回码,输出到 server 端的标准输出
- 当访问 localhost/healthz 时,应返回200