Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化 *。

参考资料:K8S 中文文档K8S 指南

本文仍在施工中。。。。。

# Kubernetes 简介

Kubernetes 是谷歌开源的容器集群管理系统,是 Google 多年大规模容器管理技术 Borg 的开源版本,主要功能包括:

  • 基于容器的应用部署、维护和滚动升级
  • 负载均衡和服务发现
  • 跨机器和跨地区的集群调度
  • 自动伸缩
  • 无状态服务和有状态服务
  • 广泛的 Volume 支持
  • 插件机制保证扩展性

Kubernetes 发展非常迅速,已经成为容器编排领域的领导者。

# Kubernetes 是一个平台

​ Kubernetes 提供了很多的功能,它可以简化应用程序的工作流,加快开发速度。通常,一个成功的应用编排系统需要有较强的自动化能力,这也是为什么 Kubernetes 被设计作为构建组件和工具的生态系统平台,以便更轻松地部署、扩展和管理应用程序。

用户可以使用 Label 以自己的方式组织管理资源,还可以使用 Annotation 来自定义资源的描述信息,比如为管理工具提供状态检查等。

此外,Kubernetes 控制器也是构建在跟开发人员和用户使用的相同的 API 之上。用户还可以编写自己的控制器和调度器,也可以通过各种插件机制扩展系统的功能。

这种设计使得可以方便地在 Kubernetes 之上构建各种应用系统。

# Kubernetes 认知误区

Kubernetes 不是一个传统意义上,包罗万象的 PaaS (平台即服务) 系统。它给用户预留了选择的自由。

  • 不限制支持的应用程序类型,它不插手应用程序框架,也不限制支持的语言 (如 Java, Python, Ruby 等),只要应用符合 12 因素 即可。Kubernetes 旨在支持极其多样化的工作负载,包括无状态、有状态和数据处理工作负载。只要应用可以在容器中运行,那么它就可以很好的在 Kubernetes 上运行。
  • 不提供内置的中间件 (如消息中间件)、数据处理框架 (如 Spark)、数据库 (如 mysql) 或集群存储系统 (如 Ceph) 等。这些应用直接运行在 Kubernetes 之上。
  • 不提供点击即部署的服务市场。
  • 不直接部署代码,也不会构建您的应用程序,但您可以在 Kubernetes 之上构建需要的持续集成 (CI) 工作流。
  • 允许用户选择自己的日志、监控和告警系统。
  • 不提供应用程序配置语言或系统 (如 jsonnet)。
  • 不提供机器配置、维护、管理或自愈系统。

另外,已经有很多 PaaS 系统运行在 Kubernetes 之上,如 Openshift, DeisEldarion 等。 您也可以构建自己的 PaaS 系统,或者只使用 Kubernetes 管理您的容器应用。

当然了,Kubernetes 不仅仅是一个 “编排系统”,它消除了编排的需要。Kubernetes 通过声明式的 API 和一系列独立、可组合的控制器保证了应用总是在期望的状态,而用户并不需要关心中间状态是如何转换的。这使得整个系统更容易使用,而且更强大、更可靠、更具弹性和可扩展性。

# Kubernetes 基本概念

# Container

Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术。它使用 namespace 隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行。

由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序。这种一对一的应用镜像关系拥有很多好处。使用容器,不需要与外部的基础架构环境绑定,因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖。完美解决了从开发到生产环境的一致性问题。

容器同样比虚拟机更加透明,这有助于监测和管理。尤其是容器进程的生命周期由基础设施管理,而不是被进程管理器隐藏在容器内部。最后,每个应用程序用容器封装,管理容器部署就等同于管理应用程序部署。

其他容器的优点还包括

  • 敏捷的应用程序创建和部署:与虚拟机镜像相比,容器镜像更易用、更高效。
  • 持续开发、集成和部署:提供可靠与频繁的容器镜像构建、部署和快速简便的回滚(镜像是不可变的)。
  • 开发与运维的关注分离:在构建 / 发布时即创建容器镜像,从而将应用与基础架构分离。
  • 开发、测试与生产环境的一致性:在笔记本电脑上运行和云中一样。
  • 可观测:不仅显示操作系统的信息和度量,还显示应用自身的信息和度量。
  • 云和操作系统的分发可移植性:可运行在 Ubuntu, RHEL, CoreOS, 物理机,GKE 以及其他任何地方。
  • 以应用为中心的管理:从传统的硬件上部署操作系统提升到操作系统中部署应用程序。
  • 松耦合、分布式、弹性伸缩、微服务:应用程序被分成更小,更独立的模块,并可以动态管理和部署 - 而不是运行在专用设备上的大型单体程序。
  • 资源隔离:可预测的应用程序性能。
  • 资源利用:高效率和高密度。

# Pod

Kubernetes 使用 Pod 来管理容器,每个 Pod 可以包含一个或多个紧密关联的容器。

Pod 是一组紧密关联的容器集合,它们共享 IPC 和 Network namespace,是 Kubernetes 调度的基本单位。Pod 内的多个容器共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \pod.png)

在 Kubernetes 中,所有对象都使用 manifest(yaml 或 json)来定义,比如一个简单的 nginx 服务可以定义为 nginx.yaml,它包含一个镜像为 nginx 的容器:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80

# Node

节点(Node) 是 Pod 真正运行的主机,可以是物理机,也可以是虚拟机。为了管理 Pod,每个 Node 节点上至少要运行 container runtime(比如 docker 或者 rkt)、 kubeletkube-proxy 服务。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \node.png)

# Namespace

Namespace 是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的 pods, services, replication controllers 和 deployments 等都是属于某一个 namespace 的(默认是 default),而 node, persistentVolumes 等则不属于任何 namespace。

# Service

Service 是应用服务的抽象,通过 labels 为应用提供负载均衡和服务发现。匹配 labels 的 Pod IP 和端口列表组成 endpoints,由 kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上。

每个 Service 都会自动分配一个 cluster IP(仅在集群内部可访问的虚拟地址)和 DNS 名,其他容器可以通过该地址或 DNS 来访问服务,而不需要了解后端容器的运行。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \service.png)

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 8078 # the port that this service should serve on
    name: http
    # the container on each pod to connect to, can be a name
    # (e.g. 'www') or a number (e.g. 80)
    targetPort: 80
    protocol: TCP
  selector:
    app: nginx

# Label

Label 是识别 Kubernetes 对象的标签,以 key/value 的方式附加到对象上(key 最长不能超过 63 字节,value 可以为空,也可以是不超过 253 字节的字符串)。

Label 不提供唯一性,并且实际上经常是很多对象(如 Pods)都使用相同的 label 来标志具体的应用。

Label 定义好后其他对象可以使用 Label Selector 来选择一组相同 label 的对象(比如 ReplicaSet 和 Service 用 label 来选择一组 Pod)。Label Selector 支持以下几种方式:

  • 等式,如 app=nginxenv!=production
  • 集合,如 env in (production, qa)
  • 多个 label(它们之间是 AND 关系),如 app=nginx,env=test

# Annotations

Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象,Annotations 则是用来记录一些附加信息,用来辅助应用部署、安全策略以及调度策略等。比如 deployment 使用 annotations 来记录 rolling update 的状态。

# Kubernetes 架构

Kubernetes 最初源于谷歌内部的 Borg,提供了面向应用的容器集群部署和管理系统。Kubernetes 的目标旨在消除编排物理 / 虚拟计算,网络和存储基础设施的负担,并使应用程序运营商和开发人员完全将重点放在以容器为中心的原语上进行自助运营。Kubernetes 也提供稳定、兼容的基础(平台),用于构建定制化的 workflows 和更高级的自动化任务。
Kubernetes 具备完善的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建负载均衡器、故障发现和自我修复能力、服务滚动升级和在线扩容、可扩展的资源自动调度机制、多粒度的资源配额管理能力。
Kubernetes 还提供完善的管理工具,涵盖开发、部署测试、运维监控等各个环节。

# Borg 简介

Borg 是谷歌内部的大规模集群管理系统,负责对谷歌内部很多核心服务的调度和管理。Borg 的目的是让用户能够不必操心资源管理的问题,让他们专注于自己的核心业务,并且做到跨多个数据中心的资源利用率最大化。

Borg 主要由 BorgMasterBorgletborgcfgScheduler 组成

  • BorgMaster 是整个集群的大脑,负责维护整个集群的状态,并将数据持久化到 Paxos 存储中;
  • Scheduer 负责任务的调度,根据应用的特点将其调度到具体的机器上去;
  • Borglet 负责真正运行任务(在容器中);
  • borgcfgBorg 的命令行工具,用于跟 Borg 系统交互,一般通过一个配置文件来提交任务。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \borg.png)

# Kubernetes 架构

Kubernetes 借鉴了 Borg 的设计理念,比如 PodServiceLabels 和单 Pod 单 IP 等。

Kubernetes 主要由以下几个核心组件组成:

  • etcd 保存了整个集群的状态;
  • apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
  • controller manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
  • scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
  • kubelet 负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理;
  • Container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI);
  • kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡;

下图中展示了 K8S 的主从架构(控制平面 + 节点架构),用户通过 api-server 与 K8S 集群交互,控制平面(Master components)负责集群的管理和决策(如任务调度),并将任务分配给工作节点(从节点)执行。需要注意的是:

  • 工作节点(从节点)负责部署并运行 Pod(任务载体);默认情况下主节点会被打上污点( node-role.kubernetes.io/control-plane:NoSchedule ),无法调度普通工作负载,若要让主节点同时承担工作节点角色,需移除该污点(生产环境不推荐)。
  • 在 K8S 中进行的所有操作(如创建 Pod、查看集群状态)都必须通过 api-server 作为统一入口, api-server 是唯一能直接操作 etcd 的组件。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \architecture.png)

# Master 组件

Master 组件提供集群的管理控制中心。

Master 组件可以在集群中任何节点上运行。但是为了简单起见,通常在一台 VM / 机器上启动所有 Master 组件,并且不会在此 VM / 机器上运行用户容器。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \kubernetes_master.png)

# kube-apiserver

kube-apiserver 用于暴露 Kubernetes API。任何的资源请求 / 调用操作都是通过 kube-apiserver 提供的接口进行。

# ETCD

etcd 是 Kubernetes 提供默认的存储系统用于保存所有集群数据,它是一个以键值类型存储的分布式数据库,基于 Raft 算法实现自主的集群高可用,使用时需要为 etcd 数据提供备份计划。

# kube-controller-manager

运行管理控制器(kube-controller-manager)是集群中处理常规任务的后台线程。逻辑上,每个控制器是一个单独的进程,但为了降低复杂性,它们都被编译成单个二进制文件,并在单个进程中运行。

这些控制器包括:

  • 节点(Node)控制器。
  • 副本(Replication)控制器:负责维护系统中每个副本中的 pod。
  • 端点(Endpoints)控制器:填充 Endpoints 对象(即连接 Services&Pods)。
  • Service Account 和 Token 控制器:为新的 Namespace 创建默认帐户访问 API Token。

# cloud-controller-manager

云控制器管理器(cloud-controller-manager)负责与底层云提供商的平台交互。云控制器管理器是 Kubernetes 版本 1.6 中引入的,目前还是 Alpha 的功能。

云控制器管理器仅运行云提供商特定的(controller loops)控制器循环。可以通过将 --cloud-provider flag 设置为 external 启动 kube-controller-manager ,来禁用控制器循环。

cloud-controller-manager 具体功能:

  • 节点(Node)控制器
  • 路由(Route)控制器
  • Service 控制器
  • 卷(Volume)控制器

# kube-scheduler

kube-scheduler 是一个调度器,它会监视新创建没有分配到 Node 的 Pod,为 Pod 选择一个 Node。

# Addons

插件(addon)是实现集群 pod 和 Services 功能的 。Pod 由 Deployments,ReplicationController 等进行管理。Namespace 插件对象是在 kube-system Namespace 中创建。一些推荐的 Add-ons:

  • kube-dns 负责为整个集群提供 DNS 服务
  • Ingress Controller 为服务提供外网入口
  • Heapster 提供资源监控
  • Prometheus(普罗米修斯)也是一种资源监控方案,相比于 Heapster 更加全面
  • Dashboard 提供 GUI
  • Federation 提供跨可用区的集群
  • Fluentd-elasticsearch 提供集群日志采集、存储与查询

# 节点(Node)组件

节点组件运行在 Node,提供 Kubernetes 运行时环境,以及维护 Pod。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \kubernetes_node.png)

# kubelet

kubelet 是工作节点上的主要代理组件,它会监视分配给当前节点的 Pod,具体功能:

  • 挂载 Pod 所需的存储卷(Volume)。
  • 下载 Pod 所需的密钥(Secrets)。
  • 运行 Pod 中的 docker(或试验性支持的 rkt)容器。
  • 定期执行容器健康检查(存活探针、就绪探针等)。
  • 必要时创建镜像 Pod(mirror pod),并向系统其他组件报告 Pod 的状态。
  • 向系统其他组件报告节点的状态。

# kube-proxy

