StatefulSet(一):拓撲狀態


Deployment 實際上並不足以覆蓋所有的應用編排問題。

造成這個問題的根本原因,在於 Deployment 對應用做了一個簡單化假設。

它認為,一個應用的所有 Pod,是完全一樣的。所以,它們互相之間沒有順序,也無所謂運行在哪 台宿主機上。需要的時候,Deployment 就可以通過 Pod 模板創建新的 Pod;不需要的時候, Deployment 就可以“殺掉”任意一個 Pod。

但是,在實際的場景中,並不是所有的應用都可以滿足這樣的要求。 尤其是分布式應用,它的多個實例之間,往往有依賴關系,比如:主從關系、主備關系。 還有就是數據存儲類應用,它的多個實例,往往都會在本地磁盤上保存一份數據。而這些實例一旦 被殺掉,即便重建出來,實例與數據之間的對應關系也已經丟失,從而導致應用失敗。 所以,這種實例之間有不對等關系,以及實例對外部數據有依賴關系的應用,就被稱為“有狀態應 用”(Stateful Application)。

容器技術誕生后,大家很快發現,它用來封裝“無狀態應用”(Stateless Application),尤其是 Web 服務,非常好用。但是,一旦你想要用容器運行“有狀態應用”,其困難程度就會直線上升。 而且,這個問題解決起來,單純依靠容器技術本身已經無能為力,這也就導致了很長一段時間 內,“有狀態應用”幾乎成了容器技術圈子的“忌諱”,大家一聽到這個詞,就紛紛搖頭。 不過,Kubernetes 項目還是成為了“第一個吃螃蟹的人”。

得益於“控制器模式”的設計思想,Kubernetes 項目很早就在 Deployment 的基礎上,擴展出了 對“有狀態應用”的初步支持。這個編排功能,就是:StatefulSet。 StatefulSet 的設計其實非常容易理解。

 

它把真實世界里的應用狀態,抽象為了兩種情況:

1. 拓撲狀態。這種情況意味着,應用的多個實例之間不是完全對等的關系。這些應用實例,必須按 照某些順序啟動,比如應用的主節點 A 要先於從節點 B 啟動。

而如果你把 A 和 B 兩個 Pod 刪除 掉,它們再次被創建出來時也必須嚴格按照這個順序才行。並且,新創建出來的 Pod,必須和原 來 Pod 的網絡標識一樣,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod。

2. 存儲狀態。這種情況意味着,應用的多個實例分別綁定了不同的存儲數據。對於這些應用實例來 說,Pod A 第一次讀取到的數據,和隔了十分鍾之后再次讀取到的數據,應該是同一份,哪怕在 此期間 Pod A 被重新創建過。

 

這種情況最典型的例子,就是一個數據庫應用的多個存儲實例。 所以,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 記錄,來作為它的訪問入口。 實際上,在部署“有狀態應用”的時候,應用的每個實例擁有唯一並且穩定的“網絡標識”,是一 個非常重要的假設。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM