Photo by Markus Winkler on Unsplash

Longhorn — 雲原生儲存系統試玩

John Chung
Gemini Open Cloud 雙子星雲端
28 min readJan 21, 2022

--

因為近期正在尋找有什麼能夠使用在 Kubernetes 上,同時具備輕巧且功能又全面的儲存方案。這時從 CNCF 的 landscape 中看到了一個名為 Longhorn 的項目。稍微查了一下後發現這個項目在 2019 年的時候就已經被納入 CNCF 中。沒想到這都已經過去兩年了,人家都已經從 Sandbox 升到 Incubating 了。雖然感嘆自己的情報落後,但還是要轉換心情好好研究一下。

Longhorn 是一個由 Rancher Labs 建立的開源專案,其目的在於為 Kubernetes 提供雲原生的分散式儲存系統。除了部署簡單之外,同時也透過快照、副本以及遠端備份等機制保證了資料的安全性。

接下來的篇章,將會簡單介紹一下 Longhorn,並且示範如何部署以及怎麼將 Longhorn 建立出來的 volume 掛載到 Pod 裡使用。

測試環境

  • Node 數量: 1
  • Ubuntu 16.04
  • Docker 20.10.7
  • Kubernetes v1.16.15
  • Helm v2.8.1
  • Longhorn v1.1.3 (從 v1.2.0 之後只支援 Kubernetes v1.18+)

簡介

Longhorn 主要包含了以下兩個組件:

  • Longhorn Manager
    Longhorn manager 是以 Kubernetes Operator 的形式,以 DaemonSet 的方式部署在所有的節點上。Longhorn Manager 同時也作為一個 API 服務,接收從 UI 或是 CSI plugin 發送過來的 API 請求。
    當 Longhorn Manager 接收到建立 volume 的請求時,Longhorn Manager 會透過 Kubernetes API server 建立一個 Volume 及其它相關的 CRD 物件。在 volume 被掛載到某個節點上後,Longhorn Manager 會在這個節點上啟動一個 Longhorn Engine (Controller) 程序,然後在每台存放副本的節點上建立相對應的 Replica 程序。
  • Longhorn Engine
    Longhorn Engine 主要包含了 ControllerReplica 兩個部分:
    - Controller: 透過 iSCSI 介面,將 volume 的讀寫操作轉換成內部的請求發送給 Replica,再由 Replica 實際在節點上的檔案處理完需求後將結果回報。此外也負責 Replica 的狀態檢測和重建,及處理 volume 的快照與備份等操作。
    - Replica: 接收來自 Controller 的請求,實際在節點上讀寫資料及執行快照或備份等動作。
Picture from Longhorn

以上圖為例,圖中的每個 Pod 各自佔用一個獨立的 volume。每個 volume 都會有一個專屬的 Longhorn Engine (Controller) 接收 volume 的讀寫請求。假如設定每個 volume 都會保存兩個副本,則 Controller 底下就會有兩個 Replica 程序,且每個 Replica 會負責在不同的節點上 (同一個 volume 的 Replica 預設不能執行在同一台節點上) 管理各自的資料副本。

除了上述提到的兩個核心組件,Longhorn 也有提供:

  • UI 介面: 可讓管理員能以更簡便的方式管理目前系統中的 volume 、快照及備份,或是調整可以調度的節點和節點上可供使用的磁碟。
  • CSI plugin: 讓 Kubernetes 可以透過 CSI 介面管理 Longhorn 的 volume。

安裝 Longhorn

在真正進入到 Longhorn 的部署之前,首先要依照官方文件的指示,安裝好必要的套件。

Longhorn 官方也有提供一個 shell script 能事先確認環境是否有符合需求。

$ curl -sSfL https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/scripts/environment_check.sh | bash

一切準備妥當後就可以開始 Longhorn 的部署。Longhorn 的安裝方式有三種:

  • 透過 Rancher Catalog App 安裝
  • 透過 Kubectl 使用 Manifest File 安裝
  • 透過 Helm Chart 安裝

因為我們並沒有使用到 Rancher 的關係,所以第一種方式可以暫不考慮。

1. 透過 Kubectl 使用 Manifest File 安裝

$ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/deploy/longhorn.yaml

