Kubernetes 是一个强大的开源系统,最初由谷歌开发,并得到云原生计算基金会(CNCF)的支持,用于在集群环境中管理容器化应用程序。它旨在提供更好的方法来管理相关的、分布式的组件和服务,跨越各种基础设施。要了解更多关于 Kubernetes 的信息,请查看下面的指南。如果您正在寻找托管的 Kubernetes 主机服务,请查看我们为增长而构建的简单、托管的 Kubernetes 服务。
在本指南中,我们将讨论 Kubernetes 是什么,一些 Kubernetes 的基本概念。我们将讨论系统的架构,它解决的问题,以及它用于处理容器化部署和扩展的模型。
Kubernetes 在基本层面上是一个用于在一组机器的集群中运行和协调容器化应用程序的系统。它是一个旨在完全管理容器化应用程序和服务的生命周期的平台,使用提供可预测性、可扩展性和高可用性的方法。
作为 Kubernetes 用户,您可以定义应用程序的运行方式以及它们与其他应用程序或外部世界进行交互的方式。您可以扩展您的服务,执行平稳的滚动更新,并在不同版本的应用程序之间切换流量,以测试功能或回滚有问题的部署。Kubernetes 提供了接口和可组合的平台原语,允许您以高度灵活、强大和可靠的方式定义和管理您的应用程序。
要理解 Kubernetes 如何提供这些功能,有助于对其在高层次上的设计和组织有所了解。Kubernetes 可以被视为一个分层构建的系统,每个更高的层次都在抽象出低层次的复杂性。
在其基础上,Kubernetes 使用共享网络将单个物理或虚拟机器聚合成一个集群。这个 Kubernetes 集群是配置所有 Kubernetes 组件、功能和工作负载的物理平台。
Kubernetes 集群中的机器被赋予 Kubernetes 生态系统中的一个角色。一个服务器(或在高可用部署中的一小组服务器)充当 master 服务器。这个服务器充当集群的网关和大脑,通过为用户和客户端暴露 Kubernetes API、对其他服务器进行健康检查、决定如何最好地分配和分配工作(称为“调度”),以及协调其他组件之间的通信(有时被称为容器编排)。master 服务器充当与集群的主要联系点,并负责大部分 Kubernetes 提供的集中逻辑。
集群中的其他机器被指定为 nodes:负责使用本地和外部资源接受和运行工作负载的服务器。为了帮助隔离、管理和灵活性,Kubernetes 在 containers 中运行应用程序和服务,因此每个节点都需要配备容器运行时(如 Docker 或 rkt)。节点从 master 服务器接收工作指令,并相应地创建或销毁容器,调整网络规则以适当地路由和转发流量。
如上所述,应用程序和服务本身在集群中在容器中运行。底层组件确保应用程序的期望状态与集群的实际状态相匹配。用户通过直接与主 Kubernetes API 服务器通信或使用客户端和库与集群进行交互。要启动应用程序或服务,需要提交一个以 JSON 或 YAML 定义要创建和如何管理的声明性计划。然后,master 服务器通过检查要求和系统的当前状态来运行计划,并找出如何在基础设施上运行它。这组用户定义的应用程序根据指定的计划运行,代表了 Kubernetes 的最终层。
如上所述,master 服务器充当 Kubernetes 集群的主要控制平面。它作为管理员和用户的主要联系点,并为相对不复杂的工作节点提供许多集群范围的系统。总的来说,master 服务器上的组件共同工作,接受用户请求,确定最佳的方式来调度工作负载容器,对客户端和节点进行身份验证,调整集群范围的网络,以及管理扩展和健康检查责任。
这些组件可以安装在单台机器上,也可以分布在多台服务器上。我们将在本节中逐个查看与 Kubernetes 集群的 master 服务器相关的各个单独组件。
Kubernetes 需要的基本组件之一是一个全局可用的配置存储。由 CoreOS 团队开发的 etcd 项目是一个轻量级的、分布式的键值存储,可以配置为跨多个节点。
Kubernetes 使用 etcd
来存储可以被集群中每个节点访问的配置数据。这可用于服务发现,并且可以帮助组件根据最新信息进行配置或重新配置自身。它还通过提供简单的 HTTP/JSON API 来帮助维护集群状态,具有领导者选举和分布式锁定等功能。
与控制平面中的大多数其他组件一样,etcd
可以配置在单个 master 服务器上,或者在生产场景中分布在多台机器上。唯一的要求是它对每台 Kubernetes 机器都是网络可访问的。
最重要的主服务之一是 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的小型代理服务。该进程将请求转发到正确的容器,可以进行基本的负载平衡,并且通常负责确保网络环境是可预测和可访问的,但在适当的情况下是隔离的。
虽然容器是部署容器化应用程序的基本机制,但 Kubernetes 在容器接口上使用了额外的抽象层,以提供扩展、弹性和生命周期管理功能。用户不直接管理容器,而是定义和与 Kubernetes 对象模型提供的各种基元实例进行交互。我们将在下面介绍可以用来定义这些工作负载的不同类型的对象。
Pod是 Kubernetes 处理的最基本的单位。容器本身不分配给主机,而是一个或多个紧密耦合的容器封装在一个称为 pod 的对象中。
Pod 通常表示应该作为单个应用程序控制的容器。Pod 由密切协作的容器组成,共享生命周期,并且应始终在同一节点上调度。它们作为一个单元完全进行管理,并共享其环境、卷和 IP 空间。尽管它们是容器化实现,但通常应将 pod 视为单个、单块的应用程序,以最好地概念化集群将如何管理 pod 的资源和调度。
通常,pod 由满足工作负载一般目的的主要容器和可选的一些辅助容器组成。这些是受益于在自己的容器中运行和管理的程序,但与主要应用程序紧密关联。例如,一个 pod 可能有一个容器运行主要应用程序服务器,另一个辅助容器在检测到外部存储库中的更改时从中拉取文件到共享文件系统。通常不鼓励在 pod 级别进行水平扩展,因为有其他更适合此任务的更高级别对象。
通常,用户不应自行管理 pod,因为它们不提供应用程序通常需要的一些功能(如复杂的生命周期管理和扩展)。相反,鼓励用户使用使用 pod 或 pod 模板作为基本组件但实现附加功能的更高级别对象。
在使用 Kubernetes 时,通常不会直接管理单个 pod,而是管理一组相同的复制 pod。这些复制 pod 是从 pod 模板创建的,可以通过复制控制器和复制集这样的控制器进行水平扩展。
复制控制器 是一个定义了 pod 模板和控制参数的对象,可以通过增加或减少运行副本的数量来水平扩展相同的 pod。这是 Kubernetes 中本地分发负载和提高可用性的简单方式。复制控制器知道如何根据嵌入在其配置中的与 pod 定义紧密相似的模板创建新的 pod。
复制控制器负责确保集群中部署的 pod 数量与其配置中的 pod 数量相匹配。如果 pod 或底层主机失败,控制器将启动新的 pod 进行补偿。如果控制器配置中的副本数量发生变化,控制器将启动或终止容器以匹配所需的数量。复制控制器还可以执行滚动更新,逐个将一组 pod 更新到新版本,最大程度地减少对应用程序可用性的影响。
复制集 是对复制控制器设计的一种迭代,具有更大的灵活性,可以更好地识别其所管理的 pod。由于其更大的副本选择能力,复制集开始取代复制控制器,但它们无法像复制控制器那样执行滚动更新以将后端逐步切换到新版本。相反,复制集是用于在提供该功能的其他更高级别单元内部使用的。
与 pod 一样,复制控制器和复制集都很少直接使用。虽然它们基于 pod 设计,以添加水平扩展和可靠性保证,但它们缺少一些在更复杂对象中找到的细粒度生命周期管理功能。
部署 是直接创建和管理的最常见工作负载之一。部署使用复制集作为构建块,为其添加了灵活的生命周期管理功能。
虽然使用复制集构建的部署可能看起来重复了复制控制器提供的功能,但部署解决了在实施滚动更新时存在的许多痛点。在使用复制控制器更新应用程序时,用户需要提交一个用于替换当前控制器的新控制器的计划。使用复制控制器时,跟踪历史记录、在更新期间从网络故障中恢复以及回滚错误更改等任务要么很困难,要么留给用户自行负责。
部署是一个高级对象,旨在简化复制 pod 的生命周期管理。通过更改配置,可以轻松修改部署,Kubernetes 将调整副本集,管理不同应用程序版本之间的过渡,并自动维护事件历史记录和撤消功能。由于这些特性,部署可能是您最频繁使用的 Kubernetes 对象类型。
有状态集 是专门的 pod 控制器,提供顺序和唯一性保证。主要用于在具有与部署顺序、持久数据或稳定网络相关特殊要求时提供更细粒度的控制。例如,有状态集通常与需要访问相同卷的面向数据的应用程序(如数据库)相关联,即使重新调度到新节点,也需要访问相同的卷。
有状态集通过为每个 pod 创建一个唯一的基于数字的名称来提供稳定的网络标识符,即使需要将 pod 移动到另一个节点,该标识符也将持久存在。同样,持久存储卷可以在重新调度时与 pod 一起转移。即使在删除 pod 后,这些卷也会持续存在,以防止意外数据丢失。
在部署或调整规模时,有状态集根据其名称中的编号标识符执行操作。这提供了更大的可预测性和对执行顺序的控制,这在某些情况下非常有用。
守护进程集 是另一种特殊形式的 pod 控制器,它在集群中的每个节点(或指定的子集)上运行一个 pod 的副本。这在部署帮助执行维护并为 Kubernetes 节点本身提供服务的 pod 时最为有用。
例如,收集和转发日志、聚合指标以及运行增加节点本身能力的服务都是守护进程集的热门候选对象。由于守护进程集通常提供基本服务并且需要在整个集群中运行,它们可以绕过阻止其他控制器将 pod 分配给某些主机的 pod 调度限制。例如,由于其独特的责任,主服务器经常配置为无法正常进行 pod 调度,但守护进程集具有能力在逐个 pod 的基础上覆盖限制,以确保关键服务正在运行。
到目前为止,我们描述的工作负载都假定了长时间运行的类似服务的生命周期。Kubernetes 使用称为作业的工作负载来提供更基于任务的工作流,其中预期运行的容器在完成工作后会成功退出。如果需要执行一次性或批处理处理而不是运行连续服务,则作业非常有用。
在作业的基础上构建的是定时作业。与 Linux 和类 Unix 系统上的传统 cron
守护进程一样,Kubernetes 中的定时作业提供了一个运行具有调度组件的作业的接口。定时作业可用于安排将来执行的作业或定期重复执行的作业。Kubernetes 定时作业基本上是对经典 cron 行为的重新实现,使用集群作为平台,而不是单个操作系统。
除了集群中可以运行的工作负载之外,Kubernetes 还提供了许多其他抽象,帮助您管理应用程序、控制网络并实现持久性。我们将在这里讨论一些更常见的例子。
到目前为止,我们一直在使用“服务”这个术语,以传统的类 Unix 的方式来表示:用于表示长时间运行的进程,通常是网络连接的,能够响应请求。然而,在 Kubernetes 中,服务是一个作为基本内部负载均衡器和代理的组件。服务将执行相同功能的逻辑 pod 集合组合在一起,以将它们呈现为单个实体。
这使您能够部署一个服务,该服务可以跟踪并路由到特定类型的所有后端容器。内部使用者只需要知道服务提供的稳定端点。同时,服务抽象允许您根据需要扩展或替换后端工作单元。服务的 IP 地址保持稳定,而不受其路由到的 pod 的更改影响。通过部署服务,您可以轻松获得可发现性,并简化容器设计。
每当您需要向另一个应用程序或外部使用者提供对一个或多个 pod 的访问时,都应该配置一个服务。例如,如果您有一组运行 Web 服务器的 pod,应该可以从互联网访问,那么服务将提供必要的抽象。同样,如果您的 Web 服务器需要存储和检索数据,您将希望配置一个内部服务,以便它们可以访问您的数据库 pod。
尽管服务默认情况下只能使用内部可路由的 IP 地址进行访问,但可以通过选择几种策略之一来使其在集群外部可用。NodePort 配置通过在每个节点的外部网络接口上打开静态端口来工作。对外部端口的流量将自动路由到适当的 pod,使用内部集群 IP 服务。
另外,LoadBalancer 服务类型通过创建外部负载均衡器来路由到服务,使用云提供商的 Kubernetes 负载均衡器集成。云控制器管理器将创建适当的资源,并使用内部服务地址进行配置。
在许多容器化环境中,可靠地共享数据并保证其在容器重新启动时可用是一个挑战。容器运行时通常提供一些机制,将存储附加到容器,使其在容器的生命周期之外持久存在,但实现通常缺乏灵活性。
为了解决这个问题,Kubernetes 使用了自己的卷抽象,允许数据被所有 pod 中的所有容器共享,并保持可用,直到 pod 被终止。这意味着紧密耦合的 pod 可以轻松共享文件,而无需复杂的外部机制。在 pod 中的容器故障不会影响对共享文件的访问。一旦 pod 被终止,共享卷就会被销毁,因此它不是真正持久数据的好解决方案。
持久卷是一种抽象机制,用于配置与 pod 生命周期无关的更健壮的存储。相反,它允许管理员为集群配置存储资源,用户可以请求并为他们正在运行的 pod 进行声明。一旦一个 pod 使用完一个持久卷,卷的回收策略决定是否保留卷直到手动删除,或立即删除并移除数据。持久数据可用于防止基于节点的故障,并分配比本地存储更多的存储空间。
与其他概念相关但又不同的 Kubernetes 组织抽象是标记。在 Kubernetes 中,标签是可以附加到 Kubernetes 对象上的语义标记,以将它们标记为组的一部分。然后可以选择它们以进行不同实例的管理或路由。例如,每个基于控制器的对象都使用标签来标识它们应该操作的 pod。服务使用标签来了解它们应该将请求路由到的后端 pod。
标签被给定为简单的键值对。每个单元可以有多个标签,但每个单元每个键只能有一个条目。通常,使用“名称”键作为通用标识符,但您还可以根据其他标准对对象进行分类,如开发阶段、公共可访问性、应用程序版本等。
注释是一种类似的机制,允许您向对象附加任意键值信息。虽然标签应该用于语义信息,以便将 pod 与选择标准匹配,但注释更自由形式,可以包含不太结构化的数据。通常,注释是向对象添加丰富元数据的一种方式,这些元数据对于选择目的并不有用。
Kubernetes 是一个令人兴奋的项目,它允许用户在高度抽象的平台上运行可扩展、高可用的容器化工作负载。虽然 Kubernetes 的架构和一系列内部组件乍看起来可能令人生畏,但它们的强大、灵活性和健壮的功能集在开源世界和云原生开发中是无与伦比的。通过了解基本构建块如何组合在一起,您可以开始设计系统,充分利用平台的能力来以规模运行和管理工作负载,构建出色的云原生应用程序。