使用開源工具實現Kubernetes備份容災

前言

Kubernetes的備份目前官方社區沒有現成的成熟方案,當前使用最多的方式還是通過etcd快照做數據備份。

但是etcd的備份只能備份Kubernetes的資源,不能備份存儲在PV數據卷的業務數據,而這些數據往往纔是最核心的,Kubernetes資源沒了至少可以重新Apply,業務數據丟了是毀滅性的災難。

數據卷的備份可能需要取決於使用不同的PV存儲後端採用不用的備份方案,以Ceph RBD爲例,可以定時對RBD Image做快照,並通過快照把數據備份到對象存儲系統中,或者通過Ceph mirror實現異地複製容災。而公有云場景則可以直接使用公有云的備份服務實現對volume備份,至少AWS提供了類似這種方案。

如上方案是基於數據塊的形式對整卷做備份,當然也可以基於文件備份,這種方式其實備份效率會更高些,用多少備多少,結合去重技術和壓縮算法,可以大大節省存儲空間。

大家可能比較容易想到的方案是通過SideCar方式注入rsync或其他備份工具實時同步文件到遠端存儲中實現業務數據的實時增量備份。

在這裏我推薦restic[1]這款開源工具代替rsync做基於文件的備份,不僅僅適用於企業級Kubernetes環境的數據卷備份,個人的筆記本電腦備份也可以通過restic這款開源的工具實現。

當然現在很多現成的工具如iCloud、百度網盤等也能實現本地電腦備份同步,不過自己使用restic加密工具備份更自主可控、更安心些,價格也會更便宜。

總之,通過restic以SideCar的形式注入到包含PV的Pod中實現備份,技術上肯定是可行的,不過實現落地上可能會稍微複雜些,而開源的velero已經完全解決了這個問題。

因此本文接下來主要介紹如何利用開源的velero方案實現Kubernetes的應用備份。這裏需要強調的是,雖然使用velero做了應用級別的備份,但並不意味着不再需要對Kubernetes做集羣備份,etcd以及Kubernetes的證書、kubeadm的配置等依然需要在備份策略中考慮到,在集羣恢復中不可或缺。

由於Velero備份PV數據卷正是使用了前面的restic,因此本文先簡單介紹下restic的使用方法,這部分內容與Kubernetes備份沒有直接關係,但是瞭解底層對後續使用velero恢復數據很有用,已經對restic有了解的可以跳過。

restic簡介

restic是一款開源的跨Linux、MacOS、Windows等多種平臺操作系統的命令行備份工具,支持將本地文件全量或者增量加密備份到S3、SFTP服務器、遠端目錄、MinIO對象存儲等遠端倉庫中,可以代替我們常用的rsync工具。

restic包含如下五個設計理念:

簡單(Easy),備份和恢復只需要簡單一個命令即可完成,不需要太複雜的配置和指令。

快(Fast),備份和恢復速度僅受限於網絡帶寬和磁盤讀寫速率,工具本身不應該成爲性能瓶頸。

可校驗(Verifiable),用戶可以隨時查看和檢索任意備份點中備份的所有文件內容,從而確定備份是OK的。

安全(Secure),數據備份強加密存儲,即使遠端存儲倉庫泄露被攻擊者拿到,攻擊者也拿不到真實明文數據。

高效(Efficient),基於文件備份,只備份增量文件,自動去重,從而節省存儲空間。

restic配置以及倉庫初始化

restic可以從官網直接下載[2],下載後建議配置自動補全:

restic generate --bash-completion restic.bash_completion  
source restic.bash_completion

restic用法正如其設計原則,非常簡單。首先初始化備份倉庫,這裏我們使用開源的Minio對象存儲作爲備份倉庫,桶策略已提前配置好,Key和Secret通過環境變量進行配置:

export AWS_ACCESS_KEY_ID=93E0...2MV4K  
export AWS_SECRET_ACCESS_KEY=wulg1N...rXgGR

使用init子命令初始化倉庫,restic爲了安全性考慮,倉庫時需要指定密碼,密碼請務必記住,密碼丟了數據將無法恢復,這樣做是爲了防止備份倉庫的數據泄露導致業務數據泄露。

# restic -r s3:http://int32bit-minio-server/local-backup init  
enter password for new repository:  
enter password again:  
   
created restic repository 94c40f5300 at s3:http://int32bit-minio-server/local-backup

使用stats子命令查看倉庫狀態:

