返回文章列表

Kubernetes Redis StatefulSet 持久儲存與高可用性佈署

本文介紹如何在 Kubernetes 中佈署具備持久儲存和高用性的 Redis StatefulSet,包含建立 PersistentVolumeClaim、設定 Headless Service、使用 ConfigMap 管理啟動指令碼以及建立 TCP Load Balancer

Web 開發 系統設計

在 Kubernetes 環境下,佈署具備持久儲存的 Redis StatefulSet 是確保資料可靠性的關鍵。首先,我們需要定義 PersistentVolumeClaim 來動態組態儲存資源,並將其掛載至 Redis 容器。為了實作高用性,我們可以將 StatefulSet 的副本數量設定為 3,並透過 Headless Service 和自定義啟動指令碼,確保主從節點的自動切換和資料同步。此外,我們還需要建立 TCP Load Balancer 來提供對 Redis 服務的外部存取。為了佈署靜態檔案,我們可以使用 Deployment 和 Service,並透過 Ingress 暴露服務。最後,為了簡化多環境佈署的複雜性,我們可以使用 Helm 引數化應用程式組態,提高佈署效率和可維護性。

佈署具有持久儲存的Redis StatefulSet

以下範例展示了一個使用PersistentVolumes的Redis StatefulSet:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: "redis"
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:5-alpine
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

內容解密:

此YAML檔案定義了一個名為redis的StatefulSet,包含一個Redis容器,並使用PersistentVolumeClaim(PVC)範本請求10Gi的儲存空間。volumeMounts將PVC掛載到容器的/data目錄,用於持久化儲存Redis資料。

擴充套件Redis叢集以實作高用性

要實作Redis叢集的高用性,需要增加StatefulSet的副本數量至3,並確保新副本能夠連線到寫入主節點。

首先,建立一個headless Service來建立DNS記錄redis-0.redis,指向第一個Redis副本的IP位址。然後,建立一個指令碼來判斷當前容器是否為主節點(redis-0),如果是,則啟動Redis伺服器;否則,以slave模式連線到主節點。

#!/bin/sh
PASSWORD=$(cat /etc/redis-passwd/passwd)
if [[ "${HOSTNAME}" == "redis-0" ]]; then
  redis-server --requirepass ${PASSWORD}
else
  redis-server --slaveof redis-0.redis 6379 --masterauth ${PASSWORD} --requirepass ${PASSWORD}
fi

內容解密:

此指令碼首先讀取密碼,然後根據主機名判斷是否為主節點。如果是主節點,則直接啟動Redis伺服器;否則,以slave模式連線到主節點,並設定密碼。

建立ConfigMap並更新StatefulSet

將上述指令碼建立為ConfigMap,並更新StatefulSet以使用該ConfigMap。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: "redis"
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:5-alpine
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
        - name: script
          mountPath: /script/launch.sh
          subPath: launch.sh
        - name: passwd-volume
          mountPath: /etc/redis-passwd
        command:
        - sh
        - -c
        - /script/launch.sh
      volumes:
      - name: script
        configMap:
          name: redis-config
          defaultMode: 0777
      - name: passwd-volume
        secret:
          secretName: redis-passwd
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

內容解密:

此更新後的StatefulSet YAML檔案包含了ConfigMap和Secret的掛載,並使用launch.sh指令碼作為容器的啟動命令。該StatefulSet現在具有3個副本,實作了Redis叢集的高用性。

建立TCP Load Balancer以存取Redis服務

為了使前端能夠存取Redis服務,需要建立兩個Kubernetes Service:一個用於讀取資料,另一個用於寫入資料。

建立讀取Service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
  name: redis
  namespace: default
spec:
  ports:
  - port: 6379
    protocol: TCP
    targetPort: 6379
  selector:
    app: redis
  sessionAffinity: None
  type: ClusterIP

內容解密:

此Service將請求路由到Redis StatefulSet的所有副本,用於讀取資料。

