在 Kubernetes 環境中,Helm 有效簡化了應用程式佈署和管理的流程。除了基本佈署功能,Helm 也支援在應用程式升級和回復過程中執行自定義生命週期鉤子,例如在升級前備份資料或回復時還原資料。本文以 Redis 為例,示範如何在 Helm Chart 中實作升級和回復機制,包含 PersistentVolumeClaim 和 Job 的設定,以及如何透過 Helm 指令觸發這些鉤子。同時,我們也探討了 Helm Chart 輸入驗證的重要性,說明如何使用 fail 函式限制服務型別,例如確保服務型別只能是 NodePort 或 ClusterIP,避免錯誤設定。
Helm Chart 的升級與回復機制
在 Kubernetes 中,Helm 是一個非常強大的包管理工具,能夠幫助我們更方便地佈署和管理應用程式。Helm 除了提供基礎的佈署功能外,還支援在應用程式升級和回復過程中執行自定義的生命週期鉤子(hooks)。這些鉤子可以在特定時刻執行自定義的動作,例如在升級前備份資料或在回復時還原資料。本文將探討如何在 Helm Chart 中實作升級與回復機制,並且使用具體案例來說明其實際應用。
升級前備份資料
在升級應用程式之前,我們通常需要先備份現有的資料,以防升級過程中發生錯誤。這裡我們以 Redis 作為例子,說明如何在 Helm Chart 中實作升級前備份機制。
1. 建立 PVC 範本
首先,我們需要建立一個 PersistentVolumeClaim(PVC)範本來儲存備份資料。這個範本會在升級前被建立,並且只會在 redis.master.persistence.enabled 值為 true 時才會包含在 Helm Chart 中。
{{- if .Values.redis.master.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data-redis-master-0-backup-{{ .Release.Revision | sub 1 }}
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "0"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
{{- end }}
內容解密:
這段程式碼使用了 Helm 的範本語法來定義一個 PVC。首先,我們使用 {{- if .Values.redis.master.persistence.enabled }} 來檢查 redis.master.persistence.enabled 值是否為 true。如果為 true,則繼續建立 PVC。接著,我們使用 {{ .Release.Revision | sub 1 }} 來計算備份的版本號碼,並將其加入 PVC 的名稱中。最後,我們設定 PVC 的存取模式和儲存需求。
2. 建立 Job 範本
接著,我們需要建立一個 Job 範本來執行實際的備份操作。這個 Job 會在升級前被觸發,並且負責將 Redis 的資料備份到剛剛建立的 PVC 中。
apiVersion: batch/v1
kind: Job
metadata:
name: redis-backup-job-{{ .Release.Revision }}
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation, hook-succeeded
spec:
template:
spec:
containers:
- name: redis-backup
image: redis:latest
command: ["sh", "-c"]
args:
- >
redis-cli -h redis-master SLAVEOF NO ONE &&
cp /data/dump.rdb /backup/dump.rdb &&
redis-cli SAVE &&
cp /data/dump.rdb /backup/dump.rdb
volumeMounts:
- name: redis-data
mountPath: /data
- name: backup-data
mountPath: /backup
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data-redis-master-0
- name: backup-data
persistentVolumeClaim:
claimName: redis-data-redis-master-0-backup-{{ .Release.Revision | sub 1 }}
restartPolicy: OnFailure
內容解密:
這段程式碼定義了一個 Job 資源來執行備份操作。首先,我們設定了 Job 的後設資料和註解,包括 pre-upgrade 鉤子和權重設定。接著,我們定義了一個容器來執行備份操作。該容器使用 Redis 命令列工具將 Redis 資料函式庫的狀態儲存到 /data/dump.rdb 檔案中,並將其複製到備份 PVC 中。
生命週期鉤子的實際執行
完成以上步驟後,我們可以透過以下命令安裝 Helm Chart:
$ helm install my-guestbook guestbook -n chapter5
安裝完成後,我們可以透過以下命令觸發升級前的鉤子:
$ helm upgrade my-guestbook guestbook -n chapter5
等待命令完成後,我們可以檢查是否成功建立了新的 PVC:
$ kubectl get pvc -n chapter5
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
redis-data-redis-master-0 Bound pvc-ba6f7236-e8c3-494e-aa78-f00b5a9b66d8 1Gi RWO standard 4m43s
redis-data-redis-master-0-backup-1 Bound pvc-e7e6e572-fc78-4fba-b2bd-d43f7a8e2efd 1Gi RWO standard 4m43s
建立回復鉤子
除了升級前的鉤子外,我們還需要在應用程式回復時還原資料。這裡我們以 Redis 作為例子,說明如何在 Helm Chart 中實作回復機制。
1. 建立回復 Job
首先,我們需要建立一個 Job 範本來執行實際的還原操作。這個 Job 會在回復時被觸發,並且負責將 Redis 的資料從備份 PVC 還原到原始 PVC 中。
apiVersion: batch/v1
kind: Job
metadata:
name: redis-restore-job-{{ .Release.Revision }}
annotations:
"helm.sh/hook": pre-rollback
spec:
template:
spec:
containers:
- name: redis-restore
image: busybox:latest
command: ["sh", "-c"]
args:
- >
cp /backup/dump.rdb /data/dump.rdb &&
redis-server --appendonly yes &
sleep 5 &&
kill $! || true && sleep 5 && cp /backup/dump.rdb /data/dump.rdb && sleep 2 && redis-server --appendonly yes &
volumeMounts:
- name: redis-data
mountPath: /data
- name: backup-data
mountPath: /backup
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data-redis-master-0
- name: backup-data
persistentVolumeClaim:
claimName: redis-data-redis-master-0-backup-{{ .Release.Revision | sub }}
restartPolicy: OnFailure
內容解密:
這段程式碼定義了一個 Job 資源來執行還原操作。首先,我們設定了 Job 的後設資料和註解,包括 pre-rollback 鉤子設定。接著,我們定義了一個容器來執行還原操作。該容器將 /backup/dump.rdb 檔案中的資料複製到 /data/dump.rdb 檔案中。
生命週期鉤子的實際執行
完成以上步驟後, 一但擁有了預升級及預回復鉤子, 則可以透過以下命令來觀察它們的運作:
kubectl rollout undo deployment/guestbook-deployment
此指令會開始回退動作, 在過程中, 預回復鉤子會從先前生成的備份PVC內載入最新版本以還原Redis狀態.
Conclusion
透過以上步驟, 你已經學習如何在Helm Chart中實作升級及回復機制, 能夠有效地保護你的應用程式資料免受意外損壞及遺失. 倘若你對於Kubernetes及Helm有一定基礎認知, 再進一步自行調整內容即可達到更多應用場景需求.
升級與最佳化 Helm Chart 練習
首先,讓我們來新增一條額外的訊息到 Guestbook 前端。你應該會在「提交」按鈕下方看到兩條訊息,如下圖所示:
此圖示:Guestbook 訊息在回復前的狀態
接著,執行 helm rollback 命令來回復到第一個版本。這個命令會暫時懸掛,直到還原過程完成,如下所示:
$ helm rollback my-guestbook 1 -n chapter5
當這個命令傳回後,請在瀏覽器中重新整理 Guestbook 前端。你會發現之前新增的訊息消失了,因為在資料備份之前它並不存在,如下圖所示:
此圖示:回復生命週期階段完成後的 Guestbook 前端
雖然這個備份與還原的案例非常簡單,但它展示了為 Helm Chart 新增生命週期鉤子可以提供的多種可能性。
注意事項
鉤子可以透過在對應的生命週期命令中新增 --no-hooks 旗標來跳過,例如 helm install、helm upgrade、helm rollback 或 helm uninstall。這些命令將跳過該生命週期的鉤子。
接下來,我們將專注於使用者輸入驗證,並探討如何進一步最佳化 Guestbook Chart 以防止提供不正確的值。
新增輸入驗證
在使用 Kubernetes 和 Helm 的時候,Kubernetes 的應用程式介面(API)伺服器會自動進行輸入驗證。這意味著如果 Helm 建立了一個無效的資源,API 伺服器會傳回錯誤訊息,導致安裝失敗。儘管 Kubernetes 已經進行了本地化輸入驗證,但在某些情況下,Chart 開發者可能希望在資源到達 API 伺服器之前進行驗證。
讓我們開始探索如何使用 fail 函式來進行輸入驗證。
使用 fail 函式
fail 函式用於立即停止範本渲染。當使用者提供無效的值時,可以使用這個函式。在此部分,我們將實作一個示例使用案例來限制使用者輸入。
你的 guestbook chart 的 values.yaml 檔案中包含一個名為 service.type 的值,用於決定應為前端建立哪種服務型別。這個值如下所示:
service:
type: NodePort
我們將其設定為預設使用 NodePort,但技術上其他服務型別也可以使用。假設你想要限制服務型別僅為 NodePort 和 ClusterIP。可以使用 fail 函式來實作這一點。
限制服務型別
按照以下步驟來限制你的 guestbook chart 中的服務型別:
找到
templates/service.yaml服務範本檔案。這個檔案包含一行程式碼,根據service.type值設定服務型別,如下所示:type: {{ .Values.service.type }}在設定服務型別之前,我們應該檢查
service.type值是否等於 ClusterIP 或 NodePort。可以透過設定一個變數來實作這一點,該變數等於正確的設定列表。然後檢查service.type值是否包含在有效設定列表中。如果是,則繼續設定服務型別;否則停止範本渲染並傳回錯誤訊息給使用者。service.yamlservice.type 驗證已在 service.yaml 範本中實作。第 8 行到第 13 行代表輸入驗證。第 8 行建立了一個名為
serviceTypes的變數,其等於正確的服務型別列表。第 9 行到第 13 行代表一個 if 操作。第 9 行中的has函式檢查service.type值是否包含在serviceTypes中。如果是,則繼續渲染至第 10 行設定服務型別;否則繼續渲染至第 12 行。第 12 行使用fail函式停止範本渲染並顯示一條訊息給使用者關於有效的service.type輸入。
嘗試透過提供無效的服務型別來升級你的 my-guestbook 發行版(如果你已解除安裝發行版本,安裝也可以)。要做到這一點,執行以下命令:
$ helm upgrade my-guestbook . -n chapter5 --set service.type=LoadBalancer
如果前面步驟中的更改成功了,你應該會看到類別似以下的訊息:
Error: UPGRADE FAILED: template: guestbook/templates/service.yaml:12:6: executing 'guestbook/templates/service.yaml' at <fail 'value 'service.type' must be either 'ClusterIP' or 'NodePort''>: error calling fail: value 'service.type' must be either 'ClusterIP' or 'NodePort'
儘管使用 fail 驗證使用者輸入是確保提供值符合某些約束條件的一種好方法,但在某些情況下需要確保使用者已經提供了某些值。這可以透過使用下一節介紹的 required 函式來實作。