kubernetes故障排查记录
本文整理了在工作过程中经历的部分kubernetes故障排查记录。
1. k8s网络直接路由模式下网卡流量异常
起因
:node 1到node 2的通信延迟比其他集群高,经过vnstat监听node 2 网卡发现eth0几乎没流量,eth1流量高达几百Mbps,几乎打满带宽。
确认问题
:k8s节点node 1 eth0到node 2的流量预期经过node 2 eth0,但是却在node 2的eth1网卡监听到,流量链路异常。
分析
:在和运维同学沟通确认了接线和路由配置没问题后,画出简易网络拓扑:
确认三层网络正常,问题发生在二层数据链路层,而二层使用MAC地址定位出口网卡,查看node 1记录的目标ip 192.168.4.112对应的MAC地址发现是192.168.3.112的MAC地址,说明node 1 arp洪泛学习了错误的MAC地址。
解决方案
:
ip neighbour删除arp记录让node 1重新学习。学习错误MAC地址的原因待观察。
2. internal ip在集群安装和投产时不一致导致API Server向kubelet的请求返回x509 ip非法错误。
组建k3s/k8s集群时,kubelet初始化会经历两个步骤:
- 选择默认路由的网卡ip作为节点internal ip
- 主节点将kubelet设置的当前节点internal ip,加入kubelet服务端证书的ip白名单, 签发并下发证书,供节点的kubelet作为服务端tls证书使用
背景
: 我们的k3s/k8s集群主要用于边缘计算,使用物联网卡接入公网,流量费用较贵,因此为了在投产前能更经济更快地把基础服务的镜像拉下来,我们上游运维人员会在初始化集群时给gateway节点/主节点接入网线,加入办公室网络,其他从节点将主节点作为默认网关拉镜像。因此在初始化集群时,gateway节点会多出来个网卡,作为访问默认网关的网卡使用。
问题
:集群的基础服务的容器全部running后,运维人员给集群断开办公室网络,交付使用。发现在gateway节点使用kubectl工具无法访问pod的日志,返回报错:
Error from server: Get "https://192.168.3.123:10250/containerLogs/<NAMESPACE>/<POD_NAME>/<CONTAINER_NAME>": x509 certificate is valid for 127.0.0.1, 172.29.1.100, not 192.168.3.123
分析
:
日志请求的链路上遇到了TLS双向验证失败的问题,具体原因是某个服务在TLS握手阶段拒绝了请求方,因为请求方的ip未在该服务ip白名单内,而白名单是写进x509证书中的,进一步根据10250端口定位该服务是kubelet,也就是说,使用serving-kubelet.crt作为服务端证书的kubelet没有放行API Server的访问容器日志的请求。
询问运维同学,了解了初始化集群到投入使用的过程:
定位到问题后,找到机器上serving-kubelet.crt证书的位置,openssl x509命令查看证书内容:
$ sudo openssl x509 -in serving-kube-apiserver.crt -text
Certificate:
...
X509v3 Subject Alternative Name:
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:localhost, DNS:master, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:172.29.10.100, IP Address:10.43.0.1
...
可以看到证书SAN配置的宿主机网卡的ip白名单有127.0.0.1,172.29.10.100,并没有请求的ip 192.168.3.123
解决方案
问题确认后,需要找到合适的修复方案,达到两个目的:
1 为已投入使用的存量集群更新ip白名单
2 为后续初始化的增量的集群固定internal ip,防止将临时ip作为internal ip。
因此技术调研的方向确定为…
存量集群:
- 固定internal ip
- 更新kubelet服务端证书
增量集群:
- 固定internal ip
固定 internal ip
google后得知kubelet可以指定node-ip作为节点internal-ip, K3S可以通过/etc/rancher/k3s/config.yaml中透传kubelet配置参数,设置node-ip
更新kubelet服务端证书
未google到相关文档,需要查阅K3S源码,分析kubelet证书签署的流程和函数逻辑。
k3s 签署kubelet证书的函数调用关系:
router内配置了一条路由,向该路由发起请求会触发调用servingKubeletCert签署kubelet证书
authed.Path(prefix + "/serving-kubelet.crt").Handler(servingKubeletCert(serverConfig, serverConfig.Runtime.ServingKubeletKey, nodeAuth))
k3s 从节点加入集群获取ca,或者主节点启动后,k3s服务自身会访问该路由调用servingKubeletCert函数签署serving-kubelet.crt证书 servingKubeletCert实现:
func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBootstrapper) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if req.TLS == nil {
resp.WriteHeader(http.StatusNotFound)
return
}
nodeName, errCode, err := auth(req)
if err != nil {
sendError(err, resp, req, errCode)
return
}
caCerts, caKey, key, err := getCACertAndKeys(server.Runtime.ServerCA, server.Runtime.ServerCAKey, server.Runtime.ServingKubeletKey)
if err != nil {
sendError(err, resp, req)
return
}
ips := []net.IP{net.ParseIP("127.0.0.1")} // 默认配置的IP Address
// append 额外ip, 从请求头中的K3S-Node-IP中获取
if nodeIP := req.Header.Get(version.Program + "-Node-IP"); nodeIP != "" {
for _, v := range strings.Split(nodeIP, ",") {
ip := net.ParseIP(v)
if ip == nil {
sendError(fmt.Errorf("invalid node IP address %s", ip), resp, req)
return
}
ips = append(ips, ip)
}
}
// 签署/更新 证书
cert, err := certutil.NewSignedCert(certutil.Config{
CommonName: nodeName,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
AltNames: certutil.AltNames{
DNSNames: []string{nodeName, "localhost"},
IPs: ips,
},
}, key, caCerts[0], caKey)
if err != nil {
sendError(err, resp, req)
return
}
keyBytes, err := os.ReadFile(keyFile)
if err != nil {
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}
//返回证书内容
resp.Write(util.EncodeCertsPEM(cert, caCerts))
resp.Write(keyBytes)
})
}
得知证书额外的ip需要从请求头K3S-Node-IP的值中获取,继续了解如何设置Node-IP
在pkg/cli/cmds/agent.go 定义的Agent结构体中,最终确认了Node-IP的源头
type Agent struct {
...
DisableServiceLB bool
ETCDAgent bool
LBServerPort int
ResolvConf string
DataDir string
NodeIP cli.StringSlice // node-ip list 通过cli命令行参数传递
NodeExternalIP cli.StringSlice
NodeName string
...
}
也就是说,为k3s启动命令添加–node-ip,再重启k3s服务即可刷新证书。经过测试,符合预期。
3. K3S使用自签k3s ca证书
2023-04, k3s certificate相关文档未发布,自签证书预置方案是通过issue得知的:https://github.com/k3s-io/k3s/issues/1868
一共三个ca client-ca、server-ca、request-header.ca
flow:
- Stop K3s service
- Remove all certs from /var/lib/rancher/k3s/server/tls
- Recreate all three CAs.
- Start k3s service.
4. 集群gateway节点无法转发ip报文
我们边缘计算业务会部署一套一主多从的k3s集群,只有主节点能与外网通信,称为gateway节点,其他从节点将主节点作为网关,请求公网的流量由公网转发
主节点配置源地址伪装:
iptables -A -t nat -s xxxx -j MASQUERADE
从节点配置默认网关:
ip route add default via <主节点ip>
在主节点系统从ubuntu16.04升级到20.04后,发现主节点无法转发从节点报文了,了解了linux ip转发的相关配置发现,linux系统在网卡间转发ip报文需要确认两项:
- 系统参数 net.ipv4.ip_forward=1
- iptables -A FORWARD -j ACCEPT
经过排查,发现iptables FORWARD链的流量默认DROP,是ubuntu20.04的默认配置,需要手动添加-j ACCEPT命令才能开启,可见ubuntu 20.04对网络流量的管控更严格。
5. flannel ip泄露
环境
- ubuntu16.04
- docker 19.02
- k3s 1.25.11
- flannel
起因
有pod未正常运行,kubectl get node 发现该pod未分配ip, 用kubectl describe pod 发现kubelet返回如下报错
Error adding network, no ip address available in network "cbr0"
分析
fannel使用cni插件host-local作为IPAM工具管理ip,google出古早issue讨论并在kubelet添加pod ip垃圾回收机制,kubelet重启会触发,但是现在问题仍存在,原因待分析
临时处理方案
rm -rf /var/lib/cni/flannel/ #清理ip
systemctl restart docker
systemctl restart k3s