Kubernetes網(wǎng)絡(luò)機(jī)制深入淺出-Linux網(wǎng)絡(luò)基礎(chǔ)

筆者在前邊系列文章中反復(fù)強(qiáng)調(diào)過(guò)Kubernetes是云計(jì)算時(shí)代應(yīng)用程序部署的操作系統(tǒng),這個(gè)特殊的操作系統(tǒng)運(yùn)行在一組節(jié)點(diǎn)之上,這些計(jì)算節(jié)點(diǎn)上通常安裝了Linux或者Windows操作系統(tǒng),因此如果我們要理解Kubernetes的網(wǎng)絡(luò)實(shí)現(xiàn)細(xì)節(jié),理解Linux操作系統(tǒng)的網(wǎng)絡(luò)實(shí)現(xiàn)原理是基礎(chǔ),然后在這個(gè)基礎(chǔ)之上,我們?cè)賮?lái)討論Kubernetes對(duì)操作系統(tǒng)的網(wǎng)絡(luò)實(shí)現(xiàn)做了哪些抽象。

注:由于在Windows上運(yùn)行容器應(yīng)用仍然屬于小眾場(chǎng)景,并且Windows容器技術(shù)處于剛剛成熟階段,因此筆者在后續(xù)的文章中聚焦于Linux操作系統(tǒng)上的容器技術(shù)方案。

Kubernetes本質(zhì)上只是一套軟件系統(tǒng),這套設(shè)計(jì)精良的應(yīng)用程序主要功能就是管理一組Linux或者Windows機(jī)器,因此無(wú)論Kubernetes對(duì)外提供了多么豐富的計(jì)算和網(wǎng)絡(luò)等抽象能力,底層還是需要依賴于Linux或者Windows上的操作系統(tǒng)功能來(lái)實(shí)現(xiàn),特別是網(wǎng)絡(luò)部分。筆者在實(shí)際的項(xiàng)目中經(jīng)常遇到同學(xué)在講給客戶設(shè)計(jì)了網(wǎng)絡(luò)方案,或者網(wǎng)絡(luò)拓?fù)浞桨?,但是看了?shí)際方案后,很多時(shí)候是一個(gè)部署方案,里邊呈現(xiàn)的是SLB,網(wǎng)關(guān),WAF,防火墻以及EIP,NAT這樣的云原生技術(shù)組件。從方案設(shè)計(jì)角度把這些組件按照邏輯關(guān)系組織在一起有沒(méi)有啥問(wèn)題,但是筆者認(rèn)為叫網(wǎng)絡(luò)方案不合適。

在Kubernetes平臺(tái)上,網(wǎng)絡(luò)是對(duì)底層工作節(jié)點(diǎn)上的網(wǎng)絡(luò)能力的抽象,集群層級(jí)的網(wǎng)絡(luò)應(yīng)該是跨越了多個(gè)機(jī)器節(jié)點(diǎn)而形成的overlay網(wǎng)絡(luò)。在這個(gè)網(wǎng)絡(luò)上,應(yīng)用程序?qū)嵗≒OD,當(dāng)然一個(gè)POD中可以部署多個(gè)應(yīng)用程序容器實(shí)例)之間可以不經(jīng)過(guò)NAT直接進(jìn)行通信。因此如何解決這層虛擬的overlay網(wǎng)絡(luò)在底層的underlay網(wǎng)絡(luò)上的互訪,IP地址分配等是Kubernetes網(wǎng)絡(luò)方案要解決的核心問(wèn)題。

計(jì)算機(jī)網(wǎng)絡(luò)本身就很復(fù)雜,由于涉及到大量的協(xié)議,規(guī)范,分層等,很容易就陷入到各種協(xié)議理論層面的說(shuō)明,這樣的信息已經(jīng)汗牛充棟,也沒(méi)有必要再?gòu)?fù)制粘貼。為了讓我們的討論稍微有點(diǎn)生機(jī)(筆者在很多項(xiàng)目給C級(jí)別做匯報(bào)的時(shí)候,遵循的一個(gè)基本原則就是給客戶除了展現(xiàn)靜態(tài)層面的系統(tǒng)架構(gòu)之外,會(huì)通過(guò)一個(gè)真實(shí)的請(qǐng)求如何在系統(tǒng)中被處理來(lái)展示動(dòng)態(tài)的一面),我們后續(xù)的討論會(huì)使用如下用Golang編寫(xiě)的極簡(jiǎn)Web服務(wù),以期通過(guò)這個(gè)服務(wù)如何處理curl發(fā)出的請(qǐng)求,來(lái)庖丁解牛式的分析請(qǐng)求從應(yīng)用層,到傳輸層,到網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層的處理細(xì)節(jié)。