kube-proxy 是部署在每个工作节点上的网络代理组件,它通过在节点上维护网络规则(iptables/IPVS 规则为主)、执行 TCP/UDP 连接转发,实现 Kubernetes Service 的网络抽象,核心作用包括:

  • 为 Service 提供集群内部的负载均衡能力,将对 Service 的请求转发到后端 Pod;
  • 实现 Pod 与 Service、集群内外与 Service 的网络连通;
  • 维护网络规则的动态更新(当 Pod 扩缩容、Service 配置变更时自动同步)。

# container-runtime

container-runtime(容器运行时)是 Kubernetes 集群中负责管理容器生命周期(创建、启动、停止、销毁)的底层软件,需遵循 K8S 定义的容器运行时接口(CRI)标准,为 Pod 内的容器提供隔离的运行环境。常见的 container-runtime 包括:

  • containerd:目前 K8S 默认推荐的容器运行时(从 1.24 版本起移除对 Docker 的直接支持),轻量且稳定,专注于容器生命周期管理;
  • CRI-O:专为 K8S 设计的轻量级运行时,完全兼容 CRI 标准,无 Docker 依赖;
  • Docker:早期主流运行时,因未原生实现 CRI(需通过 dockershim 适配),现已被 K8S 逐步弃用,实际底层依赖 containerd 提供容器运行能力。

# RKT

rkt 运行容器,作为 docker 工具的替代方案。

# supervisord

supervisord 是一个轻量级的监控系统,用于保障 kubelet 和 docker 运行。

# fluentd

fluentd 是一个守护进程,可提供 cluster-level logging.。

# 分层架构

Kubernetes 的整体架构设计借鉴了分层思想(类似 Linux 分层架构),其生态与功能分层可划分为以下层级(从核心到外围):

![](C:\Users\Amadues\Desktop\K8S\ 图片 \K8s_layer.jpg)

Nucleus(内核层):Kubernetes 最核心的功能层,包含控制平面核心组件(api-server、etcd、scheduler、controller-manager)、工作节点核心组件(kubelet、kube-proxy)及容器运行时,是集群运行的基础,对外提供稳定的核心 API,对内支撑所有上层功能的执行。

Application Layer(应用层):基于内核层能力构建的应用相关功能,涵盖 Pod、Service、Deployment、StatefulSet 等核心资源对象,以及应用部署、扩缩容、滚动更新等编排能力,直接面向用户的应用管理需求。

Governance Layer(治理层):保障集群与应用安全、可靠、高效运行的能力层,包括 RBAC 权限控制、网络策略、资源配额(Resource Quota)、准入控制(Admission Control)、日志监控、服务网格(如 Istio)等治理相关功能。

Interface Layer(接口层):连接用户 / 外部系统与 Kubernetes 内核的桥梁,包含 kubectl 命令行工具、Dashboard 可视化界面、第三方 CI/CD 集成接口、自定义 Operator 的扩展接口等,提供多样化的集群访问与操作方式。

Ecosystem(生态层):Kubernetes 外围的扩展生态,包括存储插件(如 CSI)、网络插件(如 CNI)、监控工具(如 Prometheus)、日志工具(如 ELK)、云厂商适配组件等,丰富集群的功能边界,实现与周边系统的集成。

# 核心概念

# 服务分类

在 Kubernetes(k8s)中,有状态服务和无状态服务是两种不同类型的应用程序部署方式,它们在容器编排和管理方面有一些关键区别。

# 无状态服务(Stateless Services)

无状态服务是指应用程序不依赖于本地状态,并且对于每个请求都能以相同的方式处理。这意味着无状态服务的任何一个实例都可以处理来自客户端的请求,而请求之间没有关联。

无状态服务的典型示例包括 Web 服务器、API 服务、负载均衡器等,这些服务可以水平扩展,即可以通过添加更多的副本来处理更多的请求流量。

无状态服务通常适合使用 Kubernetes 中的 Deployment 进行部署。因为它们的实例可以随意启动和停止,而不会影响应用程序的状态或数据。

  • 优点:对客户端透明,无依赖关系,可高效实现扩容、迁移
  • 缺点:不能存储数据,需要额外的数据服务支撑

# 有状态服务(Stateful Services)

有状态服务是指应用程序依赖于本地状态,通常需要稳定的标识和数据。这意味着每个实例都有唯一的标识,如数据库服务器或消息队列,它们需要在启动、停止或故障转移时保留其数据和状态。

有状态服务的典型示例包括数据库系统(如 MySQL、PostgreSQL)、消息队列(如 RabbitMQ、Kafka)等,这些服务通常需要持久性存储和网络标识的稳定性。

Kubernetes 提供 StatefulSet 来部署有状态服务。StatefulSet 具有管理有状态应用程序的能力,为每个 Pod 提供唯一的标识,以及稳定的网络标识,从而支持有状态服务的稳定性。

  • 优点:可独立存储数据,实现数据管理
  • 缺点:集群环境下需要实现主从、数据同步、备份、水平扩容复杂

# 资源和对象

Kubernetes 中的所有内容都被抽象为资源(Resource),资源是对集群中各类实体的抽象定义(如 Pod、Service、Node、Deployment 等);对象(Object)是资源的具体实例,是持久化存储在 etcd 中的实体(例如名为 nginx-pod-xxx 的具体 Pod、名为 mysql-service 的具体 Service),Kubernetes 通过管理这些对象的生命周期(创建、更新、删除)来维护整个集群的状态。

对象的创建、删除、修改、查询等所有操作,都必须通过 Kubernetes API (由 API Server 统一提供)完成,这些 RESTful 格式的 API 完全契合 K8S “一切皆对象” 的设计理念;命令行工具 kubectl 本质是 Kubernetes API 的客户端封装,所有 kubectl 命令最终都会转化为对 API Server 的 HTTP 请求(如 kubectl create 对应 POST 请求、 kubectl get 对应 GET 请求)。

K8S 中的资源类型分为核心资源(如 Pod、Service)和扩展资源(如 Ingress、CustomResourceDefinition), kubectl 支持通过配置文件(声明式管理)或命令行(命令式管理)创建对象,其中配置文件是描述对象期望状态(Spec)和元数据(Metadata)的文本文件,格式支持 JSON 或 YAML(YAML 因可读性更高成为主流)。

# 对象(Object)规约和状态

每个 Kubernetes 对象都包含两个嵌套对象字段,用于管理 Object 的配置:Object Spec 和 Object Status。

  • 规约(Spec)描述了 Kubernetes 对象的期望状态(Desired State),即用户希望对象具备的特征、配置和目标状态(如 Pod 的副本数、容器镜像、资源限制等)。创建 K8S 对象时,必须在 YAML/JSON 配置中提供 Spec(部分系统默认字段可省略),同时需包含对象的元数据(Metadata,如名称、命名空间、标签等)。

  • 状态(Status)表示 Kubernetes 对象的实际状态(Actual State),由 K8S 集群自动维护和更新(用户无法直接修改)。K8S 通过各类控制器(如 Deployment 控制器、StatefulSet 控制器)持续监控对象的 Spec 与 Status 差异,执行调谐操作(如创建 / 销毁 Pod、扩容缩容),确保实际状态尽可能向期望状态收敛(Reconciliation,调和过程)。

例如,通过 Kubernetes Deployment 来表示在集群上运行的应用的对象。创建 Deployment 时,可以设置 Deployment Spec,来指定要运行应用的三个副本。Kubernetes 系统将读取 Deployment Spec,并启动你想要的三个应用实例 - 来更新状态以符合之前设置的 Spec。如果这些实例中有任何一个失败(状态更改),Kuberentes 系统将响应 Spec 和当前状态之间差异来调整,这种情况下,将会开始替代实例。

# 描述 Kubernetes 对象

在 Kubernetes 中创建对象时,必须提供描述其所需 Status 的对象 Spec,以及关于对象(如 name)的一些基本信息。当使用 Kubernetes API 创建对象(直接或通过 kubectl)时,该 API 请求必须将该信息作为 JSON 包含在请求 body 中。通常,可以将信息提供给 kubectl .yaml 文件,在进行 API 请求时,kubectl 将信息转换为 JSON。

以下示例是一个.yaml 文件,显示 Kubernetes Deployment 所需的字段和对象 Spec:

nginx-depoloyment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

使用上述.yaml 文件创建 Deployment,是通过在 kubectl 中使用 kubectl create 命令来实现。将该.yaml 文件作为参数传递。如下例子:

$ kubectl create -f docs/user-guide/nginx-deployment.yaml --record
deployment "nginx-deployment" created

# 必填字段

对于要创建的 Kubernetes 对象的 yaml 文件,需要为以下字段设置值:

  • apiVersion - 创建对象的 Kubernetes API 版本
  • kind - 要创建什么样的对象?
  • metadata- 具有唯一标示对象的数据,包括 name(字符串)、UID 和 Namespace(可选项)

还需要提供对象 Spec 字段,对象 Spec 的精确格式(对于每个 Kubernetes 对象都是不同的),以及容器内嵌套的特定于该对象的字段。Kubernetes API reference 可以查找所有可创建 Kubernetes 对象的 Spec 格式。

# 元数据

元数据(Metadata)是所有 K8S 对象的内置字段(如 name、namespace、labels、annotations),用于描述对象的基本属性。

# 资源分类

K8S 中资源按作用域(Scope) 可分为两类核心类型:

集群级资源(Cluster-scoped resources):作用域覆盖整个 Kubernetes 集群,不隶属于任何命名空间,所有命名空间均可访问(部分需权限控制)。示例:

  • Node(节点)、Namespace(命名空间本身)、ClusterRole(集群角色)、ClusterRoleBinding(集群角色绑定)、StorageClass(存储类)、CustomResourceDefinition(CRD)

命名空间级资源(Namespaced resources):隶属于特定命名空间,仅在所属命名空间内有效,不同命名空间的同名资源相互隔离。示例:

  • Pod、Service、Deployment、ConfigMap、Secret、LimitRange、HPA(HorizontalPodAutoscaler)、PodTemplate

# 资源和对象

# Autoscaling

Horizontal Pod Autoscaling (HPA) 可以根据 CPU 使用率或应用自定义 metrics 自动扩展 Pod 数量(支持 replication controller、deployment 和 replica set )。

  • 控制管理器每隔 15s(可以通过 --horizontal-pod-autoscaler-sync-period 修改)查询 metrics 的资源使用情况
  • 支持三种 metrics 类型
    • 预定义 metrics(比如 Pod 的 CPU)以利用率的方式计算
    • 自定义的 Pod metrics,以原始值(raw value)的方式计算
    • 自定义的 object metrics
  • 支持两种 metrics 查询方式:Heapster 和自定义的 REST API
  • 支持多 metrics

示例

# 创建 pod 和 service
$ kubectl run php-apache --image=k8s.gcr.io/hpa-example --requests=cpu=200m --expose --port=80
service "php-apache" created
deployment "php-apache" created
# 创建 autoscaler
$ kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
deployment "php-apache" autoscaled
$ kubectl get hpa
NAME         REFERENCE                     TARGET    MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%  1         10        1          18s
# 增加负载
$ kubectl run -i --tty load-generator --image=busybox /bin/sh
Hit enter for command prompt
$ while true; do wget -q -O- http://php-apache.default.svc.cluster.local; done
# 过一会就可以看到负载升高了
$ kubectl get hpa
NAME         REFERENCE                     TARGET      CURRENT   MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   305% / 50%  305%      1         10        1          3m
# autoscaler 将这个 deployment 扩展为 7 个 pod
$ kubectl get deployment php-apache
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
php-apache   7         7         7            7           19m
# 删除刚才创建的负载增加 pod 后会发现负载降低,并且 pod 数量也自动降回 1 个
$ kubectl get hpa
NAME         REFERENCE                     TARGET       MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%     1         10        1          11m
$ kubectl get deployment php-apache
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
php-apache   1         1         1            1           27m

# 自定义 metrics

使用方法

  • 控制管理器开启 --horizontal-pod-autoscaler-use-rest-clients
  • 控制管理器配置的 --master 或者 --kubeconfig
  • 在 API Server Aggregator 中注册自定义的 metrics API,如 https://github.com/kubernetes-incubator/custom-metrics-apiserverhttps://github.com/kubernetes/metrics

注:可以参考 k8s.io/metics 开发自定义的 metrics API server。

比如 HorizontalPodAutoscaler 保证每个 Pod 占用 50% CPU、1000pps 以及 10000 请求 /s:

HPA 示例

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1beta1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 50
  - type: Pods
    pods:
      metricName: packets-per-second
      targetAverageValue: 1k
  - type: Object
    object:
      metricName: requests-per-second
      target:
        apiVersion: extensions/v1beta1
        kind: Ingress
        name: main-route
      targetValue: 10k
status:
  observedGeneration: 1
  lastScaleTime: <some-time>
  currentReplicas: 1
  desiredReplicas: 1
  currentMetrics:
  - type: Resource
    resource:
      name: cpu
      currentAverageUtilization: 0
      currentAverageValue: 0

# 可配置容忍度

