前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kubernetes 中 Pod 的优雅退出机制

Kubernetes 中 Pod 的优雅退出机制

作者头像
CS实验室
发布2022-06-14 14:19:17
3.1K0
发布2022-06-14 14:19:17
举报
文章被收录于专栏:CS实验室

本文基于对 Kubernetes v1.23.1 的源码阅读

Kubernetes 提供了一种 Pod 优雅退出机制,使 Pod 在退出前可以完成一些清理工作。但若执行清理工作时出错了,Pod 能正常退出吗?多久能退出?退出时间可以指定吗?系统有默认参数吗?这其中有若干细节值得我们去注意,本文就从这些细节出发,梳理清楚每种情况下 Kubernetes 的组件的各项行为及其参数设定。

Pod 正常退出

Pod 正常退出是指非被驱逐时退出,包括人为删除、执行出错被删除等。在 Pod 退出时,kubelet 删除容器之前,会先执行 pod 的 preStop,允许 pod 在退出前执行一段脚本用以清除必要的资源等。然而 preStop 也有执行失败或者直接 hang 住的情况,这个时候 preStop 并不会阻止 pod 的退出,kubelet 也不会重复执行,而是会等一段时间,超过这个时间会直接删除容器,保证整个系统的稳定。

整个过程在函数 killContainer 中,我们在 pod 优雅退出时,需要明确的是,kubelet 的等待时间由那几个因素决定,用户可以设置的字段和系统组件的参数是如何共同作用的。

gracePeriod

kubelet 计算 gracePeriod 的过程为:

  1. 如果 pod 的 DeletionGracePeriodSeconds 不为 nil,表示是 ApiServer 删除的,gracePeriod 直接取值;
  2. 如果 pod 的 Spec.TerminationGracePeriodSeconds 不为 nil,再看 pod 删除的原因是什么;
    1. 若删除原因为执行 startupProbe 失败,gracePeriod 取值为 startupProbe 中设置的 TerminationGracePeriodSeconds
    2. 若删除原因为执行 livenessProbe 失败,gracePeriod 取值为 livenessProbe 中设置的 TerminationGracePeriodSeconds

获得到 gracePeriod 之后,kubelet 执行 pod 的 preStop,函数 executePreStopHook 中会起一个 goroutine ,并计算其执行的时间,gracePeriod 再减去该时间,就是最终传给 runtime 的删除容器的 timeout 时间。所以,若我们设置了 pod preStop,需要同时考虑到 preStop 的执行时间以及容器退出的时间,可以给 TerminationGracePeriodSeconds 设置一个大于 preStop + 容器退出的时间。

代码语言:javascript
复制
func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, message string, reason containerKillReason, gracePeriodOverride *int64) error {
   ...
 // From this point, pod and container must be non-nil.
 gracePeriod := int64(minimumGracePeriodInSeconds)
 switch {
 case pod.DeletionGracePeriodSeconds != nil:
  gracePeriod = *pod.DeletionGracePeriodSeconds
 case pod.Spec.TerminationGracePeriodSeconds != nil:
  gracePeriod = *pod.Spec.TerminationGracePeriodSeconds

  switch reason {
  case reasonStartupProbe:
   if containerSpec.StartupProbe != nil && containerSpec.StartupProbe.TerminationGracePeriodSeconds != nil {
    gracePeriod = *containerSpec.StartupProbe.TerminationGracePeriodSeconds
   }
  case reasonLivenessProbe:
   if containerSpec.LivenessProbe != nil && containerSpec.LivenessProbe.TerminationGracePeriodSeconds != nil {
    gracePeriod = *containerSpec.LivenessProbe.TerminationGracePeriodSeconds
   }
  }
 }

 // Run internal pre-stop lifecycle hook
 if err := m.internalLifecycle.PreStopContainer(containerID.ID); err != nil {
  return err
 }

 // Run the pre-stop lifecycle hooks if applicable and if there is enough time to run it
 if containerSpec.Lifecycle != nil && containerSpec.Lifecycle.PreStop != nil && gracePeriod > 0 {
  gracePeriod = gracePeriod - m.executePreStopHook(pod, containerID, containerSpec, gracePeriod)
 }
 // always give containers a minimal shutdown window to avoid unnecessary SIGKILLs
 if gracePeriod < minimumGracePeriodInSeconds {
  gracePeriod = minimumGracePeriodInSeconds
 }
 if gracePeriodOverride != nil {
  gracePeriod = *gracePeriodOverride
 }

 err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)
   ...
 return nil
}

