天天看點

docker ip位址_記一次KUBERNETES/DOCKER網絡排障

昨天周五晚上,臨下班的時候,使用者給我們報了一個比較怪異的Kubernetes叢集下的網絡不能正常通路的問題,讓我們幫助檢視一下,我們從下午5點半左右一直跟進到晚上十點左右,在遠端不能通路使用者機器隻能遠端遙控使用者的情況找到了的問題。這個問題比較有意思,我個人覺得其中的調查用到的的指令以及排障的一些方法可以分享一下,是以寫下了這篇文章。

問題的症狀

使用者直接在微信裡說,他們發現在Kuberbnetes下的某個pod被重新開機了幾百次甚至上千次,于是開啟調查這個pod,發現上面的服務時而能夠通路,時而不能通路,也就是有一定機率不能通路,不知道是什麼原因。而且并不是所有的pod出問題,而隻是特定的一兩個pod出了網絡通路的問題。使用者說這個pod運作着Java程式,為了排除是Java的問題,使用者用

docker exec -it

指令直接到容器内啟了一個 Python的 SimpleHttpServer來測試發現也是一樣的問題。

我們大概知道使用者的叢集是這樣的版本,Kuberbnetes 是1.7,網絡用的是flannel的gw模式,Docker版本未知,作業系統CentOS 7.4,直接在實體機上跑docker,實體的配置很高,512GB記憶體,若幹CPU核,上面運作着幾百個Docker容器。

問題的排查

問題初查

首先,我們排除了flannel的問題,因為整個叢集的網絡通信都正常,隻有特定的某一兩個pod有問題。而用

telnet ip port

的指令手工測試網絡連接配接時有很大的機率出現

connection refused

錯誤,大約 1/4的機率,而3/4的情況下是可以正常連接配接的。

當時,我們讓使用者抓個包看看,然後,使用者抓到了有問題的TCP連接配接是收到了

SYN

後,立即傳回了

RST, ACK

docker ip位址_記一次KUBERNETES/DOCKER網絡排障

我問一下使用者這兩個IP所在的位置,知道了,

10.233.14.129

docker0

10.233.14.145

是容器内的IP。是以,這基本上可以排除了所有和kubernets或是flannel的問題,這就是本地的Docker上的網絡的問題。

對于這樣被直接 Reset 的情況,在

telnet

上會顯示

connection refused

的錯誤資訊,對于我個人的經驗,這種

SYN

完直接傳回

RST, ACK

的情況隻會有三種情況:

  1. TCP連結不能建立,不能建立連接配接的原因基本上是辨別一條TCP連結的那五元組不能完成,絕大多數情況都是服務端沒有相關的端口号。
  2. TCP連結建錯誤,有可能是因為修改了一些TCP參數,尤其是那些預設是關閉的參數,因為這些參數會導緻TCP協定不完整。
  3. 有防火牆iptables的設定,其中有

    REJECT

    規則。

因為當時還在開車,在等紅燈的時候,我感覺到有點像 NAT 的網絡中服務端開啟了

tcp_tw_recycle

tcp_tw_reuse

的症況(詳細參看《TCP的那些事(上)》),是以,讓使用者檢視了一上TCP參數,發現使用者一個TCP的參數都沒有改,全是預設的,于是我們排除了TCP參數的問題。

然後,我也不覺得容器内還會設定上iptables,而且如果有那就是100%的問題,不會時好時壞。是以,我懷疑容器内的端口号沒有偵聽上,但是馬上又好了,這可能會是應用的問題。于是我讓使用者那邊看一下,應用的日志,并用

kublet describe

看一下運作的情況,并把主控端的 iptables 看一下。

然而,我們發現并沒有任何的問題。這時,

我們失去了所有的調查線索,感覺不能繼續下去了……

重新梳理

這個時候,回到家,大家吃完飯,和使用者通了一個電話,把所有的細節再重新梳理了一遍,這個時候,使用者提供了一個比較關鍵的資訊—— “

抓包這個事,在

docker0

上可以抓到,然而到了容器内抓不到容器傳回

RST, ACK

” !然而,根據我的知識,我知道在

docker0

和容器内的

veth

網卡上,中間再也沒有什麼網絡裝置了(參看《Docker基礎技術:LINUX NAMESPACE(下)》)!

于是這個事把我們逼到了最後一種情況 —— IP位址沖突了!

Linux下看IP位址沖突還不是一件比較簡單事的,而在使用者的生産環境下沒有辦法安裝一些其它的指令,是以隻能用已有的指令,這個時候,我們發現使用者的機器上有

arping

于是我們用這個指令來檢測有沒有沖突的IP位址。使用了下面的指令:

$ arping -D -I docker0 -c 2 10.233.14.145
$ echo $?
           

根據文檔,

-D

參數是檢測IP位址沖突模式,如果這個指令的退狀态是

那麼就有沖突。結果傳回了

1

。而且,我們用

arping

IP的時候,沒有發現不同的mac位址。

這個時候,似乎問題的線索又斷了

