Kubernetes
我们用经典的What? Why? How?三段式分析框架来快速的了解和学习Kubernetes。
What
Kubernetes是一个可移植,可扩展的开源平台,用于管理容器化的工作负载和服务,支持声明式的配置和自动化。
Kubernetes名字来源于希腊语,以为舵手或飞行员,由于名字过长,经常缩写为K8s,是将”K”和”s”之间的八个字母缩写而成的简称。
Kubernetes的前身是Google内部的Borg,Borg最初是为了管理Google内部的大规模分布式系统而开发的,其中包括了大量的容器。Google 在 2014 年开源了 Kubernetes 项目。Kubernetes 融合了 Google 超过 15 年的大规模生产环境工作负载管理经验,以及社区中最佳的理念和实践。
Why
我们先从服务部署和交付的技术发展历史来看一下,为什么会产生Kubernetes,如下是程序部署交付经历的三个时代:
- 传统部署时代
我也称之为物理机部署时代,早期,我们直接在物理服务器上运行应用程序。由于无法在物理服务器上定义应用程序的资源边界,导致资源分配问题频发。例如,若多个应用程序运行在同一台物理服务器上,可能出现某个应用占用大部分资源的情况,导致其他应用性能下降。解决方案是将每个应用部署在独立的物理服务器上。然而,这种方式资源利用率低下且维护成本高昂,难以扩展。
当然,你如果了解Linux,就会知道Linux内核引入了cgroup来对进程的资源使用进行的限制,这也是虚拟化技术进行资源隔离的基础。
- 虚拟化部署时代
虚拟化技术应运而生。它允许在单个物理服务器的CPU上运行多个虚拟机(VM)。虚拟化实现了应用程序在虚拟机之间的隔离,提升了安全性。
虚拟化显著提高了物理服务器的资源利用率,并增强了扩展性:应用可快速添加或更新,硬件成本降低。通过虚拟化,可将一组物理资源抽象为可动态分配的虚拟机集群。每台虚拟机均是一台完整机器,在虚拟化硬件上运行包括操作系统在内的所有组件。
这个时代也称之为:云计算的时代,也就是现在各大云厂商所提供的基础的IaaS服务,可以提供,VM级别的灵活调度,例如:超卖,冷热迁移,机器硬件的自动扩容。Iaas基础设施带来了更多的容错性和可用性,例如可以冷热迁移,可以自动扩容
- 容器部署时代
传统部署时代和虚拟化部署时代,对于业务开发者来说,没有什么大的区别,应用程序都是基于OS部署,都是直接和OS打交道。
随着容器化技术的发展,来到了容器部署时代,容器与虚拟机类似,但隔离性限制较少,允许多个容器共享操作系统内核,因此更为轻量。与虚拟机类似,容器拥有独立的文件系统、CPU份额、内存、进程空间等资源。由于与底层基础设施解耦,容器可跨云平台及操作系统发行版无缝迁移。
容器流行的核心优势包括:
- 敏捷应用创建与部署:相比虚拟机镜像,容器镜像的创建更高效。
- 持续开发、集成与部署:得益于镜像的不可变性,可快速构建、部署容器镜像并实现可靠回滚。
- 开发与运维的关注点分离:在构建/发布阶段创建应用容器镜像,而非部署阶段,从而将应用与基础设施解耦。
- 可观测性:不仅提供操作系统级指标,还能监控应用健康状态等信号。
- 环境一致性:在开发、测试和生产环境中表现统一(如笔记本电脑与云端行为一致)。
- 云与操作系统兼容性:支持Ubuntu、RHEL、CoreOS、本地环境及主流公有云平台。
- 以应用为中心的管理:从虚拟硬件上的操作系统运行,升级为基于逻辑资源的操作系统级应用运行。
- 松耦合、分布式、弹性、独立的微服务:应用拆分为小型独立组件,可动态部署和管理,而非运行在单一用途的巨型单体架构上。
- 资源隔离:确保应用性能的可预测性。
- 资源高效利用:提升资源利用率与密度。
上面列出了容器流行的很多原因,最直观的来说:容器是打包和运行应用程序的好方法。随着云原生技术的发展,容器化越来越成为行业首选的技术方案,我觉得有一个很好的对容器的概括:
Build Once, Run anywhere.
容器让交付更简单,一次构建,可以在任何地方部署,依托registery,可以方便的进行发布和回滚,基于虚拟化环境,可以做到更好的环境隔离和高度的可移植性。如下:一个简单容器的构建和交付流程图:
那么随之而来的就是,在生产环境中,我们需要管理运行应用程序的容器并确保不会出现停机。例如,如果一个容器发生故障,需要启动另一个容器。如果由系统来处理这种行为,会不会更容易?
这就是Kubernetes的用武之地!Kubernetes为您提供了一个弹性运行分布式系统的框架。它会处理应用程序的扩展和故障转移,提供部署模式等功能。例如:Kubernetes可以轻松管理系统的金丝雀部署。
Kubernetes为您提供以下核心能力:
Service discovery and load balancing ,服务发现与负载均衡:Kubernetes可以通过DNS名称或容器IP地址暴露容器。当容器流量激增时,Kubernetes能够进行负载均衡并分配网络流量,确保部署稳定。
Storage orchestration,存储编排:支持自动挂载您选择的存储系统(如本地存储、公有云存储等)。
Automated rollouts and rollbacks,自动化部署与回滚:可以通过Kubernetes声明容器的期望状态,系统将以受控速率将实际状态调整至目标状态。例如,可自动化创建新容器、回收旧容器资源。
- Automatic bin packing ,自动完成装箱计算:向Kubernetes提供节点集群后,您只需声明容器所需的CPU和内存资源,Kubernetes会自动优化容器在节点间的分布以最大化资源利用率。
Self-healing,自我修复:自动重启故障容器,替换不可用容器,并屏蔽未通过健康检查的容器,直到其恢复服务能力。
Secret and configuration management ,密钥与配置管理:安全存储和管理密码、OAuth令牌、SSH密钥等敏感信息,支持不重建镜像即可更新密钥和应用配置。
Batch execution,批量任务执行:除常驻服务外,还能管理批处理和CI工作负载,按需替换故障容器。
Horizontal scaling,水平扩缩容:通过命令行、UI或基于CPU使用率自动横向扩展应用规模。
IPv4/IPv6 dual-stack,IPv4/IPv6双栈支持:为Pod和服务分配双协议栈地址
Designed for extensibility,可扩展架构:无需修改上游源码即可为集群添加新功能。
How
这里先跳过How,本文主要是Kubernetes的基础知识学习和汇总,不讨论How的问题。
Kubernetes集群架构
Kubernetes集群由一个控制平面Control Plane和一组运行容器化应用程序的工作机器,称为Nodes组成。每个集群至少需要一个工作节点来运行 Pod。
Node托管了作为应用程序工作负载组件的 Pod,Control Plane负责管理集群中Node和Pod。在生产环境中,Control Plane通常跨多台计算机运行,而集群通常会运行多个Node,以提供容错能力并实现高可用性。
如下是Kubernetes集群架构图,列出了一个完整且可运行的 Kubernetes 集群所需的各种组件:
下面的Kubernets集群架构图概括性的阐述了构建集群必不可少的Control Plane组件,相对上图,更简洁。
Kubernetes集群组件主要分为两个部分:
- Master节点:集群的控制节点,对集群进行调度管理,包含组件:
- kube-apiserver:集群的统一入口,提供REST API接口,所有对集群的操作都通过这个组件进行;
- kube-scheduler:负责监听新创建的但未调度的 Pod,根据资源需求(CPU、内存)、策略(如亲和性)和节点状态,选择合适的节点部署 Pod。
- kube-controller-manager:负责运行和管理所有的Controller进程,如节点控制器、副本控制器、端点控制器等,它们负责监控集群状态并使其与期望状态保持一致。
- cloud-controller-manager:负责运行与云服务提供商交互的控制器,允许集群连接到云提供商的API。
- etcd:一个高可用的键值存储,用于保存集群的所有配置数据。
- Node节点:集群的工作节点,运行业务应用,包含组件:
- kubelet:在每个节点上运行的代理,确保容器在Pod中运行。
- kube-proxy:网络代理,维护节点上的网络规则,实现Kubernetes服务的抽象。
- 容器运行时(Container Runtime):如Docker、containerd或CRI-O,负责运行容器。
如下Kubernetes Architecture文中的动态的集群架构图,可以更形象和详细的展示了集群核心组件间的交互:
控制平面组件
Master节点的控制平面组件会为整个集群做出全局决策,比如资源调度,集群事件的检测和响应,例如当对象的spec.replicas没有达到预期申明的数量,会启动新的Pod;控制平面的组件如下:
kube-apiserver
APIserver是 Kubernetes 集群的核心组件,可以被视为控制平面(Control Plane)的中枢。它通过暴露一个 REST API 接口提供前端服务,允许终端用户、Kubernetes 的其他内部组件(如控制器、调度器)以及外部系统进行通信。
Kubernetes API 接口主要提供了以下功能:
- 查询与请求信息:用于检索 Kubernetes 对象(如 Pod、Deployment、ConfigMap、Secret、命名空间等)的状态和元数据。
- 修改对象状态:作为修改 Kubernetes API 对象状态的唯一网关(例如创建、更新或删除资源)。
所有与 Kubernetes 集群的交互(如通过 kubectl 发送的命令)最终都会通过 API 服务器进行,确保操作的安全性、一致性和可审计性。
下图引用自文章,这张图可以很好的展示apiserver作为控制面的核心组件,在集群内的关键作用。
kube-apiserver 设计上考虑了水平扩缩,如下是一个Kubernetes集群的kube-apiserver的部署情况:
1 | $ kubectl -n kube-system get pods -o wide|grep kube-apiserver |
kube-scheduler
kube-scheduler负责监听新创建的,且未指定Node的Pods,并为其选择合适的Node来运行;
调度决策考虑的因素包括单个 Pod 及 Pods 集合的资源需求、软硬件及策略约束、 亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。
下图引用自Kubernetes Scheduler如何工作一文,很好的描述了kube-scheduler的基本职责。
kube-controller-manager
kube-controller-manager相当于所有Controller的大脑,负责运行和管理所有的Controller进程,如节点控制器、副本控制器、端点控制器等,它们负责监控集群状态并使其与期望状态保持一致。
kube-controller-manager是控制平面的核心组件之一,它的的实现方式是:将内置的Controller打包成一个二进制文件,并在一个进程中运行它们。以下是kube-controller-manager包含的主要控制器及其职责:
- **节点控制器(Node Controller)**:监控节点状态,当节点不可用时采取响应措施,例如更新节点状态并从节点移除Pod。
- **副本控制器(Replication Controller)**:确保指定数量的Pod副本在运行。
- **部署控制器(Deployment Controller)**:管理Deployment资源,实现应用的声明式更新。
- **状态集控制器(StatefulSet Controller)**:管理StatefulSet资源,为有状态应用提供保障。
- **服务控制器(Service Controller)**:创建和更新服务相关的资源,如LoadBalancer。
- **端点控制器(Endpoints Controller)**:填充Endpoints对象(即连接Services和Pods)。
- **服务账号控制器(ServiceAccount Controller)**:为新的命名空间创建默认的服务账号。
- **持久卷控制器(PersistentVolume Controller)**:管理持久卷的绑定、回收和动态供应。
- **命名空间控制器(Namespace Controller)**:确保对已删除的命名空间进行清理。
- **作业控制器(Job Controller)**:监控代表一次性任务的Job对象,然后创建Pods来运行这些任务。
- **定时作业控制器(CronJob Controller)**:管理定时任务的执行。
- **水平Pod自动扩缩器(HorizontalPodAutoscaler)**:根据CPU使用率或其他指标自动调整Pod数量。
- **证书签名控制器(Certificate Controller)**:处理证书请求并生成证书。
下图引用自Kubernetes Controller Manager 如何工作一文:
cloud-controller-manager
云基础设施技术能够让我们在公有云、私有云和混合云环境中运行 Kubernetes。Kubernetes 秉承自动化、API 驱动的基础设施理念,强调各组件间的松散耦合。
cloud-controller-manager 是 Kubernetes 控制平面的核心组件,内嵌了云厂商定制化控制逻辑。它的主要功能包括:
- 云平台集成:将 Kubernetes 集群与云服务商的 API 深度集成;
- 职责分离:将与云平台交互的组件(如节点控制器、路由控制器)与集群内部组件(如调度器、控制器管理器)解耦;
通过将 Kubernetes 与底层云基础设施的互操作逻辑解耦,cloud-controller-manager 使云厂商能够以不同于 Kubernetes 主项目的节奏发布特性更新。
该组件采用插件机制构建,允许不同云服务商通过标准化接口将其平台与 Kubernetes 集成。
cloud-controller-manager内置的核心控制器主要包括:
节点控制器(Node Controller):监控节点状态,同步云厂商的实例元数据(如实例类型、区域信息)。若节点与云实例失联,自动标记节点为
NotReady并触发剔除(Eviction)。服务控制器(Service Controller):管理
Service类型为LoadBalancer的资源,自动在云平台创建/删除负载均衡器(如 AWS ELB、GCP LB)。同步负载均衡器的状态到 Kubernetes Service 对象。路由控制器(Route Controller):在云厂商网络中自动配置路由表,确保 Pod 间跨节点通信(如 AWS VPC 路由、GCP VPC 路由)。处理
Flannel等 CNI 插件的网络路由需求。持久卷标签控制器(PersistentVolumeLabel Controller):为动态创建的 PersistentVolume(PV)自动附加云厂商的标签(如 AWS EBS 的
failure-domain.beta.kubernetes.io/zone)。确保 PV 的拓扑信息与云存储区域匹配。存储控制器(StorageController):管理云存储资源(如 AWS EBS、GCP Persistent Disk)的附加/分离操作。
自动处理存储卷的生命周期(如扩容、快照)。
下图引用自Kubernetes Cloud Controller Manager 一文,展示了Kubernets如何通过cloud-controller-manager来使用云厂商的Load Balancer来暴露集群中的Service的,以及管理存储。
etcd
etcd 是兼顾一致性与高可用性的nosql db,作为保存 Kubernetes 所有集群数据的后台数据库。可以称之为Kubernetes的大脑, etcd 在 Kubernetes 中的核心的功能包括:
集中式存储:etcd 存储所有 Kubernetes 对象的配置、状态和元数据,包括 Pod、Secret、DaemonSet、Deployment、ConfigMap 和 StatefulSet 等。
实时追踪:Kubernetes 利用 etcd 的监听功能(通过
Watch()API)监控对象状态的变化,从而实现实时跟踪和响应更新。API 可访问性:etcd 通过 gRPC 暴露键值 API。它还包含一个 gRPC 网关(RESTful 代理),可将 HTTP API 调用转换为 gRPC 消息。这使得 etcd 成为 Kubernetes 的理想数据库。
数据存储结构:所有 Kubernetes 对象以键值对形式存储在 etcd 的
/registry目录下,称为 etcd 键空间(etcd key space)。例如:默认命名空间中名为nginx的 Pod 信息存储在/registry/pods/default/nginx路径下。
如下图,引用etcd in Kubernetes: A Quick Guide,解释了etcd在Kubernetes如何使用的过程:
Node组件
kubelet
kubelet 是 Kubernetes 集群中 每个节点(Node)上运行的核心代理组件。 它保证 Pod及其Container的运行,kubelet 接收一组通过各类机制提供给它的 PodSpecs, 确保这些 PodSpecs 中描述的容器处于运行状态且健康。kubelet直接与容器运行时(如 Docker、containerd)交互,但 kubelet 不会管理不是由 Kubernetes 创建的容器。
kubelet的核心职责包括:
Pod 生命周期管理
- 根据 API Server 下发的 Pod 配置,创建、启动、停止容器。
- 监控容器状态,若容器异常退出,按重启策略(如
Always、OnFailure)重新启动。 - 执行 Pod 的生命周期钩子(Lifecycle Hooks),如
PostStart、PreStop。
节点的管理
将节点注册到 Kubernetes 集群中,使 API 服务器能够感知到该节点的存在。
定期向 API Server 提交节点状态(如 CPU/内存容量、就绪状态)。
若节点故障(如磁盘满、网络断开),主动标记节点为
NotReady并触发驱逐逻辑。
资源管理
- 通过 cgroups 限制容器的 CPU、内存、磁盘 I/O 等资源使用。
- 监控资源使用情况,执行 OOM(内存溢出)终止或资源不足时的 Pod 驱逐。
- 镜像和容器垃圾回收 - 清理未使用的容器和镜像以释放节点资源
kubelet 作为每个节点上的“Kubernetes代理”,不仅管理该节点上的 Pod 和容器,还负责整个节点的管理和维护,确保节点正常运行并能够被 Kubernetes 控制平面有效管理。但完整的节点管理(如操作系统更新、硬件维护)需依赖外部工具(如云平台、运维系统)。
下图引用自Kubelet Deep Dive 一文,描述了kubelet是如何进行Pod管理的。
kube-proxy
kube-proxy是集群中每个Node上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。
如果操作系统提供了可用的数据包过滤层(如 iptables 或 ipvs),则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。
如果您使用的网络插件(如 Cilium 或 Calico)已自行实现了 Service 的流量转发功能,且其行为与 kube-proxy 等效,则无需在集群节点上运行 kube-proxy。
kube-proxy 当前支持以下几种实现
- userspace:最早的负载均衡方案,它在用户空间监听一个端口,所有服务通过 iptables 转发到这个端口,然后在其内部负载均衡到实际的 Pod。该方式最主要的问题是效率低,有明显的性能瓶颈。
- iptables:目前推荐的方案,完全以 iptables 规则的方式来实现 service 负载均衡。该方式最主要的问题是在服务多的时候产生太多的 iptables 规则,非增量式更新会引入一定的时延,大规模情况下有明显的性能问题。
- ipvs:为解决 iptables 模式的性能问题,v1.11 新增了 ipvs 模式,采用增量式更新,并可以保证 service 更新期间连接保持不断开。
- winuserspace:同 userspace,但仅工作在 windows 节点上。
如下是引用cilium中kube-proxy在iptables模式下如何通过iptables来进行流量管理的,和Docker Engine的网络管理基本一致。
kube-proxy运行在kube-system namespace中,每个Node上都有一个kube-proxy,具体查看哪个kube-proxy运行在哪个Node上,可以通过下面的命令
1 | root@VM-131-137-centos ~# kubectl -n kube-system get pods -o wide|grep kube-proxy |
Container Runtime
容器运行时环境是负责容器的运行,它是 Kubernetes 节点上的一个关键组件,负责管理容器的生命周期、镜像和执行环境。容器运行时的主要功能:
- 容器生命周期管理:创建、启动、停止和删除容器
- 镜像管理:下载、解压和存储容器镜像
- 网络管理:为容器设置网络命名空间和网络接口
- 存储管理:为容器挂载存储卷
- 资源隔离:使用 Linux 内核特性(如 cgroups、namespaces)实现容器隔离
如下是CNCF 生态全景图列出的目前支持CRI的Container Runtime列表:
目前主要流行的是:
containerd:目前最广泛采用的容器运行时,最初是从 Docker 中提取的核心组件,现在是 CNCF 项目,功能全面,性能优良,安全性好
CRI-O:专为 Kubernetes 设计的轻量级运行时,只实现了 CRI 所需的功能,没有额外功能,由 Red Hat 主导开发,OpenShift 默认使用
CRI Intro一文中介绍了,在Kubernetes中如何通过CRI来调用不同的Container Runtime来实现容器的管理,如下图:
Kubernetes对象
在Kubernetes系统中,所有资源都视为对象,Kubernetes对象是持久化的实体,Kubernetes 使用这些实体去表示整个集群的状态。例如,这些对象描述了:
- 哪些容器化应用正在运行(以及在哪些节点上运行);
- 可以被应用使用的资源;
- 关于应用运行时行为的策略,比如重启策略、升级策略以及容错策略;
Kubernetes的对象是一种“意向表达(Record of Intent)”,什么意思呢?就是Kubernetes对象一旦创建之后,Kubernetes 系统将持续工作以确保对象存在。通过创建对象,本质上是告诉Kubernetes系统,你所期望的工作负载的样子,这就是Kubernetes集群所谓的期望状态desired state。
当操作Kubernetes的对象的时候,无论是创建,修改,或者删除,都需要通过Kubernetes API.,比如,当使用 kubectl 命令的时候, kubectl 命令会调用对应的Kubernetes API,我们也可以直接在程序中通过Client Libraries来调用Kubernetes API。
下面我们先看一下Kubernetes的对象的结构的组成和定义:
对象结构
在想要创建的 Kubernetes 对象对应的 manifest中(YAML or JSON文件)中,需要配置如下的字段:
apiVersion:创建该对象所使用的 Kubernetes API 的版本kind:想要创建的对象的类型metadata:帮助识别对象唯一性的数据,包括一个name字符串、UID 和可选的namespacespec:期望对象的状态,spec的精确格式对每个 Kubernetes 对象来说是不同的,包含了特定于该对象的嵌套字段。Kubernetes API 参考能够帮助我们找到任何我们想创建的对象的 spec 格式。
如下是Deployment对象的结构定义:
1 | //https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/apps/v1/types.go |
Deployment对象结构中的结构成员中:
TypeMeta,标识对象的API版本和对象的类型,所有对象都包含此子结构,此子结构的定义使用了inline内敛标签,在序列化/反序列化的时候会平铺在父结构体中。ObjectMeta,标识对象的metadata,里面主要包含识别对象唯一性的数据,以及一些元数据。
TypeMeta和ObjectMeta的基础定义如下:
1 | //https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go |
从上面Deployment对象结构的定义我们可以看到对应着Kubernetes 对象对应的 manifest中结构定义要求,包含了了基本的:apiVersion ,kind ,metadata,spec等,如下官方示例,是创建Deployment对象的manifest:
1 | apiVersion: apps/v1 |
通过kubectl apply命令可以在Kubernetes集群中部署上面的Deployment对象:
1 | $ kubectl apply -f https://k8s.io/examples/application/deployment.yaml |
服务端字段校验
Kubernetes从v1.25版本开始,API server提供了服务器端的对象字段的校验,针对操作时传入的对象的manifest,检测其中是否存在未知,重复的对象的字段。它在服务器端提供了 kubectl --validate 的所有功能。
kubectl --validate 接受值如下:
strict:严格的字段验证,验证失败时会报错;warn:执行字段验证,但错误会以警告形式提供而不是拒绝请求;ignore:不执行服务器端字段验证;true:等价于strict,默认值;false:等价于ignore;
当kubectl 无法连接到支持字段验证的 API 服务器时,它将回退为使用客户端验证。
| 特性 | 客户端验证(kubectl) | 服务器端验证(API Server) |
|---|---|---|
| 触发时机 | 在发送请求前由 kubectl 本地检查 | 请求到达 API 服务器后强制执行 |
| 可靠性 | 依赖 kubectl 版本和本地 schema 的准确性 | 始终基于集群当前 API 的权威 schema |
| 验证范围 | 可能因版本差异遗漏新字段 | 检查所有字段,包括未知或重复字段 |
| 绕过方式 | 可通过 --validate=false 跳过 |
无法绕过,强制执行 |
相对于validate的字段验证,其实Kubernetes很早就支持了--dry-run来模拟执行指令,可以验证server-side default 值注入、mutating webhook 或者 CRD 的复杂校验,但不会真正的改变集群中的资源,在Kubernetes v1.18以后,提供了--dry-run=client|server|none
client:只在本地做验证和模拟(不与 API Server 通讯)。server:将请求发送到服务器,由服务器模拟执行但不真正持久化资源。none:正常执行(等价于不 dry-run)。
对象管理
kubectl提供了三种方式来创建和管理Kubernetes的对象,如下:
| 管理方式 | 操作对象 | 推荐使用环境 | 支持的编写者 | 学习曲线 |
|---|---|---|---|---|
| 指令式命令 | 活动对象 | 开发项目 | 1+ | 最低 |
| 指令式对象配置 | 单个文件 | 生产项目 | 1 | 中等 |
| 声明式对象配置 | 文件目录 | 生产项目 | 1+ | 最高 |
指令式命令
通过指令式命令,我们可以直接操作集群中的活动对象,通过调用kubectl传入操作的命令和参数来进行对象的操作。
指令式命令只推荐在:开始学习Kubernetes时或者执行一次性的Task的情况下使用。因为:该方式是直接操作活动对象,不提供以前配置的历史记录。
如下是一些指令式命令:
1 | # 创建一个 Deployment |
指令式对象配置
通过指令式对象配置来操作对象,kubectl需要指定操作的命令(例如create,replace等)+可选参数+至少一个文件名。指定的文件必须包含 YAML 或 JSON 格式的对象的完整定义。
Warning:
replace命令会用文件中最新的Spec替换对象已经存在规约Spec,所以它会删除不存在最终配置中所有的已经发生的对象状态。此方式不能用在资源类型是独立于配置文件更新的,例如类型为LoadBalancer的服务,它的externalIPs字段就是独立于集群配置进行更新
如下示例:
1 | # 从文件创建对象(若对象已存在则报错) |
声明式对象配置
用户可以通过本地的对象配置文件(YAML/JSON)来进行对象的操作,但用户不需要定义操作的类型,kubectl 会自动检测每个文件的创建、更新和删除操作。 这使得配置可以在目录上工作,根据目录中配置文件对不同的对象执行不同的操作。
Note:声明式对象配置会保留其他操作者所做的修改,即使这些修改没有被合入最新对象的配置文件。这是通过
patchAPI的只写入新增的变化值来实现的,而不是通过replaceAPI来全量覆盖对象的配置。
例如如下:通过目录中的所有对象配置文件,创建或者更新活跃对象:
1 | # 对比配置文件与活动对象状态 |
下面是Kubernetes中三种对象的管理方式的优缺点的对比,可以对他们有个概括性的了解:
| 操作方式 | 优点 | 缺点 |
|---|---|---|
| 指令式命令 Imperative commands |
• 简单直接,易于学习和使用 • 只需一条命令即可完成操作 • 适合快速测试和学习 • 不需要预先定义完整的YAML/JSON文件 |
• 无法实现版本控制 • 难以自动化和重复操作 • 不适合复杂应用部署 • 无法方便地记录配置历史 |
| 指令式对象配置 Imperative object configuration |
• 对象配置可以进行版本控制 • 可以与Git等版本控制系统集成 • 操作意图明确 • 可以审查配置文件后再执行 |
• 需要理解对象的完整定义 • replace操作会覆盖其他方式做出的变更 • 不适合对象有独立更新字段的情况 • 每次更改都需要完整的配置文件 |
| 声明式对象配置 Declarative object configuration |
• 可以处理目录中的多个文件 • 自动检测每个对象需要的操作 • 保留其他方式所做的更改 • 最适合生产环境 • 通过patch仅更新差异部分 |
• 较难调试和理解 • 部分更新可能导致意外的配置 • 学习曲线较陡 • 操作预览较复杂 |
对象规约和状态
几乎每个 Kubernetes 对象都包含两个嵌套的对象字段来管理对象的配置:对象规约 spec 和对象状态**status**。
对象的 **
spec**字段,在对象创建的时候设置,描述你希望对象所具有的特征:期望状态desired state。对象的 **
status**字段,描述了对象的当前状态,它是由 Kubernetes 系统和组件设置并更新的。在任何时刻,Kubernete控制平面都一直在积极地管理着对象的实际状态,以使之达成期望状态。
如上面提到的Deployment对象中的定义,其中就有对象规约 spec 和对象状态**status**两个对象字段。
1 | //https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/apps/types.go |
Kubernetes 中的 Deployment 对象能够表示运行在集群中的应用。 当创建 Deployment 时,你可能会设置 Deployment 的 spec对象来指定该应用要有 3 个副本运行。 Kubernetes 系统读取 Deployment 的 spec, 并启动我们所期望的应用的 3 个实例,更新状态以与规约相匹配。 如果这些实例中有的失败了(一种状态变更),Kubernetes 系统会通过执行修正操作来响应 spec 和 status 间的不一致 ,即再启动一个新的实例来替换。
对象的名字和ID
每个对象都有一个该类型的资源内的唯一的Name字段。同样,每个对象还有一个全局唯一的UID,此UID集群中唯一,不管资源类型。
例如一个Pod和一个Deployment对象的名字都可以是myapp-1234,但是他们在集群中的UID是唯一的。如下:
1 | $kubectl run my-app-666 --image=nginx |
在「对象结构」一节我们知道,对象的唯一性标识,包括Name字段和UID字段都在对象的metadata中,如下:
1 | type ObjectMeta struct { |
对象的标签和选择算符
标签(Labels)是一组附加到Kubernetes 对象的key/value对,标签旨在用于指定对于用户有意义的对象的标识属性,但不直接对核心系统有语义含义。标签设计目的:可以被用来组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。 每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。
1 | //https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go |
Kubernetes中的每个对象的元数据都可以包含一组键值对,称之为标签,标签的设计目的:为用户提供,在对象中添加有意义的标识属性,但是对整个核心系统没有直接语义含义。
有效的标签键:
- 可选的前缀:用斜杠(
/)和名称分隔。如果指定,前缀必须是 DNS 子域:由点(.)分隔的一系列 DNS 标签,总共不超过 253 个字符, 后跟斜杠(/)。 - 名称:名称段是必需的,必须小于等于 63 个字符,以字母数字字符(
[a-z0-9A-Z])开头和结尾, 带有破折号(-),下划线(_),点(.)和之间的字母数字。
如果省略前缀,则假定标签键对用户是私有的。 向最终用户对象添加标签的自动系统组件(例如 kube-scheduler、kube-controller-manager、 kube-apiserver、kubectl 或其他第三方自动化工具)必须指定前缀。
kubernetes.io/ 和 k8s.io/ 前缀是为 Kubernetes 核心组件保留的。
有效标签值:
- 必须为 63 个字符或更少(可以为空)
- 除非标签值为空,必须以字母数字字符(
[a-z0-9A-Z])开头和结尾 - 包含破折号(
-)、下划线(_)、点(.)和字母或数字
标签选择符
和names and UIDs不同的是,labels不具有唯一性,通常我们会设置多个对象含有相同的标签,通过标签选择算符,客户端/用户可以识别一组对象。标签选择算符是 Kubernetes 中的核心分组原语。
API 目前支持两种类型的选择算符:基于等值的和基于集合的。 标签选择算符可以由逗号分隔的多个需求组成。 在多个需求的情况下,必须满足所有要求,因此逗号分隔符充当逻辑与(&&)运算符。
- 基于等值的需求
基于等值或基于不等值的需求允许按标签键和值进行过滤。 匹配对象必须满足所有指定的标签约束,尽管它们也可能具有其他标签。 可接受的运算符有 =、== 和 != 三种。 前两个表示相等(并且是同义词),而后者表示不相等。例如:
1 | environment = production |
- 基于集合的需求
基于集合的标签需求允许你通过一组值来过滤键。 支持三种操作符:in、notin 和 exists(只可以用在键标识符上)。例如:
1 | environment in (production, qa) |
- 第一个示例选择了所有键等于
environment并且值等于production或者qa的资源。 - 第二个示例选择了所有键等于
tier并且值不等于frontend或者backend的资源,以及所有没有tier键标签的资源。 - 第三个示例选择了所有包含了有
partition标签的资源;没有校验它的值。 - 第四个示例选择了所有没有
partition标签的资源;没有校验它的值。
类似地,逗号分隔符充当与运算符。因此,使用 partition 键(无论为何值)和 environment 不同于 qa 来过滤资源可以使用 partition, environment notin (qa) 来实现。
基于集合的标签选择算符是相等标签选择算符的一般形式,因为 environment=production 等同于 environment in (production);!= 和 notin 也是类似的。
基于集合的要求可以与基于相等的要求混合使用。例如:partition in (customerA, customerB),environment!=qa。
如下示例:
1 | $ kubectl get pods -l key1=value1,key2=value2 |
有时需要要在创建新资源之前对现有的 Pod 和其它资源重新打标签。 这可以用 kubectl label 完成:如下是匹配标签key1=value1的Pod,然后进行打标签key4=value4,
1 | $ kubectl label pods -l key1=value1 key4=value4 |
API对象的选择应用
一些 Kubernetes 对象,例如 services 和 replicationcontrollers, 也使用了标签选择算符去指定了其他资源的集合,例如 pods。
一个 Service 指向的一组 Pod 是由标签选择算符定义的。同样,一个 ReplicationController 应该管理的 Pod 的数量也是由标签选择算符定义的。
1 | apiVersion: v1 |
Namespace
在 Kubernetes 中,名字空间(Namespace) 提供一种机制,将同一Cluster中的资源划分为相互隔离的组。 同一Namespace内的资源名称要唯一(不同资源的Name前缀不同),但跨名字空间时没有这个要求。 Namespace的作用域仅针对名字空间的对象: (e.g. Deployments, Services, etc.) ,针对**集群作用域的对象 (e.g. StorageClass, Nodes, PersistentVolumes, etc.)并不适用。
Namespace适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。
Namespace为资源提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。 名字空间不能相互嵌套,每个 Kubernetes 资源只能在一个名字空间中。
Namespace是在多个用户之间划分集群资源的一种方法(通过资源配额)。不必使用多个名字空间来分隔仅仅轻微不同的资源,例如同一软件的不同版本: 应该使用标签来区分同一名字空间中的不同资源。
Kubernetes集群初始化会默认创建以下4个Namespace:
| Namespace | 描述 |
|---|---|
| default | Kubernetes 包含这个名字空间,以便于你无需创建新的名字空间即可开始使用新集群。 |
| kube-node-lease | 该名字空间包含用于与各个Node关联的 Lease(租约)对象。Node租约允许 kubelet 发送心跳,由此控制面能够检测到节点故障。 |
| kube-public | 所有的客户端(包括未经身份验证的客户端)都可以读取该名字空间。该名字空间主要预留为集群使用,以便某些资源需要在整个集群中可见可读。该名字空间的公共属性只是一种约定而非要求。 |
| kube-system | 该名字空间用于 Kubernetes 系统创建的对象。 |
对象的Annotation
你可以使用 Kubernetes 注解为对象附加任意的非标识的元数据。 客户端程序(例如工具和库)能够获取这些元数据信息。
你可以使用标签或注解将元数据附加到 Kubernetes 对象。 标签可以用来选择对象和查找满足某些条件的对象集合。 相反,注解不用于标识和选择对象。 注解中的元数据,可以很小,也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。
注解的设计主要是存储非识别性的元数据,为对象提供附加信息。供工具、库、系统(如 CI/CD、监控、运营商)或用户查看,例如:构建信息、Git 哈希、配置说明、监控链接、许可证信息。
注解(Annotations) 存储的形式也是键/值对。
有效的注解键分为两部分:
- 可选的前缀:以斜杠(
/)和名称分隔。如果指定,则前缀必须是 DNS 子域:一系列由点(.)分隔的 DNS 标签, 总计不超过 253 个字符,后跟斜杠(/)。 - 名称, 名称段是必需项,并且必须在 63 个字符以内,以字母数字字符(
[a-z0-9A-Z])开头和结尾, 并允许使用破折号(-),下划线(_),点(.)和字母数字。
前缀是可选的。 如果省略前缀,则假定注解键对用户是私有的。 由系统组件添加的注解 (例如,kube-scheduler,kube-controller-manager,kube-apiserver,kubectl 或其他第三方组件),必须为终端用户添加注解前缀。
kubernetes.io/ 和 k8s.io/ 前缀是为 Kubernetes 核心组件保留的。如下是简单的annotation操作:
1 | $ kubectl annotate pod nginx walkerdu.com/key1=value1 |
Finalizers
Finalizers 是 Kubernetes对象元数据(metadata)中的一个可选字段(可见定义:type ObjectMeta struct),用于控制对象的删除流程,确保在对象真正被删除前执行某些“清理”或“预删除”操作。它本质上是一种删除阻塞机制(deletion blocker)。
例如:
1 | metadata: |
当一个对象(如 Pod、Namespace、CustomResource 等)设置了 Finalizers,当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers 字段中的值, 并进行以下操作:
- 将对象的
metadata.deletionTimestamp设为当前时间(标记为“终止中”)。 - 等待
metadata.finalizers字段内的所有项被删除。 - 只有当 finalizers 列表为空时,才真正删除对象。返回
202状态码(HTTP “Accepted”)
自定义 Finalizer(CRD 控制器常用)常使用 finalizer 实现资源清理,如删除外部数据库、云资源等。示例:自定义控制器逻辑
1 | // 伪代码 |
Owners(属主) and Dependents(附属)
在 Kubernetes中,一些对象是另一些对象的所有者,例如一个ReplicaSet对象是一组Pod对象的所有者。
Owners 和 Dependents 是对象间所有者-从属关系的核心概念,主要用于垃圾回收(Garbage Collection)机制,帮助自动管理资源的生命周期。这与 Finalizers 密切相关,因为删除过程会涉及这些关系。
Owners 和 Dependents 的关系通过 metadata.ownerReferences 字段定义,对象元数据(metadata)中的一个可选字段(可见定义:type ObjectMeta struct),表示这个对象被哪些更高层的对象控制或创建。
Kubernetes 使用 Owners 和 Dependents 来实现级联删除(Cascading Deletion):
- 设置 Owner Reference:控制器(如 Deployment Controller)在创建子对象时,自动添加 ownerReferences。
- 垃圾回收:当 Owner 被删除(设置 deletionTimestamp)时:
- Kubernetes 的 Garbage Collector 检查所有 Dependents。
- 如果 Dependent 有匹配的 Owner Reference,且没有 finalizers 阻塞,则自动删除 Dependent。
- 删除类型:
- Orphaned Deletion:不级联删除 Dependents(使用 –cascade=orphan)。
- Foreground Deletion:默认,Owner 先删除 Dependents,再删除自己。
- Background Deletion:Owner 立即删除,Dependents 在后台删除。
与 Finalizers 的交互:
- 如果 Dependent 有 Finalizers(如 kubernetes.io/pv-protection),删除会被阻塞,直到控制器移除 Finalizer。
- 这确保了清理逻辑(如释放持久卷)在删除前执行。
常见应用场景
| 场景 | Owners 示例 | Dependents 示例 | 作用 |
|---|---|---|---|
| Deployment | Deployment | ReplicaSet | Deployment 删除时,级联删除 ReplicaSet 和其 Pod。 |
| StatefulSet | StatefulSet | Pod、PVC | 确保有序删除 Pod,并释放 PVC(如果有 finalizer 保护)。 |
| Custom Resources (CRD) | Custom Controller | Custom Resource Instances | Operator 删除时,清理外部资源(如数据库)。 |
| Namespace | Namespace Controller | 所有 Namespace 内对象 | Namespace 删除时,级联删除内部资源,除非有 finalizers。 |
如下我通过deployment创建的一个Pod的过程中,相关对象中的属主的配置:
1 | $ kubectl get deployment.apps/wecom-read-it-later -o yaml |
例如上面Pod的metadata.ownerReferences中标记了属主ReplicaSet对象的uid: b92aba2f-1af4-4529-8a31-59d249d0fd15,然后ReplicaSet对象的metadata.ownerReferences中标记了属主Deployment对象的uid: eb579845-53c6-4176-9bfe-05fa50041315。
Kubernetes API
Kubernetes API 使我们可以查询和操纵 Kubernetes 中对象的状态。API server作为Kubernetes控制面的核心,负责提供 HTTP API,以供用户、集群中的不同部分和集群外部组件相互通信。
REST API是Kubernetes架构的基础,组件之间所有的操作以及外部用户命令都是通过REST API调用API server进行处理。
API groups
API Groups 是 Kubernetes 中用于对 API 资源进行逻辑分组和版本管理的核心概念。它是 Kubernetes 从 v1.2 开始引入的机制,目的是解决早期 API(如 v1)过于庞大、难以演进的问题。
为什么需要 API 组?在 Kubernetes 早期:
- 所有资源(Pod、Service、ReplicationController 等)都放在一个扁平的
v1API 中。 - 随着功能增加,API 越来越臃肿,无法独立演进(比如网络策略和批处理作业不该和核心 Pod 绑定同一版本)。
解决方案:按功能划分 API 组,每个组独立版本控制。API组从结构上分为两类:
- 核心组(core/legacy)组, REST 路径为
/api/v1。 核心组并不作为apiVersion字段的一部分,例如,apiVersion: v1。核心组只有v1版本,包括了最古老的核心资源:Pod、Service、Node,ConfigMap,Secret。 - 命名组(Named groups),有明确的Group名字,每个组可有多个版本(
v1,v1beta1,v1alpha1),指定的组位于 REST 路径/apis/$GROUP_NAME/$VERSION, 并且使用apiVersion: $GROUP_NAME/$VERSION(例如,apiVersion: batch/v1)。
| REST API Path | apiVersion 写法 | API Group(组名) | 版本 | 说明 | |
|---|---|---|---|---|---|
| /api/v1 | v1 | (空组,也叫 core 组) | v1 | 最古老的核心资源:Pod、Service、Node… | |
| /apis/apps/v1 | apps/v1 | apps | v1 | Deployment、StatefulSet、DaemonSet | |
| /apis/batch/v1 | batch/v1 | batch | v1 | Job、CronJob | |
| /apis/networking.k8s.io/v1 | networking.k8s.io/v1 | networking.k8s.io | v1 | Ingress、NetworkPolicy | |
| /apis/discovery.k8s.io/v1 | discovery.k8s.io/v1 | discovery.k8s.io | v1 | EndpointSlice |
版本控制是在 API 级别而不是在资源或字段级别完成的,以确保 API 呈现出清晰、一致的系统资源和行为视图, 并能够控制对生命结束和/或实验性 API 的访问。
每个 API 组可独立演进版本,遵循以下阶段:
| 版本后缀 | 含义 | 是否稳定 | 可否用于生产 |
|---|---|---|---|
v1 |
稳定版(GA) | ✅ 是 | ✅ 推荐 |
v1beta1 |
Beta 版 | ⚠️ 可能有 breaking change | ⚠️ 谨慎使用 |
v1alpha1 |
Alpha 版 | ❌ 不稳定,可能删除 | ❌ 仅测试 |
例如:
Ingress最初在extensions/v1beta1,后来迁移到networking.k8s.io/v1。
Discovery API & OpenAPI
Kubernetes 集群里有很多 API(比如 Pod、Deployment、Service 这些资源都是通过 API 来操作的)。不同的集群版本、不同的厂商(比如原生 Kubernetes、OpenShift、各种云厂商的 K8s)支持的 API 可能有点差别。为了让各种工具(kubectl、helm、CI/CD、监控、第三方客户端等)能自动适配任何一个 Kubernetes 集群,集群就必须把自己“我到底支持哪些 API、支持哪些版本、每个资源长什么样”这些信息告诉外面。
为了解决这个问题,每个 Kubernetes 集群都会发布集群所使用的 API 规范。 Kubernetes 使用两种机制来发布这些 API 规范;这两种机制可以相互结合来提供操作。 所支持的两种机制如下:
- The Discovery API :提供有关 Kubernetes API 的信息:API 名称、资源、版本和支持的操作。它是一个独立于 Kubernetes OpenAPI 的 API。 其目的是为可用的资源提供简要总结,不详细说明资源的具体模式。有关资源模式的参考,请参阅 OpenAPI 文档。
- Kubernetes OpenAPI Document :所有 Kubernetes API 端点提供(完整的)OpenAPI v2.0 and 3.0 schemas。OpenAPI v3 是访问 OpenAPI 的首选方法, 因为它提供了更全面和准确的 API 视图。其中包括所有可用的 API 路径,以及每个端点上每个操作所接收和生成的所有资源。 它还包括集群支持的所有可扩展组件。这些数据是完整的规范,比 Discovery API 提供的规范要大得多。
每个Kubernetes版本默认支持的上述两种机制在Kubernetes源码都是可以看到的(所有API的描述都是由Go源码自动生成json文件),包含了所有的API和资源,这些json文件是“快照副本”,仅用于客户端代码生成、文档生成、离线查看。
两种API暴露机制对应的结构分为如下:
1 | // https://github.com/kubernetes/kubernetes/tree/master/api |
如下是openapi中关于Pod对象的结构描述,可以看得出完全是由Go的Pod结构生成出来的API资源描述。
1 | // https://github.com/kubernetes/kubernetes/blob/master/api/openapi-spec/v3/api__v1_openapi.json |
我们可以通过kubectl来动态的查看Kubernetes集群所有的API资源的详细信息:
1 | kubectl api-versions |
如下是查看API资源的简要信息
1 | $ kubectl api-resources --api-group='' |
如下是查看API的名称,分组,版本信息:
1 | $ kubectl api-versions |
kubetctl api-versions和kubetctl api-resources都是通过Dicovery API来获取集群的API简要信息,我们可以通过kubetctl explain来访问集群暴露的OpenAPI 详细资源的文档说明:
1 | $ kubectl explain pod |
也可以通过--raw 参数来获取OpenAPI暴露的资源详细信息:
1 | $ kubectl get --raw /openapi/v3/api/v1 |
API Server 维护 OpenAPI Schema,kubectl api-versions / api-resources / openapi/v3 全部是实时从内存动态生成的。
- API Server 启动时会为所有注册的 API 资源维护 OpenAPI/Swagger 规范
- 这些规范在资源注册时就已经定义好了(可以理解为”静态”的元数据)
- CRD(自定义资源)也会在安装时向 API Server 注册其 schema
Persistence
Kubernetes 会将集群内所有的API资源对象进行序列化(protobuf encoding)的写到 etcd 中存储
参考
Scheduler in Kubernetes: A Quick Guide
How to Monitor Kubernetes API Server
How to Monitor kube-controller-manager
