Kubernetes的本质
kubernetes项目要解决的问题是什么?
编排?调度?容器云?还是集群管理?这个问题到目前为止都没有固定的答案。因为在不同的发展阶段,Kubernetes 需要着重解决的问题是不同的。
对于用户来说,希望kubernetes能给我提供路由网关、水平扩展、监控、备份、灾难恢复等一系列运维能力。(经典paas能力)
代表性的容器编排工具
- Docker 公司的 Compose+Swarm 组合。
- Google 与 RedHat 公司共同主导的 Kubernetes 项目。
在定义核心功能的过程中,Kubernetes 项目正是依托着 Borg 项目的理论优势,才在短短几个月内迅速站稳了脚跟,进而确定了一个如下图所示的全局架构:

kubernetes项目的架构与borg非常类似, 都是由master和node两种节点组成,这两种觉得分别对应着控制节点和计算节点。
其中控制节点,既master节点, 和三个协作的独立组件组合而成,他们分别是负责api服务的kube-apiserver、负责调度的kube-scheduler,以及负责容器编排的kube-controller-manager。整个集群的持久化数据,则由kube-apiserver处理后保存在etcd中
计算节点最核心的部分,是一个叫作kubelet的组件。
在kubernetes项目中, kubelet主要负责同容器运行时打交道,而这个交互所依赖的,是一个称作CRI(container runtime interface)的远程调用接口, 而这接口定义了容器运行时的各个核心操作, 比如 启动一个容器需要的所有参数。
kubernetes项目并不关心你部署的是什么容器运行时, 使用的什么技术实现,只要这个容器能够运行标准的容器镜像,他就可以通过实现CRI接入到kubernetes项目中。
具体的容器运行时,一般通过oci这个容器运行时规范同底层的Linux操作系统进行交互,也就是说吧CRI请求翻译成对Linux操作系统的调用(操作Linux namespace 和cgroups等)。
此外,kubelet还通过grpc协议同一个叫作device plugin的插件进行交互。这个插件主要是用来管理等宿主机物理设备的重要组件,也是基于kubernetes项目进行机器学习训练、高性能作业支持等工作必须关注的功能。
kubelet的另一个重要功能,则是调用网络插件和存储插件为容器配置网络和持久化存储。 这俩插件与kubelet进行交互的接口,分别是CNI(Container Networking Interface) 和CSI(Container Storage Interface)
borg项目与kubernetes项目
kubectl来自与borg项目里的同源组件borglet,这一点也是唯一相似之处
borg项目,并不支持我们所将的容器技术,而只是简单的使用了linux cgroups对进程进行限制。所以borglet组件不需要像kubelet考虑docker进行交互, 如何对容器镜像进行管理,也不需要支持CRI、CNI、CSI等诸多容器技术接口。
可以说,kubelet完全就是为了实现kubernetes项目对容器管理能力而重新实现的一个组件, 与borg之前没有直接的传承关系
备注:borg虽然不使用docker,但google内部确实在使用一个名为midas package manager(MPM),其实他可以部分取代docker镜像角色。
borg对与kubernetes项目知道作用体现在master节点。
虽然master节点实现细节两个项目不尽相同,但他们出发点高度一直:如何编排、管理、调度用户提交的作业。
Borg 项目完全可以把 Docker 镜像看作一种新的应用打包方式。这样,Borg 团队过去在大规模作业管理与编排上的经验就可以直接“套”在 Kubernetes 项目上了。
Kubernetes 项目要着重解决的问题,则来自于 Borg 的研究人员在论文中提到的一个非常重要的观点:
运行在大规模集群中的各种任务之间,实际上存在着各种各样的关系。这些关系的处理,才是作业编排和管理系统最困难的地方。
pod的生命周期

