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

# API 概述

REST API 是 Kubernetes 的基本结构。 所有操作和组件之间的通信及外部用户命令都是调用 API 服务器处理的 REST API。 因此,Kubernetes 平台视一切皆为 API 对象, 且它们在 API 中有相应的定义。

Kubernetes API 参考 列出了 Kubernetes v1.35 版本的 API。

如需了解一般背景信息,请查阅 Kubernetes APIKubernetes 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):单个的、不可再分的值

# 常用字段解释说明

# 必须存在的属性

参数名字段类型说明
versionString这里指的是 K8s API 的版本,目前基本上是 v1,可以用 kubectl api-version 命令查询
kindString这里指的是 yaml 文件定义的资源类型和角色,比如:pod
metadataObject元数据对象,固定值就写 metadata
metadata.nameString元数据对象的名字,这里由我们编写,比如命令 Pod 的名字
metadata.namespaceString元数据对象的命名空间,由我们自身定义
specObject详细定义对象,固定值就写 Spec
spec.containers []list这里是 Spec 对象的容器列表定义,是个列表
spec.containers [].nameString这里定义容器的名字
spec.containers [].imageString这里定义要用到的镜像名称

# 主要对象

参数名字段类型说明
spec.containers [].nameString定义容器的名字
spec.containers [].imageString定义要用到的镜像名称
spec.containers [].imagePullRolicyString定义镜像拉取策略,有 Always、Never、IfNotPresent 三个选项; Always:每次都尝试重新拉取镜像,默认 Always Never:仅使用本地镜像 IfNotPresent:如果本地有镜像就使用本地就像,如果没有就拉取在线镜像;
spec.containers [].command []List指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令。
spec.containers [].args []List指定容器启动命令参数,因为是数组可以指定多个。
spec.containers [].workingDirString指定容器的工作目录。
spec.containers [].volumeMounts []List指定容器内部的存储卷配置
spec.containers [].volumeMounts [].nameString指定可以被容器挂载的存储卷的名称
spec.containers [].volumeMounts [].mountPathString指定可以被容器挂载的存储卷的路径
spec.containers [].volumeMounts [].readOnlyString设置存储卷路径的读写模式,true 或者 false,默认为读写模式
spec.containers [].ports []List指定容器需要用到的端口列表
spec.containers [].ports [].nameString指定端口名称
spec.containers [].containerPortString指定容器需要监听的端口号
spec.containers [].ports [].hostPortString指定容器所在主机需要监听的端口号,默认跟上面 containerPort 相同;注意设置了 hostPort 同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
spec.containers [].ports [].protocolString指定端口协议,支持 TCP 和 UDP,默认为 TCP
spec.containers [].env []List指定容器运行前需设置的环境变量列表

# 额外的参数项

参数名字段类型说明
spec.restartPolicyString** 定义 Pod 的重启策略 **,可选值为 Always、OnFailure,默认值为 Always; 1.Always:pod 一旦终止运行,则无论容器是如何终止的,kubelet 服务都将重启它。 2.OnFailure:只有 pod 以非零退出码终止时,kubelet 才会重启该容器;如果容器正常退出 (退出码为 0),则 kubelet 将不会重启它; 3.Never:pod 终止后,kubelet 将退出码报告给 master,不会重启 pod。
spec.nodeSelectorObject定义 Node 的 Label 过滤标签,以 key: value 格式指定
spec.imagePullSecretsObject定义 pull 镜像时使用 sercet 名称,以 name: secretkey 格式指定
spec.hostNetworkBoolean定义是否使用主机网络模式,默认值 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-mastercurl 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 podkubectl scale )时, PreStop 钩子立即执行。
  • 在示例中, PreStop 钩子输出一条日志并等待 5 秒,模拟资源清理。

# 注意事项

执行时间限制:

  • 钩子执行时间受 Pod 的 terminationGracePeriodSeconds 控制,默认宽限时间为 30 秒。如果 PreStop 未在宽限时间内完成,容器将被强制终止。

运行环境:

  • 钩子在容器的文件系统和环境变量中运行,因此可以直接访问容器内部的资源。

错误处理:

  • 如果钩子失败,容器不会因此失败,但会记录错误日志。

钩子与主进程的关系:

  • 钩子运行与容器主进程无直接依赖关系。PostStart 并不阻塞主进程启动,而 PreStop 是在终止信号发送后触发。

# 资源调度

# 标签和选择器

