Kubernetes的本质

在前面的四篇文章中,我以 Docker 项目为例,一步步剖析了 Linux 容器的具体实现方式。经过这 些讲解你应该可以明白:一个“容器”,其实是一个由 Linux Namespace、Linux Cgroups 和 rootfs 三种技术构建出来的进程的隔离环境。nginx

从这个结构中咱们不难看出,一个正在运行的 Linux 容器,其实能够被“一分为二”地看待:web

  • 1. 一组联合挂载在 /var/lib/docker/aufs/mnt 上的 rootfs,这一部分咱们称为“容器镜 像”(Container Image),是容器的静态视图;
  • 2. 一个由 Namespace+Cgroups 构成的隔离环境,这一部分咱们称为“容器运行 时”(Container Runtime),是容器的动态视图。

更进一步地说,做为一名开发者,我并不关心容器运行时的差别。由于,在整个“开发 - 测试 - 发 布”的流程中,真正承载着容器信息进行传递的,是容器镜像,而不是容器运行时。docker

这个重要假设,正是容器技术圈在 Docker 项目成功后不久,就迅速走向了“容器编排”这个“上 层建筑”的主要缘由:做为一家云服务商或者基础设施提供商,我只要可以将用户提交的 Docker 镜像以容器的方式运行起来,就能成为这个很是热闹的容器生态图上的一个承载点,从而将整个容 器技术栈上的价值,沉淀在个人这个节点上。数据库

更重要的是,只要从我这个承载点向 Docker 镜像制做者和使用者方向回溯,整条路径上的各个服 务节点,好比 CI/CD、监控、安全、网络、存储等等,都有我能够发挥和盈利的余地。这个逻辑, 正是全部云计算提供商如此热衷于容器技术的重要缘由:经过容器镜像,它们能够和潜在用户 (即,开发者)直接关联起来。 从一个开发者和单一的容器镜像,到无数开发者和庞大的容器集群,容器技术实现了从“容 器”到“容器云”的飞跃,标志着它真正获得了市场和生态的承认。后端

这样,容器就从一个开发者手里的小工具,一跃成为了云计算领域的绝对主角;而可以定义容器组 织和管理规范的“容器编排”技术,则当仁不让地坐上了容器技术领域的“头把交椅”。 这其中,最具表明性的容器编排工具,当属 Docker 公司的 Compose+Swarm 组合,以及 Google 与 RedHat 公司共同主导的 Kubernetes 项目。api

我在前面介绍容器技术发展历史的四篇预习文章中,已经对这两个开源项目作了详细地剖析和评 述。因此,在今天的此次分享中,我会专一于本专栏的主角 Kubernetes 项目,谈一谈它的设计与 架构。安全

跟不少基础设施领域先有工程实践、后有方法论的发展路线不一样,Kubernetes 项目的理论基础则要 比工程实践走得靠前得多,这固然要归功于 Google 公司在 2015 年 4 月发布的 Borg 论文了。 Borg 系统,一直以来都被誉为 Google 公司内部最强大的“秘密武器”。虽然略显夸张,但这个说 法倒不算是吹牛。由于,相比于 Spanner、BigTable 等相对上层的项目,Borg 要承担的责任,是承载 Google 公司 整个基础设施的核心依赖。网络

在 Google 公司已经公开发表的基础设施体系论文中,Borg 项目当仁不 让地位居整个基础设施技术栈的最底层。架构

上面这幅图,来自于 Google Omega 论文的第一做者的博士毕业论文。它描绘了当时 Google 已 经公开发表的整个基础设施栈。在这个图里,你既能够找到 MapReduce、BigTable 等知名项目, 也能看到 Borg 和它的继任者 Omega 位于整个技术栈的最底层。 正是因为这样的定位,Borg 能够说是 Google 最不可能开源的一个项目。而幸运地是,得益于 Docker 项目和容器技术的风靡,它却终于得以以另外一种方式与开源社区见面,这个方式就是 Kubernetes 项目。app

