kubelet是k8s中节点上运行的管理工具,它负责接受api-server发送的调度请求,在Node上创建管理pod,并且向api-server同步节点的状态.这篇文章主要讲讲kubelet组件如何使用docker的.

一.kubecontainer.Runtime

type Kubelet struct {
//....
containerRuntime kubecontainer.Runtime
//...
}
type Runtime interface {
//...
    // Type returns the type of the container runtime.
    Type() string
    // Version returns the version information of the container runtime.
    Version() (Version, error)
// Syncs the running pod into the desired pod.
    SyncPod(pod *v1.Pod, apiPodStatus v1.PodStatus, podStatus *PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) PodSyncResult
    KillPod(pod *v1.Pod, runningPod Pod, gracePeriodOverride *int64) error
    GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error)
//...
}

在类Kubelet中定义了很多的组件和对象,其中containerRuntime就是负责和容器Runtime进行交互的,这里的容器Runtime可以理解为就是docker.我们可以看到, 这个containerRuntime是一个接口类型,这个接口中主要定义了一系列直接操作Pod的函数和获取Runtime信息的函数.其中最关键的函数是SyncPod, 它接受一个Pod的配置信息,启动一次同步,决定启动/更新/删除这个Pod. 目前Runtime接口只有一个实现类,kubeGenericRuntimeManager.

二.kubeGenericRuntimeManager

这个类是kubecontainer.Runtime的实现类,如上述所说,这个类对外暴露的接口是直接对Pod进行管理的.也就是说,这个类对使用者屏蔽了container的概念.对于一个创建Pod的请求,SyncPod函数负责进行处理.这个函数会首先创建一个sandbox,然后依次创建init containers和normal containers. 这里sandbox是用来提供pod层级的namespace隔离,具体细节不在本文的讨论范围之内.kubeGenericRuntimeManager中负责创建container的成员变量是runtimeService.

type kubeGenericRuntimeManager struct {
//...
    // gRPC service clients
    runtimeService internalapi.RuntimeService
//...
}

三.internalapi.RuntimeService

首先,我们观察一下这个接口的定义,这个接口聚合了好几个其它接口,每一个接口定义了一组操作.runtimeService的实例化函数是getRuntimeAndImageServices().这个函数做的事情稍有点复杂.在解析这个函数之前,首先解析下面这个代码片段,为了便于阅读,只列出了核心代码.

    switch containerRuntime {
    case kubetypes.DockerContainerRuntime:
//sentence 1
        ds, err := dockershim.NewDockerService(kubeDeps.DockerClientConfig, crOptions.PodSandboxImage, streamingConfig,
            &pluginSettings, runtimeCgroups, kubeCfg.CgroupDriver, crOptions.DockershimRootDirectory, !crOptions.RedirectContainerStreaming)
//sentence 2
        server := dockerremote.NewDockerServer(remoteRuntimeEndpoint, ds)
        server.Start()
    }
// sentence 3
    runtimeService, imageService, err := getRuntimeAndImageServices(remoteRuntimeEndpoint, remoteImageEndpoint, kubeCfg.RuntimeRequestTimeout)

A. sentence 1

这个函数调用的核心功能就是一句话:连接docker.

docker的守护进程会创建一个gPRC服务接口,本质上它是一个默认定义在/var/run/docker.sock的套接字.NewDockerService()函数首先会连接这个套接字,经过包装后,返回一个dockerService类的实例,该实例的client掌握了和docker守护进程的连接.

B. sentence 2

NewDockerServer函数有两个参数,一个是string类型的remoteRuntimeEndpoint,其默认值是/var/run/dockershim.sock.另一个就是NewDockerService函数构建的dockerService实例.在Start()函数中,首先启动dockerService,然后在endpoint(也就是/var/run/dockershim.sock)上启动一个gRPC服务.函数RegisterRuntimeServiceServer()将s.server上的请求映射到s.service上.

// NewDockerServer creates the dockershim grpc server.
func NewDockerServer(endpoint string, s dockershim.CRIService) *DockerServer {
    return &DockerServer{
        endpoint: endpoint,
        service:  s,
    }
}

// Start starts the dockershim grpc server.
func (s *DockerServer) Start() error {
    // Start the internal service.
  s.service.Start()
    l, err := util.CreateListener(s.endpoint)// Create the grpc server and register runtime and image services.
    s.server = grpc.NewServer(
        grpc.MaxRecvMsgSize(maxMsgSize),
        grpc.MaxSendMsgSize(maxMsgSize),
    )
    runtimeapi.RegisterRuntimeServiceServer(s.server, s.service)
    runtimeapi.RegisterImageServiceServer(s.server, s.service)
    go func() {
        if err := s.server.Serve(l); err != nil {
            glog.Fatalf("Failed to serve connections: %v", err)
        }
    }()
    return nil
}

C. sentence 3

sentence 1建立了和docker的连接,sentence 2在/var/run/dockershim.sock上监听gRPC服务并且将收到的请求转发到sentence 1所构建的和docker的连接上.那么sentence 3所完成的事就很简单了,那就是建立到/var/run/dockershim.sock的连接.

四.小结

kubelet和docker的交互简单来说是通过两层gPRC服务来实现的,kubelet自己在/var/run/dockershim.sock启动一层gPRC服务,然后这个gPRC服务将请求转发给docker守护进程所创建的/var/run/docker.sock上.从而完成一次交互.那么kubelet为什么要再创建一层gPRC服务呢?原因是k8s为了兼容其他的container runtime,提出了CRI(container runtime interface)的概念,其理想的模型是kubelet->[x-runtime].sock.也就是说,所有runtime统一实现一个gPRC接口,kubelet直接调用这个接口,这样屏蔽了底层rutime的差异.而dockershim.sock只是这个CRI概念的docker化实现.

五. 其它container runtime

根据上文我们可以知道,CRI的存在是为了屏蔽container runtime的存在,从而使k8s可以更好的支持多种container runtime,例如rkt.那么其它的kubelet是如何管理其它的runtime呢.原来docker的CRI实现是以dockershim的形式存在于k8s的主线代码中了,其它的runtime需要自己实现类似于dockershim的一套gPRC服务给kubelet访问.

 kubelet ----container-runtime-endpoint选项就是定义这个gPRC服务的地址的.

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