# restic -r s3:http://int32bit-minio-server/local-backup stats  
enter password for new repository:  
enter password again:  
repository 94c40f53 opened successfully, password is correct  
scanning...  
Stats in restore-size mode:  
Snapshots processed:   0  
         Total Size:   0 B

當前爲空倉庫,因此大小和快照數量均爲0。

爲了安全性考慮,每次對備份倉庫進行查看、備份、恢復等所有操作均需要輸入密碼,這在生產環境上是必須的,這裏爲了測試方便寫入環境變量中並指定倉庫地址:

export RESTIC_PASSWORD=*********  
export RESTIC_REPOSITORY=s3:http://int32bit-minio-server/local-backup

此時只需要直接運行restic stats即可查看倉庫信息,無需指定倉庫地址以及輸入密碼。

# restic stats  
scanning...  
Stats in restore-size mode:  
Snapshots processed:   0  
         Total Size:   0 B

執行備份

通過backup子命令執行備份操作:

# mkdir -p backup-demo  
# echo "hello" >backup-demo/hello.txt  
# restic backup backup-demo/  
no parent snapshot found, will read all files  
   
Files:           1 new,     0 changed,     0 unmodified  
Dirs:            1 new,     0 changed,     0 unmodified  
Added to the repo: 754 B  
   
processed 1 files, 6 B in 0:00  
snapshot 55572d0c saved

首次備份因爲沒有父備份點,因此爲全量備份,從備份中輸出中我們可以查看備份的文件數量以及大小。

我們寫入一個新文件並修改其中一個文件,再次執行備份操作:

# echo "new_file" >backup-demo/new_file.txt  
# echo "helloworld!" >backup-demo/hello.txt  
# restic backup backup-demo/  
using parent snapshot 55572d0c  
   
Files:           1 new,     1 changed,     0 unmodified  
Dirs:            0 new,     1 changed,     0 unmodified  
Added to the repo: 1.107 KiB  
   
processed 2 files, 21 B in 0:00  
snapshot f7d5b7c5 saved

可見當我們寫入一個新文件並且修改了原hello.txt文件,再次運行備份程序,此時默認爲增量備份,從備份結果中我們看到新增了1個文件、修改了一個文件。

備份時默認會備份指定目錄的所有文件,包含隱藏文件,可以通過指定--exclude參數排除需要備份的文件,也可以通過--file-from指定需要備份的文件列表。

另外可以每次備份時指定一個或者多個標籤,便於後期基於tag做快照檢索。

每次備份時都會創建一個snapshot快照實例,backup結果會輸出snapshot id,可以通過snapshots參數列舉該倉庫下的所有snapshots實例,當然也可以指定標籤過濾:

# restic snapshots  
ID        Time                 Host               Paths  
 --------------------------------------------------------------------  
55572d0c  2021-10-11 14:09:11  int32bit-test-1    /root/backup-demo  
f7d5b7c5  2021-10-11 14:12:39  int32bit-test-1    /root/backup-demo  
--------------------------------------------------------------------

通過diff參數查看兩個snapshots的差量:

# restic diff 55572d0c f7d5b7c5  
comparing snapshot 55572d0c to f7d5b7c5:  
   
M    /backup-demo/hello.txt  
+    /backup-demo/new_file.txt  
   
Files:           1 new,     0 removed,     1 changed  
Dirs:            0 new,     0 removed  
Others:          0 new,     0 removed  
Data Blobs:      2 new,     1 removed  
Tree Blobs:      2 new,     2 removed  
  Added:   1.107 KiB  
  Removed: 754 B

文件檢索以及查看文件內容

通過ls子命令可以查看指定快照中的所有文件列表:

# restic ls f18cccc5  
snapshot f18cccc5:  
/backup-demo  
/backup-demo/hello.txt  
/backup-demo/hello2.txt  
/backup-demo/new_file.txt  
# restic ls -l f18cccc5  
snapshot f18cccc5:  
drwxr-xr-x     0     0      0 2021-10-11 14:40:30 /backup-demo  
--w-r--r--     0     0     12 2021-10-11 14:12:20 /backup-demo/hello.txt  
-rw-r--r--     0     0      7 2021-10-11 14:40:30 /backup-demo/hello2.txt  
-rw-r--r--     0     0      9 2021-10-11 14:11:38 /backup-demo/new_file.txt

通過find命令從所有快照中查找文件,這個命令對於文件誤刪除後進行文件找回非常有用:

