暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

TiDB K8S 定时备份状态异常问题排查

2021-11-19
213

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

定时备份流程

  1. 检查是否需要开始下一次备份,下面情况都需要

  2. bs.Status.LastBackup 为空

  3. bs.Status.LastBackup 已经完成、被调度、失败

  4. 计算下次备份的时间,根据已有的备份,计算是否需要执行下次备份,返回需要执行定时备份的时间点

  5. 删除上次备份的 backup job

  6. 创建 backup crd

  7. 设置 bs.Status.LastBackup = backup.GetName()

  8. 设置 bs.Status.LastBackupTime = \&metav1.Time{Time: *scheduledTime}
  9. 设置 bs.Status.AllBackupCleanTime = nil

  10. 清理过期备份

  11. 如果设置最大保留时间,根据最大保留时间进行清理

  12. 如果设置最大保留副本,根据副本数进行清理

  13. 检查如果所有的 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 触发的判断条件应该都可以。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论