【是否原创】是
【首发渠道】TiDB 社区
问题背景
之前阅读学习 tidb operator 备份恢复相关的代码时候,做了上面部分笔记,有下面一段总结:
在备份设置了需要删除情况下,如果删除backup crd,那么会创建 clean job清理数据,然后删除crd的操作会阻塞;如果clean job失败,那么会一直阻塞的
然后这个问题今天就遇到了!
问题描述
在 k8s 中进行 br 备份到 s3 的测试,由于 s3 参数设置有问题,导致备份失败。删除失败的备份任务,命令被卡主,backup 删除失败:
备份失败是因为 s3 参数设置有误,导致访问 s3 失败。同样的,执行删除后,创建的 clean job 也因为访问 s3 失败导致清理失败,然后删除 backup crd 就被卡主了。
$ kubectl describe backup backup-test -n tidb-test
...
Last Transition Time: 2021-11-17T09:29:59Z
Message: blob (code=Unknown): RequestError: send request failed
caused by: Get http://192.168.102.96:9000/backup?list-type=2&max-keys=1000&prefix=demo%2F: dial tcp 192.168.102.96:9000: connect: connection refused
Reason: CleanBackupDataFailed
Status: True
Type: Failed
...
$ kubectl logs clean-backup-test-2xz4l -n tidb-test
Create rclone.conf file.
/tidb-backup-manager clean --namespace=tidb-test --backupName=backup-test
I1117 09:39:54.889554 10 clean.go:69] start to clean backup tidb-test/backup-test
E1117 09:39:55.177011 10 manager.go:83] clean cluster tidb-test/backup-test backup s3://backup/demo failed, err: blob (code=Unknown): RequestError: send request failed
caused by: Get http://192.168.102.96:9000/backup?list-type=2&max-keys=1000&prefix=demo%2F: dial tcp 192.168.102.96:9000: connect: connection refused
error: blob (code=Unknown): RequestError: send request failed
caused by: Get http://192.168.102.96:9000/backup?list-type=2&max-keys=1000&prefix=demo%2F: dial tcp 192.168.102.96:9000: connect: connection refused
有以下现象(后面再解释原因):
- 执行删除 backup crd 被卡住,删除 backup 失败
- 执行删除 backup job 成功
- 执行删除 clean job 成功,但是一会又会被创建(backup crd还是清理不了)
问题很容易复现,在 tidb operator 中设置一个 br 备份,备份到 s3 存储,在 backup crd 中故意将备份参数设置错误,导致备份失败。同时设置 .spec.cleanPolicy 为 Delete,意思是删除备份任务时候,同时清理备份文件。
.spec.cleanPolicy`:备份集群后删除备份 CR 时的备份文件清理策略。目前支持三种清理策略:
Retain
:任何情况下,删除备份 CR 时会保留备份出的文件Delete
:任何情况下,删除备份 CR 时会删除备份出的文件OnFailure
:如果备份中失败,删除备份 CR 时会删除备份出的文件如果不配置该字段,或者配置该字段的值为上述三种以外的值,均会保留备份出的文件。值得注意的是,在 v1.1.2 以及之前版本不存在该字段,且默认在删除 CR 的同时删除备份的文件。若 v1.1.3 及之后版本的用户希望保持该行为,需要设置该字段为Delete
。
备份的流程
当 backup crd 创建、变更、删除等变更后,tidb operator controller 会监听到变化,然后检查 backup crd,根据 crd 定义,operator 执行后面的备份、清理等流程(详细的流程可以翻开代码自行查看):
-
检查删除标志 DeletionTimestamp 是否被设置,是则执行 clean 流程
- 检查是否需要创建 clean job 进行清理(cleanPolicy 设置为 Delelte 的目的)
- 检查 clean job 是否已经存在,已经存在就返回
- 检查备份路径 backup.Status.BackupPath 是否为空,不为空需要清理
- 新建建 clean job
-
检查备份参数是否有问题
-
检查备份是否已经完成
-
检查备份是否已经失败
-
在 3、4 都不存在的情况下,检查备份任务是否被调度、执行中、准备状态
-
以上都不是,那就新建 backup job
- 创建 backup job 这里不描述细节,注意的是执行备份前会执行 backup.Status.BackupPath 设置的工作
这里如果没有没有清理完成,backup crd 的删除就会被一直卡主,这里解释了上面的第一个现象。
第二个现象:因为 DeletionTimestamp 被设置后,controller 会进入 clean 的流程,不会进行 backup job 流程的检查,所以这里删除 backup job 是可以的,不会被新建。
第三个现象:DeletionTimestamp 被设置后,进入 clean 流程,如果 clean job 被删除了,那么 contoller 就会新建 job,就出现了删也删不尽的流程。
解决方法
backup 之所以删除不成功,是因为 backup crd 的 finalizers 被设置了,关于 finalizers 可以查看 k8s 文档。
$ kubectl edit backup backup-test -n tidb-test
apiVersion: pingcap.com/v1alpha1
kind: Backup
metadata:
creationTimestamp: "2021-11-17T09:17:53Z"
deletionGracePeriodSeconds: 0
deletionTimestamp: "2021-11-17T09:24:41Z"
finalizers:
- tidb.pingcap.com/backup-protection
因为回调函数 tidb.pingcap.com/backup-protection 没有满足条件,所以资源是不允许删除的。
看 finalizers 是如何被添加与删除的:
// UpdateBackup executes the core logic loop for a Backup.
func (c *defaultBackupControl) UpdateBackup(backup *v1alpha1.Backup) error {
backup.SetGroupVersionKind(controller.BackupControllerKind)
if err := c.addProtectionFinalizer(backup); err != nil {
return err
}
if err := c.removeProtectionFinalizer(backup); err != nil {
return err
}
return c.updateBackup(backup)
}
// addProtectionFinalizer will be called when the Backup CR is created
func (c *defaultBackupControl) addProtectionFinalizer(backup *v1alpha1.Backup) error {
...
if needToAddFinalizer(backup) {
...
}
...
}
func (c *defaultBackupControl) removeProtectionFinalizer(backup *v1alpha1.Backup) error {
...
if needToRemoveFinalizer(backup) {
...
}
...
}
func needToAddFinalizer(backup *v1alpha1.Backup) bool {
return backup.DeletionTimestamp == nil && v1alpha1.IsCleanCandidate(backup) && !slice.ContainsString(backup.Finalizers, label.BackupProtectionFinalizer, nil)
}
func needToRemoveFinalizer(backup *v1alpha1.Backup) bool {
return v1alpha1.IsCleanCandidate(backup) && isDeletionCandidate(backup) &&
(v1alpha1.IsBackupClean(backup) || v1alpha1.NeedNotClean(backup))
}
很简单,通过 edit 将 finalizers 删除就可以了呀。
$ kubectl edit backup backup-test -n tidb-test
将 finalizers 清空删除
$ kubectl get backup -n tidb-test
No resources found in tidb-test namespace.
注意:这里采用这种方式删除能比较好的解决当前的问题。因为本身也没有备份的数据需要进行清理,所以采用了这个方案。
总结
其实这个场景,因为没有真实备份数据存在(连不上 s3),所以 clean 这里其实是可以去掉的。又由于 clean 同样失败,导致这次备份就卡在这里,相关的 crd、job、pod 资源无法完成清理,关于备份清理这里相关的同步机制稍微有点不足,比如说:
- backup 失败的原因、失败 point,通过 backup crd status 没有较好的完成同步,导致没有正确的判断这里其实是不需要进行 clean 的(clean 也会失败)
- 同样的是同步机制问题,backup job 创建的时候就设置了 backup.Status.BackupPath,clean 的时候通过这个来判断是否有数据需要删除。两者信息判断不一致导致决策有问题。