引子:
线上的k8s集群内部,每一台机器,都有2块网卡,一块儿em1(10网段),一个 em2(192网段)。然后,线上的机器,通过iptables限制为:访问192段的服务任意端口都是畅通的,而访问10段的服务端口,只有333端口可以连通。333端口,是线上机器的ssh端口。
线上集群的k8s,使用的calico网络。
线上有一个问题,就是容器访问当前宿主机192段IP的999端口不通,访问其他机器192段IP的9999端口畅通。
我们知道,当从容器访问宿主机的应用时,会重新进入iptables的INPUT链,而线上的iptables INPUT链如下:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 1477K 2519M cali-INPUT all -- * * 0.0.0.0/0 0.0.0.0/0 /* cali:Cz_u1IQiXIMmKD4c */
2 272K 20M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
3 273K 20M KUBE-FIREWALL all -- * * 0.0.0.0/0 0.0.0.0/0
4 263K 20M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
5 0 0 ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0
6 393 17292 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0
7 11880 1019K ACCEPT all -- em2 * 0.0.0.0/0 0.0.0.0/0
8 7 308 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:333
9 228 62052 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
意思是:
①:如果报文是途径em2网卡,访问任意其他网卡。则全部放行。(这就是为什么192段的访问全部放行)
②:如果报文是tcp,不管报文从哪个网卡出来,去往哪个网卡,只要目的端口是333,就放行。(这就是为什么10段的访问,只能访问333)
我们容器发出的报文,网卡是 cal 网卡(calico的网卡名称),所以,容器只能访问333。
错误操作:
要把9999端口放开,意味着,我要添加开放9999端口到INPUT链路中,为了让iptables再重启后依然生效,我将规则,添加到
/etc/sysconfig/iptables
在添加完成后其实并未生效,然后,有2种方式,用于让iptables生效:
①:手动添加规则,比如 iptables -t filter -I INPUT 9 -s 172.20.0.0/14 -p tcp -m tcp --dport 9999 -j ACCEPT
②:重启iptables(会自动加载 /etc/sysconfig/iptables内容 )
我选择了后者,之所以选择后者,其实是因为,即便重启了iptables,加载了刚刚的规则,原来的k8s和calico的iptables会丢失,但是,k8s会重建自己的整套iptables规则的。
事实上,k8s确实也在我重启iptables后,重建了iptables,但问题来了。
引发的2个网络问题:
1、我们线上的CI系统,是基于drone,做的二次开发,改动了很多的东西,添加了很多特性,结果上面一系列操作之后,我发现,我们的CI系统中,个别镜像的构建(Dockerfile中的RUN操作,有网络相关操作时,比如yum)会失败。
2、线上的机器,执行 docker run,起来的容器,无法访问外网了。然而,k8s起来的pod里的容器,却可以访问外网。
镜像的构建,其实本质上,也是起容器来做事情,而且,你无法定制docker build操作时,使用的网络模式,只能是bridge模式。而 docker run 起来的容器,默认也是 bridge 模式。结合上面2个内容,基本上可以确定,此问题,和k8s无关,而是纯粹的docker的问题了。
排查:
①:一开始以为是k8s的iptables重建不完整,所以,多次用线上的机器的机器 nodeA(无人访问的一台),不断尝试清空iptables,等待k8s重建iptables,多次尝试无效。
②:stop docker -> stop kubelet -> clean iptables -> start docker -> start kubelet。这个操作的意义是,以为重启docker,重启k8s,docker 和 k8s 都会重建自己的 iptables。多次尝试无效。
③:使用现在的k8s安装工具:kubespray,重新安装 nodeA,并添加到k8s集群内。寄希望于安装工具重装docker和k8s,结果依然无效。
④:回归问题本身,问题一定出在iptables上。也是因为一开始重启了iptables导致此一系列问题。
解决过程:
既然问题出在iptables上,而我们要访问外网,那么,就得重iptables 报文转出入手。好在线上的机器,并没有都为了适配9999端口重启iptables,有3台 k8s 的 master 节点,并没有重启 iptables。
然后,对比有问题机器、没有问题机器的 FORWARD、POSTROUTING 的规则。发现,缺少DOCKER相关的规则,这些规则,本来是安装docker之后,由docker创建的。那么,解决此问题,就需要重建docker的iptables。
①:找一台干净的机器(只安装了docker,未启动容器,为安装k8s),然后从中提取docker相关的规则
②:将提取的规则,与出问题的机器的iptables默认规则融合。
③:执行 iptables-restore 恢复规则。
后续验证:
在问题机器上,docker run centos 容器,然后ping qq.com,链路畅通。有一个小问题,就是如果你先启动的容器执行 ping 外网操作。然后才再目的机器上执行 iptables-restore 的话,会发现,容器的 ping,有一个短暂的耗时,然后才会畅通,这是因为 iptables-restore 恢复规则后,有一个过程。
最后:
我整理一个相对标准的docker iptables规则备份,如果你也遇到了启动的docker容器,无法访问外网的问题,可以通过下面的内容,恢复规则
将下面的规则,保存到一个文件,比如 docker-iptables-backup
# Generated by iptables-save v1.4.21 on Thu Apr 30 20:48:42 2015
*nat
:PREROUTING ACCEPT [18:1080]
:INPUT ACCEPT [18:1080]
:OUTPUT ACCEPT [22:1550]
:POSTROUTING ACCEPT [22:1550]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.1/32 -d 172.17.0.1/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 3001 -j DNAT --to-destination 172.17.0.1:80
COMMIT
# Completed on Thu Apr 30 20:48:42 2015
# Generated by iptables-save v1.4.21 on Thu Apr 30 20:48:42 2015
*filter
:INPUT ACCEPT [495:53218]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [480:89217]
:DOCKER - [0:0]
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.1/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
COMMIT
# Completed on Thu Apr 30 20:48:42 2015
然后,执行恢复 iptables-restore docker-iptables-backup
注意:
①:如果你的docker在k8s集群内部,理论上来说,上面的操作会有清空iptables的能力,但是你不需要担心,因为k8s会自行修复本身的iptables,也就是说,k8s会重建自己的iptables。但是假如你的k8s没能完整重建,就需要手动恢复k8s的iptables。如果是calico网络,只需要删除当前节点的calico pod即可,删除之后,k8s会重启这个pod,自然也就会重建iptables了。同理,flannel网络也是一样的。
②:上面的规则,有一个关键,是 172.17.0.0 和 172.17.0.1,这是docker默认安装后的网段和docker0网卡的IP。如果你是默认安装的docker,上面的规则不需要改动,但如果你定制了docker0网卡的IP,记得修改一下上面的规则,适配你的环境。