# restic find hello*  
Found matching entries in snapshot f18cccc5 from 2021-10-11 14:40:36  
/backup-demo/hello.txt  
/backup-demo/hello2.txt  
   
Found matching entries in snapshot 55572d0c from 2021-10-11 14:09:11  
/backup-demo/hello.txt  
   
Found matching entries in snapshot 7728a603 from 2021-10-11 14:23:47  
/backup-demo/hello.txt  
   
Found matching entries in snapshot f7d5b7c5 from 2021-10-11 14:12:39  
/backup-demo/hello.txt

通過dump命令可以查看指定快照指定文件的內容:

# restic dump f18cccc5 /backup-demo/hello2.txt  
hello2

更強大的是可以通過mount命令把整個快照內容掛載到本地:

# restic mount /mnt  
Now serving the repository at /mnt  
When finished, quit with Ctrl-c or umount the mountpoint.  
# mount | grep /mnt  
restic on /mnt type fuse (ro,nosuid,nodev,relatime,user_id=0,group_id=0)  
# cat /mnt/snapshots/latest/backup-demo/hello2.txt  
hello2  
# umount /mnt

如上把所有快照掛載到本地的/mnt目錄下,並查看了latest最新快照的hello2.txt的內容,最後卸載/mnt。

數據恢復

恢復文件也非常簡單,直接使用restore命令即可。

通過restore命令可恢復指定快照的文件到本地:

# restic restore f18cccc5 -t /tmp/restore_data  
restoring <Snapshot f18cccc5 of [/root/backup-demo] at 2021-10-11 14:40:36.459899498 +0800 CST by root@k8s-master-1> to /tmp/restore_data  
# find /tmp/restore_data/  
/tmp/restore_data/  
/tmp/restore_data/backup-demo  
/tmp/restore_data/backup-demo/new_file.txt  
/tmp/restore_data/backup-demo/hello.txt  
/tmp/restore_data/backup-demo/hello2.txt

當然也可以通過前面介紹的dump命令實現單個文件恢復:

restic dump f18cccc5 /backup-demo/hello2.txt >hello2.txt

備份刪除

通過forget可以刪除指定id的快照內容,當然我們實際使用更多的是按照時間或者快照數量進行快照保留或者刪除,比如保留前7天的快照,保留最新的3個快照等等。

我們可以通過--dry-run參數查看指定策略會刪除的快照,但實際不會執行刪除操作,用於檢驗參數是否符合預期。

如下我們執行只保留最新的3個快照:

# restic forget --keep-last=3 --dry-run  
Applying Policy: keep 3 latest snapshots  
keep 3 snapshots:  
ID        Time                 Host                      Tags           Reasons        Paths  
--------------------------------------------------------------------------------------------------------  
7728a603  2021-10-11 14:23:47  int32bit-test-1                 last snapshot  /root/backup-demo  
f18cccc5  2021-10-11 14:40:36  int32bit-test-1                 last snapshot  /root/backup-demo  
56e7b24f  2021-10-11 15:37:50  int32bit-test-1  app_name=test  last snapshot  /root/backup-demo  
--------------------------------------------------------------------------------------------------------  
3 snapshots  
   
remove 2 snapshots:  
ID        Time                 Host                      Tags        Paths  
--------------------------------------------------------------------------------------  
55572d0c  2021-10-11 14:09:11  int32bit-test-1              /root/backup-demo  
f7d5b7c5  2021-10-11 14:12:39  int32bit-test-1              /root/backup-demo  
--------------------------------------------------------------------------------------  
2 snapshots  
   
keep 1 snapshots:  
ID        Time                 Host                      Tags        Reasons        Paths  
--------------------------------------------------------------------------------------------  
112668f0  2021-10-11 15:28:22  int32bit-test-1              last snapshot  /recover  
--------------------------------------------------------------------------------------------  
1 snapshots  
   
Would have removed the following snapshots:  
{55572d0c f7d5b7c5}

其他刪除策略,比如保留前2個小時的最新備份:

# restic  forget --dry-run --keep-hourly 2  
Applying Policy: keep 2 hourly snapshots  
keep 2 snapshots:  
ID        Time                 Host                      Tags           Reasons          Paths  
----------------------------------------------------------------------------------------------------------  
56e7b24f  2021-10-11 15:37:50  int32bit-test-1  app_name=test  hourly snapshot  /root/backup-demo  
f949e14b  2021-10-11 16:11:44  int32bit-test-1                 hourly snapshot  /root/backup-demo  
----------------------------------------------------------------------------------------------------------  
2 snapshots  
   