輸入上面的指令後,會創建一個叫 longhorn-system 的 namespace,並且將 Longhorn 相關的資源建立在該 namespace 下。接著可以再使用指令確認服務是否已經都準備完畢。

$ kubectl get po -n longhorn-system

2. 透過 Helm Chart 安裝

# Add the Longhorn Helm repository
$ helm repo add longhorn https://charts.longhorn.io
# Fetch the latest charts from the repository
$ helm repo update
# Install Longhorn in the longhorn-system namespace
$ helm install longhorn/longhorn -n longhorn --namespace longhorn-system --version 1.1.3

從 repository 下載 helm chart 預設會使用最新的版本進行安裝,所以必須額外指定安裝版本為 1.1.3。

如果安裝環境沒辦法使用外部網路的話,官方也有提供相關的步驟說明如何進行離線安裝。如果有需要請自行參閱官方文件,這裡就不再深入介紹。

客制化安裝

Longhorn 預設會為每個 volume 建立三個副本到不同的節點上,但是目前我們只是為了做實驗,整個 Kubernetes 環境裡又只有一個節點。因此想要把預設的三個副本改成一個就好。

1. 透過 Kubectl 使用 Manifest File 安裝

首先將 Longhorn 的 manifest file 下載回來:

$ wget https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/deploy/longhorn.yaml -O longhorn.yaml

打開下載回來的 longhorn.yaml,尋找 numberOfReplicas 這個關鍵字,把原本的值從 3 改成 1:

...
data:
storageclass.yaml: |
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: longhorn
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
numberOfReplicas: "1" # 3 -> 1
staleReplicaTimeout: "2880"
fromBackup: ""
...

接著用這個修改過後的檔案部署:

$ kubectl apply -f longhorn.yaml

2. 透過 Helm Chart 安裝

首先把 helm chart 下載回來:

$ helm fetch longhorn/longhorn --version 1.1.3 --untar

進到下載回來的 longhorn 目錄下,編輯 values.yaml,將 defaultClassReplicaCount 的值改成 1:

...
persistence:
defaultClass: true
defaultClassReplicaCount: 1 # 3 -> 1
reclaimPolicy: Delete
recurringJobs:
enable: false
jobList: []
...

安裝修改後的 helm chart:

$ helm install longhorn -n longhorn --namespace longhorn-system

除了修改安裝時的參數,在安裝完成之後,也可以直接修改 Longhorn 設定:

$ kubectl -n longhorn-system edit cm longhorn-storageclass

與修改 kubectl 的 manifest file 類似,只要改掉 numberOfReplicas 的值就可以了。

設定 UI 介面

Longhorn 雖然有提供 UI 介面,但預設是沒有對外接口也沒有任何權限認證的。所以官方建議是另外安裝一個 Nginx Ingress Controller。除了作為對外的入口之外,同時 Nginx Ingress Controller 也有提供認證機制,避免管理介面對外後被人隨便連進來亂搞。

Ingress controller 的安裝請參考官方的文件進行。另外雖然 Nginx Ingress Controller 有提供其它的認證方式,但這邊還是採用最簡單的帳密認證形式來操作。

# Create a basic auth file
$ USER=<USERNAME_HERE>; PASSWORD=<PASSWORD_HERE>; echo "${USER}:$(openssl passwd -stdin -apr1 <<< ${PASSWORD})" >> auth
# Create a secret
$ kubectl -n longhorn-system create secret generic basic-auth --from-file=auth

首先透過上面的指令建立一組帳密並且輸出到一個名為 auth 的檔案中。
接著再用 kubectl 指令將檔案內容儲存成一個 Kubernetes secret 物件。

最後再建立 ingress 物件:

# ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: longhorn-ingress
namespace: longhorn-system
annotations:
# type of authentication
nginx.ingress.kubernetes.io/auth-type: basic
# prevent the controller from redirecting (308) to HTTPS
nginx.ingress.kubernetes.io/ssl-redirect: 'false'
# name of the secret that contains the user/password definitions
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# message to display with an appropriate context why the authentication is required
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required '
spec:
rules:
- http:
paths:
- backend:
serviceName: longhorn-frontend
servicePort: 80

此為我從官網的範例根據目前的 Kubernetes 版本 (v1.16.15) 修改過的模版

$ kubectl apply -f ingress.yaml