从 Kubernetes v1.33 开始,HPA 支持可配置的容忍度(tolerance),允许用户自定义扩缩容的触发阈值。这一功能通过 HPAConfigurableTolerance 特性门控启用。

在之前的版本中,Kubernetes 使用固定的 10% 容忍度来决定是否进行扩缩容操作。现在通过 tolerance 可以为扩容(scale-up)和缩容(scale-down)分别设置不同的容忍度,提供更细粒度的控制。

配置实例

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: configurable-tolerance-example
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 20
  behavior:
    scaleDown:
      tolerance: 0.05  # 5% 容忍度用于缩容
    scaleUp:
      tolerance: 0     # 0% 容忍度用于扩容(更敏感)
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

容忍度使用场景

** 高敏感度扩容 **:设置较低的扩容容忍度,快速响应负载增长

behavior:
  scaleUp:
    tolerance: 0.02  # 2% 容忍度,快速扩容

** 保守缩容 **:设置较高的缩容容忍度,避免频繁缩容

behavior:
  scaleDown:
    tolerance: 0.15  # 15% 容忍度,稳定缩容

** 不同工作负载的定制策略 **:

# 批处理工作负载 - 积极扩容,保守缩容
behavior:
  scaleUp:
    tolerance: 0
  scaleDown:
    tolerance: 0.2
# Web 服务 - 平衡策略
behavior:
  scaleUp:
    tolerance: 0.05
  scaleDown:
    tolerance: 0.1

注意事项

  • 该功能目前处于 Alpha 阶段,需要启用 HPAConfigurableTolerance 特性门控
  • 容忍度值范围为 0.0 到 1.0(表示 0% 到 100%)
  • 较低的容忍度会导致更频繁的扩缩容操作
  • 需要结合具体的应用特性和负载模式进行调优

# 状态条件

v1.7+ 可以在客户端中看到 Kubernetes 为 HorizontalPodAutoscaler 设置的状态条件 status.conditions ,用来判断 HorizontalPodAutoscaler 是否可以扩展(AbleToScale)、是否开启扩展(ScalingActive)以及是否受到限制(ScalingLimitted)。

$ kubectl describe hpa cm-test
Name:                           cm-test
Namespace:                      prom
Labels:                         <none>
Annotations:                    <none>
CreationTimestamp:              Fri, 16 Jun 2017 18:09:22 +0000
Reference:                      ReplicationController/cm-test
Metrics:                        (current /target)
  "http_requests" on pods:      66m / 500m
Min replicas:                   1
Max replicas:                   4
ReplicationController pods:     1 current / 1 desired
Conditions:
  Type                  Status  Reason                  Message
  ----                  ------  ------                  -------
  AbleToScale           True    ReadyForNewScale        the last scale time was sufficiently old as to warrant a new scale
  ScalingActive         True    ValidMetricFound        the HPA was able to successfully calculate a replica count from pods metric http_requests
  ScalingLimited        False   DesiredWithinRange      the desired replica count is within the acceptable range
Events:

# Vertical Pod Autoscaler (VPA) 与原地调整

Vertical Pod Autoscaler (VPA) 可以根据资源使用历史和当前需求自动调整 Pod 的 CPU 和内存请求。从 Kubernetes v1.33 开始,VPA 可以与原地 Pod 资源调整功能集成,实现无需重启的动态资源调整。

VPA 与原地调整集成

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "InPlace"  # 启用原地调整模式
  resourcePolicy:
    containerPolicies:
    - containerName: web-container
      mode: "Auto"
      minAllowed:
        cpu: "100m"
        memory: "128Mi"
      maxAllowed:
        cpu: "2000m"
        memory: "4Gi"
      controlledResources: ["cpu", "memory"]

混合调整策略

结合 HPA 和 VPA,实现完整的自动伸缩:

# HPA 配置 - 水平扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
---
# VPA 配置 - 垂直扩缩容
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "InPlace"
  resourcePolicy:
    containerPolicies:
    - containerName: web-container
      mode: "Auto"
      # VPA 调整范围要与 HPA 的目标利用率协调
      minAllowed:
        cpu: "100m"
        memory: "128Mi"
      maxAllowed:
        cpu: "1000m"    # 限制单个 Pod 最大资源
        memory: "2Gi"

VPA 调整策略优化

配置智能的 VPA 调整策略:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: optimized-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "InPlace"
    # 控制更新频率
    minReplicas: 1
    evictionPolicy:
      changeRequirement: 0.1  # 10% 变化才触发调整
  resourcePolicy:
    containerPolicies:
    - containerName: app
      mode: "Auto"
      # 设置不同资源的调整策略
      controlledValues: RequestsAndLimits
      minDiff:
        cpu: "50m"      # 最小 CPU 调整幅度
        memory: "64Mi"  # 最小内存调整幅度
      maxAllowed:
        cpu: "4000m"
        memory: "8Gi"
      # 配置调整时机
      recommendationMarginFraction: 0.15  # 15% 安全余量

VPA 推荐模式

Off 模式 - 仅推荐,仅生成推荐值,不自动应用:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: recommendation-only-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "Off"  # 仅推荐,不自动调整
  resourcePolicy:
    containerPolicies:
    - containerName: app
      mode: "Auto"

查看推荐值:

# 查看 VPA 推荐
kubectl describe vpa recommendation-only-vpa
# 输出示例
Recommendation:
  Container Recommendations:
    Container Name:  app
    Lower Bound:
      Cpu:     100m
      Memory:  262144k
    Target:
      Cpu:     250m
      Memory:  524288k
    Uncapped Target:
      Cpu:     250m
      Memory:  524288k
    Upper Bound:
      Cpu:     1
      Memory:  1Gi

Initial 模式 - 初始调整

仅在 Pod 创建时设置资源:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: initial-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "Initial"  # 仅在创建时调整
  resourcePolicy:
    containerPolicies:
    - containerName: app
      mode: "Auto"

# VPA 与应用类型的最佳实践

Web 应用

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-service-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-service
  updatePolicy:
    updateMode: "InPlace"
  resourcePolicy:
    containerPolicies:
    - containerName: web
      mode: "Auto"
      # Web 应用通常 CPU 变化较大,内存相对稳定
      controlledValues: RequestsAndLimits
      minAllowed:
        cpu: "100m"
        memory: "128Mi"
      maxAllowed:
        cpu: "2000m"
        memory: "1Gi"
      # 对内存调整更保守
      recommendationMarginFraction: 0.2

数据处理应用

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: data-processor-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: data-processor
  updatePolicy:
    updateMode: "InPlace"
  resourcePolicy:
    containerPolicies:
    - containerName: processor
      mode: "Auto"
      # 数据处理应用内存需求变化大
      controlledValues: RequestsAndLimits
      minAllowed:
        cpu: "500m"
        memory: "1Gi"
      maxAllowed:
        cpu: "4000m"
        memory: "16Gi"
      # 更积极的内存调整
      recommendationMarginFraction: 0.1

# VPA 监控和故障排查

监控 VPA 行为

# 查看 VPA 状态
kubectl get vpa
# 查看详细推荐信息
kubectl describe vpa my-app-vpa
# 查看 VPA 事件
kubectl get events --field-selector involvedObject.kind=VerticalPodAutoscaler
# 监控资源调整事件
kubectl get events --field-selector reason=VPAEvict

VPA 故障排查

常见问题和解决方案:

**VPA 不生成推荐 **

# 检查 metrics-server 状态
kubectl get pods -n kube-system | grep metrics-server
# 检查 VPA 组件
kubectl get pods -n kube-system | grep vpa

** 调整频率过高 **

# 增加变化阈值
evictionPolicy:
  changeRequirement: 0.2  # 提高到 20%

** 资源不足导致调整失败 **

# 检查节点资源
kubectl describe nodes | grep -A 5 "Allocated resources"

# HPA 最佳实践

  • 为容器配置 CPU Requests
  • HPA 目标设置恰当,如设置 70% 给容器和应用预留 30% 的余量
  • 保持 Pods 和 Nodes 健康(避免 Pod 频繁重建)
  • 保证用户请求的负载均衡
  • 使用 kubectl top nodekubectl top pod 查看资源使用情况
  • ** 容忍度配置建议(v1.33+)**:
    • Web 应用:扩容容忍度 3-5%,缩容容忍度 10-15%
    • 批处理任务:扩容容忍度 0-2%,缩容容忍度 15-20%
    • 关键服务:使用较低的扩容容忍度确保快速响应
    • 成本敏感场景:使用较高的缩容容忍度减少不必要的资源消耗
  • **VPA 与 HPA 协调使用 **:
    • 避免在同一资源上同时使用 HPA 和 VPA
    • VPA 适用于单个 Pod 资源优化,HPA 适用于负载分散
    • 考虑使用 VPA 的 Initial 模式为新 Pod 设置合适的初始资源
    • 监控原地调整的成功率和对应用的影响

# DaemonSet

DaemonSet 保证在每个 Node 上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用。典型的应用包括:

  • 日志收集,比如 fluentd,logstash 等
  • 系统监控,比如 Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond 等
  • 系统程序,比如 kube-proxy, kube-dns, glusterd, ceph 等

使用 Fluentd 收集日志的例子:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: gcr.io/google-containers/fluentd-elasticsearch:1.20
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

# 滚动更新

v1.6 + 支持 DaemonSet 的滚动更新,可以通过 .spec.updateStrategy.type 设置更新策略。目前支持两种策略

  • OnDelete:默认策略,更新模板后,只有手动删除了旧的 Pod 后才会创建新的 Pod
  • RollingUpdate:更新 DaemonSet 模版后,自动删除旧的 Pod 并创建新的 Pod

在使用 RollingUpdate 策略时,还可以设置

  • .spec.updateStrategy.rollingUpdate.maxUnavailable , 默认 1
  • spec.minReadySeconds ,默认 0

# 回滚

v1.7 + 还支持回滚

# 查询历史版本
$ kubectl rollout history daemonset <daemonset-name>
# 查询某个历史版本的详细信息
$ kubectl rollout history daemonset <daemonset-name> --revision=1
# 回滚
$ kubectl rollout undo daemonset <daemonset-name> --to-revision=<revision>
# 查询回滚状态
$ kubectl rollout status ds/<daemonset-name>

# 指定 Node 节点

DaemonSet 会忽略 Node 的 unschedulable 状态,有两种方式来指定 Pod 只运行在指定的 Node 节点上:

  • nodeSelector:只调度到匹配指定 label 的 Node 上
  • nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作
  • podAffinity:调度到满足条件的 Pod 所在的 Node 上

nodeSelector 示例

首先给 Node 打上标签

kubectl label nodes node-01 disktype=ssd

然后在 daemonset 中指定 nodeSelector 为 disktype=ssd

spec:
  nodeSelector:
    disktype: ssd

nodeAffinity 示例

nodeAffinity 目前支持两种:requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution,分别代表必须满足条件和优选条件。比如下面的例子代表调度到包含标签 kubernetes.io/e2e-az-name 并且值为 e2e-az1 或 e2e-az2 的 Node 上,并且优选还带有标签 another-node-label-key=another-node-label-value 的 Node。

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: gcr.io/google_containers/pause:2.0

podAffinity 示例

podAffinity 基于 Pod 的标签来选择 Node,仅调度到满足条件 Pod 所在的 Node 上,支持 podAffinity 和 podAntiAffinity。这个功能比较绕,以下面的例子为例:

  • 如果一个 “Node 所在 Zone 中包含至少一个带有 security=S1 标签且运行中的 Pod”,那么可以调度到该 Node
  • 不调度到 “包含至少一个带有 security=S2 标签且运行中 Pod” 的 Node 上
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: gcr.io/google_containers/pause:2.0

# 静态 Pod

除了 DaemonSet,还可以使用静态 Pod 来在每台机器上运行指定的 Pod,这需要 kubelet 在启动的时候指定 manifest 目录:

kubelet --pod-manifest-path=/etc/kubernetes/manifests

后将所需要的 Pod 定义文件放到指定的 manifest 目录中。

注意:静态 Pod 不能通过 API Server 来删除,但可以通过删除 manifest 文件来自动删除对应的 Pod。

# Deployment

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的 ReplicationController 来方便的管理应用。

Deployment 适用于无状态服务

比如一个简单的 nginx 应用可以定义为

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

扩容:

kubectl scale deployment nginx-deployment --replicas 10

如果集群支持 horizontal pod autoscaling 的话,还可以为 Deployment 设置自动扩展:

kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

对于 Kubernetes v1.33+,还可以使用 YAML 配置文件创建带有可配置容忍度的 HPA:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-deployment-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deployment
  minReplicas: 10
  maxReplicas: 15
  behavior:
    scaleUp:
      tolerance: 0.03  # 3% 容忍度,快速响应负载增长
    scaleDown:
      tolerance: 0.1   # 10% 容忍度,避免频繁缩容
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 80

更新镜像也比较简单:

kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

回滚:

kubectl rollout undo deployment/nginx-deployment

Deployment 的典型应用场景包括:

  • 定义 Deployment 来创建 Pod 和 ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续 Deployment

# Deployment 概念解析

