【是否原创】是
【首发渠道】TiDB 社区
问题场景
在进行 tidb operator 定时备份测试环境中,配置了使用 br 定时备份到 s3 的测试。定时备份 backupschedule crd 关键参数是这样的:
maxReservedTime: 1h
schedule: '*/10 * * * *'
代表每 10 分钟进行一次定时备份,备份数据保留时长为 1 小时。
使用的 k8s 环境跑了很多测试应用,悲催的是遇到了 k8s 的一个 bug 问题,导致 pod 创建失败。
pod 的错误信息如下:
Warning FailedCreatePodContainer 2s (x144754 over 29d) kubelet, 192.168.10.10 unable to ensure pod container exists: failed to create container for [kubepods besteffort pod12685337-1956-464c-9050-4b8551567ea2] : mkdir /sys/fs/cgroup/memory/kubepods/besteffort/pod12685337-1956-464c-9050-4b8551567ea2: cannot allocate memory
针对这个 k8s 问题,pingcap 还发过一个博客文档来修复这个问题:诊断修复 TiDB Operator 在 K8s 测试中遇到的 Linux 内核问题
具体的 k8s 环境、修复方法暂且不提,重点是因为这个问题,导致已有的备份 clean pod ContainerCreating,新的备份 backup pod ContainerCreating。
backupschedule 执行删除已有的 backup 没有完成删除,导致过期,新建的因为处于 ContainerCreating 可以完成删除,然后新的在定时创建,过期删除,已有的都处于无法删除状态。
定时备份流程
-
检查是否需要开始下一次备份,下面情况都需要
- bs.Status.LastBackup 为空
- bs.Status.LastBackup 已经完成、被调度、失败
-
计算下次备份的时间,根据已有的备份,计算是否需要执行下次备份,返回需要执行定时备份的时间点
-
删除上次备份的 backup job
-
创建 backup crd
- 设置 bs.Status.LastBackup = backup.GetName()
- 设置 bs.Status.LastBackupTime = &metav1.Time{Time: *scheduledTime}
- 设置 bs.Status.AllBackupCleanTime = nil
-
清理过期备份
-
如果设置最大保留时间,根据最大保留时间进行清理
-
如果设置最大保留副本,根据副本数进行清理
-
检查如果所有的 backup 都被清理,那么 resetLastBackup
- bs.Status.LastBackupTime = nil
- bs.Status.LastBackup = “”
- bs.Status.AllBackupCleanTime = &metav1.Time{Time: bm.now()}
-
上面第 2 步计算下次备份时间点源码:
// getLastScheduledTime return the newest time need to be scheduled according last backup time.
// the return time is not before now and return nil if there's no such time.
func getLastScheduledTime(bs *v1alpha1.BackupSchedule, nowFn nowFn) (*time.Time, error) {
...
var earliestTime time.Time
if bs.Status.LastBackupTime != nil {
earliestTime = bs.Status.LastBackupTime.Time
} else if bs.Status.AllBackupCleanTime != nil {
...
earliestTime = bs.Status.AllBackupCleanTime.Time
} else {
...
earliestTime = bs.ObjectMeta.CreationTimestamp.Time
}
...
var scheduledTimes []time.Time
for t := sched.Next(earliestTime); !t.After(now); t = sched.Next(t) {
scheduledTimes = append(scheduledTimes, t)
...
return nil, nil
}
}
...
scheduledTime := scheduledTimes[len(scheduledTimes)-1]
return &scheduledTime, nil
}
从代码上看 earliestTime 作为计算备份的上次时间点,取值根据情况依次可能从下面三个值选取:
- bs.Status.LastBackupTime.Time
- bs.Status.AllBackupCleanTime.Time
- bs.ObjectMeta.CreationTimestamp.Time
在上面第 4 步中,创建 backup 后会设置 bs.Status.LastBackup 等。
清理备份函数如下:
func (bm *backupScheduleManager) backupGCByMaxReservedTime(bs *v1alpha1.BackupSchedule) {
...
backupsList, err := bm.getBackupList(bs)
if err != nil {
klog.Errorf("backupGCByMaxReservedTime, err: %s", err)
return
}
var deleteCount int
for _, backup := range backupsList {
if backup.CreationTimestamp.Add(reservedTime).After(bm.now()) {
continue
}
// delete the expired backup
if err := bm.deps.BackupControl.DeleteBackup(backup); err != nil {
...
return
}
deleteCount += 1
...
}
if deleteCount == len(backupsList) {
// All backups have been deleted, so the last backup information in the backupSchedule should be reset
bm.resetLastBackup(bs)
}
}
func (bm *backupScheduleManager) resetLastBackup(bs *v1alpha1.BackupSchedule) {
bs.Status.LastBackupTime = nil
bs.Status.LastBackup = ""
bs.Status.AllBackupCleanTime = &metav1.Time{Time: bm.now()}
}
也就是说如果 deleteCount == len(backupsList) 那么进行 resetLastBackup。
func (bm *backupScheduleManager) getBackupList(bs *v1alpha1.BackupSchedule) ([]*v1alpha1.Backup, error) {
...
backupLabels := label.NewBackupSchedule().Instance(bsName).BackupSchedule(bsName)
selector, err := backupLabels.Selector()
...
backupsList, err := bm.deps.BackupLister.Backups(ns).List(selector)
...
return backupsList, nil
}
注意这里 list backup 是从 k8s 缓存里读,如果缓存没有更新及时,新创建的 backup 这里没有 list 出来。
问题产生的原因
- 由于测试环境中所有旧的 backup 都超过保留时间,那么都需要被删除
- 从 k8s list 出 backup 时候,没有查询刚创建的最新的 backup
- 导致所有的 list 的 backup 都需要执行删除操作,达到 deleteCount == len(backupsList) 那么进行 resetLastBackup
- 出现异常状态,新创建的 backup 是存在的,但是 backupschedule crd 的 status 中 bs.Status.LastBackup = “”
- 下次备份的 earliestTime 选取有偏差
修复方法
等缓存更新后再 list backup、不通过缓存去查询最近创建的 backup、或者增加 resetLastBackup 触发的判断条件应该都可以。