基础概念
概述
Kubernetes(k8s)是由云原生计算基金会维护的基于容器的应用程序的开源编排和集群管理的开源系统。主要用于自动化容器化应用程序的部署、扩展和管理。简而言之,Kubernetes使管理多个主机上的容器变得容易。此外,它还使用声明性YAML文件进行容器部署变得简单。
常用组件
Control Pannel
一个集群可能会有一个或者多个控制平面。 Control Pannel也就是控制平面,负责容器编排并维护集群所需状态。它具有以下组件,
kube-apiserver
- kube-apiserver就是集群的主控,暴露了Kubernetes API,用户通过
kubectl控制集群实际就是通过HTTP REST APIs与api server交互的,而集群内部的组件,比如scheduler、controller等,则是通过gRPC与api server进行交互的。 - 集群内部通信都是通过TLS进行的。
- kube-apiserver 发起连接的唯一组件是 etcd 组件。所有其他组件都连接到 API 服务器。
- 每个组件(Kubelet、调度程序、控制器)独立监视 API 服务器以确定它需要做什么。
etcd
Kubernetes 是一个分布式系统,它需要像 etcd 这样高效的分布式数据库来支持其分布式特性。它既充当后端服务发现又充当数据库。 你可以将其称为 Kubernetes 集群的大脑。
此外,etcd 是控制平面中唯一的 Statefulset 组件。
etcd 是一个开源的强一致性、分布式键值存储系统:
- 强一致性:如果对一个节点进行更新,强一致性将确保它立即更新到集群中的所有其他节点。另外,如果你看看 CAP 定理,实现 100% 可用性、强一致性和分区容错性是不可能的。
- 分布式:etcd 被设计为作为集群在多个节点上运行,而不牺牲一致性。
- 键值存储:将数据存储为键和值的非关系数据库。它还公开了一个键值 API。该数据存储构建在 BboltDB 之上,BboltDB 是 BoltDB 的一个分支。
简单来说,当您使用 kubectl 获取 kubernetes 对象详细信息时,您是从 etcd 获取的。此外,当您部署像 pod 这样的对象时,会在 etcd 中创建一个条目。
etcd的主要功能
- 存储集群状态:
- etcd保存了Kubernetes集群中所有的元数据,包括节点信息、Pod状态、配置数据等。
- 例如,Kubernetes中的资源对象(如Deployments、Services、ConfigMaps)的定义和状态都会存储在etcd中。
- 支持分布式一致性:
- etcd使用Raft协议实现分布式一致性,确保再多节点的分布式环境中数据的一致性和可靠性。
- 服务发现和配置管理:
- etcd可用作服务发现工具让Kubernetes控制平面组件实时获取集群状态和配置信息。
- 提供数据持久化和高可用性:
- 所有关键数据都持久化存储,并且通过多副本机制提高可靠性,即使某些节点发生故障也不会丢失数据。etcd的快照功能可以用来恢复整个Kubenetes集群的状态,是灾难恢复的重要工具。
etcd 在 Kubernetes 中的作用:
- 作为控制平面的数据存储:
- Kubernetes 的 API Server 通过 etcd 存储和检索集群的所有状态数据。
- 控制平面的其他组件(如 Controller Manager 和 Scheduler)通过 API Server 与 etcd 交互。
- 保存集群元数据:
- 例如:
- 所有的资源对象(如 Pod、Node、Service、ConfigMap)。
- 集群状态(如节点的心跳、Pod 的调度状态)。
- 例如:
- 支持动态配置和服务发现:
- Kubernetes 的动态配置和状态变化(如 HPA 自动扩缩容)依赖于 etcd 的一致性和实时性。
- 灾难恢复:
- etcd 的快照(snapshot)功能可以用来恢复整个 Kubernetes 集群的状态,是灾难恢复的重要工具。
etcd 的工作原理
- 键值存储模型:
- etcd 的数据以键值对的形式存储,例如:
/registry/pods/default/my-pod -> Pod 的状态数据- 分布式一致性:
- etcd 使用 Raft 共识算法确保多个 etcd 节点之间的数据一致性。
- 即使发生网络分区或节点故障,数据仍然是一致的。
- 版本化和监控:
- etcd 为每个键值分配版本号,可以记录和回溯数据的变化历史。
- 支持实时监控(watch),当数据发生变化时通知相关组件。
etcd 的特性
- 高可用性:
- etcd 是分布式系统,通常部署在奇数个节点(如 3 或 5 个节点)以确保高可用性。
- 强一致性:
- etcd 保证每次读写操作都能看到最新的状态。
- 事务支持:
- etcd 提供事务功能,允许对多个键值执行原子操作。
- 高性能:
- 具有低延迟和高吞吐量,适合 Kubernetes 的高频状态更新需求。
etcd 的使用示例
存储和读取数据
- 写入一个键值对:
etcdctl put /example/key "value"- 读取键值:
etcdctl get /example/key监控数据变化
- 监听某个键的变化:
etcdctl watch /example/key获取快照
- 备份 etcd 的数据:
etcdctl snapshot save snapshot.db- 恢复快照:
etcdctl snapshot restore snapshot.dbetcd 在 Kubernetes 集群中的部署
- 单节点 etcd:
- 通常用于测试和小型集群,但存在单点故障风险。
- 多节点 etcd:
- 在生产环境中,etcd 集群通常由 3、5 或 7 个节点组成,确保高可用性和容错能力。
- 嵌套模式:
- etcd 可以嵌入到 Kubernetes 的控制平面中(如使用 kubeadm 部署的集群)。
- 独立模式:
- etcd 也可以独立部署和管理,与 Kubernetes 控制平面分离。
etcd 的局限性
- 对存储和网络的要求较高:
- etcd 的高性能依赖于低延迟的存储和稳定的网络。
- 运维复杂:
- 多节点 etcd 集群的部署、扩缩容和升级需要一定的专业知识。
- 数据丢失风险:
- 如果没有定期备份和正确配置,可能导致关键数据丢失。
etcd 的备份与恢复
备份
- 定期备份 etcd 的数据以防止灾难性故障:
etcdctl snapshot save /path/to/snapshot.db恢复
- 如果 etcd 数据损坏,可以使用快照进行恢复:
etcdctl snapshot restore /path/to/snapshot.dbkube-scheduler
kube-scheduler 负责在工作节点上调度 Kubernetes Pod。
部署 Pod 时,您需要指定 Pod 要求,例如 CPU、内存、关联性、污点或容忍度、优先级、持久卷 (PV) 等。调度程序的主要任务是识别创建请求并为 Pod 选择最佳节点。满足要求的 Pod。
下图显示了调度程序如何工作的高级概述。
在一个 Kubernetes 集群中,会有多个工作节点。那么调度程序如何从所有工作节点中选择节点呢?
以下是调度程序的工作原理:
- 为了选择最佳节点,Kube 调度程序使用过滤和评分操作。
- 在过滤过程中,调度程序会找到最适合调度 Pod 的节点。例如,如果有五个具有资源可用性的工作节点来运行 Pod,则它会选择所有五个节点。如果没有节点,则 pod 不可调度并移至调度队列。如果是一个大型集群,假设有 100 个工作节点,那么调度程序不会迭代所有节点。有一个名为percentageOfNodesToScore 的调度程序配置参数。默认值通常为 50%。因此它尝试以循环方式迭代 50% 以上的节点。如果工作节点分布在多个区域,则调度程序将迭代不同区域中的节点。对于非常大的集群,默认的 percentageOfNodesToScore 百分比是 5%。
- 在评分(scoring phase)阶段,调度程序通过为过滤后的工作节点分配分数来对节点进行排名。调度器通过调用多个调度插件来进行评分。最后,将选择排名最高的工作节点来调度 Pod。如果所有节点的等级相同,则将随机选择一个节点。
- 一旦选择了节点,调度程序就会在 API 服务器中创建一个绑定事件。意思是绑定 pod 和节点的事件。
- 它是一个监听 API 服务器中 pod 创建事件的控制器。
- 调度程序有两个阶段。调度周期和绑定周期。它们一起被称为调度上下文。调度周期选择一个工作节点,绑定周期将该更改应用于集群。
- 调度程序始终将高优先级 pod 放在低优先级 pod 之前进行调度。此外,在某些情况下,Pod 开始在选定节点中运行后,Pod 可能会被驱逐或移动到其他节点。如果您想了解更多信息,请阅读 Kubernetes Pod 优先级指南。
- 调度器有一个可插拔的调度框架。这意味着您可以将自定义插件添加到调度工作流程中。
kube-controller-manager
控制器是运行无限控制循环的程序。这意味着它连续运行并观察对象的实际和期望状态。如果实际状态和期望状态存在差异,它可以确保 kubernetes 资源/对象处于期望状态。 根据官方文档:
In Kubernetes, controllers are control loops that watch the state of your cluster, then make or request changes where needed. Each controller tries to move the current cluster state closer to the desired state.假设您想要创建一个部署,您可以在清单 YAML 文件中指定所需的状态(声明性方法)。例如,2 个副本、1 个卷挂载、configmap 等。内置的部署控制器可确保部署始终处于所需状态。如果用户使用 5 个副本更新部署,部署控制器会识别它并确保所需的状态为 5 个副本。
kube-controller-manager是管理所有Kubernetes控制器的组件。 Kubernetes 资源/对象(例如 pods、namespaces、jobs、replicaset)由各自的控制器管理。另外,Kube调度器也是一个由Kube控制器管理器管理的控制器。
以下是重要的内置 Kubernetes 控制器的列表:
- Deployment controller
- Replicaset controller
- DaemonSet controller
- Job Controller (Kubernetes Jobs)
- CronJob Controller
- endpoints controller
- namespace controller
- service accounts controller.
- Node controller 以下是您应该了解的有关 Kube 控制器管理器的信息。
- 它管理所有控制器,控制器尝试将集群保持在所需的状态。
- 您可以使用与自定义资源定义关联的自定义控制器来扩展 Kubernetes。
cloud-controller-manager(CCM)
当kubernetes部署在云环境中时,云控制器管理器充当云平台API和Kubernetes集群之间的桥梁。
这样,核心 kubernetes 核心组件就可以独立工作,并允许云提供商使用插件与 kubernetes 集成。 (例如kubernetes集群与AWS云API之间的接口)。
云控制器集成允许 Kubernetes 集群配置云资源,例如实例(用于节点)、负载均衡器(用于服务)和存储卷(用于持久卷)。
云控制器管理器包含一组特定于云平台的控制器,可确保特定于云的组件(节点、负载均衡器、存储等)的所需状态。以下是属于云控制器管理器一部分的三个主要控制器。
- 节点控制器:该控制器通过与云提供商 API 对话来更新节点相关信息。例如,节点标记和注释、获取主机名、CPU 和内存可用性、节点健康状况等。
- 路由控制器:负责在云平台上配置网络路由。这样不同节点中的 Pod 就可以互相通信。
- 服务控制器:它负责为 kubernetes 服务部署负载均衡器、分配 IP 地址等。 以下是云控制器管理器的一些经典示例。
- 部署负载均衡器类型的 Kubernetes 服务。这里 Kubernetes 提供了一个特定于云的负载均衡器并与 Kubernetes 服务集成。
- 为云存储解决方案支持的 Pod 供应存储卷 (PV)。 总体云控制器管理器管理 kubernetes 使用的特定于云的资源的生命周期。
Worker Node
Worker Node负责运行容器化的应用程序,通常包含以下组件:
kubelet
Kubelet 是一个代理组件,运行在集群中的每个节点上。它不作为容器运行,而是作为守护进程运行,由 systemd 管理。
它负责向 API 服务器注册工作节点,并主要使用来自 API 服务器的 podSpec(Pod 规范 - YAML 或 JSON)。 podSpec 定义了应该在 Pod 内运行的容器、它们的资源(例如 CPU 和内存限制)以及其他设置,例如环境变量、卷和标签。
然后,它通过创建容器将 podSpec 带到所需的状态。
简单来说,kubelet 负责以下工作。
- 创建、修改和删除 Pod 的容器。
- 负责处理活跃度、就绪度和启动探测。
- 负责通过读取 pod 配置并在主机上创建相应的目录来挂载卷以进行卷挂载。
- 通过调用 API 服务器以及 cAdvisor 和 CRI 等实现来收集和报告节点和 Pod 状态。
Kubelet 也是一个控制器,用于监视 pod 更改并利用节点的容器运行时来拉取映像、运行容器等。
除了来自 API 服务器的 PodSpec 之外,kubelet 还可以接受来自文件、HTTP 端点和 HTTP 服务器的 podSpec。 “来自文件的 podSpec"的一个很好的例子是 Kubernetes 静态 Pod。
静态 Pod 由 kubelet 控制,而不是 API 服务器。
这意味着您可以通过向 Kubelet 组件提供 pod YAML 位置来创建 pod。但是,Kubelet 创建的静态 Pod 不受 API 服务器管理。
这是静态 Pod 的真实示例用例。
在引导控制平面时,kubelet 将 api 服务器、调度程序和控制器管理器作为来自位于 /etc/kubernetes/manifests 的 podSpecs 的静态 Pod 启动
以下是有关 kubelet 的一些关键内容。
- Kubelet 使用 CRI(container runtime interface)gRPC 接口与容器运行时通信。
- 它还公开一个 HTTP 端点来流日志并为客户端提供执行会话。
- 使用CSI(container storage interface)gRPC 配置块存储卷。
- 它使用集群中配置的 CNI 插件来分配 Pod IP 地址并为 Pod 设置任何必要的网络路由和防火墙规则。
kube-proxy
要了解 Kube proxy,您需要具备 Kubernetes 服务(Kubernetes Service)和Endpoint对象(Endpoint objects)的基本知识。
Kubernetes 中的服务是一种向内部或外部流量公开一组 Pod 的方法。当您创建服务对象时,它会获得分配给它的虚拟 IP。它被称为 clusterIP。它只能在 Kubernetes 集群内访问。
Endpoint对象包含Service对象下所有Pod组的IP地址和端口。Endpoint控制器负责维护 Pod IP 地址(端点)列表。服务控制器负责配置服务的端点。
您无法 ping ClusterIP,因为它仅用于服务发现,与可 ping 通的 pod IP 不同。
现在我们来了解一下 Kube Proxy。
Kube-proxy 是一个守护进程,作为守护进程集在每个节点(Node)上运行。它是一个代理组件,为 Pod 实现 Kubernetes 服务概念。 (一组具有负载平衡功能的 Pod 的单个 DNS)。它主要代理 UDP、TCP 和 SCTP,不理解 HTTP。
当您使用Service (ClusterIP) 公开 Pod 时,Kube-proxy 会创建网络规则以将流量发送到分组在 Service 对象下的后端 Pod(端点)。这意味着,所有负载平衡和服务发现都由 Kube Proxy处理。
那么 Kube-proxy 是如何工作的呢?
Kube 代理与 API 服务器通信以获取有关服务 (ClusterIP) 以及相应 pod IP 和端口(端点)的详细信息。它还监视服务和端点的变化。
然后,Kube-proxy 使用以下任一模式来创建/更新规则,以将流量路由到服务后面的 Pod。
- IPTables:这是默认模式。在 IPTables 模式下,流量由 IPtable 规则处理。这意味着对于每个服务,都会创建 IPtable 规则。这些规则捕获传入 ClusterIP 的流量,然后将其转发到后端 Pod。此外,在此模式下,kube-proxy 会随机选择后端 pod 进行负载均衡。一旦建立连接,请求就会发送到同一个 pod,直到连接终止。
- IPVS:对于服务超过1000个的集群,IPVS提供性能提升。它支持后端以下负载均衡算法。
rr: round-robin : It is the default mode.lc: least connection (smallest number of open connections)dh: destination hashingsh: source hashingsed: shortest expected delaynq: never queue
- Userspace (legacy & not recommended)
- Kernelspace:此模式仅适用于Windows系统。
container runtime
您可能了解 Java 运行时 (JRE)。它是在主机上运行Java程序所需的软件。同样,容器运行时是运行容器所需的软件组件。
容器运行时运行在 Kubernetes 集群中的所有节点上。它负责从容器注册表中提取镜像、运行容器、为容器分配和隔离资源以及管理主机上容器的整个生命周期。
为了更好地理解这一点,让我们看一下两个关键概念:
- Container Runtime Interface (CRI):它是一组 API,允许 Kubernetes 与不同的容器运行时交互。它允许不同的容器运行时与 Kubernetes 互换使用。 CRI 定义了用于创建、启动、停止和删除容器以及管理镜像和容器网络的 API。
- Open Container Initiative (OCI):它是一组容器格式和运行时的标准。
Kubernetes 支持多种符合容器运行时接口 (CRI) 的容器运行时(CRI-O、Docker Engine、containerd 等)。这意味着,所有这些容器运行时都实现 CRI 接口并公开 gRPC CRI API(runtime and image service endpoints)。
那么 Kubernetes 如何利用容器运行时呢?
正如我们在 Kubelet 部分中了解到的,kubelet 代理负责使用 CRI API 与容器运行时交互,以管理容器的生命周期。它还从容器运行时获取所有容器信息并将其提供给控制平面。
我们以 CRI-O 容器运行时接口为例。以下是容器运行时如何与 Kubernetes 配合使用的高级概述。