Deployment 为 Pod 和 Replica Set(下一代 Replication Controller)提供声明式更新。

你只需要在 Deployment 中描述你想要的目标状态是什么,Deployment controller 就会帮你将 Pod 和 Replica Set 的实际状态改变到你的目标状态。你可以定义一个全新的 Deployment,也可以创建一个新的替换旧的 Deployment。

一个典型的用例如下:

  • 使用 Deployment 来创建 ReplicaSet。ReplicaSet 在后台创建 pod。检查启动状态,看它是成功还是失败。
  • 通过更新 Deployment 的 PodTemplateSpec 字段来声明 Pod 的新状态。这会创建一个新的 ReplicaSet,Deployment 会按照控制的速率将 pod 从旧的 ReplicaSet 移动到新的 ReplicaSet 中。
  • 如果当前状态不稳定,回滚到之前的 Deployment revision。每次回滚都会更新 Deployment 的 revision。
  • 扩容 Deployment 以满足更高的负载。
  • 暂停 Deployment 来应用 PodTemplateSpec 的多个修复,然后恢复上线。
  • 根据 Deployment 的状态判断上线是否 hang 住了。
  • 清除旧的不必要的 ReplicaSet。

# 创建 Deployment

下面是一个 Deployment 示例,它创建了一个 Replica Set 来启动 3 个 nginx pod。

下载示例文件并执行命令:

$ kubectl create -f docs/user-guide/nginx-deployment.yaml --record
deployment "nginx-deployment" created

将 kubectl 的 —record 的 flag 设置为 true 可以在 annotation 中记录当前命令创建或者升级了该资源。这在未来会很有用,例如,查看在每个 Deployment revision 中执行了哪些命令。

然后立即执行 get 将获得如下结果:

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         0         0            0           1s

输出结果表明我们希望的 repalica 数是 3(根据 deployment 中的 .spec.replicas 配置)当前 replica 数( .status.replicas )是 0, 最新的 replica 数( .status.updatedReplicas )是 0,可用的 replica 数( .status.availableReplicas )是 0。

过几秒后再执行 get 命令,将获得如下输出:

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           18s

我们可以看到 Deployment 已经创建了 3 个 replica,所有的 replica 都已经是最新的了(包含最新的 pod template),可用的(根据 Deployment 中的 .spec.minReadySeconds 声明,处于已就绪状态的 pod 的最少个数)。执行 kubectl get rskubectl get pods 会显示 Replica Set(RS)和 Pod 已创建。

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-2035384211   3         3         0       18s

你可能会注意到 Replica Set 的名字总是 <Deployment 的名字 >-<pod template 的 hash 值 >

$ kubectl get pods --show-labels
NAME                                READY     STATUS    RESTARTS   AGE       LABELS
nginx-deployment-2035384211-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=2035384211
nginx-deployment-2035384211-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=2035384211
nginx-deployment-2035384211-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=2035384211

刚创建的 Replica Set 将保证总是有 3 个 nginx 的 pod 存在。

** 注意:** 你必须在 Deployment 中的 selector 指定正确 pod template label(在该示例中是 app = nginx ),不要跟其他的 controller 搞混了(包括 Deployment、Replica Set、Replication Controller 等)。Kubernetes 本身不会阻止你这么做,如果你真的这么做了,这些 controller 之间会相互打架,并可能导致不正确的行为。

# 更新 Deployment

** 注意:** Deployment 的 rollout 当且仅当 Deployment 的 pod template(例如 .spec.template )中的 label 更新或者镜像更改时被触发。其他更新,例如扩容 Deployment 不会触发 rollout。

假如我们现在想要让 nginx pod 使用 nginx:1.9.1 的镜像来代替原来的 nginx:1.7.9 的镜像。

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
deployment "nginx-deployment" image updated

我们可以使用 edit 命令来编辑 Deployment,修改 .spec.template.spec.containers [0].image ,将 nginx:1.7.9 改写成 nginx:1.9.1

$ kubectl edit deployment/nginx-deployment
deployment "nginx-deployment" edited

查看 rollout 的状态,只要执行:

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

Rollout 成功后, get Deployment

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           36s

UP-TO-DATE 的 replica 的数目已经达到了配置中要求的数目。

CURRENT 的 replica 数表示 Deployment 管理的 replica 数量,AVAILABLE 的 replica 数是当前可用的 replica 数量。

我们通过执行 kubectl get rs 可以看到 Deployment 更新了 Pod,通过创建一个新的 Replica Set 并扩容了 3 个 replica,同时将原来的 Replica Set 缩容到了 0 个 replica。

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         0       6s
nginx-deployment-2035384211   0         0         0       36s

下次更新这些 pod 的时候,只需要更新 Deployment 中的 pod 的 template 即可。

Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的 Pod 数量少一个的 Pod 是 up 状态(最多一个不可用)。

Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的 Pod 数量多一个的 Pod 是 up 的(最多 1 个 surge)。

例如,如果你自己看下上面的 Deployment,你会发现,开始创建一个新的 Pod,然后删除一些旧的 Pod 再创建一个新的。当新的 Pod 创建出来之前不会杀掉旧的 Pod。这样能够确保可用的 Pod 数量至少有 2 个,Pod 的总数最多 4 个。

$ kubectl describe deployments
Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 12:01:06 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       3 updated | 3 total | 3 available | 0 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:     <none>
NewReplicaSet:      nginx-deployment-1564180365 (3/3 replicas created)
Events:
  FirstSeen LastSeen    Count   From                     SubobjectPath   Type        Reason              Message
  --------- --------    -----   ----                     -------------   --------    ------              -------
  36s       36s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  23s       23s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  23s       23s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  23s       23s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  21s       21s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  21s       21s         1       {deployment-controller}                 Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3

我们可以看到当我们刚开始创建这个 Deployment 的时候,创建了一个 Replica Set(nginx-deployment-2035384211),并直接扩容到了 3 个 replica。

当我们更新这个 Deployment 的时候,它会创建一个新的 Replica Set(nginx-deployment-1564180365),将它扩容到 1 个 replica,然后缩容原先的 Replica Set 到 2 个 replica,此时满足至少 2 个 Pod 是可用状态,同一时刻最多有 4 个 Pod 处于创建的状态。

接着继续使用相同的 rolling update 策略扩容新的 Replica Set 和缩容旧的 Replica Set。最终,将会在新的 Replica Set 中有 3 个可用的 replica,旧的 Replica Set 的 replica 数目变成 0。

Rollover(多个 rollout 并行)

每当 Deployment controller 观测到有新的 deployment 被创建时,如果没有已存在的 Replica Set 来创建期望个数的 Pod 的话,就会创建出一个新的 Replica Set 来做这件事。已存在的 Replica Set 控制 label 匹配 .spec.selector 但是 template 跟 .spec.template 不匹配的 Pod 缩容。最终,新的 Replica Set 将会扩容出 .spec.replicas 指定数目的 Pod,旧的 Replica Set 会缩容到 0。

如果你更新了一个的已存在并正在进行中的 Deployment,每次更新 Deployment 都会创建一个新的 Replica Set 并扩容它,同时回滚之前扩容的 Replica Set—— 将它添加到旧的 Replica Set 列表,开始缩容。

例如,假如你创建了一个有 5 个 niginx:1.7.9 replica 的 Deployment,但是当还只有 3 个 nginx:1.7.9 的 replica 创建出来的时候你就开始更新含有 5 个 nginx:1.9.1 replica 的 Deployment。在这种情况下,Deployment 会立即杀掉已创建的 3 个 nginx:1.7.9 的 Pod,并开始创建 nginx:1.9.1 的 Pod。它不会等到所有的 5 个 nginx:1.7.9 的 Pod 都创建完成后才开始执行滚动更新。

# 回退 Deployment

有时候你可能想回退一个 Deployment,例如,当 Deployment 不稳定时,比如一直 crash looping。

默认情况下,kubernetes 会在系统中保存所有的 Deployment 的 rollout 历史记录,以便你可以随时回退(你可以修改 revision history limit 来更改保存的 revision 数)。

** 注意:** 只要 Deployment 的 rollout 被触发就会创建一个 revision。也就是说当且仅当 Deployment 的 Pod template(如 .spec.template )被更改,例如更新 template 中的 label 和容器镜像时,就会创建出一个新的 revision。

其他的更新,比如扩容 Deployment 不会创建 revision—— 因此我们可以很方便的手动或者自动扩容。这意味着当你回退到历史 revision 时,只有 Deployment 中的 Pod template 部分才会回退。

假设我们在更新 Deployment 的时候犯了一个拼写错误,将镜像的名字写成了 nginx:1.91 ,而正确的名字应该是 nginx:1.9.1

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91
deployment "nginx-deployment" image updated

Rollout 将会卡住。

$ kubectl rollout status deployments nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...

按住 Ctrl-C 停止上面的 rollout 状态监控。

你会看到旧的 replicas(nginx-deployment-1564180365 和 nginx-deployment-2035384211)和新的 replicas (nginx-deployment-3066724191)数目都是 2 个。

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   2         2         0       25s
nginx-deployment-2035384211   0         0         0       36s
nginx-deployment-3066724191   2         2         2       6s

看下创建 Pod,你会看到有两个新的 Replica Set 创建的 Pod 处于 ImagePullBackOff 状态,循环拉取镜像。

$ kubectl get pods
NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-1564180365-70iae   1/1       Running            0          25s
nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
nginx-deployment-3066724191-eocby   0/1       ImagePullBackOff   0          6s

注意,Deployment controller 会自动停止坏的 rollout,并停止扩容新的 Replica Set。

$ kubectl describe deployment
Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       2 updated | 3 total | 2 available | 2 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:     nginx-deployment-1564180365 (2/2 replicas created)
NewReplicaSet:      nginx-deployment-3066724191 (2/2 replicas created)
Events:
  FirstSeen LastSeen    Count   From                    SubobjectPath   Type        Reason              Message
  --------- --------    -----   ----                    -------------   --------    ------              -------
  1m        1m          1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  22s       22s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  22s       22s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  22s       22s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  21s       21s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  21s       21s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
  13s       13s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1
  13s       13s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-1564180365 to 2
  13s       13s         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 2

为了修复这个问题,我们需要回退到稳定的 Deployment revision。

检查 Deployment 升级的历史记录

首先,检查下 Deployment 的 revision:

$ kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment":
REVISION    CHANGE-CAUSE
1           kubectl create -f docs/user-guide/nginx-deployment.yaml --record
2           kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
3           kubectl set image deployment/nginx-deployment nginx=nginx:1.91

因为我们创建 Deployment 的时候使用了 —recored 参数可以记录命令,我们可以很方便的查看每次 revison 的变化。

查看单个 revision 的详细信息:

$ kubectl rollout history deployment/nginx-deployment --revision=2
deployments "nginx-deployment" revision 2
  Labels:       app=nginx
          pod-template-hash=1159050644
  Annotations:  kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
  Containers:
   nginx:
    Image:      nginx:1.9.1
    Port:       80/TCP
     QoS Tier:
        cpu:      BestEffort
        memory:   BestEffort
    Environment Variables:      <none>
  No volumes.

回退到历史版本

现在,我们可以决定回退当前的 rollout 到之前的版本:

$ kubectl rollout undo deployment/nginx-deployment
deployment "nginx-deployment" rolled back

也可以使用 --to-revision 参数指定某个历史版本:

$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment "nginx-deployment" rolled back

与 rollout 相关的命令详细文档见 kubectl rollout

该 Deployment 现在已经回退到了先前的稳定版本。如你所见,Deployment controller 产生了一个回退到 revison 2 的 DeploymentRollback 的 event。

$ kubectl get deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           30m
$ kubectl describe deployment
Name:           nginx-deployment
Namespace:      default
CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
Labels:         app=nginx
Selector:       app=nginx
Replicas:       3 updated | 3 total | 3 available | 0 unavailable
StrategyType:       RollingUpdate
MinReadySeconds:    0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:     <none>
NewReplicaSet:      nginx-deployment-1564180365 (3/3 replicas created)
Events:
  FirstSeen LastSeen    Count   From                    SubobjectPath   Type        Reason              Message
  --------- --------    -----   ----                    -------------   --------    ------              -------
  30m       30m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 2
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1
  29m       29m         1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-1564180365 to 2
  2m        2m          1       {deployment-controller}                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-3066724191 to 0
  2m        2m          1       {deployment-controller}                Normal      DeploymentRollback  Rolled back deployment "nginx-deployment" to revision 2
  29m       2m          2       {deployment-controller}                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3

# 扩容 Deployment

你可以使用以下命令扩容 Deployment:

$ kubectl scale deployment nginx-deployment --replicas 10
deployment "nginx-deployment" scaled

假设你的集群中启用了 horizontal pod autoscaling (HPA),你可以给 Deployment 设置一个 autoscaler,基于当前 Pod 的 CPU 利用率选择最少和最多的 Pod 数。

$ kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80
deployment "nginx-deployment" autoscaled

比例扩容

