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

2023年 10月 14日 141.0k 0

作者:jiyf

原文来源: tidb.net/blog/75e8c9…

【是否原创】是
【首发渠道】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 触发的判断条件应该都可以。

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论