本文章仍在施工中。。。。
# API 概述
REST API 是 Kubernetes 的基本结构。 所有操作和组件之间的通信及外部用户命令都是调用 API 服务器处理的 REST API。 因此,Kubernetes 平台视一切皆为 API 对象, 且它们在 API 中有相应的定义。
Kubernetes API 参考 列出了 Kubernetes v1.35 版本的 API。
如需了解一般背景信息,请查阅 Kubernetes API。 Kubernetes API 控制访问 描述了客户端如何向 Kubernetes API 服务器进行身份认证以及他们的请求如何被鉴权。
更多相关信息,请参考 官方文档 - API 概述
# API 版本控制
JSON 和 Protobuf 序列化模式遵循相同的模式更改原则。 以下描述涵盖了这两种格式。
API 版本控制和软件版本控制是间接相关的。 API 和发布版本控制提案 描述了 API 版本控制和软件版本控制间的关系。
不同的 API 版本代表着不同的稳定性和支持级别。 你可以在 API 变更文档 中查看到更多的不同级别的判定标准。
下面是每个级别的摘要:
Alpha:
- 版本名称包含
alpha(例如:v1alpha1)。 - 内置的 Alpha API 版本默认被禁用且必须在
kube-apiserver配置中显式启用才能使用。 - 软件可能会有 Bug。启用某个特性可能会暴露出 Bug。
- 对某个 Alpha API 特性的支持可能会随时被删除。
- API 可能在以后的软件版本中以不兼容的方式更改。
- 由于缺陷风险增加和缺乏长期支持,建议该软件仅用于短期测试集群。
- 版本名称包含
Beta:
版本名称包含
beta(例如:v2beta3)。内置的 Beta API 版本默认被禁用且必须在
kube-apiserver配置中显式启用才能使用 (例外情况是 Kubernetes 1.22 之前引入的 Beta 版本的 API,这些 API 默认被启用)。内置 Beta API 版本从引入到弃用的最长生命周期为 9 个月或 3 个次要版本(以较长者为准), 从弃用到移除的最长生命周期为 9 个月或 3 个次要版本(以较长者为准)。
软件被很好的测试过。启用某个特性被认为是安全的。
尽管一些特性会发生细节上的变化,但它们将会被长期支持。
在随后的 Beta 版或 Stable 版中,对象的模式和(或)语义可能以不兼容的方式改变。 当这种情况发生时,将提供迁移说明。 适配后续的 Beta 或 Stable API 版本可能需要编辑或重新创建 API 对象,这可能并不简单。 对于依赖此功能的应用程序,可能需要停机迁移。
该版本的软件不建议生产使用。 后续发布版本可能会有不兼容的变动。 一旦 Beta API 版本被弃用且不再提供服务, 则使用 Beta API 版本的用户需要转为使用后续的 Beta 或 Stable API 版本。
Stable:
- 版本名称如
vX,其中X为整数。 - 特性的 Stable 版本会出现在后续很多版本的发布软件中。 Stable API 版本仍然适用于 Kubernetes 主要版本范围内的所有后续发布, 并且 Kubernetes 的主要版本当前没有移除 Stable API 的修订计划。
- 版本名称如
Alpha为先行版本(不稳定),Beta基于Alpha优化了稳定性,Stable为长期稳定版本
# 资源清单
在 k8s 中,一般使用 yaml 格式的文件来创建符合我们预期的 pod,这样的 yaml 文件我们一般称为资源清单。
# YAML 语法
yaml 是一个可读性高,用来表达数据序列的格式;yaml 的意思其实是:仍是一种标记语言,但为了强调这种语言以数据为中心,而不是以标记语言为重点。
基本语法:
- 缩进时不允许使用 Tab 键,只允许使用空格。
- 缩进的空格数量不重要,只要相同层级左侧对齐即可
- # 标识注释,从这个字符一直到行尾,都会被解释器忽略
支持的数据结构:
- 对象:键值对的集合,又称映射(mapping)/ 哈希(hashes)/ 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence)/ 列表(list)
- 纯量(scalars):单个的、不可再分的值
# 常用字段解释说明
# 必须存在的属性
| 参数名 | 字段类型 | 说明 |
|---|---|---|
| version | String | 这里指的是 K8s API 的版本,目前基本上是 v1,可以用 kubectl api-version 命令查询 |
| kind | String | 这里指的是 yaml 文件定义的资源类型和角色,比如:pod |
| metadata | Object | 元数据对象,固定值就写 metadata |
| metadata.name | String | 元数据对象的名字,这里由我们编写,比如命令 Pod 的名字 |
| metadata.namespace | String | 元数据对象的命名空间,由我们自身定义 |
| spec | Object | 详细定义对象,固定值就写 Spec |
| spec.containers [] | list | 这里是 Spec 对象的容器列表定义,是个列表 |
| spec.containers [].name | String | 这里定义容器的名字 |
| spec.containers [].image | String | 这里定义要用到的镜像名称 |
# 主要对象
| 参数名 | 字段类型 | 说明 |
|---|---|---|
| spec.containers [].name | String | 定义容器的名字 |
| spec.containers [].image | String | 定义要用到的镜像名称 |
| spec.containers [].imagePullRolicy | String | 定义镜像拉取策略,有 Always、Never、IfNotPresent 三个选项; Always:每次都尝试重新拉取镜像,默认 Always Never:仅使用本地镜像 IfNotPresent:如果本地有镜像就使用本地就像,如果没有就拉取在线镜像; |
| spec.containers [].command [] | List | 指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令。 |
| spec.containers [].args [] | List | 指定容器启动命令参数,因为是数组可以指定多个。 |
| spec.containers [].workingDir | String | 指定容器的工作目录。 |
| spec.containers [].volumeMounts [] | List | 指定容器内部的存储卷配置 |
| spec.containers [].volumeMounts [].name | String | 指定可以被容器挂载的存储卷的名称 |
| spec.containers [].volumeMounts [].mountPath | String | 指定可以被容器挂载的存储卷的路径 |
| spec.containers [].volumeMounts [].readOnly | String | 设置存储卷路径的读写模式,true 或者 false,默认为读写模式 |
| spec.containers [].ports [] | List | 指定容器需要用到的端口列表 |
| spec.containers [].ports [].name | String | 指定端口名称 |
| spec.containers [].containerPort | String | 指定容器需要监听的端口号 |
| spec.containers [].ports [].hostPort | String | 指定容器所在主机需要监听的端口号,默认跟上面 containerPort 相同;注意设置了 hostPort 同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突) |
| spec.containers [].ports [].protocol | String | 指定端口协议,支持 TCP 和 UDP,默认为 TCP |
| spec.containers [].env [] | List | 指定容器运行前需设置的环境变量列表 |
# 额外的参数项
| 参数名 | 字段类型 | 说明 |
|---|---|---|
| spec.restartPolicy | String | ** 定义 Pod 的重启策略 **,可选值为 Always、OnFailure,默认值为 Always; 1.Always:pod 一旦终止运行,则无论容器是如何终止的,kubelet 服务都将重启它。 2.OnFailure:只有 pod 以非零退出码终止时,kubelet 才会重启该容器;如果容器正常退出 (退出码为 0),则 kubelet 将不会重启它; 3.Never:pod 终止后,kubelet 将退出码报告给 master,不会重启 pod。 |
| spec.nodeSelector | Object | 定义 Node 的 Label 过滤标签,以 key: value 格式指定 |
| spec.imagePullSecrets | Object | 定义 pull 镜像时使用 sercet 名称,以 name: secretkey 格式指定 |
| spec.hostNetwork | Boolean | 定义是否使用主机网络模式,默认值 false;设置 true 表示使用宿主机网络,不使用 docker 网桥,同时设置了 true 将无法再同一台宿主机上启动第二个副本。 |
# Pod 详解
# 创建 Pod
K8S 遵循一切皆资源的概念,可以使用资源清单创建资源,资源清单具体表现为一个 yaml 文件,现在,我们根据资源清单的 yaml 参数,创建一个 pod
- 创建一个命名空间,以便将本练习中创建的资源与集群的其余部分隔离。
# 创建一个名为 pod-example 的命名空间 | |
[root@kubernetes-master ~]# kubectl create namespace pod-example | |
namespace/pod-example created | |
# 查看命名空间 | |
[root@kubernetes-master ~]# kubectl get ns | |
NAME STATUS AGE | |
default Active 13h | |
kube-flannel Active 13h | |
kube-node-lease Active 13h | |
kube-public Active 13h | |
kube-system Active 13h | |
pod-example Active 5s |
- 创建 pod 资源清单
[root@kubernetes-master ~]# cat nginx-demo.yaml | |
apiVersion: v1 # API 版本(K8s 核心组的稳定版本 v1) | |
kind: Pod # 资源对象类型,也可以配置 Deployment 等资源对象 | |
metadata: # Pod 相关的元数据,用于描述 Pod | |
name: nginx-demo # Pod 名称 | |
labels: # 定义 Pod 的标签,可以自定义信息 | |
type: app # 自定义标签,名为 type,值为 app | |
test-label-name: test-label-value | |
namespace: pod-example # Pod 的命名空间 | |
spec: # 期望 Pod 按照如下描述进行创建 | |
containers: # 对 Pod 中容器的描述 | |
- name: nginx # 容器名称 | |
image: docker.io/library/nginx:latest # 容器镜像,使用 docker images 或者 crictl images 可以查看已安装的镜像,注意需要在 nodes 节点查看 | |
imagePullPolicy: IfNotPresent # 拉取镜像策略,若本地有就不拉取 | |
command: # 指定容器启动时需要执行的命令 | |
- nginx | |
- -g | |
- 'daemon off;' # nginx -g 'daemon off;' 让 nginx 前台运行(容器必须前台进程才不会退出) | |
workingDir: /usr/share/nginx/html # 指定容器启动后的工作目录 | |
ports: # 端口定义 | |
- name: http # 端口名称 | |
containerPort: 80 # 描述容器内要暴露的端口 | |
protocol: TCP # 指定通信协议 | |
env: # 环境变量,可自定义 | |
- name: JVM_OPTS # 变量名称 | |
value: 'test-value' # 变量值 | |
resources: # 资源请求 | |
requests: # 容器运行的最小资源保障,CPU 100 毫核(0.1 核),内存 128Mi(mebibyte,1Mi=1024×1024 字节) | |
cpu: 100m | |
memory: 128Mi | |
limits: # 资源限制,容器能使用的最大资源上限,CPU 1000 毫核(1 核),内存 1024Mi(1Gi) | |
cpu: 1000m | |
memory: 1024Mi | |
restartPolicy: OnFailure # Pod 重启策略,仅当容器运行失败(退出码非 0)时重启容器 |
- 创建 Pod
# 根据文件创建资源 | |
[root@kubernetes-master ~]# kubectl apply -f nginx-demo.yaml | |
pod/nginx-demo created | |
# 查看命名空间 pod-example 内的资源 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 13s | |
# 查看 Pod nginx-demo 的状态信息 | |
[root@kubernetes-master ~]# kubectl describe pod nginx-demo -n pod-example | |
......... | |
Events: # 事件 | |
Type Reason Age From Message | |
---- ------ ---- ---- ------- | |
Normal Scheduled 32s default-scheduler Successfully assigned pod-example/nginx-demo to kubernetes-node1 # 分配节点 | |
Normal Pulled 32s kubelet Container image "docker.io/library/nginx:latest" already present on machine # 拉取镜像 | |
Normal Created 32s kubelet Created container nginx # 创建容器 | |
Normal Started 32s kubelet Started container nginx # 启动容器 |
- 验证创建状态
# 查看 Pod 详细信息 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example -o wide | |
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES | |
nginx-demo 1/1 Running 0 5m51s 10.244.1.16 kubernetes-node1 <none> <none> | |
# 查看 Pod 标签 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example --show-labels | |
NAME READY STATUS RESTARTS AGE LABELS | |
nginx-demo 1/1 Running 0 5m16s test-label-name=test-label-value,type=app | |
# 查看工作目录和写入的环境变量 | |
[root@kubernetes-master ~]# kubectl exec -it nginx-demo -n pod-example -- pwd | |
/usr/share/nginx/html | |
[root@kubernetes-master ~]# kubectl exec -it nginx-demo -n pod-example -- bash -c 'echo $JVM_OPTS' | |
test-value | |
# 验证 nginx 服务 | |
[root@kubernetes-master ~]# curl 10.244.1.16 | |
<!DOCTYPE html> | |
......... | |
# 查看路由状态 | |
[root@kubernetes-master ~]# route -n | |
Kernel IP routing table | |
Destination Gateway Genmask Flags Metric Ref Use Iface | |
0.0.0.0 192.168.0.1 0.0.0.0 UG 100 0 0 ens33 | |
10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1 | |
10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1 | |
192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33 |
根据上述信息, kubernetes-master 能跟 pod 通信,是因为 pod 对外暴露了 80 端口(暴露到宿主机 node2 节点的 80 端口上),因为安装了 flannel ,master 节点能够和 node2 通过隧道 flannel.1 通信。所以在 kubernetes-master 上 curl pod 中的 nginx,流量请求为:
kubernetes-master --( flannel.1 )--> node2 --( containerPort: 80 )--> pod: nginx-demo
# 探针技术
容器内的监测机制,根据不同的探针判断容器当前的状态,K8S 提供了三种探针:存活探针、就绪探针、启动探针
- 启动探针(StartupProbe)
启动探针检查容器内的应用是否已启动。 启动探针可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被 kubelet 杀掉。
如果配置了这类探针,它会禁用存活探针和就绪探针,直到启动探针成功为止。
这类探针仅在启动时执行,不像存活探针和就绪探针那样周期性地运行。
- 就绪探针(ReadinessProbe)
就绪探针决定容器何时准备好接受流量。 这种探针在等待应用执行耗时的初始任务时非常有用; 例如:建立网络连接、加载文件和预热缓存。在容器的生命周期后期, 就绪探针也很有用,例如,从临时故障或过载中恢复时。
如果就绪探针返回的状态为失败,Kubernetes 会将该 Pod 从所有对应服务的端点中移除。
就绪探针在容器的整个生命期内持续运行。
- 存活探针(LivenessProbe)
存活探针决定何时重启容器。 例如,当应用在运行但无法取得进展时,存活探针可以捕获这类死锁。
如果一个容器的存活探针失败多次,kubelet 将重启该容器。
存活探针不会等待就绪探针成功。 如果你想在执行存活探针前等待,你可以定义 initialDelaySeconds ,或者使用启动探针。
探针的探测方式分为三种:
- ExecAction:执行某个命令,根据执行成功与否确认存活状态
- TCPSocketAction:TCP 端口探测,根据端口是否开放决定存活状态
- HTTPGetAction:HTTP 请求探测,根据返回状态码决定存活状态
# 启动探针
HTTPGetAction
基于上一章节创建 POD 的 yaml 文件,新增一个启动探针,使用 HTTP 的探测方式
# 在 spec.containers 下配置 startupProbe | |
[root@kubernetes-master ~]# vim nginx-demo.yaml | |
metadata: | |
name: nginx-demo-startup-probe # 修改 pod 名称,便于测试 | |
spec: | |
containers: | |
startupProbe: # 启动探针配置 | |
httpGet: # 探测方式,基于 http 请求探测 | |
path: /api/version # http 请求路径 | |
port: 80 # 请求端口 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 |
因为配置了探测 nginx 的 /api/version ,而实际上 nginx-demo 没有这个接口,所以探测会失败,现在运行容器查看状态、
startupProbe 探测路径 /api/version 是 Nginx 不存在的路径,返回 404,导致启动探针持续失败,容器反复重启后因 restartPolicy: OnFailure (仅失败时重启),最终容器正常退出(Exit Code 0)变为 Completed 状态;
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 54m | |
[root@kubernetes-master ~]# kubectl apply -f nginx-demo.yaml | |
pod/nginx-demo-startup-probe created | |
# 重新创建资源,新资源的 READY 为 0/1,RESTARTS 为 2,即重启了 2 次 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 59m | |
nginx-demo-startup-probe 0/1 Completed 2 5m7s | |
# 查看详情信息 | |
[root@kubernetes-master ~]# kubectl describe pod -n pod-example nginx-demo-startup-probe | |
......... | |
Events: | |
Type Reason Age From Message | |
---- ------ ---- ---- ------- | |
Normal Scheduled 83s default-scheduler Successfully assigned pod-example/nginx-demo-startup-probe to kubernetes-node1 | |
Normal Pulled 66s (x3 over 83s) kubelet Container image "docker.io/library/nginx:latest" already present on machine | |
Normal Created 66s (x3 over 83s) kubelet Created container nginx | |
Normal Started 66s (x3 over 83s) kubelet Started container nginx | |
Warning Unhealthy 57s (x9 over 81s) kubelet Startup probe failed: HTTP probe failed with statuscode: 404 # http 探测失败 | |
Normal Killing 57s (x3 over 75s) kubelet Container nginx failed startup probe, will be restarted # startup probe 探测失败,重启 pod | |
Warning BackOff 57s kubelet Back-off restarting failed container |
TCPSocketAction
修改 yaml 文件的启动探针资源配置,使用 TCP 的探测方式
# 删除上一轮测试中启动失败的容器 | |
[root@kubernetes-master ~]# kubectl delete pod -n pod-example nginx-demo-startup-probe | |
# 在 spec.containers 下配置 startupProbe | |
[root@kubernetes-master ~]# vim nginx-demo.yaml | |
metadata: | |
name: nginx-demo-startup-probe # 修改 pod 名称,便于测试 | |
spec: | |
containers: | |
startupProbe: # 启动探针配置 | |
tcpSocket: # 探测方式,基于 tcp 探测 | |
port: 80 # 请求端口 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 |
因为配置了探测 nginx 的 80 端口,nginx 启动后探测就会成功,现在运行容器查看状态
[root@kubernetes-master ~]# kubectl apply -f nginx-demo.yaml | |
pod/nginx-demo-startup-probe created | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 69m | |
nginx-demo-startup-probe 0/1 Running 0 2s | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 69m | |
nginx-demo-startup-probe 1/1 Running 0 30s |
ExecAction
修改 yaml 文件的启动探针资源配置,使用 exec 的探测方式,exec 会执行一个命令,判断探针命令的退出码(0 = 成功,非 0 = 失败)
# 删除上一轮测试中启动失败的容器 | |
[root@kubernetes-master ~]# kubectl delete pod -n pod-example nginx-demo-startup-probe | |
# 在 spec.containers 下配置 startupProbe | |
[root@kubernetes-master ~]# vim nginx-demo.yaml | |
metadata: | |
name: nginx-demo-startup-probe # 修改 pod 名称,便于测试 | |
spec: | |
containers: | |
startupProbe: # 启动探针配置 | |
exec: | |
command: | |
- sh | |
- -c | |
- 'echo "success Message!" > success.txt;' # 执行命令 sh -c 'echo "success message" > success.txt;' 命令正常退出则判断存活 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 |
现在查看容器状态,在容器工作目录下应该有新建的文件和内容
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 93m | |
nginx-demo-startup-probe 1/1 Running 0 5s | |
[root@kubernetes-master ~]# kubectl exec -it -n pod-example nginx-demo-startup-probe -- bash -c 'cat success.txt;' | |
success Message! |
# 存活探针
许多长时间运行的应用最终会进入损坏状态,除非重新启动,否则无法被恢复。 Kubernetes 提供了存活探针来发现并处理这种情况。
构建一个存活探针,每隔五秒探测一次
[root@kubernetes-master ~]# cat nginx-demo.yaml | |
apiVersion: v1 | |
name: nginx-demo-liveness-probe # Pod 名称 | |
spec: | |
containers: | |
- name: nginx # 容器名称 | |
startupProbe: # 启动探针配置 | |
exec: | |
command: | |
- sh | |
- -c | |
- 'echo "<h1>hello</h1>" > wellcome.html;' | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 | |
livenessProbe: # 存活探针配置 | |
httpGet: | |
path: /wellcome.html | |
port: 80 | |
initialDelaySeconds: 5 # 指定第一次探测前等待 5 秒 | |
periodSeconds: 5 # 每隔 5 秒进行一个探测 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 |
加载资源,查看 pod 状态,等待 pod 运行起来后,删除掉 pod 里的 wellcome.html 再查看 pod 状态
[root@kubernetes-master ~]# kubectl apply -f nginx-demo.yaml | |
pod/nginx-demo-liveness-probe created | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 109m | |
nginx-demo-liveness-probe 1/1 Running 0 5s | |
# 删除掉 pod 的 index.html,重启一次后,pod 依旧正常运行 | |
[root@kubernetes-master ~]# kubectl exec -it -n pod-example nginx-demo-liveness-probe -- bash -c "rm -f wellcome.html;" | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 111m | |
nginx-demo-liveness-probe 1/1 Running 1 (22s ago) 97s | |
# 存活探针(livenessProbe)检测失败触发容器重启,清空可写层,恢复镜像原始状态 | |
[root@kubernetes-master ~]# kubectl exec -it -n pod-example nginx-demo-liveness-probe -- bash -c "ls" | |
50x.html index.html wellcome.html |
# 就绪探针
就绪探针的配置和存活探针的配置相似。 唯一区别就是要使用 readinessProbe 字段,而不是 livenessProbe 字段。
HTTP 和 TCP 的就绪探针配置也和存活探针的配置完全相同。
就绪和存活探测可以在同一个容器上并行使用。 两者共同使用,可以确保流量不会发给还未就绪的容器,当这些探测失败时容器会被重新启动。
构建一个就绪探针,仅当 nginx 目录下存在 wellcome.html 时,认为服务就绪
[root@kubernetes-master ~]# cat nginx-demo.yaml | |
apiVersion: v1 | |
name: nginx-demo-readiness-probe | |
spec: | |
containers: | |
- name: nginx # 容器名称 | |
startupProbe: # 启动探针配置 | |
httpGet: | |
path: /index.html | |
port: 80 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 | |
livenessProbe: # 存活探针配置 | |
httpGet: | |
path: /index.html | |
port: 80 | |
initialDelaySeconds: 5 # 指定第一次探测前等待 5 秒 | |
periodSeconds: 5 # 每隔 5 秒进行一个探测 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # http 请求成功 1 次即判定为存活探测成功 | |
timeoutSeconds: 5 # 请求超时时间 | |
readinessProbe: # 就绪探针配置 | |
httpGet: | |
path: /wellcome.html | |
port: 80 | |
periodSeconds: 5 # 每隔 5 秒进行一个探测 | |
failureThreshold: 3 # 请求失败 3 次即判定为存活探测失败 | |
periodSeconds: 3 # 探测间隔时间 | |
successThreshold: 1 # http 请求成功 1 次即判定为探测成功 | |
timeoutSeconds: 5 # 请求超时时间 |
构建资源,查看资源状态,因为启动时目录下没有 wellcome.html,pod 不会被认为是 READY ,即不接受流量
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 131m | |
[root@kubernetes-master ~]# kubectl apply -f nginx-demo.yaml | |
pod/nginx-demo-readiness-probe created | |
# pod READY 为 0/1,未准备好 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 131m | |
nginx-demo-readiness-probe 0/1 Running 0 3s | |
# 查看 pod 信息,就绪探针探测失败 | |
[root@kubernetes-master ~]# kubectl describe pod nginx-demo-readiness-probe -n pod-example | |
......... | |
Events: | |
Type Reason Age From Message | |
---- ------ ---- ---- ------- | |
Normal Scheduled 28s default-scheduler Successfully assigned pod-example/nginx-demo-readiness-probe to kubernetes-node1 | |
Normal Pulled 28s kubelet Container image "docker.io/library/nginx:latest" already present on machine | |
Normal Created 28s kubelet Created container nginx | |
Normal Started 28s kubelet Started container nginx | |
Warning Unhealthy 1s (x10 over 25s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404 |
现在,将文件写入 pod 中,再查看 pod 状态
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 134m | |
nginx-demo-readiness-probe 0/1 Running 0 3m5s | |
[root@kubernetes-master ~]# kubectl exec -it -n pod-example nginx-demo-readiness-probe -- bash -c 'echo "<h1>wellcome</h1>" > wellcome.html;' | |
# 写入后,就绪探针成功探活,pod 变为 1/1 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 136m | |
nginx-demo-readiness-probe 1/1 Running 0 4m24s | |
# 查看日志信息 | |
[root@kubernetes-master ~]# kubectl logs -f nginx-demo-readiness-probe -n pod-example | |
......... | |
# 写入 wellcome.html 前 | |
2026/01/02 10:09:08 [error] 9#9: *177 open () "/usr/share/nginx/html/wellcome.html" failed (2: No such file or directory), client: 10.244.1.1, server: localhost, request: "GET /wellcome.html HTTP/1.1", host: "10.244.1.27:80" | |
# 写入 wellcome.html 后 | |
10.244.1.1 - - [02/Jan/2026:10:09:08 +0000] "GET /index.html HTTP/1.1" 200 615 "-" "kube-probe/1.23" "-" | |
10.244.1.1 - - [02/Jan/2026:10:09:08 +0000] "GET /wellcome.html HTTP/1.1" 404 153 "-" "kube-probe/1.23" "-" | |
10.244.1.1 - - [02/Jan/2026:10:09:11 +0000] "GET /wellcome.html HTTP/1.1" 200 18 "-" "kube-probe/1.23" "-" |
# 生命周期钩子
Kubernetes 中的 生命周期钩子(Lifecycle Hooks) 是在容器生命周期的特定阶段执行操作的机制。通过钩子,可以在容器启动后(PostStart)或停止前(PreStop)执行一些初始化或清理工作。
PostStart(启动后)
- 在容器启动后立即触发执行。
- 用于完成启动后的初始化操作,例如加载配置、启动辅助进程。
PreStop(停止前)
- 在容器收到终止信号(如
SIGTERM)时触发执行。 - 用于执行停止前的清理工作,例如保存状态、关闭连接、释放资源。
注意:postStart,不一定在容器的 command 之前运行,我们可以使用 init 容器去实现一些我们需要在容器运行前的初始化操作
钩子支持以下两种执行方式:
exec: 直接在容器内部运行指定命令。httpGet:通过 HTTP GET 请求调用一个端点
钩子定义的基本结构
lifecycle: | |
postStart: | |
exec: | |
command: ["sh", "-c", "echo 'Container started'"] | |
preStop: | |
exec: | |
command: ["sh", "-c", "echo 'Container stopping'; sleep 5"] |
参照如下示例
apiVersion: v1 | |
kind: Pod | |
metadata: | |
name: lifecycle-demo | |
spec: | |
containers: | |
- name: nginx | |
image: nginx:1.21.1 | |
lifecycle: | |
postStart: | |
exec: | |
command: | |
- "/bin/sh" | |
- "-c" | |
- | | |
echo "PostStart hook triggered! Initializing..."; | |
sleep 3 | |
preStop: | |
exec: | |
command: | |
- "/bin/sh" | |
- "-c" | |
- | | |
echo "PreStop hook triggered! Cleaning up..."; | |
sleep 5 | |
ports: | |
- containerPort: 80 | |
resources: | |
requests: | |
memory: "64Mi" | |
cpu: "250m" |
# 工作原理与解释
PostStart:
- 容器启动后,
PostStart钩子立即执行。 - 在示例中,
PostStart钩子通过echo输出一条日志并等待 3 秒。
PreStop:
- 当容器收到终止信号(如
kubectl delete pod或kubectl scale)时,PreStop钩子立即执行。 - 在示例中,
PreStop钩子输出一条日志并等待 5 秒,模拟资源清理。
# 注意事项
执行时间限制:
- 钩子执行时间受 Pod 的
terminationGracePeriodSeconds控制,默认宽限时间为 30 秒。如果PreStop未在宽限时间内完成,容器将被强制终止。
运行环境:
- 钩子在容器的文件系统和环境变量中运行,因此可以直接访问容器内部的资源。
错误处理:
- 如果钩子失败,容器不会因此失败,但会记录错误日志。
钩子与主进程的关系:
- 钩子运行与容器主进程无直接依赖关系。PostStart 并不阻塞主进程启动,而 PreStop 是在终止信号发送后触发。
# 资源调度
# 标签和选择器
在 Kubernetes 集群中,会运行大量不同类型的资源(Pod、Service、Deployment、Node 等)。随着集群规模扩大,如何高效分类、筛选、关联这些资源成为核心问题。标签(Label)和选择器(Selector)正是为解决这一问题而生的核心机制,具体价值体现在以下 3 点:
- 资源分类与筛选:通过标签为资源添加多维度的自定义标识(如环境、应用、版本、角色),再通过选择器快速筛选出目标资源。例如:区分开发环境(
env=dev)和生产环境(env=prod)的 Pod,筛选某个应用(app=nginx)的所有实例。 - 资源松耦合关联:实现不同资源之间的动态绑定,无需硬编码。例如:Service 通过选择器关联后端 Pod,无论 Pod 如何重启、扩容,只要标签匹配,Service 就能自动转发流量;Deployment 通过选择器管理对应的 Pod 副本。
- 灵活的资源管理:支持多标签组合筛选,满足复杂的资源管理需求。例如:筛选
app=nginx且version=1.29且env=prod的 Pod。
# 定义标签
标签是附在资源 metadata.labels 上的键值对,有两种定义方式:
- 在资源 yaml 文件中通过
metadata.labels定义,以 Pod 为例
apiVersion: v1 | |
kind: Pod | |
metadata: | |
name: nginx-demo | |
namespace: pod-example | |
labels: # 定义标签 | |
type: app # 资源类型:应用 | |
app: nginx # 应用名称:nginx | |
version: 1.29.4 # 应用版本:1.29.4 | |
env: test # 运行环境:测试 |
# 临时创建 Label
通过 kubectl label 创建临时 Label,格式为 kubectl label pod <podname> < 键 = 值 >
# 为 pod 新增一个临时 label | |
[root@kubernetes-master ~]# kubectl label pod nginx-demo-readiness-probe -n pod-example version=0.1 | |
pod/nginx-demo-readiness-probe labeled | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example --show-labels | |
NAME READY STATUS RESTARTS AGE LABELS | |
nginx-demo-readiness-probe 1/1 Running 0 114s version=0.1 |
# 修改 Label
在创建 label 的基础上,添加 --overwrite 可以修改临时 label 参数
# 修改 pod 的 label | |
[root@kubernetes-master ~]# kubectl label pod nginx-demo-readiness-probe -n pod-example version=0.2 --overwrite | |
pod/nginx-demo-readiness-probe labeled | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example --show-labels | |
NAME READY STATUS RESTARTS AGE LABELS | |
nginx-demo-readiness-probe 1/1 Running 0 5m34s test-label-name=test-label-value,type=app,version=0.2 |
# 查询标签
以 kubectl get pod 为例,使用 -l 参数即可查询指定标签的 pod
# 查看 pod 的标签 | |
[root@kubernetes-master ~]# kubectl get pod -n pod-example --show-labels | |
NAME READY STATUS RESTARTS AGE LABELS | |
nginx-demo 1/1 Running 0 5m29s type=app,version=1.0.0 | |
nginx-demo-2 1/1 Running 0 3m41s version=1.0.2 | |
# 指定查看 version 在 (1.0.0,1.0.2) 中的 pod | |
[root@kubernetes-master ~]# kubectl get pod -l 'version in (1.0.0,1.0.2)' -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 5m39s | |
nginx-demo-2 1/1 Running 0 3m51s | |
# 查看 version!=1.0.2 的 pod | |
[root@kubernetes-master ~]# kubectl get pod -l version!=1.0.2 -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 5m44s | |
# 查看 version!=1.0.2 且 type=app 的 pod | |
[root@kubernetes-master ~]# kubectl get pod -l 'version!=1.0.2,type=app' -n pod-example | |
NAME READY STATUS RESTARTS AGE | |
nginx-demo 1/1 Running 0 5m48s |
# 删除标签
在添加标签的语法上,键名后加 - 表示删除该标签: kubectl label pod < 节点名 > < 标签键 >-
# 删除标签 type=test | |
[root@kubernetes-master ~]# kubectl label pod nginx type- |
# Deployment
在前面的章节中,我们通过 YAML 直接创建 Pod,但这种方式创建的 Pod 是 “一次性资源”:Pod 异常退出后无法自动重建、无法批量扩缩容、无法平滑升级,管理成本极高。
Kubernetes 中,ReplicaSet(RS)是副本控制器,核心作用是保证指定数量的 Pod 副本始终运行(Pod 挂了自动重建、副本数不足自动补充);而 Deployment 是对 ReplicaSet 的更高层次编排控制器,在 RS 的基础上增加了滚动升级、版本回滚、扩缩容、暂停 / 恢复部署等核心能力,是管理无状态 Pod 的首选方式(有状态应用推荐使用 StatefulSet)。
层级关系本质:Deployment 不直接操作 Pod,而是通过控制 ReplicaSet 的 “版本” 实现对 Pod 的管理(比如升级 Deployment 时,会创建新的 RS,逐步缩容旧 RS、扩容新 RS,实现滚动升级);
ReplicaSet 的核心价值:仅负责 “维持 Pod 副本数”,无版本管理、滚动升级能力;
Deployment 的核心优势:相比直接管理 RS/Pod,支持一键扩缩容(
kubectl scale deploy nginx-deploy --replicas=3)、滚动升级(kubectl set image deploy nginx-deploy nginx=nginx:1.28)、版本回滚(kubectl rollout undo deploy nginx-deploy)等运维操作
Deployment、ReplicaSet、Pod 存在明确的层级关系:Deployment 管理 ReplicaSet(每个版本对应一个 RS),ReplicaSet 管控 Pod 的副本数,我们可通过实操验证这一层级
# 创建一个名为 nginx-deploy 的 Deployment,指定本地 nginx 镜像 | |
[root@kubernetes-master ~]# kubectl create deployment nginx-deploy --image=docker.io/library/nginx:latest | |
deployment.apps/nginx-deploy created | |
# 查看 Deployment(READY = 就绪副本数 / 期望副本数,UP-TO-DATE = 最新版本副本数,AVAILABLE = 可用副本数) | |
[root@kubernetes-master ~]# kubectl get deploy | |
NAME READY UP-TO-DATE AVAILABLE AGE | |
nginx-deploy 1/1 1 1 11s | |
# 查看 ReplicaSet(名称规则:Deployment 名称 - 随机哈希值,哈希由 Deployment 的配置生成,每个版本对应一个唯一 RS) | |
[root@kubernetes-master ~]# kubectl get replicaset | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 1 1 1 37s | |
# 查看 Pod(名称规则:ReplicaSet 名称 - 随机字符串,RS 会保证该 Pod 的副本数始终等于 DESIRED 值) | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
nginx-deploy-64b476c78b-dqjxp 1/1 Running 0 41s | |
# 查看 pod,rs,deployment 的标签信息 | |
[root@kubernetes-master ~]# kubectl get pod,rs,deploy --show-labels | |
NAME READY STATUS RESTARTS AGE LABELS | |
pod/nginx-deploy-64b476c78b-dqjxp 1/1 Running 0 25m app=nginx-deploy,pod-template-hash=64b476c78b | |
NAME DESIRED CURRENT READY AGE LABELS | |
replicaset.apps/nginx-deploy-64b476c78b 1 1 1 25m app=nginx-deploy,pod-template-hash=64b476c78b | |
NAME READY UP-TO-DATE AVAILABLE AGE LABELS | |
deployment.apps/nginx-deploy 1/1 1 1 25m app=nginx-deploy |
# 创建 Deployment
我们可以通过一个简单方式获取 deployment 的 yaml 资源文件
# 创建一个名为 nginx-deploy 的 Deployment,指定本地 nginx 镜像 | |
[root@kubernetes-master ~]# kubectl create deployment nginx-deploy --image=docker.io/library/nginx:latest | |
deployment.apps/nginx-deploy created | |
# 将名为 nginx-deploy 的 Deployment 以 yaml 的形式输出 | |
[root@kubernetes-master ~]# kubectl get deploy nginx-deploy -o yaml > nginx-deploy.yaml |
之后,通过编辑 nginx-deploy.yaml ,删除掉 status 之后的内容,再删除掉一些不必要的信息,我们可以获得一个简单的 deployment
apiVersion: apps/v1 # deployment api 版本 | |
kind: Deployment # 资源类型 | |
metadata: # 元信息 | |
labels: # deployment 标签 | |
app: nginx-deploy | |
name: nginx-deploy | |
namespace: default # 命名空间 | |
spec: | |
replicas: 1 # 期望副本数 | |
revisionHistoryLimit: 10 # 进行滚动更新,保留的历史版本数 | |
selector: # 选择器,用于找到匹配的 rs | |
matchLabels: # 按照标签匹配,需要包含 pod 标签,用于匹配 pod | |
app: nginx-deploy # 匹配的标签 key/value | |
strategy: # 更新策略 | |
rollingUpdate: # 滚动更新配置 | |
maxSurge: 25% # 滚动更新时,可同时启最多 25%的实例 | |
maxUnavailable: 25% # 滚动更新时,可同时停止最多 25%的实例 | |
type: RollingUpdate # 更新类型:滚动更新 | |
template: # pod 模板 | |
metadata: # pod 元信息 | |
labels: # pod 标签 | |
app: nginx-deploy | |
spec: # pod 期望信息 | |
containers: # pod 容器 | |
- image: docker.io/library/nginx:latest # 镜像 | |
imagePullPolicy: IfNotPresent # 拉取策略 | |
name: nginx # 容器名称 | |
restartPolicy: Always # 重启策略 | |
terminationGracePeriodSeconds: 30 # 删除操作最多宽限时长(优雅退出时间) |
如果想对某个已经创建的 deployment 修改资源配置文件,也可以使用 edit
[root@kubernetes-master ~]# kubectl edit deploy nginx-deploy |
现在,通过这个 yaml 可以创建一个简单的 deployment
[root@kubernetes-master ~]# kubectl apply -f nginx-deploy.yaml |
# 水平扩展 / 收缩
水平扩展 / 收缩非常容易实现, Deployment 只需要修改它所控制的 ReplicaSet 的 Pod 副本个数就可以了。比如,把这个值从 1 改成 3,那么 Deployment 所对应的 ReplicaSet ,就会根据修改后的值自动创建两个新的 Pod ,"水平收缩" 则反之。这个操作的指令也非常简单,我们可以直接通过修改 deployment 资源文件或者 kubectl scale 命令实现
# 修改 deployment 实现水平扩展,修改 spec.replicasc 参数 | |
[root@kubernetes-master ~]# kubectl edit deploy nginx-deploy | |
spec: | |
replicas: 1 # 副本数 | |
# 使用 scale 命令修改副本数 | |
[root@kubernetes-master ~]# kubectl scale deployment nginx-deploy --replicas=4 |
如果你手快点还能通过 kubectl rollout status deployment <deployment_name> 看到扩展过程中 Deployment 对象的状态变化:
[root@kubernetes-master ~]# kubectl rollout status deploy nginx-deploy | |
Waiting for deployment "nginx-deploy" rollout to finish: 2 of 4 updated replicas are available... | |
Waiting for deployment "nginx-deploy" rollout to finish: 3 of 4 updated replicas are available... | |
deployment "nginx-deploy" successfully rolled out | |
# ReplicaSet 的 Name 没有发生变化 | |
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 4 4 4 69m |
这证明了 Deployment 水平扩展和收缩副本集是不会创建新的 ReplicaSet 的,但是涉及到 Pod 模板的更新后,比如更改容器的镜像,那么 Deployment 会用创建一个新版本的 ReplicaSet 用来替换旧版本。
# 滚动更新
当修改 deployment 资源文件中 template 的属性时,会触发更新操作
在上面的 Deployment 定义里,Pod 模板里的容器镜像设置的是,如果项目代码更新了,打包了新的镜像 ,部署新镜像的过程就会触发 Deployment 的滚动更新。
有两种方式更新镜像,一种是更新 deployment.yaml 里的镜像名称,然后执行 kubectl apply -f deployment.yaml 。一般公司里的 Jenkins 等持续继承工具用的就是这种方式。还有一种就是使用 kubectl set image 命令。
# 修改 image: docker.io/library/nginx:1.28 | |
[root@kubernetes-master ~]# kubectl edit deploy nginx-deploy | |
deployment.apps/nginx-deploy edited | |
# 直接设置镜像:kubectl set image deployment/<Deployment 名称 > < 容器名 >=< 新镜像地址 > [--record] | |
[root@kubernetes-master ~]# kubectl set image deployment/nginx-deploy nginx=1.9.1 |
执行滚动更新后通过命令行查看 ReplicaSet 的状态会发现 Deployment 用新版本的 ReplicaSet 对象替换旧版本对象的过程。
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 3 3 3 76m | |
nginx-deploy-6cbf9498b8 2 2 0 4s | |
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 0 0 0 79m | |
nginx-deploy-6cbf9498b8 4 4 4 2m41s |
通过这个 Deployment 的 Events 可以查看到这次滚动更新的详细过程:
[root@kubernetes-master ~]# kubectl describe deploy nginx-deploy | |
......... | |
Events: | |
Type Reason Age From Message | |
---- ------ ---- ---- ------- | |
Normal ScalingReplicaSet 13m (x2 over 25m) deployment-controller Scaled up replica set nginx-deploy-64b476c78b to 4 | |
Normal ScalingReplicaSet 4m32s deployment-controller Scaled up replica set nginx-deploy-6cbf9498b8 to 1 | |
Normal ScalingReplicaSet 4m32s deployment-controller Scaled down replica set nginx-deploy-64b476c78b to 3 | |
Normal ScalingReplicaSet 4m32s deployment-controller Scaled up replica set nginx-deploy-6cbf9498b8 to 2 | |
Normal ScalingReplicaSet 3m47s (x2 over 13m) deployment-controller Scaled down replica set nginx-deploy-64b476c78b to 1 | |
Normal ScalingReplicaSet 3m47s deployment-controller Scaled down replica set nginx-deploy-64b476c78b to 2 | |
Normal ScalingReplicaSet 3m47s deployment-controller Scaled up replica set nginx-deploy-6cbf9498b8 to 3 | |
Normal ScalingReplicaSet 3m47s deployment-controller Scaled up replica set nginx-deploy-6cbf9498b8 to 4 | |
Normal ScalingReplicaSet 3m45s deployment-controller Scaled down replica set nginx-deploy-64b476c78b to 0 |
当你修改了 Deployment 里的 Pod 定义之后, Deployment 会使用这个修改后的 Pod 模板,创建一个新的 ReplicaSet ,这个新的 ReplicaSet 的初始 Pod 副本数是:0。然后 Deployment 开始将这个新的 ReplicaSet 所控制的 Pod 副本数从 0 个变成 1 个,即:"水平扩展" 出一个副本。紧接着 Deployment 又将旧的 ReplicaSet 所控制的旧 Pod 副本数减少一个,即:"水平收缩" 成 3 个副本。如此交替进行就完成了这一组 Pod 的版本升级过程。像这样,将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是 "滚动更新"。
为了保证服务的连续性, Deployment 还会确保,在任何时间窗口内,只有指定比例的 Pod 处于离线状态。同时,它也会确保,在任何时间窗口内,只有指定比例的新 Pod 被创建出来。这两个比例的值都是可以配置的,默认都是期望状态里 spec.relicas 值的 25%。所以,在上面这个 Deployment 的例子中,它有 4 个 Pod 副本,那么控制器在 “滚动更新” 的过程中永远都会确保至少有 2 个 Pod 处于可用状态,至多只有 2 个 Pod 同时存在于集群中。这个策略可以通过 Deployment 对象的一个字段,RollingUpdateStrategy 来设置。
# 设置注解
--record=true 是 kubectl 命令的可选参数,这个参数能让 Kubernetes 在这个 Deployment 的变更记录里记录上产生变更当时执行的命令。 --record 只对修改 Deployment 状态的命令生效(如 scale 、 set image 、 apply 改配置),纯查询命令(如 get 、 describe )加 --record 无意义。
执行 kubectl rollout history deployment <deploy_name> 就能看到这个 Deployment 的更新记录:
[root@kubernetes-master ~]# kubectl rollout history deploy nginx-deploy | |
deployment.apps/nginx-deploy | |
REVISION CHANGE-CAUSE | |
1 <none> | |
2 <none> |
废弃说明: --record 在 kubectl 1.14+ 版本后被标记为 “废弃(deprecated)”(但仍可正常使用),K8s 官方推荐手动添加注解替代(效果一致):
执行 kubectl annotate 给 deploy 添加的 change-cause 注解仅会作用于后续新建的 Revision
# 手动添加变更原因注解 | |
kubectl annotate deployment nginx-deploy kubectl.kubernetes.io/change-cause="更新版本到 1.28" | |
# 如果需要修改,则添加 `--overwrite` 字段进行强制修改 | |
kubectl annotate deployment nginx-deploy kubectl.kubernetes.io/change-cause="更新版本到 1.28" --overwrite | |
# 进行滚动更新 | |
kubectl set deployment/nginx-deploy nginx=nginx:1.28 |
# 回滚
执行 kubectl rollout history deployment <deployment_name> 就能看到这个 Deployment 的更新记录,如果设置了注解,则在 CHANGE-CAUSE 能看到对应的注解
[root@kubernetes-master ~]# kubectl rollout history deploy nginx-deploy deployment.apps/nginx-deploy | |
REVISION CHANGE-CAUSE | |
1 <none> | |
2 <none> |
kubectl rollout history deploy <deployment_name> --revision=1 就能看到对应 revision 的详细信息
[root@kubernetes-master ~]# kubectl rollout history deploy nginx-deploy --revision=1 | |
deployment.apps/nginx-deploy with revision #1 | |
Pod Template: | |
Labels: app=nginx-deploy | |
pod-template-hash=64b476c78b | |
Containers: | |
nginx: | |
Image: docker.io/library/nginx:latest | |
Port: <none> | |
Host Port: <none> | |
Environment: <none> | |
Mounts: <none> | |
Volumes: <none> | |
[root@kubernetes-master ~]# kubectl rollout history deploy nginx-deploy --revision=2 | |
deployment.apps/nginx-deploy with revision #2 | |
Pod Template: | |
Labels: app=nginx-deploy | |
pod-template-hash=6cbf9498b8 | |
Containers: | |
nginx: | |
Image: docker.io/library/nginx:1.28 | |
Port: <none> | |
Host Port: <none> | |
Environment: <none> | |
Mounts: <none> | |
Volumes: <none> |
kubectl rollout undo deployment <deployment_name> --to-revision=1 命令实现 Deployment 对象的版本回滚。
# 回滚到 REVISION 1 | |
[root@kubernetes-master ~]# kubectl rollout undo deployment nginx-deploy --to-revision=1 | |
deployment.apps/nginx-deploy rolled back |
执行完后我们会发现:以前那个版本的 ReplicaSet 的 Pod 的数又变回 4,新 ReplicaSet 的 Pod 数变成了 0。
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 4 4 4 121m | |
nginx-deploy-6cbf9498b8 0 0 0 44m | |
[root@kubernetes-master ~]# |
证明 Deployment 在上次滚动更新后并不会把旧版本的 ReplicaSet 删掉,而是留着回滚的时候用,所以 ReplicaSet 相当于一个基础设施层面的应用的版本管理。
回滚后在看变更记录,发现已经没有修订号 1 的内容了,而是多了修订号为 3 的内容,这个版本的变更内容其实就是回滚前修订号 1 里的变更内容。
[root@kubernetes-master ~]# kubectl rollout history deployment nginx-deploy | |
deployment.apps/nginx-deploy | |
REVISION CHANGE-CAUSE | |
2 <none> | |
3 <none> |
# 控制 ReplicaSet 版本数
你可能已经想到了一个问题:我们对 Deployment 进行的每一次更新操作,都会生成一个新的 ReplicaSet 对象,是不是有些多余,甚至浪费资源?所以,Kubernetes 项目还提供了一个指令,使得我们对 Deployment 的多次更新操作,最后只生成一个 ReplicaSet 对象。
在更新 Deployment 前,你要先执行一条 kubectl rollout pause deployment <deployment_name> 指令,这个命令会让 Deployment 进入了一个 "暂停" 状态。由于此时 Deployment 正处于 “暂停” 状态,所以我们对 Deployment 的所有修改,都不会触发新的 “滚动更新”,也不会创建新的 ReplicaSet 。
而等到我们对 Deployment 修改操作都完成之后,只需要再执行一条 kubectl rollout resume deployment <deployment_name> 指令,就可以把这个 它恢复回来。
# 暂停 deployment 更新 | |
[root@kubernetes-master ~]# kubectl rollout pause deploy nginx-deploy | |
deployment.apps/nginx-deploy paused | |
# 设置注解 | |
[root@kubernetes-master ~]# kubectl annotate deployment nginx-deploy kubectl.kubernetes.io/change-cause="更新版本到 1.9.1" --overwrite | |
deployment.apps/nginx-deploy annotated | |
# 修改 deployment 镜像版本,此时不会更新 | |
[root@kubernetes-master ~]# kubectl set image deployment/nginx-deploy nginx=1.9.1 | |
deployment.apps/nginx-deploy image updated | |
# 查看 rs 状态,没有更新行为 | |
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 4 4 4 150m | |
nginx-deploy-6cbf9498b8 0 0 0 74m | |
# 启动 deployment 更新 | |
[root@kubernetes-master ~]# kubectl rollout resume deployment nginx-deploy | |
deployment.apps/nginx-deploy resumed | |
# 再次查看 rs 状态,正在进行更新 | |
[root@kubernetes-master ~]# kubectl get rs | |
NAME DESIRED CURRENT READY AGE | |
nginx-deploy-64b476c78b 3 3 3 153m | |
nginx-deploy-6cbf9498b8 0 0 0 76m | |
nginx-deploy-74978448d5 2 2 0 7s | |
# 再次查看历史版本信息 | |
[root@kubernetes-master ~]# kubectl rollout history deployment nginx-deploy | |
deployment.apps/nginx-deploy | |
REVISION CHANGE-CAUSE | |
2 <none> | |
3 <none> | |
4 <none> |
随着应用版本的不断增加,
Kubernetes会为同一个Deployment保存很多不同的ReplicaSet。Deployment对象中spec.revisionHistoryLimit指定了Kubernetes为Deployment保留的 "历史版本" 个数。如果把它设置为 0,就再也不能做回滚操作了。
# StatefulSet
StatefulSet 是用来管理有状态应用的工作负载 API 对象。,StatefulSet 运行一组 Pod,并为每个 Pod 保留一个稳定的标识。 这可用于管理需要持久化存储或稳定、唯一网络标识的应用。
和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
# 创建 StatefulSet
创建一个 StatefulSet 简单的如下所示
# ------------- 第一部分:Headless Service 定义 ------------- | |
--- # YAML 分隔符:用于在单个文件中定义多个 Kubernetes 资源 | |
apiVersion: v1 # 指定 Kubernetes API 版本,Service 资源使用 v1 版本 | |
kind: Service # 定义资源类型为 Service(服务),用于暴露 Pod 网络 | |
metadata: # 元数据部分:描述 Service 的基本信息 | |
name: nginx # Service 的名称,需与后续 StatefulSet 的 serviceName 字段匹配 | |
labels: # 给 Service 打标签,用于资源筛选和关联 | |
app: nginx | |
spec: # Service 的规约 | |
ports: # 定义 Service 暴露的端口列表 | |
- port: 80 # Service 对外暴露的端口号 | |
name: web # 端口名称,用于标识(需符合 DNS 规范) | |
clusterIP: None # 设置 clusterIP 为 None,创建 Headless Service(无头服务),无头服务不会分配集群 IP,kube-dns 会为每个 Pod 解析出独立的 DNS 记录 | |
selector: # 标签选择器:匹配带有 app=nginx 标签的 Pod,将 Service 流量转发到这些 Pod | |
app: nginx | |
--- | |
# ------------- 第二部分:StatefulSet 定义 ------------- | |
apiVersion: apps/v1 # StatefulSet 属于 apps/v1 API 组(K8s 1.9 + 稳定版) | |
kind: StatefulSet # 定义资源类型为 StatefulSet(有状态集合),用于管理有状态应用 | |
metadata: # 元数据部分:描述 StatefulSet 的基本信息 | |
name: web # StatefulSet 的名称 | |
spec: | |
selector: # 标签选择器:匹配符合条件的 Pod,用于管理 StatefulSet 下的 Pod | |
matchLabels: | |
app: nginx # 必须与.spec.template.metadata.labels 中的标签完全匹配,否则无法管理 Pod | |
serviceName: "nginx" # 关联的 Headless Service 名称,StatefulSet 依赖此 Service 为 Pod 生成固定 DNS 名称 | |
replicas: 3 # 副本数 | |
minReadySeconds: 10 # 最小就绪秒数:Pod 启动后至少就绪 10 秒,才被视为可用 | |
template: # Pod 模板 | |
metadata: | |
labels: # Pod 的标签,必须与.spec.selector.matchLabels 匹配 | |
app: nginx | |
spec: | |
terminationGracePeriodSeconds: 10 # 终止宽限期:发送终止信号后,等待 10 秒再强制杀死容器 | |
containers: # 容器列表:定义 Pod 内运行的容器 | |
- name: nginx | |
image: registry.k8s.io/nginx-slim:0.24 | |
ports: # 声明容器监听的端口 | |
- containerPort: 80 | |
name: web | |
volumeMounts: # 卷挂载配置:将 PVC 挂载到容器内指定路径 | |
- name: www # 卷名称,需与 volumeClaimTemplates.metadata.name 匹配 | |
mountPath: /usr/share/nginx/html # 容器内挂载路径 | |
volumeClaimTemplates: # PVC 模板:为每个 StatefulSet Pod 自动创建 PVC | |
- metadata: | |
name: www # PVC 名称,与 volumeMounts.name 匹配 | |
spec: | |
accessModes: [ "ReadWriteOnce" ] # 访问模式:ReadWriteOnce 表示只能被单个节点挂载为读写模式 | |
storageClassName: "my-storage-class" # 指定存储类名称:需提前创建对应的 StorageClass | |
resources: # 资源请求:声明需要的存储资源大小 | |
requests: | |
storage: 1Gi # 请求 1GiB 的存储容量 |
因为 StatefulSet 涉及到持久化存储,还需要进行存储配置,创建存储类并手动创建 3 个 PV(对应副本数为 pod 副本数 3)
创建 storageclass.yaml 文件:
apiVersion: storage.k8s.io/v1 | |
kind: StorageClass | |
metadata: | |
name: my-storage-class # 与 StatefulSet 中 storageClassName 完全一致 | |
provisioner: kubernetes.io/no-provisioner # 手动创建 PV,不依赖动态供给 | |
volumeBindingMode: WaitForFirstConsumer # 延迟绑定,适配测试环境 | |
reclaimPolicy: Delete # 删除 PVC 时自动删除 PV(测试环境方便清理) |
[root@kubernetes-master ~]# vim storageclass.yaml | |
[root@kubernetes-master ~]# kubectl apply -f storageclass.yaml | |
storageclass.storage.k8s.io/my-storage-class created | |
[root@kubernetes-master ~]# kubectl get sc | |
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE | |
my-storage-class kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 25s |
手动创建 3 个 PV,在所有节点上创建对应目录后应用 PV
# PV1:供 web-0 Pod 使用 | |
apiVersion: v1 | |
kind: PersistentVolume | |
metadata: | |
name: pv-nginx-0 # 自定义名称,便于识别 | |
spec: | |
capacity: | |
storage: 1Gi # 匹配 PVC 的 1Gi 请求 | |
accessModes: | |
- ReadWriteOnce # 匹配 PVC 的访问模式 | |
persistentVolumeReclaimPolicy: Delete # 测试环境删除 PVC 自动删 PV | |
storageClassName: my-storage-class # 关联上面创建的存储类 | |
hostPath: # 测试环境用主机目录(最简单,缺点:Pod 调度到对应节点才可用) | |
path: /data/nginx/pv-0 # 节点上的目录,需提前创建 | |
type: DirectoryOrCreate | |
# PV2:供 web-1 Pod 使用 | |
--- | |
apiVersion: v1 | |
kind: PersistentVolume | |
metadata: | |
name: pv-nginx-1 | |
spec: | |
capacity: | |
storage: 1Gi | |
accessModes: | |
- ReadWriteOnce | |
persistentVolumeReclaimPolicy: Delete | |
storageClassName: my-storage-class | |
hostPath: | |
path: /data/nginx/pv-1 | |
type: DirectoryOrCreate | |
# PV3:供 web-2 Pod 使用 | |
--- | |
apiVersion: v1 | |
kind: PersistentVolume | |
metadata: | |
name: pv-nginx-2 | |
spec: | |
capacity: | |
storage: 1Gi | |
accessModes: | |
- ReadWriteOnce | |
persistentVolumeReclaimPolicy: Delete | |
storageClassName: my-storage-class | |
hostPath: | |
path: /data/nginx/pv-2 | |
type: DirectoryOrCreate |
[root@kubernetes-master ~]# vim pv.yaml | |
# 手动在三台节点上都创建目录并修改权限 | |
[root@kubernetes-master ~]# mkdir -p /data/nginx/pv-0 | |
[root@kubernetes-master ~]# mkdir -p /data/nginx/pv-1 | |
[root@kubernetes-master ~]# mkdir -p /data/nginx/pv-2 | |
[root@kubernetes-master ~]# chmod 777 /data/nginx/* | |
# 创建三个 PV | |
[root@kubernetes-master ~]# kubectl apply -f pv.yaml | |
persistentvolume/pv-nginx-0 created | |
persistentvolume/pv-nginx-1 created | |
persistentvolume/pv-nginx-2 created | |
# 查看 PV 状态 | |
[root@kubernetes-master ~]# kubectl get pv | |
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE | |
pv-nginx-0 1Gi RWO Delete Available my-storage-class 9s | |
pv-nginx-1 1Gi RWO Delete Available my-storage-class 9s | |
pv-nginx-2 1Gi RWO Delete Available my-storage-class 9s |
PV 创建完后,现在可以创建 StatefulSet
[root@kubernetes-master ~]# kubectl apply -f statfulset-nginx.yaml | |
service/nginx-de created | |
statefulset.apps/web created | |
# 验证创建状态 | |
# kubectl get statefulset 可以简写为 kubectl get sts | |
[root@kubernetes-master ~]# kubectl get statefulset | |
NAME READY AGE | |
web 3/3 79s | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
web-0 1/1 Running 0 90s | |
web-1 1/1 Running 0 60s | |
web-2 1/1 Running 0 40s | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d21h # K8S 自带,与本示例无关 | |
nginx-de ClusterIP None <none> 80/TCP 102s | |
[root@kubernetes-master ~]# kubectl get statefulset | |
NAME READY AGE | |
web 3/3 119s | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
web-0 1/1 Running 0 2m5s | |
web-1 1/1 Running 0 95s | |
web-2 1/1 Running 0 75s | |
[root@kubernetes-master ~]# kubectl get pvc | |
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE | |
www-web-0 Bound pv-nginx-0 1Gi RWO my-storage-class 2m10s | |
www-web-1 Bound pv-nginx-2 1Gi RWO my-storage-class 100s | |
www-web-2 Bound pv-nginx-1 1Gi RWO my-storage-class 80s |
# 水平扩展 / 收缩
StatefulSet 的水平扩缩容也只需要修改副本数即可,与 deployments 一样,有两种修改办法
与 deployments 不同的是, StatefulSet 的扩容和缩容有顺序性,可以理解成栈结构(后进先出)例如将副本扩展到 5 个,再缩容到 2 个(首个 pod 序号 0),扩容时依次创建 1、2、3、4,缩容时依次删除 4、3、2
# scale 参数扩容 | |
kubectl scale sts <StatefulSet_name> --replicas=2 | |
# path 参数修改副本数 | |
kubectl patch sts <StatefulSet_name> -p '{"spec":{"replicas":3}}' |
将上述示例资源缩容至 1,再扩容至 3
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 3/3 27m | |
# 缩容至 1 | |
[root@kubernetes-master ~]# kubectl scale sts web --replicas=1 | |
statefulset.apps/web scaled | |
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 1/1 30m | |
# 扩容至 3 | |
[root@kubernetes-master ~]# kubectl scale sts web --replicas=3 | |
statefulset.apps/web scaled | |
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 3/3 33m | |
# 查看 sts 信息 | |
[root@kubernetes-master ~]# kubectl describe sts web | |
Events: | |
Type Reason Age From Message | |
---- ------ ---- ---- ------- | |
Normal SuccessfulDelete 6m6s statefulset-controller delete Pod web-2 in StatefulSet web successful | |
Normal SuccessfulDelete 6m6s statefulset-controller delete Pod web-1 in StatefulSet web successful | |
Normal SuccessfulCreate 108s (x2 over 33m) statefulset-controller create Pod web-1 in StatefulSet web successful | |
Normal SuccessfulCreate 88s (x2 over 33m) statefulset-controller create Pod web-2 in StatefulSet web successful |
# 滚动更新
与 deployment 相同,滚动更新(以更新镜像为例)也有多种方法
- 使用
set image参数更新 image - 使用
path参数实现镜像更新 - 通过
edit修改资源 yaml 实现更新
以下用更新 sts 名为 web 的 nginx 镜像为例
$ kubectl get sts | |
NAME READY AGE | |
web 3/3 59m | |
# set 更新镜像 | |
kubectl set image sts web nginx=docker.io/library/nginx:latest | |
# path 更新镜像 | |
kubectl path sts web --type='json' -p='[{"op": "replace","path": "/spec/template/spec/containers/0/image", "value": "docker.io/library/nginx:latest"}]' | |
# edit 更新镜像 | |
kubectl edit sts web | |
spec: | |
template: | |
spec: | |
containers: | |
- name: nginx | |
image: docker.io/library/nginx:latest |
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 3/3 64m | |
# 查看 pod 镜像 | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:1.28 | |
web-2: docker.io/library/nginx:1.28 | |
# 滚动更新 | |
[root@kubernetes-master ~]# kubectl set image sts web nginx=docker.io/library/nginx:latest | |
statefulset.apps/web image updated | |
# 更新验证 | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:1.28 | |
web-2: docker.io/library/nginx:latest | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:latest | |
web-2: docker.io/library/nginx:latest | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:latest | |
web-1: docker.io/library/nginx:latest | |
web-2: docker.io/library/nginx:latest |
# 更新策略
StatefulSet 更新策略有两种
- 默认:
RollingUpdate(滚动更新),按 Pod 序号倒序更新(web-2 → web-1 → web-0),确保服务不中断 OnDelete(需手动删除 Pod 才会重建为新镜像)。
编辑 StatefulSet 资源文件,可以看到具体的策略配置
spec: | |
updateStrategy: | |
rollingUpdate: | |
partition: 0 | |
type: RollingUpdate |
由于创建 pod 是顺序创建的,在 RollingUpdate 模式下,更新 pod 会按 Pod 倒序更新(web-2 → web-1 → web-0),确保服务不中断,这也是默认的更新策略
如果改用 OnDelete 模式,则表示仅当删除 pod 的时候,才更新 pod
# 设置更新策略 | |
[root@kubernetes-master ~]# kubectl edit sts web | |
updateStrategy: | |
type: OnDelete | |
# 滚动更新,此时 pod 不会更新 | |
[root@kubernetes-master ~]# kubectl set image sts web nginx=docker.io/library/nginx:latest | |
statefulset.apps/web image updated | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:1.28 | |
web-2: docker.io/library/nginx:1.28 | |
# 删掉一个 pod,再查看,被删掉的 pod 更新了 | |
[root@kubernetes-master ~]# kubectl delete pod web-1 | |
pod "web-1" deleted | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:latest | |
web-2: docker.io/library/nginx:1.28 |
# 灰度发布
灰度发布(Grayscale Release),又称金丝雀发布,是一种通过逐步扩大用户覆盖范围实现平滑过渡的软件发布方式。其核心机制为将新功能先向部分用户开放,通过 A/B 测试收集反馈并调整问题,最终完成全量发布。该方式可降低系统升级风险,保障稳定性并完善产品功能。
利用滚动更新中的 partition 属性,可以实现灰度发布的效果
注:在实际生产环境中,并不会通过修改
partition属性做灰度发布,现在都是通过istio ingress来做灰度发布
例如此时有 5 个 pod,如果当前的 partition 设置为 3,则滚动更新时只会更新序号 >= 3 的 pod
也就是说,通过控制 partition 值,可以决定只更新其中一部分 pod,待到更新完成确认新 pod 没有问题后再逐渐增大更新的 pod 数量,最终实现全部的 pod 更新
# 将 web 的 partition 值设置为 2 | |
[root@kubernetes-master ~]# kubectl edit sts web | |
spec: | |
updateStrategy: | |
rollingUpdate: | |
partition: 0 | |
type: RollingUpdate | |
# 再进行滚动更新 | |
[root@kubernetes-master ~]# kubectl set image sts web nginx=docker.io/library/nginx:latest | |
# 查看 pod 镜像版本,此时只有序号 >=2 的 pod 被更新 | |
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 3/3 85m | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:1.28 | |
web-2: docker.io/library/nginx:latest | |
# 再修改 partition 为 1,查看 pod | |
[root@kubernetes-master ~]# kubectl get pods -l app=nginx -o jsonpath='{range .items [*]}{.metadata.name}: {.spec.containers [0].image}{"\n"}{end}' | |
web-0: docker.io/library/nginx:1.28 | |
web-1: docker.io/library/nginx:latest | |
web-2: docker.io/library/nginx:latest |
# 回滚
若更新后出问题,可回滚到上一版本:
kubectl rollout undo sts web # 回滚到上一版本 | |
kubectl rollout history sts web # 查看更新历史 |
# 删除
StatefulSet 删除时,默认仅删除 STS 控制器本身,不会自动删除其管理的 Pod 和 PVC(这是 K8s 保护有状态应用数据的设计)
级联删除
删除掉 StatefulSet 时默认删除掉对应的 pod( --cascade=foreground ),这就叫做级联删除,如果不想在删掉 sts 时删掉 pod(非级联删除),可通过 kubectl delete sts web --cascade=orphan 实现。
仅删除 STS
如果仅需要删除 STS 控制器,保留 pod/pvc
# 删除名为 web 的 StatefulSet(仅删控制器,Pod/PVC 保留) | |
kubectl delete sts web --cascade=orphan | |
# 验证结果: | |
[root@kubernetes-master ~]# kubectl get sts | |
No resources found in default namespace. | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
web-0 1/1 Running 0 43m | |
web-1 1/1 Running 0 18m | |
web-2 1/1 Running 0 21m |
如果想让保留的 pod 被新的 STS 接管,可以 apply 以前的 sts yaml,重新创建 STS
[root@kubernetes-master ~]# kubectl apply -f statfulset-nginx.yaml | |
service/nginx-de unchanged | |
statefulset.apps/web created | |
[root@kubernetes-master ~]# kubectl get sts | |
NAME READY AGE | |
web 3/3 9s | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
web-0 1/1 Running 0 46m | |
web-1 1/1 Running 0 11s | |
web-2 1/1 Running 0 23m |
彻底删除
如果需要彻底删除 STS 和所有关联的资源(STS + Pod + PVC + PV + Service),需要对创建的资源逐一删除
# 1. 删除 sts,默认删除 pod | |
[root@kubernetes-master ~]# kubectl delete sts web | |
statefulset.apps "web" deleted | |
[root@kubernetes-master ~]# kubectl get pod | |
No resources found in default namespace. | |
# 2. 删除 Headless Service,示例中为 nginx-de | |
[root@kubernetes-master ~]# kubectl delete svc nginx-de | |
service "nginx-de" deleted | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d23h | |
# 3. 删除 PVC(存储声明),需手动删除 | |
[root@kubernetes-master ~]# kubectl delete pvc www-web-o www-web-1 www-web-2 | |
persistentvolumeclaim "www-web-0" deleted | |
persistentvolumeclaim "www-web-1" deleted | |
persistentvolumeclaim "www-web-2" deleted | |
[root@kubernetes-master ~]# kubectl get pvc | |
No resources found in default namespace. | |
# 4. 删除 PV(持久化卷),需要手动删除 | |
[root@kubernetes-master ~]# kubectl delete pv pv-nginx-0 pv-nginx-1 pv-nginx-2 | |
persistentvolume "pv-nginx-0" deleted | |
persistentvolume "pv-nginx-1" deleted | |
persistentvolume "pv-nginx-2" deleted | |
[root@kubernetes-master ~]# kubectl get pv | |
No resources found | |
# 5. 删除 StorageClass | |
[root@kubernetes-master ~]# kubectl delete sc my-storage-class | |
storageclass.storage.k8s.io "my-storage-class" deleted | |
[root@kubernetes-master ~]# kubectl get sc | |
No resources found | |
# 6. 清理各节点存储目录(/data/nginx/pv-0/1/2) | |
rm -rf /data/nginx/pv-* |
# DaemonSet
如果我们需要采集多个微服务 pod 下的日志或者监控 pod 信息,就需要在每个 pod 创建时再部署一个副本用于收集并集中 pod 信息,而手动部署显然不可能,因此我们需要一个自动在创建 pod 时就部署副本的组件,这就是 DaemonSet 所解决的问题
DaemonSet 可以保证集群中所有的或者部分的节点都能够运行一份 Pod 副本,每当有新的节点被加入到集群时,Pod 就会在目标的节点上启动,如果节点被从集群中剔除,节点上的 Pod 也会被垃圾收集器清除;DaemonSet 的作用就像是计算机中的守护进程,它能够运行集群存储、日志收集和监控等 “守护进程”,这些服务一般是集群中必备的基础服务。
使用 DaemonSet 的一些典型用法:
- 运行集群存储 daemon(守护进程),例如在每个节点上运行 Glusterd、Ceph 等;
- 在每个节点运行日志收集 daemon,例如 Fluentd、Logstash;
- 在每个节点运行监控 daemon,比如 Prometheus Node Exporter、Collectd、Datadog 代理、New Relic 代理或 Ganglia gmond;
一个简单的用法是,在所有的 Node 上都存在一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是,对单独的每种类型的 daemon 使用多个 DaemonSet,但具有不同的标志,和 / 或对不同硬件类型具有不同的内存、CPU 要求。
# 创建 DaemonSet
基于 DaemonSet 部署一个 Fluentd-Elasticsearch Pod , Fluentd-Elasticsearch 是 Kubernetes 集群中核心的日志采集与转发组件,核心作用是统一采集集群所有节点的系统日志( /var/log )和容器日志(K8S 默认将节点容器的日志写入到节点 /var/lib/docker/containers 目录下),并将日志标准化后转发到 Elasticsearch 存储 / 检索。
DaemonSet 可以确保每个 node 在加入节点时都创建一个 Fluentd-Elasticsearch Pod ,节点被剔除时销毁该 pod。
apiVersion: apps/v1 | |
kind: DaemonSet # 资源类型 | |
metadata: # 元数据 | |
name: fluentd-elasticsearch | |
spec: | |
selector: # 标签选择器,匹配要管理的 pod | |
matchLabels: # 匹配标签 | |
name: fluentd-elasticsearch | |
template: # pod 模板,定义要在每个 node 上创建的 pod 模板 | |
metadata: | |
labels: # 标签,需要与上方标签选择器定义一致,这样 pod 才能被管理到 | |
name: fluentd-elasticsearch | |
spec: # pod 规格 | |
tolerations: # 容忍度(允许 Pod 调度到有指定污点的节点) | |
- key: node-role.kubernetes.io/master # 匹配的污点键:master 节点默认的污点(禁止普通 Pod 调度) | |
effect: NoSchedule # 容忍的污点效果:NoSchedule(允许调度到该节点) | |
containers: # pod 内的容器列表,该示例仅有一个容器 | |
- name: fluentd-elasticsearch | |
image: agilestacks/fluentd-elasticsearch:v1.3.0 | |
volumeMounts: # 容器内的卷挂载 | |
- name: varlog # 要挂载的卷名称 | |
mountPath: /var/log # 将卷挂载到容器中的该路径 | |
- name: varlibdockercontainers | |
mountPath: /var/lib/docker/containers | |
readOnly: true # 只读挂载,防止容器篡改宿主机文件 | |
terminationGracePeriodSeconds: 30 # 优雅终止时间 | |
volumes: # pod 定义卷,此为宿主机路径挂载 | |
- name: varlog # 名称,与 volumeMounts 中名称对应 | |
hostPath: # 宿主机卷路径,该路径卷挂载到 pod 内 | |
path: /var/log | |
- name: varlibdockercontainers | |
hostPath: | |
path: /var/lib/docker/containers |
现在, apply 资源文件,查看 ds ( DaemonSet ),确认在三个 node 节点上都创建了 fluentd-elasticsearch pod
[root@kubernetes-master ~]# kubectl get ds | |
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE | |
fluentd-elasticsearch 3 3 3 3 3 <none> 71s | |
[root@kubernetes-master ~]# kubectl get pod -o wide | |
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES | |
fluentd-elasticsearch-9lcqt 1/1 Running 0 77s 10.244.2.15 kubernetes-node2 <none> <none> | |
fluentd-elasticsearch-dh99v 1/1 Running 0 77s 10.244.1.65 kubernetes-node1 <none> <none> | |
fluentd-elasticsearch-mlmsx 1/1 Running 0 77s 10.244.0.3 kubernetes-master <none> <none> |
# 指定 Node 节点
在如上示例中, DaemonSet 将会作用于所有的 node,如果我们只需要 DaemonSet 作用在某一些 node 上,则需要指定 Node 节点
DaemonSet 有三种指定 Node 节点的方式:
nodeSelector:只调度到匹配指定 label 的 Node 上nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作podAffinity:调度到满足条件的 Pod 所在的 Node 上
# nodeSelector
nodeSelector 模式只调度到匹配指定 label 的 Node 上,只需要给 node 打上标签,然后在 DaemonSet 中设置匹配的标签即可
spec: | |
template: | |
spec: | |
nodeSelector: # nodeSelector 节点选择器 | |
test: service # 匹配该标签 |
先给 node 打上标签,然后创建 DaemonSet 资源,此时 DaemonSet 只对匹配到标签的 node 生效
[root@kubernetes-master ~]# kubectl label node kubernetes-node1 test=service | |
node/kubernetes-node1 labeled | |
[root@kubernetes-master ~]# kubectl apply -f daemonset.yaml | |
daemonset.apps/fluentd-elasticsearch created | |
[root@kubernetes-master ~]# | |
[root@kubernetes-master ~]# kubectl get ds | |
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE | |
fluentd-elasticsearch 1 1 1 1 1 test=service 5s | |
[root@kubernetes-master ~]# kubectl get pod -o wide | |
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES | |
fluentd-elasticsearch-4zlkk 1/1 Running 0 11s 10.244.1.66 kubernetes-node1 <none> <none> |
# 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 |
# 滚动更新
如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod, 同时删除不匹配的节点上的 Pod。
你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。 下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。
您可以删除一个 DaemonSet。如果使用 kubectl 并指定 --cascade=false 选项, 则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet, 新的 DaemonSet 会收养已有的 Pod。 如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy 来替换。
DaemonSet 有两种更新策略:
- OnDelete: 使用 OnDelete 更新策略时,在更新 DaemonSet 模板后,只有当你手动删除老的 DaemonSet pods 之后,新的 DaemonSet Pod 才会被自动创建。跟 Kubernetes 1.6 以前的版本类似。
- RollingUpdate: 这是默认的更新策略。使用 RollingUpdate 更新策略时,在更新 DaemonSet 模板后, 老的 DaemonSet pods 将被终止,并且将以受控方式自动创建新的 DaemonSet pods。 更新期间,最多只能有 DaemonSet 的一个 Pod 运行于每个节点上。
与 statefulset 中介绍的更新策略相同,默认为 RollingUpdate 策略,如果想设置为策略 OnDelete ,则需要配置 .spec.updateStrategy.type 为 RollingUpdate 。
实际上,建议使用
OnDelete的更新模式,因为滚动更新会更新所有的 pod,考虑到资源损耗,采用OnDelete模式只将某些需要更新的 pod 删除掉。
# HPA
HPA 的全称为 Horizontal Pod Autoscaling ,它可以根据当前 pod 资源的使用率(如 CPU、磁盘、内存等),进行副本数的动态的扩容与缩容,以便减轻各个 pod 的压力。当 pod 负载达到一定的阈值后,会根据扩缩容的策略生成更多新的 pod 来分担压力,当 pod 的使用比较空闲时,在稳定空闲一段时间后,还会自动减少 pod 的副本数量。
- HPA:基于 CPU、内存或自定义指标触发,扩展 Pod 副本数(横向扩展),适用于业务流量波动(如电商促销)的场景。
- VPA:基于指标触发(资源不足),扩展 Pod 资源配额(纵向扩展),适用于单 Pod 性能瓶颈(如内存不够)。
HPA 本身不收集指标,需依赖外部组件提供 “指标数据”,核心依赖是 Metrics Server(K8s 官方的基础指标采集组件)。
# HPA 工作原理
HPA 基于集群中运行的应用程序资源使用情况动态调整 Pod 副本的数量。HPA 的工作原理可以概括为以下几个步骤:
采集指标
HPA 定期(默认 15 秒,可通过--horizontal-pod-autoscaler-sync-period调整)从 Metrics Server 或自定义指标源(如 Prometheus)获取指标,支持 3 类指标:- 资源指标(Resource Metrics):K8s 内置的 CPU / 内存,基于 Pod 请求(Request)计算使用率(如 “CPU 使用率 50%”= 实际使用 / Request);
- 容器资源指标(Container Resource Metrics):针对单个容器的 CPU / 内存(而非整个 Pod);
- 自定义指标(Custom Metrics):业务指标(如 QPS、请求延迟、并发用户数),需依赖 Prometheus + k8s-prometheus-adapter 等组件。
计算期望副本数
HPA 基于 “当前指标值” 和 “目标指标值”,通过公式计算期望副本数:
期望副本数 = ceil (当前副本数 × (当前指标值 / 目标指标值))如果当前副本数 = 2,目标 CPU 使用率 = 50%,实际 CPU 使用率 = 100%,期望副本数 = 2 × (100%/50%)=4 ,则自动扩容到 4 个副本。
关键约束(避免极端情况):
最小副本数(minReplicas):扩缩容的下限(如至少 2 个副本,避免缩到 0);
最大副本数(maxReplicas):扩缩容的上限(如最多 10 个副本,避免资源耗尽);
容忍阈值:指标波动在 ±10% 内(默认),不触发扩缩容(避免频繁调整);
冷却时间:扩容后需等待 3 分钟(默认)才能再次扩容,缩容后需等待 5 分钟(默认)才能再次缩容(避免 “震荡”)。执行扩缩容
HPA 通过 K8s API 调用 Deployment/StatefulSet 的
scale接口,修改其replicas字段,实现 Pod 副本数的自动调整。
# 部署 Metrics Server
Metrics Server 是 K8s 官方的基础指标采集组件,先确认集群中是否有该服务,如果没有,则参照如下方式部署
# 查看 Metrics Server pod | |
kubectl get pods -n kube-system | grep metrics-server |
- 下载官方部署文件(适配 K8s 1.24+,低版本需调整):
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml |
- 编辑
components.yaml,在spec.containers.args中添加--kubelet-insecure-tls
(跳过 Kubelet 证书验证,测试环境用;生产环境需配置证书)
spec: | |
containers: | |
- name: metrics-server | |
image: k8s.gcr.io/metrics-server/metrics-server:v0.6.4 # 替换为最新版本 | |
args: | |
- --cert-dir=/tmp | |
- --secure-port=4443 | |
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname | |
- --kubelet-use-node-status-port | |
- --kubelet-insecure-tls # 新增这行(测试环境) |
- 部署并验证
# 部署 | |
kubectl apply -f components.yaml | |
# 检查 Pod 是否运行(命名空间 kube-system) | |
kubectl get pods -n kube-system | grep metrics-server | |
# 验证指标是否能获取(查看 Node/Pod 指标) | |
kubectl top node # 查看节点 CPU / 内存使用 | |
kubectl top pod # 查看 Pod CPU / 内存使用 |
# 创建 HPA
# 创建 deployment 资源
在创建 HPA 前,我们先构建一个简单的 deployment 文件 nginx-deployment.yaml ,其中必须配置 requests 用于限制资源大小
apiVersion: apps/v1 | |
kind: Deployment | |
metadata: | |
name: nginx-deploy | |
spec: | |
replicas: 2 # 初始副本数 | |
selector: | |
matchLabels: | |
app: nginx | |
template: | |
metadata: | |
labels: | |
app: nginx | |
spec: | |
containers: | |
- name: nginx | |
image: nginx:1.21 | |
resources: # HPA 依赖 Request 计算使用率,必须配置。 | |
requests: # 容器启动时保障资源(最小资源) | |
cpu: 100m # 100 毫核(0.1 CPU) | |
memory: 128Mi | |
limits: # 容器最大上限资源 | |
cpu: 200m | |
memory: 256Mi | |
--- | |
# 可选:创建 Service(方便外部访问压测) | |
apiVersion: v1 | |
kind: Service | |
metadata: | |
name: nginx-svc | |
spec: | |
selector: | |
app: nginx | |
ports: | |
- port: 80 | |
targetPort: 80 | |
type: NodePort # 测试环境用 NodePort 暴露服务 |
部署该资源
[root@kubernetes-master ~]# kubectl apply -f nginx-deployment.yaml | |
deployment.apps/nginx-deploy created | |
service/nginx-svc created | |
[root@kubernetes-master ~]# kubectl get deploy | |
NAME READY UP-TO-DATE AVAILABLE AGE | |
nginx-deploy 2/2 2 2 47s | |
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
nginx-deploy-8559b958fd-4fzn4 1/1 Running 0 67s | |
nginx-deploy-8559b958fd-l4svf 1/1 Running 0 67s | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d23h | |
nginx-svc NodePort 10.105.200.61 <none> 80:31213/TCP 89s |
# 创建 HAP 资源
通过 yaml 创建 HPA 并配置基于 CPU 使用率的扩缩容规则
apiVersion: autoscaling/v2 # 注意版本:v2 支持更多指标(v1 仅支持 CPU) | |
kind: HorizontalPodAutoscaler | |
metadata: | |
name: nginx-hpa | |
spec: | |
scaleTargetRef: # 关联的 Deployment(HPA 要控制的目标) | |
apiVersion: apps/v1 | |
kind: Deployment | |
name: nginx-deploy | |
minReplicas: 2 # 最小副本数(避免缩到 0) | |
maxReplicas: 10 # 最大副本数(避免资源耗尽) | |
metrics: # 扩缩容指标配置(这里用 CPU 使用率) | |
- type: Resource | |
resource: | |
name: cpu | |
target: | |
type: Utilization # 基于 “使用率”(而非绝对值) | |
averageUtilization: 50 # 目标 CPU 使用率:50% |
创建 HPA 并验证
[root@kubernetes-master ~]# kubectl apply -f nginx-hpa.yaml | |
horizontalpodautoscaler.autoscaling/nginx-hpa created | |
# 等待一段时间,hpa 完成指标检查后,<unknown > 变为具体数值 | |
[root@kubernetes-master ~]# kubectl get hpa nginx-hpa -o wide | |
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | |
nginx-hpa Deployment/nginx-deploy <unknown>/50% 2 10 0 14s | |
[root@kubernetes-master ~]# kubectl get hpa nginx-hpa -o wide | |
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | |
nginx-hpa Deployment/nginx-deploy 0%/50% 2 10 2 98s |
# 测试 HPA 扩缩容
通过 busybox 容器向 Nginx 发送大量请求,触发 CPU 使用率升高,观察 HPA 是否自动扩容。
# 获取 Nginx Service 的 ClusterIP | |
[root@kubernetes-master ~]# echo $(kubectl get svc nginx-svc -o jsonpath='{.spec.clusterIP}') | |
10.105.200.61 | |
# 启动一个交互式 busybox 容器 | |
[root@kubernetes-master ~]# kubectl run -it --rm load-generator --image=busybox /bin/sh | |
If you don't see a command prompt, try pressing enter. | |
# 执行压测(用 wget 循环请求 Nginx,消耗 CPU) | |
/ # while true; do wget -q -O /dev/null 10.105.200.61; done |
打开一个新的窗口,每隔 10 秒查看 HPA 状态,观察 HPA 变化
watch kubectl get hpa nginx-hpa -o wide |
预期结果:随着压测进行,TARGETS 中的 “当前 CPU 使用率” 会从 5% 上升到 50% 以上,HPA 检测到指标超标后,会逐步将 REPLICAS 从 2 扩容到 4、6…(直到 CPU 使用率降至 50% 左右)
Every 2.0s: kubectl get hpa nginx-hpa -o wide Thu Jan 8 01:42:27 2026 | |
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | |
nginx-hpa Deployment/nginx-deploy 51%/50% 2 10 3 13m |
停止负载,验证 HPA 缩容
在 busybox 容器内按 Ctrl+C 停止循环请求,然后 exit 退出容器(容器会自动删除)
继续用 watch 查看 HPA 状态:CPU 使用率逐步下降到 50% 以下,等待 5 分钟(默认缩容冷却时间)后,HPA 会将副本数逐步缩回到 2(minReplicas), TARGETS 回到 50% 以下。
[root@kubernetes-master ~]# kubectl get hpa nginx-hpa -o wide | |
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | |
nginx-hpa Deployment/nginx-deploy 0%/50% 2 10 3 19m | |
# 等待默认缩容时间后,hpa 缩容 | |
[root@kubernetes-master ~]# kubectl get hpa nginx-hpa -o wide | |
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE | |
nginx-hpa Deployment/nginx-deploy 0%/50% 2 10 2 22m |
# 自定义 HPA 行为
默认的扩缩容规则(如冷却时间、扩容速度)可能不满足生产需求,可通过 behavior 字段自定义 HPA 行为。
apiVersion: autoscaling/v2 | |
kind: HorizontalPodAutoscaler | |
metadata: | |
name: nginx-hpa-advanced | |
spec: | |
scaleTargetRef: | |
apiVersion: apps/v1 | |
kind: Deployment | |
name: nginx-deploy | |
minReplicas: 2 | |
maxReplicas: 10 | |
metrics: | |
- type: Resource | |
resource: | |
name: cpu | |
target: | |
type: Utilization | |
averageUtilization: 50 | |
# 自定义扩缩容行为 | |
behavior: | |
scaleUp: # 扩容策略 | |
stabilizationWindowSeconds: 60 # 扩容前等待 60 秒(确认指标稳定) | |
policies: | |
- type: Percent # 按百分比扩容 | |
value: 50 # 每次扩容 50% 的副本数(如当前 2 个,扩到 3 个) | |
periodSeconds: 60 # 每 60 秒最多扩容一次 | |
scaleDown: # 缩容策略 | |
stabilizationWindowSeconds: 300 # 缩容前等待 300 秒(避免误缩容) | |
policies: | |
- type: Fixed # 按固定数量缩容 | |
value: 1 # 每次缩容 1 个副本 | |
periodSeconds: 120 # 每 120 秒最多缩容一次 |
# 服务发现
# Service
# Endpoint 和 Service
Endpoints 是 K8s 的独立核心资源对象(和 Service 平级),属于纯数据存储型对象,没有任何转发和调度能力;
只存储
PodIP: 容器端口的键值对列表,除此之外无任何多余信息;作为
Service和Pod之间的中间桥梁,实现解耦,因此Service不用关心Pod的动态变化,只需要读取Endpoints即可拿到最新的后端地址;Pod的变化由Endpoints控制器同步到Endpoints对象中。
Service 是 K8s 的核心资源,为一组功能相同的 Pod 提供固定、统一的访问地址;
Service创建后会分配一个集群内唯一的虚拟 IP(ClusterIP)+ 固定的Service端口,这个ClusterIP在Service生命周期内永不改变;- 集群内的客户端(其他 Pod、节点)只需访问
ClusterIP:Service 端口,就可以访问到后端的业务服务,无需关心后端 Pod 的 IP 变化、实例数量变化; Service自身不存储任何 Pod 信息、不处理任何流量转发、不直接对接 Pod,它的唯一依赖就是同名的Endpoints对象。
# 查看 service,简写为 svc | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8d | |
nginx-svc NodePort 10.105.200.61 <none> 80:31213/TCP 2d23h | |
# 查看 endpoint,简写为 ep | |
[root@kubernetes-master ~]# kubectl get ep | |
NAME ENDPOINTS AGE | |
kubernetes 192.168.0.200:6443 8d | |
nginx-svc 10.244.1.75:80,10.244.2.20:80 2d21h | |
# 查看 pod | |
[root@kubernetes-master ~]# kubectl get pod -o wide | |
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES | |
nginx-deploy-8559b958fd-4fzn4 1/1 Running 1 (21m ago) 2d21h 10.244.2.20 kubernetes-node2 <none> <none> | |
nginx-deploy-8559b958fd-l4svf 1/1 Running 1 (21m ago) 2d21h 10.244.1.75 kubernetes-node1 <none> <none> |
pod、endpoint、service 之间的绑定规则
Service 与其对应的 Endpoints 对象,必须是同名且同命名空间,跨命名空间不会关联。
pod 需要满足两个条件才会被 Endpoints 控制器收录到 Endpoints 的 IP 清单中:Pod 标签与 Service 的
spec.selector标签选择器匹配、Pod 为就绪状态。如果 Pod 的就绪探针失败(比如服务启动中、服务异常),该 Pod 会被立即从 Endpoints 中剔除,流量不会转发到这个 Pod 上。Service 创建 / 删除则同名 Endpoints 自动创建 / 删除;Service 修改标签选择器则 Endpoints 自动更新 Pod 清单;Pod 扩缩容 / 重建则 Endpoints 自动增删 PodIP;
Service 工作原理
- 创建 Service 对象,API Server 持久化到 etcd
通过 kubectl create service / YAML 文件 / 编程调用 K8s API,向 API Server 提交 Service 创建请求,Service 的配置(包含标签选择器、端口映射等)会被持久化到 etcd 中。
- Endpoints 控制器通过
Informer感知Service变更
K8s 的 Endpoints 控制器(属于 kube-controller-manager 的一个核心控制器,运行在 master 节点)内部集成了 Informer 机制:
- Informer 会实时监听 API Server 中 Service 对象的变更事件(创建、修改、删除),并将 Service 的配置缓存到本地;
- 作用:Informer 是 K8s 的本地缓存和事件监听核心组件,避免控制器直接频繁访问
etcd,减少etcd的压力,同时提升控制器的响应速度。
- Endpoints 控制器构建并维护 Endpoints 对象
Endpoints 控制器读取 Service 的 spec.selector 标签选择器;遍历集群中同命名空间的所有 Pod,筛选出标签匹配的健康 Pod;基于这些健康 Pod,自动创建 / 更新同名的 Endpoints 对象,将这些 Pod 的 PodIP + 容器端口 写入 Endpoints;Endpoints 对象被持久化到 etcd 中,并且控制器会持续监听 Pod 的状态变化并更新 Endpoints 中的 Pod 清单。
Endpoints 控制器的核心职责只有一个:维护 Service 和 Pod 的关联关系,保证 Endpoints 的准确性。
- kube-proxy 监听 Service/Endpoints 变更,配置节点转发规则
Service 本身不转发任何流量,真正做流量转发的是 kube-proxy,kube-proxy 运行在每一个 Node 节点上的 DaemonSet(每个节点必有一个 kube-proxy 实例)通过 Informer 机制,实时监听集群中所有 Service 和 Endpoints 的变更事件;当监听到 Service/Endpoints 变化时,kube-proxy 会在当前 Node 节点上,自动配置 iptables 或 IPVS 规则(K8s 默认 iptables,高并发场景推荐 IPVS);将访问 Service 的 ClusterIP: 端口的流量,转发到 Endpoints 中的任意一个 PodIP: 容器端口上,并实现轮询 / 随机的负载均衡。
Service 的 ClusterIP 是虚拟 IP(VIP),它不是一个真实的网卡 IP,不绑定任何节点的网卡,也不会被任何进程监听,ClusterIP 的存在仅作为流量转发的目标标识,由 kube-proxy 维护其转发规则。
- 集群内的客户端访问
ClusterIP:Service 端口,请求流量会经过以下链路到达 Pod:
客户端的请求到达 Node 节点后,会被该节点的 iptables/IPVS 规则拦截;
规则根据负载均衡策略(默认轮询),随机选择一个 Endpoints 中的 PodIP: 容器端口;
请求流量被转发到选中的 Pod 上,Pod 的容器处理请求并返回响应;
响应流量原路返回给客户端,整个请求链路完成。

# 创建 Service
在创建 service 之前,还是先 [创建 Deployment](### 创建 Deployment),再参照如下示例创建一个 Service,当然,Service 也可以写在其他的资源文件中,参考 [HPA 中的 Deployment](### 创建 HPA)
NodePort 是在 基础的 ClusterIP Service 之上的功能扩展,当该 service 被创建时,K8S 自动为这个 Service 分配两个访问入口
分配一个固定的
ClusterIP(虚拟 IP),集群内的 Pod 可以通过ClusterIP:80访问服务(和普通 ClusterIP Service 一样);自动分配一个 NodePort 端口(端口号范围:
30000~32767),集群外部可以通过任意节点的 IP:NodePort 端口访问服务(比如192.168.1.100:30080)。
核心逻辑:外部请求 → 节点 IP:NodePort → 转发到 Service 的 ClusterIP:80 → 再转发到 Pod:80
apiVersion: v1 | |
kind: Service # 资源类型为 service | |
metadata: | |
name: nginx-svc # service 名称 | |
labels: # service 自身的标签 | |
app: service | |
namespace: default # 命名空间,非必须 | |
spec: | |
selector: # 标签选择器,匹配如下标签的 pod | |
app: nginx | |
ports: # 端口映射,监听 port,转发到 targetPort | |
- port: 80 # service 自身监听端口 | |
targetPort: 8080 # 将监听转发到目标 pod 端口 | |
name: web # 端口名称,非必须 | |
protocol: TCP # 声明协议,非必须 | |
type: NodePort # 用 NodePort 暴露服务,端口直接绑定在 node 上,集群每个 node 都会绑定该端口,测试可以该类型,生产不推荐 |
现在,我们创建一个 service 服务并测试其功能
[root@kubernetes-master ~]# kubectl apply -f service.yaml | |
service/nginx-svc created | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d | |
nginx-svc NodePort 10.110.84.92 <none> 8080:32239/TCP 8s | |
[root@kubernetes-master ~]# kubectl get svc -o wide | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE SELECTOR | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d <none> | |
nginx-svc NodePort 10.110.84.92 <none> 8080:32239/TCP 11s app=nginx | |
[root@kubernetes-master ~]# kubectl get ep | |
NAME ENDPOINTS AGE | |
kubernetes 192.168.0.200:6443 9d | |
nginx-svc 10.244.2.22:80 2m44s |
# Service 类型
service 的类型是一个至下而上的,逐级升高的类型,service 有如下类型:
clusterIP类型,主要是解决 pod 之间,负载均衡的问题,参考 [基于 service 访问外部服务](### 基于 service 访问外部服务) 小节,配置了多个 IP 进行轮询NodePort类型,主要是解决将外部访问内部服务的问题,参考 [创建 Service](### 创建 Service) 小节,实现外部访问 podLoadBalancer类型,主要就是在NodePort的基础上,实现 Node 之间的负载均衡ExternalName(DNS 别名服务),主要是解决内部访问外部服务的问题,K8S 1.7 及以上支持,参考 [反向代理外部域名](### 反向代理外部域名) 小节,使内部 pod 访问外部域名比如说 pod 访问 db,如果你的 pod 很多,修改这个 db 指向就比较麻烦。ExternalName 就可以类型 DNS 的方式,生成一个域名,帮你指向 DB,这样后面你只需要修改域名指向 DB 的 IP,就能实现快速修改
# 基于 service 访问内部服务
service 创建后,外部访问可直接通过访问 节点 IP:NodePort ,内部访问可使用 ClusterIP:Clusterport ,如果是 pod 之间进行访问,还能通过 servicename:Clusterport 的形式,例如 curl http://nginx-svc:8080
K8s 集群中会默认部署 coredns/kube-dns 组件(集群的 DNS 服务器),所有 Pod 启动时,都会自动配置这个 DNS 服务器的地址(写入 Pod 的 /etc/resolv.conf 文件)。这个 DNS 服务支持用 Service 的名称直接解析出对应的 ClusterIP,规则如下:
同命名空间:直接写
Service 名称即可解析 → 比如你在 Pod 里输入nginx-svc,DNS 会自动解析出它的 ClusterIP10.110.84.92;跨命名空间:写
Service 名称。命名空间.svc.cluster.local→ 比如nginx-svc.default.svc.cluster.local。
为了验证,我们创建两个 deployment,通过标记不可调度的方式,将两个 pod 创建在两个 node 上,且两个 pod 命名空间不同
# pod1 位于 ns1,node1 上,pod2 位于 ns2,node2 上 | |
[root@kubernetes-master ~]# kubectl get pod -A -o wide | |
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES | |
ns1 nginx-deploy-v1-8679b4bd58-lgljw 1/1 Running 0 7m15s app=nginx-v1,pod-template-hash=8679b4bd58 | |
ns2 nginx-deploy-v2-5c7fb8bb7-vzxfw 1/1 Running 0 12m app=nginx-v2,pod-template-hash=5c7fb8bb7 |
创建两个 service,分别匹配两个 node
[root@kubernetes-master ~]# kubectl get all -n ns1 | |
NAME READY STATUS RESTARTS AGE | |
pod/nginx-deploy-v1-8679b4bd58-lgljw 1/1 Running 0 21m | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
service/nginx-svc-v1 NodePort 10.96.129.123 <none> 8081:31416/TCP 11m | |
NAME READY UP-TO-DATE AVAILABLE AGE | |
deployment.apps/nginx-deploy-v1 1/1 1 1 27m | |
NAME DESIRED CURRENT READY AGE | |
replicaset.apps/nginx-deploy-v1-8679b4bd58 1 1 1 27m | |
[root@kubernetes-master ~]# kubectl get all -n ns2 | |
NAME READY STATUS RESTARTS AGE | |
pod/nginx-deploy-v2-5c7fb8bb7-vzxfw 1/1 Running 0 27m | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
service/nginx-svc-v2 NodePort 10.101.19.227 <none> 8082:32382/TCP 7s | |
NAME READY UP-TO-DATE AVAILABLE AGE | |
deployment.apps/nginx-deploy-v2 1/1 1 1 27m | |
NAME DESIRED CURRENT READY AGE | |
replicaset.apps/nginx-deploy-v2-5c7fb8bb7 1 1 1 27m |
现在,进入到 nginx-svc-v1 这个 pod 中,去访问 nginx-svc-v2
[root@kubernetes-master ~]# kubectl exec -it nginx-deploy-v1-8679b4bd58-lgljw -n ns1 -- bash | |
# 使用 ClusterIP:Clusterport 访问 | |
root@nginx-deploy-v1-8679b4bd58-lgljw:/# curl http://10.101.19.227:8082 | |
<!DOCTYPE html> | |
<html> | |
......... | |
# 使用 Service 名称,跨 ns 访问 | |
root@nginx-deploy-v1-8679b4bd58-lgljw:/# curl http://nginx-svc-v2.ns2.svc.cluster.local:8082 | |
<!DOCTYPE html> | |
<html> | |
......... |
# 基于 service 访问外部服务
有时候,pod 内部也需要访问外部服务,如果外部服务出现变动,则需要在每个 pod 中都修改访问地址,这样显然会麻烦,我们可是用 service 实现外部访问,通过 pod 访问 service,再由 service 访问到外部环境,后续修改只需要维护 service 即可
例如,内部多个 pod 需要访问外部服务:
与访问内部的设置不同在于:service 资源文件中,不指定 selector 属性,此时 service 不会自动创建 endpoint,需要自己单独创建 endpoint
# 创建 service 资源文件 | |
[root@kubernetes-master ~]# cat service.yaml | |
apiVersion: v1 | |
kind: Service # 资源类型为 service | |
metadata: | |
name: nginx-svc-external # service 名称 | |
labels: # service 自身的标签 | |
app: service-external | |
spec: | |
ports: # 端口映射,监听 port,转发到 targetPort | |
- port: 8080 # service 自身监听端口 | |
targetPort: 80 # 将监听转发到目标端口 | |
name: out-web # 端口名称 | |
protocol: TCP # 声明协议,非必须 | |
type: ClusterIP | |
# 创建 endpoint 资源文件 | |
[root@kubernetes-master ~]# cat nginx-ep-external.yaml | |
apiVersion: v1 | |
kind: Endpoints | |
metadata: | |
labels: # ep 自身标签,可以与 svc 不一致 | |
app: service-ep-external | |
name: nginx-svc-external # 必须与 service 一致 | |
namespace: default # 必须与 service 一致 | |
subsets: | |
- addresses: | |
- ip: 149.28.121.93 # 目标 IP 地址,即 pod 访问 svc,会转发到该外部 IP 地址,例如希望 pod 访问 www.bilibili.com,此处填写域名的对应 IP | |
- ip: 221.178.63.11 # subsets.addresses 是一个数组,可填写多个 IP 实现负载均衡,此时 Pod 访问 Service 时,请求会被轮询转发。 | |
ports: # 与 service 一致 | |
- name: out-web # 与 service 一致 | |
port: 80 | |
protocol: TCP # 与 service 一致 |
现在,创建对应的资源文件
# 创建 service 资源,查看状态,此时如果 get ep,会发现没有自动创建 ep | |
[root@kubernetes-master ~]# kubectl apply -f service.yaml | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d | |
nginx-svc-external ClusterIP 10.96.124.187 <none> 8080/TCP 23m | |
# 创建 endpoint | |
[root@kubernetes-master ~]# kubectl apply -f nginx-ep-external.yaml | |
[root@kubernetes-master ~]# kubectl get ep | |
NAME ENDPOINTS AGE | |
kubernetes 192.168.0.200:6443 9d | |
nginx-svc-external 149.28.121.93:80 4s |
验证 svc 与 ep 是否绑定,通过 kubectl describe svc XXXX 查看输出中的 Endpoints 是否有实际内容
[root@kubernetes-master ~]# kubectl describe svc nginx-svc-external | |
Name: nginx-svc-external | |
Namespace: default | |
Labels: app=service-external | |
Annotations: <none> | |
Selector: <none> | |
Type: ClusterIP | |
IP Family Policy: SingleStack | |
IP Families: IPv4 | |
IP: 10.96.124.187 | |
IPs: 10.96.124.187 | |
Port: out-web 8080/TCP | |
TargetPort: 80/TCP | |
Endpoints: 120.78.159.117:80 | |
Session Affinity: None | |
Events: <none> |
测试外部访问,pod 内访问 svc 名称和端口(ClusterIP:Clusterport),endpoint 会将请求转发到 yaml 中配置的 subsets.addresses.ip
[root@kubernetes-master ~]# kubectl run -it --rm load-generator --image=busybox /bin/sh | |
If you don't see a command prompt, try pressing enter. | |
/ # | |
/ # wget http://nginx-svc-external:8080 | |
Connecting to nginx-svc-external:8080 (10.96.124.187:8080) | |
saving to 'index.html' | |
index.html 100% |
# 反向代理外部域名
上一小节中,通过在 yaml 中写入外部地址实现 pod 的对外访问,如果需要直接使用域名访问,而不是将域名解析成地址再写入,则可以使用 ExternalName 类型
ExternalName 是特殊类型的 Service,它的本质是给外部域名做一个集群内的 DNS 别名,它没有端口转发的功能,也没有 Endpoints 绑定逻辑,资源创建后会在 K8s 集群的 CoreDNS 中,为这个 Service 创建一条 DNS CNAME 别名记录,当 Pod 访问 <svc-Name>.default.svc.cluster.local:<port> 时,K8s 的 DNS 会直接把这个域名解析为配置 `externalName,然后 Pod 的请求就直接发送到目标域名上,全程没有任何端口转发 / 代理。
ExternalName类型没有任何端口转发能力,Pod 访问svc:port时,会直接把请求发往externalName 域名:port,端口原样透传,因此 port 需要写成目标域名的目标端口
apiVersion: v1 | |
kind: Service | |
metadata: | |
name: svc-externalname | |
namespace: default | |
labels: | |
app: service-external | |
spec: | |
ports: | |
- port: 80 # Pod 访问该 Service 时使用的端口号,也是访问目的域名的端口号 | |
name: web # 端口名称,非必须 | |
protocol: TCP # 协议,默认 TCP,非必须 | |
type: ExternalName # 核心类型,声明为外部域名别名服务 | |
externalName: www.bilibili.com # 外部服务的完整域名 |
创建资源文件后,查看 svc 信息并测试
# 查看 svc 信息,External Name 为目标域名 | |
[root@kubernetes-master ~]# kubectl describe svc svc-externalname | |
Name: svc-externalname | |
Namespace: default | |
Labels: app=service-external | |
Annotations: <none> | |
Selector: <none> | |
Type: ExternalName | |
IP Families: <none> | |
IP: | |
IPs: <none> | |
External Name: www.bilibili.com | |
Port: web 8080/TCP | |
TargetPort: 8080/TCP | |
Endpoints: <none> | |
Session Affinity: None | |
Events: <none> | |
# 启动一个交互式 busybox 容器 | |
[root@kubernetes-master ~]# kubectl run -it --rm load-generator --image=busybox /bin/sh | |
If you don't see a command prompt, try pressing enter. | |
/ # wget https://svc-externalname | |
Connecting to svc-externalname (221.178.63.10:443) | |
wget: note: TLS certificate validation not implemented | |
wget: server returned error: HTTP/1.1 403 Forbidden | |
/ # | |
# 由于目标网站反爬,所以回显 403,但是 svc 正常工作,如果需要正确回显,可尝试: | |
# --no-check-certificate # 关闭 busybox 的 TLS 证书校验(busybox 没有内置根证书,不加会报证书错误) | |
# --user-agent="xxx" # 模拟 Chrome 浏览器的请求头,绕过 B 站的反爬校验 | |
# -O - # 把下载的内容输出到控制台,而不是保存为文件,方便查看结果 | |
# 2>/dev/null # 屏蔽无关的警告信息,只显示返回的内容 | |
/ # wget --no-check-certificate --user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" -O - https://www.bilibili.com 2>/dev/null |
# Ingress
在上一章节中提到,Service 对集群之外暴露服务的主要方式有两种, NotePort 和 LoadBalancer ,但这两种方式,都有一定的缺点,具体来说:
NodePort 会占用很多集群机器的端口,当集群服务变多的时候,过多的端口会给 k8s 的运维人员带来诸多的不便;而 LB 的缺点是每个 service 需要一个 LB,不仅浪费而且麻烦,并且需要 kubernetes 之外设备的支持;
基于这种现状,k8s 提供了 Ingress 这种资源对象, Ingress 只需要一个 NodePort 或者一个 LB 就可以满足暴露多个 Service 的需求;
# Ingress 工作原理
实际上,Ingress 相当于一个 7 层的负载均衡器,可以理解为 kubernetes 对反向代理的一个抽象,它的工作原理类似于 Nginx;
或者可以理解为:在 Ingress 里建立了诸多的映射规则, Ingress Controller 通过监听这些配置规则并转化成 Nginx 的反向代理配置,然后对外部提供服务;
ingress 的工作机制可以参考下图

关于 Ingress,有下面两个概念需要重点理解
ingress是 kubernetes 中的一个对象,作用是定义请求如何转发到 service 的规则;ingress controller是具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现方式有很多,比如Nginx,Contour,Haproxy等;
类比 Nginx 来说,Ingress 工作原理如下
编写 Ingress 规则,说明哪个域名对应 kubernetes 集群中哪个 Service ; Ingressnen 控制器动态感知 Ingress 服务规则的变化,然后生成一段对应的 Nginx 反向代理配置; Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中,并动态更新;
到此为止,不难发现,Ingress 其实真正在工作的时候就像是充当一个 Nginx 在使用,内部配置了用户定义的请求转发规则;

# 安装 ingress-nginx
通过 helm 安装 ingress-nginx
- 安装 heml
[root@kubernetes-master ~]# mkdir helm | |
[root@kubernetes-master ~]# cd helm/ | |
# 下载 heml | |
[root@kubernetes-master helm]# wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz | |
--2026-01-11 18:17:26-- https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz | |
正在解析主机 get.helm.sh (get.helm.sh)... 13.107.213.74, 13.107.246.74, 2620:1ec:46::73, ... | |
正在连接 get.helm.sh (get.helm.sh)|13.107.213.74|:443... 已连接。 | |
已发出 HTTP 请求,正在等待回应... 200 OK | |
长度:12924654 (12M) [application/x-tar] | |
正在保存至: “helm-v3.2.3-linux-amd64.tar.gz” | |
# 解压 | |
[root@kubernetes-master helm]# tar -zxvf helm-v3.2.3-linux-amd64.tar.gz | |
linux-amd64/ | |
linux-amd64/README.md | |
linux-amd64/LICENSE | |
linux-amd64/helm | |
[root@kubernetes-master helm]# ls | |
helm-v3.2.3-linux-amd64.tar.gz linux-amd64 | |
# 将 heml 程序移动到 /usr/local/bin | |
[root@kubernetes-master helm]# cd linux-amd64/ | |
[root@kubernetes-master linux-amd64]# ls | |
helm LICENSE README.md | |
[root@kubernetes-master linux-amd64]# cp helm /usr/local/bin/ |
- 添加 helm 仓库,下载 ingress-nginx(版本需要与 K8S 版本兼容,
Kubernetes 1.23.x选用ingress-nginx 1.2.1
# 添加 helm 仓库 | |
[root@kubernetes-master ~]# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx | |
"ingress-nginx" has been added to your repositories | |
# 更新仓库索引 | |
[root@kubernetes-master ~]# helm repo update | |
Hang tight while we grab the latest from your chart repositories... | |
...Unable to get an update from the "ingress-nginx" chart repository (https://kubernetes.github.io/ingress-nginx): | |
Get "https://kubernetes.github.io/ingress-nginx/index.yaml": read tcp 192.168.0.200:32988->185.199.111.153:443: read: connection reset by peer | |
Update Complete. ⎈Happy Helming!⎈ | |
# 查看仓库 | |
[root@kubernetes-master ~]# helm repo list | |
NAME URL | |
ingress-nginx https://kubernetes.github.io/ingress-nginx | |
# 搜索仓库中的 ingress-nginx 包,CHART VERSION 为 helm 包版本号,APP VERSION 为 ingress-nginx 控制器内核版本 | |
[root@kubernetes-master ~]# helm search repo ingress-nginx | |
NAME CHART VERSION APP VERSION DESCRIPTION | |
ingress-nginx/ingress-nginx 4.14.1 1.14.1 Ingress controller for Kubernetes using NGINX a... | |
# 下载 ingress-nginx,指定 helm 包版本 4.14.1 | |
[root@kubernetes-master ~]# helm pull ingress-nginx/ingress-nginx --version=4.14.1 | |
[root@kubernetes-master ~]# ls | |
ingress-nginx-4.14.1.tgz | |
# 解压 ingress-nginx 包,建议不要解压到根目录,创建一个目录 mv 后解压 | |
[root@kubernetes-master helm]# tar -xf ingress-nginx-4.14.1.tgz | |
[root@kubernetes-master helm]# cd ingress-nginx/ |
- 配置参数,修改 values.yaml(先备份再修改)
# vim 先 set number | |
# 镜像地址修改为国内镜像,找到:image: ingress-nginx/controller | |
registry: registry.cn-hangzhou.aliyuncs.com | |
image: google_containers/nginx-ingress-controller | |
tag: "v1.14.1" # 记住这个版本号 | |
# digest: 注释掉下方得 digest 和 digestChroot 两行,防止校验 | |
# digestChroot: | |
# 搜索 /kube-web 找到这行内容 image: ingress-nginx/kube-webhook-certgen | |
# 修改成下面的内容 | |
registry: registry.cn-hangzhou.aliyuncs.com | |
image: google_containers/kube-webhook-certgen | |
tag: 1.14.1 # 改成和上方得 tag 一样,不要引号 | |
# digest 这个也注释掉 | |
# 修改部署配置的 kind: DaemonSet | |
# 搜索 DaemonSet,按 n 匹配下一个,找到这行内容:Use a `DaemonSet` or `Deployment`,往下看 | |
kind: DaemonSet # 原来是 Deployment,把这个改为 DaemonSet | |
# 往下找 nodeSelector 加入 ingress: "true" | |
# 大概在 338 行得位置,上一点会有 terminationGracePeriodSeconds: 300 | |
nodeSelector: | |
kubernetes.io/os: linux | |
ingress: "true" # 增加选择器,如果 node 上有 ingress=true 就部署 | |
# 搜索 dnsPolicy: ClusterFirst 把这个集群优先 改为下面的主机映射 | |
dnsPolicy: ClusterFirstWithHostNet | |
# 往下 30 行左右,将 hostNetwork 改为 true | |
hostNetwork: true #使用本地网络 | |
# 搜索 admissionWebhooks,下方得 enabled 改为 false | |
admissionWebhooks: | |
enabled: false # 原本为 true,改为 false,取消证书验证 | |
# 搜索 LoadBalancer,service 下的 type: LoadBalancer,改为 ClusterIP | |
service: | |
type: ClusterIP # 由 LoadBalancer 修改为 ClusterIP,如果服务器是云平台才用 LoadBalancer |
- 创建 namespace,安装 ingress-nginx
在配置中,我们使用了 hostNetwork: true ,这会让 ingress 控制器的 svc 资源占用 node 的 80 和 443 端口
# 创建命名空间 | |
[root@kubernetes-master ingress-nginx]# kubectl create ns ingress-nginx | |
namespace/ingress-nginx created | |
# 将 node 打上 values.yaml 中设置的标签,只有匹配到标签的 node 才会安装 ingress-nginx | |
[root@kubernetes-master ingress-nginx]# kubectl label node kubernetes-node1 ingress=true | |
node/kubernetes-node1 labeled | |
# 安装 ingress-nginx ,注意最后的点号,表示在当前目录下找 values.yaml | |
[root@kubernetes-master ingress-nginx]# helm install ingress-nginx -n ingress-nginx . | |
# 验证安装 | |
[root@kubernetes-master ingress-nginx]# kubectl get all -n ingress-nginx | |
NAME READY STATUS RESTARTS AGE | |
pod/ingress-nginx-controller-xmjkw 1/1 Running 0 109s | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
service/ingress-nginx-controller ClusterIP 10.101.23.233 <none> 80/TCP,443/TCP 109s | |
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE | |
daemonset.apps/ingress-nginx-controller 1 1 1 1 1 ingress=true,kubernetes.io/os=linux 109s | |
# 如果需要卸载 ingress-nginx 组件 | |
helm uninstall ingress-nginx -n ingress-nginx | |
# 如果需要删除标签 | |
kubectl label node < 节点名 > ingress- |
# 创建 Ingress
通过部署 Ingress-nginx ,我们可以轻松地将外部流量路由到不同的服务。例如,当用户访问 example.com 时,Ingress 控制器可以根据规则将流量路由到 service-a 或 service-b 。
我们可以创建一个简单的 Ingress 资源来演示这个场景:
apiVersion: networking.k8s.io/v1 | |
kind: Ingress | |
metadata: | |
name: example-ingress | |
annotations: # 注解,一些额外配置项 | |
kubernetes.io/ingress.class: "nginx" # 配置 ingress.class 类型为 nginx,即 ingress-nginx | |
nginx.ingress.kubernetes.io/rewrite-target: / # 将请求路径重写为指定的目标路径。 | |
spec: | |
rules: # 定义路由规则 | |
- host: example.com # 指定了该 Ingress 处理的主机名为 example.com | |
http: # 规定 HTTP 流量的路由 | |
paths: # 定义访问路径 | |
- path: / # 设为 /,表示处理所有路径前缀为 / 的请求 | |
pathType: Prefix # 设置为 Prefix,表示前缀匹配 | |
backend: # 指定服务名称和端口,流量将被路由到该后端服务。 | |
service: | |
name: nginx-a # 指定目标服务的名称为 nginx-a | |
port: | |
number: 80 # 目标服务的端口设置为 80 |
创建出 service-a 和 service-b 两个 pod,然后创建 ingress 资源
# 创建两个 deploy 资源 | |
[root@kubernetes-master ~]# kubectl create deployment nginx-a --image=docker.io/library/nginx:1.28 | |
[root@kubernetes-master ~]# kubectl create deployment nginx-b --image=docker.io/library/nginx:1.28 | |
# 为 Deployment 控制器自动创建对应的 Service 资源,Service 对外暴露 80 端口,将接收到的所有请求,转发到后端 Pod 的 80 端口上 | |
[root@kubernetes-master ~]# kubectl expose deployment service-a --port=80 --target-port=80 --type=NodePort | |
[root@kubernetes-master ~]# kubectl expose deployment service-b --port=80 --target-port=80 --type=NodePort | |
# 查看 service | |
[root@kubernetes-master ~]# kubectl get svc | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d | |
nginx-a NodePort 10.96.53.108 <none> 80:30944/TCP 115s | |
nginx-b NodePort 10.106.223.243 <none> 80:31371/TCP 45s | |
# 创建 ingress 资源 | |
[root@kubernetes-master ~]# kubectl apply -f ingress.yaml | |
ingress.networking.k8s.io/example-ingress created | |
[root@kubernetes-master ~]# kubectl get ingress | |
NAME CLASS HOSTS ADDRESS PORTS AGE | |
example-ingress <none> example.com 80 11s |
此时,修改本地的 C:\Windows\System32\drivers\etc\hosts 文件,设置 example.com 的对应 IP 地址是 node1 节点的 IP 地址,因为 ingress 控制器部署在 node1 上,然后在 cmd 中情况 dns 缓存 ipconfig /flushdns ,使用浏览器访问 example.com,即可访问到 nginx 页面
查看创建的 ingress 和 controller,Ingress 资源显示的 ADDRESS 是 Ingress Controller 的 Service IP,这是因为此处使用的是 ClusterIP 类型的 Service,且 Ingress Controller 是以 DaemonSet 方式部署的。
Ingress 资源只是一个 “规则集合”(比如:访问
example.com转发到哪个 Service)。真正负责转发的是 Ingress Controller(此环境里是ingress-nginx-controller)。Kubernetes 会自动将 Ingress 资源的
ADDRESS字段设置为它所关联的 Ingress Controller 的入口地址。这意味着,所有进入该 Ingress 的流量,实际上都被转发到了这个 Service IP 上。
这种架构(DaemonSet + ClusterIP)通常配合 hostNetwork: true (使用宿主机网络)使用,真正的入口 ADDRESS 其实是 Node 的物理 IP,而不是 Ingress 资源显示的 ClusterIP。
[root@kubernetes-master ~]# kubectl get all -n ingress-nginx | |
NAME READY STATUS RESTARTS AGE | |
pod/ingress-nginx-controller-xmjkw 1/1 Running 0 5h41m | |
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT (S) AGE | |
service/ingress-nginx-controller ClusterIP 10.101.23.233 <none> 80/TCP,443/TCP 5h41m | |
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE | |
daemonset.apps/ingress-nginx-controller 1 1 1 1 1 ingress=true,kubernetes.io/os=linux 5h41m | |
[root@kubernetes-master ~]# kubectl get ingress | |
NAME CLASS HOSTS ADDRESS PORTS AGE | |
nginx-ingress <none> example.com 10.101.23.233 80 22m |
# 多域名匹配
按照不同主机不同域名进行匹配
apiVersion: networking.k8s.io/v1 | |
kind: Ingress # 资源类型为 Ingress | |
metadata: | |
name: nginx-ingress-mul | |
annotations: | |
kubernetes.io/ingress.class: "nginx" | |
nginx.ingress.kubernetes.io/rewrite-target: / | |
spec: | |
rules: # ingress 规则配置,可以配置多个 | |
- host: example.com # 域名配置,可以使用通配符 * | |
http: | |
paths: # 相当于 nginx 的 location 配置,可以配置多个 | |
- pathType: Prefix # 设置为 Prefix,表示前缀匹配 | |
backend: | |
service: | |
name: nginx-a # 代理到哪个 service | |
port: | |
number: 80 # service 的端口 | |
path: /api/v1 # 等价于 nginx 中的 location 的路径前缀匹配 | |
- pathType: Exact # 路径类型,Exact:精确匹配 >,URL 需要与 path 完全匹配上,且区分大小写 | |
backend: | |
service: | |
name: nginx-b # 代理到哪个 service | |
port: | |
number: 80 # service 的端口 | |
path: /api/v2 |
现在,为了验证多域名匹配,我们将 pod 中 nginx 的 index.html 修改下
[root@kubernetes-master ~]# kubectl get pod | |
NAME READY STATUS RESTARTS AGE | |
nginx-a-5d684cf4d8-5rjxq 1/1 Running 0 68m | |
nginx-b-78c9967d5c-4gdfs 1/1 Running 0 68m | |
# 将 index.html 中的 Welcome to nginx! 分别换成 Welcome to service-a 和 service-b | |
[root@kubernetes-master ~]# kubectl exec -it nginx-a-5d684cf4d8-5rjxq -- sed sed -i's#<h1>Welcome to nginx!</h1>#<h1>Welcome to service-a</h1>#g' /usr/share/nginx/html/index.html | |
sed: -e expression #1, char 3: unterminated `s' command | |
command terminated with exit code 1 | |
[root@kubernetes-master ~]# kubectl exec -it nginx-a-5d684cf4d8-5rjxq -- sed -i's#<h1>Welcome to nginx!</h1>#<h1>Welcome to service-a</h1>#g' /usr/share/nginx/html/index.html | |
[root@kubernetes-master ~]# kubectl exec -it nginx-b-78c9967d5c-4gdfs -- sed -i's#<h1>Welcome to nginx!</h1>#<h1>Welcome to service-b</h1>#g' /usr/share/nginx/html/index.html |
打开浏览器,访问 http://example.com/api/v1 和 http://example.com/api/v2 ,可以分别看到显示 Welcome to service-a 和 Welcome to service-b
# 路径类型
Ingress 中的每个路径都需要有对应的路径类型(Path Type)。未明确设置 pathType 的路径无法通过合法性检查。当前支持的路径类型有三种:
ImplementationSpecific:对于这种路径类型,匹配方法取决于 IngressClass。 具体实现可以将其作为单独的pathType处理或者作与Prefix或Exact类型相同的处理。Exact:精确匹配 URL 路径,且区分大小写。Prefix:基于以/分隔的 URL 路径前缀匹配。匹配区分大小写, 并且对路径中各个元素逐个执行匹配操作。 路径元素指的是由/分隔符分隔的路径中的标签列表。 如果每个 p 都是请求路径 p 的元素前缀,则请求与路径 p 匹配。
如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会被视为匹配 (例如:
/foo/bar匹配/foo/bar/baz, 但不匹配/foo/barbaz)。
关于路径类型的更多细节,请参考官方文档:Ingress | Kubernetes