kubelet分析
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服务的地址的.