版本信息如下:
a、操作系统:centos 7.6
b、kubernetes版本:v1.15.0
kubelet在创建容器container和停止容器container的阶段,提供了回调扩展点。
lifecycle.preStop,用于定义在kubelet在即将停止容器(docker stop)之前的前置工作,例如从服务注册中心下线微服务实例、通知其他系统等。注意,lifecycle.preStop的最大耗时时间是spec.terminationGracePeriodSeconds,超过这个时间kubelet会直接调用cri的接口来停止容器。
lifecycle.postStart,用于定义在kubelet在创建容器(docker run)之后的后置工作,例如下载一些文件、在数据库准备数据等,由于postStart会阻塞kubelet启动后一个容器,因此postStart还能用来阻塞后一个容器的启动。
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
......
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
}
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())
}
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
}
通俗地理解,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
}
kubelet在创建容器container和停止容器container的阶段,提供了回调扩展点,本质就是kubelet在调用cni的接口的之前之后,顺带执行一些hook。