作者:jiyf​

【是否原创】是

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 可以完成删除,然后新的在定时创建,过期删除,已有的都处于无法删除状态。



定时备份流程

  1. 检查是否需要开始下一次备份,下面情况都需要
  • bs.Status.LastBackup 为空
  • bs.Status.LastBackup 已经完成、被调度、失败
  1. 计算下次备份的时间,根据已有的备份,计算是否需要执行下次备份,返回需要执行定时备份的时间点
  2. 删除上次备份的 backup job
  3. 创建 backup crd
  • 设置 bs.Status.LastBackup = backup.GetName()
  • 设置 bs.Status.LastBackupTime = &metav1.Time{Time: *scheduledTime}
  • 设置 bs.Status.AllBackupCleanTime = nil
  1. 清理过期备份
  1. 如果设置最大保留时间,根据最大保留时间进行清理
  2. 如果设置最大保留副本,根据副本数进行清理
  3. 检查如果所有的 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 出来。



问题产生的原因

  1. 由于测试环境中所有旧的 backup 都超过保留时间,那么都需要被删除
  2. 从 k8s list 出 backup 时候,没有查询刚创建的最新的 backup
  3. 导致所有的 list 的 backup 都需要执行删除操作,达到 deleteCount == len(backupsList) 那么进行 resetLastBackup
  4. 出现异常状态,新创建的 backup 是存在的,但是 backupschedule crd 的 status 中 bs.Status.LastBackup = “”
  5. 下次备份的 earliestTime 选取有偏差


修复方法

等缓存更新后再 list backup、不通过缓存去查询最近创建的 backup、或者增加 resetLastBackup 触发的判断条件应该都可以。