预警:本期开始涉及kubernetes的实现源代码,也就是干货,请各位深呼吸。
上回我们说到,kubernetes与docker之间的距离,其实就是一个cri接口。
kubernetes在每个工作节点(node)上安装了代理端kubelet,而kubelet本质上就是一个发放启动/停止容器命令的组件。当然,执行启动/停止容器命令的组件,就是容器运行时引擎了。
docker就是最常见的容器运行时引擎,没有之一。在kubernetes 1.3以前的版本中,它和docker是紧耦合的。
什么叫紧耦合呢?
我们前面提到,kubernetes发放容器启停命令的组件叫做kubelet。让我们看看kubelet的实现:
幸运的是,咱们生在一个伟大的时代,越来越多的信息透明化。
在全球最大同性交友网站github上,可以找到大多数开源组件的实现。
我们追溯到kubernetes 0.8:
https://github.com/kubernetes/kubernetes/blob/release-0.8/pkg/kubelet/kubelet.go
这个文件长达近1200行,我们只需要关注从500行开始的这个地方:
func (kl *Kubelet) runContainer(pod *api.BoundPod, container *api.Container, podVolumes volumeMap, netMode string) (id dockertools.DockerID, err error) {
//......
dockerContainer, err := kl.dockerClient.CreateContainer(opts)
if err != nil {
if ref != nil {
record.Eventf(ref, "failed", "failed",
"Failed to create docker container with error: %v", err)
}
return "", err
}
肉眼可见地,kubelet直接调用了docker的API创建容器。
在kubernetes 1.0版本中,这个地方实现终于发生了变化。
https://github.com/kubernetes/kubernetes/blob/release-1.0/pkg/kubelet/kubelet.go
在函数NewMainKubelet中的280行:
// Initialize the runtime.
switch containerRuntime {
case "docker":
// Only supported one for now, continue.
klet.containerRuntime = dockertools.NewDockerManager(
dockerClient,
recorder,
readinessManager,
containerRefManager,
podInfraContainerImage,
pullQPS,
pullBurst,
containerLogsDir,
osInterface,
klet.networkPlugin,
klet,
klet.httpClient,
newKubeletRuntimeHooks(recorder),
dockerExecHandler)
case "rkt":
conf := &rkt.Config{InsecureSkipVerify: true}
rktRuntime, err := rkt.New(
conf,
klet,
recorder,
containerRefManager,
readinessManager,
klet.volumeManager)
if err != nil {
return nil, err
}
klet.containerRuntime = rktRuntime
即使忘光了代码如何写的同学,也可以看出,这个地方kubelet做了一个判断,根据选择的运行时引擎是docker或rkt,来选择容器运行时调用的接口。
显然,如果咱们期望使用docker或rkt以外的第三种容器运行时引擎,在kubernetes 1.0版本中,是行不通的。
在kubernetes 1.5版本中,这个问题通过引入CRI得到了解决。
让我们再一次打开世界最大的同性交友网站:
https://github.com/kubernetes/kubernetes/blob/release-1.5/pkg/kubelet/kubelet.go
还是找到函数NewMainKubelet:
从525行开始的地方,开始出现了CRI相关的内容。
case "docker":
streamingConfig := getStreamingConfig(kubeCfg, kubeDeps)
// Use the new CRI shim for docker.
ds, err := dockershim.NewDockerService(klet.dockerClient, kubeCfg.SeccompProfileRoot, kubeCfg.PodInfraContainerImage, streamingConfig, &pluginSettings, kubeCfg.RuntimeCgroups)
if err != nil {
return nil, err
}
// TODO: Once we switch to grpc completely, we should move this
// call to the grpc server start.
if err := ds.Start(); err != nil {
return nil, err
}
注意到注释内容:"Once we switch to grpc completely..."
实际上,在1.6版本的kubernetes中,才真正开始使用grpc机制实现CRI接口,这一机制也沿用至今。
在下一期中,我们开始拆解kubernetes 1.6引入的grpc机制实现的CRI规范。
我们可以在github上如此方便地通过kubernetes的源码修改历程学习到它的演进历史,做到“知其然,并知其所以然”,是生在我们这个时代的幸运,我们应该珍惜和感激,通过实际行动把这个时代建设得更美好……