在 Kubernetes 集群中,会运行大量不同类型的资源(Pod、Service、Deployment、Node 等)。随着集群规模扩大,如何高效分类、筛选、关联这些资源成为核心问题。标签(Label)和选择器(Selector)正是为解决这一问题而生的核心机制,具体价值体现在以下 3 点:

  1. 资源分类与筛选:通过标签为资源添加多维度的自定义标识(如环境、应用、版本、角色),再通过选择器快速筛选出目标资源。例如:区分开发环境( env=dev )和生产环境( env=prod )的 Pod,筛选某个应用( app=nginx )的所有实例。
  2. 资源松耦合关联:实现不同资源之间的动态绑定,无需硬编码。例如:Service 通过选择器关联后端 Pod,无论 Pod 如何重启、扩容,只要标签匹配,Service 就能自动转发流量;Deployment 通过选择器管理对应的 Pod 副本。
  3. 灵活的资源管理:支持多标签组合筛选,满足复杂的资源管理需求。例如:筛选 app=nginxversion=1.29env=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 只需要修改它所控制的 ReplicaSetPod 副本个数就可以了。比如,把这个值从 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=truekubectl 命令的可选参数,这个参数能让 Kubernetes 在这个 Deployment 的变更记录里记录上产生变更当时执行的命令。 --record 只对修改 Deployment 状态的命令生效(如 scaleset imageapply 改配置),纯查询命令(如 getdescribe )加 --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>

废弃说明--recordkubectl 1.14+ 版本后被标记为 “废弃(deprecated)”(但仍可正常使用),K8s 官方推荐手动添加注解替代(效果一致):

执行 kubectl annotatedeploy 添加的 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 保存很多不同的 ReplicaSetDeployment 对象中 spec.revisionHistoryLimit 指定了 KubernetesDeployment 保留的 "历史版本" 个数。如果把它设置为 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 PodFluentd-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 资源文件,查看 dsDaemonSet ),确认在三个 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.typeRollingUpdate

实际上,建议使用 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 的工作原理可以概括为以下几个步骤:

  1. 采集指标
    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 等组件。
  2. 计算期望副本数

    HPA 基于 “当前指标值” 和 “目标指标值”,通过公式计算期望副本数: 期望副本数 = ceil (当前副本数 × (当前指标值 / 目标指标值))

    如果当前副本数 = 2,目标 CPU 使用率 = 50%,实际 CPU 使用率 = 100%,期望副本数 = 2 × (100%/50%)=4 ,则自动扩容到 4 个副本。

    关键约束(避免极端情况)
    最小副本数(minReplicas):扩缩容的下限(如至少 2 个副本,避免缩到 0);
    最大副本数(maxReplicas):扩缩容的上限(如最多 10 个副本,避免资源耗尽);
    容忍阈值:指标波动在 ±10% 内(默认),不触发扩缩容(避免频繁调整);
    冷却时间:扩容后需等待 3 分钟(默认)才能再次扩容,缩容后需等待 5 分钟(默认)才能再次缩容(避免 “震荡”)。

  3. 执行扩缩容

    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
  1. 下载官方部署文件(适配 K8s 1.24+,低版本需调整):
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
  1. 编辑 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  # 新增这行(测试环境)
  1. 部署并验证
# 部署
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: 容器端口 的键值对列表,除此之外无任何多余信息;

  • 作为 ServicePod 之间的中间桥梁,实现解耦,因此 Service 不用关心 Pod 的动态变化,只需要读取 Endpoints 即可拿到最新的后端地址; Pod 的变化由 Endpoints 控制器同步到 Endpoints 对象中。

