最近几天因为一些需求,在使用几台服务器建立了一个小型的k8s集群,配置过程遇到了一些问题,记录一下。
环境
一共有四台服务器,分别是:
- 控制平面服务器A, 网卡有公网IP A_PUBLIC_IP
- 工作节点服务器B, 网卡有公网IP B_PUBLIC_IP
- 工作节点服务器C, 网卡无公网IP,但是配置了NAT,外部可以直接通过一公网IP访问到该服务器(很多公有云都是这样,网卡上分配的是一个内网地址), 分别记为 C_PRIVATE_IP 和 C_PUBLIC_IP
- 工作节点服务器D, 无公网IP,也没有NAT,只能通过内网访问, 记录为 D_PRIVATE_IP
目前这四台服务器都不在一个内网中,首先在A上使用kubeadm初始化了一个集群 kubeadm init --pod-network-cidr=10.244.0.0/16
,我选用了 Flannel 作为CNI插件,使用其vxlan模式。
VXLAN (Virtual Extensible LAN) 是一种网络虚拟化技术,用于在第 2 层网络上创建虚拟网络。它通过在第 4 层 UDP 协议上封装第 2 层帧来实现跨物理网络的数据传输,从而实现跨越多个物理网络的虚拟网络连接。
安装好网络插件后,在B、C、D上使用 kubeadm join
加入集群, 此时使用 kubectl get nodes -o wide
已经可以看到所有的节点了,但是问题没那么简单。
系列问题
无法访问位于NAT后的节点上的POD
我创建了一个replicate为3的nginx deployment,这样的话,三个工作节点上都会运行对应的pod,在尝试使用POD IP访问时,可以发现只有A、B之间可以互相访问,而C访问不到B,A、B也无法访问到C、D。了解了VXLAN的通信方式之后,不难理解这个问题, 因为C的网卡上并没有绑定其公网IP,那么C上的kubelet上报的节点IP就是C_PRIVATE_IP,而B、D也无法直接访问到C_PRIVATE_IP,所以导致了这个问题。对于位于NAT后的Node,flannel文档中的 Annotations 章节提到了这个问题:
flannel.alpha.coreos.com/node-public-ip, flannel.alpha.coreos.com/node-public-ipv6: Define the used IP of the node in case the node has multiple interface it selects the interface with the configured IP for the backend tunnel. If configured when Flannel starts it’ll be used as the public-ip and public-ipv6 flag. flannel.alpha.coreos.com/public-ip-overwrite, flannel.alpha.coreos.com/public-ipv6-overwrite: Allows to overwrite the public IP of a node that IP can be not configured on the node. Useful if the public IP can not determined from the node, e.G. because it is behind a NAT and the other nodes need to use it to create the tunnel. It can be automatically set to a nodes ExternalIP using the flannel-node-annotator. See also the “NAT” section in troubleshooting if UDP checksums seem corrupted.
因此我们给C添加了对应的annotation:
|
|
接着删除C上的flannel pod,让其重新创建,这样C上的flannel就会使用C_PUBLIC_IP作为其公网IP,重启完毕后,C上的POD就可以被其他节点访问了。
kubectl无法操作位于NAT后的节点
当配置了注解之后,flannel已经知道了对应容器的实际公网IP,也能够建立起Overlay网络,但是在使用kubectl操作时,发现kubectl无法连接到C上,这是因为kubectl是通过apiserver来操作的,apiserver并不使用Overlay网络,在NodeA上执行 kubectl logs test-nginx-deployment-xxx
也能看到报错 Error from server: Get “https://192.168.10.165:10250/containerLogs/default/test-nginx-deployment-576c6b7b6-qcxzj/nginx”: dial tcp 192.168.10.165:10250: i/o timeout,问题出在,当前flannel通过注解知道了对应node的实际公网ip,但是apiserver是不知道这个信息的,使用 kubectl get nodes -o wide
可以看到当前nodeC的INTERNAL-IP依旧是C_PRIVATE_IP,apiserver是没办法访问到这个IP的,因此我们还需要想办法修改nodeC上报的nodeIP,可以通过下面的方法实现。
首先给C的网卡添加该IP(不然kubelet启动的时候会发现指定的nodeIP不属于该设备,不会生效)
|
|
接着修改kubelet的启动参数,让其上报C_PUBLIC_IP作为nodeIP
|
|
重启kubelet后,再次查看节点信息,可以看到nodeC的INTERNAL-IP已经变成了C_PUBLIC_IP,这样apiserver就可以访问到C了。
这里补充一点,为什么一开始C无法被其他节点通过Overlay访问,也无法被apiserver访问,但是C却也创建出了对应的POD,这是因为POD的创建是C通过主动请求apiserver了解到有分配给自己的任务后创建的。并不涉及外部节点的访问C的情况。
让无法配置NAT的节点也能被控制与互相通信
这里也许还有更好的方案吗,之后再逐渐完善吧
解决了C的问题后,D上的问题也就不难解决了,起初我想通过wireguard建立一个虚拟的网络来实现,但是想到这几个节点存在跨境连接的情况,使用wireguard这类vpn可能存在不稳定的问题,因此我尝试了另一种方案。对于vxlan的端口,我使用了frp来进行端口映射,并使用注解将D的公网IP修改为frps的公网IP,这样D上的POD就可以被其他节点访问了。
frpc.toml如下
|
|
还是使用 kubectl annotate node D flannel.alpha.coreos.com/public-ip-overwrite=FRPS_SERVER_PUBLIC_IP
来修改D的公网IP为frps服务的IP,重启flannel后,D上的POD也可以被其他节点访问了。
不过要让apiserver连接到D还有一些问题,因为需要指定nodeIP,而kubelet又要检查本地接口是否有这个地址,如果添加了这个地址,frpc又无法连接到frps了(因为直接连接到本地的接口了),这个问题暂时还没有解决。
|
|
kubernetes有一个PR Add kubelet flag to disable nodeIP validation #58573, 提到了这个问题,但是并没有被合并。
不过在PR里面我看到了一个人的解决方案:
Just to leave a quick feedback, adding the address to the node actually works fine. Aside from that I found another approach which is to configure externalIP as preferred way in the API server to contact the kubelet: –kubelet-preferred-address-types=ExternalIP,InternalIP,Hostname Thank you for your time, I’ll close this.
kubeadm初始化的集群的apiserver的配置文件路径为 /etc/kubernetes/manifests/kube-apiserver.yaml,在修改保存后,apiserver会自动重启。
也就是将apiserver改成优先使用externalIP来访问kubelet,然后参考 Issue No way to configure ExternalIP addresses for a node with kubelet 来给node添加一个externalIP,这样apiserver就可以访问到kubelet了,但是这个方法还没有尝试过,不过按照他的思路,我使用了Hostname优先的方式,然后修改控制平面服务器A上的/etc/hosts文件,将nodeD的ip设置为frps的服务器ip,这样apiserver使用hostname访问nodeD的kubelet时就会使用frps的ip进行连接了。但是不知道为什么,在使用hostname解析其他节点失败的情况下它也不会使用下一个方式,而是直接报错,这个问题暂时没有解决,只能为所有的workernode都配置hosts文件了,虽然很不优雅,但是至少能用了。
不过考虑到这种方法还是太麻烦了,最后我使用的方案是 zerotier 组网后,对于这类节点,直接使用zerotier的ip来作为nodeIP。