引言

Kubernetes 是一个功能强大的开源系统,最初由 Google 开发,用于管理集群环境中的集装箱化应用程序。 它旨在提供更好的方法来管理跨越不同基础设施的相关的、分布式的组件和服务。

在本指南中,我们将讨论库伯内特斯的一些基本概念。 我们将讨论系统的体系结构、它解决的问题,以及它用于处理集装箱式部署和扩展的模型。

什么是库伯内特斯?

在它的基本层次上,Kubernetes 是一个跨集群机器运行和协调集装箱化应用程序的系统。 它是一个旨在完全管理集装箱化应用程序和服务的生命周期的平台,使用的方法提供可预测性、可扩展性和高可用性。

作为一个 Kubernetes 用户,您可以定义应用程序应该如何运行,以及它们应该能够与其他应用程序或外部世界交互的方式。 您可以向上或向下扩展服务,执行优雅的滚动更新,并在不同版本的应用程序之间切换流量,以测试特性或回滚有问题的部署。 Kubernetes 提供了接口和可组合平台原语,允许您以高度的灵活性、能力和可靠性定义和管理应用程序。

库伯内特斯建筑

为了理解 Kubernetes 是如何提供这些功能的,了解它是如何在高层次上进行设计和组织的,这是很有帮助的。 可以将 Kubernetes 可视化为一个内置层的系统,每个高层抽象出低层的复杂性。

在其基础上,Kubernetes 将单个物理或虚拟机集成到一个集群中,使用共享网络在每个服务器之间进行通信。 这个集群是配置所有 Kubernetes 组件、功能和工作负载的物理平台。

集群中的每台机器在库伯内特生态系统中都被赋予了一个角色。 一个服务器(或高可用部署中的一个小组)充当主服务器。 这个服务器充当集群的网关和大脑,它为用户和客户机公开 API,检查其他服务器的健康状况,决定如何最佳地分配和分配工作(称为“调度”) ,以及编排其他组件之间的通信。 主服务器充当与集群的主要联系点,并负责大部分集中化的 Kubernetes 逻辑。

集群中的其他机器被指定为节点: 使用本地和外部资源负责接受和运行工作负载的服务器。 为了帮助实现隔离、管理和灵活性,Kubernetes 在容器中运行应用程序和服务,因此每个节点都需要配备一个容器运行时(如 Docker 或 rkt)。 节点从主服务器接收工作指令,并相应地创建或销毁容器,调整网络规则以适当地路由和转发通信。

如上所述,应用程序和服务本身是在容器内的集群上运行的。 基础组件确保应用程序的所需状态与集群的实际状态匹配。 用户通过直接与主 API 服务器或与客户机和库通信与集群进行交互。 要启动应用程序或服务,需要在 JSON 或 YAML 中提交声明性计划,定义要创建什么以及如何管理它。 然后,主服务器接受计划,并通过检查需求和系统的当前状态,确定如何在基础设施上运行计划。 这组根据指定计划运行的用户定义应用程序代表了 Kubernetes 的最终层。

主服务器组件

正如我们在上面所描述的,主服务器充当了 Kubernetes 集群的主控制平面。 它作为管理员和用户的主要联系点,还为相对简单的工作者节点提供许多集群范围的系统。 总体而言,主服务器上的组件共同工作,以接受用户请求、确定调度工作负载容器的最佳方式、对客户端和节点进行身份验证、调整整个集群范围的网络以及管理扩展和健康检查职责。

这些组件可以安装在单台机器上,也可以分布在多个服务器上。 在本节中,我们将查看与主服务器关联的每个单独组件。

Etcd

Kubernetes 需要运行的基本组件之一是全局可用的配置存储。 由 CoreOS 团队开发的 etcd 项目是一个轻量级的分布式键值存储,可以配置为跨多个节点。

Kubernetes 使用 etcd 存储集群中每个节点可以访问的配置数据。 这可用于服务发现,并可帮助组件根据最新信息配置或重新配置自己。 它还通过诸如领导者选举和分布式锁定等特性帮助维护集群状态。 通过提供一个简单的 http / json API,设置或检索值的接口非常直观。

与控制平面中的大多数其他组件一样,etcd 可以配置在单个主服务器上,或者在生产场景中,分布在多台机器上。 唯一的要求是每台 Kubernetes 机器都可以访问网络。

kube-apiserver

最重要的主服务之一是 API 服务器。 这是整个集群的主要管理点,因为它允许用户配置 Kubernetes 的工作负载和组织单元。 它还负责确保 etcd 存储和已部署容器的服务细节一致。 它充当各个组件之间的桥梁,以维护集群的健康状态并发布信息和命令。

Api 服务器实现了一个 RESTful 接口,这意味着许多不同的工具和库可以轻松地与它通信。 一个名为 kubectl 的客户机可以作为从本地计算机与 Kubernetes 集群进行交互的默认方法。

库贝-控制器-经理

控制器经理是一个总务,有许多责任。 它主要管理不同的控制器,这些控制器调节集群的状态、管理工作负载生命周期和执行例行任务。 例如,复制控制器确保为 pod 定义的副本数量(相同的副本)与当前部署在集群上的副本数量相匹配。 这些操作的详细信息被写到 etcd 中,控制器管理器在其中通过 API 服务器监视更改。

当出现更改时,控制器读取新信息并实现满足所需状态的过程。 这可以包括向上或向下扩展应用程序、调整端点等。

库贝调度程序

将工作负载实际分配给集群中特定节点的过程是调度程序。 该服务读取工作负载的操作需求,分析当前的基础设施环境,并将工作放置在可接受的节点上。

调度程序负责跟踪每个主机上的可用容量,以确保工作负载的调度不会超过可用资源。 调度程序必须知道总容量以及已经分配给每个服务器上现有工作负载的资源。

云控制器-管理器

可以在许多不同的环境中部署 Kubernetes,并且可以与各种基础设施提供程序交互,以理解和管理集群中的资源状态。 虽然 Kubernetes 使用可附加存储和负载平衡器等资源的通用表示,但是它需要一种方法将这些表示映射到非同类云提供者提供的实际资源。

云控制器管理器扮演着粘合剂的角色,允许 Kubernetes 在内部维护相对通用的结构的同时,与具有不同功能、特性和 api 的供应商进行交互。 这使得 Kubernetes 可以根据从云服务提供商收集的信息更新其状态信息,根据系统需要调整云资源,并创建和使用额外的云服务来满足提交给集群的工作需求。

节点服务器组件

在 Kubernetes,通过运行容器执行工作的服务器被称为节点。 节点服务器有一些与主组件通信、配置容器网络和运行分配给它们的实际工作负载所必需的要求。

一个容器运行时

每个节点必须具有的第一个组件是容器运行时。 通常,通过安装和运行 Docker 可以满足这个需求,但是也可以使用 rkt 和 runc 等替代方法。

容器运行时负责启动和管理容器,即封装在相对隔离但轻量级的操作环境中的应用程序。 集群上的每个工作单元在其基本级别上实现为必须部署的一个或多个容器。 每个节点上的容器运行时是最终运行在提交给集群的工作负载中定义的容器的组件。

库贝莱特

每个节点与集群组的主要联系点是一个名为 kubelet 的小型服务。 该服务负责将信息中继到控制平面服务,并与 etcd 存储交互以读取配置细节或写入新值。

Kubelet 服务与主组件通信,以便对集群进行身份验证并接收命令和工作。 工作以清单的形式被接收,清单定义了工作负载和操作参数。 Kubelet 进程负责维护节点服务器上的工作状态。 它控制容器运行时,以根据需要启动或销毁容器。

kube-proxy

