網(wǎng)站首頁 編程語言 正文
正文
Deployment認(rèn)為,一個應(yīng)用的所有 Pod,是完全一樣的。所以,它們互相之間沒有順序,也無所謂運行在哪臺宿主機上。需要的時候,Deployment 就可以通過 Pod 模板創(chuàng)建新的 Pod;不需要的時候,Deployment 就可以“殺掉”任意一個 Pod。
但是,在實際的場景中,并不是所有的應(yīng)用都可以滿足這樣的要求。
尤其是分布式應(yīng)用,它的多個實例之間,往往有依賴關(guān)系,比如:主從關(guān)系、主備關(guān)系。
還有就是數(shù)據(jù)存儲類應(yīng)用,它的多個實例,往往都會在本地磁盤上保存一份數(shù)據(jù)。而這些實例一旦被殺掉,即便重建出來,實例與數(shù)據(jù)之間的對應(yīng)關(guān)系也已經(jīng)丟失,從而導(dǎo)致應(yīng)用失敗。
所以,這種實例之間有不對等關(guān)系,以及實例對外部數(shù)據(jù)有依賴關(guān)系的應(yīng)用,就被稱為“有狀態(tài)應(yīng)用”(Stateful Application)。
容器技術(shù)誕生后,大家很快發(fā)現(xiàn),它用來封裝“無狀態(tài)應(yīng)用”(Stateless Application),尤其是 Web 服務(wù),非常好用。但是,一旦你想要用容器運行“有狀態(tài)應(yīng)用”,其困難程度就會直線上升。而且,這個問題解決起來,單純依靠容器技術(shù)本身已經(jīng)無能為力,這也就導(dǎo)致了很長一段時間內(nèi),“有狀態(tài)應(yīng)用”幾乎成了容器技術(shù)圈子的“忌諱”,大家一聽到這個詞,就紛紛搖頭。
得益于“控制器模式”的設(shè)計思想,Kubernetes 項目很早就在 Deployment 的基礎(chǔ)上,擴展出了對“有狀態(tài)應(yīng)用”的初步支持。這個編排功能,就是:StatefulSet。
StatefulSet 的設(shè)計理解
StatefulSet 的設(shè)計其實非常容易理解。它把真實世界里的應(yīng)用狀態(tài),抽象為了兩種情況:
- 拓?fù)錉顟B(tài)。這種情況意味著,應(yīng)用的多個實例之間不是完全對等的關(guān)系。這些應(yīng)用實例,必須按照某些順序啟動,比如應(yīng)用的主節(jié)點 A 要先于從節(jié)點 B 啟動。而如果你把 A 和 B 兩個 Pod 刪除掉,它們再次被創(chuàng)建出來時也必須嚴(yán)格按照這個順序才行。并且,新創(chuàng)建出來的 Pod,必須和原來 Pod 的網(wǎng)絡(luò)標(biāo)識一樣,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod。
- 存儲狀態(tài)。這種情況意味著,應(yīng)用的多個實例分別綁定了不同的存儲數(shù)據(jù)。對于這些應(yīng)用實例來說,Pod A 第一次讀取到的數(shù)據(jù),和隔了十分鐘之后再次讀取到的數(shù)據(jù),應(yīng)該是同一份,哪怕在此期間 Pod A 被重新創(chuàng)建過。這種情況最典型的例子,就是一個數(shù)據(jù)庫應(yīng)用的多個存儲實例。
所以,StatefulSet 的核心功能,就是通過某種方式記錄這些狀態(tài),然后在 Pod 被重新創(chuàng)建時,能夠為新 Pod 恢復(fù)這些狀態(tài)。
在開始講述 StatefulSet 的工作原理之前,我就必須先為你講解一個 Kubernetes 項目中非常實用的概念:Headless Service。
我在和你一起討論 Kubernetes 架構(gòu)的時候就曾介紹過,Service 是 Kubernetes 項目中用來將一組 Pod 暴露給外界訪問的一種機制。比如,一個 Deployment 有 3 個 Pod,那么我就可以定義一個 Service。然后,用戶只要能訪問到這個 Service,它就能訪問到某個具體的 Pod。
Service 如何被訪問
那么,這個 Service 又是如何被訪問的呢?
第一種方式,是以 Service 的 VIP(Virtual IP,即:虛擬 IP)方式。比如:當(dāng)我訪問 10.0.23.1 這個 Service 的 IP 地址時,10.0.23.1 其實就是一個 VIP,它會把請求轉(zhuǎn)發(fā)到該 Service 所代理的某一個 Pod 上。這里的具體原理,我會在后續(xù)的 Service 章節(jié)中進(jìn)行詳細(xì)介紹。
第二種方式,就是以 Service 的 DNS 方式。比如:這時候,只要我訪問“my-svc.my-namespace.svc.cluster.local”這條 DNS 記錄,就可以訪問到名叫 my-svc 的 Service 所代理的某一個 Pod。
而在第二種 Service DNS 的方式下,具體還可以分為兩種處理方法:
第一種處理方法,是 Normal Service。這種情況下,你訪問“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 這個 Service 的 VIP,后面的流程就跟 VIP 方式一致了。
而第二種處理方法,正是 Headless Service。這種情況下,你訪問“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一個 Pod 的 IP 地址。可以看到,這里的區(qū)別在于,Headless Service 不需要分配一個 VIP,而是可以直接以 DNS 記錄的方式解析出被代理 Pod 的 IP 地址。
那么,這樣的設(shè)計又有什么作用呢? 想要回答這個問題,我們需要從 Headless Service 的定義方式看起。
Headless Service 對應(yīng)的 YAML文件
下面是一個標(biāo)準(zhǔn)的 Headless Service 對應(yīng)的 YAML 文件:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
可以看到,所謂的 Headless Service,其實仍是一個標(biāo)準(zhǔn) Service 的 YAML 文件。只不過,它的 clusterIP 字段的值是:None,即:這個 Service,沒有一個 VIP 作為“頭”。這也就是 Headless 的含義。所以,這個 Service 被創(chuàng)建后并不會被分配一個 VIP,而是會以 DNS 記錄的方式暴露出它所代理的 Pod。
而它所代理的 Pod,依然是采用 Label Selector 機制選擇出來的,即:所有攜帶了 app=nginx 標(biāo)簽的 Pod,都會被這個 Service 代理起來。
當(dāng)你按照這樣的方式創(chuàng)建了一個 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都會被綁定一個這樣格式的 DNS 記錄,如下所示:<pod-name>.<svc-name>.<namespace>.svc.cluster.local
這個 DNS 記錄,正是 Kubernetes 項目為 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。
有了這個“可解析身份”,只要你知道了一個 Pod 的名字,以及它對應(yīng)的 Service 的名字,你就可以非常確定地通過這條 DNS 記錄訪問到 Pod 的 IP 地址。
那么,StatefulSet 又是如何使用這個 DNS 記錄來維持 Pod 的拓?fù)錉顟B(tài)的呢?
StatefulSet 的 YAML 文件
為了回答這個問題,現(xiàn)在我們就來編寫一個 StatefulSet 的 YAML 文件,如下所示:
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.9.1 ports: - containerPort: 80 name: web
這個 YAML 文件,和我們在前面文章中用到的 nginx-deployment 的唯一區(qū)別,就是多了一個 serviceName=nginx 字段。
這個字段的作用,就是告訴 StatefulSet 控制器,在執(zhí)行控制循環(huán)(Control Loop)的時候,請使用 nginx 這個 Headless Service 來保證 Pod 的“可解析身份”。
所以,當(dāng)你通過 kubectl create 創(chuàng)建了上面這個 Service 和 StatefulSet 之后,就會看到如下兩個對象:
$ kubectl create -f svc.yaml $ kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 10s $ kubectl create -f statefulset.yaml $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 19s
查看StatefulSet 的 Events 這些信息
$ kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 19s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 20s
通過上面這個 Pod 的創(chuàng)建過程,我們不難看到,StatefulSet 給它所管理的所有 Pod 的名字,進(jìn)行了編號,編號規(guī)則是:-。
而且這些編號都是從 0 開始累加,與 StatefulSet 的每個 Pod 實例一一對應(yīng),絕不重復(fù)。
更重要的是,這些 Pod 的創(chuàng)建,也是嚴(yán)格按照編號順序進(jìn)行的。比如,在 web-0 進(jìn)入到 Running 狀態(tài)、并且細(xì)分狀態(tài)(Conditions)成為 Ready 之前,web-1 會一直處于 Pending 狀態(tài)。
當(dāng)這兩個 Pod 都進(jìn)入了 Running 狀態(tài)之后,你就可以查看到它們各自唯一的“網(wǎng)絡(luò)身份”了。
我們使用 kubectl exec 命令進(jìn)入到容器中查看它們的 hostname:
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
可以看到,這兩個 Pod 的 hostname 與 Pod 名字是一致的,都被分配了對應(yīng)的編號。接下來,我們再試著以 DNS 的方式,訪問一下這個 Headless Service:
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh?
解析一下 Pod 對應(yīng)的 Headless Service
通過這條命令,我們啟動了一個一次性的 Pod,因為–rm 意味著 Pod 退出后就會被刪除掉。然后,在這個 Pod 的容器里面,我們嘗試用 nslookup 命令,解析一下 Pod 對應(yīng)的 Headless Service:
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh $ nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.7 $ nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.7
從 nslookup 命令的輸出結(jié)果中,我們可以看到,在訪問 web-0.nginx 的時候,最后解析到的,正是 web-0 這個 Pod 的 IP 地址;而當(dāng)訪問 web-1.nginx 的時候,解析到的則是 web-1 的 IP 地址。
這時候,如果你在另外一個 Terminal 里把這兩個“有狀態(tài)應(yīng)用”的 Pod 刪掉,然后,再在當(dāng)前 Terminal 里 Watch 一下這兩個 Pod 的狀態(tài)變化,就會發(fā)現(xiàn)一個有趣的現(xiàn)象
$ kubectl get pod -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 32s
可以看到,當(dāng)我們把這兩個 Pod 刪除之后,Kubernetes 會按照原先編號的順序,創(chuàng)建出了兩個新的 Pod。并且,Kubernetes 依然為它們分配了與原來相同的“網(wǎng)絡(luò)身份”:web-0.nginx 和 web-1.nginx。
通過這種嚴(yán)格的對應(yīng)規(guī)則,StatefulSet 就保證了 Pod 網(wǎng)絡(luò)標(biāo)識的穩(wěn)定性。
比如,如果 web-0 是一個需要先啟動的主節(jié)點,web-1 是一個后啟動的從節(jié)點,那么只要這個 StatefulSet 不被刪除,你訪問 web-0.nginx 時始終都會落在主節(jié)點上,訪問 web-1.nginx 時,則始終都會落在從節(jié)點上,這個關(guān)系絕對不會發(fā)生任何變化。
所以,如果我們再用 nslookup 命令,查看一下這個新 Pod 對應(yīng)的 Headless Service 的話:
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh $ nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.8 $ nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.8
我們可以看到,在這個 StatefulSet 中,這兩個新 Pod 的“網(wǎng)絡(luò)標(biāo)識”(比如:web-0.nginx 和 web-1.nginx),再次解析到了正確的 IP 地址(比如:web-0 Pod 的 IP 地址 10.244.1.8)。
通過這種方法,Kubernetes 就成功地將 Pod 的拓?fù)錉顟B(tài)(比如:哪個節(jié)點先啟動,哪個節(jié)點后啟動),按照 Pod 的“名字 + 編號”的方式固定了下來。此外,Kubernetes 還為每一個 Pod 提供了一個固定并且唯一的訪問入口,即:這個 Pod 對應(yīng)的 DNS 記錄。
這些狀態(tài),在 StatefulSet 的整個生命周期里都會保持不變,絕不會因為對應(yīng) Pod 的刪除或者重新創(chuàng)建而失效。
不過,相信你也已經(jīng)注意到了,盡管 web-0.nginx 這條記錄本身不會變,但它解析到的 Pod 的 IP 地址,并不是固定的。這就意味著,對于“有狀態(tài)應(yīng)用”實例的訪問,你必須使用 DNS 記錄或者 hostname 的方式,而絕不應(yīng)該直接訪問這些 Pod 的 IP 地址。
原文鏈接:https://juejin.cn/post/7186652464677355576
相關(guān)推薦
- 2023-02-27 詳解C++?STL模擬實現(xiàn)list_C 語言
- 2022-09-05 C語言深入淺出分析函數(shù)指針_C 語言
- 2022-04-25 ASP.NET?Core?MVC中過濾器工作原理介紹_實用技巧
- 2022-11-22 Python?arrow模塊使用方法_python
- 2023-02-10 Golang中interface的基本用法詳解_Golang
- 2022-03-23 C語言可變長的參數(shù)列表詳解_C 語言
- 2022-03-22 C語言圍圈報數(shù)題目代碼實現(xiàn)_C 語言
- 2023-07-16 spring boot 多環(huán)境配置
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支