RollingUpdate Deployment 支持同时运行一个应用的多个版本。当你或者 autoscaler 扩容一个正在 rollout 中(进行中或者已经暂停)的 RollingUpdate Deployment 的时候,为了降低风险,Deployment controller 将会平衡已存在的 active 的 ReplicaSets(有 Pod 的 ReplicaSets)和新加入的 replicas。这被称为比例扩容。

例如,你正在运行中含有 10 个 replica 的 Deployment。maxSurge=3,maxUnavailable=2。

$ kubectl get deploy
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     10        10        10           10          50s

你更新了一个镜像,而在集群内部无法解析。

$ kubectl set image deploy/nginx-deployment nginx=nginx:sometag
deployment "nginx-deployment" image updated

镜像更新启动了一个包含 ReplicaSet nginx-deployment-1989198191 的新的 rollout,但是它被阻塞了,因为我们上面提到的 maxUnavailable。

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   5         5         0         9s
nginx-deployment-618515232    8         8         8         1m

然后发起了一个新的 Deployment 扩容请求。autoscaler 将 Deployment 的 replica 数目增加到了 15 个。Deployment controller 需要判断在哪里增加这 5 个新的 replica。如果我们没有使用比例扩容,所有的 5 个 replica 都会加到一个新的 ReplicaSet 中。如果使用比例扩容,新添加的 replica 将传播到所有的 ReplicaSet 中。大的部分加入 replica 数最多的 ReplicaSet 中,小的部分加入到 replica 数少的 ReplciaSet 中。0 个 replica 的 ReplicaSet 不会被扩容。

在我们上面的例子中,3 个 replica 将添加到旧的 ReplicaSet 中,2 个 replica 将添加到新的 ReplicaSet 中。rollout 进程最终会将所有的 replica 移动到新的 ReplicaSet 中,假设新的 replica 成为健康状态。

$ kubectl get deploy
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

# 暂停和恢复 Deployment

你可以在触发一次或多次更新前暂停一个 Deployment,然后再恢复它。这样你就能多次暂停和恢复 Deployment,在此期间进行一些修复工作,而不会触发不必要的 rollout。

例如使用刚刚创建 Deployment:

$ kubectl get deploy
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx     3         3         3            3           1m
[mkargaki@dhcp129-211 kubernetes]$ kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         1m

使用以下命令暂停 Deployment:

$ kubectl rollout pause deployment/nginx-deployment
deployment "nginx-deployment" paused

然后更新 Deployment 中的镜像:

$ kubectl set image deploy/nginx nginx=nginx:1.9.1
deployment "nginx-deployment" image updated

注意没有启动新的 rollout:

$ kubectl rollout history deploy/nginx
deployments "nginx"
REVISION  CHANGE-CAUSE
1   <none>
$ kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         2m

你可以进行任意多次更新,例如更新使用的资源:

$ kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi
deployment "nginx" resource requirements updated

Deployment 暂停前的初始状态将继续它的功能,而不会对 Deployment 的更新产生任何影响,只要 Deployment 是暂停的。

最后,恢复这个 Deployment,观察完成更新的 ReplicaSet 已经创建出来了:

$ kubectl rollout resume deploy nginx
deployment "nginx" resumed
$ KUBECTL get rs -w
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   2         2         2         2m
nginx-3926361531   2         2         0         6s
nginx-3926361531   2         2         1         18s
nginx-2142116321   1         2         2         2m
nginx-2142116321   1         2         2         2m
nginx-3926361531   3         2         1         18s
nginx-3926361531   3         2         1         18s
nginx-2142116321   1         1         1         2m
nginx-3926361531   3         3         1         18s
nginx-3926361531   3         3         2         19s
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         1         1         2m
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         20s
^C
$ KUBECTL get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   0         0         0         2m
nginx-3926361531   3         3         3         28s

** 注意:** 在恢复 Deployment 之前你无法回退一个暂停了的 Deployment。

# Deployment 状态

Deployment 在生命周期中有多种状态。在创建一个新的 ReplicaSet 的时候它可以是 progressing 状态, complete 状态,或者 fail to progress 状态。

# Progressing Deployment

Kubernetes 将执行过下列任务之一的 Deployment 标记为 progressing 状态:

  • Deployment 正在创建新的 ReplicaSet 过程中。
  • Deployment 正在扩容一个已有的 ReplicaSet。
  • Deployment 正在缩容一个已有的 ReplicaSet。
  • 有新的可用的 pod 出现。

你可以使用 kubectl rollout status 命令监控 Deployment 的进度。

# Complete Deployment

Kubernetes 将包括以下特性的 Deployment 标记为 complete 状态:

  • Deployment 最小可用。最小可用意味着 Deployment 的可用 replica 个数等于或者超过 Deployment 策略中的期望个数。
  • 所有与该 Deployment 相关的 replica 都被更新到了你指定版本,也就说更新完成。
  • 该 Deployment 中没有旧的 Pod 存在。

你可以用 kubectl rollout status 命令查看 Deployment 是否完成。如果 rollout 成功完成, kubectl rollout status 将返回一个 0 值的 Exit Code。

$ kubectl rollout status deploy/nginx
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx" successfully rolled out
$ echo $?
0

# Failed Deployment

你的 Deployment 在尝试部署新的 ReplicaSet 的时候可能卡住,永远也不会完成。这可能是因为以下几个因素引起的:

  • 无效的引用
  • 不可读的 probe failure
  • 镜像拉取错误
  • 权限不够
  • 范围限制
  • 程序运行时配置错误

探测这种情况的一种方式是,在你的 Deployment spec 中指定 spec.progressDeadlineSeconds spec.progressDeadlineSeconds 表示 Deployment controller 等待多少秒才能确定(通过 Deployment status)Deployment 进程是卡住的。

下面的 kubectl 命令设置 progressDeadlineSeconds 使 controller 在 Deployment 在进度卡住 10 分钟后报告:

$ kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
"nginx-deployment" patched

当超过截止时间后,Deployment controller 会在 Deployment 的 status.conditions 中增加一条 DeploymentCondition,它包括如下属性:

  • Type=Progressing
  • Status=False
  • Reason=ProgressDeadlineExceeded

浏览 Kubernetes API conventions 查看关于 status conditions 的更多信息。

** 注意:** kubernetes 除了报告 Reason=ProgressDeadlineExceeded 状态信息外不会对卡住的 Deployment 做任何操作。更高层次的协调器可以利用它并采取相应行动,例如,回滚 Deployment 到之前的版本。

** 注意:** 如果你暂停了一个 Deployment,在暂停的这段时间内 kubernetnes 不会检查你指定的 deadline。你可以在 Deployment 的 rollout 途中安全的暂停它,然后再恢复它,这不会触发超过 deadline 的状态。

你可能在使用 Deployment 的时候遇到一些短暂的错误,这些可能是由于你设置了太短的 timeout,也有可能是因为各种其他错误导致的短暂错误。例如,假设你使用了无效的引用。当你 Describe Deployment 的时候可能会注意到如下信息:

$ kubectl describe deployment nginx-deployment
<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

执行 kubectl get deployment nginx-deployment -o yaml ,Deployement 的状态可能看起来像这个样子:

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods"nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

最终,一旦超过 Deployment 进程的 deadline,kubernetes 会更新状态和导致 Progressing 状态的原因:

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

你可以通过缩容 Deployment 的方式解决配额不足的问题,或者增加你的 namespace 的配额。如果你满足了配额条件后,Deployment controller 就会完成你的 Deployment rollout,你将看到 Deployment 的状态更新为成功状态( Status=True 并且 Reason=NewReplicaSetAvailable )。

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

Type=AvailableStatus=True 意味着你的 Deployment 有最小可用性。 最小可用性是在 Deployment 策略中指定的参数。 Type=ProgressingStatus=True 意味着你的 Deployment 或者在部署过程中,或者已经成功部署,达到了期望的最少的可用 replica 数量(查看特定状态的 Reason—— 在我们的例子中 Reason=NewReplicaSetAvailable 意味着 Deployment 已经完成)。

你可以使用 kubectl rollout status 命令查看 Deployment 进程是否失败。当 Deployment 过程超过了 deadline, kubectl rollout status 将返回非 0 的 exit code。

$ kubectl rollout status deploy/nginx
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
$ echo $?
1

# 操作失败的 Deployment

所有对完成的 Deployment 的操作都适用于失败的 Deployment。你可以对它扩/缩容,回退到历史版本,你甚至可以多次暂停它来应用 Deployment pod template。

# 清理 Policy

你可以设置 Deployment 中的 .spec.revisionHistoryLimit 项来指定保留多少旧的 ReplicaSet。 余下的将在后台被当作垃圾收集。默认的,所有的 revision 历史都会被保留。在未来的版本中,将会更改为 2。

** 注意:** 将该值设置为 0,将导致该 Deployment 的所有历史记录都被清除,也就无法回退了。

# Ingress

在本篇文章中你将会看到一些在其他地方被交叉使用的术语,为了防止产生歧义,我们首先来澄清下。

  • 节点:Kubernetes 集群中的服务器;
  • 集群:Kubernetes 管理的一组服务器集合;
  • 边界路由器:为局域网和 Internet 路由数据包的路由器,执行防火墙保护局域网络;
  • 集群网络:遵循 Kubernetes 网络模型 实现集群内的通信的具体实现,比如 flannelOVS
  • 服务:Kubernetes 的服务 (Service) 是使用标签选择器标识的一组 pod Service。 除非另有说明,否则服务的虚拟 IP 仅可在集群内部访问。

# 什么是 Ingress?

通常情况下,service 和 pod 的 IP 仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到 service 在 Node 上暴露的 NodePort 上,然后再由 kube-proxy 通过边缘路由器 (edge router) 将其转发给相关的 Pod 或者丢弃。如下图所示

internet
       | 
  ------------
  [Services]

而 Ingress 就是为进入集群的请求提供路由规则的集合,如下图所示

![](C:\Users\Amadues\Desktop\K8S\ 图片 \Ingress.png)

Ingress 可以给 service 提供集群外部访问的 URL、负载均衡、SSL 终止、HTTP 路由等。为了配置这些 Ingress 规则,集群管理员需要部署一个 Ingress controller,它监听 Ingress 和 service 的变化,并根据规则配置负载均衡并提供访问入口。

# Ingress 格式

每个 Ingress 都需要配置 rules ,目前 Kubernetes 仅支持 http 规则。上面的示例表示请求 /testpath 时转发到服务 test 的 80 端口。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

# Ingress 类型

根据 Ingress Spec 配置的不同,Ingress 可以分为以下几种类型:

# 单服务 Ingress

单服务 Ingress 即该 Ingress 仅指定一个没有任何规则的后端服务。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  backend:
    serviceName: testsvc
    servicePort: 80

注:单个服务还可以通过设置 Service.Type=NodePort 或者 Service.Type=LoadBalancer 来对外暴露。

# 多服务的 Ingress

路由到多服务的 Ingress 即根据请求路径的不同转发到不同的后端服务上,比如

foo.bar.com -> 178.91.123.132 -> /foo    s1:80
                                 /bar    s2:80

可以通过下面的 Ingress 来定义:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80

使用 kubectl create -f 创建完 ingress 后:

$ kubectl get ing
NAME      RULE          BACKEND   ADDRESS
test      -
          foo.bar.com
          /foo          s1:80
          /bar          s2:80

# 虚拟主机 Ingress

虚拟主机 Ingress 即根据名字的不同转发到不同的后端服务上,而他们共用同一个的 IP 地址,如下所示

foo.bar.com --|                 |-> foo.bar.com s1:80
              | 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com s2:80

下面是一个基于 Host header 路由请求的 Ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: s1
          servicePort: 80
  - host: bar.foo.com
    http:
      paths:
      - backend:
          serviceName: s2
          servicePort: 80

注:没有定义规则的后端服务称为默认后端服务,可以用来方便的处理 404 页面。

# TLS Ingress

TLS Ingress 通过 Secret 获取 TLS 私钥和证书 (名为 tls.crttls.key ),来执行 TLS 终止。如果 Ingress 中的 TLS 配置部分指定了不同的主机,则它们将根据通过 SNI TLS 扩展指定的主机名(假如 Ingress controller 支持 SNI)在多个相同端口上进行复用。

定义一个包含 tls.crttls.key 的 secret:

apiVersion: v1
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
kind: Secret
metadata:
  name: testsecret
  namespace: default
type: Opaque

Ingress 中引用 secret:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: no-rules-map
spec:
  tls:
    - secretName: testsecret
  backend:
    serviceName: s1
    servicePort: 80

注意,不同 Ingress controller 支持的 TLS 功能不尽相同。

# 更新 Ingress

Ingress 正常工作需要集群中运行 Ingress Controller。Ingress Controller 与其他作为 kube-controller-manager 中的在集群创建时自动启动的 controller 成员不同,需要用户选择最适合自己集群的 Ingress Controller,或者自己实现一个。

Ingress Controller 以 Kubernetes Pod 的方式部署,以 daemon 方式运行,保持 watch Apiserver 的 /ingress 接口以更新 Ingress 资源,以满足 Ingress 的请求。比如可以使用 Nginx Ingress Controller

