筆者在前邊系列文章中反復(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ò)等抽象能力,底層還是需要依賴(lài)于Linux或者Windows上的操作系統(tǒng)功能來(lái)實(shí)現(xiàn)疲酌,特別是網(wǎng)絡(luò)部分蜡峰。筆者在實(shí)際的項(xiàng)目中經(jīng)常遇到同學(xué)在講給客戶(hù)設(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è)基本原則就是給客戶(hù)除了展現(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端口上收到的客戶(hù)端請(qǐng)求脆霎,重定向到這個(gè)POD上的8080端口服務(wù)总处。
接下來(lái),假設(shè)這個(gè)Golang的Web服務(wù)運(yùn)行在Linux服務(wù)器上睛蛛,外部用戶(hù)可以直接通過(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ī)專(zhuān)業(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)自于客戶(hù)端的請(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)容测蹲,如下圖所示:
從上圖我們可以看到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ù)器處理客戶(hù)端請(qǐng)求的處理過(guò)程按順序進(jìn)行梳理邀杏,描述如下:
- Epoll返回,喚醒咱們的極簡(jiǎn)Web服務(wù)應(yīng)用程序
- 服務(wù)從請(qǐng)求信息中獲取到客戶(hù)端的IP地址是::ffff:10.0.0.63
- 服務(wù)器端會(huì)檢查socket的狀態(tài)以及設(shè)置相關(guān)的參數(shù)
上邊就是一個(gè)極簡(jiǎn)的Golang服務(wù)從客戶(hù)端當(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)期待拷恨!