因此,相比于“小打小闹”的 Docker 公司、“旧瓶装新酒”的 Mesos 社区,Kubernetes 项目从 一开始就比较幸运地站上了一个他人难以企及的高度:在它的成长阶段,这个项目每个核心特性 的提出,几乎都脱胎于 Borg/Omega 系统的设计与经验。更重要的是,这些特性在开源社区落地的 过程当中,又在整个社区的协力之下获得了极大的改进,修复了不少当年遗留在 Borg 体系中的缺陷和 问题。

因此,尽管在发布之初被批评是“曲高和寡”,可是在逐渐觉察到 Docker 技术栈的“稚嫩”和 Mesos 社区的“老迈”以后,这个社区很快就明白了:Kubernetes 项目在 Borg 体系的指导下, 体现出了一种独有的“先进性”与“完备性”,而这些特质才是一个基础设施领域开源项目赖以生 存的核心价值。 为了更好地理解这两种特质,咱们不妨从 Kubernetes 的顶层设计提及。

首先,Kubernetes 项目要解决的问题是什么? 编排?调度?容器云?仍是集群管理? 实际上,这个问题到目前为止都没有固定的答案。由于在不一样的发展阶段,Kubernetes 须要着重解 决的问题是不一样的。 可是,对于大多数用户来讲,他们但愿 Kubernetes 项目带来的体验是肯定的:如今我有了应用的 容器镜像,请帮我在一个给定的集群上把这个应用运行起来。 更进一步地说,我还但愿 Kubernetes 能给我提供路由网关、水平扩展、监控、备份、灾难恢复等 一系列运维能力。

等一下,这些功能听起来好像有些耳熟?这不就是经典 PaaS(好比,Cloud Foundry)项目的能力 吗? 并且,有了 Docker 以后,我根本不须要什么 Kubernetes、PaaS,只要使用 Docker 公司的 Compose+Swarm 项目,就彻底能够很方便地 DIY 出这些功能了! 因此说,若是 Kubernetes 项目只是停留在拉取用户镜像、运行容器,以及提供常见的运维功能的 话,那么别说跟“原生”的 Docker Swarm 项目竞争了,哪怕跟经典的 PaaS 项目相比也难有什么 优点可言。

 

 而实际上,在定义核心功能的过程当中,Kubernetes 项目正是依托着 Borg 项目的理论优点,才在短 短几个月内迅速站稳了脚跟,进而肯定了一个以下图所示的全局架构:

 

 

咱们能够看到,Kubernetes 项目的架构,跟它的原型项目 Borg 很是相似,都由 Master 和 Node 两种节点组成,而这两种角色分别对应着控制节点和计算节点。

其中,控制节点,即 Master 节点,由三个紧密协做的独立组件组合而成,它们分别是负责 API 服 务的 kube-apiserver、负责调度的 kube-scheduler,以及负责容器编排的 kube-controllermanager。整个集群的持久化数据,则由 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)。 实际上,kubelet 这个奇怪的名字,来自于 Borg 项目里的同源组件 Borglet。不过,若是你浏览过 Borg 论文的话,就会发现,这个命名方式多是 kubelet 组件与 Borglet 组件的惟一类似之处。因 为 Borg 项目,并不支持咱们这里所讲的容器技术,而只是简单地使用了 Linux Cgroups 对进程进 行限制。 这就意味着,像 Docker 这样的“容器镜像”在 Borg 中是不存在的,Borglet 组件也天然不须要像 kubelet 这样考虑如何同 Docker 进行交互、如何对容器镜像进行管理的问题,也不须要支持 CRI、 CNI、CSI 等诸多容器技术接口。

能够说,kubelet 彻底就是为了实现 Kubernetes 项目对容器的管理能力而从新实现的一个组件, 与 Borg 之间并无直接的传承关系。 备注:虽然不使用 Docker,但 Google 内部确实在使用一个包管理工具,名叫 Midas Package Manager (MPM),其实它能够部分取代 Docker 镜像的角色。

 答案是,Master 节点。 虽然在 Master 节点的实现细节上 Borg 项目与 Kubernetes 项目不尽相同,但它们的出发点却高度 一致,即:如何编排、管理、调度用户提交的做业? 因此,Borg 项目彻底能够把 Docker 镜像看作是一种新的应用打包方式。这样,Borg 团队过去在 大规模做业管理与编排上的经验就能够直接“套”在 Kubernetes 项目上了。

这些经验最主要的表现就是,从一开始,Kubernetes 项目就没有像同时期的各类“容器云”项目 那样,把 Docker 做为整个架构的核心,而仅仅把它做为最底层的一个容器运行时实现。 而 Kubernetes 项目要着重解决的问题,则来自于 Borg 的研究人员在论文中提到的一个很是重要 的观点: 事实也正是如此。 其实,这种任务与任务之间的关系,在咱们日常的各类技术场景中随处可见。

好比,一个 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 项目对容器间的“访问”进行了分类,首先总结出了一类很是常见的“紧密交 互”的关系,即:这些应用之间须要很是频繁的交互和访问;又或者,它们会直接经过本地文件进 行信息交换。 在常规环境下,这些应用每每会被直接部署在同一台机器上,经过 Localhost 通讯,经过本地磁盘 目录交换文件。而在 Kubernetes 项目中,这些容器则会被划分为一个“Pod”,Pod 里的容器共 享同一个 Network Namespace、同一组数据卷,从而达到高效率交换信息的目的。 Pod 是 Kubernetes 项目中最基础的一个对象,源自于 Google Borg 论文中一个名叫 Alloc 的设 计。

在后续的章节中,咱们会对 Pod 作更进一步地阐述。 而对于另一种更为常见的需求,好比 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 项目核心功能的“全景图”。

 

 按照这幅图的线索,咱们从容器这个最基础的概念出发,首先遇到了容器间“紧密协做”关系的难 题,因而就扩展到了 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 项目中,咱们所推崇的使用方法是: 这种使用方法,就是所谓的“声明式 API”。

这种 API 对应的“编排对象”和“服务对象”,都是 Kubernetes 项目中的 API 对象(API Object)。 这就是 Kubernetes 最核心的设计理念,也是接下来我会重点剖析的关键技术点。 最后,我来回答一个更直接的问题: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)。 而后执行:

$ kubectl create -f nginx-deployment.yaml

这样,两个彻底相同的 Nginx 容器副本就被启动了。 不过,这么看来,作一样一件事情,Kubernetes 用户要作的工做也很多嘛。 别急,在后续的讲解中,我会陆续介绍 Kubernetes 项目这种“声明式 API”的种种好处,以及基 于它实现的强大的编排能力。 拭目以待吧。 总结 首先,我和你一块儿回顾了容器的核心知识,说明了容器其实能够分为两个部分:容器运行时和容器 镜像。

 

而后,我重点介绍了 Kubernetes 项目的架构,详细讲解了它如何使用“声明式 API”来描述容器 化业务和容器间关系的设计思想。 实际上,过去不少的集群管理项目(好比 Yarn、Mesos,以及 Swarm)所擅长的,都是把一个容 器,按照某种规则,放置在某个最佳节点上运行起来。这种功能,咱们称为“调度”。 而 Kubernetes 项目所擅长的,是按照用户的意愿和整个系统的规则,彻底自动化地处理好容器之 间的各类关系。这种功能,就是咱们常常听到的一个概念:编排。 因此说,Kubernetes 项目的本质,是为用户提供一个具备广泛意义的容器编排工具。 不过,更重要的是,Kubernetes 项目为用户提供的不只限于一个工具。它真正的价值,乃在于提供 了一套基于容器构建分布式系统的基础依赖。关于这一点,相信你会在从此的学习中,体会的越来 越深。  

相关文章
相关标签/搜索