helm install stable/nginx-ingress --name nginx-ingress --set rbac.create=true

其他 Ingress Controller 还有:

  • traefik ingress 提供了一个 Traefik Ingress Controller 的实践案例
  • kubernetes/ingress-nginx 提供了一个详细的 Nginx Ingress Controller 示例
  • kubernetes/ingress-gce 提供了一个用于 GCE 的 Ingress Controller 示例

# Ingress Class

在 Ingress Class 之前,要给 Ingress 选择具体的 Controller,需要加上特殊的 annotation(如 kubernetes.io/ingress.class: nginx)。而有了 IngressClass,集群管理员就可以预先创建好支持的 Ingress 类型,并可以 Ingress 中直接引用。

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: example.com/ingress-controller
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb

# Ingress 的下一代演进

Gateway API 是 Kubernetes 社区推出的 Ingress 资源的下一代演进版本,提供了更强大和更灵活的流量管理能力。Gateway API 于 2025 年 4 月 24 日发布了 v1.3.0 版本,带来了多项新特性。

Gateway API v1.3.0 主要特性

** 标准通道特性:**

  • ** 基于百分比的请求镜像 **:支持将指定百分比的请求镜像到另一个后端,适用于蓝绿部署和性能测试

** 实验性通道特性:**

  • **CORS 过滤 **:支持跨域资源共享配置,包括允许的来源、方法、头部和凭据
  • XListenerSets:标准化的 Gateway 监听器合并机制,支持跨命名空间的监听器配置委托
  • ** 重试预算 (XBackendTrafficPolicy)**:限制客户端在服务端点间的重试行为,提供可配置的重试百分比和间隔

兼容性要求

  • 要求 Kubernetes 1.26 或更高版本
  • 发布时已有四个符合标准的实现:Airlock、Cilium、Envoy Gateway 和 Istio

与传统 Ingress 的关系

Gateway API 为传统 Ingress 提供了更丰富的功能和更好的可扩展性,建议新项目考虑采用 Gateway API,现有项目可以逐步迁移。

# StatefulSet

StatefulSet 是为了解决有状态服务的问题(对应 Deployments 和 ReplicaSets 是为无状态服务而设计),其应用场景包括

  • 稳定的持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现
  • 稳定的网络标志,即 Pod 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有 Cluster IP 的 Service)来实现
  • 有序部署,有序扩展,即 Pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依序进行(即从 0 到 N-1,在下一个 Pod 运行之前所有之前的 Pod 必须都是 Running 和 Ready 状态),基于 init containers 来实现
  • 有序收缩,有序删除(即从 N-1 到 0)

从上面的应用场景可以发现,StatefulSet 由以下几个部分组成:

  • 用于定义网络标志(DNS domain)的 Headless Service
  • 用于创建 PersistentVolumes 的 volumeClaimTemplates
  • 定义具体应用的 StatefulSet

StatefulSet 中每个 Pod 的 DNS 格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local ,其中

  • serviceName 为 Headless Service 的名字
  • 0..N-1 为 Pod 所在的序号,从 0 开始到 N-1
  • statefulSetName 为 StatefulSet 的名字
  • namespace 为服务所在的 namespace,Headless Service 和 StatefulSet 必须在相同的 namespace
  • .cluster.local 为 Cluster Domain

以一个简单的 nginx 服务 web.yaml 为例:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
$ kubectl create -f web.yaml
service "nginx" created
statefulset "web" created
# 查看创建的 headless service 和 statefulset
$ kubectl get service nginx
NAME      CLUSTER-IP   EXTERNAL-IP   PORT (S)   AGE
nginx     None         <none>        80/TCP    1m
$ kubectl get statefulset web
NAME      DESIRED   CURRENT   AGE
web       2         2         2m
# 根据 volumeClaimTemplates 自动创建 PVC(在 GCE 中会自动创建 kubernetes.io/gce-pd 类型的 volume)
$ kubectl get pvc
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-d064a004-d8d4-11e6-b521-42010a800002   1Gi        RWO           16s
www-web-1   Bound     pvc-d06a3946-d8d4-11e6-b521-42010a800002   1Gi        RWO           16s
# 查看创建的 Pod,他们都是有序的
$ kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          5m
web-1     1/1       Running   0          4m
# 使用 nslookup 查看这些 Pod 的 DNS
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
/ # nslookup web-0.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-0.nginx
Address 1: 10.244.2.10
/ # nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-1.nginx
Address 1: 10.244.3.12
/ # nslookup web-0.nginx.default.svc.cluster.local
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web-0.nginx.default.svc.cluster.local
Address 1: 10.244.2.10

还可以进行其他的操作

# 扩容
$ kubectl scale statefulset web --replicas=5
# 缩容
$ kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
# 镜像更新(目前还不支持直接更新 image,需要 patch 来间接实现)
$ kubectl patch statefulset web --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"gcr.io/google_containers/nginx-slim:0.7"}]'
# 删除 StatefulSet 和 Headless Service
$ kubectl delete statefulset web
$ kubectl delete service nginx
# StatefulSet 删除后 PVC 还会保留着,数据不再使用的话也需要删除
$ kubectl delete pvc www-web-0 www-web-1

# 更新 StatefulSet

v1.7 + 支持 StatefulSet 的自动更新,通过 spec.updateStrategy 设置更新策略。目前支持两种策略

  • OnDelete:当 .spec.template 更新时,并不立即删除旧的 Pod,而是等待用户手动删除这些旧 Pod 后自动创建新 Pod。这是默认的更新策略,兼容 v1.6 版本的行为
  • RollingUpdate:当 .spec.template 更新时,自动删除旧的 Pod 并创建新 Pod 替换。在更新时,这些 Pod 是按逆序的方式进行,依次删除、创建并等待 Pod 变成 Ready 状态才进行下一个 Pod 的更新。

# Partitions

RollingUpdate 还支持 Partitions,通过 .spec.updateStrategy.rollingUpdate.partition 来设置。当 partition 设置后,只有序号大于或等于 partition 的 Pod 会在 .spec.template 更新的时候滚动更新,而其余的 Pod 则保持不变(即便是删除后也是用以前的版本重新创建)。

# 设置 partition 为 3
$ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset "web" patched
# 更新 StatefulSet
$ kubectl patch statefulset web --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"gcr.io/google_containers/nginx-slim:0.7"}]'
statefulset "web" patched
# 验证更新
$ kubectl delete po web-2
pod "web-2" deleted
$ kubectl get po -lapp=nginx -w
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running             0          18s

# Pod 管理策略

v1.7 + 可以通过 .spec.podManagementPolicy 设置 Pod 管理策略,支持两种方式

  • OrderedReady:默认的策略,按照 Pod 的次序依次创建每个 Pod 并等待 Ready 之后才创建后面的 Pod
  • Parallel:并行创建或删除 Pod(不等待前面的 Pod Ready 就开始创建所有的 Pod)

Parallel 示例

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: gcr.io/google_containers/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

可以看到,所有 Pod 是并行创建的

$ kubectl create -f webp.yaml
service "nginx" created
statefulset "web" created
$ kubectl get po -lapp=nginx -w
NAME      READY     STATUS              RESTARTS  AGE
web-0     0/1       Pending             0         0s
web-0     0/1       Pending             0         0s
web-1     0/1       Pending             0         0s
web-1     0/1       Pending             0         0s
web-0     0/1       ContainerCreating   0         0s
web-1     0/1       ContainerCreating   0         0s
web-0     1/1       Running             0         10s
web-1     1/1       Running             0         10s

# zookeeper

另外一个更能说明 StatefulSet 强大功能的示例为 zookeeper.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: zk-headless
  labels:
    app: zk-headless
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: zk-config
data:
  ensemble: "zk-0;zk-1;zk-2"
  jvm.heap: "2G"
  tick: "2000"
  init: "10"
  sync: "5"
  client.cnxns: "60"
  snap.retain: "3"
  purge.interval: "1"
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-budget
spec:
  selector:
    matchLabels:
      app: zk
  minAvailable: 2
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: zk
spec:
  serviceName: zk-headless
  replicas: 3
  template:
    metadata:
      labels:
        app: zk
      annotations:
        pod.alpha.kubernetes.io/initialized: "true"
        scheduler.alpha.kubernetes.io/affinity: >
            {
              "podAntiAffinity": {
                "requiredDuringSchedulingRequiredDuringExecution": [{
                  "labelSelector": {
                    "matchExpressions": [{
                      "key": "app",
                      "operator": "In",
                      "values": ["zk-headless"]
                    }]
                  },
                  "topologyKey": "kubernetes.io/hostname"
                }]
              }
            }
    spec:
      containers:
      - name: k8szk
        imagePullPolicy: Always
        image: gcr.io/google_samples/k8szk:v1
        resources:
          requests:
            memory: "4Gi"
            cpu: "1"
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        env:
        - name : ZK_ENSEMBLE
          valueFrom:
            configMapKeyRef:
              name: zk-config
              key: ensemble
        - name : ZK_HEAP_SIZE
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: jvm.heap
        - name : ZK_TICK_TIME
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: tick
        - name : ZK_INIT_LIMIT
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: init
        - name : ZK_SYNC_LIMIT
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: tick
        - name : ZK_MAX_CLIENT_CNXNS
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: client.cnxns
        - name: ZK_SNAP_RETAIN_COUNT
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: snap.retain
        - name: ZK_PURGE_INTERVAL
          valueFrom:
            configMapKeyRef:
                name: zk-config
                key: purge.interval
        - name: ZK_CLIENT_PORT
          value: "2181"
        - name: ZK_SERVER_PORT
          value: "2888"
        - name: ZK_ELECTION_PORT
          value: "3888"
        command:
        - sh
        - -c
        - zkGenConfig.sh && zkServer.sh start-foreground
        readinessProbe:
          exec:
            command:
            - "zkOk.sh"
          initialDelaySeconds: 15
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - "zkOk.sh"
          initialDelaySeconds: 15
          timeoutSeconds: 5
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/zookeeper
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
      annotations:
        volume.alpha.kubernetes.io/storage-class: anything
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 20Gi
kubectl create -f zookeeper.yaml

详细的使用说明见 zookeeper stateful application

# StatefulSet 注意事项

  1. 推荐在 Kubernetes v1.9 或以后的版本中使用
  2. 所有 Pod 的 Volume 必须使用 PersistentVolume 或者是管理员事先创建好
  3. 为了保证数据安全,删除 StatefulSet 时不会删除 Volume
  4. StatefulSet 需要一个 Headless Service 来定义 DNS domain,需要在 StatefulSet 之前创建好

# Pod

Pod 是 Kubernetes 创建或部署的最小 / 最简单的基本单位,一个 Pod 代表集群上正在运行的一个进程。

一个 Pod 封装一个应用容器(也可以有多个容器),存储资源、一个独立的网络 IP 以及管理控制容器运行方式的策略选项。Pod 代表部署的一个单位:Kubernetes 中单个应用的实例,它可能由单个容器或多个容器共享组成的资源。

Docker 是 Kubernetes Pod 中最常见的 runtime ,Pods 也支持其他容器 runtimes。

Kubernetes 中的 Pod 使用可分两种主要方式:

  • Pod 中运行一个容器。“one-container-per-Pod” 模式是 Kubernetes 最常见的用法;在这种情况下,你可以将 Pod 视为单个封装的容器,但是 Kubernetes 是直接管理 Pod 而不是容器。
  • Pods 中运行多个需要一起工作的容器。Pod 可以封装紧密耦合的应用,它们需要由多个容器组成,它们之间能够共享资源,这些容器可以形成一个单一的内部 service 单位 - 一个容器共享文件,另一个 sidecar 容器来更新这些文件。Pod 将这些容器的存储资源作为一个实体来管理。

![](C:\Users\Amadues\Desktop\K8S\ 图片 \pod.png)

Pod 的特征

  • 包含多个共享 IPC 和 Network namespace 的容器,可直接通过 localhost 通信
  • 所有 Pod 内容器都可以访问共享的 Volume,可以访问共享数据
  • 无容错性:直接创建的 Pod 一旦被调度后就跟 Node 绑定,即使 Node 挂掉也不会被重新调度(而是被自动删除),因此推荐使用 Deployment、Daemonset 等控制器来容错
  • 优雅终止:Pod 删除的时候先给其内的进程发送 SIGTERM,等待一段时间(grace period)后才强制停止依然还在运行的进程
  • 特权容器(通过 SecurityContext 配置)具有改变系统配置的权限(在网络插件中大量应用)

Kubernetes v1.8+ 支持容器间共享 PID namespace,需要 docker >= 1.13.1,并配置 kubelet --docker-disable-shared-pid=false

用户命名空间隔离(Kubernetes v1.33+): 从 Kubernetes v1.33 开始,支持通过用户命名空间提供额外的安全隔离。设置 hostUsers: false 可以启用用户命名空间,将容器内的用户 ID 映射到主机的非特权用户,大大提高安全性:

spec:
  hostUsers: false  # 启用用户命名空间隔离
  securityContext:
    runAsUser: 0    # 容器内以 root 运行,但映射到主机非特权用户

