写点什么

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

  • 2022 年 7 月 11 日
  • 本文字数:2490 字

    阅读完需:约 8 分钟

作者: 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

  14. bs.Status.LastBackupTime = nil

  15. bs.Status.LastBackup = “”

  16. 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 触发的判断条件应该都可以。


发布于: 刚刚阅读数: 2
用户头像

TiDB 社区官网:https://tidb.net/ 2021.12.15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
TiDB K8S 定时备份状态异常问题排查_管理与运维_TiDB 社区干货传送门_InfoQ写作社区