• pod生命周期回调prestop、poststart源码分析


    1 概述

    1.1 环境

    版本信息如下:
    a、操作系统:centos 7.6
    b、kubernetes版本:v1.15.0


    1.2 pod生命周期的回调扩展点的概述

    kubelet在创建容器container和停止容器container的阶段,提供了回调扩展点。

    lifecycle.preStop,用于定义在kubelet在即将停止容器(docker stop)之前的前置工作,例如从服务注册中心下线微服务实例、通知其他系统等。注意,lifecycle.preStop的最大耗时时间是spec.terminationGracePeriodSeconds,超过这个时间kubelet会直接调用cri的接口来停止容器。

    lifecycle.postStart,用于定义在kubelet在创建容器(docker run)之后的后置工作,例如下载一些文件、在数据库准备数据等,由于postStart会阻塞kubelet启动后一个容器,因此postStart还能用来阻塞后一个容器的启动。


    1.3 pod生命周期的回调扩展点的yaml实例

    lifecycle.preStop和lifecycle.postStart的内容可以是执行shell指令、也可以执行http get,也可以是纯粹的tcp,一个yaml例子如下:

    ......
         lifecycle:
           postStart:
             exec:
               command:
                 - "sh"
                 - "-c"
                 - "sleep 10"
           prestop:
             httpGet:
               host: example.com
               psth: /test
               port: 8080
               scheme: HTTP
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2 源码分析

    2.1 lifecycle.preStop

    killContainer( )是kubelet停止一个container的逻辑,可以简单地理解成:一个协程先执行docker exec来执行preStop逻辑,再执行docker stop停止容器。

    func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, message string, gracePeriodOverride *int64) error {
    	/*
    		其他代码
    	*/
    
    	// 这里正式执行preStop
    	if containerSpec.Lifecycle != nil && containerSpec.Lifecycle.PreStop != nil && gracePeriod > 0 {
    		// m.executePreStopHook(  )阻塞的最大时间是gracePeriod(就是spec.terminationGracePeriodSeconds)
    		// 如果preStop逻辑的耗时小于gracePeriod, 则m.executePreStopHook(  )返回的是preStop的耗时,此时gracePeriod要减去preStop的耗时
    		gracePeriod = gracePeriod - m.executePreStopHook(pod, containerID, containerSpec, gracePeriod)
    	}
    	
    	// 如果gracePeriod 小于 2s,则gracePeriod = 2s
    	if gracePeriod < minimumGracePeriodInSeconds {
    		gracePeriod = minimumGracePeriodInSeconds
    	}
    
    	// 再给一个机会修改gracePeriod的机会
    	if gracePeriodOverride != nil {
    		gracePeriod = *gracePeriodOverride
    	}
    
    	// 正式执行docker stop来关闭容器,将gracePeriod作为docker stop api的一个参数
    	err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)
    	/*
    		其他代码
    	*/
    
    	return err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    executePreStopHook( )会启动一个独立协程来执行preStop的逻辑(简单地理解成调用docker exec执行命令),然后用select指令来阻塞等待直到preStop执行完毕或达到gracePeriod指定的时间。

    func (m *kubeGenericRuntimeManager) executePreStopHook(pod *v1.Pod, containerID kubecontainer.ContainerID, containerSpec *v1.Container, gracePeriod int64) int64 {
    
    	start := metav1.Now()
    	done := make(chan struct{})
    	// 通过一个独立的协程来执行PreStop的逻辑
    	go func() {
    		defer close(done)
    		if msg, err := m.runner.Run(containerID, pod, containerSpec, containerSpec.Lifecycle.PreStop); err != nil {
    		}
    	}()
    
    	// 如果时间超过gracePeriod或preStop执行完毕,则退出select指令的阻塞
    	select {
    	case <-time.After(time.Duration(gracePeriod) * time.Second):
    	case <-done:
    	}
    
    	// 计算本方法的耗时时间,单位为秒
    	return int64(metav1.Now().Sub(start.Time).Seconds())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.1 lifecycle.postStart

    SyncPod()是一个固定流程,用于同步一个pod对象,通俗地说就是执行一系列的docker run和docker stop命令。启动业务容器是SyncPod()的第6个步骤。

    func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
    	// Step 1: Compute sandbox and container changes.
    	// Step 2: Kill the pod if the sandbox has changed.
    	// Step 3: kill any running containers in this pod which are not to keep.
    	// Step 4: Create a sandbox for the pod if necessary.
    	// Step 5: start the init container.
    	
    	// Step 6: start containers in podContainerChanges.ContainersToStart.
    	// for循环遍历来创建需要创建的业务container
    	for _, idx := range podContainerChanges.ContainersToStart {
    		/*
    			其他代码
    		*/
    		
    		// 调用m.startContainer( )启动容器
    		// m.startContainer( )会执行postStart逻辑,postStart不完毕,m.startContainer( )不会退出
    		if msg, err := m.startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podIP); err != nil {
    		
    		/*
    			其他代码
    		*/
    		
    			continue
    		}
    	}
    
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    通俗地理解,startContainer( )在启动了业务容器(执行docker run)后,紧接着执行[ docker exec 容器ID 命令 ],这就是所谓的lifecycle.postStart。

    func (m *kubeGenericRuntimeManager) startContainer(podSandboxID string, podSandboxConfig *runtimeapi.PodSandboxConfig, container *v1.Container, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, podIP string) (string, error) {
    	// Step 1: pull the image.
    	// Step 2: create the container.
    	// Step 3: start the container.
    
    	// 这里就是lifecycle.postStart
    	// Step 4: execute the post start hook.
    	if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
    		/*
    			其他代码
    		*/
    		
    		// 通俗地理解就是docker exec 容器ID 命令
    		msg, handlerErr := m.runner.Run(kubeContainerID, pod, container, container.Lifecycle.PostStart)
    		
    		/*
    			其他代码
    		*/
    	}
    
    	return "", nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3 小结

    kubelet在创建容器container和停止容器container的阶段,提供了回调扩展点,本质就是kubelet在调用cni的接口的之前之后,顺带执行一些hook。

  • 相关阅读:
    ESP8266-Arduino网络编程实例-ESP-MESH传感器数据发送与接收
    SQL Server、MySQL主从搭建,EF Core读写分离代码实现
    Wireshark TS | FIN 之后不关闭连接疑问
    逆向分析练习三(最长公共前缀)
    打印机 默认使用 首选项配置
    Springboot结合redis实现注册、登录、登录
    SpringBoot-13-mvc配置原理
    DNS基础之使用dig查询DNS解析过程
    20231018 自然常数的存在性
    阿里、华为都是外包公司?
  • 原文地址:https://blog.csdn.net/nangonghen/article/details/127605262