remove 5 snapshots:  
ID        Time                 Host                      Tags        Paths  
--------------------------------------------------------------------------------------  
55572d0c  2021-10-11 14:09:11  int32bit-test-1              /root/backup-demo  
f7d5b7c5  2021-10-11 14:12:39  int32bit-test-1              /root/backup-demo  
7728a603  2021-10-11 14:23:47  int32bit-test-1              /root/backup-demo  
f18cccc5  2021-10-11 14:40:36  int32bit-test-1              /root/backup-demo  
ab268923  2021-10-11 16:01:04  int32bit-test-1              /root/backup-demo  
--------------------------------------------------------------------------------------  
5 snapshots  
   
Would have removed the following snapshots:  
{55572d0c 7728a603 ab268923 f18cccc5 f7d5b7c5}

如上只保留前2個小時的備份,注意由於14點以及16點均備份了多次,該策略只會保留以小時爲單位計算中最新的一份備份。

備份計劃

restic爲命令行CLI工具,不支持通過後臺服務形式運行,因此不支持備份計劃配置,但是很容易通過Linux自帶的crontab工具進行配置。

使用開源Velero工具實現Kubernetes應用備份容災

前面介紹了restic工具以及提到了Velero工具,它是一個雲原生的Kubernetes災難恢復和遷移工具,Velero[3]的前身是Heptio公司的Ark工具,後被VMware公司收購,底層數據卷的備份用的正是restic。

Kubernetes備份工具除了Velero,其實還有已被veeam收購的kasten以及專門做PV卷備份的Stash[4](底層用的也是restic)。

Velero配置

關於Velero的詳細配置和安裝方法可以參考官方文檔,這裏僅做簡要描述。

以Minio對象存儲爲備份目標端爲例,通過Velero客戶端生成yaml文件:

./velero install \  
    --provider aws \  
    --plugins xxx/velero-plugin-for-aws:v1.0.0 \  
    --bucket velero \  
    --secret-file ./aws-iam-creds \  
    --backup-location-config region=test,s3Url=http://192.168.0.1,s3ForcePathStyle="true" \  
    --snapshot-location-config region=test \  
    --image xxx/velero:v1.6.3 \  
    --features=EnableCSI \  
    --use-restic \  
    --dry-run -o yaml

其中:

--plugins以及--image參數指定鏡像倉庫地址,僅當使用私有鏡像倉庫時需要配置。

--use-restic參數開啓使用restic備份PV數據卷功能。

早期Kubernetes的volume卷不支持快照,因此備份PV卷時需要安裝特定的後端存儲卷插件,Kubernetes從v1.12開始CSI引入Snapshot後可以利用Snapshot特性實現備份,指定--features=EnableCSI參數開啓,開啓該模式的底層存儲必須支持snapshot,並且配置了snapshot相關的CRD以及volumesnapshotclass(類似storageclass)。

數據恢復需要依賴velero-restic-restore-helper工具,如果使用私有鏡像倉庫,可以通過restic configmap配置私有鏡像地址:

apiVersion: v1  
kind: ConfigMap  
metadata:  
  name: restic-config  
  namespace: velero  
  labels:  
    velero.io/plugin-config: ""  
    velero.io/restic: RestoreItemAction  
data:  
  image: xxx/velero-restic-restore-helper:v1.6.3

通過Velero執行備份

關於Velero的使用方法可以參考其他資料,這裏僅以帶PV的Nginx服務爲例闡述備份過程以及恢復原理,Nginx的yaml聲明文件內容如下:

# nginx-app-demo.yaml  
---  
apiVersion: v1  
kind: Namespace  
metadata:  
  name: nginx-app  
---  
apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: pvc-demo  
  namespace: nginx-app  
spec:  
  accessModes:  
    - ReadWriteOnce  
  resources:  
    requests:  
      storage: 1Gi  
  storageClassName: ceph-rbd-sata  
---  
apiVersion: apps/v1  
kind: Deployment  
metadata:  
  labels:  
    app: nginx  
  name: nginx  
  namespace: nginx-app  
