作者: jiyf 原文来源:https://tidb.net/blog/75e8c99e
【是否原创】是
【首发渠道】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 触发的判断条件应该都可以。
评论