建立寫入Service(Headless Service)

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis-write
  name: redis-write
spec:
  clusterIP: None
  ports:
  - port: 6379
  selector:
    app: redis

內容解密:

此Headless Service建立了一個DNS記錄,用於存取Redis主節點(redis-0),用於寫入資料。

使用Ingress路由流量到靜態檔案伺服器

建立靜態檔案伺服器Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: fileserver
  name: fileserver
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: fileserver
  template:
    metadata:
      labels:
        app: fileserver
    spec:
      containers:
      # 請替換為包含靜態檔案的自有映像
      - image: my-repo/static-files:v1-abcde
        imagePullPolicy: Always
        name: fileserver
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        resources:
          requests:
            cpu: "1.0"
            memory: "1G"
          limits:
            cpu: "1.0"
            memory: "1G"
      dnsPolicy: ClusterFirst
      restartPolicy: Always

內容解密:

此YAML檔案定義了一個名為 fileserver 的Deployment,使用自定義的靜態檔案Docker映像,並設定了資源請求與限制。該Deployment包含2個副本,以實作負載平衡和高用性。請確保將映像替換為實際包含靜態檔案的映像。

建立靜態檔案伺服器Service

為了使靜態檔案伺服器能夠被外部存取,需要建立一個Kubernetes Service:

apiVersion:v1
kind:Service
metadata:
   labels:
     app:fileserver
   name:fileserver
   namespace:default
spec:
   ports:
   - port:80
     protocol:TCP
     targetPort:80
   selector:
     app:fileserver
   sessionAffinity:None
   type:ClusterIP

內容解密:

此Service將流量路由到 fileserver Deployment的Pod,監聽TCP協定的80埠。由於 type 設定為 ClusterIP,該Service僅在叢集內部可存取,若需對外暴露服務,需使用Ingress或其他方法。

更新Ingress資源以路由靜態檔案請求

為了使靜態檔案能夠正確地被存取,需更新現有的Ingress資源,將靜態檔案請求路由至 fileserver Service。同時需注意路徑優先順序的設定,以避免 /api 路徑被錯誤路由至靜態檔案伺服器。

apiVersion:networking.k8s.io/v1
kind:Ingress
metadata:
   name:frontend-ingress
spec:
   rules:
   - http:
       paths:
       - path:/api
         pathType:Prefix
         backend:
           service:
             name:frontend # 請確認前端Service名稱正確性,若有誤需修正
             port:
               number:8080 # 請確認前端Service的埠號是否正確,若有誤需修正為正確值

       # 注意:此規則必須放在/api之後,以避免比對/api請求導致錯誤路由至靜態檔案伺服器。  
       - path:/
         pathType:Prefix
         backend:
           service:
             name:fileserver # 請確認靜態檔案Service名稱正確性,若有誤需修正為實際名稱  
             port:
               number:80 # 請確認靜態檔案Service的埠號是否正確,若有誤需修正為正確值

內容解密

  • 此Ingress資源定義了兩個路徑規則,將 /api 路徑的請求路由至前端服務,而將根路徑 / 的請求路由至靜態檔案伺服器。

  • 為了避免路徑衝突, /api 路徑必須優先比對,否則所有請求(包括 /api )都將被錯誤導向靜態檔案伺服器。

  • YAML中已標註需確認Service名稱與埠號是否正確,請根據實際佈署情況進行調整,以確保組態生效。

使用 Helm 引數化您的應用程式

到目前為止,我們所討論的一切都集中在將單一服務例項佈署到單一叢集。然而,事實上,幾乎每個服務和服務團隊都需要佈署到多個環境(即使他們共用一個叢集)。即使您是單一開發人員,開發單一應用程式,您也可能希望至少有一個開發版本和一個生產版本,以便在不破壞生產使用者的情況下進行迭代和開發。在考慮整合測試和 CI/CD 時,即使只有一個服務和少數開發人員,您也可能希望佈署到至少三個不同的環境,如果考慮處理資料中心級別的故障,則可能需要更多。