Kubernetes 中的 Pod 使用可分两种主要方式:

  • Pod 中运行一个容器。“one-container-per-Pod” 模式是 Kubernetes 最常见的用法;在这种情况下,你可以将 Pod 视为单个封装的容器,但是 Kubernetes 是直接管理 Pod 而不是容器。
  • Pods 中运行多个需要一起工作的容器。Pod 可以封装紧密耦合的应用,它们需要由多个容器组成,它们之间能够共享资源,这些容器可以形成一个单一的内部 service 单位 - 一个容器共享文件,另一个 “sidecar” 容器来更新这些文件。Pod 将这些容器的存储资源作为一个实体来管理。

每个 Pod 都是运行应用的单个实例,如果需要水平扩展应用(例如,运行多个实例),则应该使用多个 Pods,每个实例一个 Pod。在 Kubernetes 中,这样通常称为 Replication。Replication 的 Pod 通常由 Controller 创建和管理。

# Pods 资源共享

Pods 提供两种共享资源:网络和存储。

  • 网络:每个 Pod 被分配一个独立的 IP 地址,Pod 中的每个容器共享网络命名空间,包括 IP 地址和网络端口。Pod 内的容器可以使用 localhost 相互通信。当 Pod 中的容器与 Pod 外部通信时,他们必须协调如何使用共享网络资源(如端口)。

  • 存储:Pod 可以指定一组共享存储 * volumes*。Pod 中的所有容器都可以访问共享 * volumes*,允许这些容器共享数据。volumes 还用于 Pod 中的数据持久化,以防其中一个容器需要重新启动而丢失数据。

# 使用 Pod

通过 yaml 或 json 描述 Pod 和其内容器的运行环境以及期望状态,比如一个最简单的 nginx pod 可以定义为

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80

你很少会直接在 kubernetes 中创建单个 Pod。因为 Pod 的生命周期是短暂的,用后即焚的实体。当 Pod 被创建后(不论是由你直接创建还是被其他 Controller),都会被 Kuberentes 调度到集群的 Node 上。直到 Pod 的进程终止、被删掉、因为缺少资源而被驱逐、或者 Node 故障之前这个 Pod 都会一直保持在那个 Node 上。

注意:重启 Pod 中的容器跟重启 Pod 不是一回事。Pod 只提供容器的运行环境并保持容器的运行状态,重启容器不会造成 Pod 重启。

Pod 不会自愈。如果 Pod 运行的 Node 故障,或者是调度器本身故障,这个 Pod 就会被删除。同样的,如果 Pod 所在 Node 缺少资源或者 Pod 处于维护状态,Pod 也会被驱逐。Kubernetes 使用更高级的称为 Controller 的抽象层,来管理 Pod 实例。虽然可以直接使用 Pod,但是在 Kubernetes 中通常是使用 Controller 来管理 Pod 的。

# Pod 生命周期

Pod 遵循预定义的生命周期,起始于 Pending 阶段, 如果至少其中有一个主要容器正常启动,则进入 Running ,之后取决于 Pod 中是否有容器以失败状态结束而进入 Succeeded 或者 Failed 阶段。

和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID(UID), 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。 如果一个 节点 死掉了,调度到该节点的 Pod 也被计划在给定超时期限结束后 删除

在 Pod 运行期间, kubelet 能够重启容器以处理一些失效场景。 在 Pod 内部,Kubernetes 跟踪不同容器的 状态 并确定使 Pod 重新变得健康所需要采取的动作。

在 Kubernetes API 中,Pod 包含规约部分和实际状态部分。 Pod 对象的状态包含了一组 Pod 状况(Conditions)。 如果应用需要的话,你也可以向其中注入 自定义的就绪态信息

Pod 在其生命周期中只会被 调度 一次。 将 Pod 分配到特定节点的过程称为 ** 绑定 **,而选择使用哪个节点的过程称为 ** 调度 **。 一旦 Pod 被调度并绑定到某个节点,Kubernetes 会尝试在该节点上运行 Pod。 Pod 会在该节点上运行,直到 Pod 停止或者被 终止; 如果 Kubernetes 无法在选定的节点上启动 Pod(例如,如果节点在 Pod 启动前崩溃), 那么特定的 Pod 将永远不会启动。

你可以使用 Pod 调度就绪态 来延迟 Pod 的调度,直到所有的 ** 调度门控 ** 都被移除。 例如,你可能想要定义一组 Pod,但只有在所有 Pod 都被创建完成后才会触发调度。

# Pod 故障恢复

如果 Pod 中的某个容器失败,Kubernetes 可能会尝试重启特定的容器。 有关细节参阅 Pod 如何处理容器问题

然而,Pod 也可能以集群无法恢复的方式失败,在这种情况下,Kubernetes 不会进一步尝试修复 Pod; 相反,Kubernetes 会删除 Pod 并依赖其他组件提供自动修复。

如果 Pod 被调度到某个 节点 而该节点之后失效, Pod 会被视为不健康,最终 Kubernetes 会删除 Pod。 Pod 无法在因节点资源耗尽或者节点维护而被 驱逐 期间继续存活。

Kubernetes 使用一种高级抽象来管理这些相对而言可随时丢弃的 Pod 实例, 称作 控制器

任何给定的 Pod (由 UID 定义)从不会被 “重新调度(rescheduled)” 到不同的节点; 相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。 如果你创建一个替换 Pod,它甚至可以拥有与旧 Pod 相同的名称(如 .metadata.name ), 但替换 Pod 将具有与旧 Pod 不同的 .metadata.uid

Kubernetes 不保证现有 Pod 的替换 Pod 会被调度到与被替换的旧 Pod 相同的节点。

# 关联生命周期

如果某物声称其生命期与某 Pod 相同,例如存储 , 这就意味着该对象在此 Pod (UID 亦相同)存在期间也一直存在。 如果 Pod 因为任何原因被删除,甚至某完全相同的替代 Pod 被创建时, 这个相关的对象(例如这里的卷)也会被删除并重建。

# Pod 阶段

Pod 的 status 字段是一个 PodStatus 对象,其中包含一个 phase 字段。

Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。 该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。

Pod 阶段的数量和含义是严格定义的。 除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase 值。

查看 pod 生命周期 kubectl get pod <pod_name> -o jsonpath="{.status.phase}"

下面是 phase 可能的值:

取值描述
Pending (悬决)Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
Running (运行中)Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
Succeeded (成功)Pod 中的所有容器都已成功结束,并且不会再重启。
Failed (失败)Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止,且未被设置为自动重启。
Unknown (未知)因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

当 Pod 反复启动失败时,某些 kubectl 命令的 Status 字段中可能会出现 CrashLoopBackOff 。 同样,当 Pod 被删除时,某些 kubectl 命令的 Status 字段中可能会出现 Terminating

不要将 Status(kubectl 用于用户直觉的显示字段)与 Pod 的 phase 混淆。 Pod 阶段(phase)是 Kubernetes 数据模型和 Pod API 的一个明确的部分。

优雅终止:Pod 被赋予一个可以体面终止的期限,默认为 30 秒。 你可以使用 --force 参数来 强制终止 Pod

从 Kubernetes 1.27 开始,除了 静态 Pod 和没有 Finalizer 的 强制终止 Pod 之外, kubelet 会将已删除的 Pod 转换到终止阶段 ( FailedSucceeded 具体取决于 Pod 容器的退出状态),然后再从 API 服务器中删除。

如果某节点死掉或者与集群中其他节点失联,Kubernetes 会实施一种策略,将失去的节点上运行的所有 Pod 的 phase 设置为 Failed

# Pod 状态

Pod 有一个 PodStatus 对象,其中包含一个 PodConditions 数组。Pod 可能通过也可能未通过其中的一些状况测试。 Kubelet 管理以下 PodCondition:

  • PodScheduled :Pod 已经被调度到某节点;
  • PodReadyToStartContainers :Pod 沙箱被成功创建并且配置了网络(Beta 特性,默认 启用);
  • ContainersReady :Pod 中所有容器都已就绪;
  • Initialized :所有的 Init 容器 都已成功完成;
  • Ready :Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
  • DisruptionTarget :由于干扰(例如抢占、驱逐或垃圾回收),Pod 即将被终止。
  • PodResizePending :已请求对 Pod 进行调整大小,但尚无法应用。 详见 Pod 调整大小状态
  • PodResizeInProgress :Pod 正在调整大小中。 详见 Pod 调整大小状态
字段名称描述
typePod 状况的名称
status表明该状况是否适用,可能的取值有 " True "、" False "或" Unknown "
lastProbeTime上次探测 Pod 状况时的时间戳
lastTransitionTimePod 上次从一种状态转换到另一种状态时的时间戳
reason机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因
message人类可读的消息,给出上次状态转换的详细信息

# 容器状态

Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的 阶段 一样。 你可以使用 容器生命周期回调 来在容器生命周期中的特定时间点触发事件。

一旦 调度器 将 Pod 分派给某个节点, kubelet 就通过 容器运行时 开始为 Pod 创建容器。容器的状态有三种: Waiting (等待)、 Running (运行中)和 Terminated (已终止)

要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称 > 。 其输出中包含 Pod 中每个容器的状态。

每种状态都有特定的含义:

  • Waiting (等待)

如果容器并不处在 RunningTerminated 状态之一,它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如, 从某个容器镜像仓库拉取容器镜像,或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。

  • Running (运行中)

Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行且已完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时, 你也会看到关于容器进入 Running 状态的信息。

  • Terminated (已终止)

处于 Terminated 状态的容器开始执行后,或者运行至正常结束或者因为某些原因失败。 如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时, 你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。

如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行。

# 容器重启

Kubernetes 通过在 Pod spec 中定义的 restartPolicy 管理 Pod 内容器出现的失效。 该策略决定了 Kubernetes 如何对由于错误或其他原因而退出的容器做出反应,其顺序如下:

  1. ** 最初的崩溃 **:Kubernetes 尝试根据 Pod 的 restartPolicy 立即重新启动。
  2. ** 反复的崩溃 **:在最初的崩溃之后,Kubernetes 对于后续重新启动的容器采用指数级回退延迟机制, 如 restartPolicy 中所述。 这一机制可以防止快速、重复的重新启动尝试导致系统过载。
  3. **CrashLoopBackOff 状态 **:这一状态表明,对于一个给定的、处于崩溃循环、反复失效并重启的容器, 回退延迟机制目前正在生效。
  4. ** 回退重置 **:如果容器成功运行了一定时间(如 10 分钟), Kubernetes 会重置回退延迟机制,将新的崩溃视为第一次崩溃。

在实际部署中, CrashLoopBackOff 是在描述或列出 Pod 时从 kubectl 命令输出的一种状况或事件。 当 Pod 中的容器无法正常启动,并反复进入尝试与失败的循环时就会出现。

换句话说,当容器进入崩溃循环时,Kubernetes 会应用 容器重启策略 中提到的指数级回退延迟机制。这种机制可以防止有问题的容器因不断进行启动失败尝试而导致系统不堪重负。

下列问题可以导致 CrashLoopBackOff

  • 应用程序错误导致的容器退出。
  • 配置错误,如环境变量不正确或配置文件丢失。
  • 资源限制,容器可能没有足够的内存或 CPU 正常启动。
  • 如果应用程序没有在预期时间内启动服务,健康检查就会失败。
  • 容器的存活探针或者启动探针返回 失败 结果,如 探针部分 所述。

要调查 CrashLoopBackOff 问题的根本原因,用户可以:

  1. ** 检查日志 **:使用 kubectl logs <pod 名称 > 检查容器的日志。 这通常是诊断导致崩溃的问题的最直接方法。
  2. ** 检查事件 **:使用 kubectl describe pod <pod 名称 > 查看 Pod 的事件, 这可以提供有关配置或资源问题的提示。
  3. ** 审查配置 **:确保 Pod 配置正确无误,包括环境变量和挂载卷,并且所有必需的外部资源都可用。
  4. ** 检查资源限制 **: 确保容器被分配了足够的 CPU 和内存。有时,增加 Pod 定义中的资源可以解决问题。
  5. ** 调试应用程序 **:应用程序代码中可能存在错误或配置不当。 在本地或开发环境中运行此容器镜像有助于诊断应用程序的特定问题。

当 Pod 中的某个容器停止或发生故障时,Kubernetes 可以重新启动此容器。但重启并不总是合适的;例如, Init 容器 只在 Pod 启动期间运行一次。

你可以将重启配置为适用于所有 Pod 的策略,或者使用容器级别的配置(例如: 在你定义 边车容器 时)。

Kubernetes 项目建议遵循云原生原则,包括能够应对未预告或随意重启的弹性设计。 你可以通过让 Pod 失败并依赖自动 替换, 或者通过容器级别的弹性设计来实现。 无论哪种方式,都有助于确保即使在部分故障的情况下,你的整体工作负载依然保持可用。

容器重启策略

Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always、OnFailure 和 Never。默认值是 Always。