我们平常的各种技术场景的任务与任务之间的关系有哪些
比如:一个 Web 应用与数据库之间的访问关系,一个负载均衡器和它的后端服务之间的代理关系,一个门户应用与授权组件之间的调用关系。同属于一个服务单位的不同功能之间,也完全可能存在这样的关系。比如,一个 Web 应用与日志搜集组件之间的文件交换关系。
传统虚拟机任务与任务之间关系
传统虚拟机环境对这种关系的处理方法都是比较“粗粒度”的。你会经常发现很多功能并不相关的应用被一股脑儿地部署在同一台虚拟机中,只是因为它们之间偶尔会互相发起几个 HTTP 请求。一个常见的情况,应用被部署在虚拟机里之后,你还得手动维护很多跟它协作的守护进程(Daemon),用来处理它的日志搜集、灾难恢复、数据备份等辅助工作。
容器技术任务与任务之间关系
那些原先拥挤在同一个虚拟机里的各个应用、组件、守护进程,都可以被分别做成镜像,然后运行在一个个专属的容器中。它们之间互不干涉,拥有各自的资源配额,可以被调度在整个集群里的任何一台机器上。而这,正是一个 PaaS 系统最理想的工作状态,也是所谓“微服务”思想得以落地的先决条件。当然,如果只做到“封装微服务、调度单容器”这一层次,Docker Swarm 项目就已经绰绰有余了。如果再加上 Compose 项目,你甚至还具备了处理一些简单依赖关系的能力,比如:一个“Web 容器”和它要访问的数据库“DB 容器”。在 Compose 项目中,你可以为这样的两个容器定义一个“link”,而 Docker 项目则会负责维护这个“link”关系,其具体做法是:Docker 会在 Web 容器中,将 DB 容器的 IP 地址、端口等信息以环境变量的方式注入进去,供应用进程使用
例如:
DB_NAME=/web/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
DB_PORT_5432_TCP_PROTO=tcp
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_ADDR=172.17.0.5
而当 DB 容器发生变化时(比如,镜像更新,被迁移到其他宿主机上等等),这些环境变量的值会由 Docker 项目自动更新。这就是平台项目自动地处理容器间关系的典型例子。
不足点:
如果我们现在的需求是,要求这个项目能够处理前面提到的所有类型的关系,甚至还要能够支持未来可能出现的更多种类的关系呢?这时,“link”这种单独针对一种案例设计的解决方案就太过简单了。如果你做过架构方面的工作,就会深有感触:一旦要追求项目的普适性,那就一定要从顶层开始做好设计。
kubernetes务与任务之间关系
Kubernetes 项目最主要的设计思想是,从更宏观的角度,以统一的方式来定义任务之间的各种关系,并且为将来支持更多种类的关系留有余地。
场景:
比如,Kubernetes 项目对容器间的“访问”进行了分类,首先总结出了一类非常常见的“紧密交互”的关系,即:这些应用之间需要非常频繁的交互和访问;又或者,它们会直接通过本地文件进行信息交换。
解决方案:
在 Kubernetes 项目中,这些容器则会被划分为一个“Pod”,Pod 里的容器共享同一个 Network Namespace、同一组数据卷,从而达到高效率交换信息的目的。
Pod 是 Kubernetes 项目中最基础的一个对象,源自于 Google Borg 论文中一个名叫 Alloc 的设计。
常见场景之 Web 应用与数据库之间的访问关系。
kubernetes解决方案:
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核心功能全景图