spec:  
  replicas: 1  
  selector:  
    matchLabels:  
      app: nginx  
  template:  
    metadata:  
      labels:  
        app: nginx  
      annotations:  
        backup.velero.io/backup-volumes: mypvc  
    spec:  
      containers:  
      - image: nginx  
        name: nginx  
        volumeMounts:  
          - name: mypvc  
            mountPath: /usr/share/nginx/html  
      volumes:  
      - name: mypvc  
        persistentVolumeClaim:  
          claimName: pvc-demo  
          readOnly: false  
---  
apiVersion: v1  
kind: Service  
metadata:  
  labels:  
    app: nginx  
  name: nginx  
  namespace: nginx-app  
spec:  
  ports:  
  - port: 80  
    protocol: TCP  
    targetPort: 80  
  selector:  
    app: nginx

如上yaml僅需要關注如下兩點:

聲明瞭一個PVC,並掛載到Nginx Pod的/usr/share/nginx/html路徑。

Pod添加了註解backup.velero.io/backup-volumes: mypvc用於指定需要備份的Volume。因爲並不是所有的Volume都必須備份,實際生產中可根據數據的重要性設置合理的備份策略,因此不建議開啓--default-volumes-to-restic選項,該選項會默認備份所有的Volume。

我們進入Nginx中寫入測試數據:

# kubectl  exec -t -i nginx-86f99c968-sj8ds -- /bin/bash  
cd /usr/share/nginx/html/  
echo "HelloWorld" >index.html  
echo "hello1" >hello1.html  
echo "hello2" >hello2.html

此時我們訪問Nginx Service會輸出HelloWorld。

執行velero backup命令創建備份:

velero backup create nginx-backup-1 --include-namespaces nginx-app

查看備份信息:

# velero describe backups nginx-backup-1  
Name:         nginx-backup-1  
Namespace:    velero  
Labels:       velero.io/storage-location=default  
Phase:  Completed  
Namespaces:  
  Included:  nginx-app  
Storage Location:  default  
Velero-Native Snapshot PVs:  auto  
TTL:  720h0m0s  
Backup Format Version:  1.1.0  
Started:    2021-12-18 09:35:35 +0800 CST  
Completed:  2021-12-18 09:35:47 +0800 CST  
Expiration:  2022-01-17 09:35:35 +0800 CST  
Total items to be backed up:  22  
Items backed up:              22  
Restic Backups :  
  Completed:  1

從描述信息中有如下幾個值得關注的點:

備份狀態爲Completed,說明備份完成,記錄中會有備份開始時間和完成時間。

備份的資源數和完成數。

備份的Volume數(Restic Backups)。

備份數據管理以及遷移

在S3中可以查看備份的內容:

# aws s3 ls velero/backups/nginx-backup-1/  
2021-12-18 09:35:47         29 nginx-backup-1-csi-volumesnapshotcontents.json.gz  
2021-12-18 09:35:47         29 nginx-backup-1-csi-volumesnapshots.json.gz  
2021-12-18 09:35:47       4730 nginx-backup-1-logs.gz  
2021-12-18 09:35:47        936 nginx-backup-1-podvolumebackups.json.gz  
2021-12-18 09:35:47        372 nginx-backup-1-resource-list.json.gz  
2021-12-18 09:35:47         29 nginx-backup-1-volumesnapshots.json.gz  
2021-12-18 09:35:47      10391 nginx-backup-1.tar.gz  
2021-12-18 09:35:47       2171 velero-backup.json

當然也可以通過download把備份下載導出到本地:

# velero backup download nginx-backup-1  
Backup nginx-backup-1 has been successfully downloaded to /tmp/nginx-backup-1-data.tar.gz  
# mkdir -p nginx-backup-1  
# tar xvzf nginx-backup-1-data.tar.gz -C nginx-backup-1/  
# ls -l nginx-backup-1/resources/  
total 48  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 deployments.apps  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 endpoints  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 endpointslices.discovery.k8s.io  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 events  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 namespaces  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 persistentvolumeclaims  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 persistentvolumes  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 pods  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 replicasets.apps  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 secrets  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 serviceaccounts  
drwxr-xr-x 4 root root 4096 Dec 18 09:46 services

如上兩種方式都可以實現備份數據的導出遷移,但是需要注意的是,如上數據只包含Kubernetes聲明資源的yaml文件,不包含最重要的Volume業務數據。這些數據保存在S3的velero/restic/nginx-app/路徑下,而這些數據是加密存儲的。

