Justin Ellingwood 2018-05-03
简介
Kubernetes 是一个功能强大的开源系统,最初由 Google 开发,并由云原生计算基金会(CNCF)维护,用于在集群环境中管理容器化应用。它的目标是为跨异构基础设施管理相互关联的分布式组件和服务提供更优的方法。若想深入了解 Kubernetes,请参阅下方指南。如果您正在寻找托管型 Kubernetes 服务,不妨试试我们为业务增长打造的简单易用、全托管的 Kubernetes 服务。
在本指南中,我们将讨论 Kubernetes 是什么、一些 Kubernetes 的基本概念,并介绍其 系统架构、它所解决的问题,以及用于处理容器化部署与扩缩容的模型。
什么是 Kubernetes?
从本质上讲,Kubernetes 是一个用于在机器集群上运行和协调容器化应用程序的系统。它是一个平台,旨在通过提供可预测性、可扩展性和高可用性的方法,全面管理容器化应用和服务的整个生命周期。
作为 Kubernetes 用户,您可以定义应用程序应如何运行,以及它们如何与其他应用或外部世界交互。您可以对服务进行弹性伸缩、执行优雅的滚动更新,并在不同版本的应用之间切换流量,以测试新功能或回滚有问题的部署。Kubernetes 提供了接口和可组合的平台原语,使您能够以高度的灵活性、强大功能和可靠性来定义和管理您的应用。
Kubernetes 架构
要理解 Kubernetes 如何提供这些能力,了解其高层设计和组织结构会很有帮助。Kubernetes 可以被看作是一个分层构建的系统,每一层都对下层的复杂性进行了抽象。
在其底层,Kubernetes 将多个物理机或虚拟机通过共享网络连接组成一个集群(无论这些机器是物理还是虚拟的)。这个 Kubernetes 集群就是所有 Kubernetes 组件、能力和工作负载配置运行的物理平台。
Kubernetes 集群中的每台机器在生态系统中都有特定角色。其中一台服务器(或在高可用部署中的一小组服务器)充当 主节点(Master)。该服务器作为集群的“网关”和“大脑”,对外暴露 Kubernetes API 供用户和客户端使用,对其他服务器进行健康检查,决定如何最佳地分配和调度工作负载(称为“调度”),并协调各组件之间的通信(有时也称为“容器编排”)。主节点是与集群交互的主要入口点,负责 Kubernetes 提供的大部分集中式逻辑。
集群中的其他机器被指定为 工作节点(Node):它们负责接受并运行工作负载,利用本地和外部资源。为了便于隔离、管理和提升灵活性,Kubernetes 在容器中运行应用和服务,因此每个节点都需要配备一个容器运行时(如 Docker 或 rkt)。节点从主节点接收工作指令,并据此创建或销毁容器,同时调整网络规则以正确路由和转发流量。
如前所述,应用和服务本身是在集群内的容器中运行的。底层组件确保应用的期望状态与集群的实际状态一致。用户通过直接或借助客户端/库与主 Kubernetes API 服务器通信来与集群交互。要启动一个应用或服务,用户需提交一份声明式的 JSON 或 YAML 配置文件,定义要创建的内容及其管理方式。主节点随后接收该计划,并根据系统当前状态和需求,决定如何在基础设施上运行它。这组按指定计划运行的用户定义应用构成了 Kubernetes 的最上层。
主节点组件
如上所述,主节点充当 Kubernetes 集群的主要控制平面。它既是管理员和用户的主要接触点,也为相对简单的 Worker 节点提供许多集群级系统服务。总体而言,主节点上的组件协同工作,以接受用户请求、确定最佳容器调度方案、认证客户端和节点、调整集群网络,并管理扩缩容和健康检查等职责。
这些组件可以安装在同一台机器上,也可以分布在多台服务器上。下面我们将逐一介绍 Kubernetes 主节点相关的各个组件。
etcd
Kubernetes 正常运行所需的一个核心组件是全局可用的配置存储。etcd 项目由 CoreOS 团队开发,是一个轻量级的分布式键值存储系统,可配置为跨多个节点运行。
Kubernetes 使用 etcd 存储可被集群中每个节点访问的配置数据。这些数据可用于服务发现,并帮助组件根据最新信息自行配置或重新配置。它还通过领导者选举和分布式锁等功能帮助维护集群状态。etcd 提供简单的 HTTP/JSON API,使得设置或获取值的操作非常直观。
与控制平面中的大多数其他组件一样,etcd 可以部署在单个主节点上,也可以在生产环境中分布于多台机器上。唯一的要求是它必须能被所有 Kubernetes 机器通过网络访问。
kube-apiserver
最重要的主节点服务之一是 API 服务器。它是整个集群的主要管理入口,允许用户配置 Kubernetes 的工作负载和组织单元。它还负责确保 etcd 存储中的数据与已部署容器的服务详情保持一致。API 服务器充当各组件之间的桥梁,以维持集群健康并传递信息和命令。
API 服务器实现了 RESTful 接口,这意味着许多不同的工具和库都可以轻松与其通信。默认情况下,用户可通过名为 kubectl 的客户端从本地计算机与 Kubernetes 集群交互。
kube-controller-manager
控制器管理器 是一个通用服务,承担多项职责。其主要功能是管理各种控制器,这些控制器用于调节集群状态、管理工作负载生命周期并执行例行任务。例如,副本控制器(Replication Controller) 确保为某个 Pod 定义的副本数量与集群中当前部署的数量一致。这些操作的详细信息会被写入 etcd,而控制器管理器则通过 API 服务器监听 etcd 中的变化。
当检测到变化时,控制器读取新信息并执行相应操作以达成期望状态。这可能包括扩缩容应用、调整端点等。
kube-scheduler
实际将工作负载分配到集群中特定节点的进程是 调度器(Scheduler)。该服务读取工作负载的运行需求,分析当前基础设施环境,并将工作负载放置在合适的节点上。
调度器负责跟踪每个主机的可用容量,确保不会将工作负载调度到超出可用资源的节点上。它必须了解每台服务器的总容量以及已分配给现有工作负载的资源。
cloud-controller-manager
Kubernetes 可部署在多种环境中,并能与各种基础设施提供商交互,以理解和管理集群中的资源状态。虽然 Kubernetes 使用通用的资源表示(如可挂载存储和负载均衡器),但它需要一种机制将这些通用概念映射到不同云提供商提供的实际资源上。
云控制器管理器(Cloud Controller Manager) 充当“粘合剂”,使 Kubernetes 能够与具有不同能力、特性和 API 的云提供商交互,同时在内部保持相对通用的抽象。这使得 Kubernetes 能根据从云提供商获取的信息更新自身状态,在系统需要时调整云资源,并创建和使用额外的云服务以满足提交到集群的工作负载需求。
工作节点组件
在 Kubernetes 中,通过运行容器来执行工作的服务器称为 节点(Node)。节点服务器必须满足一些要求,才能与主节点组件通信、配置容器网络并运行分配给它们的实际工作负载。
容器运行时(Container Runtime)
每个节点必须具备的第一个组件是 容器运行时。通常通过安装和运行 Docker 来满足此要求,但也可使用 rkt、runc 等替代方案。
容器运行时负责启动和管理容器——即封装在相对隔离但轻量级操作系统环境中的应用程序。集群中的每个工作单元本质上都是一个或多个必须部署的容器。节点上的容器运行时是最终运行集群提交的工作负载中所定义容器的组件。
kubelet
每个节点与集群通信的主要接触点是一个名为 kubelet 的小型服务。该服务负责在控制平面服务与节点之间传递信息,并与 etcd 存储交互以读取配置详情或写入新值。
kubelet 与主节点组件通信以完成集群认证并接收命令和工作。工作以 清单(manifest) 的形式接收,其中定义了工作负载及其运行参数。随后,kubelet 负责维护节点上工作负载的状态,并控制容器运行时按需启动或销毁容器。
kube-proxy
为了管理单个主机的子网划分并将服务暴露给其他组件,每个节点上都会运行一个名为 kube-proxy 的小型代理服务。该进程将请求转发到正确的容器,可执行基本的负载均衡,并总体上确保网络环境可预测、可访问,同时在必要时保持隔离。
Kubernetes 对象与工作负载
虽然容器是部署容器化应用的基础机制,但 Kubernetes 在容器接口之上增加了额外的抽象层,以提供扩缩容、弹性恢复和生命周期管理功能。用户不直接管理容器,而是通过 Kubernetes 对象模型提供的各种原语来定义和交互。下面我们介绍可用于定义这些工作负载的不同对象类型。
Pod
Pod 是 Kubernetes 处理的最基本单元。容器本身不会直接分配到主机上,而是一个或多个紧密耦合的容器被封装在一个称为 Pod 的对象中。
Pod 通常代表应作为一个单一应用进行控制的容器集合。Pod 中的容器紧密协作、共享生命周期,并始终被调度到同一节点上。它们作为一个整体被管理,共享环境、卷和 IP 地址。尽管其实现基于容器,但您应将 Pod 视为一个单一的整体应用,以便更好地理解集群如何管理其资源和调度。
通常,Pod 包含一个满足工作负载主要目的的主容器,以及可选的一些辅助容器,用于执行紧密相关的任务。这些辅助程序虽受益于在独立容器中运行和管理,但与主应用紧密绑定。例如,一个 Pod 可能包含一个运行主应用服务器的容器,以及一个在检测到外部仓库变更时将文件拉取到共享文件系统的辅助容器。由于存在更适合扩缩容的高级对象,通常不建议在 Pod 层面进行水平扩缩容。
一般而言,用户不应直接管理 Pod,因为它们缺乏应用通常所需的高级功能(如复杂的生命周期管理和扩缩容)。相反,建议使用以 Pod 或 Pod 模板为基础、但提供额外功能的高级对象。
副本控制器(Replication Controller)与副本集(Replica Set)
在 Kubernetes 中,用户通常不是管理单个 Pod,而是管理一组相同且可复制的 Pod。这些 Pod 由 Pod 模板创建,并可通过 副本控制器 或 副本集 进行水平扩缩容。
副本控制器 是一种对象,它定义了 Pod 模板和控制参数,用于通过增减运行副本数量来水平扩缩容相同 Pod 的副本。这是在 Kubernetes 内部分布负载和提高可用性的简便方法。副本控制器知道如何按需创建新 Pod,因为其配置中嵌入了一个与 Pod 定义非常相似的模板。
副本控制器负责确保集群中部署的 Pod 数量与其配置中定义的数量一致。如果某个 Pod 或其所在主机发生故障,控制器会启动新 Pod 进行补偿。如果控制器配置中的副本数发生变化,控制器会启动或终止容器以匹配期望数量。副本控制器还可执行滚动更新,逐个将一组 Pod 升级到新版本,从而最小化对应用可用性的影响。
副本集 是副本控制器的演进版本,具有更灵活的 Pod 识别能力。由于其更强的副本选择能力,副本集正逐步取代副本控制器,但它无法像副本控制器那样执行滚动更新。因此,副本集通常被用作更高级别单元(如 Deployment)的内部组件,由后者提供滚动更新功能。
与 Pod 类似,副本控制器和副本集也很少由用户直接操作。尽管它们在 Pod 基础上增加了水平扩缩容和可靠性保障,但仍缺乏更复杂对象所提供的精细生命周期管理能力。
Deployment(部署)
Deployment 是最常见且直接创建和管理的工作负载类型。它以副本集为基础,增加了灵活的生命周期管理功能。
虽然基于副本集的 Deployment 在功能上看似与副本控制器重复,但它解决了副本控制器在滚动更新实现中的诸多痛点。使用副本控制器更新应用时,用户需提交一个新副本控制器的计划来替换当前控制器。在此过程中,跟踪历史记录、在网络故障时恢复、回滚错误变更等任务要么困难,要么完全由用户负责。
Deployment 是一种高级对象,专为简化复制 Pod 的生命周期管理而设计。通过修改 Deployment 配置,Kubernetes 会自动调整副本集、管理不同应用版本之间的过渡,并可选地自动维护事件历史和回滚能力。正因这些特性,Deployment 很可能是您最常使用的 Kubernetes 对象类型。
StatefulSet(有状态集)
StatefulSet 是一种专用的 Pod 控制器,提供顺序性和唯一性保证。主要用于在部署顺序、持久化数据或稳定网络方面有特殊需求的场景。例如,StatefulSet 常用于数据库等数据密集型应用,这些应用即使被重新调度到新节点,也需要访问相同的存储卷。
StatefulSet 通过为每个 Pod 创建唯一且基于编号的名称来提供稳定的网络标识符,即使 Pod 被迁移到其他节点,该名称也会保留。同样,持久化存储卷可在重新调度时随 Pod 一起迁移。即使 Pod 被删除,这些卷仍会保留,以防止意外数据丢失。
在部署或调整规模时,StatefulSet 会按照其名称中的编号顺序执行操作,从而提供更高的可预测性和执行顺序控制,这在某些场景下非常有用。
DaemonSet(守护进程集)
DaemonSet 是另一种专用的 Pod 控制器,它在集群的每个节点(或指定子集)上运行一个 Pod 副本。这通常用于部署协助节点自身维护或提供服务的 Pod。
例如,收集和转发日志、聚合指标、运行增强节点能力的服务等,都是 DaemonSet 的典型应用场景。由于 DaemonSet 通常提供基础服务且在整个集群中都需要运行,因此它可以绕过阻止其他控制器将 Pod 分配到某些主机的调度限制。例如,主节点通常被配置为不可用于常规 Pod 调度,但 DaemonSet 可以逐个 Pod 地覆盖此限制,以确保关键服务正常运行。
Job(任务)与 CronJob(定时任务)
到目前为止,我们描述的工作负载都假定具有长期运行、服务类的生命周期。Kubernetes 使用一种名为 Job 的工作负载来提供基于任务的工作流,其中运行的容器在完成工作后会成功退出。Job 适用于一次性或批处理任务,而非持续运行的服务。
在 Job 基础上构建的是 CronJob。类似于 Linux 和类 Unix 系统中的传统 cron 守护进程(按计划执行脚本),Kubernetes 的 CronJob 提供了带调度功能的 Job 接口。CronJob 可用于在未来某个时间点或定期重复执行 Job。Kubernetes 的 CronJob 本质上是在集群平台上重新实现了经典 cron 的行为,而非依赖单个操作系统。
其他 Kubernetes 组件
除了可在集群上运行的工作负载外,Kubernetes 还提供了许多其他抽象,用于管理应用、控制网络和启用持久化存储。下面我们介绍几个常见示例。
Kubernetes Service(服务)
到目前为止,我们一直以传统的 Unix 意义使用“服务”一词:指长期运行、通常通过网络连接、能响应请求的进程。但在 Kubernetes 中,Service 是一种充当 Pod 内部负载均衡器和代理的组件。Service 将执行相同功能的逻辑 Pod 集合分组,将其呈现为单一实体。
这使您能够部署一个 Service,自动跟踪并路由到所有同类型的后端容器。内部消费者只需知道 Service 提供的稳定端点即可。同时,Service 抽象允许您按需扩缩容或替换后端工作单元。Service 的 IP 地址在后端 Pod 变更时保持稳定。通过部署 Service,您可以轻松实现服务发现并简化容器设计。
每当您需要向其他应用或外部消费者提供一个或多个 Pod 的访问权限时,都应配置一个 Service。例如,如果您有一组运行 Web 服务器的 Pod 需要从互联网访问,Service 就提供了必要的抽象。同样,如果您的 Web 服务器需要存储和检索数据,您也应配置一个内部 Service 以使其能访问数据库 Pod。
默认情况下,Service 仅可通过集群内部可路由的 IP 地址访问,但可通过几种策略将其暴露到集群外部。NodePort 配置通过在每个节点的外部网络接口上打开一个静态端口实现。发往该外部端口的流量会自动通过内部集群 IP Service 路由到相应的 Pod。
另一种方式是 LoadBalancer 类型的 Service,它利用云提供商的 Kubernetes 负载均衡器集成创建一个外部负载均衡器。云控制器管理器会创建相应资源,并使用内部 Service 的地址进行配置。
卷(Volumes)与持久卷(Persistent Volumes)
在许多容器化环境中,可靠地共享数据并确保其在容器重启后仍然可用是一项挑战。容器运行时通常提供某种机制将存储挂载到容器上,使其在容器生命周期结束后仍能保留,但这些实现通常缺乏灵活性。
为解决此问题,Kubernetes 使用自己的 卷(Volume) 抽象,允许 Pod 内所有容器共享数据,并在 Pod 终止前一直保持可用。这意味着紧密耦合的 Pod 可轻松共享文件,而无需复杂的外部机制。Pod 内容器的故障不会影响对共享文件的访问。但一旦 Pod 终止,共享卷也会被销毁,因此它不适合真正需要持久化的数据。
持久卷(Persistent Volume, PV) 是一种更健壮的存储抽象机制,不与 Pod 生命周期绑定。它允许管理员为集群配置存储资源,用户可按需申请并将其绑定到运行的 Pod 上。当 Pod 不再需要持久卷时,其回收策略将决定该卷是保留至手动删除,还是立即连同数据一并移除。持久化数据可用于防范节点故障,并分配比本地可用存储更大的空间。
标签(Labels)与注解(Annotations)
Kubernetes 中一种与前述概念相关但又独立的组织抽象是 标签(Label)。标签是可附加到 Kubernetes 对象上的语义标签,用于将对象标记为某个组的一部分。随后可在管理或路由时通过标签选择目标实例。例如,所有基于控制器的对象都使用标签来识别其应操作的 Pod。Service 也使用标签来确定应将请求路由到哪些后端 Pod。
标签以简单的键值对形式给出。每个对象可拥有多个标签,但每个键只能有一个值。通常使用 “name” 键作为通用标识符,此外还可按开发阶段、公开访问性、应用版本等标准对对象进行分类。
注解(Annotation) 是类似的机制,允许您将任意键值信息附加到对象上。与标签应用于语义信息(便于选择匹配)不同,注解更自由,可包含非结构化数据。通常,注解用于为对象添加丰富的元数据,这些信息对选择操作无直接帮助。
结论
Kubernetes 是一个令人振奋的项目,它允许用户在高度抽象的平台上运行可扩展、高可用的容器化工作负载。尽管 Kubernetes 的架构和内部组件初看可能令人望而生畏,但其在开源世界和云原生开发领域所提供的强大功能、灵活性和健壮特性是无与伦比的。通过理解这些基本构建模块如何协同工作,您便能开始设计充分利用平台能力的系统,以大规模运行和管理您的工作负载,构建出色的云原生应用。