Service 是 K8s 的核心资源,为一组功能相同的 Pod 提供固定、统一的访问地址;

  • Service 创建后会分配一个集群内唯一的虚拟 IP( ClusterIP )+ 固定的 Service 端口,这个 ClusterIPService 生命周期内永不改变;
  • 集群内的客户端(其他 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 工作原理

  1. 创建 Service 对象,API Server 持久化到 etcd

通过 kubectl create service / YAML 文件 / 编程调用 K8s API,向 API Server 提交 Service 创建请求,Service 的配置(包含标签选择器、端口映射等)会被持久化到 etcd 中。

  1. Endpoints 控制器通过 Informer 感知 Service 变更

K8s 的 Endpoints 控制器(属于 kube-controller-manager 的一个核心控制器,运行在 master 节点)内部集成了 Informer 机制:

  • Informer 会实时监听 API Server 中 Service 对象的变更事件(创建、修改、删除),并将 Service 的配置缓存到本地;
  • 作用:Informer 是 K8s 的本地缓存和事件监听核心组件,避免控制器直接频繁访问 etcd ,减少 etcd 的压力,同时提升控制器的响应速度。
  1. Endpoints 控制器构建并维护 Endpoints 对象

Endpoints 控制器读取 Service 的 spec.selector 标签选择器;遍历集群中同命名空间的所有 Pod,筛选出标签匹配的健康 Pod;基于这些健康 Pod,自动创建 / 更新同名的 Endpoints 对象,将这些 Pod 的 PodIP + 容器端口 写入 Endpoints;Endpoints 对象被持久化到 etcd 中,并且控制器会持续监听 Pod 的状态变化并更新 Endpoints 中的 Pod 清单。

Endpoints 控制器的核心职责只有一个:维护 Service 和 Pod 的关联关系,保证 Endpoints 的准确性。

  1. 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 维护其转发规则。

  1. 集群内的客户端访问 ClusterIP:Service 端口 ,请求流量会经过以下链路到达 Pod:
  • 客户端的请求到达 Node 节点后,会被该节点的 iptables/IPVS 规则拦截;

  • 规则根据负载均衡策略(默认轮询),随机选择一个 Endpoints 中的 PodIP: 容器端口;

  • 请求流量被转发到选中的 Pod 上,Pod 的容器处理请求并返回响应;

  • 响应流量原路返回给客户端,整个请求链路完成。

![](C:\Users\Amadues\Desktop\K8S\K8S 实操 \service.png)

# 创建 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) 小节,实现外部访问 pod

  • LoadBalancer 类型,主要就是在 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 会自动解析出它的 ClusterIP 10.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 对集群之外暴露服务的主要方式有两种, NotePortLoadBalancer ,但这两种方式,都有一定的缺点,具体来说:
NodePort 会占用很多集群机器的端口,当集群服务变多的时候,过多的端口会给 k8s 的运维人员带来诸多的不便;而 LB 的缺点是每个 service 需要一个 LB,不仅浪费而且麻烦,并且需要 kubernetes 之外设备的支持;

基于这种现状,k8s 提供了 Ingress 这种资源对象, Ingress 只需要一个 NodePort 或者一个 LB 就可以满足暴露多个 Service 的需求;

# Ingress 工作原理

实际上,Ingress 相当于一个 7 层的负载均衡器,可以理解为 kubernetes 对反向代理的一个抽象,它的工作原理类似于 Nginx;
或者可以理解为:在 Ingress 里建立了诸多的映射规则, Ingress Controller 通过监听这些配置规则并转化成 Nginx 的反向代理配置,然后对外部提供服务;

ingress 的工作机制可以参考下图

![](C:\Users\Amadues\Desktop\K8S\K8S 实操 \ingress_1.png)

关于 Ingress,有下面两个概念需要重点理解

  • ingress 是 kubernetes 中的一个对象,作用是定义请求如何转发到 service 的规则;
  • ingress controller 是具体实现反向代理及负载均衡的程序,对 ingress 定义的规则进行解析,根据配置的规则来实现请求转发,实现方式有很多,比如 Nginx , Contour , Haproxy 等;

类比 Nginx 来说,Ingress 工作原理如下
编写 Ingress 规则,说明哪个域名对应 kubernetes 集群中哪个 ServiceIngressnen 控制器动态感知 Ingress 服务规则的变化,然后生成一段对应的 Nginx 反向代理配置; Ingress 控制器会将生成的 Nginx 配置写入到一个运行着的 Nginx 服务中,并动态更新;

到此为止,不难发现,Ingress 其实真正在工作的时候就像是充当一个 Nginx 在使用,内部配置了用户定义的请求转发规则;

![](C:\Users\Amadues\Desktop\K8S\K8S 实操 \ingress_2.png)

# 安装 ingress-nginx

通过 helm 安装 ingress-nginx

  1. 安装 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/
  1. 添加 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/
  1. 配置参数,修改 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
  1. 创建 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-aservice-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/v1http://example.com/api/v2 ,可以分别看到显示 Welcome to service-aWelcome to service-b

# 路径类型

Ingress 中的每个路径都需要有对应的路径类型(Path Type)。未明确设置 pathType 的路径无法通过合法性检查。当前支持的路径类型有三种:

  • ImplementationSpecific :对于这种路径类型,匹配方法取决于 IngressClass。 具体实现可以将其作为单独的 pathType 处理或者作与 PrefixExact 类型相同的处理。
  • Exact :精确匹配 URL 路径,且区分大小写。
  • Prefix :基于以 / 分隔的 URL 路径前缀匹配。匹配区分大小写, 并且对路径中各个元素逐个执行匹配操作。 路径元素指的是由 / 分隔符分隔的路径中的标签列表。 如果每个 p 都是请求路径 p 的元素前缀,则请求与路径 p 匹配。

如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会被视为匹配 (例如: /foo/bar 匹配 /foo/bar/baz , 但不匹配 /foo/barbaz )。

关于路径类型的更多细节,请参考官方文档:Ingress | Kubernetes