從安全角度而言,這樣做是安全合理的。但是對於運維人員來說,這是黑盒子,我們如何確定Volume的數據完整備份了呢(Verifiable原則)。或者極端場景下,我的業務不跑容器了,想遷到物理機上本地直接運行,我的業務數據如何高效快速遷移。

辦法總是有的,通過Velero恢復到容器中,然後通過容器把數據遷走就可以了,但是這似乎有點麻煩,而且依賴於Velero。有沒有辦法直接通過restic工具進行備份數據的管理呢?

根據前面關於restic的介紹,這些數據是加密存儲的,那我們讀取數據就需要restic的倉庫密碼。

這個密碼其實存儲在velero-restic-credentials Secret中,任何有權限的管理員都可以讀取,因此這裏也特別需要注意控制velero的訪問權限。

# kubectl get secrets velero-restic-credentials \  
  -o jsonpath='{.data.repository-password}' | base64 -d

拿到了倉庫密碼,我們就能使用原生的restic工具對備份數據進行管理了。

首先查看snapshots列表。

# restic -r s3:http://192.168.0.1/velero/restic/nginx-app snapshots  

14fc2081 2021-12-18 09:35:45 ... # 輸出有點長,省去了後面的輸出內容

查看備份的文件:

# restic -r s3:http://192.168.0.1/velero/restic/nginx-app ls 14fc2081  
snapshot 14fc2081:  
/hello1.html  
/hello2.html  
/index.html

查看指定備份文件的內容:

# restic -r s3:http://192.168.0.1/velero/restic/nginx-app dump 14fc2081 /hello2.html  
hello2

通過restic工具,我們可以很輕易的進行備份數據管理以及數據遷移。

數據恢復

前面我們通過Velero備份了nginx-app namespace下的所有資源包括Volume數據。

現在我們把整個nginx-app刪除:
kubectl delete -f nginx-app-demo.yaml

該命令會把整個namespace的所有資源徹底刪除,包括PV數據卷的文件,在底層存儲中也會徹底把Volume刪除。

# kubectl get all -n nginx-app  
No resources found in nginx-app namespace.  
# kubectl get ns nginx-app  
Error from server (NotFound): namespaces "nginx-app" not found

從如上輸出結果看,數據已經完全刪除。

接着我們通過Velero執行數據恢復:

# velero restore create --from-backup nginx-backup-1   
Restore request "nginx-backup-1-20211218102506" submitted successfully.  
# velero restore get  
NAME                            BACKUP           STATUS  
nginx-backup-1-20211218102506   nginx-backup-1   InProgress  
# velero restore get  
NAME                            BACKUP           STATUS  
nginx-backup-1-20211218102506   nginx-backup-1   Completed

Velero恢復完成後,我們驗證Nginx應用是否完全恢復,首先查看Pod和Service:

# kubectl get pod -n nginx-app  
NAME                    READY   STATUS    RESTARTS   AGE  
nginx-86f99c968-8zh6m   1/1     Running   0          103s  
# kubectl get svc -n nginx-app  
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE  
nginx   ClusterIP   10.106.140.195   <none>        80/TCP    2m37

從輸出結果看,原來nginx-app namespace的資源均完全恢復並且處於運行狀態。接下來只需要檢查業務數據是否恢復:

# kubectl exec -t -i -n nginx-app nginx-86f99c968-8zh6m -- ls /usr/share/nginx/html/  
hello1.html  hello2.html  index.html  lost+found  
# kubectl get svc -n nginx-app  
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE  
nginx   ClusterIP   10.106.140.195   <none>        80/TCP    2m37s  
# curl  10.106.140.195  
HelloWorld

經驗證,業務數據是OK的,業務也正常恢復。

我們查看Pod聲明:

apiVersion: v1  
kind: Pod  
metadata:  
  annotations:  
    backup.velero.io/backup-volumes: mypvc  
  labels:  
    app: nginx  
    pod-template-hash: 86f99c968  
    velero.io/backup-name: nginx-backup-1  
    velero.io/restore-name: nginx-backup-1-20211218102506  
  name: nginx-86f99c968-8zh6m  
  namespace: nginx-app  
