通过前几片文章,我们不难看出一个正在运行的 Linux 容器,其实可以被“一分为二”地看待:
- 一组联合挂载在 /var/lib/docker/aufs/mnt 上的 rootfs,这一部分我们称为“容器镜像”(Container Image),是容器的静态视图;
- 一个由 Namespace+Cgroups 构成的隔离环境,这一部分我们称为“容器运行时”(Container Runtime),是容器的动态视图。
了解了Linux容器的原理,我们就开始讲讲 Kubernetes
首先,Kubernetes 项目要解决的问题是什么?
编排?调度?容器云?还是集群管理?实际上,这个问题到目前为止都没有固定的答案。
对于大多数用户来说,我们希望 Kubernetes 项目带来的体验是:现在我有了应用的容器镜像,请帮我在一个给定的集群上把这个应用运行起来。
更进一步说,我还希望 Kubernetes 能给我提供路由网关、水平扩展、灾难恢复等一系列运维能力。
以下是 Kubernetes 的全局架构图:
我们可以看到 Kubernetes 的项目架构是由 Master 和 Node 两种节点组成,这两种角色分别对应着控制节点和计算节点。
其中,控制节点,即 Master 节点,由三个紧密协作的独立组件组合而成。负责API服务的 kube-apiserver、负责调度的 kube-scheduler、以及负责容器编排的 kube-controller-manager。整个集群的的持久化数据,由 kube-apiserver 处理后保存在Etcd中。而计算节点上最核心的部分,则是一个叫作 Kubelet 的组件。
在 Kubernetes 项目中, kubelet 主要负责同容器运行时(比如 Docker 项目)打交道。 而这个交互所依赖的,是一个称作 CRI(Container Runtime Interface)的远程调用接口,这个接口定义了容器运行时的各项核心操作,比如:启动一个容器需要的所有参数。
所以, Kubernetes 项目并不关心你部署的是什么容器运行时、使用的什么技术实现,只要你的这个容器运行时能够运行标准的容器镜像,它就可以通过实现 CRI 接入到 Kubernetes 项目当中。
而具体的容器运行时,比如 Docker 项目,一般通过 OCI 这个容器运行时规范同底层的Linux操作系统进行交互,即:把 CRI 请求翻译成对 Linux 操作系统的调用(操作 Linux Namespace 和 Cgroups 等)。
此外,kubelet 还通过 gRPC 协议同一个叫作 Device Plugin 的插件进行交互。这个插件,是 Kubernetes 项目用来管理 GPU 等宿主机物理设备的主要组件,也是基于 Kubernetes 项目进行机器学习训练、高性能作业支持等工作必须关注的功能。
而 kubelet 的另一个重要功能,则是调用网络插件和存储插件为容器配置网络和持久化存储。 这两个插件与 kubelet 进行交互的接口,分别是 CNI(Container Networking Interface)和 CSI(Container Storage Interface)。
从一开始,Kubernetes 项目就没有像同时期的各种“容器云”项目那样,把 Docker 作为整个架构的核心,而仅仅把它作为最底层的一个容器运行时实现。
Kubernetes 项目着重解决的是:运行在大规模集群中的各种任务之间,实际上存在着各种各样的关系。这些关系的处理,才是作业编排和管理系统最困难的地方。
在常规环境下,相关联的应用往往会被直接部署在同一台机器上,通过 Localhost 通信,通过本地磁盘目录交换文件。而在 Kubernetes 项目中,这些容器则会被划分为一个“Pod”,Pod 里的容器共享同一个 Network Namespace、同一组数据卷,从而达到高效率交换信息的目的。
Pod 是 Kubernetes 项目中最基础的一个对象
对于 Web 应用与数据库的需求,Kubernetes 项目则提供了一种叫作“Service”的服务。像这样的两个应用,往往故意不部署在同一台机器上,这样即使 Web 应用所在的机器宕机了,数据库也完全不受影响。但是对于一个容器来说,它的 IP 地址等信息不是固定的,那么 Web 应用又怎么找到数据库容器的 Pod 呢?Kubernetes 项目的做法是给 Pod 绑定一个 Service 服务。 service服务声明的IP地址是终生不变的,而这个 Service 服务的主要作用,就是作为 Pod 的代理入口(Portal),从而代替 Pod 对外暴露一个固定的网络地址。
这样,对于 Web 应用的 Pod 来说,它需要关心的就是数据库 Pod 的 Service 信息。不难想象,Service 后端真正代理的 Pod 的 IP 地址、端口等信息的自动更新、维护,则是 Kubernetes 项目的职责。
像这样,围绕着容器和 Pod 不断向真实的技术场景扩展,我们就能够摸索出一幅如下所示的 Kubernetes 项目核心功能的“全景图”。
在 Kubernetes 项目中,我们所推崇的使用方法是:
- 首先,通过一个“编排对象”,比如 Pod、Job、CronJob 等,来描述你试图管理的应用;
- 然后,再为它定义一些“服务对象”,比如 Service、Secret、Horizontal Pod Autoscaler(自动水平扩展器)等。
这些对象,会负责具体的平台级功能。这种使用方法,就是所谓的“声明式 API”。这种 API 对应的“编排对象”和“服务对象”,都是 Kubernetes 项目中的 API 对象(API Object)。