上述步驟完成後就可以透過 ingress controller 連到 Longhorn UI 了。只是一般而言存取 ingress controller 必須透過 node port。要記 node port 對我來說有點麻煩,而且這也只是個實驗環境,所以可以讓我隨便亂玩,因此我選擇將 ingress controller 的 Deployment 修改成直接使用主機網路,好讓我可以直接用 443 port 存取服務。

# kubectl -n ingress-nginx edit deploy ingress-nginx-controller
...
spec:
containers:
- name: controller
hostNetwork: true # set hostNetwork to true
nodeSelector:
kubernetes.io/os: linux
restartPolicy: Always
schedulerName: default-scheduler
...

實際連上 UI 試試

在嘗試連上 UI 時會跳出視窗要求輸入帳號密碼。在登入成功後便可以看到 Longhorn 的 UI 介面。

實際運用

Longhorn 安裝完成之後會自動建立一個叫作 longhorn 的 StorageClass。我們可以透過這個 StorageClass 建立 PersistentVolumeClaim,接著 CSI plugin 就會自動在 Longhorn 建立一個 volume 並創建一個對應的 PersistentVolume 與我們建立的 PersistentVolumeClaim 綁定在一起。

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi

根據上面的模版建立一個名為 test-pvc 容量為 1GiB 的 volume。要記得 storageClassName 必須設定成 longhorn。不過如果是用 helm 安裝 Longhorn 的話,會預設將 longhorn 這個 StorageClass 設定成系統預設值。所以在不指定的情況下會直接採用 longhorn 作為其 storageClassName。

如果不想要 longhorn 作為預設的 StorageClass,可以在 helm 安裝時設定persistence.defaultClass 為 false:

...
persistence:
defaultClass: false # true -> false
defaultClassReplicaCount: 1
reclaimPolicy: Delete
recurringJobs:
enable: false
jobList: []
...

又或者是直接編輯 longhorn-storageclass 這個 ConfigMap,把 storageclass.kubernetes.io/is-default-class 這個 annotation 改成 false 或是刪除掉。

在建立完成後,可以看到系統已經自動建立好一個
PersistentVolume 並且與 test-pvc 綁定完成。

接著再建立一個 Pod,將 test-pvc 掛載上去:

# pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: test
name: test
spec:
containers:
- command:
- sh
image: alpine:3.7
name: test
tty: true
stdin: true
volumeMounts:
- name: test-pvc
mountPath: /data
volumes:
- name: test-pvc
persistentVolumeClaim:
claimName: test-pvc

用上面的模版建立好 Pod 之後,進到 container 內驗證 volume 的運作:

同時從 UI 上觀察 volume 的狀態:

擴充容量

當有擴充 volume 容量的需求時,可以透過兩種方式調整:

  • 透過 UI 調整
  • 透過 PersistentVolumeClaim 調整

不過要執行這個動作必須先把 volume 卸載才行,所以若是 volume 已經被掛載到某個 Pod 上的話,就必須先把那個 Pod 刪除。等 Pod 刪除完成後,應該可以從 UI 看到目前 volume 的狀態為 Detached。確認 volume 的狀態改變後,就可以執行擴充容量的動作。

1. 透過 UI 調整
在 volume 的右側選單中選擇 Expand Volume,填寫好新的大小後確認。接著等待擴充結束即可。

2. 透過 PersistentVolumeClaim 調整

直接更新 PersistentVolumeClaim 的 spec.resources.requests.storage 即可。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 3Gi

快照與備份

為了避免一些不當操作或是意外狀況導致資料的毀損,定期對 volume 做快照或備份是很重要的。

要對一個 volume 做快照,只要點選 volume 進到 details 頁面,然後按下 Take Snapshot 按鈕等待其執行結束即可。

除了透過快照將不同版本的差異化資料儲存在每個副本之外,也可以使用備份將快照的內容存放在外部的 NFS 或 S3 中。

備份的方式與快照類似,可以直接點選 Create Backup 按鈕對最新的快照做備份,或是指定備份某個版本的快照。

備份最新的快照
備份指定的快照

不過在執行備份前要先確認是否已經設定好備份的位置,不然就會像下圖一樣失敗。

