.NET Core + Kubernetes:StatefulSet

在 Kubernetes 中,Pod 資源的控制器 Deployment、Replicaset、Daemonset 等常用於管理無狀態應用,它們所管理的 Pod 對應的 IP、名字,啓停順序等都是隨機的,Pod 之間也並不存在任何關聯關係。而實際情況下,在應用集羣部署時,實例彼此之間可能是需要存在關聯關係的(啓動順序、角色),如 MySQL、MongoDB,所以 StatefulSet 就是爲了運行有狀態服務引入的一種資源類型,StatefulSet 爲每個 Pod 維持一個唯一且固定的標識符,必要時還會爲其創建專用的存儲卷,當 Pod 被重建時,也依然能保持原來的標識符和存儲卷。

完整的 StatefulSet 通常由三部分構成:StatefulSetVolumeClaimTemplateHeadless Service

StatefulSet 用於 Pod 資源定義與管控,在 StatefulSet 模式下,Pod 有自己固定的命名規則(StatfulSet 名稱 + Pod 創建時所在的索引),假設設置的 StatefulSet 名稱爲 k8sdemo,replicas 爲3,則對應的 Pod 名稱將分別是 k8sdemo-0k8sdemo-1k8sdemo-0,同時在進行 Pod 副本伸縮時也能做到按序號進行升降。

VolumeClaimTemplate 用於定義 Pod 所需存儲的 PVC 聲明 ,PVC 與 PV 進行綁定,提供專有固定的存儲卷。

Headless ServiceclusterIP: None)用於爲 Pod 生成可解析的 DNS 域名記錄,基於 Pod 名稱的有序規則,Pod 域名是不會變的(Pod 名稱.serviceName),這也保證了 Pod 網絡標識的穩定性。

下面繼續以 .NET Core 項目構建的 beckjin/k8sdemo:1.2.0 鏡像爲例,增加了接口訪問日誌記錄的功能。通過集成 log4net 將接口訪問日誌進行文件記錄,日誌將輸出到 /Data/ 目錄,每個 Pod 都會擁有自己的一份日誌文件(這只是一個假設的場景,切勿較真,實際情況下日誌記錄一般都會使用統一的日誌採集工具)。

定義資源

k8sdemo-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: k8sdemo
spec:
  serviceName: "k8sdemo-service"  # 需要與創建的 service name 一致
  replicas: 3
  selector:
    matchLabels:
      name: k8sdemo
  template:
    metadata:
      labels:
        name: k8sdemo
    spec:
      containers:
      - name: k8sdemo
        image: beckjin/k8sdemo:1.2.0
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: data
          mountPath: /app/Data  # 將容器內的 Data 目錄進行掛載
  volumeClaimTemplates:  # 定義模板,自動創建 PVC
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadOnlyMany
        resources:
          requests:
            storage:  100Mi
        storageClassName: "k8sdemo-sc"  # 將自動與集羣內 storageClassName 匹配的 PV 進行綁定

k8sdemo-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: k8sdemo-service
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
  selector:
    name: k8sdemo

StatefulSet 模式下需要設置 serviceName 字段,用來告訴 StatefulSet 控制器具體使用哪個 service 來解析它所管理的 Pod。同時通過 volumeClaimTemplates 字段進行 PVC 定義,StatefulSet 控制器會自動創建與 Pod 對應的 PVC,PVC 的名稱爲 (volumeClaimTemplateName)-(podName),然後 PVC 會自動與滿足要求的 PV 進行綁定,PV 如果不支持自動創建可手動完成。另外當 Pod 被刪除時 PVC 與 PV 依然會被保留,Pod 重建時會重新關聯之前對應的 PVC 與 PV。

這裏還是使用的 NFS 創建 PV 來實現存儲,分別創建 3 個(data-k8sdemo-pv-[1~3])滿足定義要求的 PV,如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: data-k8sdemo-pv-1
spec:
  nfs:
    server: 192.168.124.21
    path: /statefulset/data1
  accessModes:
  - ReadOnlyMany
  capacity:
    storage: 100Mi
  storageClassName: k8sdemo-sc

部署與測試

創建 PV 與 StatefulSet:

kubectl apply -f k8sdemo-statefulset-pv1.yaml
kubectl apply -f k8sdemo-statefulset-pv2.yaml
kubectl apply -f k8sdemo-statefulset-pv3.yaml
kubectl apply -f k8sdemo-statefulset.yaml

注意:: PV 命名順序並不代表被 PVC 的綁定順序,這兩者沒有關係,所以不用對上圖的數字編號對應關係有疑問。

創建 Service:

kubectl apply -f k8sdemo-service.yaml