package main

import (

? ? ? ? "fmt"

? ? ? ? "net/http"

)

func hello(w http.ResponseWriter, _ *http.Request) {

? ? fmt.Fprintf(w, "qiwangyue")

}

func main() {

? ? http.HandleFunc("/", hello)

? ? http.ListenAndServe("0.0.0.0:8080", nil)

}

注:在Linux操作系統(tǒng)上,端口號(hào)1-1023屬于特權(quán)端口,需要root權(quán)限才能bind。咱們編寫(xiě)的應(yīng)用程序應(yīng)該避免使用低于1024的端口號(hào),而應(yīng)該選擇1024-65535之間的值。咱們這個(gè)極簡(jiǎn)Web服務(wù)使用的就是大家熟知的8080,如果這個(gè)服務(wù)被部署在Kubernetes集群中,可以選擇Service或者外部負(fù)載均衡重定向的能力,來(lái)把從80端口上收到的客戶端請(qǐng)求,重定向到這個(gè)POD上的8080端口服務(wù)。

接下來(lái),假設(shè)這個(gè)Golang的Web服務(wù)運(yùn)行在Linux服務(wù)器上,外部用戶可以直接通過(guò)路徑/來(lái)訪問(wèn)這個(gè)服務(wù),那么當(dāng)服務(wù)啟動(dòng)的時(shí)候,在操作系統(tǒng)上具體發(fā)生了什么?或者說(shuō)服務(wù)啟動(dòng)的時(shí)候,從網(wǎng)絡(luò)設(shè)備和組件的角度,具體發(fā)生了哪些動(dòng)作?計(jì)算機(jī)專業(yè)的同學(xué)應(yīng)該大概知道當(dāng)我們啟動(dòng)這個(gè)編譯好的二進(jìn)制文件時(shí),應(yīng)用程序會(huì)監(jiān)聽(tīng)某個(gè)網(wǎng)絡(luò)地址(Linux服務(wù)器的IP地址)和端口號(hào)(8080)。具體來(lái)說(shuō)應(yīng)用程序會(huì)基于IP地址和端口號(hào)創(chuàng)建socket結(jié)構(gòu),并且和機(jī)器上的IP地址和端口號(hào)綁定(bind),這樣就完成了服務(wù)的啟動(dòng)工作。

很多同學(xué)可能不理解為啥分為創(chuàng)建socket和bind這兩個(gè)步驟,咱們后續(xù)的內(nèi)容會(huì)詳細(xì)說(shuō)明,這里你可以簡(jiǎn)單的理解為創(chuàng)建這個(gè)叫socket的邏輯對(duì)象,以及將這個(gè)邏輯對(duì)象和物理設(shè)備進(jìn)行關(guān)聯(lián)這么兩步操作。當(dāng)應(yīng)用程序運(yùn)行起來(lái)之后,我們的應(yīng)用就可以收到來(lái)自于客戶端的請(qǐng)求,具體來(lái)說(shuō)就是目標(biāo)地址是機(jī)器的IP地址和端口號(hào)為8080的請(qǐng)求。

注:咱們的極簡(jiǎn)Web服務(wù)監(jiān)聽(tīng)的IP地址是0.0.0.0這樣的IPv4地址,在IPv6應(yīng)該寫(xiě)成【::】通配符地址,這是個(gè)特殊的地址,用來(lái)標(biāo)識(shí)這個(gè)服務(wù)監(jiān)聽(tīng)這臺(tái)機(jī)器上的所有可用的IP地址。這是一種非常有效的監(jiān)聽(tīng)和bind機(jī)制,因?yàn)楹芏鄷r(shí)候我們可能在編寫(xiě)應(yīng)用程序的時(shí)候,不知道應(yīng)用程序?qū)⒁\(yùn)行在哪些IP地址上,大部分的網(wǎng)絡(luò)服務(wù)都是以這種方式啟動(dòng)并綁定到宿主機(jī)(容器實(shí)例)的網(wǎng)絡(luò)接口上。

基于上邊的信息,我們知道socket對(duì)象是運(yùn)行中的應(yīng)用程序的入口,那么如何能觀察到這個(gè)在服務(wù)啟動(dòng)時(shí)創(chuàng)建的socket對(duì)象呢?還記得我們?cè)谇斑叾嗥恼轮蟹磸?fù)提到的Linux至理名言:一切皆文件。實(shí)際上咱們可以通過(guò)ls -lah /proc/<server proc/fd來(lái)羅列相關(guān)進(jìn)程(服務(wù))的網(wǎng)絡(luò)套接字socket。在筆者的機(jī)器上輸出如下:

# ps -aux

USER? ? ? PID %CPU %MEM? ? VSZ? RSS TTY? ? ? STAT START? TIME COMMAND

root? ? ? ? 22? 0.3? 0.9 928116 19960 pts/1? ? Sl+? 10:11? 0:00 go run web-server.go

root? ? ? ? 90? 0.0? 0.2 477816? 5760 pts/1? ? Sl+? 10:11? 0:00 /tmp/go-build957677948/b001/exe/web-server

# ls -lah /proc/90/fd

total 0

dr-x------ 2 root root? 0 Dec 17 10:13 .

dr-xr-xr-x 9 root root? 0 Dec 17 10:11 ..

lrwx------ 1 root root 64 Dec 17 10:13 0 -> /dev/pts/1

lrwx------ 1 root root 64 Dec 17 10:13 1 -> /dev/pts/1

lrwx------ 1 root root 64 Dec 17 10:13 2 -> /dev/pts/1

lrwx------ 1 root root 64 Dec 17 10:13 3 -> 'socket:[26705]'

lrwx------ 1 root root 64 Dec 17 10:13 5 -> 'anon_inode:[eventpoll]'

當(dāng)內(nèi)核從socket上接收到數(shù)據(jù)包packt之后,會(huì)將packet和特定的connection進(jìn)行管理,并且操作系統(tǒng)通過(guò)狀態(tài)機(jī)來(lái)管理connection的狀態(tài),大白話說(shuō)就是連接狀態(tài)。同樣咱們有非常豐富的工具供選擇來(lái)觀察連接以及狀態(tài),后續(xù)文章會(huì)詳細(xì)介紹。在Linux操作系統(tǒng)上,連接也是通過(guò)文件來(lái)表示,當(dāng)應(yīng)用accept一個(gè)連接請(qǐng)求后,實(shí)質(zhì)上在操作系統(tǒng)的對(duì)應(yīng)目錄創(chuàng)建一個(gè)文件,后續(xù)的數(shù)據(jù)寫(xiě)入和讀出都是通過(guò)這個(gè)文件來(lái)進(jìn)行。

有了這些基礎(chǔ)之后,咱們回到極簡(jiǎn)Golang服務(wù)上,我們可以通過(guò)strace來(lái)觀察服務(wù)的運(yùn)行情況,通過(guò)命令strace ./main來(lái)監(jiān)控咱們的應(yīng)用程序,由于strace會(huì)捕捉到所有在這臺(tái)機(jī)器上進(jìn)行的系統(tǒng)調(diào)用,因此輸出的內(nèi)容會(huì)非常豐富。為了能夠捕捉到關(guān)鍵信息,咱們對(duì)輸出結(jié)果進(jìn)行了簡(jiǎn)化,只保留了和極簡(jiǎn)Golang服務(wù)相關(guān)的內(nèi)容,如下圖所示:

《圖1.1 strace捕捉的和極簡(jiǎn)Web服務(wù)相關(guān)的系統(tǒng)調(diào)用》

從上圖我們可以看到web服務(wù)在啟動(dòng)的時(shí)候,發(fā)生了如下四個(gè)關(guān)鍵的系統(tǒng)調(diào)用:

- 打開(kāi)一個(gè)文件描述符

- 為IPv6協(xié)議的連接創(chuàng)建socket

- 在socket上禁用IPV6_V6oNLY,應(yīng)用可以同時(shí)提供IPV4和V6服務(wù)

- 將socket綁定(bind)到機(jī)器上的所有IP地址的8080端口號(hào)

- 等待連接請(qǐng)求

特別是最后一步,當(dāng)服務(wù)啟動(dòng)后,我們從輸出的信息中可以看到,strace卡在epoll_wait上等待訪問(wèn)請(qǐng)求。