gracePeriodOverride

在上面分析的过程中,kubelet 调用 runtime 接口之前,会再判断一步 gracePeriodOverride,若传进来的值不为空,直接用该值覆盖前面的 gracePeriod

kubelet 计算 gracePeriodOverride 的主要过程如下:

  1. 取值 pod 的 DeletionGracePeriodSeconds
  2. 若 kubelet 是在驱逐 pod,则用驱逐的设置 pod 退出时间覆盖;
代码语言:javascript
复制
func calculateEffectiveGracePeriod(status *podSyncStatus, pod *v1.Pod, options *KillPodOptions) (int64, bool) {
 gracePeriod := status.gracePeriod
 // this value is bedrock truth - the apiserver owns telling us this value calculated by apiserver
 if override := pod.DeletionGracePeriodSeconds; override != nil {
  if gracePeriod == 0 || *override < gracePeriod {
   gracePeriod = *override
  }
 }
 // we allow other parts of the kubelet (namely eviction) to request this pod be terminated faster
 if options != nil {
  if override := options.PodTerminationGracePeriodSecondsOverride; override != nil {
   if gracePeriod == 0 || *override < gracePeriod {
    gracePeriod = *override
   }
  }
 }
 // make a best effort to default this value to the pod's desired intent, in the event
 // the kubelet provided no requested value (graceful termination?)
 if gracePeriod == 0 && pod.Spec.TerminationGracePeriodSeconds != nil {
  gracePeriod = *pod.Spec.TerminationGracePeriodSeconds
 }
 // no matter what, we always supply a grace period of 1
 if gracePeriod < 1 {
  gracePeriod = 1
 }
 return gracePeriod, status.gracePeriod != 0 && status.gracePeriod != gracePeriod
}

ApiServer 的行为

在上面分析 kubelet 处理 pod 的退出时间时,我们会发现 kubelet 会首先用 pod 的 DeletionGracePeriodSeconds,而该值正是 ApiServer 删除 pod 时写入的。本节我们来分析 ApiServer 删除 pod 时的行为。

ApiServer 中计算 pod 的 GracePeriodSeconds 过程为:

  1. options.GracePeriodSeconds 不为空,则设置为该值;否则设置为 spec 中用户指定的 Spec.TerminationGracePeriodSeconds(默认为 30s);
  2. 若 pod 未被调度或已经退出,则设置为 0,即立即删除;

其中,options.GracePeriodSeconds 为 kubectl 删除 pod 时,可以指定的参数 --grace-period;或者程序里调用 ApiServer 接口时指定的参数,如 client-go 中的 DeleteOptions.GracePeriodSeconds

代码语言:javascript
复制
func (podStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool {
 if options == nil {
  return false
 }
 pod := obj.(*api.Pod)
 period := int64(0)
 // user has specified a value
 if options.GracePeriodSeconds != nil {
  period = *options.GracePeriodSeconds
 } else {
  // use the default value if set, or deletes the pod immediately (0)
  if pod.Spec.TerminationGracePeriodSeconds != nil {
   period = *pod.Spec.TerminationGracePeriodSeconds
  }
 }
 // if the pod is not scheduled, delete immediately
 if len(pod.Spec.NodeName) == 0 {
  period = 0
 }
 // if the pod is already terminated, delete immediately
 if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
  period = 0
 }

 if period < 0 {
  period = 1
 }

 // ensure the options and the pod are in sync
 options.GracePeriodSeconds = &period
 return true
}

kubelet 驱逐 pod

另外,在 kubelet 驱逐 pod 时,pod 的优雅退出时间是被覆盖的。

