前言
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集羣級別的備份容災。