引子:
線上的k8s集群內(nèi)部,每一臺(tái)機(jī)器,都有2塊網(wǎng)卡,一塊兒em1(10網(wǎng)段),一個(gè) em2(192網(wǎng)段)。然后,線上的機(jī)器,通過iptables限制為:訪問192段的服務(wù)任意端口都是暢通的,而訪問10段的服務(wù)端口,只有333端口可以連通。333端口,是線上機(jī)器的ssh端口。
線上集群的k8s,使用的calico網(wǎng)絡(luò)。
線上有一個(gè)問題,就是容器訪問當(dāng)前宿主機(jī)192段IP的999端口不通,訪問其他機(jī)器192段IP的9999端口暢通。
我們知道,當(dāng)從容器訪問宿主機(jī)的應(yīng)用時(shí),會(huì)重新進(jìn)入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
意思是:
①:如果報(bào)文是途徑em2網(wǎng)卡,訪問任意其他網(wǎng)卡。則全部放行。(這就是為什么192段的訪問全部放行)
②:如果報(bào)文是tcp,不管報(bào)文從哪個(gè)網(wǎng)卡出來,去往哪個(gè)網(wǎng)卡,只要目的端口是333,就放行。(這就是為什么10段的訪問,只能訪問333)
我們?nèi)萜靼l(fā)出的報(bào)文,網(wǎng)卡是 cal 網(wǎng)卡(calico的網(wǎng)卡名稱),所以,容器只能訪問333。
錯(cuò)誤操作:
要把9999端口放開,意味著,我要添加開放9999端口到INPUT鏈路中,為了讓iptables再重啟后依然生效,我將規(guī)則,添加到
/etc/sysconfig/iptables
在添加完成后其實(shí)并未生效,然后,有2種方式,用于讓iptables生效:
①:手動(dòng)添加規(guī)則,比如 iptables -t filter -I INPUT 9 -s 172.20.0.0/14 -p tcp -m tcp --dport 9999 -j ACCEPT
②:重啟iptables(會(huì)自動(dòng)加載 /etc/sysconfig/iptables內(nèi)容 )
我選擇了后者,之所以選擇后者,其實(shí)是因?yàn)椋幢阒貑⒘薸ptables,加載了剛剛的規(guī)則,原來的k8s和calico的iptables會(huì)丟失,但是,k8s會(huì)重建自己的整套iptables規(guī)則的。
事實(shí)上,k8s確實(shí)也在我重啟iptables后,重建了iptables,但問題來了。
引發(fā)的2個(gè)網(wǎng)絡(luò)問題:
1、我們線上的CI系統(tǒng),是基于drone,做的二次開發(fā),改動(dòng)了很多的東西,添加了很多特性,結(jié)果上面一系列操作之后,我發(fā)現(xiàn),我們的CI系統(tǒng)中,個(gè)別鏡像的構(gòu)建(Dockerfile中的RUN操作,有網(wǎng)絡(luò)相關(guān)操作時(shí),比如yum)會(huì)失敗。
2、線上的機(jī)器,執(zhí)行 docker run,起來的容器,無法訪問外網(wǎng)了。然而,k8s起來的pod里的容器,卻可以訪問外網(wǎng)。
鏡像的構(gòu)建,其實(shí)本質(zhì)上,也是起容器來做事情,而且,你無法定制docker build操作時(shí),使用的網(wǎng)絡(luò)模式,只能是bridge模式。而 docker run 起來的容器,默認(rèn)也是 bridge 模式。結(jié)合上面2個(gè)內(nèi)容,基本上可以確定,此問題,和k8s無關(guān),而是純粹的docker的問題了。
排查:
①:一開始以為是k8s的iptables重建不完整,所以,多次用線上的機(jī)器的機(jī)器 nodeA(無人訪問的一臺(tái)),不斷嘗試清空iptables,等待k8s重建iptables,多次嘗試無效。
②:stop docker -> stop kubelet -> clean iptables -> start docker -> start kubelet。這個(gè)操作的意義是,以為重啟docker,重啟k8s,docker 和 k8s 都會(huì)重建自己的 iptables。多次嘗試無效。
③:使用現(xiàn)在的k8s安裝工具:kubespray,重新安裝 nodeA,并添加到k8s集群內(nèi)。寄希望于安裝工具重裝docker和k8s,結(jié)果依然無效。
④:回歸問題本身,問題一定出在iptables上。也是因?yàn)橐婚_始重啟了iptables導(dǎo)致此一系列問題。
解決過程:
既然問題出在iptables上,而我們要訪問外網(wǎng),那么,就得重iptables 報(bào)文轉(zhuǎn)出入手。好在線上的機(jī)器,并沒有都為了適配9999端口重啟iptables,有3臺(tái) k8s 的 master 節(jié)點(diǎn),并沒有重啟 iptables。
然后,對(duì)比有問題機(jī)器、沒有問題機(jī)器的 FORWARD、POSTROUTING 的規(guī)則。發(fā)現(xiàn),缺少DOCKER相關(guān)的規(guī)則,這些規(guī)則,本來是安裝docker之后,由docker創(chuàng)建的。那么,解決此問題,就需要重建docker的iptables。
①:找一臺(tái)干凈的機(jī)器(只安裝了docker,未啟動(dòng)容器,為安裝k8s),然后從中提取docker相關(guān)的規(guī)則
②:將提取的規(guī)則,與出問題的機(jī)器的iptables默認(rèn)規(guī)則融合。
③:執(zhí)行 iptables-restore 恢復(fù)規(guī)則。
后續(xù)驗(yàn)證:
在問題機(jī)器上,docker run centos 容器,然后ping qq.com,鏈路暢通。有一個(gè)小問題,就是如果你先啟動(dòng)的容器執(zhí)行 ping 外網(wǎng)操作。然后才再目的機(jī)器上執(zhí)行 iptables-restore 的話,會(huì)發(fā)現(xiàn),容器的 ping,有一個(gè)短暫的耗時(shí),然后才會(huì)暢通,這是因?yàn)?iptables-restore 恢復(fù)規(guī)則后,有一個(gè)過程。
最后:
我整理一個(gè)相對(duì)標(biāo)準(zhǔn)的docker iptables規(guī)則備份,如果你也遇到了啟動(dòng)的docker容器,無法訪問外網(wǎng)的問題,可以通過下面的內(nèi)容,恢復(fù)規(guī)則
將下面的規(guī)則,保存到一個(gè)文件,比如 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
然后,執(zhí)行恢復(fù) iptables-restore docker-iptables-backup
注意:
①:如果你的docker在k8s集群內(nèi)部,理論上來說,上面的操作會(huì)有清空iptables的能力,但是你不需要擔(dān)心,因?yàn)閗8s會(huì)自行修復(fù)本身的iptables,也就是說,k8s會(huì)重建自己的iptables。但是假如你的k8s沒能完整重建,就需要手動(dòng)恢復(fù)k8s的iptables。如果是calico網(wǎng)絡(luò),只需要?jiǎng)h除當(dāng)前節(jié)點(diǎn)的calico pod即可,刪除之后,k8s會(huì)重啟這個(gè)pod,自然也就會(huì)重建iptables了。同理,flannel網(wǎng)絡(luò)也是一樣的。
②:上面的規(guī)則,有一個(gè)關(guān)鍵,是 172.17.0.0 和 172.17.0.1,這是docker默認(rèn)安裝后的網(wǎng)段和docker0網(wǎng)卡的IP。如果你是默認(rèn)安裝的docker,上面的規(guī)則不需要改動(dòng),但如果你定制了docker0網(wǎng)卡的IP,記得修改一下上面的規(guī)則,適配你的環(huán)境。