spec:  
  containers:  
  - image: nginx  
    name: nginx  
    volumeMounts:  
    - mountPath: /usr/share/nginx/html  
      name: mypvc  
  initContainers:  
  - args:  
    - ead72033-f495-4223-9358-6f97c920e9ae  
    command:  
    - /velero-restic-restore-helper  
    env:  
    - name: POD_NAMESPACE  
      valueFrom:  
        fieldRef:  
          apiVersion: v1  
          fieldPath: metadata.namespace  
    - name: POD_NAME  
      valueFrom:  
        fieldRef:  
          apiVersion: v1  
          fieldPath: metadata.name  
    image: velero-restic-restore-helper:v1.6.3  
    imagePullPolicy: IfNotPresent  
    name: restic-wait  
    volumeMounts:  
    - mountPath: /restores/mypvc  
      name: mypvc  
  volumes:  
  - name: mypvc  
    persistentVolumeClaim:  
      claimName: rbd-pvc-demo

yaml文件與之前初始化聲明的大體一樣,僅需留意如下兩點:

Pod增加Velero備份和恢復相關label。

嵌入了一個initContainer,通過velero-restic-restore-helper實現volume數據的恢復,該工具其實就是restic命令的包裝。

備份策略與計劃

前面提到restic本身是一個命令行CLI工具,不支持備份計劃任務。但是velero是支持備份計劃的,備份計劃支持如下配置:

備份時間,crontab語法。

備份保留時間,通過ttl指定,默認30天。

備份內容,支持指定namespace或者基於label指定具體的備份資源。

關於Velero備份計劃的管理,這裏不詳細介紹,感興趣的讀者可以參考官方文檔,也通過velero create schedule -h命令查看幫助文檔和樣例:

# Create a backup every 6 hours.  
velero create schedule NAME --schedule="0 */6 * * *"  
  
# Create a backup every 6 hours with the @every notation.  
velero create schedule NAME --schedule="@every 6h"  
  
# Create a daily backup of the web namespace.  
velero create schedule NAME --schedule="@every 24h" --include-namespaces web  
  
# Create a weekly backup, each living for 90 days (2160 hours).  
velero create schedule NAME --schedule="@every 168h" --ttl 2160h0m0s

Kubernetes企業備份容災方案

根據前面的介紹,可設計Kubernetes的粗略版備份容災方案:

其中:

minio爲開源的對象存儲,爲velero/restic提供備份存儲後端,實際生產時調整爲企業對象存儲系統。

遠端存儲爲異地存儲系統,比如異地磁帶庫、NBU,或者跨region的異地對象存儲系統。

備份流程:

Kubernetes的所有資源包括Pod、Deployment、ConfigMap、Secret、PV卷數據等通過Velero備份到對象存儲。

通過minio-sync實現實時同步數據到遠端同城異地存儲系統。

恢復流程:

場景一:集羣狀態無異常,人爲誤操作導致數據被刪。

直接通過Velero恢復指定時間的數據進行恢復即可。

場景二:PV底層的存儲系統crash導致數據丟失。

恢復存儲系統集羣或者極端情況下重搭存儲集羣,然後使用Velero從對象存儲中恢復數據。

場景三:極端場景下,整個數據中心或者region crash導致數據丟失。

重建環境,業務數據需要從異地數據中複製到本地,然後藉助velero從新建對象存儲中進行數據恢復。

場景四:Kubernetes環境遷移。

新建Kubernetes集羣,通過Velero指定備份點遷移數據到新環境中。

場景五:業務從Kubernetes運行遷移到虛擬機或者物理機運行。

通過Restic從對象存儲中把業務數據導出到虛擬機的數據卷中即可。

Kubernetes集羣備份

前面介紹了Kubernetes應用級別的備份方案,除了上層應用級別備份,集羣本身的備份也尤爲重要,Kubernetes幾乎所有的元數據均存儲在etcd中,因此集羣備份的核心就是etcd的備份,除此之外Kubernetes的證書、kubeadm的配置等也需要在備份策略中考慮到,在集羣恢復中不可或缺。

關於Kubernetes證書、kubeadm配置的備份可以直接使用前面介紹的restic工具對整個/etc/kubernetes目錄進行備份,而etcd的備份官方也有介紹backing-up-an-etcd-cluster[5]。

ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshotdb

比如備份etcd到Minio對象存儲中,參考腳本如下:

#!/bin/sh  
# bootstrap.sh  
export ETCDCTL_API=3  
  
MASTER_ENDPOINT=$(etcdctl --endpoints=$ETCD_ENDPOINTS \  
  --cacert=/etc/ssl/etcd/ca.crt \  
  --cert=/etc/ssl/etcd/etcd.crt \  
  --key=/etc/ssl/etcd/etcd.key \  
  endpoint status \  
  | awk -F ',' '{printf("%s %s\n", $1,$5)}' \  
  | tr -s ' ' |  awk '/true/{print $1}')  
  