- 当 API 服务器发出对 pod 的新请求时,kubelet 与 CRI-O 守护进程通信,通过 Kubernetes 容器运行时接口启动所需的容器。
- CRI-O 使用容器/映像库从配置的容器注册表中检查并提取所需的容器映像。
- 然后,CRI-O 为容器生成 OCI 运行时规范 (JSON)。
- 然后,CRI-O 启动与 OCI 兼容的运行时 (runc),以根据运行时规范启动容器进程。
Kubernetes Cluster Addon Components
除了核心组件之外,kubernetes 集群还需要附加组件才能完全运行。选择插件取决于项目要求和用例。以下是集群上可能需要的一些流行插件组件。
- CNI Plugin (Container Network Interface)
- CoreDNS (For DNS server):CoreDNS 充当 Kubernetes 集群内的 DNS 服务器。通过启用此插件,您可以启用基于 DNS 的服务发现。
- Metrics Server (For Resource Metrics): 该插件可帮助您收集集群中节点和 Pod 的性能数据和资源使用情况。
- Web UI (Kubernetes Dashboard): 该插件使 Kubernetes 仪表板能够通过 Web UI 管理对象。
CNI Plugin
首先你需要理解Container Networking Interface (CNI)。
它是一个基于插件的架构,具有供应商中立的规范和库,用于为容器创建网络接口。
它并非特定于 Kubernetes。通过 CNI,容器网络可以在 Kubernetes、Mesos、CloudFoundry、Podman、Docker 等容器编排工具之间实现标准化。
当谈到容器网络时,企业可能有不同的需求,如网络隔离、安全、加密等。随着容器技术的进步,许多网络提供商为容器创建了基于 CNI 的解决方案,并具有广泛的网络功能。您可以将其称为 CNI-Plugins
这使得用户可以从不同的提供商中选择最适合其需求的网络解决方案。
CNI 插件如何与 Kubernetes 配合使用?
- Kube-controller-manager 负责为每个节点分配 pod CIDR。每个 Pod 从 Pod CIDR 获取唯一的 IP 地址。
- Kubelet 与容器运行时交互以启动预定的 pod。 CRI 插件是容器运行时的一部分,它与 CNI 插件交互来配置 Pod 网络。
- CNI 插件支持使用覆盖网络(overlay network)在相同或不同节点上分布的 Pod 之间进行联网。
以下是 CNI 插件提供的高级功能。 - Pod 网络
- Pod 网络安全和隔离使用网络策略来控制 Pod 之间以及命名空间之间的流量。 一些流行的 CNI 插件包括:
- Calico
- Flannel
- Weave Net
- Cilium (Uses eBPF)
- Amazon VPC CNI (For AWS VPC)
- Azure CNI (For Azure Virtual network)Kubernetes networking is a big topic and it differs based on the hosting platforms.
Kubernetes Native Objects
到目前为止,我们已经了解了 Kubernetes 的核心组件以及每个组件的工作原理。
所有这些组件都致力于管理以下关键 Kubernetes 对象。
- Pod
- Namespaces
- Replicaset
- Deployment
- Daemonset
- Statefulset
- Jobs & Cronjobs
- ConfigMaps and Secrets 在网络方面,以下 Kubernetes 对象起着关键作用。
- Services
- Ingress
- Network policies. 此外,Kubernetes 还可以使用 CRD 和自定义控制器进行扩展。因此,集群组件还管理使用自定义控制器和自定义资源定义创建的对象。
CNI(Container Network Interface)
CNI是一个用于容器网络的标准化接口规范。它定义了一组规范和插件,用于为容器化应用配置网络功能,并管理它们的声明周期。CNI是由 CNCF(Cloud Native Computing Foundation) 维护的,是Kubernetes等容器编排系统中常用的网络解决方案。
在 Kubernetes 中,CNI 是用于实现 Pod 网络 的主要机制。每个 Pod 都需要一个 IP 地址,CNI 插件负责分配这些地址,并配置 Pod 间的通信规则。
常见 CNI 插件在 Kubernetes 中的功能:
- Flannel:
- 简单易用的跨主机网络解决方案。
- 提供覆盖网络(overlay network)。
- Calico:
- 支持网络策略,提供安全性和高性能的路由。
- 支持 BGP 协议。
- Weave Net:
- 支持自动发现和加密的分布式网络。
- Cilium:
- 基于 eBPF 的高性能网络解决方案,支持网络安全和可观察性。
CNI的主要功能
- 容器网络设置:
- 在容器启动时,动态地为其分配网络资源(如IP地址)。
- 提供路由和网络策略。
- 网络资源管理:
- 在容器生命周期内,管理其网络连接。
- 在容器停止或删除时,清理相应的网络资源。
- 插件化设计:
- CNI是一组接口规范,其具体实现依赖于各种插件。
- 支持灵活地扩展不同地网络功能。
gRPC(Google Remote Procedure Call)
gRPC是一个开源的、现代化的、基于HTTP/2协议的高性能远程过程调用(RPC)框架。它由Google开发,支持多种语言,旨在帮助分布式系统中的服务实现高效通信。
CIDR(Classless Inter-Domain Routing)
CIDR在Kubernetes中主要用于管理和分配集群中的网络地址,确保容器、Pod和服务能够在集群内部进行通信。以下是CIDR在Kubernetes中的具体作用和应用场景:
- Pod网络分配
- Kubernetes使用CIDR来定义Pod的网络地址范围。
- 每个Pod在创建时,会从CIDR池中分配一个唯一的IP地址。
- 这些IP地址在集群内部是唯一的,Pod之间可以通过直接使用IP进行通信。
- 服务网络分配
- Kubernetes 服务也有一个独立的 CIDR,用于分配集群内部的服务虚拟 IP(ClusterIP)。
- 这些地址范围是虚拟的,通常不会与实际的 Pod 或节点 IP 冲突。
- 在
kubeadm初始化时,可以通过--service-cidr参数指定服务 IP 的范围:
kubeadm init --service-cidr=10.96.0.0/12
# 服务发现:Service 的 ClusterIP 地址用于集群内部的服务发现和负载均衡。
# 区分范围:Pod 网络和服务网络的 CIDR 必须不重叠,以避免冲突。CRI(Container Runtime Interface)
CRI是容器运行时,常用的容器运行时有:
- containerd(unix:///var/run/containerd/containerd.sock):containerd 是一个独立的容器运行时,最初是 Docker 的核心组件,后来成为 CNCF (Cloud Native Computing Foundation) 的项目。支持标准化接口,如 OCI (Open Container Initiative),用来管理容器生命周期,包括镜像管理、容器执行、存储、网络等。
- crio(unix:///var/run/crio/crio.sock):CRI-O 是 Red Hat 开发的一个轻量级容器运行时,专门为 Kubernetes 而设计,完全兼容 CRI 标准。直接支持 OCI 镜像和运行时(如 runc、crun)。
- docker(dockershim):Docker 曾是 Kubernetes 默认的容器运行时,但 Kubernetes 1.20 起正式弃用 dockershim,1.24 起完全移除。
| 特性 | containerd | CRI-O | Docker (dockershim) | Kata Containers | gVisor |
|---|---|---|---|---|---|
| CRI 原生支持 | 是 | 是 | 否 (通过 dockershim) | 是 | 是 |
| 性能 | 高 | 高 | 较低 | 较低 (因虚拟化开销) | 较低 |
| 复杂性 | 中 | 中 | 高 | 高 | 中 |
| 安全性 | 中 | 高 | 中 | 高 | 高 |
| 适用场景 | 通用 | Kubernetes 优化 | 过渡方案 | 多租户、高隔离 | 高隔离 |
| 社区支持 | 活跃 | 活跃 | 已弃用 | 专注于特定场景 | 特定场景支持 |
CRD(Custom Resource Definitions)
用户资源自定义,用于自定义资源,比如GPU资源等。
PV(Persistent Volume)
在k8s中,PV(Persistent Volume)是集群中一块已经由管理员配置好的存储资源,它与Pod的声明周期是独立的。你可以把PV理解成Kubernetes集群中的存储池。 PV的核心概念:
- 抽象存储: PV 抽象了底层的存储实现细节,例如是 AWS EBS 卷、Google Persistent Disk、NFS 共享、本地存储等。这使得用户不需要关心具体的存储类型,只需要关注容量和访问模式。
- 静态供给: PV 通常由 Kubernetes 管理员事先创建和配置。管理员需要配置 PV 的类型、容量、访问模式等信息。
- 与 Pod 解耦: PV 的生命周期与 Pod 的生命周期是独立的。即使 Pod 被删除或重建,PV 仍然存在,数据不会丢失。这使得数据具有持久性。
- 资源: PV 是 Kubernetes 集群的一种资源对象,可以像其他 Kubernetes 资源一样被管理和查看。
- 持久化存储: PV 用于持久化存储应用程序的数据。它们保证即使 Pod 被重新调度到不同的节点,数据仍然可以被访问。 PV 的作用:
- 持久化数据: 确保应用程序的数据在 Pod 被删除或重新调度后仍然存在。
- 解耦存储和计算: 允许将存储资源与计算资源分开管理。
- 统一的存储接口: 为不同的存储类型提供一个统一的接口。
- 数据共享: 可以通过不同的访问模式在 Pod 之间共享数据。 PV 的主要属性:
- capacity (容量): 指定了 PV 的存储容量,例如 10Gi、100Gi 等。
- accessModes (访问模式): 指定了如何访问 PV 的权限:
- ReadWriteOnce (RWO): 可以被单个节点以读写模式挂载。
- ReadOnlyMany (ROX): 可以被多个节点以只读模式挂载。
- ReadWriteMany (RWX): 可以被多个节点以读写模式挂载。
- storageClassName (存储类): 与 StorageClass 关联,用于动态供给 PV。
- persistentVolumeReclaimPolicy (回收策略): 指定了当 PV 被释放时 Kubernetes 应该如何处理底层的存储资源:
- Retain: 保留,不删除底层的存储资源,需要手动清理。
- Recycle: 回收,删除底层存储资源中的数据,并将其返回到 PV 池中。
- Delete: 删除,删除底层存储资源。
- volumeMode (卷模式): 指定卷是作为文件系统还是块设备:
- Filesystem: 作为文件系统使用。
- Block: 作为原始块设备使用。
- nodeAffinity (节点亲和性): 指定 PV 只能在某些特定的节点上被挂载。
- claimRef (声明引用): 绑定到 PVC 后,会记录 PVC 的引用。
PV 与 PVC 的关系:
- Persistent Volume (PV) 是实际的存储资源。
- Persistent Volume Claim (PVC) 是对 PV 的请求。
- PVC 相当于用户对存储资源的"声明”。
- Kubernetes 会将 PVC 绑定到合适的 PV 上,从而让 Pod 可以使用存储。
疑问及解释
- 一个PV同时只能绑定一个PVC,和AccessMode无关
- hostPath的PV中hostPath无关节点,会在pod所在的工作节点上使用该hostPath指定的路径作为PV的存储路径。
常用操作
- 创建PV:
kubectl apply -f pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-0
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
volumeMode: Filesystem
hostPath:
path: /data/k8s/pv/pv-0- 删除PV:
kubectl delete -f pv.yaml - 查看信息:
kubectl describe pv <pv-name> - 修改信息:
kubectl edit pv <pv-name>,主要用于删除RefClaim将Released pv手动变成Availabled。 - 注意PV的hostPath如果是自动创建的,权限有时候可能需要手动调整
StatefullSet
StatefulSet 是 Kubernetes 中的一种用于管理有状态应用的控制器,与无状态的 Deployment 不同,它专为需要稳定标识、顺序启动和终止的有状态应用设计,比如数据库(MySQL、MongoDB)、分布式系统(Zookeeper、Kafka)等。
StatefulSet 的核心特性
- 稳定的 Pod 标识:
- StatefulSet 的每个 Pod 都有一个稳定且唯一的网络标识,格式为
<statefulset-name>-<ordinal>,例如mysql-0,mysql-1。 - 即使 Pod 被重新调度到其他节点,其名称仍然保持不变。
- StatefulSet 的每个 Pod 都有一个稳定且唯一的网络标识,格式为
- 稳定的存储卷:
- 每个 Pod 都可以绑定自己的 PersistentVolume(PV),并且 PV 的生命周期与 Pod 分离。
- 适合需要持久化存储的应用。
- 有序部署和更新:
- Pod 按顺序启动(从
0开始)。 - 滚动更新时,新的 Pod 按顺序替换旧的 Pod。
- Pod 按顺序启动(从
- 有序缩放:
- 缩容时,Pod 从最后一个开始删除(如先删除
mysql-2,再删除mysql-1)。
- 缩容时,Pod 从最后一个开始删除(如先删除
Job
Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束。
Job 的核心特性
- 任务完成即退出:
- 与 Deployment 或 StatefulSet 不同,Job 管理的 Pod 在任务完成后会自动退出,不会重启(除非配置了重试)。
- 并行执行:
- Job 支持并发运行多个 Pod 来加快任务处理速度 (
.spec.parallelism)。
- Job 支持并发运行多个 Pod 来加快任务处理速度 (
- 完成次数:
- 可以指定任务需要成功完成的次数 (
.spec.completions)。
- 可以指定任务需要成功完成的次数 (
关键配置
- restartPolicy:
- Job 的 Pod
restartPolicy只能设置为OnFailure或Never。- OnFailure: Pod 故障时,容器会在当前 Pod 内重启。
- Never: Pod 故障时,Kubernetes 会创建一个新的 Pod 进行重试。
- 注意: 不能设置为
Always,因为 Job 是为了“完成”任务,而不是一直运行。
- Job 的 Pod
- backoffLimit:
- 指定 Job 标记为失败前的重试次数。默认值为 6。
- 重试间隔呈指数级增长(10s, 20s, 40s…),上限为 6 分钟。
Ingress
Ingress 是 Kubernetes 中一个非常重要的资源对象,它允许你将外部的 HTTP 和 HTTPS 流量路由到集群内部的服务。你可以把 Ingress 理解为 Kubernetes 集群的 “入口控制器” 或 “反向代理”。它在 Kubernetes 集群的边缘工作,负责接收外部请求,并根据你定义的规则将这些请求转发到正确的后端服务。 Ingress 的核心概念:
- 外部流量路由: Ingress 的主要功能是接收来自外部的 HTTP/HTTPS 请求,并根据域名、路径或其他规则,将这些请求转发到 Kubernetes 集群内部的 Service。
- 反向代理: Ingress 本身不提供服务,它只是一个反向代理,将外部请求转发到后端的 Service。
- Ingress Controller: 为了让 Ingress 对象工作,你需要部署一个 Ingress Controller。Ingress Controller 是一个特殊的 Pod,它负责监听 Ingress 资源的创建和更新,并根据 Ingress 对象的配置,实现流量路由。
- 规则定义: Ingress 对象中定义了路由规则,这些规则指定了哪些外部请求应该被转发到哪些后端服务。
- HTTPS 支持: Ingress 可以处理 HTTPS 请求,并且可以配置 TLS/SSL 证书。 为什么需要 Ingress?
- 简化服务暴露: Kubernetes Service 默认只能在集群内部访问,如果想将 Service 暴露给外部,通常需要使用 LoadBalancer 类型的 Service 或者 NodePort 类型的 Service。但这两种方式都比较复杂,并且会暴露内部端口。Ingress 提供了一种更简洁、灵活的方式来管理外部流量。
- 统一入口: Ingress 可以作为所有外部请求的统一入口,并且可以根据不同的规则将流量分发到不同的服务。
- 域名路由: Ingress 可以根据请求的域名将流量转发到不同的服务,例如,将 www.example.com 的请求转发到 Web 服务,将 api.example.com 的请求转发到 API 服务。
- 路径路由: Ingress 可以根据请求的 URL 路径将流量转发到不同的服务,例如,将 /api 开头的请求转发到 API 服务,将 / 开头的请求转发到 Web 服务。
- HTTPS 支持: Ingress 可以配置 TLS/SSL 证书,实现 HTTPS 加密,保护用户数据安全。
- 灵活配置: Ingress 支持各种路由规则、重定向、负载均衡等高级配置。 Ingress 的主要组件:
- Ingress 对象:
- 这是一个 Kubernetes 资源对象,其中定义了如何将外部请求路由到集群内部服务的规则。
- Ingress 对象本身不具备路由能力,它只是一个配置描述。
- Ingress 对象需要配合 Ingress Controller 才能工作。
- Ingress Controller:
- 这是一个运行在 Kubernetes 集群中的 Pod,它负责监听 Ingress 对象的创建和更新。
- Ingress Controller 会根据 Ingress 对象的定义,配置底层的反向代理 (例如 Nginx, Traefik, HAProxy 等) 来实现流量路由。
- Ingress Controller 有多种选择,例如:
- nginx-ingress-controller (最常用)
- traefik
- haproxy-ingress
- kong-ingress-controller
- 后端 Service:
- Ingress 对象会将流量转发到后端的 Kubernetes Service。
重要概念
扩容
在Kubernetes中,水平扩缩容和垂直扩缩容是管理应用负载时常用的两种扩缩容方式,它们的核心区别在于资源增加的方向(节点数量 vs 资源规格)。以下是两者的含义和对比。
| 特性 | 水平扩缩容 | 垂直扩缩容 |
|---|---|---|
| 核心方式 | 增减 Pod 的数量 | 增减 Pod 的资源规格 |
| 扩展方向 | 宽度(多个实例) | 深度(单实例能力提升) |
| 适用场景 | 无状态服务(如 Web 应用、API) | 状态服务(如数据库、缓存) |
| 实现难度 | 配置简单,弹性强 | 配置复杂,可能引发重启 |
| 性能瓶颈 | 节点资源不足或 Pod 过多 | 单 Pod 的资源限制 |
| 容错能力 | 高,支持多实例 | 低,单实例失败可能影响服务 |
水平扩缩容(Horizontal Scaling)
含义
- 水平扩缩容通过增加或减少运行相同应用实例(Pod)的数量来应对负载的变化。
- 每个实例(Pod)的配置(如CPU和内存)保持不变。
实现方式
k8s提供以下方式进行水平扩缩容:
- 手动扩缩容使用
kubectl scale命令手动调整副本数:
kubectl scale deployment <deployment-name> --replicas=<number>- 自动扩缩容借助 Horizontal Pod Autoscalar(HPA) 自动调整Pod的数量:
kubectl autoscale deployment <deployment-name> --cpu-percent=50 --min=2 --max=10HPA 会根据指标(如 CPU 使用率)动态调整 Pod 的副本数。
优点
- 灵活性高,适合应对短期流量高峰。
- 容错能力强,多个实例分布在不同节点上,单个Pod的失败不会影响整体服务。
缺点
- 对状态ful应用(如数据库)支持较差,需要额外的机制来同步数据。
- 扩容需要考虑节点的可用资源。
垂直扩缩容(Vertical Scaling)
含义
- 垂直扩缩容通过增加或减少单个实例(Pod)分配的资源(如CPU或内存)来应对负载变化。
- Pod的数量保持不变,但每个Pod的性能得到提升。
实现方式
- 手动调整资源编辑Pod或Deployment的资源限制:
kubectl edit deployment <deployment-name>修改resources.requests和resources.limits:
resources:
requests:
memory: "512Mi"
cpu: "0.5"
limits:
memory: "1Gi"
cpu: "1"- 自动调整资源借助 Vertical Pod Autoscalar(VPA) 动态调整Pod的资源:
kubectl apply -f vpa.yaml
# 示例VPA配置:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: example-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: example-deployment
updatePolicy:
updateMode: "Auto"优点
- 适合状态ful的应用程序(如数据库)或无法轻松横向扩展的服务。
- 提高单个Pod的性能,无需复杂的数据同步逻辑。
缺点
- 存在资源瓶颈,单个实例的能力有限。
- 扩容过程中可能会重启Pod,导致短暂服务中断。
Taint
在 Kubernetes 中,taints 是一种机制,用于控制 Pods 的调度行为。通过给节点添加 taints,您可以确保特定的 Pods 只被调度到满足特定条件的节点上。taints 和 tolerations(容忍)一起工作,以帮助 Kubernetes 在调度 Pods 时执行更细粒度的控制。
举个例子,某个节点上有以下taints:
taints:
- effect: NoSchedule
key: GPU_ONLY
value: "1"这个配置的含义是:
key: GPU_ONLY:定义了 taint 的键(key)。在这种情况下,它的键是GPU_ONLY,意味着节点上的资源(例如 GPU)是专用的,仅允许带有GPU_ONLY=1容忍的 Pods 被调度到该节点上。value: "1":这是 taint 的值。在这里,值是"1",意味着该节点需要具有对应容忍GPU_ONLY=1的 Pods。effect: NoSchedule:指定 taint 的效果。NoSchedule表示如果一个节点有这个 taint,Kubernetes 将不会调度不具有相应容忍(toleration)的 Pods 到这个节点上。- 节点配置:节点上添加了
GPU_ONLY=1的 taint,意味着该节点上只允许具有对应容忍的 Pods 被调度过去。如果一个 Pod 没有GPU_ONLY=1的容忍,它将不会被调度到该节点上。 - Pods 的容忍(Toleration):如果某个 Pod 需要在这个带有 taint 的节点上运行,您需要在 Pod 的配置中显式添加相应的
toleration。例如:
tolerations:
- key: "GPU_ONLY"
operator: "Equal"
value: "1"
effect: "NoSchedule"
# 这意味着该 Pod 可以容忍 `GPU_ONLY=1` taint,因此它可以被调度到带有该 taint 的节点上。调度策略
topologySpreadConstraints
# 按照节点上的满足label的pod数量进行分配,使得pod尽量分散的分布在不同的节点上。
kind: Job
metadata:
labels:
app: my-job # 1. 自己的标签
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1 # 每个区域最大偏差
topologyKey: kubernetes.io/hostname # 按什么粒度(范围)来区分节点
whenUnsatisfiable: ScheduleAnyway # 类似 Affinity 的软策略,无法满足均匀时依然调度
labelSelector:
matchLabels:
app: my-job # 2. 告诉调度器:数一下节点上 "app=my-job" 的 Pod 有几个Affinity
节点亲和度用来调节pod的节点调度优先级,样例:
"affinity": {
"podAffinity": {
"preferredDuringSchedulingIgnoredDuringExecution": [
{
"weight": 100,
"podAffinityTerm": {
"topologyKey": "kubernetes.io/hostname",
"labelSelector": {
"matchExpressions": [
{
"key": "ApplicationType",
"values": [
"scheduler"
],
"operator": "In"
}
]
}
}
}
]
}
}滚动更新
基础概念
滚动更新(Rolling Update) 是 Kubernetes 中 Deployment 对象的默认更新策略。它的核心思想是:通过逐个替换旧版本的 Pod 来实现应用的平滑升级,从而保证在整个更新过程中,服务始终可用,实现零停机部署。(与之相对的是Recreate策略,会一次性关闭所有实例,然后再启动,会导致服务中断)。
基本流程:
- 启动一个(多个)新版本的Pod。
- 等待新的Pod成功启动并进入"就绪(Ready)“状态。
- 一旦新 Pod 就绪,就关闭一个(或多个)旧版本的 Pod。
- 重复此过程,直到所有旧 Pod 都被新 Pod 替换。
核心参数
在你的 deployment.yaml 文件中,滚动更新的策略由 .spec.strategy.rollingUpdate 下的两个关键参数控制:maxUnavailable 和 maxSurge。这两个参数决定了更新的速度和安全性。
让我们假设你的 Deployment 配置了 replicas: 10,即期望有 10 个运行中的 NestJS 应用 Pod。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nestjs-app
spec:
replicas: 10
strategy:
type: RollingUpdate # 这是默认值,可以不写
rollingUpdate:
maxUnavailable: 25% # 可以是百分比或整数
maxSurge: 25% # 可以是百分比或整数
template:
# ... Pod template spec ...maxUnavailable:
- 含义:在更新过程中,最多允许有多少个 Pod 处于不可用状态。
- 作用:保证服务的最低可用性。这是安全性的保障。
- 计算方式:
- 百分比 (25%): ceil(10 * 0.25) = 3。这意味着在更新过程中,最多可以有 3 个 Pod 同时挂掉或正在被终止,K8s 必须保证至少有 10 - 3 = 7 个 Pod 始终在运行。
- 整数 (2): 明确指定最多只能有 2 个 Pod 不可用,即必须保证至少有 10 - 2 = 8 个 Pod 在运行。
- 设置为 0:这意味着在整个更新过程中,必须时刻保持 replicas 数量(10个)的 Pod 可用。在这种情况下,必须先增加新 Pod(Surge),等新 Pod 就绪后,才能删除旧 Pod。因此,如果 maxUnavailable 为 0,maxSurge 必须大于 0。 maxSurge:
- 含义:在更新过程中,最多允许超出 replicas 数量的 Pod 存在。
- 作用:加快更新速度,但需要消耗额外的计算资源(CPU, Memory)。这是速度的权衡。
- 计算方式:
- 百分比 (25%): ceil(10 * 0.25) = 3。这意味着在更新过程中,Pod 的总数最多可以达到 10 + 3 = 13 个。Kubernetes 可以先启动 3 个新 Pod,然后再开始销毁旧 Pod。
- 整数 (2): 明确指定最多可以额外创建 2 个 Pod,总数最高可达 10 + 2 = 12 个。
- 设置为 0:这意味着不能创建任何额外的 Pod。在这种情况下,必须先删除一个旧 Pod,腾出空间后才能创建一个新 Pod。因此,如果 maxSurge 为 0,maxUnavailable 必须大于 1(或 10%)。
健康探针(Health Probes):滚动更新的眼睛
仅仅配置了 maxSurge 和 maxUnavailable 是不够的。Kubernetes 如何知道一个新启动的 NestJS Pod “已经准备好” 接收流量了呢?答案是就绪探针(Readiness Probe)。
- 就绪探针 (Readiness Probe):告诉 K8s 你的应用是否准备好处理请求。在滚动更新中,只有当新 Pod 的就绪探针成功后,K8s 才会认为这个 Pod 是“Ready”的,然后才会去终止一个旧 Pod。
- 存活探针 (Liveness Probe):告诉 K8s 你的应用是否还在运行。如果失败,K8s 会重启这个 Pod。
- 启动探针 (Startup Probe):用于启动时间较长的应用。在启动探针成功前,K8s 不会执行存活和就绪探针,这可以防止应用因启动慢而被误杀。
对于你的 NestJS 应用,配置健康探针至关重要!
一个好的实践是使用 NestJS 的 @nestjs/terminus 库来创建健康检查端点。
示例 deployment.yaml 中的探针配置:
# ... in your Pod template spec ...
spec:
containers:
- name: my-nestjs-app
image: your-image:tag
ports:
- containerPort: 3000
readinessProbe: # 关键!
httpGet:
path: /health/ready # 你在 NestJS 中创建的就绪检查接口
port: 3000
initialDelaySeconds: 15 # Pod启动后,等待15秒再开始第一次探测
periodSeconds: 10 # 每10秒探测一次
failureThreshold: 3 # 连续失败3次后,认为Pod未就绪
livenessProbe:
httpGet:
path: /health/live # 你在 NestJS 中创建的存活检查接口
port: 3000
initialDelaySeconds: 20
periodSeconds: 10为什么这很重要? 想象一下你的 NestJS 应用启动时需要连接数据库、初始化缓存等,这可能需要几秒钟。如果没有就绪探针,K8s 刚启动 Pod 就会立即认为它“准备好了”,并把流量发过去,同时终止一个旧 Pod。但此时你的 NestJS 应用可能还在初始化,无法处理请求,导致用户收到 50x 错误。有了就绪探针,K8s 会耐心等待,直到你的 /health/ready 接口返回 200 OK,才进行下一步操作。
优雅停机(Graceful Shutdown)
如何保证新来的流量不进入即将被终止的旧Pod?
通过 Service、Endpoints 和 Readiness Probe 的联动机制。
这背后是一个精妙的自动化流程,理解这个流程是关键:
- Service 和 Endpoints 的关系:
- 在 Kubernetes 中,流量通常不是直接打到 Pod 上的,而是先打到一个抽象层 Service。
- Service 通过一个标签选择器(Label Selector)来发现它应该代理哪些 Pod。
- 但 Service 并不直接持有 Pod 列表,它依赖一个叫做 Endpoints (或在新版本中叫 EndpointSlice) 的对象。这个 Endpoints 对象才真正维护了一个健康的、准备好接收流量的 Pod 的 IP 地址列表。
- 你的 kube-proxy 组件会监视 Endpoints 对象,并根据这个列表实时更新节点上的网络规则(如 iptables),从而将发往 Service IP 的流量正确路由到健康的 Pod 上。
- 滚动更新触发时的关键步骤:
- 当滚动更新决定要终止一个旧 Pod 时,Kubernetes 不会立即杀死它。
- 第一步是:将该 Pod 的状态从 Running 更新为 Terminating。
- 当 Pod 进入 Terminating 状态时,一个非常重要的变化发生了:这个 Pod 会被认为是不“就绪”(Not Ready)的。
- Kubernetes 的 EndpointSlice Controller 一直在监视 Pod 的“就绪”状态。它一旦发现这个 Pod 不再 Ready,就会立即将其 IP 地址从 Endpoints 对象中移除。
- 一旦 IP 从 Endpoints 中移除,kube-proxy 就会在集群的所有节点上更新网络规则。
结论就是:在 K8s 发送终止信号给 Pod 之前,它就已经先将 Pod 从负载均衡池中摘除了。这样一来,所有新的流量请求就不会再被路由到这个即将被终止的旧 Pod 上了。
这个过程非常快,但不是绝对瞬时的。在极少数情况下,如果网络规则更新有延迟,可能会有一两个新请求“溜”进去。为了处理这种情况,我们还有第二道防线。
问题二:如何保证旧 Pod 处理完已有请求后再退出?
答案:通过 Pod 的生命周期钩子(Lifecycle Hooks)和 terminationGracePeriodSeconds。
在将 Pod 从 Endpoints 中移除后,Kubernetes 才开始真正地终止 Pod 进程。这个过程被称为优雅停机(Graceful Shutdown)。
- SIGTERM 信号:
- Kubernetes 会向 Pod 内的主进程(在你的场景下就是 NestJS 应用的 Node.js 进程)发送一个 SIGTERM 信号。
- SIGTERM 不是一个强制杀死信号(那是 SIGKILL),而是一个“礼貌的通知”,意思是:“请准备关闭,完成你手头的工作,然后自行退出。”
- 应用程序的责任(你的 NestJS 代码):
- 一个设计良好的应用程序需要能够捕获 SIGTERM 信号。
- 当你的 NestJS 应用收到 SIGTERM 后,它应该执行以下操作:
- 停止接受新请求:虽然理论上 K8s 已经把流量入口关了,但作为双重保险,应用自身也应该关闭监听端口(例如调用 server.close())。这能拒绝掉那些在 Endpoints 更新延迟期间“溜”进来的请求。
- 处理完现有请求:继续处理所有已经接收但尚未完成的请求。
- 清理资源:关闭数据库连接、清理文件句柄等。
- 正常退出:当所有工作完成后,主动退出进程 (process.exit(0))。
- terminationGracePeriodSeconds:
- 这是在 Pod Spec 中定义的一个字段,默认值是 30 秒。
- 它代表了 Kubernetes 的**“耐心”**。从发送 SIGTERM 信号开始计时,K8s 会给你的应用最多 terminationGracePeriodSeconds 这么长的时间来完成上述的优雅停机操作。
- 如果你的应用在这段时间内成功自行退出,一切完美。
- 如果超过了这个时间你的应用还没退出,Kubernetes 就会失去耐心,发送 SIGKILL 信号,强制杀死进程。这时,任何还在处理中的请求都会被中断。
# In your deployment.yaml spec: template: spec: containers: - name: my-nestjs-app # ... other container config terminationGracePeriodSeconds: 60 # 比如你的请求最长可能处理50秒,可以设置为60秒
给你的 NestJS 应用的实践建议
好消息是,NestJS 框架内置了对优雅停机的支持!你只需要在你的 main.ts 中启用它即可。
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 关键!启用关机钩子
// 这会让 NestJS 监听 SIGTERM 等系统信号
// 当收到信号时,它会触发所有模块的 onModuleDestroy() 和 onApplicationShutdown() 生命周期钩子
// 并确保在应用关闭前,所有现有的HTTP请求都被处理完毕。
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();通过调用 app.enableShutdownHooks(),NestJS 会为你处理大部分 SIGTERM 的逻辑。你只需要确保:
- 在你的 deployment.yaml 中设置一个合理的 terminationGracePeriodSeconds,确保它比你应用中最长的请求处理时间要长。
- 如果你的应用有复杂的清理逻辑(如关闭数据库连接池),你可以在你的 Service 中实现 OnApplicationShutdown 接口来处理。
ResourceQuota
ResourceQuota 是Kubernetes的一个内置对象,用于限制单个命名空间(Namespace)中所有资源的总消耗量。
作用机制
- 创建:用户在某个命名空间(Namespace)中定义一个ResourceQuota对象。
- 资源请求拦截:当任何用户尝试在该Namespace中创建或更新资源时(例如,创建一个Pod、一个Service或者一个PVC),kubernetes的API Server会拦截这个请求
- 计算和检查:API Server会检查该Namespace中已有的ResourceQuota,他会计算“当前已用资源” + “本次请求资源”是否会超过设定的配额(Hard Limit)。
- 准入或拒绝:
- 如果总量未超过配额:请求被批准,资源被成功创建
- 如果总量将超过配额:请求被拒绝,返回一个明确的错误。
- 重要:对于计算资源(CPU和Memory等),ResourceQuota只有在Pod的每个容器都明确设置了资源的requests和limits时才能有效工作
例子
apiVersion: v1
kind: ResourceQuota
metadata:
name: offline-renderer-resource-quota
namespace: scheduler
spec:
hard:
pods: "2"
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values:
- offline-renderer-priority-class调度:优先级&抢占
核心思想
优先级与抢占机制的根本目标是:确保在集群资源紧张时,最重要的应用(高优先级)能够优先获得资源并运行,即使这需要牺牲掉不太重要的应用(低优先级)。
关键组件
PriorityClass对象- 定义:这是一个集群级别(Cluster-Scoped)的对象,与命名空间无关。它用来定义一个优先级的“等级名称”和其对应的整数值。
- 关键字段:
value(整数):数字越大,优先级越高。这是调度器进行比较的唯一依据。globalDefault(布尔值):如果设为true,那么集群中没有明确指定priorityClassName的 Pod 都会自动获得这个默认优先级。一个集群中最多只能有一个globalDefault的PriorityClass。preemptionPolicy(字符串):PreemptLowerPriority(默认值): 允许这个优先级的 Pod 抢占更低优先级的 Pod。Never: 不允许这个优先级的 Pod 抢占任何其他 Pod。
- 示例:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority-apps value: 1000000 preemptionPolicy: PreemptLowerPriority description: "用于关键业务应用,允许抢占。" --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority-preemptible value: 100000 preemptionPolicy: Never description: "用于可被挤占的低优先级任务。"- Pod Spec 中的
priorityClassName字段- 作用:在 Pod 的规约(spec)中,通过这个字段来“申请”使用一个已经定义好的
PriorityClass - 示例:
apiVersion: v1 kind: Pod metadata: name: my-high-priority-pod spec: priorityClassName: high-priority-apps # <--- 在这里指定 containers: - name: main image: nginx - 作用:在 Pod 的规约(spec)中,通过这个字段来“申请”使用一个已经定义好的
工作流程:抢占如何发生
当一个高优先级的 Pod 因为资源不足而无法被调度时(处于 Pending 状态),抢占流程被触发:
- 寻找牺牲品
kube-scheduler开始在整个集群(跨所有节点、所有命名空间)中寻找可以被驱逐的 Pod。- 筛选条件是:这些 Pod 的优先级严格低于当前
Pending的高优先级 Pod。
- 选择最佳节点
- 调度器会进行模拟计算,找出哪个节点在驱逐一个或多个“牺牲品”后,能够满足高优先级 Pod 的资源需求。可能会有多个节点符合条件。调度器会选择最优的一个(例如,驱逐的 Pod 数量最少或优先级最低的)。
- 提名节点
- 一旦选定目标节点和牺牲品,调度器并不会立即调度高优先级 Pod。它会先更新高优先级 Pod 的
.status.nominatedNodeName字段,将其“提名”到目标节点。 - 这是一个关键的预留步骤,它告诉集群:“这个节点很快会空出资源,请为我这个高优先级 Pod 预留”。这可以防止在牺牲品被驱逐后,另一个中等优先级的 Pod “插队”抢走资源。
- 一旦选定目标节点和牺牲品,调度器并不会立即调度高优先级 Pod。它会先更新高优先级 Pod 的
- 执行驱逐
- 调度器向
kube-api-server发送请求,要求删除(驱逐)在提名节点上选中的那些牺牲品 Pod。 kubelet收到删除指令后,会优雅地终止这些 Pod(发送SIGTERM信号)。
- 调度器向
- 最终调度
- 当牺牲品 Pod 被成功终止,资源被释放后,调度器会再次尝试调度那个高优先级的 Pod。由于之前已经“提名”了节点,它会被顺利地调度到该节点上。
重要特性与关联概念
- 集群范围
-
PriorityClass是集群对象,抢占也是跨命名空间的。namespace-a的高优先级 Pod 可以驱逐namespace-b的低优先级 Pod。
-
- PodDisruptionBudget (PDB) 的制约
- PDB 是抢占的“刹车片”。调度器在驱逐一个 Pod 前,必须检查这个操作是否会违反该 Pod 所属应用的 PDB(例如,
minAvailable规定)。 - 如果驱逐会违反 PDB,调度器会放弃驱逐这个 Pod,并去寻找其他牺牲品。如果找不到可以安全驱逐的牺牲品,抢占就会失败,高优先级 Pod 将继续
Pending。
- PDB 是抢占的“刹车片”。调度器在驱逐一个 Pod 前,必须检查这个操作是否会违反该 Pod 所属应用的 PDB(例如,
- 与 ResourceQuota 的关系
ResourceQuota限制一个命名空间内的资源总量,是关于“容量”的。PriorityClass决定资源分配的优先顺序,是关于“权利”的。- 两者共同作用,但目的不同。
- 高优先级 Pod 也可能 Pending
- 一个高优先级 Pod 无法调度时,并不总是能成功抢占。如果集群中所有比它优先级低的 Pod 加起来的资源,都不足以满足它的需求,或者所有可被抢占的 Pod 都受 PDB 保护,那么它将无法执行抢占,只能继续
Pending。
- 一个高优先级 Pod 无法调度时,并不总是能成功抢占。如果集群中所有比它优先级低的 Pod 加起来的资源,都不足以满足它的需求,或者所有可被抢占的 Pod 都受 PDB 保护,那么它将无法执行抢占,只能继续
Service概念
Service是Kubernetes中用于定义一组Pod的访问策略的抽象。它为一组Pod提供了一个稳定的网络端点(IP地址,端口),使得其他服务或外部客户端可以通过这个端点访问这些Pod,而不需要关注Pod的具体IP地址或状态。
总结下来Service的作用是:
- 提供负载均衡:将流量分发到后端的多个
Pod。 - 提供稳定的网络标识:即使
Pod的IP地址发生变化,Service的IP地址和端口保持不变。 - 支持多种访问方式:ClusterIP(集群内部访问)、NodePort(通过节点IP访问,可以通过所有节点的IP进行访问,k8s内部会对流量进行转发)、LoadBalancer(通过云提供商的负载均衡器访问)。
示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 9376ServiceAccount概念
ServiceAccount是Kubernetes中用于为Pod提供身份验证的机制。每个Pod都可以关联一个ServiceAccount,用于在访问Kubernetes API时进行身份。
作用:
- 为
Pod提供身份:Pod可以使用ServiceAccount的凭证访问Kubernetes API。 - 控制访问权限:通过
RoleBinding或ClusterRoleBinding将ServiceAccount与特定的权限绑定,从而控制Pod可以执行的操作。
机制:
- 与Pod关联之后,Kubernetes会自动将
ServiceAccount的认证信息挂载到Pod的/var/run/secret/kubernetes.io/serviceaccount目录中。该目录包含以下文件:- token:用于身份验证的JWT令牌。
- ca.crt:Kubernetes API服务器的CA证书。
- namespace:
Pod所在的命名空间。 - 这些文件使得
Pod可以通过Kubernete API进行身份认证。
示例:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-accountEndpoint的概念(deprecated)
在Kubernetes中,Service的Endpoint是一个至关重要的概念,它连接了Service这个抽象服务和实际提供服务的Pods。简单来说,Endpoint就是Service背后真正提供服务的Pods的IP地址和端口的列表。 更详细的解释: 1. Service 的作用:抽象和稳定访问点 在 Kubernetes 中,Service 的主要作用是:
- 抽象服务访问: Service 为一组 Pods 提供了一个稳定的网络访问入口,无论 Pods 如何变化(例如,重启、扩容、缩容),客户端始终可以通过 Service 的 IP 地址和端口访问服务。
- 负载均衡: Service 可以将请求负载均衡到后端的多个 Pods 上,提高服务的可用性和性能。
- 服务发现: Service 提供了一个稳定的 DNS 名称,方便集群内部的其他应用通过服务名称来发现和访问服务。
EndpointSlice概念
EndpointSlice 是 Service 和 Pod 之间的桥梁。它存储了 Service selector 匹配到的所有健康 Pod 的真实网络端点(IP 地址和端口)。
- 自动创建和管理:Kubernetes 的一个控制器(endpoint-slice-controller)会持续监控 Pod 和 Service。
- 当一个 Service 被创建时,控制器会找到所有匹配其 selector 的 Pod。
- 它会收集这些 Pod 的 IP 地址和 targetPort。
- 然后,它会为这个 Service 创建一个或多个 EndpointSlice 对象,并将 Pod 的信息填进去。
- 当有 Pod 创建、删除、或变得不健康时,控制器会自动更新相关的 EndpointSlice 对象。
- 与 Service 关联:EndpointSlice 通过一个特殊的标签
kubernetes.io/service-name: <service-name>来与对应的 Service 关联。 - 可扩展性:之所以叫 “Slice”(切片),是因为当一个 Service 后面有成千上万个 Pod 时,端点信息会被分割到多个 EndpointSlice 对象中,避免了单个 Endpoint 对象过大导致的性能问题。这是它取代旧的 Endpoints 资源的主要原因。
Role, ClusterRole和对应的Binding
Role
Role是Kubernetes中用于定义一组权限的机制。它通常与RoleBinding结合使用,用于授权某个主体在特定 命名空间级别的权限。
作用:
- 定义权限:
Role定义了可以在某个命名空间中执行的操作(创建、读取、更新、删除资源)。 - 命名空间作用域:
Role的作用范围仅限于单个命名空间。
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: my-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]RoleBinding是Kubernetes中用于将Role或ClusterRole绑定到User、Group或ServiceAccount的机制。它定义了谁(主体)在哪个命名空间中拥有哪些权限。
作用:
- 授权:将特定的权限授予某个主体(如
ServiceAccount),使其能够在指定的命名空间执行某些操作。 - 细粒度控制:通过
RoleBinding可以精确控制每个主体的权限范围。
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-role-binding
namespace: default
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: default
roleRef:
kind: Role
name: my-role
apiGroup: rbac.authorization.k8s.ioClusterRole
ClusterRole和Role类似,但是它定义的是 集群级别的权限,而不是命名空间级别的权限。ClusterRole可以与ClusterRoleBinding结合使用,授予主体在整个集群中的权限。
作用:
- 定义集群范围的权限:
ClusterRole定义了可以在整个集群中执行的操作。 - 集群作用域:
ClusterRole的作用范围是整个集群,而不是单个命名空间。
示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: my-cluster-role
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]ClusterRoleBinding是用于将ClusterRole绑定到User、Group或ServiceAccount。
跨namespace的Service访问
在 Kubernetes 中,如果 Namespace A 中的服务需要访问 Namespace B 中的服务,可以通过以下步骤实现: 使用服务的全限定域名(FQDN) Kubernetes 中的服务可以通过全限定域名(FQDN)访问。格式为:
<service-name>.<namespace>.svc.cluster.local例如,Namespace B 中的服务 my-service 可以通过以下域名访问:
my-service.namespace-b.svc.cluster.localresources
requests & limits
定义了容器资源请求和限制。 举个例子:
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi- limits:标识容器能够使用的最大资源限制。容器不会查出这个上限,否则可能导致性能受限或被系统限制。如果cpu超的话,不会杀掉任务,会限制CPU使用,如果memory超的话会OOMKilled。可以超限。
- request:标识容器创建需要的资源,调度器会寻找符合要求的节点调度该pod,如果所有都不满足的话,任务创建会pendding。不可超限。如果只写了limits,而没写requests的情况下,requests会自动设置成和limits相同(按字段)。
| 特性 | requests | limits |
|---|---|---|
| 目的 | 保证资源,用于调度 | 限制资源,用于运行时 |
| 效果 | Pod 必须被调度到能满足其请求的节点上 | 容器使用资源不能超过此上限 |
| 超出后 | (不会超出,因为是保证值) | CPU: 被限流;内存: 被 OOMKilled |
| 核心作用 | 保证 Pod 的基本运行需求和集群的调度效率 | 防止单个 Pod 耗尽节点资源,保证集群稳定性 |
QoS(服务质量)
为了更清晰地理解,我们对比一下三种常见的设置方式:
| 设置方式 | requests 的值 | limits 的值 | QoS 等级 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 只设置 limits | 等于 limits | 用户定义 | Guaranteed | 最高稳定性,不易被杀死。 | 资源浪费,调度要求高。 |
| 只设置 requests | 用户定义 | 无限制(节点容量) | Burstable | 调度灵活,允许突发使用资源。 | 容易成为“吵闹邻居”,可能耗尽节点资源影响他人。不推荐。 |
| requests < limits | 用户定义 | 用户定义 | Burstable | 平衡了稳定性和资源利用率,是最推荐的方式。 | 在资源紧张时,稳定性低于 Guaranteed。 |
annotations & labels
在Kubernetes中,Labels和Annotations都是资源对象(如Pod、Deployment、Service等)添加元数据的机制,但他们的用途和行为有明显的区别:
| 内容 | Labels | Annotations |
|---|---|---|
| 核心用途 | 标识和选择资源:用于标识对象的属性,以便通过查询(Label Selector)筛选和操作相关资源。 例如:标识应用名称( app=frontend)、环境(env=prod)、版本(version=v1)。 | 存储非识别性元数据:用于记录与对象相关的附加信息,这些信息不会被 Kubernetes 直接用于资源选择,而是供外部工具或用户查看。 例如:记录构建版本、维护者信息、监控配置、日志策略等。 |
| 数据内容 | 通常是 简短的键值对,用于快速识别和分组资源。 例如: app: myapp, tier: backend。 | 可以包含 任意类型的数据(如长文本、JSON、URL 等),适合存储复杂或非结构化的信息。 例如: deployment.kubernetes.io/revision: "2"build-info: "{\"commit\": \"abc123\", \"branch\": \"main\"}"。 |
| Kubernetes 的行为 | Kubernetes 的控制器和 API 会 主动使用 Labels 来选择和操作资源。 例如: - Service 通过 Label Selector 选择 Pod。 - Deployment 通过 Labels 管理 ReplicaSet。 | Kubernetes 不会解析 Annotations 的内容,仅作为存储键值对的容器。 外部工具(如 CI/CD 系统、监控工具)可以读取并使用这些信息。 |
| 命名规则 | 键名和值有严格限制: - 键名格式: [前缀/]名称,其中前缀可选,名称部分必须是 DNS 子域名格式(如 k8s.io/app)。- 总长度不超过 63 字符。 | 无严格格式限制,键名可以自由定义(但建议遵循类似 Labels 的命名规范)。 |
| 典型使用场景 | - 标识应用组件(如 app=frontend, tier=backend)。- 环境区分(如 env=dev, env=prod)。- 版本管理(如 version=v1, version=v2)。- 资源选择(如 Service 选择 Pod)。 | - 记录构建/部署信息(如 Git Commit ID、构建时间)。 - 存储监控配置(如 Prometheus 的抓取规则)。 - 定义滚动更新策略的附加参数。 - 存储第三方工具需要的元数据(如 Istio 的流量管理配置)。 |
StorageClass,PV和PVC
在Kubernetes中,StorageClass,PersistentVolume(PV)和PersistentVolumeClaim(PVC)是用于管理存储资源的三个核心概念。他们之间的关系如下:
概念说明
StorageClass
- 作用:
StorageClass定义了存储类别(即存储的类型和配置),用于动态创建PersistentVolume(PV)。 - 特点:
- 每个
StorageClass都有一个名称,PVC可以通过指定StorageClass来请求特定类型的存储。 StorageClass可以关联到后端的存储系统(如AWS EBS、GCP Persistent Disk、NFS、Ceph等)- 支持动态卷供应(Dynamic Provisioning),即根据PVC的请求自动创建PV。
- 每个
PersistentVolume(PV)
- 作用:
PV是集群中的的一块存储资源,由管理员预先创建或由StorageClass动态创建。 - 特点:
PV是集群级别的资源,独立于Pod和Namespace。PV可以手动创建(静态供应),也可以由StorageClass动态创建。PV定义了存储的大小、访问模式(如ReadWriteOnce、ReadOnlyMany、ReadWriteMany)和后端存储的具体配置。
PersistentVolumeClaim(PVC)
- 作用:
PVC是用户对存储资源的请求,类似于Pod对CPU的请求。 - 特点:
PVC是namespace级别的资源,只能被同一namespace中的Pod使用。PVC通过指定存储大小、访问模式和StorageClass来请求存储资源。PVC会绑定到一个满足其要求的PV。
三者之间的关系
- 静态供应(Static Provisioning)
- 管理员手动创建
PV。 - 用户创建
PVC,PVC会根据其配置(如存储大小、访问模式)绑定到合适的PV。 - 如果没有合适的
PV,PVC会一直处于Pending状态。
- 管理员手动创建
- 动态供应(Dynamic Provisioning)
- 管理员创建
StorageClass,定义存储的类型和配置。 - 用户创建
PVC,并指定StorageClass。 Kubernetes根据PVC的请求,自动创建PV并绑定到PVC。
- 管理员创建
工作流程
静态供应流程
- 管理员创建 PV。
- 用户创建 PVC。
- PVC 绑定到 PV。
- Pod 使用 PVC 挂载存储。
动态供应流程
- 管理员创建
StorageClass。 - 用户创建 PVC,并指定
StorageClass。 - Kubernetes 根据
StorageClass自动创建 PV。 - PVC 绑定到 PV。
- Pod 使用 PVC 挂载存储。
示例
静态供应示例
- 创建 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/my-pv- 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi- 绑定结果
- PVC 会自动绑定到 PV,因为它们的大小和访问模式匹配。
动态供应示例
- 创建 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2- 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast- 动态创建 PV
- Kubernetes 会根据
StorageClass自动创建一个 PV,并绑定到 PVC。
- Kubernetes 会根据
CoreAPI,BatchAPI
在 Kubernetes 中,CoreAPI 和 BatchAPI 是两个不同的 API 组,分别用于管理不同类别的资源。以下是它们的区别和用途:
CoreAPI(核心 API 组)
CoreAPI 是 Kubernetes 的核心 API 组,包含 Kubernetes 中最基本和常用的资源。这些资源通常位于 API 路径 /api/v1 下。
主要资源:
- Pods:管理容器组。
- Services:管理服务暴露。
- ConfigMaps:管理配置数据。
- Secrets:管理敏感信息。
- Namespaces:管理命名空间。
- PersistentVolumes (PV) 和 PersistentVolumeClaims (PVC):管理持久化存储。
- ReplicationControllers:管理副本控制器。
- Events:查看集群事件。
BatchAPI(批处理 API 组)
BatchAPI 是 Kubernetes 中用于管理批处理任务的 API 组,包含与任务调度和批处理相关的资源。这些资源通常位于 API 路径 /apis/batch/v1 或 /apis/batch/v1beta1 下。
主要资源:
- Jobs:管理一次性任务。
- CronJobs:管理定时任务。
Configmap,Secret
CPU资源单位
k8s中CPU的单位通常是毫核(millicores),1个CPU核心 = 1000毫核。
Job资源回收
在Kubernetes中,完成的Job及其相关的Pod的清理行为主要取决于以下几个因素:
- 从Kubernetes1.12开始,可以通过设置
.spec.ttlSecondsAfterFinihsed字段来指定Job完成之后的保留时间,举个例子apiVersion: batch/v1 kind: Job metadata: name: example-job spec: ttlSecondsAfterFinished: 100 template: spec: containers: - name: example image: busybox command: ["echo", "Hello World"] restartPolicy: Never - 默认保留行为:如果没有设置
ttlSecondsAfterFinished,默认情况下Kubernetes不会自动删除已经完成的Job,会一直保存在系统中,直到手动删除。 - 垃圾收集器:Kubernetes的垃圾收集器会清理孤儿资源,但不会自动清理已完成的Job。
sidecar的概念
“Sidecar” 在 Kubernetes 及微服务架构中,指的是一种设计模式,用于将辅助功能从主应用程序容器中分离出来,形成一个独立的容器(即 Sidecar 容器),与主容器并肩运行在同一个 Pod 中。 就像摩托车旁边的边车一样,Sidecar 容器“伴随”着主容器,提供支持和服务。
具体表现比如:原来一个deployment只有一个replica的情况下,如果sidecar部署之后,容器组的容器数目就会变成2,例如,使用telepresence的情况下:




