记一次 K8S HostPort 引发的服务故障排错指南
问题背景
排查过程
-A CNI-DN-xxxx -p tcp -m tcp --dport 3306 -j DNAT --to-destination 10.224.0.222:3306
-A CNI-HOSTPORT-DNAT -m comment --comment "dnat name": \"cni0\" id: \"xxxxxxxxxxxxx\"" -j CNI-DN-xxx
-A CNI-HOSTPORT-SNAT -m comment --comment "snat name": \"cni0\" id: \"xxxxxxxxxxxxx\"" -j CNI-SN-xxx
-A CNI-SN-xxx -s 127.0.0.1/32 -d 10.224.0.222/32 -p tcp -m tcp --dport 80 -j MASQUERADE
这几条规则是谁入到 iptables 中的? 怎么解决呢,是不是删掉就可以?
问题复现
同样是 Mysql,为何 Mysql-A 没有呢? 那么比对一下这两个 Mysql 的部署差异
比对发现, 除了用户名密码,ns 不一样外,Mysql-B 部署时使用了 hostPort=3306, 其它的并无异常
难道是因为 hostPort?
作者日常会使用 NodePort,倒却是没怎么在意 hostPort,也就停留在 hostPort 跟 NodePort 的差别在于 NodePort 是所有 Node 上都会开启端口,而 hostPort 只会在运行机器上开启端口,由于 hostPort 使用的也少,也就没太多关注,网上短暂搜了一番,描述的也不是很多,看起来大家也用的不多
那到底是不是因为 hostPort 呢?
Talk is cheap, show me the code
通过实验来验证,这里简单使用了三个 nginx 来说明问题, 其中两个使用了 hostPort,这里特意指定了不同的端口,其它的都完全一样,发布到集群中,yaml 文件如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hostport2
labels:
k8s-app: nginx-hostport2
spec:
replicas: 1
selector:
matchLabels:
k8s-app: nginx-hostport2
template:
metadata:
labels:
k8s-app: nginx-hostport2
spec:
nodeName: spring-38
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
hostPort: 31123
Finally,问题复现:
可以肯定,这些规则就是因为使用了 hostPort 而写入的,但是由谁写入的这个问题还是没有解决?
罪魁祸首
作者开始以为这些 iptables 规则是由 kube-proxy 写入的, 但是查看 kubelet 的源码并未发现上述规则的关键字
再次实验及结合网上的探索,可以得到以下结论:
首先从 kubernetes 的官方发现以下描述:
The CNI networking plugin supports hostPort. You can use the official portmap[1] plugin offered by the CNI plugin team or use your own plugin with portMapping functionality.
If you want to enable hostPort support, you must specify portMappings capability in your cni-conf-dir. For example:
{
"name": "k8s-pod-network",
"cniVersion": "0.3.0",
"plugins": [
{
# ...其它的plugin
}
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
也就是如果使用了 hostPort, 是由 portmap 这个 cni 提供 portMapping 能力,同时,如果想使用这个能力,在配置文件中一定需要开启 portmap,这个在作者的集群中也开启了,这点对应上了
另外一个比较重要的结论是:
The CNI ‘portmap’ plugin, used to setup HostPorts for CNI, inserts rules at the front of the iptables nat chains; which take precedence over the KUBE- SERVICES chain. Because of this, the HostPort/portmap rule could match incoming traffic even if there were better fitting, more specific service definition rules like NodePorts later in the chain
参考: https://ubuntu.com/security/CVE-2019-9946
翻译过来就是使用 hostPort 后,会在 iptables 的 nat 链中插入相应的规则,而且这些规则是在 KUBE- SERVICES 规则之前插入的,也就是说会优先匹配 hostPort 的规则,我们常用的 NodePort 规则其实是在 KUBE- SERVICES 之中,也排在其后
从 portmap 的源码中果然是可以看到相应的代码
所以,最终是调用 portmap 写入的这些规则。
端口占用
进一步实验发现,hostport 可以通过 iptables 命令查看到, 但是无法在 ipvsadm 中查看到
使用 lsof/netstat 也查看不到这个端口,这是因为 hostport 是通过 iptables 对请求中的目的端口进行转发的,并不是在主机上通过端口监听
既然 lsof 跟 netstat 都查不到端口信息,那这个端口相当于没有处于 listen 状态?
如果这时再部署一个 hostport 提定相同端口的应用会怎么样呢?
结论是: 使用 hostPort 的应用在调度时无法调度在已经使用过相同 hostPort 的主机上,也就是说,在调度时会考虑 hostport
如果强行让其调度在同一台机器上,那么就会出现以下错误,如果不删除的话,这样的错误会越来越多,吓的作者赶紧删了.
如果这个时候创建一个 nodePort 类型的 svc, 端口也为 31123,结果会怎么样呢?
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-nodeport2
labels:
k8s-app: nginx-nodeport2
spec:
replicas: 1
selector:
matchLabels:
k8s-app: nginx-nodeport2
template:
metadata:
labels:
k8s-app: nginx-nodeport2
spec:
nodeName: spring-38
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport2
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 31123
selector:
k8s-app: nginx-nodeport2
可以发现,NodePort 是可以成功创建的,同时监听的端口也出现了.
从这也可以说明使用 hostposrt 指定的端口并没有 listen 主机的端口,要不然这里就会提示端口重复之类
那么问题又来了,同一台机器上同时存在有 hostPort 跟 nodePort 的端口,这个时候如果 curl 31123 时, 访问的是哪一个呢?
经多次使用 curl 请求后,均是使用了 hostport 那个 nginx pod 收到请求
原因还是因为 KUBE-NODE-PORT 规则在 KUBE-SERVICE 的链中是处于最后位置,而 hostPort 通过 portmap 写入的规则排在其之前
因此会先匹配到 hostport 的规则,自然请求就被转到 hostport 所在的 pod 中,这两者的顺序是没办法改变的,因此无论是 hostport 的应用发布在前还是在后都无法影响请求转发
另外再提一下,hostport 的规则在 ipvsadm 中是查询不到的,而 nodePort 的规则则是可以使用 ipvsadm 查询得到
问题解决
要想把这些规则删除,可以直接将 hostport 去掉,那么规则就会随着删除,比如下图中去掉了一个 nginx 的 hostport
另外使用较多的 port-forward 也是可以进行端口转发的,它又是个什么情况呢? 它其实使用的是 socat 及 netenter 工具,网上看到一篇文章,原理写的挺好的,感兴趣的可以看一看
参考: https://vflong.github.io/sre/k8s/2020/03/15/how-the-kubectl-port-forward-command-works.html
生产建议
一句话,生产环境除非是必要且无他法,不然一定不要使用 hostport,除了会影响调度结果之外,还会出现上述问题,可能造成的后果是非常严重的
来源:https://izsk.me/2021/08/01/Kubernetes-hostport/
文章转载:高效运维
(版权归原作者所有,侵删)
点击下方“阅读原文”查看更多