因為客戶那邊還在處理一些别的事情,是以,我們在時斷時續的情況下工作,而還一些工作都需要使用者完成,是以,進展有點緩慢,但是也給我們一些時間思考問題。

柳暗花明

現在我們知道,IP沖突的可能性是非常大的,但是我們找不出來是和誰的IP沖突了。而且,我們知道隻要把這台機器重新開機一下,問題一定就解決掉了,但是我們覺得這并不是解決問題的方式,因為重新開機機器可以暫時的解決掉到這個問題,而如果我們不知道這個問題怎麼發生的,那麼未來這個問題還會再來。而重新開機線上機器這個成本太高了。

于是,我們的好奇心驅使我們繼續調查。我讓使用者

kubectl delete

其中兩個有問題的pod,因為本來就服務不斷重新開機,是以,删掉也沒有什麼問題。删掉這兩個pod後(一個是IP為

10.233.14.145

另一個是

10.233.14.137

),我們發現,kubernetes在其它機器上重新啟動了這兩個服務的新的執行個體。然而,

在問題機器上,這兩個IP位址居然還可以ping得通

好了,IP位址沖突的問題可以确認了。因為

10.233.14.xxx

這個網段是 docker 的,是以,這個IP位址一定是在這台機器上。是以,我們想看看所有的 network namespace 下的 veth 網卡上的IP。

在這個事上,我們費了點時間,因為對相關的指令也 很熟悉,是以花了點時間Google,以及看相關的man。

  • 首先,我們到

    /var/run/netns

    目錄下檢視系統的network namespace,發現什麼也沒有。
  • 然後,我們到

    /var/run/docker/netns

    目錄下檢視Docker的namespace,發現有好些。
  • 于是,我們用指定位置的方式檢視Docker的network namespace裡的IP位址

這裡要動用

nsenter

指令,這個指令可以進入到namespace裡執行一些指令。比如

$ nsenter --net=/var/run/docker/netns/421bdb2accf1 ifconfig -a
           

上述的指令,到

var/run/docker/netns/421bdb2accf1

這個network namespace裡執行了

ifconfig -a

指令。于是我們可以用下面 指令來周遊所有的network namespace。

$ ls /var/run/docker/netns | xargs -I {} nsenter --net=/var/run/docker/netns/{} ip addr 
           

然後,我們發現了比較詭異的事情。

  • 10.233.14.145

    我們查到了這個IP,說明,docker的namespace下還有這個IP。
  • 10.233.14.137

    ,這個IP沒有在docker的network namespace下查到。

有namespace leaking?于是我上網查了一下,發現了一個docker的bug – 在docker remove/stop 一個容器的時候,沒有清除相應的network namespace,這個問題被報告到了 Issue#31597 然後被fix在了 PR#31996,并Merge到了 Docker的 17.05版中。而使用者的版本是 17.09,應該包含了這個fix。不應該是這個問題,感覺又走不下去了。

不過,

10.233.14.137

這個IP可以ping得通,說明這個IP一定被綁在某個網卡,而且被隐藏到了某個network namespace下。

到這裡,要檢視所有network namespace,隻有最後一條路了,那就是到

/proc/

目錄下,把所有的pid下的

/proc/<pid>/ns

目錄給窮舉出來。好在這裡有一個比較友善的指令可以幹這個事 :

lsns

于是我寫下了如下的指令:

$ lsns -t net | awk ‘{print $4}' | xargs -t -I {} nsenter -t {}&nbsp;-n ip addr | grep -C 4 "10.233.14.137"
           

解釋一下。

  • lsns -t net

    列出所有開了network namespace的程序,其第4列是程序PID
  • 把所有開過network namespace的程序PID拿出來,轉給

    xargs

    指令
  • xargs

    指令把這些PID 依次傳給

    nsenter

    指令,
    • xargs -t

      的意思是會把相關的執行指令打出來,這樣我知道是那個PID。
    • xargs -I {}

      是聲明一個占位符來替換相關的PID

最後,我們發現,雖然在

/var/run/docker/netns

下沒有找到

10.233.14.137

,但是在

lsns

中找到了三個程序,他們都用了

10.233.14.137

這個IP(沖突了這麼多),

而且他們的MAC位址全是一樣的!

(怪不得arping找不到)。通過

ps

指令,可以查到這三個程序,有兩個是java的,還有一個是

/pause

(這個應該是kubernetes的沙盒)。

我們繼續乘勝追擊,窮追猛打,用

pstree

指令把整個程序樹打出來。發現上述的三個程序的父程序都在多個同樣叫

docker-contiane

的程序下!

這明顯還是docker的,但是在

docker ps

中卻找不道相應的容器,什麼鬼!快崩潰了……

繼續看程序樹,發現,這些

docker-contiane

的程序的父程序不在

dockerd

下面,而是在

systemd

這個超級父程序PID 1下,我靠!進而發現了一堆這樣的野程序(這種野程序或是僵屍程序對系統是有害的,至少也是會讓系統進入亞健康的狀态,因為他們還在占着資源)。

docker-contiane

應該是

