Kubernetes 二次开发 之 修改 kubelet StopContainer 逻辑

一、背景介绍

       公司部署的 Kubernetes 集群偶尔会出现容器杀不死的情况,影响了应用程序的正常运行

       本着解决问题的态度,博主查看了 kubernetes 源码,尤其是 kubelet 部分,对其中的部分逻辑进行了增强

       可能这不是问题的真正症结所在,但间接地也一定程度解决了问题,下边简单记录下源码修改的过程

二、源码分析

       源码地址:https://github.com/kubernetes/kubernetes

       新版本的 kubernetes(这里是 1.10.4),不再直接操作 docker ,而是使用了 CRI (容器运行时)接口间接调用

       之所以这么设计,主要是为了屏蔽容器的具体实现(目前的容器实现不止 Docker,还有诸如 rkt、gVisor 等等)

       kuernetes 只需要面向 CRI 接口编程就可以,无需关注具体容器的实现细节

       使用 CRI 实现的逻辑架构如下:

       Kubernetes 二次开发 之 修改 kubelet StopContainer 逻辑

        Kubelet 的代码中提供了 CRI shim 的实现代码,kubelet 自己既充当客户端,又充当服务器,之间通过 gRPC 调用

        实际操作 Docker 的过程通过 CRI shim 实现

        因此,增强杀死 container 的代码也许可以嵌入到 CRI shim 的代码实现中,我实际也是这么做的

        好了,下面就是去找 CRI shim 中关于杀死 container 的代码了

        CRI shim 的源码地址在 pkg/kubelet/dockershim/libdocker 中

        首先,查看 client.go

// Interface is an abstract interface for testability.  It abstracts the interface of docker client.
type Interface interface {
    ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error)
    InspectContainer(id string) (*dockertypes.ContainerJSON, error)
    InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error)
    CreateContainer(dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error)
    StartContainer(id string) error
    StopContainer(id string, timeout time.Duration) error
    UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error
    RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error
    InspectImageByRef(imageRef string) (*dockertypes.ImageInspect, error)
    InspectImageByID(imageID string) (*dockertypes.ImageInspect, error)
    ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error)
    PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error
    RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error)
    ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error)
    Logs(string, dockertypes.ContainerLogsOptions, StreamOptions) error
    Version() (*dockertypes.Version, error)
    Info() (*dockertypes.Info, error)
    CreateExec(string, dockertypes.ExecConfig) (*dockertypes.IDResponse, error)
    StartExec(string, dockertypes.ExecStartCheck, StreamOptions) error
    InspectExec(id string) (*dockertypes.ContainerExecInspect, error)
    AttachToContainer(string, dockertypes.ContainerAttachOptions, StreamOptions) error
    ResizeContainerTTY(id string, height, width uint) error
    ResizeExecTTY(id string, height, width uint) error
    GetContainerStats(id string) (*dockertypes.StatsJSON, error)
}

        发现,其中并没有诸如 KillContainer 的接口,只有一个 StopContainer 方法,

       (也许就是 Stop 不够干脆导致的问题?我的心里萌生了这样的念头)

        继续查找具体实现,在 pkg/kubelet/dockershim/libdocker/kube_docker_client.go 中

// Stopping an already stopped container will not cause an error in dockerapi.
func (d *kubeDockerClient) StopContainer(id string, timeout time.Duration) error {
    ctx, cancel := d.getCustomTimeoutContext(timeout)
    defer cancel()
    err := d.client.ContainerStop(ctx, id, &timeout)
    if ctxErr := contextError(ctx); ctxErr != nil {
        return ctxErr
    }
    return err
}

         源码确实是调用了 docker client 的 ContainerStop 方法

         继续查看 docker client 提供的接口,发现不止提供了 Stop 方法,同样提供了 Kill 方法

func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
    query := url.Values{}
    if timeout != nil {
        query.Set("t", timetypes.DurationToSecondsString(*timeout))
    }
    resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
    ensureReaderClosed(resp)
    return err
}

func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
    query := url.Values{}
    query.Set("signal", signal)

    resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil)
    ensureReaderClosed(resp)
    return err
}

         至此,一个尝试方案是在 StopContainer 函数中,判断 Stop 是否成功,不成功就补一刀,调用 Kill 强制杀死容器

 三、修改源码并编译替换

        修改  pkg/kubelet/dockershim/libdocker/kube_docker_client.go 的 StopContainer 函数,

        增加强制 Kill 逻辑,改后如下

func (d *kubeDockerClient) StopContainer(id string, timeout time.Duration) error {
    ctx, cancel := d.getCustomTimeoutContext(timeout)
    defer cancel()
    err := d.client.ContainerStop(ctx, id, &timeout)
    if err != nil {
        err = d.client.ContainerKill(ctx, id, "SIGKILL")
    }

    if ctxErr := contextError(ctx); ctxErr != nil {
        return ctxErr
    }
    return err
}

          执行 make quick-release 开始编译(这步需要下载相关镜像,需要 Cross the Great Wall,你懂的)

          编译完后,新版的 kubelet 文件位于 _output/release-stage/server/linux-amd64/kubernetes/server/bin

          Kubernetes 二次开发 之 修改 kubelet StopContainer 逻辑

          我们将新版本的 kubelet 替换原来的 kubelet,执行 systemctl restart kubelet 就能启用了

          好了,本文就到此结束了,不对的地方还请指正!