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

针对Kubernetes集群中出现的容器无法被正常终止的问题,本文分析了kubelet组件的源码,并通过增强CRIshim代码实现了在StopContainer失败后的强制Kill逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景介绍

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

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

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

二、源码分析

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

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

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

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

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

       

        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

          

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

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

       

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值