dockerd

的子程序,被挂到了

pid 1

隻有一個原因,那就是父程序“飛”掉了,隻能找 pid 1 當養父。這說明,這台機器上出現了比較嚴重的

dockerd

程序退出的問題,而且是非正常的,因為

systemd

之是以要成為 pid 1,其就是要監管所有程序的子子孫孫,居然也沒有管理好,說明是個非正常的問題。(注,關于 systemd,請參看《Linux PID 1 和 Systemd 》,關于父子程序的事,請參看《Unix進階環境程式設計》一書)

接下來就要看看

systemd

dockerd

記錄的日志了…… (然而日志隻有3天的了,這3天

dockerd

沒有任何異常)

總結

通過這個調查,可以總結一下,

1) 對于問題調查,需要比較紮實的基礎知識,知道問題的成因和範圍。

2)如果走不下去了,要重新梳理一下,回頭仔細看一下一些蛛絲馬迹,認真推敲每一個細節。

3) 各種診斷工具要比較熟悉,這會讓你事半功倍。

4)系統維護和做清潔比較類似,需要經常看看系統中是否有一些僵屍程序或是一些垃圾東西,這些東西要及時清理掉。

最後,多說一下,很多人都說,

Docker适合放在實體機内運作,這并不完全對,因為他們隻考慮到了性能成本,沒有考慮到運維成本,在這樣512GB中啟動幾百個容器的玩法,其實并不好,因為這本質上是個大單體,因為你一理要重新開機某些關鍵程序或是機器,你的影響面是巨大的

問題原因

這兩天在自己的環境下測試了一下,發現,隻要是通過

systemctl start/stop docker

這樣的指令來啟停 Docker, 是可以把所有的程序和資源全部幹掉的。這個是沒有什麼問題的。我唯一能重制使用者問題的的操作就是直接

kill -9 <dockerd pid>

但是這個事使用者應該不會幹。而 Docker 如果有 crash 事件時,Systemd 是可以通過

journalctl -u docker

這樣的指令檢視相關的系統日志的。

于是,我找使用者了解一下他們在Docker在啟停時的問題,使用者說,

他們的執行

systemctl stop docker

這個指令的時候,發現這個指令不響應了,有可能就直接按了

Ctrl +C

這個應該就是導緻大量的

docker-containe

程序挂到

PID 1

下的原因了。前面說過,使用者的一台實體機上運作着上百個容器,是以,那個程序樹也是非常龐大的,我想,停服的時候,系統一定是要周遊所有的docker子程序來一個一個發退出信号的,這個過程可能會非常的長。導緻操作員以為指令假死,而直接按了

Ctrl + C

,最後導緻很多容器程序并沒有終止……

其它事宜

有同學問,為什麼我在這個文章裡寫的是

docker-containe

而不是

containd

程序?這是因為被

pstree

給截斷了,用

ps

指令可以看全,隻是程序名的名字有一個

docker-

的字首。

下面是這兩種不同安裝包的程序樹的差别(其中

sleep

是我用

buybox

鏡像啟動的)

systemd───dockerd─┬─docker-contained─┬─3*[docker-contained-shim─┬─sleep]
                  │                 │                    └─9*[{docker-containe}]]
                  │                 ├─docker-contained-shim─┬─sleep
                  │                 │                 └─10*[{docker-containe}]
                  │                 └─14*[{docker-contained-shim}]
                  └─17*[{dockerd}]

systemd───dockerd─┬─containerd─┬─3*[containerd-shim─┬─sleep]
                  │            │                 └─9*[{containerd-shim}]
                  │            ├─2*[containerd-shim─┬─sleep]
                  │            │                    └─9*[{containerd-shim}]]
                  │            └─11*[{containerd}]
                  └─10*[{dockerd}]

           

順便說一下,自從 Docker 1.11版以後,Docker程序組模型就改成上面這個樣子了.

  • dockerd

    是 Docker Engine守護程序,直接面向操作使用者。

    dockerd

    啟動時會啟動

    containerd

    子程序,他們之前通過RPC進行通信。
  • containerd

    dockerd

    runc

    之間的一個中間交流元件。他與

    dockerd

    的解耦是為了讓Docker變得更為的中立,而支援OCI 的标準 。
  • containerd-shim

    是用來真正運作的容器的,每啟動一個容器都會起一個新的shim程序, 它主要通過指定的三個參數:容器id,boundle目錄(containerd的對應某個容器生成的目錄,一般位于:

    /var/run/docker/libcontainerd/containerID

    ), 和運作指令(預設為

    runc

    )來建立一個容器。
  • docker-proxy

    你有可能還會在新版本的Docker中見到這個程序,這個程序是使用者級的代理路由。隻要你用

    ps -elf

    這樣的指令把其指令行打出來,你就可以看到其就是做端口映射的。如果你不想要這個代理的話,你可以在

    dockerd

    啟動指令行參數上加上:

    --userland-proxy=false

    這個參數。

來源:酷殼

作者:陳皓

原文:https://coolshell.cn/articles/18654.html