restartPolicy 应用于 Pod 中的 应用容器 和常规的 Init 容器Sidecar 容器 忽略 Pod 级别的 restartPolicy 字段:在 Kubernetes 中,Sidecar 被定义为 initContainers 内的一个条目,其容器级别的 restartPolicy 被设置为 Always 。 对于因错误而退出的 Init 容器,如果 Pod 级别 restartPolicyOnFailureAlways , 则 kubelet 会重新启动 Init 容器。

  • Always :只要容器终止就自动重启容器。
  • OnFailure :只有在容器错误退出(退出状态非零)时才重新启动容器。
  • Never :不会自动重启已终止的容器。

当 kubelet 根据配置的重启策略处理容器重启时,仅适用于同一 Pod 内替换容器并在同一节点上运行的重启。当 Pod 中的容器退出时, kubelet 会以指数级回退延迟机制(10 秒、20 秒、40 秒......)重启容器, 上限为 300 秒(5 分钟)。一旦容器顺利执行了 10 分钟, kubelet 就会重置该容器的重启延迟计时器。 Sidecar 容器和 Pod 生命周期 中解释了 init containers 在指定 restartpolicy 字段时的行为。

# Pod 垃圾收集

对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API 服务器上, 直到用户或者 控制器 进程显式地将其删除。

Pod 的垃圾收集器(PodGC)是控制平面的控制器,它会在 Pod 个数超出所配置的阈值 (根据 kube-controller-managerterminated-pod-gc-threshold 设置)时删除已终止的 Pod(阶段值为 SucceededFailed )。 这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。

此外,PodGC 会清理满足以下任一条件的所有 Pod:

  1. 孤儿 Pod - 绑定到不再存在的节点,
  2. 计划外终止的 Pod
  3. 终止过程中的 Pod,绑定到有 node.kubernetes.io/out-of-service 污点的未就绪节点。

在清理 Pod 的同时,如果它们处于非终止状态阶段,PodGC 也会将它们标记为失败。 此外,PodGC 在清理孤儿 Pod 时会添加 Pod 干扰状况。参阅 Pod 干扰状况 了解更多详情。

# PodPreset

PodPreset 用来给指定标签的 Pod 注入额外的信息,如环境变量、存储卷等。这样,Pod 模板就不需要为每个 Pod 都显式设置重复的信息。

当然,你也可以给 Pod 增加注解 podpreset.admission.kubernetes.io/exclude:"true" 来避免它们被 PodPreset 修改。

开启 PodPreset

  • 开启 API kube-apiserver --runtime-config=settings.k8s.io/v1alpha1=true
  • 开启准入控制 --enable-admission-plugins=..,PodPreset

PodPreset 示例

增加环境变量和存储卷的 PodPreset

kind: PodPreset
apiVersion: settings.k8s.io/v1alpha1
metadata:
  name: allow-database
  namespace: myns
spec:
  selector:
    matchLabels:
      role: frontend
  env:
    - name: DB_PORT
      value: "6379"
  volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}

用户提交 Pod

apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
spec:
  containers:
    - name: website
      image: ecorp/website
      ports:
        - containerPort: 80

经过准入控制 PodPreset 后,Pod 会自动增加环境变量和存储卷

apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
  annotations:
    podpreset.admission.kubernetes.io/allow-database: "resource version"
spec:
  containers:
    - name: website
      image: ecorp/website
      volumeMounts:
        - mountPath: /cache
          name: cache-volume
      ports:
        - containerPort: 80
      env:
        - name: DB_PORT
          value: "6379"
  volumes:
    - name: cache-volume
      emptyDir: {}

# ReplicationController & ReplicaSet

ReplicationController(也简称为 rc)用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代;而异常多出来的容器也会自动回收。ReplicationController 的典型应用场景包括确保健康 Pod 的数量、弹性伸缩、滚动升级以及应用多版本发布跟踪等。

在新版本的 Kubernetes 中建议使用 ReplicaSet(也简称为 rs)来取代 ReplicationController。ReplicaSet 跟 ReplicationController 没有本质的不同,只是名字不一样,并且 ReplicaSet 支持集合式的 selector(ReplicationController 仅支持等式)。

虽然 ReplicaSet 也可以独立使用,但建议使用 Deployment 来自动管理 ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如 ReplicaSet 不支持 rolling-update 但 Deployment 支持),并且还支持版本记录、回滚、暂停升级等高级特性。

ReplicationController 示例

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

ReplicaSet 示例

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: frontend
  # these labels can be applied automatically
  # from the labels in the pod template if not set
  # labels:
    # app: guestbook
    # tier: frontend
spec:
  # this replicas value is default
  # modify it according to your case
  replicas: 3
  # selector can be applied automatically
  # from the labels in the pod template if not set,
  # but we are specifying the selector here to
  # demonstrate its usage.
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
          # If your cluster config does not include a dns service, then to
          # instead access environment variables to find service host
          # info, comment out the 'value: dns' line above, and uncomment the
          # line below.
          # value: env
        ports:
        - containerPort: 80

# Service

Kubernetes 在设计之初就充分考虑了针对容器的服务发现与负载均衡机制,提供了 Service 资源,并通过 kube-proxy 配合 cloud provider 来适应不同的应用场景。随着 kubernetes 用户的激增,用户场景的不断丰富,又产生了一些新的负载均衡机制。目前,kubernetes 中的负载均衡大致可以分为以下几种机制,每种机制都有其特定的应用场景:

  • Service:直接用 Service 提供 cluster 内部的负载均衡,并借助 cloud provider 提供的 LB 提供外部访问
  • Ingress Controller:还是用 Service 提供 cluster 内部的负载均衡,但是通过自定义 Ingress Controller 提供外部访问
  • Service Load Balancer:把 load balancer 直接跑在容器中,实现 Bare Metal 的 Service Load Balancer
  • Custom Load Balancer:自定义负载均衡,并替代 kube-proxy,一般在物理部署 Kubernetes 时使用,方便接入公司已有的外部服务

Service 是对一组提供相同功能的 Pods 的抽象,并为它们提供一个统一的入口。借助 Service,应用可以方便的实现服务发现与负载均衡,并实现应用的零宕机升级。Service 通过标签来选取服务后端,一般配合 Replication Controller 或者 Deployment 来保证后端容器的正常运行。这些匹配标签的 Pod IP 和端口列表组成 endpoints,由 kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上。

** 重要提示 (Kubernetes 1.33+)**: Endpoints API 已在 Kubernetes 1.33 中被标记为弃用,建议迁移到 EndpointSlices API。虽然 Endpoints API 仍然可用并且不会被移除(由于弃用策略),但新的功能(如双栈网络)只在 EndpointSlices 中支持。

Service 有四种类型:

  • ClusterIP:默认类型,自动分配一个仅 cluster 内部可以访问的虚拟 IP
  • NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 <NodeIP>:NodePort 来访问该服务。如果 kube-proxy 设置了 --nodeport-addresses=10.240.0.0/16 (v1.10 支持),那么仅该 NodePort 仅对设置在范围内的 IP 有效。
  • LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到 <NodeIP>:NodePort
  • ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。需要 kube-dns 版本在 1.7 以上。

另外,也可以将已有的服务以 Service 的形式加入到 Kubernetes 集群中来,只需要在创建 Service 的时候不指定 Label selector,而是在 Service 创建好后手动为其添加 endpoint。

# Service 定义

Service 的定义也是通过 yaml 或 json,比如下面定义了一个名为 nginx 的服务,将服务的 80 端口转发到 default namespace 中带有标签 run=nginx 的 Pod 的 80 端口

apiVersion: v1
kind: Service
metadata:
  labels:
    run: nginx
  name: nginx
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: nginx
  sessionAffinity: None
  type: ClusterIP
# service 自动分配了 Cluster IP 10.0.0.108
$ kubectl get service nginx
NAME      CLUSTER-IP   EXTERNAL-IP   PORT (S)   AGE
nginx     10.0.0.108   <none>        80/TCP    18m
# 自动创建的 endpoint
$ kubectl get endpoints nginx
NAME      ENDPOINTS       AGE
nginx     172.17.0.5:80   18m
# Service 自动关联 endpoint
$ kubectl describe service nginx
Name:            nginx
Namespace:        default
Labels:            run=nginx
Annotations:        <none>
Selector:        run=nginx
Type:            ClusterIP
IP:            10.0.0.108
Port:            <unset>    80/TCP
Endpoints:        172.17.0.5:80
Session Affinity:    None
Events:            <none>

当服务需要多个端口时,每个端口都必须设置一个名字

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  - name: https
    protocol: TCP
    port: 443
    targetPort: 9377

# 协议

Service、Endpoints(已弃用,建议使用 EndpointSlices)和 Pod 支持三种类型的协议:

  • TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
  • UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,用于不可靠信息传送服务。
  • SCTP(Stream Control Transmission Protocol,流控制传输协议),用于通过 IP 网传输 SCN(Signaling Communication Network,信令通信网)窄带信令消息。

# 不指定 Selectors 的服务

在创建 Service 的时候,也可以不指定 Selectors,用来将 service 转发到 kubernetes 集群外部的服务(而不是 Pod)。目前支持两种方法

(1)自定义 endpoint,即创建同名的 service 和 endpoint,在 endpoint 中设置外部服务的 IP 和端口

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
# 传统 Endpoints 方式(已弃用)
kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

推荐使用 EndpointSlices 替代 Endpoints:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
# 推荐的 EndpointSlices 方式
kind: EndpointSlice
apiVersion: discovery.k8s.io/v1
metadata:
  name: my-service-abc123
  labels:
    kubernetes.io/service-name: my-service
addressType: IPv4
endpoints:
- addresses:
  - "1.2.3.4"
ports:
- name: ""
  port: 9376
  protocol: TCP

(2)通过 DNS 转发,在 service 定义中指定 externalName。此时 DNS 服务会给 <service-name>.<namespace>.svc.cluster.local 创建一个 CNAME 记录,其值为 my.database.example.com 。并且,该服务不会自动分配 Cluster IP,需要通过 service 的 DNS 来访问。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: default
spec:
  type: ExternalName
  externalName: my.database.example.com

注意:Endpoints 的 IP 地址不能是 127.0.0.0/8、169.254.0.0/16 和 224.0.0.0/24,也不能是 Kubernetes 中其他服务的 clusterIP。

# Headless 服务

Headless 服务即不需要 Cluster IP 的服务,即在创建服务的时候指定 spec.clusterIP=None 。包括两种类型

  • 不指定 Selectors,但设置 externalName,即上面的(2),通过 CNAME 记录处理
  • 指定 Selectors,通过 DNS A 记录设置后端 endpoint 列表
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  clusterIP: None
  ports:
  - name: tcp-80-80-3b6tl
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: default
spec:
  replicas: 2
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        imagePullPolicy: Always
        name: nginx
        resources:
          limits:
            memory: 128Mi
          requests:
            cpu: 200m
            memory: 128Mi
      dnsPolicy: ClusterFirst
      restartPolicy: Always
# 查询创建的 nginx 服务
$ kubectl get service --all-namespaces=true
NAMESPACE     NAME         CLUSTER-IP      EXTERNAL-IP      PORT (S)         AGE
default       nginx        None            <none>           80/TCP          5m
kube-system   kube-dns     172.26.255.70   <none>           53/UDP,53/TCP   1d
$ kubectl get pod
NAME                       READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-2204978904-6o5dg     1/1       Running   0          14s       172.26.2.5   10.0.0.2
nginx-2204978904-qyilx     1/1       Running   0          14s       172.26.1.5   10.0.0.8
$ dig @172.26.255.70  nginx.default.svc.cluster.local
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 30 IN    A    172.26.1.5
nginx.default.svc.cluster.local. 30 IN    A    172.26.2.5

备注: 其中 dig 命令查询的信息中,部分信息省略

# 保留源 IP

各种类型的 Service 对源 IP 的处理方法不同:

  • ClusterIP Service:使用 iptables 模式,集群内部的源 IP 会保留(不做 SNAT)。如果 client 和 server pod 在同一个 Node 上,那源 IP 就是 client pod 的 IP 地址;如果在不同的 Node 上,源 IP 则取决于网络插件是如何处理的,比如使用 flannel 时,源 IP 是 node flannel IP 地址。
  • NodePort Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。为了避免这种情况,可以给 service 设置 spec.ExternalTrafficPolicy=Local (1.6-1.7 版本设置 Annotation service.beta.kubernetes.io/external-traffic=OnlyLocal ),让 service 只代理本地 endpoint 的请求(如果没有本地 endpoint 则直接丢包),从而保留源 IP。
  • LoadBalancer Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。设置 service.spec.ExternalTrafficPolicy=Local 后可以自动从云平台负载均衡器中删除没有本地 endpoint 的 Node,从而保留源 IP。
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  internalTrafficPolicy: Local

注意,开启内网网络策略之后,即使其他 Node 上面有正常工作的 Endpoints,只要 Node 本地没有正常运行的 Pod,该 Service 就无法访问。

# 工作原理

kube-proxy 负责将 service 负载均衡到后端 Pod 中,如下图所示

![](C:\Users\Amadues\Desktop\K8S\ 图片 \kubProxy.png)