可以到 Settings 頁面,找到 Backup Target 這個欄位後,把備份的位置填寫上去,並且按下最下方的 Save。如果要知道備份位置的填寫格式,或是想要建立一個實驗用的備份服務 (NFS or S3),可以參考官方的教學

設定好後就可以執行備份操作,並且可以到 Backup 頁面瀏覽 volume 目前有哪些備份。

快照回滾

當我們想要取得舊的快照中的資料時,可以將目前的 volume 回滾到快照當時的狀態。不過要將資料回滾必須先把 volume 以維護模式掛載到節點上。與擴充容量時一樣,需先將 volume 從目前使用中的 Pod 中卸載。只是在刪除掉 Pod 之前,讓我們先進到 Pod 裡確認一下目前資料的狀態:

然後再將這個 Pod 刪除掉。接著把這個 volume 掛載到任意一個節點上。記得要選擇以 Maintenance 模式掛載。

掛載上去後指定一個你想回溯的快照,點選 Revert,就會將目前的 volume 回溯到指定的狀態。

從圖中可看到現在 volume 的資料是指向剛剛所選定的快照。確認回溯完成後將 volume 再次卸載。

現在重新建立一個 Pod 並且把 volume 掛載上去,可以看到 volume 裡的資料已經變回指定快照的狀態。

從備份回復

除了快照回滾之外,我們也可以透過備份檔案重新將 volume 建立回來。

首先進到 Backup 頁面,可以看到有哪些 volume 存有備份檔案。可以選擇直接以最新的備份回復,或是點進選定的 volume,再從備份清單中另外指定回復的備份來源。

把回復的 volume 名稱、副本數量等資料填好後按下確認就可以在 Volume 頁面看到回復好的 volume。

雖然將 volume 建立回來了,可是從 UI 建立出來的 volume 不會有 PersistentVolume、PersistentVolumeClaim 可供 Pod 使用。所以我們還需要再幫這個 volume 補建這兩個物件。

點選 Create PV/PVC,填好 PersistentVolume 和 PersistentVolumeClaim 的名稱,以及要建立在哪個 namespace 之後,就能夠在 Kubernetes 上看到建立好的物件。

從 UI 建立出來的 PersistentVolume 和 PersistentVolumeClaim 並非動態建立的,因此若是需要刪除 volume,就必須手動將 PersistentVolume、PersistentVolumeClaim,以及 volume 本身刪除。

定時工作

不管是快照還是備份,到目前為止都還是採用手動的方式操作。但若總是都得由人工觸發的話不僅麻煩,而且也很難確保隨時都有可用的快照或備份能夠使用。所以 Longhorn 也支援設定自動化工作,定時執行快照或備份。

要對一個 volume 設定自動化快照 (或備份),首先需在 UI 上點選指定 volume 進到 details 頁面。接著在下方的 Recurring Snapshot and Backup Schedule 區塊中按 + New 新增一個工作。定時工作有分快照 (Snapshot) 和備份 (Backup) 兩種類型。選擇好類型後,再設定觸發間隔時間。時間設定是使用 Crontab 格式。然後是設定保留數量,當快照 (或備份) 的數量超過設定數量的話,就會將最舊的一筆資料刪除。全部設定好之後按下 Save 即可。

自動快照 (或備份) 只有在 volume 資料更新後才會建立新的,所以不用擔心重複內容的快照 (或備份) 佔用住所有的保留數量。

在 Kubernetes 上可以看到 Longhorn 為設定好的工作建立了相對應的 CronJob。

$ kubectl -n longhorn-system get cronjob

雖然已經知道怎麼設定自動化工作了,但若每次建立 volume 後都要自己設定這些還是會很麻煩。因此如果可以在建立 volume 的時候就自動設定好排程的話那就更完美了。

所以這邊就再簡單地講解如何調整安裝時的設定,讓 volume 建立出來的時候就會自動設定好排程:

1. 透過 Kubectl 使用 Manifest File 安裝
打開 longhorn.yaml 找到 recurringJobs 欄位,會看到這一段已經被註解。只要把註解拿掉,然後依照自己的需求調整觸發時間 (cron) 及保留數量 (retain) 即可。

...
recurringJobs: '[{"name":"snap", "task":"snapshot", "cron":"0 0 * * *", "retain":20},
{"name":"backup", "task":"backup", "cron":"0 0 * * *", "retain":20,
"labels": {"interval":"2m"}}]'
...