代码语言:javascript
复制
func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc) []*v1.Pod {
   ...
 // we kill at most a single pod during each eviction interval
 for i := range activePods {
  pod := activePods[i]
  gracePeriodOverride := int64(0)
  if !isHardEvictionThreshold(thresholdToReclaim) {
   gracePeriodOverride = m.config.MaxPodGracePeriodSeconds
  }
  message, annotations := evictionMessage(resourceToReclaim, pod, statsFunc)
  if m.evictPod(pod, gracePeriodOverride, message, annotations) {
   metrics.Evictions.WithLabelValues(string(thresholdToReclaim.Signal)).Inc()
   return []*v1.Pod{pod}
  }
 }
 ...
}

其 override 值为 EvictionMaxPodGracePeriod,且只有软驱逐时有效,该值为 kubelet 的驱逐相关的配置参数:

代码语言:javascript
复制
 // Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}.
 EvictionHard map[string]string
 // Map of signal names to quantities that defines soft eviction thresholds.  For example: {"memory.available": "300Mi"}.
 EvictionSoft map[string]string
 // Map of signal names to quantities that defines grace periods for each soft eviction signal. For example: {"memory.available": "30s"}.
 EvictionSoftGracePeriod map[string]string
 // Duration for which the kubelet has to wait before transitioning out of an eviction pressure condition.
 EvictionPressureTransitionPeriod metav1.Duration
 // Maximum allowed grace period (in seconds) to use when terminating pods in response to a soft eviction threshold being met.
 EvictionMaxPodGracePeriod int32

kubelet 驱逐 pod 的函数是启动时注入的,函数如下:

代码语言:javascript
复制
func killPodNow(podWorkers PodWorkers, recorder record.EventRecorder) eviction.KillPodFunc {
 return func(pod *v1.Pod, isEvicted bool, gracePeriodOverride *int64, statusFn func(*v1.PodStatus)) error {
  // determine the grace period to use when killing the pod
  gracePeriod := int64(0)
  if gracePeriodOverride != nil {
   gracePeriod = *gracePeriodOverride
  } else if pod.Spec.TerminationGracePeriodSeconds != nil {
   gracePeriod = *pod.Spec.TerminationGracePeriodSeconds
  }

  // we timeout and return an error if we don't get a callback within a reasonable time.
  // the default timeout is relative to the grace period (we settle on 10s to wait for kubelet->runtime traffic to complete in sigkill)
  timeout := int64(gracePeriod + (gracePeriod / 2))
  minTimeout := int64(10)
  if timeout < minTimeout {
   timeout = minTimeout
  }
  timeoutDuration := time.Duration(timeout) * time.Second

  // open a channel we block against until we get a result
  ch := make(chan struct{}, 1)
  podWorkers.UpdatePod(UpdatePodOptions{
   Pod:        pod,
   UpdateType: kubetypes.SyncPodKill,
   KillPodOptions: &KillPodOptions{
    CompletedCh:                              ch,
    Evict:                                    isEvicted,
    PodStatusFunc:                            statusFn,
    PodTerminationGracePeriodSecondsOverride: gracePeriodOverride,
   },
  })

  // wait for either a response, or a timeout
  select {
  case <-ch:
   return nil
  case <-time.After(timeoutDuration):
   recorder.Eventf(pod, v1.EventTypeWarning, events.ExceededGracePeriod, "Container runtime did not kill the pod within specified grace period.")
   return fmt.Errorf("timeout waiting to kill pod")
  }
 }
}

killPodNow 函数是 kubelet 在驱逐 pod 时所调用的函数,gracePeriodOverride 为软驱逐时设置的参数,当其没有设置时,gracePeriod 依然取值 pod.Spec.TerminationGracePeriodSeconds。然后该函数会调用 podWorkers.UpdatePod,传入相应参数,并且设置一个跟 gracePeriod 相关的超时时间,等待其返回。

总结

Pod 的优雅退出是由 preStop 实现的,本文就 Pod 正常退出和被驱逐时,Pod 的退出时间受哪些因素影响,各参数之间是如何相互作用的做了简要的分析。了解了这些细节后,我们对 Pod 的退出流程就有了一个更加全面的认知。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CS实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Pod 正常退出
    • gracePeriod
      • gracePeriodOverride
      • ApiServer 的行为
      • kubelet 驱逐 pod
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档