我们从容器这个最基础的概念出发,首先遇到了容器间“紧密协作”关系的难题,于是就扩展到了 Pod;有了 Pod 之后,我们希望能一次启动多个应用的实例,这样就需要 Deployment 这个 Pod 的多实例管理器;而有了这样一组相同的 Pod 后,我们又需要通过一个固定的 IP 地址和端口以负载均衡的方式访问它,于是就有了 Service。
问题:
如果现在两个不同 Pod 之间不仅有“访问关系”,还要求在发起时加上授权信息。最典型的例子就是 Web 应用对数据库访问时需要 Credential(数据库的用户名和密码)信息。那么,在 Kubernetes 中这样的关系又如何处理呢?
答案:
kubernetes 项目提供了一种叫作 Secret 的对象,它其实是一个保存在 Etcd 里的键值对数据。这样,你把 Credential 信息以 Secret 的方式存在 Etcd 里,Kubernetes 就会在你指定的 Pod(比如,Web 应用的 Pod)启动时,自动把 Secret 里的数据以 Volume 的方式挂载到容器里。这样,这个 Web 应用就可以访问数据库了。
应用与应用之间的关系外,应用运行的形态是影响“如何容器化这个应用”的第二个重要因素。
Kubernetes 定义了新的、基于 Pod 改进后的对象。比如 Job,用来描述一次性运行的 Pod(比如,大数据任务);再比如 DaemonSet,用来描述每个宿主机上必须且只能运行一个副本的守护进程服务;又比如 CronJob,则用于描述定时任务等等。如此种种,正是 Kubernetes 项目定义容器间关系和形态的主要方法。
可以看到,Kubernetes 项目并没有像其他项目那样,为每一个管理功能创建一个指令,然后在项目中实现其中的逻辑。这种做法,的确可以解决当前的问题,但是在更多的问题来临之后,往往会力不从心。
相比之下,在 Kubernetes 项目中,我们所推崇的使用方法是:
首先,通过一个“编排对象”,比如 Pod、Job、CronJob 等,来描述你试图管理的应用;
然后,再为它定义一些“服务对象”,比如 Service、Secret、Horizontal Pod Autoscaler(自动水平扩展器)等。这些对象,会负责具体的平台级功能。
这种使用方法,就是所谓的“声明式 API”。这种 API 对应的“编排对象”和“服务对象”,都是 Kubernetes 项目中的 API 对象(API Object)。
使用Kubernetes 启动一个容器化任务
已经制作好了一个 Nginx 容器镜像,希望让平台帮我启动这个镜像。并且,我要求平台帮我运行两个完全相同的 Nginx 副本,以负载均衡的方式共同对外提供服务。
-
如果是自己 DIY 的话,可能需要启动两台虚拟机,分别安装两个 Nginx,然后使用 keepalived 为这两个虚拟机做一个虚拟 IP。
-
而如果使用 Kubernetes 项目呢?你需要做的则是编写如下这样一个 YAML 文件(比如名叫 nginx-deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
在上面这个 YAML 文件中,我们定义了一个 Deployment 对象,它的主体部分(spec.template 部分)是一个使用 Nginx 镜像的 Pod,而这个 Pod 的副本数是 2(replicas=2)。
创建pod
kubectl create -f nginx-deployment.yaml
这样,两个完全相同的 Nginx 容器副本就被启动了。
总结
容器其实可以分为两个部分:容器运行时和容器镜像。
然后重点介绍了 Kubernetes 项目的架构,详细讲解了它如何使用“声明式 API”来描述容器化业务和容器间关系的设计思想。
实际上,过去很多的集群管理项目(比如 Yarn、Mesos,以及 Swarm)所擅长的,都是把一个容器,按照某种规则,放置在某个最佳节点上运行起来。这种功能,我们称为“调度”。
而 Kubernetes 项目所擅长的,是按照用户的意愿和整个系统的规则,完全自动化地处理好容器之间的各种关系。这种功能,就是我们经常听到的一个概念:编排。
Kubernetes 项目的本质,是为用户提供一个具有普遍意义的容器编排工具。更重要的是,Kubernetes 项目为用户提供的不仅限于一个工具。它真正的价值,乃在于提供了一套基于容器构建分布式系统的基础依赖。
前几篇文章,主要阐述了这样一个思想:要真正发挥容器技术的实力,你就不能仅仅局限于对 Linux 容器本身的钻研和使用。

浙公网安备 33010602011771号