2. 透過 Helm Chart 安裝
開啟 longhorn 目錄下的 values.yaml,找到 recurringJobs,將底下的 enable 改為 true,jobList 按照 manifest file 同樣的設定方式寫好後,再用 helm 部署即可。

...
persistence:
defaultClass: false
defaultClassReplicaCount: 1
reclaimPolicy: Delete
recurringJobs:
enable: true
jobList: '[
{
"name": "snap",
"task": "snapshot",
"cron": "0 0 * * *",
"retain": 20
},
{
"name": "backup",
"task": "backup",
"cron": "0 0 * * *",
"retain": 20,
"labels": {"interval":"2m"}
}
]'
...

這個設定只會被套用在由 PersistentVolumeClaim 動態建立出來的 volume 上,直接從 UI 建立的 volume 依舊要自己設定定時工作。

跨節點掛載 (RWX)

除了 ReadWriteOnce (RWO) 之外,其實 Longhorn 也有支援 ReadWriteMany (RWX)。如果建立 PersistentVolumeClaim 時 accessModes 設定為 ReadWriteMany,當 volume 被掛載時,Longhorn 會自動建立一個以 share-manager-<volume_name> 命名的 Pod。volume 會先被掛載到這個 Pod 上,再由這個 Pod 提供 NFS 服務。其它需要用 volume 的 Pod 就會透過 NFS 掛載 volume 從而達到跨節點存取的能力。

$ kubectl -n longhorn-system get po,svc | grep pvc

解除安裝

為了避免解除安裝的動作對 Kubernetes 叢集造成影響,建議先確保任何有使用到 Longhorn volume 的資源 (Deployment、StatefulSet、PersistentVolume…) 都已被清除乾淨。

然後根據先前安裝方式的不同,解除安裝方式也會有些許的差別:

1. 透過 Kubectl 解除安裝

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/uninstall/uninstall.yaml

上面的指令會建立一個 job 執行解除安裝的動作,接著就耐心等待 job 執行結束,不過一般來說是不會等太久。

kubectl get job longhorn-uninstall -w

最後就可以將 Longhorn 相關資源及剛才執行的 job 透過 manifest file 清除。

kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/deploy/longhorn.yaml
kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/uninstall/uninstall.yaml

2. 透過 Helm 解除安裝

helm delete longhorn --purge

根據官方文件,使用 helm 解除安裝應該只需要執行上面的指令即可。但是實際操作的時候,發現這樣刪除還是會有殘留的資源。所以最後還是需要手動執行 kubectl delete ns longhorn-system 把剩下的資源連同 namespace 一起刪除掉。

假如在解除安裝的過程中把執行順序搞錯,先執行了

kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/deploy/longhorn.yaml

或是其它不明原因導致刪除的進度卡住不會動的狀況 ,可以嘗試執行

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.3/uninstall/uninstall.yaml

如果 longhorn-uninstall job 已經建立過,請先刪除原來的 job後再建立一次。

小結

這次只是簡單地過了一遍 Longhorn 的基本使用方式,除此之外還是有其它東西可以繼續研究。像是設定 PrometheusGrafana 以便監控系統狀態、各種系統錯誤的排查,又或者是寫程式與 Longhorn API 整合等。也許等之後將測試用的 Kubernetes 升級到更新的版本後可以再更深入的研究其它部分。

雙子星雲端為 CNCF 會員,是 CNCF 所認證的 Kubernetes 服務提供商,在雲端技術擁有十多年以上的經驗,為台灣雲端技術早期領先者。目前為國家級 AI 雲的軟體及 Kubernetes 技術與服務提供商,更是諸多企業與單位導入容器與管理平台的最佳夥伴。

雙子星雲端除了既有的產品 AI Console 與 Gemini API Gateway 之外,也提供企業諮詢與導入雲原生與 Kubernetes 相關技術服務,協助企業擁抱 Cloud Natvive,達到數位轉型的目標。

Reference

--

--

John Chung
Gemini Open Cloud 雙子星雲端

剛畢業便踏入雲端的世界,在天上遨遊之際不忘點份外送,BMI 隨著年資不斷增長的宅系工程師。目前任職於雙子星雲端。主要專注於後端開發,並跟著公司一起學習成長。