Deployment 實際上並不足以覆蓋全部的應用編排問題。nginx
形成這個問題的根本緣由,在於 Deployment 對應用作了一個簡單化假設。web
它認爲,一個應用的全部 Pod,是徹底同樣的。因此,它們互相之間沒有順序,也無所謂運行在哪 臺宿主機上。須要的時候,Deployment 就能夠經過 Pod 模板建立新的 Pod;不須要的時候, Deployment 就能夠「殺掉」任意一個 Pod。數據庫
可是,在實際的場景中,並非全部的應用均可以知足這樣的要求。 尤爲是分佈式應用,它的多個實例之間,每每有依賴關係,好比:主從關係、主備關係。 還有就是數據存儲類應用,它的多個實例,每每都會在本地磁盤上保存一份數據。而這些實例一旦 被殺掉,即使重建出來,實例與數據之間的對應關係也已經丟失,從而致使應用失敗。 因此,這種實例之間有不對等關係,以及實例對外部數據有依賴關係的應用,就被稱爲「有狀態應 用」(Stateful Application)。api
容器技術誕生後,你們很快發現,它用來封裝「無狀態應用」(Stateless Application),尤爲是 Web 服務,很是好用。可是,一旦你想要用容器運行「有狀態應用」,其困難程度就會直線上升。 並且,這個問題解決起來,單純依靠容器技術自己已經無能爲力,這也就致使了很長一段時間 內,「有狀態應用」幾乎成了容器技術圈子的「忌諱」,你們一聽到這個詞,就紛紛搖頭。 不過,Kubernetes 項目仍是成爲了「第一個吃螃蟹的人」。網絡
得益於「控制器模式」的設計思想,Kubernetes 項目很早就在 Deployment 的基礎上,擴展出了 對「有狀態應用」的初步支持。這個編排功能,就是:StatefulSet。 StatefulSet 的設計其實很是容易理解。app
它把真實世界裏的應用狀態,抽象爲了兩種狀況:less
1. 拓撲狀態。這種狀況意味着,應用的多個實例之間不是徹底對等的關係。這些應用實例,必須按 照某些順序啓動,好比應用的主節點 A 要先於從節點 B 啓動。分佈式
而若是你把 A 和 B 兩個 Pod 刪除 掉,它們再次被建立出來時也必須嚴格按照這個順序才行。而且,新建立出來的 Pod,必須和原 來 Pod 的網絡標識同樣,這樣原先的訪問者才能使用一樣的方法,訪問到這個新 Pod。oop
2. 存儲狀態。這種狀況意味着,應用的多個實例分別綁定了不一樣的存儲數據。對於這些應用實例來 說,Pod A 第一次讀取到的數據,和隔了十分鐘以後再次讀取到的數據,應該是同一份,哪怕在 此期間 Pod A 被從新建立過。spa
這種狀況最典型的例子,就是一個數據庫應用的多個存儲實例。 因此,StatefulSet 的核心功能,就是經過某種方式記錄這些狀態,而後在 Pod 被從新建立時,能 夠爲新 Pod 恢復這些狀態。 在開始講述 StatefulSet 的工做原理以前,我就必須先爲你講解一個 Kubernetes 項目中很是實用的 概念:Headless Service。
svc.yaml
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
能夠看到,所謂的 Headless Service,其實還是一個標準 Service 的 YAML 文件。只不過,它的 clusterIP 字段的值是:None,即:這個 Service,沒有一個 VIP 做爲「頭」。
這也就是 Headless 的含義。因此,這個 Service 被建立後並不會被分配一個 VIP,而是會以 DNS 記錄的方式暴露出它 所代理的 Pod。
而它所代理的 Pod,依然是採用我在前面第 12 篇文章《牛刀小試:個人第一個容器化應用》中提 到的 Label Selector 機制選擇出來的,即:全部攜帶了 app=nginx 標籤的 Pod,都會被這個 Service 代理起來。
而後關鍵來了。 當你按照這樣的方式建立了一個 Headless Service 以後,它所代理的全部 Pod 的 IP 地址,都會被 綁定一個這樣格式的 DNS 記錄,以下所示:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local # 如 web-1.nginx.default.svc.cluster.local
這個 DNS 記錄,正是 Kubernetes 項目爲 Pod 分配的惟一的「可解析身份」(Resolvable Identity)。
有了這個「可解析身份」,只要你知道了一個 Pod 的名字,以及它對應的 Service 的名字,你就可 以很是肯定地經過這條 DNS 記錄訪問到 Pod 的 IP 地址。
那麼,StatefulSet 又是如何使用這個 DNS 記錄來維持 Pod 的拓撲狀態的呢?
爲了回答這個問題,如今咱們就來編寫一個 StatefulSet 的 YAML 文件,以下所示:
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 的惟一區別,就是多了一個 serviceName=nginx 字段。
這個字段的做用,就是告訴 StatefulSet 控制器,在執行控制循環(Control Loop)的時候,請使 用 nginx 這個 Headless Service 來保證 Pod 的「可解析身份」。
因此,當你經過 kubectl create 建立了上面這個 Service 和 StatefulSet 以後,就會看到以下兩個 對象:
kubectl create -f svc.yaml kubectl get service nginx
kubectl create -f statefulset.yaml kubectl get statefulset web
這時候,若是你手比較快的話,還能夠經過 kubectl 的 -w 參數,即:Watch 功能,實時查看 StatefulSet 建立兩個有狀態實例的過程:不過,你依然能夠經過這個 StatefulSet 的 Events 看到這些信息。
kubectl get pods -w -l app=nginx
經過上面這個 Pod 的建立過程,咱們不難看到,StatefulSet 給它所管理的全部 Pod 的名字,進行 了編號,編號規則是:-。
並且這些編號都是從 0 開始累加,與 StatefulSet 的每一個 Pod 實例一一對應,毫不重複。 更重要的是,這些 Pod 的建立,也是嚴格按照編號順序進行的。
好比,在 web-0 進入到 Running 狀態、而且細分狀態(Conditions)成爲 Ready 以前,web-1 會一直處於 Pending 狀態。
當這兩個 Pod 都進入了 Running 狀態以後,你就能夠查看到它們各自惟一的「網絡身份」了。 咱們使用 kubectl exec 命令進入到容器中查看它們的 hostname:
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
能夠看到,這兩個 Pod 的 hostname 與 Pod 名字是一致的,都被分配了對應的編號。
接下來,我 們再試着以 DNS 的方式,訪問一下這個 Headless Service:
注意:busybox 不要使用最新的版本,在這裏我使用的是1.28.4的版本
kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
經過這條命令,咱們啓動了一個一次性的 Pod,由於–rm 意味着 Pod 退出後就會被刪除掉。而後, 在這個 Pod 的容器裏面,咱們嘗試用 nslookup 命令,解析一下 Pod 對應的 Headless Service:
nslookup web-0.nginx nslookup web-1.nginx
從 nslookup 命令的輸出結果中,咱們能夠看到,在訪問 web-0.nginx 的時候,最後解析到的,正 是 web-0 這個 Pod 的 IP 地址;
而當訪問 web-1.nginx 的時候,解析到的則是 web-1 的 IP 地 址。 這時候,若是你在另一個 Terminal 裏把這兩個「有狀態應用」的 Pod 刪掉:
$ kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
而後,再在當前 Terminal 裏 Watch 一下這兩個 Pod 的狀態變化,就會發現一個有趣的現象:
$ 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
能夠看到,當咱們把這兩個 Pod 刪除以後,Kubernetes 會按照原先編號的順序,建立出了兩個新 的 Pod。而且,Kubernetes 依然爲它們分配了與原來相同的「網絡身份」:web-0.nginx 和 web1.nginx。 經過這種嚴格的對應規則,StatefulSet 就保證了 Pod 網絡標識的穩定性。 好比,若是 web-0 是一個須要先啓動的主節點,web-1 是一個後啓動的從節點,那麼只要這個 StatefulSet 不被刪除,你訪問 web-0.nginx 時始終都會落在主節點上,訪問 web-1.nginx 時,則 始終都會落在從節點上,這個關係絕對不會發生任何變化。 因此,若是咱們再用 nslookup 命令,查看一下這個新 Pod 對應的 Headless Service 的話:
kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh nslookup web-0.nginx nslookup web-1.nginx
咱們能夠看到,在這個 StatefulSet 中,這兩個新 Pod 的「網絡標識」(好比:web-0.nginx 和 web-1.nginx),再次解析到了正確的 IP 地址(好比:web-0 Pod 的 IP 地址 10.96.0.10)。 經過這種方法,Kubernetes 就成功地將 Pod 的拓撲狀態(好比:哪一個節點先啓動,哪一個節點後啓 動),按照 Pod 的「名字 + 編號」的方式固定了下來。
此外,Kubernetes 還爲每個 Pod 提供 了一個固定而且惟一的訪問入口,即:這個 Pod 對應的 DNS 記錄。 這些狀態,在 StatefulSet 的整個生命週期裏都會保持不變,毫不會由於對應 Pod 的刪除或者從新 建立而失效。 不過,相信你也已經注意到了,儘管 web-0.nginx 這條記錄自己不會變,但它解析到的 Pod 的 IP 地址,並非固定的。
這就意味着,對於「有狀態應用」實例的訪問,你必須使用 DNS 記錄或者 hostname 的方式,而毫不應該直接訪問這些 Pod 的 IP 地址。
因此,StatefulSet 其實能夠認爲是對 Deployment 的改良。 與此同時,經過 Headless Service 的方式,StatefulSet 爲每一個 Pod 建立了一個固定而且穩定的 DNS 記錄,來做爲它的訪問入口。 實際上,在部署「有狀態應用」的時候,應用的每一個實例擁有惟一而且穩定的「網絡標識」,是一 個很是重要的假設。