为了管理单个主机子网并使服务对其他组件可用,在每个节点服务器上运行一个名为 kube-proxy 的小型代理服务。 这个过程将请求转发到正确的容器,可以进行基本的负载平衡,并且通常负责确保网络环境是可预测的和可访问的,但是在适当的地方是隔离的。

对象和工作负载

虽然容器是用于部署应用程序的底层机制,但是 Kubernetes 在容器接口上使用额外的抽象层来提供伸缩性、弹性和生命周期管理特性。 用户不是直接管理容器,而是定义和交互由 Kubernetes 对象模型提供的各种原语组成的实例。 我们将在下面介绍可用于定义这些工作负载的不同类型的对象。

豆荚

吊舱是库伯内特斯处理的最基本单位。 容器本身不分配给主机。 相反,一个或多个紧密耦合的容器被封装在一个称为 pod 的对象中。

一个 pod 通常表示一个或多个应该作为单个应用程序控制的容器。 吊舱由紧密操作在一起的容器组成,它们共享一个生命周期,并且应该始终安排在同一个节点上。 它们完全作为一个单元来管理,并共享它们的环境、卷和 IP 空间。 尽管它们是集装箱式的实现,但是您通常应该将吊舱看作是一个单一的、单一的应用程序,以便最好地概念化集群将如何管理吊舱的资源和调度。

通常,吊舱由一个满足工作负载一般用途的主容器和一些可选的助手容器组成,这些助手容器有助于完成密切相关的任务。 这些程序可以在自己的容器中运行和管理,但是它们与主应用程序紧密相连。 例如,当在外部存储库中检测到更改时,一个 pod 可能有一个运行主应用程序服务器的容器和一个 helper 容器将文件下拉到共享文件系统。 水平缩放通常不鼓励在 pod 级别,因为还有其他更高级别的对象更适合这个任务。

通常,用户不应该自己管理吊舱,因为它们不提供应用程序中通常需要的一些特性(如复杂的生命周期管理和扩展)。 相反,鼓励用户使用更高级别的对象,这些对象使用 pods 或 pod 模板作为基本组件,但实现了其他功能。

复制控制器和复制集

通常,当与库伯内特斯一起工作时,而不是与单个吊舱一起工作时,你将转而管理一组相同的、复制的吊舱。 这些都是根据 pod 模板创建的,可以由称为复制控制器和复制集的控制器进行水平扩展。

复制控制器是一个对象,它定义 pod 模板和控制参数,以通过增加或减少运行副本的数量来水平扩展 pod 的相同副本。 这是在 Kubernetes 本地分配负载和增加可用性的一种简单方法。 复制控制器知道如何根据需要创建新的 pod,因为在复制控制器配置中嵌入了一个与 pod 定义非常相似的模板。

复制控制器负责确保集群中部署的吊舱数量与其配置中的吊舱数量相匹配。 如果一个 pod 或者底层主机出现故障,控制器将启动新的 pods 进行补偿。 如果控制器配置中的副本数量发生变化,控制器将启动或终止容器以匹配所需的数量。 复制控制器还可以执行滚动更新,将一组吊舱逐个滚动到新版本,从而最小化对应用程序可用性的影响。

复制集是复制控制器设计中的一个迭代,在控制器如何识别要管理的 pods 方面具有更大的灵活性。 复制集开始取代复制控制器,因为它们具有更强的副本选择能力,但它们不能进行滚动更新,以便将后端循环到新版本,就像复制控制器可以做到的那样。 相反,复制集意味着要在提供该功能的额外的、更高级别的单元中使用。

像 pods 一样,复制控制器和复制集很少是您直接使用的单位。 虽然它们建立在吊舱设计之上,以增加水平扩展和可靠性保证,但它们缺乏一些在更复杂对象中发现的细粒度生命周期管理功能。

部署

部署是直接创建和管理的最常见的工作负载之一。 部署使用复制集作为构建块,为其添加了灵活的生命周期管理功能。

虽然使用复制集构建的部署看起来可能会重复复制控制器提供的功能,但部署解决了滚动更新实现中存在的许多问题。 在使用复制控制器更新应用程序时,用户需要提交一个新的复制控制器计划,该计划将替换当前控制器。 在使用复制控制器时,跟踪历史记录、在更新期间从网络故障中恢复以及回滚错误更改等任务要么很困难,要么留给用户负责。

部署是一个高级对象,旨在简化复制吊舱的生命周期管理。 部署可以通过更改配置轻松修改,Kubernetes 将调整副本集,管理不同应用程序版本之间的转换,并可选择自动维护事件历史记录和撤销功能。 由于这些特性,部署很可能是您使用最频繁的 Kubernetes 对象类型。

有状态集

有状态集是专门的 pod 控制器,可以提供排序和唯一性保证。 当您有与部署顺序、持久数据或稳定网络相关的特殊需求时,主要用于具有更细粒度的控制。 例如,有状态集通常与面向数据的应用程序相关联,比如数据库,即使重新安排到新的节点,它们也需要访问相同的卷。

有状态集通过为每个 pod 创建一个唯一的、基于数字的名称来提供一个稳定的网络标识符,即使 pod 需要移动到另一个节点,这个名称也将持续存在。 同样,当需要重新调度时,可以使用 pod 传输持久存储卷。 为了防止意外的数据丢失,即使在 pod 被删除之后,卷仍然存在。

当部署或调整规模时,有状态集根据其名称中的编号标识符执行操作。 这提供了对执行顺序的更大的可预测性和控制,这在某些情况下是有用的。

守护程序集

Daemon 集是 pod 控制器的另一种特殊形式,它在集群中的每个节点(或子集,如果指定的话)上运行 pod 的副本。 这在部署帮助执行维护和为节点本身提供服务的吊舱时最为有用。

例如,收集和转发日志、聚合度量以及运行增加节点本身功能的服务是守护进程集合的热门候选对象。 因为守护进程集通常提供基本的服务,并且整个舰队都需要它,所以它们可以绕过 pod 调度限制,避免其他控制器将 pod 分配给特定的主机。 例如,由于其独特的职责,主服务器经常被配置为不可用于正常的 pod 调度,但是守护进程集有能力在逐个 pod 的基础上覆盖限制,以确保基本服务正在运行。

乔布斯和克隆乔布斯

到目前为止,我们所描述的工作负载都假定为一个长时间运行的类似服务的生命周期。 Kubernetes 使用一个称为作业的工作负载来提供一个更基于任务的工作流,在这个工作流中,运行中的容器在完成工作一段时间后将成功退出。 如果您需要执行一次性或批处理而不是运行连续的服务,那么作业非常有用。

建立在工作岗位之上的工作岗位是临时工作岗位。 与 Linux 和类 unix 系统上按时间表执行脚本的传统 cron 守护进程一样,Kubernetes 的 cron 作业提供了一个带有调度组件的用于运行作业的接口。 Cron 作业可用于安排将来要执行的作业,或定期重复执行的作业。 Kubernetes cron 作业基本上是经典 cron 行为的重新实现,使用集群作为平台,而不是单个操作系统。

其他 Kubernetes 元件

除了可以在集群上运行的工作负载之外,Kubernetes 还提供了许多其他抽象,帮助您管理应用程序、控制网络和启用持久性。 我们将在这里讨论一些更常见的例子。

服务

到目前为止,我们一直在使用传统的、类 unix 意义上的术语“服务” : 来表示长时间运行的进程,这些进程通常连接到网络,能够响应请求。 然而,在 Kubernetes,服务是一个组件,充当 pods 的基本内部负载均衡器和大使。 服务将执行相同功能的豆荚逻辑集合组合在一起,将它们表示为单个实体。

这允许您部署一个服务,该服务可以跟踪并路由到特定类型的所有后端容器。 内部使用者只需知道服务提供的稳定端点。 同时,服务抽象允许您根据需要扩展或替换后端工作单元。 一个服务的 IP 地址保持稳定,不管它路由到哪个豆荚。 通过部署服务,您可以轻松地获得可发现性,并且可以简化容器设计。

任何时候需要向其他应用程序或外部消费者提供对一个或多个 pods 的访问时,都应该配置服务。 例如,如果您有一组运行 web 服务器的豆荚,这些服务应该可以从互联网上访问,那么服务将提供必要的抽象。 同样,如果您的 web 服务器需要存储和检索数据,那么您需要配置一个内部服务,让它们访问您的数据库吊舱。

虽然默认情况下,服务只能使用内部可路由的 IP 地址,但是可以通过选择几种策略中的一种使服务在集群之外可用。 Nodeport 配置通过在每个节点的外部网络接口上打开一个静态端口来工作。 到外部端口的流量将使用内部集群 IP 服务自动路由到适当的豆荚。

或者,LoadBalancer 服务类型创建一个外部负载均衡器,使用云提供商的 Kubernetes 负载均衡器集成路由到服务。 云控制器管理器将创建适当的资源,并使用内部服务服务地址对其进行配置。

卷和持久卷

在许多集装箱化环境中,可靠地共享数据并保证其在集装箱重新启动之间的可用性是一个挑战。 容器运行时通常提供一些机制来将存储附加到容器上,这些存储在容器的生命周期之后仍然存在,但是实现通常缺乏灵活性。

为了解决这个问题,Kubernetes 使用自己的卷抽象,允许一个吊舱内的所有容器共享数据,并保持可用,直到吊舱结束。 这意味着紧密耦合的吊舱可以轻松地共享文件,而无需复杂的外部机制。 Pod 中的容器故障不会影响对共享文件的访问。 一旦 pod 终止,共享卷就会被破坏,因此对于真正持久化的数据来说,这不是一个好的解决方案。

持久性卷是一种抽象更健壮的存储的机制,与 pod 的生命周期无关。 相反,它们允许管理员为集群配置存储资源,用户可以请求并声明他们正在运行的 pods。 一旦一个 pod 用一个持久卷完成,卷的回收策略决定卷是否保留到手动删除或者随着数据立即删除为止。 可以使用持久数据防止基于节点的故障,并分配比本地可用数量更大的存储空间。

标签和注释

一个与组织抽象相关,但在其他概念之外的 Kubernetes 是标记。 在 Kubernetes,标签是一个语义标签,可以附加到 Kubernetes 对象上,以标记它们作为一个群体的一部分。 然后可以在针对不同实例进行管理或路由时选择这些实例。 例如,每个基于控制器的对象都使用标签来标识它们应该操作的吊舱。 服务使用标签来了解他们应该将请求路由到的后端吊舱。

标签是作为简单的键值对给出的。 每个单元可以有多个标签,但是每个单元对于每个键只能有一个条目。 通常,“ name”键用作通用标识符,但是您可以根据其他标准(如开发阶段、公共可访问性、应用程序版本等)对对象进行分类。

注释是一种类似的机制,允许您将任意键值信息附加到对象。 虽然标签应该用于语义信息文件,以便与选择标准匹配一个豆荚,注释是更自由的形式,可以包含较少的结构化数据。 一般来说,注释是向对象添加丰富元数据的一种方式,对于选择目的没有帮助。

总结

Kubernetes 是一个令人兴奋的项目,它允许用户在高度抽象的平台上运行可伸缩的、高可用的集装箱化工作负载。 虽然 Kubernetes 的体系结构和内部组件集乍一看似乎令人望而生畏,但它们的力量、灵活性和健壮的特性集在开源世界中是无与伦比的。 通过理解基本构建块如何组合在一起,您可以开始设计系统,充分利用平台的功能,以便在规模上运行和管理您的工作负载。