echo "etcd master endpoint is ${MASTER_ENDPOINT}"  
  
BACKUP_FILE=etcd-backup-$(date +%Y%m%d%H%M%S).db  
  
etcdctl --endpoints=$MASTER_ENDPOINT \  
  --cacert=/etc/ssl/etcd/ca.crt \  
  --cert=/etc/ssl/etcd/etcd.crt \  
  --key=/etc/ssl/etcd/etcd.key \  
  snapshot save $BACKUP_FILE  
  
aws --endpoint $S3_ENDPOINT s3 cp $BACKUP_FILE s3://$BUCKET_NAME  
  
for f in $(aws --endpoint $S3_ENDPOINT \  
    s3 ls $BUCKET_NAME | head -n "-${KEEP_LAST_BACKUP_COUNT}" \  
    | awk '{print $4}'); do  
    aws --endpoint $S3_ENDPOINT s3 rm s3://$BUCKET_NAME/$f  
done

如上腳本首先獲取Master節點的endpoint,然後通過master endpoint創建etcd快照。快照生成後通過AWS S3命令拷貝到遠端對象存儲中,最後會刪除一些老的備份,只保留指定數量的備份數量。

可以把如上腳本bootstrap.sh做成Docker鏡像:

FROM python:alpine  
ARG ETCD_VERSION=v3.4.3  
RUN apk add --update --no-cache ca-certificates tzdata openssl  
RUN wget https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz \  
 && tar xzf etcd-${ETCD_VERSION}-linux-amd64.tar.gz \  
 && mv etcd-${ETCD_VERSION}-linux-amd64/etcdctl /usr/local/bin/etcdctl \  
 && rm -rf etcd-${ETCD_VERSION}-linux-amd64*  
RUN pip3 install awscli  
ENV ETCDCTL_API=3  
ADD bootstrap.sh /  
RUN chmod +x /bootstrap.sh  
CMD ["/bootstrap.sh"]

把etcd的證書以及Minio的AKSK存儲到Kubernetes Secret中:

#!/bin/bash  
kubectl create secret generic etcd-tls -o yaml \  
  --from-file /etc/kubernetes/pki/etcd/ca.crt \  
  --from-file /etc/kubernetes/pki/etcd/server.crt \  
  --from-file /etc/kubernetes/pki/etcd/server.key \  
  | sed 's/server/etcd/g'  
kubectl create secret generic s3-credentials \  
    -o yaml --from-file ~/.aws/credentials

通過Kubernetes自帶內置的CronJob實現定時備份:

apiVersion: batch/v1beta1  
kind: CronJob  
metadata:  
  name: etcd-backup  
  namespace: etcd-backup  
spec:  
  jobTemplate:  
    metadata:  
      name: etcd-backup  
    spec:  
      template:  
        spec:  
          containers:  
          - image: etcd-backup:v3.4.3  
            imagePullPolicy: IfNotPresent  
            name: etcd-backup  
            volumeMounts:  
            - name: s3-credentials  
              mountPath: /root/.aws  
            - name: etcd-tls  
              mountPath: /etc/ssl/etcd  
            - name: localtime  
              mountPath: /etc/localtime  
              readOnly: true  
            env:  
            - name: ETCD_ENDPOINTS  
              value: "192.168.1.1:2379,192.168.1.2:2379,192.168.1.3:2379"  
            - name: BUCKET_NAME  
              value: etcd-backup  
            - name: S3_ENDPOINT  
              value: "http://192.168.1.53"  
            - name: KEEP_LAST_BACKUP_COUNT  
              value: "7"  
          volumes:  
          - name: s3-credentials  
            secret:  
              secretName: s3-credentials  
          - name: etcd-tls  
            secret:  
              secretName: etcd-tls  
          - name: localtime  
            hostPath:  
              path: /etc/localtime  
          restartPolicy: OnFailure  
  schedule: '0 0 * * *'

如上CronJob配置每天0點對etcd進行備份到Minio對象存儲中。

總結

本文首先介紹了Kubernetes備份的思路以及開源restic工具。然後介紹了使用開源Velero工具實現Kubernetes應用級別備份容災方案,重點介紹了PV卷業務數據的備份和恢復過程。最後介紹了通過etcd備份實現Kubernetes集羣級別的備份容災。

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