到這里為止,服務(wù)已經(jīng)啟動(dòng),并且監(jiān)聽(tīng)在8080端口上等待請(qǐng)求的到來(lái),請(qǐng)求一般是內(nèi)核通知socket有符合你處理的packt,這個(gè)時(shí)候服務(wù)受到通知,從內(nèi)核的緩沖區(qū)讀取到數(shù)據(jù),繼續(xù)處理。這個(gè)時(shí)候需要curl給這個(gè)極簡(jiǎn)服務(wù)發(fā)送一個(gè)實(shí)際請(qǐng)求了。在相同機(jī)器的另外一個(gè)窗口上執(zhí)行curl localhost:8080/命令,如下所示:

# curl http://localhost:8080/

qiwangyue

注:筆者強(qiáng)烈建議大家在開(kāi)發(fā)服務(wù)的使用postman或者postwomen這樣的測(cè)試工具,最好不要使用瀏覽器,除非你編寫(xiě)的是前端頁(yè)面。原因是瀏覽器會(huì)發(fā)送很多額外的請(qǐng)求給服務(wù)器,比如獲取favicon文件等,這會(huì)讓我們調(diào)試變得異常的困哪,增加額外的工作量來(lái)分析結(jié)果。特別是瀏覽器有緩存功能,有時(shí)候我們的請(qǐng)求直接從緩存返回,根本就沒(méi)有到服務(wù)器端,有時(shí)候把開(kāi)發(fā)人員折磨的痛不欲生啊。因此選擇curl或者telnet這樣的輕量級(jí)工具,簡(jiǎn)潔明了,還節(jié)省時(shí)間,是開(kāi)發(fā)調(diào)試之良具。

當(dāng)服務(wù)端接受并處理請(qǐng)求后,strace的輸出如下:

[{EPOLLIN, {u32=1714573248, u64=1714573248}}], 128, -1) = 1

accept4(3, {sa_family=AF_INET6, sin6_port=htons(54202), inet_pton(AF_INET6,

"::ffff:10.0.0.63", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0},

[112->28], SOCK_CLOEXEC|SOCK_NONBLOCK) = 5

epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET,

{u32=1714573120, u64=1714573120}}) = 0

getsockname(5, {sa_family=AF_INET6, sin6_port=htons(8080),

inet_pton(AF_INET6, "::ffff:10.0.0.30", &sin6_addr), sin6_flowinfo=htonl(0),

sin6_scope_id=0}, [112->28]) = 0

setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0

setsockopt(5, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0

setsockopt(5, SOL_TCP, TCP_KEEPINTVL, [180], 4) = 0

setsockopt(5, SOL_TCP, TCP_KEEPIDLE, [180], 4) = 0

accept4(3, 0x2032d70, [112], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN

(Resource temporarily unavailable)

從strace輸出的數(shù)據(jù)中可以看到,服務(wù)器會(huì)將響應(yīng)數(shù)據(jù)“qiwangyue”寫(xiě)到response數(shù)據(jù)中,通過(guò)http協(xié)議來(lái)進(jìn)行封裝,并最后寫(xiě)到文件描述符fd中。從Linux操作系統(tǒng)內(nèi)核將這些數(shù)據(jù)轉(zhuǎn)換成packet,然后協(xié)議棧發(fā)現(xiàn)這個(gè)數(shù)據(jù)的目的地址是本機(jī),內(nèi)核通知curl有數(shù)據(jù)可以讀取,因此curl被喚醒,從內(nèi)核讀取數(shù)據(jù),將結(jié)果“qiwangyue”在控制臺(tái)打印出來(lái)。

對(duì)上邊數(shù)據(jù)的服務(wù)器處理客戶端請(qǐng)求的處理過(guò)程按順序進(jìn)行梳理,描述如下:

- Epoll返回,喚醒咱們的極簡(jiǎn)Web服務(wù)應(yīng)用程序

- 服務(wù)從請(qǐng)求信息中獲取到客戶端的IP地址是::ffff:10.0.0.63

- 服務(wù)器端會(huì)檢查socket的狀態(tài)以及設(shè)置相關(guān)的參數(shù)

上邊就是一個(gè)極簡(jiǎn)的Golang服務(wù)從客戶端當(dāng)服務(wù)器端的處理過(guò)程,特別是在操作系統(tǒng)內(nèi)核層級(jí)發(fā)生的各種類(lèi)型的系統(tǒng)調(diào)用。咱們下篇文章繼續(xù)介紹網(wǎng)絡(luò)接口以及Linux操作系統(tǒng)如何處理數(shù)據(jù)包packet,敬請(qǐng)期待!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容