问题描述
我们在使用velero插件的时候会把所有的cloud provider 插件都安装到velero中,然后我们执行velero plugin get
就会得到这样的结果:
NAME KIND
velero.io/crd-remap-version BackupItemAction
velero.io/pod BackupItemAction
velero.io/pv BackupItemAction
velero.io/service-account BackupItemAction
community.openstack.org/openstack ObjectStore
velero.io/alibabacloud ObjectStore
velero.io/aws ObjectStore
velero.io/azure ObjectStore
velero.io/gcp ObjectStore
velero.io/add-pv-from-pvc RestoreItemAction
velero.io/add-pvc-from-pod RestoreItemAction
velero.io/alibabacloud RestoreItemAction
velero.io/apiservice RestoreItemAction
velero.io/change-pvc-node-selector RestoreItemAction
velero.io/change-storage-class RestoreItemAction
velero.io/cluster-role-bindings RestoreItemAction
velero.io/crd-preserve-fields RestoreItemAction
velero.io/init-restore-hook RestoreItemAction
velero.io/job RestoreItemAction
velero.io/pod RestoreItemAction
velero.io/restic RestoreItemAction
velero.io/role-bindings RestoreItemAction
velero.io/service RestoreItemAction
velero.io/service-account RestoreItemAction
community.openstack.org/openstack VolumeSnapshotter
velero.io/alibabacloud VolumeSnapshotter
velero.io/aws VolumeSnapshotter
velero.io/azure VolumeSnapshotter
velero.io/gcp VolumeSnapshotter
但是在我们实际使用过程中,会发现我们在aws上的cluster中恢复的PV,原本的容量小于20Gi的PV恢复回来都变成了20Gi,并且都被打上了velero.io/alibabacloud-restore-plugin=1
这样的annotation。
这个问题一直让我们很困惑,为什么阿里云的插件,会在aws以及其他所有的IAAS上运行?在安装了多个IAAS的插件时,velero如何保证正确的插件被使用?
于是在这个论坛的Velero的插件框架 这篇文章下留言问了文章的作者,通过作者的帮助,我发现了这个问题的原因。
原因分析
velero 的插件一般只会实现VolumeSnapshotter
和 ObjectStore
这两个类型,velero在执行getObjectStore这个方法的时候,会根据Backup Storage Location 里定义的provider 来得到对应provider的插件:
// add the bucket name and prefix to the config map so that object stores
// can use them when initializing. The AWS object store uses the bucket
// name to determine the bucket's region when setting up its client.
if location.Spec.ObjectStorage != nil {
if location.Spec.Config == nil {
location.Spec.Config = make(map[string]string)
}
location.Spec.Config["bucket"] = bucket
location.Spec.Config["prefix"] = prefix
// Only include a CACert if it's specified in order to maintain compatibility with plugins that don't expect it.
if location.Spec.ObjectStorage.CACert != nil {
location.Spec.Config["caCert"] = string(location.Spec.ObjectStorage.CACert)
}
}
objectStore, err := objectStoreGetter.GetObjectStore(location.Spec.Provider)
if err != nil {
return nil, err
}
if err := objectStore.Init(location.Spec.Config); err != nil {
return nil, err
}
这行代码 就做了这件事情。对于VolumeSnapshotter
也是同样的操作。因此,在做pv snapshot 和访问object store的时候都会使用正确的插件。
除了上述的两个插件之外,阿里云的插件多实现了一个RestoreItemAction
, 但是在velero的代码中,没有对RestoreItemAction
这种类型的插件做判断,不会根据cloud provider来得到特定的RestoreItemAction
插件,代码如下:
// getRestoreItemAction returns the restore item action for this restartableRestoreItemAction. It does *not* restart the
// plugin process.
func (r *restartableRestoreItemAction) getRestoreItemAction() (velero.RestoreItemAction, error) {
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
if err != nil {
return nil, err
}
restoreItemAction, ok := plugin.(velero.RestoreItemAction)
if !ok {
return nil, errors.Errorf("%T is not a RestoreItemAction!", plugin)
}
return restoreItemAction, nil
}
这个方法没有参数,他会得到所有的RestoreItemAction
插件并且每一个都会apply,因此阿里云的这个插件也会被使用。
那么阿里云的这个插件做了什么事情呢?通过阿里云这个插件的源代码我们可以看到,他会把小于20Gi的PV以及PVC都resize成20Gi,并且打上velero.io/alibabacloud-restore-plugin=1
这样的annotation:
func (p *RestoreItemAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
p.log.Info("Alibaba Cloud RestorePlugin!")
var kind string
var err error
var ok bool
inputMap := input.Item.UnstructuredContent()
if kind, ok = inputMap[kindKey].(string); !ok {
return nil, errors.WithStack(err)
}
metadata, err := meta.Accessor(input.Item)
if err != nil {
return &velero.RestoreItemActionExecuteOutput{}, err
}
annotations := metadata.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations["velero.io/alibabacloud-restore-plugin"] = "1"
metadata.SetAnnotations(annotations)
switch kind {
case persistentVolumeClaimKey:
var pvc corev1api.PersistentVolumeClaim
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &pvc); err != nil {
return nil, errors.WithStack(err)
}
capacity := pvc.Spec.Resources.Requests[corev1api.ResourceName(corev1api.ResourceStorage)]
volSizeBytes := capacity.Value()
if int64(volSizeBytes) <= int64(minReqVolSizeBytes) {
p.log.Warnf("Alibaba disk volume request at least 20Gi, auto resize persistentVolumeClaim to 20Gi.")
pvc.Spec.Resources = corev1api.ResourceRequirements{
Requests: getResourceList(minReqVolSizeString),
}
pvc.Status = corev1api.PersistentVolumeClaimStatus{}
inputMap, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&pvc)
if err != nil {
return nil, errors.WithStack(err)
}
}
case persistentVolumeKey:
var pv corev1api.PersistentVolume
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &pv); err != nil {
return nil, errors.WithStack(err)
}
capacity := pv.Spec.Capacity[corev1api.ResourceName(corev1api.ResourceStorage)]
volSizeBytes := capacity.Value()
if int64(volSizeBytes) <= int64(minReqVolSizeBytes) {
p.log.Warnf("Alibaba disk volume request at least 20Gi, auto resize persistentVolume to 20Gi.")
persistentVolumeSource := pv.Spec.PersistentVolumeSource
accessModes := pv.Spec.AccessModes
claimRef := pv.Spec.ClaimRef
persistentVolumeReclaimPolicy := pv.Spec.PersistentVolumeReclaimPolicy
storageClassName := pv.Spec.StorageClassName
mountOptions := pv.Spec.MountOptions
volumeMode := pv.Spec.VolumeMode
nodeAffinity := pv.Spec.NodeAffinity
pv.Spec = corev1api.PersistentVolumeSpec{
Capacity: getResourceList(minReqVolSizeString),
PersistentVolumeSource: persistentVolumeSource,
AccessModes: accessModes,
ClaimRef: claimRef,
PersistentVolumeReclaimPolicy: persistentVolumeReclaimPolicy,
StorageClassName: storageClassName,
MountOptions: mountOptions,
VolumeMode: volumeMode,
NodeAffinity: nodeAffinity,
}
pv.Status = corev1api.PersistentVolumeStatus{}
inputMap, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&pv)
if err != nil {
return nil, errors.WithStack(err)
}
}
default:
p.log.Info("Nothing need to do, skip")
}
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: inputMap}), nil
}
结论
在我们的使用场景里,虽然backup的过程会使用正确的cloud provider 的 objectstore 和 snapshot插件,但是在restore的时候,阿里云实现的RestoreItemAction
插件也会被使用,并且这个插件会在所有的cloud上都会被使用。例如,我们在aws上做pv restore的时候,阿里云的RestoreItemAction
插件会被使用,他会修改pv和pvc的容量,并且打上annotation。
为了避免这个问题, 我们分开了所有的插件,针对不同的cloud只安装这个cloud对应的插件。