王希刚 360云计算
女主宣言
本文作者王希刚主要负责 HULK 云平台容器服务和 Kubernetes 的定制开发。本篇文章主要介绍了 kubelet 服务启动和创建 pod 的流程,给想要阅读 kubelet 源码的同学一个参考。本文最先发布于 opsdev,转载已获取作者授权。
PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!
前言
"Together we will ensure that Kubernetes is a strong and open container management framework for any application and in any environment – whether in a private, public or hybrid cloud," Google senior VP Urs Hölzlesaid in the announcement today.
“
本文 Kubelet 版本: 1.7.4
1
Kubelet 介绍
在 Kubernetes 集群中,在每个 Node 节点上都会启动一个 kubelet 服务进程。该进程用于处理 Master 节点下发到本节点的任务,管理 Pod 及 Pod 中的容器。每个 Kubelet 进程会在 APIServer 上注册节点自身信息,定期向 Master 节点汇报节点资源的使用情况,并通过 cAdvise 监控容器和节点资源。
2
Kubelet 功能
Pod管理
容器的健康检测
容器监控
3
Kubelet 代码结构
➜ kubelet git:(master) tree
.
├── BUILD
├── OWNERS
├── app
│ ├── BUILD
│ ├── OWNERS
│ ├── auth.go
│ ├── options
│ │ ├── BUILD
│ │ ├── container_runtime.go
│ │ ├── options.go
│ │ └── options_test.go
│ ├── plugins.go
│ ├── server.go
│ ├── server_linux.go
│ ├── server_test.go
│ └── server_unsupported.go
└── kubelet.go
2 directories, 15 files
// kubelet 服务的入口 (main)
cmd/kubelet/kubelet.go
// 主要负责校验参数,创建和 api-server 交互的 client 及对运行 kubelet 权限检测,启动 Kubelet 等等
cmd/kubelet/app/server.go
除了入口,kubelet 其它的主要功能实现在 pkg/kubelet 下。这里就不一一介绍了,在下面的时序图中,会标记 pkg 中用到了哪些文件,并主要实现了什么功能。
4
Kubelet服务启动流程
上面的时序图就是整个kubelet的启动流程。
// 主要对 kubelet 的 NewKubeletServer 结构体进行参数校验
validateConfig
// 创建与控制节点 api-server 交互的 client (kubeClient, eventClient)
CreateAPIServerClientConfig
// 对运行 kubelet 进程的用户的权限验证
checkPermission
在 RunKubelet 中主要做 CreateAndInitKubelet 和 startKubelet 两件事:
func CreateAndInitKubelet(...) (k kubelet.KubeletBootstrap, err error) {
k, err = kubelet.NewMainKubelet(....)
if err != nil {
return nil, err
}
k.BirthCry()
k.StartGarbageCollection()
return k, nil
}
NewMainKubelet 实例化一个 kubelet 对象,并对 kubelet 内部各个 component 进行初始化工作,如:
containerGC // 容器的垃圾回收
statusManager // pod 状态的管理
imageManager // 镜像的管路
probeManager // 容器健康检测
gpuManager // GPU 的支持
PodCache // Pod 缓存的管理
secretManager // secret 资源的管理
configMapManager // configMap 资源的管理
InitNetworkPlugin // 网络插件的初始化
// 对 pod 的管理, e.g., CRUD
PodManager
// pod 元数据的来源 (FILE, URL, api-server)
makePodSourceConfig
// 磁盘空间的管理
diskSpaceManager
// 容器运行时的选择(docker 或 rkt)
ContainerRuntime
// 通知 api-server 服务 kubelet 启动
BirthCry
// 开启垃圾回收服务
StartGarbageCollection
当之前所有的预处理工作处理完成之后,准备启动我们的 kubelet 服务 startKubelet:
func startKubelet() {
// start the kubelet
go wait.Until(func() { k.Run(podCfg.Updates()) }, 0, wait.NeverStop)
// start the kubelet server
if kubeCfg.ReadOnlyPort > 0 {
go wait.Until(func() {
k.ListenAndServeReadOnly(net.ParseIP(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort))
}, 0, wait.NeverStop)
}
}
startKubelet 方法中的第一个 goroutine 负责启动 kubelet,在 Run 中主要做的事儿就是启动 diskSpaceManager, volumeManager, statusManager, probeManager 及注册节点等相关服务组件,并最后执行 syncLoop 也就是创建 pod 的入口。
而后面则创建一个 kubelet http server,通过该 server 可以获取 pod 及 node 的相关信息。
5
Pod的创建流程
syncLoop 是 kubelet 的主循环方法,它从不同的管道 (FILE, URL, api-server) 监听到 pod 的变化,并把它们聚合起来。当有新的 pod 变化发生时,它会调用对应的函数,保证 Pod 处于期望的状态。
func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
for {
kl.syncLoopIteration(updates, handler...)
}
}
kl.syncLoopIteration 这个方法会对多个管道进行遍历,如果有 pod 动作,则会调用相应的 Handler。
下面是对应的 Interface.
type SyncHandler interface {
HandlePodAdditions(pods []*v1.Pod)
HandlePodUpdates(pods []*v1.Pod)
HandlePodRemoves(pods []*v1.Pod)
HandlePodReconcile(pods []*v1.Pod)
HandlePodSyncs(pods []*v1.Pod)
HandlePodCleanups() error
}
我们以创建 Pod 为例,它会调用对应的 HandlePodAdditionsHandler 进行处理。HandlePodAdditions 做的任务就是通过 canAdmitPod 方法校验 Pod 能否在该计算节点创建 (e.g., 磁盘空间)。之后把创建 Pod 的事交给 dispatchWork。dispatchWork 主要工作就是把接收到的参数封装成 UpdatePodOptions,调用 UpdatePod 方法.
syncPod 是创建 Pod 的核心逻辑。其中有几个主要的方法:
// 根据最新拿到的 pod 配置与当前运行的容器配置进行对比,计算其中的变化 (一个具体的 hash 值),得到需要重启的容器的信息
computePodContainerChanges
// 创建一个 PodSandBox
createPodSandBox
func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) {
podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt)
......
podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig)
......
return podSandBoxID, "", nil
}
// 获取 PodSandbox 的配置 (e.g., metadata, clusterDNS, 容器的端口映射等)
generatePodSandboxConfig
// 创建并开启一个Pod级别的sandbox
RunPodSandbox
在RunPodSandbox中主要调用如下几个方法:
// 检测用户是否设置了自己的 pause 镜像,如果没有设置则使用默认的镜像 gcr.io/google_containers/pause-amd64:3.0
ensureSandboxImageExists
// 生成创建 pause 容器的配置信息
makeSandboxDockerConfig
CreateContainer // 创建容器
StartContainer // 启动容器
// 设置容器的网络 (kubelet 加载 cni 插件对容器的网络进行设置等)
network.SetUpPod
上面的这些操作就把我们 Pod 中的第一个 pause 容器创建并启动了。之后要做的就是把该 Pod 中的其它业务容器逐一的启动。但是在启动真正的业务容器之前,首先会检查用户是否设置了 init_container。如果设置了,则会按 init_container 设置的顺序依次的执行 init_container (注意: 当其中的 init_container 执行失败了,则 Pod 会异常,并且业务容器不会被创建)。当 init_container 执行完成之后,我们真正的业务容器才会被逐一的启动。
业务容器启动的逻辑和 Pod 的初始化 pause 容器的启动的流程基本一致。下面的代码是循环的启动业务容器:
for idx := range podContainerChanges.ContainersToStart {
container := &pod.Spec.Containers[idx]
//.....
if msg, err := m. startContainer(podSandboxID, podSandboxConfig, container, pod, podStatus, pullSecrets, podI; err != nil {
//.....
continue
}
}
在startContainer方法中主要调用如下方法:
// 检查业务镜像是否存在,不存在则到 Docker/Private Registry 拉取镜像
EnsureImageExists
// 生成业务容器的配置信息
generateContainerConfig
// 通过 client.CreateContainer 调用 docker engine-api 创建业务容器
CreateContainer
StartContainer //启动业务容器
// 这个方法的主要作用就是在业务容器起来的时候,首先会执行一个container hook(PostStart和PreStop),做一些预处理工作。只有container kook执行成功才会运行具体的业务服务,否则容器异常。
runner.Run
这样 Pod 大体的启动流程就描述完了。
6
总结
kubelet 的主干道已经大体介绍完了,kubelet 还有许多中间服务,如volumeManager, diskSpaceManager, secretManager, configMapManager 以及节点注册等等,接下来有时间会把各个功能组件的基本实现原理介绍给大家。