Deep dive into the backup tool velero
2022-06-24
1 What is Velero?
1.1 简介
Velero(原名 Heptio Ark)提供一个备份Kubernetes集群内资源以及持久化存储数据并且能够对它们进行还原操作的工具。用户可以在公有云以及本地环境运行Velero。Velero提供如下的功能:
- 备份和还原集群数据以免数据丢失;
- 迁移集群资源到另一个集群;
- 复制生产集群到测试集群等其他集群。
Velero运行包括:
- 运行在集群内的Server;
- 运行在本地的命令行客户端。
1.2 功能
1.2.1 备份
- 对集群数据进行备份,对满足过滤条件的所有集群kubernetes资源进行获取并归档打包。包上传至云端OSS。
- 对持久化数据进行快照备份,调用云端OSS的API来操作云盘控制台,对持久化磁盘对应的云盘进行快照制作。
- 对持久化数据进行磁盘备份,通过restic检索对应持久化磁盘的数据,并将这些数据通过restic的备份逻辑上传到云端OSS。
- 在不同集群之间,通过自动同步云端备份记录来提供集群间的备份数据一致性。
- 根据备份记录的生命周期对过期的备份记录进行回收。
1.2.2 还原
- 对集群数据进行还原,对备份记录中满足过滤条件的所有集群kubernetes资源进行获取并还原到待还原集群中。
- 对持久化数据进行还原,在备份记录中对所有曾做过持久化数据备份的持久化磁盘进行数据的写入。
2 Why Velero?
- 优势1:开源
对于kubernetes集群内资源备份其实已经存在有比如:Rancher,Kasten K10,Portworx PX-Backup,TrilioVault这些由大厂商开发出来的优质产品。这些产品都提供了非常全面的有关kubernetes集群备份还原的功能,但是它们都是收费的。而Velero是开源,免费的。
- 优势2:能对持久化磁盘内数据进行备份
在开源优势的前提下,还存在KubeDR工具能够对kubernetes资源进行备份。KubeDR是Catalogic Software公司开源的一款kubernetes备份工具。该工具能提供对kubernetes集群内资源的备份,但不能提供对持久化磁盘数据的备份。而Velero提供对磁盘进行快照或者对磁盘数据进行备份的功能。
- 优势3:全方位覆盖备份还原流程
对于kubernetes资源备份工作,或许有这样的疑问:“我通过dynamic client获取集群内的所有资源数据不是也能实现吗?持久化数据我也可以通过脚本的方式定时拷贝,这有什么不足呢?”
其实在kubernetes数据备份还原步骤中,除了对数据的备份操作需要完成之外,怎么删除相关的数据,怎么对数据进行生命周期的设置,怎么能通过特定的需求自定义一个备份任务等因素都是需要考虑到整个流程中的。因此,使用标准化的Velero能节省极大一部分开发时间。
3 How does Velero Work?
3.1 备份流程
- 使用者通过velero命令行创建备份任务。这个步骤会生成一个Backup的CRD资源。
- Velero Server的BackupController通过与Api Server通信实时list-watch Backup资源。
- Velero Server的BackupController从etcd中获取kubernetes集群内的所有资源。
- Velero Server通过gRPC调用本地plugin将kubernetes集群内资源打成的tarball上传到对应oss。
- (optional)Velero Server通过gRPC调用本地plugin对相关磁盘做磁盘快照。
3.2 基本概念
3.2.1 Provider
Velero 通过provider字段来识别自己使用的plugin是哪个厂商的。通常provider和plugin是匹配的。Velero支持多种存储provider,并可以通过他们提供的plugin来对备份数据以及磁盘快照进行相应操作。
目前比较常用的provider包括有:Amazon Web Services (AWS),Google Cloud Platform (GCP),Microsoft Azure,AlibabaCloud。
3.2.2 Plugin
Velero通过使用provider提供的plugin对备份数据以及磁盘快照进行相应操作。通常,一个plugin包括如下但不一定都需要包含如下接口的实现:
const (
// PluginKindObjectStore represents an object store plugin.
PluginKindObjectStore PluginKind = "ObjectStore"
// PluginKindVolumeSnapshotter represents a volume snapshotter plugin.
PluginKindVolumeSnapshotter PluginKind = "VolumeSnapshotter"
// PluginKindBackupItemAction represents a backup item action plugin.
PluginKindBackupItemAction PluginKind = "BackupItemAction"
// PluginKindRestoreItemAction represents a restore item action plugin.
PluginKindRestoreItemAction PluginKind = "RestoreItemAction"
// PluginKindDeleteItemAction represents a delete item action plugin.
PluginKindDeleteItemAction PluginKind = "DeleteItemAction"
// PluginKindItemSnapshotter represents an item snapshotter plugin
PluginKindItemSnapshotter PluginKind = "ItemSnapshotter"
// PluginKindPluginLister represents a plugin lister plugin.
PluginKindPluginLister PluginKind = "PluginLister"
)
plugin和provider的在代码中以如下的形式对应起来:
// register registers a PluginIdentifier with the registry.
func (r *registry) register(id framework.PluginIdentifier) error {
key := kindAndName{kind: id.Kind, name: id.Name}
if existing, found := r.pluginsByID[key]; found {
return newDuplicatePluginRegistrationError(existing, id)
}
// no need to pass list of existing plugins since the check if this exists was done above
if err := framework.ValidatePluginName(id.Name, nil); err != nil {
return errors.Errorf("invalid plugin name %q: %s", id.Name, err)
}
r.pluginsByID[key] = id
r.pluginsByKind[id.Kind] = append(r.pluginsByKind[id.Kind], id)
return nil
}
例如现在有一个backup storage location中提供的provider名称为:csdz.io/local。它提供
ObjectStore的plugin。那么这个provider在程序中的存在形式如下:
id := framework.PluginIdentifier{
Command: "local",
Kind: "ObjectStore",
Name: "csdz.io/local",
}
key := kindAndName{kind: "ObjectStore", name: "csdz.io/local"}
r.pluginsByID[key] = id
r.pluginByKind["ObjectStore"]= append(r.pluginsByKind["ObjectStore"], id)
在备份时,根据创建备份任务时指定的backup storage location来确定需要使用哪个provider对数据进行上传。
3.2.3 Backup Storage Location
Backup Storage Location(bsl),即备份存储位置。决定了备份任务所产生的相关备份数据,持久化磁盘内的备份数据,以及磁盘快照的制作通过哪一个provider提供的plugin完成。通过命令可以对当前集群内使用的bsl进行查看:velero get backup-locations
或者kubectl get bsl -A
[root@master-1 ~]# velero get backup-locations
NAME PROVIDER BUCKET/PREFIX PHASE LAST VALIDATED ACCESS MODE DEFAULT
default aws velero Available 2022-06-23 11:23:38 +0800 CST ReadWrite true
local csdz.io/local velero Available 2022-06-23 11:23:38 +0800 CST ReadWrite
# aws plugin
spec:
config:
region: minio
s3ForcePathStyle: "true"
s3Url: http://minio-service.backend.svc.cluster.local
default: true
objectStorage:
bucket: velero
provider: aws
# local plugin
spec:
accessMode: ReadWrite
config:
path: /mnt/k8s/backups/bak1#/mnt/k8s/backups/bak2
objectStorage:
bucket: velero
provider: csdz.io/local
3.2.4 Restic
Restic是一款Go语言开发的开源免费且提供了快速 高效和安全备份功能的跨平台备份工具。Restic使用加密技术保证数据安全性和完整性,并可以将本地数据加密后上传到指定的存储位置。
3.2.5 部署基础架构
Velero的部署最多分为三个部分:命令行 服务端和Restic服务端。其中,命令行和服务端是必须的,命令行工具可以接入Kubernetes集群,通过命令生成CRD资源。服务端在集群中以Deployment资源存在,其实质也是通过命令启动服务,命令为velero server --features=
。服务端通过list-watch相关CRD资源对备份还原操作进行处理。
另外一部分为Restic服务端,这一部分是可选项。当备份还原操作需要用到Restic来对持久化磁盘做数据备份的时候,则需要部署Restic Server。Restic Server在集群中以Daemonset资源形式存在,其启动也是通过命令
velero restic server
实现的。Restic Server会list-watch需要对节点上相应pod的持久化磁盘做数据备份处理的CRD资源并通过执行restic命令来实现备份还原的操作。
3.3 相关技术
3.3.1 Cobra Framework
Velero 通过Cobra开源项目来搭建其命令行工具。
func NewCommand(name string) *cobra.Command {
// Load the config here so that we can extract features from it.
config, err := client.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}
// Declare cmdFeatures and cmdColorzied here so we can access them in the PreRun hooks
// without doing a chain of calls into the command's FlagSet
var cmdFeatures veleroflag.StringArray
var cmdColorzied veleroflag.OptionalBool
c := &cobra.Command{
Use: name,
Short: "Back up and restore Kubernetes cluster resources.",
Long: `Velero is a tool for managing disaster recovery, specifically for Kubernetes
cluster resources. It provides a simple, configurable, and operationally robust
way to back up your application state and associated data.
If you're familiar with kubectl, Velero supports a similar model, allowing you to
execute commands such as 'velero get backup' and 'velero create schedule'. The same
operations can also be performed as 'velero backup get' and 'velero schedule create'.`,
// PersistentPreRun will run before all subcommands EXCEPT in the following conditions:
// - a subcommand defines its own PersistentPreRun function
// - the command is run without arguments or with --help and only prints the usage info
PersistentPreRun: func(cmd *cobra.Command, args []string) {
features.Enable(config.Features()...)
features.Enable(cmdFeatures...)
switch {
case cmdColorzied.Value != nil:
color.NoColor = !*cmdColorzied.Value
default:
color.NoColor = !config.Colorized()
}
},
}
f := client.NewFactory(name, config)
f.BindFlags(c.PersistentFlags())
// Bind features directly to the root command so it's available to all callers.
c.PersistentFlags().Var(&cmdFeatures, "features", "Comma-separated list of features to enable for this Velero process. Combines with values from $HOME/.config/velero/config.json if present")
// Color will be enabled or disabled for all subcommands
c.PersistentFlags().Var(&cmdColorzied, "colorized", "Show colored output in TTY. Overrides 'colorized' value from $HOME/.config/velero/config.json if present. Enabled by default")
c.AddCommand(
backup.NewCommand(f),
schedule.NewCommand(f),
restore.NewCommand(f),
server.NewCommand(f),
version.NewCommand(f),
get.NewCommand(f),
install.NewCommand(f),
uninstall.NewCommand(f),
describe.NewCommand(f),
create.NewCommand(f),
runplugin.NewCommand(f),
plugin.NewCommand(f),
delete.NewCommand(f),
cliclient.NewCommand(),
completion.NewCommand(),
restic.NewCommand(f),
bug.NewCommand(),
backuplocation.NewCommand(f),
snapshotlocation.NewCommand(f),
debug.NewCommand(f),
)
// init and add the klog flags
klog.InitFlags(flag.CommandLine)
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
return c
}
3.3.2 Hashicorp Go-Plugin
Plugin,即插件。一般是指作为主程序的扩展程序存在的功能拓展程序。对于主程序的一个接口,不同的插件可以实现不同的处理逻辑,只需要实现相关接口就可以。
Go语言自带有一个plugin机制,以下面的代码为例:
var Name = "Plugin"
func GetName() string {
return Name
}
对上述插件代码做编译go build -buildmode=plugin,会得到一个.so文件。
func main() {
// 打开加载插件,参数是插件的存储位置,可以是相对路径
open, err := plugin.Open("./test.so")
if err != nil {
panic(err)
}
// 查找标识符
lookup, err := open.Lookup("GetName")
if err != nil {
panic(err)
}
res := lookup.(func() string)()
fmt.Printf("%v\n", res)
name, err := open.Lookup("Name")
if err != nil {
panic(err)
}
fmt.Printf("%v\n", *name.(*string))
}
通过上面的方式就可实现Go的动态链接,在主程序里面直接访问插件的相关变量和函数。
但是使用Go自带的Plugin功能并不成熟,有很多缺点,例如:
- 主程序和插件程序编译的GO版本必须一致;
- 主程序和插件程序以来的第三方库版本必须完全一致;
- 插件加载后不能卸载。
鉴于有上述缺点,因此go自带plugin功能实际运用场景很少。大多数情况下使用hashicorp开源的go-plugin来实现go的插件功能。
Hashicorp公司的go-plugin是通过多进程间的RPC通信实现的。这种方式将主程序调用插件程序方法的过程实现为同一台主机上的主程序进程和插件程序进程之间的通信,网络协议可以是Go标准库的net/rpc或gRPC。Velero主程序与插件程序之间采用gRPC的方式通信。主程序在需要上传备份文件或者创建磁盘快照或者上传持久化磁盘数据时调用插件程序的相应接口。
3.3.3 Dynamic Client
Kubernetes提供的client-go库中提供了许多对kubernetes数据处理的工具。其中,在创建client的时候最常用的还是ClientSet的方式,这种方式可以直接对应到集群内最常用的资源,比如deployment,statefulset等。但是,velero在处理kubernetes集群备份时不仅仅是对这些常用的资源进行备份,有许多crd资源,他们并不是kubernetes自身就提供的,例如属于monitoring.coreos.com组内的prometheuses资源。对于这些没有预期的资源,kubernetes提供了一个dynamic client来处理。
首先可以看一下ClientSet的数据结构定义:
type Clientset struct {
*discovery.DiscoveryClient
admissionregistrationV1 *admissionregistrationv1.AdmissionregistrationV1Client
admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
internalV1alpha1 *internalv1alpha1.InternalV1alpha1Client
appsV1 *appsv1.AppsV1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
authenticationV1beta1 *authenticationv1beta1.AuthenticationV1beta1Client
authorizationV1 *authorizationv1.AuthorizationV1Client
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
autoscalingV1 *autoscalingv1.AutoscalingV1Client
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client
batchV1 *batchv1.BatchV1Client
batchV1beta1 *batchv1beta1.BatchV1beta1Client
certificatesV1 *certificatesv1.CertificatesV1Client
certificatesV1beta1 *certificatesv1beta1.CertificatesV1beta1Client
coordinationV1beta1 *coordinationv1beta1.CoordinationV1beta1Client
coordinationV1 *coordinationv1.CoordinationV1Client
coreV1 *corev1.CoreV1Client
discoveryV1 *discoveryv1.DiscoveryV1Client
discoveryV1beta1 *discoveryv1beta1.DiscoveryV1beta1Client
eventsV1 *eventsv1.EventsV1Client
eventsV1beta1 *eventsv1beta1.EventsV1beta1Client
extensionsV1beta1 *extensionsv1beta1.ExtensionsV1beta1Client
flowcontrolV1alpha1 *flowcontrolv1alpha1.FlowcontrolV1alpha1Client
flowcontrolV1beta1 *flowcontrolv1beta1.FlowcontrolV1beta1Client
networkingV1 *networkingv1.NetworkingV1Client
networkingV1beta1 *networkingv1beta1.NetworkingV1beta1Client
nodeV1 *nodev1.NodeV1Client
nodeV1alpha1 *nodev1alpha1.NodeV1alpha1Client
nodeV1beta1 *nodev1beta1.NodeV1beta1Client
policyV1 *policyv1.PolicyV1Client
policyV1beta1 *policyv1beta1.PolicyV1beta1Client
rbacV1 *rbacv1.RbacV1Client
rbacV1beta1 *rbacv1beta1.RbacV1beta1Client
rbacV1alpha1 *rbacv1alpha1.RbacV1alpha1Client
schedulingV1alpha1 *schedulingv1alpha1.SchedulingV1alpha1Client
schedulingV1beta1 *schedulingv1beta1.SchedulingV1beta1Client
schedulingV1 *schedulingv1.SchedulingV1Client
storageV1beta1 *storagev1beta1.StorageV1beta1Client
storageV1 *storagev1.StorageV1Client
storageV1alpha1 *storagev1alpha1.StorageV1alpha1Client
}
可以看到Clientset为kubernetes自带的资源各自创建了相关的访问接口。而dynamic client的数据结构很简单,只有一个restClient字段。
type dynamicClient struct {
client *rest.RESTClient
}
而区别dynamic client和clientset的关键在于其关联的方法,dynamic client只有一个关联方法:
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
上述关联方法只做了一件事情:返回一个dynamicResourceClient结构体,所以关键点肯定是这个结构体下的所有方法。可以看到这个结构体下有如下一些方法:
不难看出,这些方法涵盖了对kubernetes资源的所有操作。那么,为什么dynamicClient可以通过如此少的代码内容对所有kubernetes资源进行解析和编码呢?奥秘在于Unstructured类型的数据结构。定义如下:
type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}
Unstructured数据可以无视value的类型,从而可以获取到非预期的kubernetes资源。那么,unstructured是怎么转换成对应的数据结构的呢?代码如下:
func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error {
t := reflect.TypeOf(obj)
value := reflect.ValueOf(obj)
if t.Kind() != reflect.Ptr || value.IsNil() {
return fmt.Errorf("FromUnstructured requires a non-nil pointer to an object, got %v", t)
}
// Critical Part: fromUnstructured
err := fromUnstructured(reflect.ValueOf(u), value.Elem())
if c.mismatchDetection {
newObj := reflect.New(t.Elem()).Interface()
newErr := fromUnstructuredViaJSON(u, newObj)
if (err != nil) != (newErr != nil) {
klog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err)
}
if err == nil && !c.comparison.DeepEqual(obj, newObj) {
klog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj)
}
}
return err
}
3.3.4 Restic
Velero在对持久化磁盘数据做压缩备份的时候,使用的是Restic开源工具。Restic提供数据的增量备份,通过文件的SHA-256哈希值来判断文件在新的备份和老的备份之间有没有进行修改,如果没有修改则不备份旧的数据。如下有一些基础概念:
- snapshot:快照表示在特定时间点包含所有文件和子目录的目录。 对于所做的每个备份,都会创建一个新快照。snapshot会包含tree字段。
- blob:实际的数据存储信息。分为两类:tree是目录,data是文件。
- pack:一个pack包含多个blob。
- index:index包含有关数据blob和pack之间的信息以及它们之间拼接的方式。
所有由restic备份后的数据都是用计数器模式AES-256对数据进行加密,并使用Poly305-AES对数据进行鉴权。同时对数据的前后都封装一层加密信息,以提供文件最高的安全性。
参考链接:https://blog.csdn.net/weixin_54323634/article/details/120260066
3.4 Controller 工作流程
velero通过命令行创建crd资源 --> server处理资源
的方式来实现备份和还原的功能,这种方式也称为operator的方式,即controller+crd的方式。velero在工作中主要涉及如下的一些crd资源。
NAME SHORTNAMES APIGROUP NAMESPACED KIND
backups velero.io true Backup
backupstoragelocations bsl velero.io true BackupStorageLocation
deletebackuprequests velero.io true DeleteBackupRequest
downloadrequests velero.io true DownloadRequest
podvolumebackups velero.io true PodVolumeBackup
podvolumerestores velero.io true PodVolumeRestore
resticrepositories velero.io true ResticRepository
restores velero.io true Restore
schedules velero.io true Schedule
serverstatusrequests ssr velero.io true ServerStatusRequest
volumesnapshotlocations velero.io true VolumeSnapshotLocation
针对这些crd资源相应地提供了如下的controllers:
- velero server:
// 通过informer的list-watch机制来对crd资源进行处理
enabledControllers := map[string]func() controllerRunInfo{
controller.BackupSync: backupSyncControllerRunInfo,
controller.Backup: backupControllerRunInfo,
controller.Schedule: scheduleControllerRunInfo,
controller.GarbageCollection: gcControllerRunInfo,
controller.BackupDeletion: deletionControllerRunInfo,
controller.Restore: restoreControllerRunInfo,
controller.ResticRepo: resticRepoControllerRunInfo,
}
// 通过manager对crd资源进行处理
bslr := controller.BackupStorageLocationReconciler{
Ctx: s.ctx,
Client: s.mgr.GetClient(),
Scheme: s.mgr.GetScheme(),
DefaultBackupLocationInfo: storage.DefaultBackupLocationInfo{
StorageLocation: s.config.defaultBackupLocation,
ServerValidationFrequency: s.config.storeValidationFrequency,
},
NewPluginManager: newPluginManager,
BackupStoreGetter: backupStoreGetter,
Log: s.logger,
}
if err := bslr.SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", controller.BackupStorageLocation)
}
if _, ok := enabledRuntimeControllers[controller.ServerStatusRequest]; ok {
r := controller.ServerStatusRequestReconciler{
Scheme: s.mgr.GetScheme(),
Client: s.mgr.GetClient(),
Ctx: s.ctx,
PluginRegistry: s.pluginRegistry,
Clock: clock.RealClock{},
Log: s.logger,
}
if err := r.SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", controller.ServerStatusRequest)
}
}
if _, ok := enabledRuntimeControllers[controller.DownloadRequest]; ok {
r := controller.DownloadRequestReconciler{
Scheme: s.mgr.GetScheme(),
Client: s.mgr.GetClient(),
Clock: clock.RealClock{},
NewPluginManager: newPluginManager,
BackupStoreGetter: backupStoreGetter,
Log: s.logger,
}
if err := r.SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", controller.DownloadRequest)
}
}
- restic server:
backupController := controller.NewPodVolumeBackupController(
s.logger,
s.veleroInformerFactory.Velero().V1().PodVolumeBackups(),
s.veleroClient.VeleroV1(),
s.podInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.metrics,
s.mgr.GetClient(),
os.Getenv("NODE_NAME"),
credentialFileStore,
)
restoreController := controller.NewPodVolumeRestoreController(
s.logger,
s.veleroInformerFactory.Velero().V1().PodVolumeRestores(),
s.veleroClient.VeleroV1(),
s.podInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.mgr.GetClient(),
os.Getenv("NODE_NAME"),
credentialFileStore,
)
3.4.1 BackupSyncController
BackupSyncController
会定时对集群内的Backup
资源进行同步。
func NewBackupSyncController(...) Interface{
c.resyncFunc = c.run
c.resyncPeriod = 30 * time.Second
return c
}
func (c *backupSyncController) run() {
c.logger.Debug("Checking for existing backup storage locations to sync into cluster")
locationList, err := storage.ListBackupStorageLocations(context.Background(), c.kbClient, c.namespace)
...
}
同步机制首先通过plugin获取到所有的备份记录,然后通过与现在集群内的Backup
资源对比,生成现在集群内没有的Backup
资源。但是,不会删除多余的Backup
资源。
3.4.2 BackupController
BackupController
会list-watch集群内的Backup
资源,当检测到有新增资源的时候,对这个备份任务进行处理。
在备份过程中,如果检测到某pod磁盘需要制作快照,也会通过调用plugin来执行。
3.4.3 PodVolumeBackupController
PodVolumeBackupController
会list-watch集群内的PodVolumeBackup
资源,当检测到有新增资源的时候,根据其配置找到pv对应在宿主机磁盘上的存储位置,并执行restic命令对这个pv的数据进行备份,并根据相应命名空间下的ResticRepository
来获取上传路径。
如果备份任务采用restic对持久化磁盘数据进行压缩备份的话,则会转用restic的方式对pv做处理,而不是对磁盘做快照。需要注意的是,如果bsl是一个新的记录,则还需要创建相应bsl的ResticRepository
资源,提供给restic上传数据的路径。同时,会生成PodVolumeBackup
资源,等待restic server的
PodVolumeBackupController
来处理这个备份任务。
3.4.4 BackupDeletionController
BackupDeletionController
会list-watch集群内新创建的DeleteBackupRequest
资源。当检测到有新增资源时,对需要删除的Backup
资源执行删除操作。包括其在云端的备份数据,以及持久化磁盘的备份数据,以及持久化磁盘的快照,以及通过该备份任务生成的所有Restore
资源。
3.4.5 ScheduleController
ScheduleController
会list-watch集群内新创建的Schedule
资源,同时,会定时去检查集群内的
Schedule
资源是否满足了运行的条件,如果满足,则创建Backup
资源。
func getNextRunTime(schedule *api.Schedule, cronSchedule cron.Schedule, asOf time.Time) (bool, time.Time) {
var lastBackupTime time.Time
if schedule.Status.LastBackup != nil {
lastBackupTime = schedule.Status.LastBackup.Time
} else {
lastBackupTime = schedule.CreationTimestamp.Time
}
nextRunTime := cronSchedule.Next(lastBackupTime)
return asOf.After(nextRunTime), nextRunTime
}
3.4.6 GarbageCollectionController
GarbageCollectionControlle
会list-watch集群内新创建的以及更新了的Backup
资源。并定时去检查集群内Backup
资源的有效期。如果过期则会生成一个DeleteBackupRequest
资源,通过
BackupDeletionController
对过期的Backup
资源作删除操作。
if backup.Status.Expiration == nil || backup.Status.Expiration.After(now) {
log.Debug("Backup has not expired yet, skipping")
return nil
}
log.Info("Backup has expired")
3.4.7 RestoreController
RestoreController
会list-watch集群内的Restore
资源,当检测到有新增资源的时候,对这个还原任务进行处理。
在还原过程中,如果检测到某pod磁盘曾经制作过快照,也会通过调用plugin来执行还原操作。
3.4.8 PodVolumeRestoreController
PodVolumeRestoreController
会list-watch集群内的PodVolumeRestore
资源,当检测到有新增资源的时候,根据其配置找到pv对应在宿主机磁盘上的存储位置,并执行restic命令对这个pv的数据进行还原,并根据相应命名空间下的ResticRepository
来执行下载路径。
如果还原任务检测到pod曾采用restic对持久化磁盘数据进行过压缩备份的话,则会转用restic的方式对pv做还原处理,而不是还原磁盘快照。需要注意的是,如果bsl是一个新的记录,则还需要创建相应bsl的
ResticRepository
资源,提供给restic下载备份数据的路径。同时,会生成PodVolumeRestore
资源,等待restic server的PodVolumeRestoreController
来处理这个备份任务。
3.4.9 ResticRepositoryController
ResticRepositoryController
会list-watch集群内的ResticRepository
资源,当检测到有新增资源的时候,对相关restic repository进行相关处理。主要处理代码如下:
repoIdentifier, err := restic.GetRepoIdentifier(loc, req.Spec.VolumeNamespace)
简单来说,会对每个namespace创建不同的repoIdentifier,其实就是规定了restic上传和下载备份数据的路径。通过拼接bsl的bucket路径和namespace,就可以得到restic的上传和下载路径。
4 Velero安装
4.1 下载velero二进制文件
wget https://csdz-software-package.oss-cn-hangzhou.aliyuncs.com/velero/velero_v1.8.1_hostPath_linux
mv velero_v1.8.1_hostPath_linux velero && chmod 777 velero && cp velero /usr/bin/velero
4.2 创建bsl证书文件
类似alibaba provider和aws provider在访问的时候都是需要证书的。因此使用这类provider的时候需要创建响应的证书文件,如果bsl不需要证书,则可以略过此步骤。
cat >> /root/velero/minio-credential << EOF
[default]
aws_access_key_id = minio
aws_secret_access_key = minio123
EOF
4.3 安装velero
velero install \
--provider=aws \
--plugins=registry.cn-hangzhou.aliyuncs.com/csdz/velero:velero-plugin-for-aws_v1_2_1 \
--bucket=velero \
--image=registry.cn-hangzhou.aliyuncs.com/csdz/velero:v1.8.1_hostpath_8000_root \
--backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.backend.svc:9000 \
--use-restic \
--secret-file=./minio-credential \
--restic-pod-mem-limit="0" \
--restic-pod-cpu-limit="0" \
--wait
参数说明:
--provider:集群内默认的backup storage location对应的provider;
--plugins:provider对应plugin镜像;
--bucket: provider提供object storage service对应的bucket名称,velero备份数据存在相关bucket下;
--image:velero server对应的镜像;
--backup-location-config:backup storage location对应的配置文件;
--use-restic:如果对集群内持久化磁盘做备份的方式不通过磁盘快照的方式实现,而是对数据进行压缩备份处理,那么可以添加该选项;如果不需要,则去掉该选项;
--secret-file:3.3.2步骤写入的证书文件,如果不使用证书,则换成--no-secret选项;
--restic-pod-mem-limit:restic pod的memory limit值为0;
--restic-pod-cpu-limit:restic pod的cpu limit值为0;
--wait:安装过程中等待上一次任务完成再执行下一次任务。
4.4 验证
velero安装后,会在命名空间velero中生成velero server的deployment资源和restic的daemonset资源(如果启用了--use-restic选项),可以通过下面的方式验证安装是否成功:
# 1. 验证velero deployment和restic daemonset是否都启动成功,STATUS字段为Running即可。
kubectl get pod -n velero
# 2. 验证 backup-location 成功生效,PHASE字段为Available即可。
velero backup-location get
# 3. 如果backup-location本身就有backup的记录,可以检查backup是否同步成功。
velero backup get