因爲 Service 定義的是 Headless 模式,所以需要進去 Pod 內進行接口訪問測試,如:kubectl exec -it k8sdemo-0 bash 進入 k8sdemo-0 這個 Pod,通過域名 Pod 名稱.serviceName 來訪問,如下:

curl k8sdemo-0.k8sdemo-service/weatherforecast
curl k8sdemo-1.k8sdemo-service/weatherforecast
curl k8sdemo-2.k8sdemo-service/weatherforecast

在 NFS 掛載目錄中查看接口訪問日誌,以下是 Pod k8sdemo-1 中的日誌:

2020-09-20 06:01:17,451 [17] INFO  [k8sdemo-1] - Request starting HTTP/1.1 GET http://k8sdemo-1.k8sdemo-service/weatherforecast  
2020-09-20 06:01:17,455 [17] INFO  [k8sdemo-1] - Executing endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:01:17,458 [17] INFO  [k8sdemo-1] - Route matched with {action = "Get", controller = "WeatherForecast"}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[T.K8SDemo.WeatherForecast] Get() on controller T.K8SDemo.Controllers.WeatherForecastController (T.K8SDemo).
2020-09-20 06:01:17,459 [17] INFO  [k8sdemo-1] - Executing ObjectResult, writing value of type 'T.K8SDemo.WeatherForecast[]'.
2020-09-20 06:01:17,460 [17] INFO  [k8sdemo-1] - Executed action T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo) in 2.3627ms
2020-09-20 06:01:17,460 [17] INFO  [k8sdemo-1] - Executed endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:01:17,461 [17] INFO  [k8sdemo-1] - Request finished in 9.9194ms 200 application/json; charset=utf-8

執行 kubectl delete pod k8sdemo-1 刪除 Pod k8sdemo-1,等待一會 k8sdemo-1 會自動恢復,然後重新訪問 curl k8sdemo-1.k8sdemo-service/weatherforecast,日誌依然向原來的文件內追加,也說明保留了原來的狀態。

2020-09-20 06:01:17,451 [17] INFO  [k8sdemo-1] - Request starting HTTP/1.1 GET http://k8sdemo-1.k8sdemo-service/weatherforecast  
2020-09-20 06:01:17,455 [17] INFO  [k8sdemo-1] - Executing endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:01:17,458 [17] INFO  [k8sdemo-1] - Route matched with {action = "Get", controller = "WeatherForecast"}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[T.K8SDemo.WeatherForecast] Get() on controller T.K8SDemo.Controllers.WeatherForecastController (T.K8SDemo).
2020-09-20 06:01:17,459 [17] INFO  [k8sdemo-1] - Executing ObjectResult, writing value of type 'T.K8SDemo.WeatherForecast[]'.
2020-09-20 06:01:17,460 [17] INFO  [k8sdemo-1] - Executed action T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo) in 2.3627ms
2020-09-20 06:01:17,460 [17] INFO  [k8sdemo-1] - Executed endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:01:17,461 [17] INFO  [k8sdemo-1] - Request finished in 9.9194ms 200 application/json; charset=utf-8
2020-09-20 06:17:06,467 [12] INFO  [k8sdemo-1] - Request starting HTTP/1.1 GET http://k8sdemo-1.k8sdemo-service/weatherforecast  
2020-09-20 06:17:06,494 [12] INFO  [k8sdemo-1] - Executing endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:17:06,527 [12] INFO  [k8sdemo-1] - Route matched with {action = "Get", controller = "WeatherForecast"}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[T.K8SDemo.WeatherForecast] Get() on controller T.K8SDemo.Controllers.WeatherForecastController (T.K8SDemo).
2020-09-20 06:17:06,533 [12] INFO  [k8sdemo-1] - Executing ObjectResult, writing value of type 'T.K8SDemo.WeatherForecast[]'.
2020-09-20 06:17:06,548 [12] INFO  [k8sdemo-1] - Executed action T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo) in 17.1904ms
2020-09-20 06:17:06,549 [12] INFO  [k8sdemo-1] - Executed endpoint 'T.K8SDemo.Controllers.WeatherForecastController.Get (T.K8SDemo)'
2020-09-20 06:17:06,550 [12] INFO  [k8sdemo-1] - Request finished in 84.3414ms 200 application/json; charset=utf-8

另外對 Pod 副本進行伸縮時效果也是一樣的,都會保持 Pod 具有的狀態。當然文中的例子和一些組件的集羣部署不太一樣,比如像 MySQL 這類組件,各實例間還會做數據同步來實現數據的一致性,當然最終也是每個實例關聯自己的數據存儲卷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章