佈署選項

許多團隊最初的失敗模式是簡單地將檔案從一個叢集複製到另一個叢集。與其只有一個前端/目錄,不如擁有一對 frontend-production/ 和 frontend-development/ 目錄。雖然這是一個可行的選擇,但它也很危險,因為您現在負責確保這些檔案保持彼此同步。如果它們原本應該完全相同,這可能很容易,但開發和生產之間的一些差異是可以預期的。至關重要的是,這種差異是故意的且易於管理。

另一個實作此目地的選擇是使用分支和版本控制,主生產和開發分支從中央儲存函式庫分出,並且分支之間的差異清晰可見。這對於某些團隊來說可能是一個可行的選擇,但當您希望同時將軟體佈署到不同的環境(例如,佈署到多個不同雲區域的 CI/CD 系統)時,在分支之間切換的機制很有挑戰性。

因此,大多數人最終使用範本系統。範本系統將構成應用程式組態核心的範本與引數結合起來,這些引數將範本專門化為特定的環境組態。這樣,您可以擁有一個通用的分享組態,並根據需要進行有意的(且易於理解的)自定義。Kubernetes 有多種範本系統,但迄今為止最受歡迎的是 Helm。

Helm 簡介

在 Helm 中,應用程式被封裝成稱為 chart 的檔案集合(在容器和 Kubernetes 的世界中,航海笑話比比皆是)。chart 以 chart.yaml 檔案開頭,該檔案定義了 chart 本身的後設資料:

apiVersion: v1
appVersion: "1.0"
description: A Helm chart for our frontend journal server.
name: frontend
version: 0.1.0

該檔案放置在 chart 目錄的根目錄中(例如,frontend/)。在此目錄中,有一個 templates 目錄,範本就放在這裡。範本基本上是來自前面的示例的 YAML 檔案,其中檔案中的一些值被引數參照替換。例如,假設您想要引數化前端的副本數量。以前,Deployment 是這樣的:

spec:
  replicas: 2

在範本檔案(frontend-deployment.tmpl)中,它看起來像這樣:

spec:
  replicas: {{ .replicaCount }}

內容解密:

此段落展示瞭如何使用 Helm 範本系統引數化 Kubernetes Deployment 中的副本數量。其中,{{ .replicaCount }}是一個引數參照,它將在佈署 chart 時被替換為實際的值。

這意味著當您佈署 chart 時,您將使用適當的引數替換副本的值。引數本身在 values.yaml 檔案中定義。每個環境都會有一個 values 檔案,用於佈署應用程式。這個簡單 chart 的 values 檔案如下所示:

replicaCount: 2

內容解密:

這段程式碼定義了一個名為 replicaCount 的引數,並將其值設定為 2。在佈署 Helm chart 時,這個值將被用來替換範本中的 {{ .replicaCount }}

將所有這些放在一起,您可以使用 helm 工具佈署此 chart,如下所示:

helm install path/to/chart --values path/to/environment/values.yaml

這會引數化您的應用程式並將其佈署到 Kubernetes。隨著時間的推移,這些引數化將擴充套件到涵蓋應用程式的多種環境。

佈署服務的最佳實踐

Kubernetes 是一個強大的系統,似乎很複雜。但是,如果您使用以下最佳實踐,則設定基本應用程式以獲得成功可以很簡單:

  • 大多數服務應佈署為 Deployment 資源。Deployment 建立相同的副本以實作冗餘和擴充套件。
  • Deployment 可以使用 Service 公開,Service 實際上是一個負載平衡器。
  • Service 可以在叢集內公開(預設),也可以在外部公開。如果要公開 HTTP 應用程式,可以使用 Ingress 控制器新增諸如請求路由和 SSL 之類別的功能。
  • 最終,您需要引數化您的應用程式,以使其組態在不同的環境中更具可重用性。像 Helm 這樣的封裝工具是這種引數化的最佳選擇。