RHEL------Kubernetes 容器编排平台(一):RHEL------Kubernetes容器编排平台-CSDN博客
六、Kubernets的存储
1. ConfigMap
当应用程序需要在不同环境中运行或配置需要频繁变更时,如果将配置硬编码到容器镜像中,会导致镜像无法复用,每次修改配置都需要重新构建镜像,过程繁琐且容易出错。
但若使用 ConfigMap 将配置与镜像解耦,通过环境变量或文件挂载的方式注入到 Pod 中,那么修改配置后无需重新构建镜像,只需更新 ConfigMap 并重启 Pod 即可生效,从而简化配置管理的复杂度。
(1)ConfigMap 的简介
configmap 的功能
- ConfigMap 用于保存配置数据,以键值对形式存储,将配置信息从容器镜像中分离出来。
- ConfigMap 资源提供了向 Pod 注入配置数据的方法,可以通过环境变量或挂载文件的方式将配置传递给容器。
- 因为镜像和配置文件解耦,所以同一个镜像可以在不同环境(开发、测试、生产)中使用不同的配置,实现了镜像的可移植性和可复用性。
- 需要注意的是,etcd 限制了 ConfigMap 的文件大小不能超过 1MB,因此大型配置文件不适合直接存储在 ConfigMap 中。
configmap 的使用场景
- 填充环境变量的值,将 ConfigMap 中的键值对设置为容器内的环境变量。
- 设置容器内的命令行参数,通过环境变量引用 ConfigMap 的值,再传递给容器的启动命令。
- 填充卷的配置文件,将 ConfigMap 以文件形式挂载到容器的指定目录中,每个键值对对应一个文件。
(2)ConfigMap 的创建方法
① 字面值创建
[root@master-N1 ~]# mkdir storage
[root@master-N1 ~]# cd storage/
[root@master-N1 storage]# kubectl create cm configmap-test --from-literal fname=timing --from-literal lname=lee
[root@master-N1 storage]# kubectl get cm
[root@master-N1 storage]# kubectl describe cm configmap-test
[root@master-N1 storage]# kubectl get cm configmap-test -o yaml
# 字面值创建存储实验结束
[root@master-N1 storage]# kubectl delete cm configmap-test

② 通过文件创建
[root@master-N1 storage]# echo hello timinglee > timinglee
[root@master-N1 storage]# kubectl create cm configmap-test --from-file timinglee
[root@master-N1 storage]# kubectl describe cm configmap-test
# 文件创建存储实验结束
[root@master-N1 storage]# kubectl delete cm configmap-test

③ 通过目录创建
[root@master-N1 storage]# mkdir test
[root@master-N1 storage]# echo timinglee > test/tfile
[root@master-N1 storage]# echo lee > test/lfile
[root@master-N1 storage]# ls test
[root@master-N1 storage]# cat test/*
[root@master-N1 storage]# kubectl create cm configmap-test--from-file test/
[root@master-N1 storage]# kubectl describe cm configmap-test
# 目录创建存储实验结束
[root@master-N1 storage]# kubectl delete cm configmap-test

④ 通过 yaml 文件创建
[root@master-N1 storage]# kubectl create cm configmap-test --from-literal timinglee=abc --dry-run=client -o yaml > configmap.yml
[root@master-N1 storage]# vim configmap.yml
1 apiVersion: v1
2 kind: ConfigMap
3 metadata:
4 name: configmap-test
5 data:
6 timinglee: abc
7 lee: "46187236548"
[root@master-N1 storage]# kubectl apply -f configmap.yml
[root@master-N1 storage]# kubectl get cm configmap-test
[root@master-N1 storage]# kubectl describe cm configmap-test
# yaml文件创建存储实验结束
[root@master-N1 storage]# kubectl delete -f configmap-test


(3)ConfigMap 的使用方式
- 通过环境变量的方式直接传递给 pod
- 通过 pod 的命令行运行方式
- 作为 volume 的方式挂载到 pod 内
① 使用 ConfigMap 填充环境变量
通过 configmap 将配置数据注入到 pod 的环境变量中
[root@master-N1 storage]# vim configmap-test.yml
1 apiVersion: v1
2 kind: ConfigMap
3 metadata:
4 name: configmap-test
5 data:
6 ipaddress: "192.168.153.50"
7 port: "3306"
[root@master-N1 storage]# kubectl apply -f configmap.yml
[root@master-N1 storage]# kubectl describe cm configmap-test
[root@master-N1 storage]# kubectl run pod-test --image busybox --dry-run=client -o yaml > pod-test.yml
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: busybox
10 name: pod-test
11 command:
12 - /bin/sh
13 - -c
14 - env
15 env:
16 - name: key1
17 valueFrom:
18 configMapKeyRef:
19 name: configmap-test
20 key: ipaddress
21 - name: key2
22 valueFrom:
23 configMapKeyRef:
24 name: configmap-test
25 key: port
26 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl logs pods/pod-test
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


通过 envFrom 将 ConfigMap 中的所有配置注入为 Pod 的环境变量
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: busybox
10 name: pod-test
11 command:
12 - /bin/sh
13 - -c
14 - env
15 envFrom:
16 - configMapRef:
17 name: configmap-test
18 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl logs pod-test
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


通过 envFrom 将 ConfigMap 中的所有配置注入为 Pod 的环境变量输出拼接后的结果
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: busybox
10 name: pod-test
11 command:
12 - /bin/sh
13 - -c
14 - echo ${ipaddress}_${port}
15 envFrom:
16 - configMapRef:
17 name: configmap-test
18 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl logs pod-test
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


② 通过数据卷使用 ConfigMap
[root@master-N1 storage]# kubectl describe cm configmap-test
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: busybox
10 name: pod-test
11 command:
12 - /bin/sh
13 - -c
14 - sleep 10000
15 volumeMounts:
16 - name: config-volume
17 mountPath: /config
18 volumes:
19 - name: config-volume
20 configMap:
21 name: configmap-test
22 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl exec -it pod-test -- /bin/sh
/ # ls
/ # cd config/
/config # ls
/config # cat ipaddress
/config # cat port
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml --force


③ 利用 ConfigMap 填充 Pod 的配置文件
[root@master-N1 storage]# vim nginx.conf
1 server{
2 listen 8000;
3 server_name _;
4 root /usr/share/nginx/html;
5 index index.html;
6 }
[root@master-N1 storage]# kubectl create cm nginx-cm --from-file nginx.conf
[root@master-N1 storage]# kubectl describe cm nginx-cm
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: nginx-cm
6 name: nginx-cm
7 spec:
8 containers:
9 - image: nginx:1.23
10 name: nginx-cm
11 volumeMounts:
12 - name: config-volume
13 mountPath: /etc/nginx/conf.d
14 volumes:
15 - name: config-volume
16 configMap:
17 name: nginx-cm
18 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl get pods -o wide
[root@master-N1 storage]# curl 10.244.2.9:8000
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


④ 通过热更新 cm 修改配置
配置文件修改后不会生效,需要删除 pod 后控制器会重建 pod,这时就生效了
[root@master-N1 storage]# kubectl edit cm nginx-cm
5 apiVersion: v1
6 data:
7 nginx.conf: "
8 server{
9 listen 8080;
10 server_name _;
11 root /usr/share/nginx/html;
12 index index.html;}"
13 kind: ConfigMap
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl get pods -o wide
[root@master-N1 storage]# curl 10.244.2.10:8080
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml

2. Secrets 配置管理
因为 ConfigMap 存储配置数据时是明文存储的,不适合存放密码、Token、SSH 密钥等敏感信息。
但是 Secret 可以对数据进行 Base64 编码或加密存储,并且访问 Secret 需要额外的权限控制,因此使用 Secret 来保存敏感信息更加安全。
(1)Secrets 的功能介绍
Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH key,避免将这些信息明文暴露在配置文件中。
因为敏感信息放在 Secret 中比放在 Pod 的定义或者容器镜像中更加安全和灵活,所以可以通过 Secret 独立管理密码和令牌,无需修改镜像或重启 Pod 即可更新敏感数据。
Pod 可以用两种方式使用 Secret:
- 作为 volume 中的文件被挂载到 Pod 中的一个或者多个容器里。
- 当 kubelet 为 Pod 拉取镜像时使用(如私有仓库认证)。
Secret 的常见类型有三种:
- Service Account 类型由 Kubernetes 自动创建,包含访问 API 的凭据,并自动修改 Pod 以使用此类型的 Secret。
- Opaque 类型使用 base64 编码存储信息,可以通过 base64 --decode 解码获得原始数据,因此安全性较弱。
- kubernetes.io/dockerconfigjson 类型用于存储 Docker 仓库的认证信息,方便 Pod 拉取私有镜像。
(2)Secrets 的创建
① 命令式创建
secret 策略
-
docker-registry:拉取私有仓库镜像
-
generic:数据库密码、API 密钥
-
tls:Ingress HTTPS 加密
[root@master-N1 storage]# kubectl create secret generic secret-test --from-literal userlist=timinglee --from-literal password=lee
[root@master-N1 storage]# kubectl get secrets
[root@master-N1 storage]# kubectl describe secrets secret-test
[root@master-N1 storage]# kubectl get secrets -o yaml secret-test
[root@master-N1 storage]# echo -n "dGltaW5nbGVl" | base64 -d
[root@master-N1 storage]# echo -n "bGVl" | base64 -d实验结束
[root@master-N1 storage]# kubectl delete secrets secret-test

② 用过文件创建
[root@master-N1 storage]# echo timinglee > userlist
[root@master-N1 storage]# echo lee > password
[root@master-N1 storage]# cat userlist
[root@master-N1 storage]# cat password
[root@master-N1 storage]# kubectl create secret generic secret-test --from-file userlist --from-file password
[root@master-N1 storage]# kubectl get secrets
[root@master-N1 storage]# kubectl get secrets -o yaml
# 实验结束
[root@master-N1 storage]# kubectl delete secrets secret-test

③ 编写 yaml 文件创建
[root@master-N1 storage]# echo -n "timinglee" | base64
[root@master-N1 storage]# echo -n "123" | base64
[root@master-N1 storage]# kubectl create secret generic secret-test --from-literal userlist=timinglee --dry-run=client -o yaml > secret.yml
[root@master-N1 storage]# vim secret.yml
[root@master-N1 storage]# kubectl apply -f secret.yml
[root@master-N1 storage]# kubectl describe secrets secret-test
[root@master-N1 storage]# kubectl get secrets -o yaml secret-test
# 实验结束
[root@master-N1 storage]# kubectl delete secrets secret-test


(3)Secrets 的使用方法
① 将 Secret 挂载到 Volume 中
[root@master-N1 storage]# kubectl apply -f secret.yml
[root@master-N1 storage]# kubectl describe secrets secret-test
[root@master-N1 storage]# kubectl get secrets -o yaml | head -10
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: busybox
6 name: busybox
7 spec:
8 containers:
9 - image: busybox
10 name: busybox
11 command:
12 - /bin/sh
13 - -c
14 - sleep 10000
15 volumeMounts:
16 - name: config-volume
17 mountPath: /userlist
18 volumes:
19 - name: config-volume
20 secret:
21 secretName: secret-test
22 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl exec -it pods/busybox -- /bin/sh
/ # ls
/ # cd userlist/
/userlist # ls
/userlist # cat userlist
/userlist # cat password
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml --force


② 向指定路径映射 Secret 密钥
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: busybox
6 name: busybox
7 spec:
8 containers:
9 - image: busybox
10 name: busybox
11 command:
12 - /bin/sh
13 - -c
14 - sleep 10000
15 volumeMounts:
16 - name: config-volume
17 mountPath: /userlist
18 volumes:
19 - name: config-volume
20 secret:
21 secretName: secret-test
22 items:
23 - key: userlist
24 path: my-users/username
25 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl exec -it pods/busybox -- /bin/sh
/ # cat userlist/my-users/username
[root@master-N1 storage]# kubectl get secrets -o yaml
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml --force


③ 将 Secret 设置为环境变量
[root@master-N1 storage]# kubectl get secrets -o yaml | head -10
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: busybox
6 name: busybox
7 spec:
8 containers:
9 - image: busybox
10 name: busybox
11 command:
12 - /bin/sh
13 - -c
14 - env
15 env:
16 - name: USERNAME
17 valueFrom:
18 secretKeyRef:
19 name: secret-test
20 key: userlist
21 - name: PASSWORD
22 valueFrom:
23 secretKeyRef:
24 name: secret-test
25 key: password
26 restartPolicy: Never
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl logs pods/busybox busybox
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


(4)存储 Docker Registry 的认证信息
① 上传镜像到私有项目
创建私有仓库的私有项目不能勾选公开


将镜像上传到私有项目,并启动 pod
[root@master-N1 storage]# docker tag reg.timinglee.org/library/myapp:v1 reg.timinglee.org/timinglee/myapp:v1
[root@master-N1 storage]# docker push reg.timinglee.org/timinglee/myapp:v1
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: myapp
6 name: myapp
7 spec:
8 containers:
9 - image: reg.timinglee.org/timinglee/myapp:v1
10 name: myapp
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl get pods
[root@master-N1 storage]# kubectl describe pods myapp | grep Events -A 10
[root@master-N1 storage]# kubectl delete -f pod-test.yml


② 创建 docker 认证的 secret 信息
创建 docker 认证的 secret
[root@master-N1 storage]# kubectl create secret docker-registry docker-auth \
--docker-server reg.timinglee.org \
--docker-username admin \
--docker-password lee \
--docker-email admin@timinglee.org
[root@master-N1 storage]# kubectl get secrets docker-auth
[root@master-N1 storage]# kubectl get secrets docker-auth -o yaml

引用 docker 认证的 secret
[root@master-N1 storage]# vim pod-test.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: myapp
6 name: myapp
7 spec:
8 containers:
9 - image: reg.timinglee.org/timinglee/myapp:v1
10 name: myapp
11 imagePullSecrets:
12 - name: docker-auth
[root@master-N1 storage]# kubectl apply -f pod-test.yml
[root@master-N1 storage]# kubectl get pods
[root@master-N1 storage]# kubectl get pods -o wide
[root@master-N1 storage]# curl 10.244.2.8
# 实验结束
[root@master-N1 storage]# kubectl delete -f pod-test.yml


3. Volumes 配置管理
因为容器中的文件是临时存放的,当容器崩溃或重启时数据会丢失,而 Pod 中的多个容器又需要共享文件,并且 Pod 可能会被调度到不同节点上运行,如果数据只存储在本地磁盘,迁移后数据就无法访问。
所以需要学习 Volumes,它提供了持久化存储、容器间共享数据、以及跨节点数据访问的能力,解决了容器存储的根本问题。
(1)Volumes 简介
Kubernetes 卷的功能
- 容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题,比如需要持久化数据或共享文件。
- 当容器崩溃时,kubelet 会重新启动容器,但容器中的文件将会丢失,因为容器会以干净的状态重建。
- 当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件,例如日志收集或数据同步场景。
- Kubernetes 卷具有明确的生命周期,与使用它的 Pod 相同,卷比 Pod 中运行的任何容器的存活期都长,在容器重新启动时数据也会得到保留。
- 当一个 Pod 不再存在时,卷也将不再存在,因为卷的生命周期与 Pod 绑定。
- Kubernetes 可以支持许多类型的卷(如 emptyDir、hostPath、PVC 等),Pod 也能同时使用任意数量的卷。
- 卷不能挂载到其他卷,也不能与其他卷有硬链接,Pod 中的每个容器必须独立地指定每个卷的挂载位置。
官网:https://kubernetes.io/zh/docs/concepts/storage/volumes/
Kubernetes 支持的卷的类型
- 临时存储:emptyDir、gitRepo(以弃用)
- 本地持久存储:hostPath、local
- 网络存储(外部存储系统):nfs、fc(fibre channel,光钎通道)、iscsi(IP 存储)、glusterfs、cephfs、flocker、quobyte、storageos、rbd
- 云提供商存储:awsElasticBlockStore、azureDisk、azureFile、gcePersistentDisk、vsphereVolume、cinder、portworxVolume、scaleIO
- Kubernetes 特殊类型:configMap、secret、downwardAPI、persistentVolumeClaim、projected、csi(容器存储接口)、flexVolume(以弃用)
(2)emptyDir 卷
因为容器中的文件是临时存放的,当容器崩溃或重启时数据会丢失,而且同一个 Pod 中的多个容器需要共享文件,并且 emptyDir 卷的生命周期与 Pod 绑定,Pod 删除时数据才被清理。
所以使用 emptyDir 卷可以实现 Pod 内多个容器之间的文件共享,以及临时数据的缓存存储,同时通过 medium: Memory 可以获得更高的读写性能。
① emptyDir 卷简介
功能
- 当 Pod 指定到某个节点上时,首先创建的是一个 emptyDir 卷,并且只要 Pod 在该节点上运行,卷就一直存在。
- 卷最初是空的,尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,但这些容器都可以读写 emptyDir 卷中相同的文件。
- 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会永久删除。
使用场景
- 缓存空间,例如基于磁盘的归并排序。
- 耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
- 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。
② emptyDir 卷实验
生成 emptyDir 卷的配置文件
[root@master-N1 storage]# mkdir volumes
[root@master-N1 storage]# cd volumes/
[root@master-N1 volumes]# kubectl run empty --image busybox --dry-run=client -o yaml > empty.yml
[root@master-N1 volumes]# vim empty.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: empty
6 name: empty
7 spec:
8 containers:
9 - image: busybox
10 name: busybox
11 command:
12 - /bin/sh
13 - -c
14 - sleep 10000
15 volumeMounts:
16 - mountPath: /cache
17 name: cache-empty
18
19 - image: nginx:1.23
20 name: nginx
21 volumeMounts:
22 - mountPath: /usr/share/nginx/html
23 name: cache-empty
24
25 volumes:
26 - name: cache-empty
27 emptyDir:
28 medium: Memory
29 sizeLimit: 100Mi

创建 pod,测试 emptyDir 卷的功能
[root@master-N1 volumes]# kubectl apply -f empty.yml
[root@master-N1 volumes]# kubectl get pods -o wide
[root@master-N1 volumes]# curl 10.244.2.9
[root@master-N1 volumes]# kubectl exec -it pods/empty -c busybox -- /bin/sh
/ # ls
/ # cd cache/
/cache # echo hello timinglee > index.html
/cache # dd if=/dev/zero of=bigfile bs=1M count=99
/cache # dd if=/dev/zero of=bigfile bs=1M count=100
[root@master-N1 volumes]# curl 10.244.2.9
# 实验结束
[root@master-N1 volumes]# kubectl delete -f empty.yml --force

(3)hostPath 卷
因为容器中的文件是临时存放的,当容器崩溃或重启时数据会丢失,而且 Pod 中的容器需要访问宿主机上的特定文件或目录(如读取节点日志、访问 Docker socket),并且某些应用需要将数据持久化到宿主机磁盘上,即使 Pod 被删除重建,数据仍然保留在宿主机上。
所以使用 hostPath 卷可以让容器直接访问宿主机的文件系统,实现容器与宿主机之间的文件共享,以及将数据持久化到宿主机磁盘,适用于需要感知宿主机环境的场景(如监控、日志收集、设备访问)。
① hostPath 卷简介
功能:hostPath 卷能将主机节点文件系统上的文件或目录挂载到您的 Pod 中,不会因为 Pod 关闭而被删除。
hostPath 的一些用法
- 运行一个需要访问 Docker 引擎内部机制的容器,挂载 /var/lib/docker 路径。
- 在容器中运行 cAdvisor(监控)时,以 hostPath 方式挂载 /sys。
- 允许 Pod 指定给定的 hostPath 在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。
hostPath 的安全隐患
- 具有相同配置(例如从 podTemplate 创建)的多个 Pod 会由于节点上文件的不同而在不同节点上有不同的行为。
- 当 Kubernetes 按照计划添加资源感知的调度时,这类调度机制将无法考虑由 hostPath 使用的资源。
- 基础主机上创建的文件或目录只能由 root 用户写入。您需要在特权容器中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath 卷。
hostPath 卷的缺点
- 因为 hostPath 卷绑定的是特定节点的本地目录,当 Pod 因节点故障或调度策略被移动到其他节点时,新节点上没有旧节点上的数据,导致 Pod 无法访问之前持久化的文件。
② hostPath 卷实验
生成 hostPath 卷的配置文件
hostPath type 参数说明
-
DirectoryOrCreate:如果目录不存在,则创建(权限 755)
-
Directory:目录必须已存在
-
FileOrCreate:如果文件不存在,则创建(权限 644)
-
File:文件必须已存在
-
Socket:UNIX socket 必须已存在
-
CharDevice:字符设备必须已存在
-
BlockDevice:块设备必须已存在
[root@master-N1 volumes]# kubectl run hostpath --image myapp:v1 --dry-run=client -o yaml > hostpath.yml
[root@master-N1 volumes]# vim hostpath.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: hostpath
6 name: hostpath
7 spec:
8 containers:
9 - image: myapp:v1
10 name: myapp-v1
11 volumeMounts:
12 - mountPath: /usr/share/nginx/html
13 name: cache-host
14
15 volumes:
16 - name: cache-host
17 hostPath:
18 path: /cache
19 type: DirectoryOrCreate

创建 pod,测试 hostPath 卷的功能
[root@master-N1 volumes]# kubectl apply -f hostpath.yml
[root@master-N1 volumes]# kubectl get pods -o wide
[root@master-N1 volumes]# curl 10.244.2.10
[root@master-N1 volumes]# ssh root@worker-n2
[root@worker-N2 ~]# ll /cache/
[root@worker-N2 ~]# echo "worker-N2 - 192.168.153.20" > /cache/index.html
[root@worker-N2 ~]# exit
[root@master-N1 volumes]# curl 10.244.2.10
# 实验结束
[root@master-N1 volumes]# kubectl delete -f hostpath.yml

(4)nfs 卷
因为容器中的文件是临时存放的,当容器崩溃或重启时数据会丢失,而且 Pod 中的多个容器需要共享文件,并且 Pod 可能会被调度到不同节点上运行,如果使用 hostPath 卷,Pod 迁移后数据就无法访问。
所以使用 NFS 卷可以实现跨节点的数据共享和持久化存储,多个 Pod 无论运行在哪个节点上,都可以访问到相同的 NFS 服务器上的数据。
nfs 卷的功能
- NFS 卷允许将一个现有的 NFS 服务器上的目录挂载到 Kubernetes 中的 Pod 中,这对于在多个 Pod 之间共享数据或持久化存储数据非常有用。
- 例如,如果有多个容器需要访问相同的数据集,或者需要将容器中的数据持久保存到外部存储,NFS 卷可以提供一种方便的解决方案。
① 准备 nfs 卷环境
准备一台新主机(worker-N3),在新主机上准备 nfs 卷环境;然后再给所有工作节点安装 nfs-utlis
[root@worker-N3 ~]# dnf install nfs-utils -y
[root@worker-N3 ~]# systemctl enable --now nfs-server.service
[root@worker-N3 ~]# mkdir /share
[root@worker-N3 ~]# chmod 777 /share/
[root@worker-N3 ~]# vim /etc/exports
1 /share *(sync,rw,no_root_squash)
[root@worker-N3 ~]# cat /etc/exports
[root@worker-N3 ~]# exportfs -rv
[root@worker-N3 ~]# showmount -e
[root@worker-N3 ~]# for i in 10 20 ;do ssh root@192.168.153.$i dnf install nfs-utils -y ;done
[root@worker-N3 ~]# for i in 10 20 ;do ssh root@192.168.153.$i showmount -e 192.168.153.30 ;done

② 部署 nfs 卷
生成 nfs 的 pod 配置文件
[root@master-N1 volumes]# kubectl run web --image nginx:1.23 --dry-run=client -o yaml > nfs.yml
[root@master-N1 volumes]# vim nfs.yml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: web
6 name: web
7 spec:
8 nodeName: worker-n1
9 containers:
10 - image: nginx:1.23
11 name: nginx
12 volumeMounts:
13 - mountPath: /usr/share/nginx/html
14 name: share-nfs
15
16 volumes:
17 - name: share-nfs
18 nfs:
19 server: 192.168.153.30
20 path: /share

③ 测试 nfs 卷功能
启动 pod,测试 nfs 卷的功能
[root@master-N1 volumes]# kubectl apply -f nfs.yml
[root@master-N1 volumes]# kubectl get pods -o wide
[root@master-N1 volumes]# curl 10.244.1.5
[root@master-N1 volumes]# ssh root@worker-n3
[root@worker-N3 ~]# ls /share/
[root@worker-N3 ~]# echo hello timinglee > /share/index.html
[root@worker-N3 ~]# exit
[root@master-N1 volumes]# curl 10.244.1.5
[root@master-N1 volumes]# kubectl delete -f nfs.yml

修改 pod 运行节点,观察访问 nginx 的
[root@master-N1 volumes]# vim nfs.yml
8 nodeName: worker-n2
[root@master-N1 volumes]# kubectl apply -f nfs.yml
[root@master-N1 volumes]# kubectl get pods -o wide
[root@master-N1 volumes]# curl 10.244.2.3
# 实验结束
[root@master-N1 volumes]# kubectl delete -f nfs.yml

(5)PersistentVolume 持久卷
因为容器中的文件是临时存放的,当容器崩溃或重启时数据会丢失,而且 Pod 中的多个容器需要共享文件,并且 Pod 可能会被调度到不同节点上运行,如果使用 hostPath 卷,Pod 迁移后数据就无法访问,而 NFS 卷虽然解决了跨节点共享问题,但需要管理员手动管理存储资源,配置复杂且不够灵活。
所以需要学习 PersistentVolume(持久卷),它将存储资源与集群资源统一管理,管理员预先配置存储资源(PV),用户通过 PersistentVolumeClaim(PVC)申请使用,实现了存储的供给方和使用方分离,简化了存储管理,并且支持动态扩容和多种存储后端。
① 静态持久卷与静态持久卷声明
PersistentVolume(持久卷,简称PV)
- PV 是集群内由管理员提供的网络存储的一部分,作为集群中的一种资源,是一种 volume 插件,但它的生命周期与使用它的 Pod 相互独立。
- PV 这个 API 对象捕获了诸如 NFS、ISCSI 或其他云存储系统的实现细节,将底层存储技术抽象为集群可管理的资源。
- PV 有两种提供方式:静态和动态。
- 静态 PV 是指集群管理员创建多个 PV,它们携带着真实存储的详细信息,存在于 Kubernetes API 中,并可用于存储使用。
- 动态 PV 是指当管理员创建的静态 PV 都不匹配用户的 PVC 时,集群可能会尝试专门地供给 volume 给 PVC,这种供给方式基于 StorageClass。
PersistentVolumeClaim(持久卷声明,简称PVC)
- PVC 是用户的一种存储请求,与 Pod 类似(Pod 消耗 Node 资源,而 PVC 消耗 PV 资源)。
- Pod 能够请求特定的资源(如 CPU 和内存),PVC 能够请求指定的大小和访问模式的持久卷配置。
- PVC 与 PV 的绑定是一对一的映射。如果没有找到匹配的 PV,那么 PVC 会无限期地处于 unbound(未绑定)状态。
② Volumes 技术规格
volumes 访问模式
- ReadWriteOnce:该 volume 只能被单个节点以读写的方式映射。
- ReadOnlyMany:该 volume 可以被多个节点以只读方式映射。
- ReadWriteMany:该 volume 可以被多个节点以读写的方式映射。
- 在命令行中,访问模式可以简写为:RWO 代表 ReadWriteOnce,ROX 代表 ReadOnlyMany,RWX 代表 ReadWriteMany。
volumes 回收策略
- Retain:保留,需要手动回收。
- Recycle:回收,自动删除卷中数据(在当前版本中已经废弃)。
- Delete:删除,相关联的存储资产(如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷)都会被删除。
- 注意:只有 NFS 和 HostPath 支持回收利用,AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷支持删除操作。
volumes 状态说明
- Available:卷是一个空闲资源,尚未绑定到任何申领。
- Bound:该卷已经绑定到某申领。
- Released:所绑定的申领已被删除,但是关联存储资源尚未被集群回收。
- Failed:卷的自动回收操作失败。
③ 建立静态持久卷
在存储服务器(worker-N3)上创建存储目录
[root@worker-N3 ~]# dnf install nfs-utils -y
[root@worker-N3 ~]# systemctl enable --now nfs-server
[root@worker-N3 ~]# mkdir -p /share/pv{1..3}
[root@worker-N3 ~]# vim /etc/exports
1 /share *(sync,rw,no_root_squash)
[root@worker-N3 ~]# exportfs -rv
[root@worker-N3 ~]# for i in 10 20 ;do ssh root@192.168.153.$i dnf install nfs-utils -y ;done
[root@worker-N3 ~]# for i in 10 20 ;do ssh root@192.168.153.$i showmount -e 192.168.153.30 ;done
创建 pv 的 yaml 文件
# 窗口1
[root@master-N1 volumes]# vim pv.yml
1 apiVersion: v1
2 kind: PersistentVolume
3 metadata:
4 name: pv1
5 spec:
6 capacity:
7 storage: 5Gi
8 volumeMode: Filesystem
9 accessModes:
10 - ReadWriteOnce
11 persistentVolumeReclaimPolicy: Retain
12 storageClassName: nfs
13 nfs:
14 path: /share/pv1
15 server: 192.168.153.30
16
17 ---
18
19 apiVersion: v1
20 kind: PersistentVolume
21 metadata:
22 name: pv2
23 spec:
24 capacity:
25 storage: 10Gi
26 volumeMode: Filesystem
27 accessModes:
28 - ReadWriteMany
29 persistentVolumeReclaimPolicy: Retain
30 storageClassName: nfs
31 nfs:
32 path: /share/pv2
33 server: 192.168.153.30
34
35 ---
36
37 apiVersion: v1
38 kind: PersistentVolume
39 metadata:
40 name: pv3
41 spec:
42 capacity:
43 storage: 15Gi
44 volumeMode: Filesystem
45 accessModes:
46 - ReadOnlyMany
47 persistentVolumeReclaimPolicy: Retain
48 storageClassName: nfs
49 nfs:
50 path: /share/pv3
51 server: 192.168.153.30
# 窗口2
[root@master-N1 volumes]# kubectl api-resources | grep pv
[root@master-N1 volumes]# kubectl explain PersistentVolume
[root@master-N1 volumes]# kubectl explain PersistentVolume.metadata
[root@master-N1 volumes]# kubectl explain PersistentVolume.spec
[root@master-N1 volumes]# kubectl apply -f pv.yml
[root@master-N1 volumes]# kubectl get pv


创建 pvc 的 yaml 文件
pvc 匹配 pv 的规则是最小适应规则
-
storageClassName:必须相同
-
accessModes:PVC 的访问模式必须包含在 PV 的访问模式中
-
容量:PV 的容量必须大于或等于 PVC 请求的容量
窗口1
[root@master-N1 volumes]# vim pvc.yml
1 apiVersion: v1
2 kind: PersistentVolumeClaim
3 metadata:
4 name: pvc1
5 spec:
6 storageClassName: nfs
7 accessModes:
8 - ReadWriteOnce
9 resources:
10 requests:
11 storage: 1Gi
12
13 ---
14
15 apiVersion: v1
16 kind: PersistentVolumeClaim
17 metadata:
18 name: pvc2
19 spec:
20 storageClassName: nfs
21 accessModes:
22 - ReadWriteMany
23 resources:
24 requests:
25 storage: 10Gi
26
27 ---
28
29 apiVersion: v1
30 kind: PersistentVolumeClaim
31 metadata:
32 name: pvc3
33 spec:
34 storageClassName: nfs
35 accessModes:
36 - ReadOnlyMany
37 resources:
38 requests:
39 storage: 15Gi窗口2
[root@master-N1 volumes]# kubectl api-resources | grep pvc
[root@master-N1 volumes]# kubectl explain PersistentVolumeClaim
[root@master-N1 volumes]# kubectl apply -f pvc.yml
[root@master-N1 volumes]# kubectl get pvc
[root@master-N1 volumes]# kubectl get pv


测试静态存储卷
[root@master-N1 volumes]# kubectl run web --image nginx:1.23 --dry-run=client -o yaml > pvpod.yaml
[root@master-N1 volumes]# vim pvpod.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: web
6 name: web
7 spec:
8 containers:
9 - image: nginx:1.23
10 name: nginx
11 volumeMounts:
12 - mountPath: /usr/share/nginx/html
13 name: share-pv1
14
15 volumes:
16 - name: share-pv1
17 persistentVolumeClaim:
18 claimName: pvc1
[root@master-N1 volumes]# kubectl apply -f pvpod.yaml
[root@master-N1 volumes]# kubectl get pods -o wide
[root@master-N1 volumes]# curl 10.244.3.2
[root@master-N1 volumes]# ssh root@192.168.153.30 "echo hello timinglee > /share/pv1/index.html"
[root@master-N1 volumes]# curl 10.244.3.2
# 实验结束
[root@master-n1 volumes]# kubectl delete -f pvpod.yaml
[root@master-n1 volumes]# kubectl delete -f pvc.yml
[root@master-n1 volumes]# kubectl delete -f pv.yml


4. 存储类 StorageClass
因为管理员手动创建 PV 的方式在大规模集群中维护成本高,每新增一个 PVC 都需要管理员提前准备好对应的 PV,并且无法根据 PVC 的需求动态匹配合适的存储资源。
所以需要学习 StorageClass,它支持动态存储分配,当用户创建 PVC 时,StorageClass 会自动调用对应的存储分配器创建符合需求的 PV,无需管理员提前手动创建,大大降低了存储管理和维护的复杂度。
(1)StorageClass 概述
官网:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
① StorageClass 说明
- StorageClass 提供了一种定义存储类别的方法,不同类别可以对应不同的服务质量等级、备份策略或其他管理策略。
- 每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段,这些字段在 StorageClass 需要动态分配 PersistentVolume 时会发挥作用。
② StorageClass 属性
属性说明:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/
Provisioner(存储分配器)
- 用于决定使用哪个卷插件来分配 PV,此字段为必填项。
- 可以指定 Kubernetes 内部自带的分配器,也可以使用外部分配器。
- 外部分配器的代码仓库地址为 kubernetes-incubator/external-storage,其中包含 NFS、Ceph 等常见存储的实现。
Reclaim Policy(回收策略)
- 通过 reclaimPolicy 字段指定动态创建的 PersistentVolume 的回收策略,可选值为 Delete 或 Retain。
- 如果未指定,默认值为 Delete。
③ 存储分配器 NFS Client Provisioner
源码地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
NFS Client Provisioner 是一种自动分配器(automatic provisioner),它利用外部的 NFS 作为存储后端,能够自动创建 PV 以及对应的 PVC。
- 注意它本身不提供 NFS 存储服务,部署前需要先有一套外部 NFS 存储服务。
- 在 NFS 服务器上,PV 对应的目录会按照 {namespace}-{pvcName}-${pvName} 的命名格式自动创建。
- 当 PV 被回收时,对应的目录会被重命名为 archieved-{namespace}-{pvcName}-${pvName},保留在 NFS 服务器上。
(2)部署动态持久卷
① 创建 sa 并授权
利用 rbac 权限资源生成 NFS Client Provisioner 的动态存储分配功能的配置文件
[root@master-n1 volumes]# mkdir -p /root/storage/class/
[root@master-n1 volumes]# cd /root/storage/class/
[root@master-n1 class]# curl -s https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/rbac.yaml > storage-sa.yml
[root@master-n1 class]# vim storage-sa.yml
1 apiVersion: v1
2 kind: Namespace
3 metadata:
4 name: nfs-client-provisioner
5
6 ---
7
8 apiVersion: v1
9 kind: ServiceAccount
10 metadata:
11 name: nfs-client-provisioner
12 namespace: nfs-client-provisioner
13
14 ---
15
16 kind: ClusterRole
17 apiVersion: rbac.authorization.k8s.io/v1
18 metadata:
19 name: nfs-client-provisioner-runner
20 rules:
21 - apiGroups: [""]
22 resources: ["nodes"]
23 verbs: ["get", "list", "watch"]
24 - apiGroups: [""]
25 resources: ["persistentvolumes"]
26 verbs: ["get", "list", "watch", "create", "delete"]
27 - apiGroups: [""]
28 resources: ["persistentvolumeclaims"]
29 verbs: ["get", "list", "watch", "update"]
30 - apiGroups: ["storage.k8s.io"]
31 resources: ["storageclasses"]
32 verbs: ["get", "list", "watch"]
33 - apiGroups: [""]
34 resources: ["events"]
35 verbs: ["create", "update", "patch"]
36
37 ---
38
39 kind: ClusterRoleBinding
40 apiVersion: rbac.authorization.k8s.io/v1
41 metadata:
42 name: run-nfs-client-provisioner
43 subjects:
44 - kind: ServiceAccount
45 name: nfs-client-provisioner
46 namespace: nfs-client-provisioner
47 roleRef:
48 kind: ClusterRole
49 name: nfs-client-provisioner-runner
50 apiGroup: rbac.authorization.k8s.io
51
52 ---
53
54 kind: Role
55 apiVersion: rbac.authorization.k8s.io/v1
56 metadata:
57 name: leader-locking-nfs-client-provisioner
58 namespace: nfs-client-provisioner
59 rules:
60 - apiGroups: [""]
61 resources: ["endpoints"]
62 verbs: ["get", "list", "watch", "create", "update", "patch"]
63
64 ---
65
66 kind: RoleBinding
67 apiVersion: rbac.authorization.k8s.io/v1
68 metadata:
69 name: leader-locking-nfs-client-provisioner
70 namespace: nfs-client-provisioner
71 subjects:
72 - kind: ServiceAccount
73 name: nfs-client-provisioner
74 namespace: nfs-client-provisioner
75 roleRef:
76 kind: Role
77 name: leader-locking-nfs-client-provisioner
78 apiGroup: rbac.authorization.k8s.io


创建 rbac 权限资源
[root@master-n1 class]# kubectl apply -f storage-sa.yml
[root@master-n1 class]# kubectl -n nfs-client-provisioner get sa

② 部署应用
找到"Step 4: Configure the NFS subdir external provisioner",模仿添加 deployment.yaml 的配置文件

[root@master-n1 class]# vim storage-dep.yml
1 kind: Deployment
2 apiVersion: apps/v1
3 metadata:
4 name: nfs-client-provisioner
5 namespace: nfs-client-provisioner
6 spec:
7 replicas: 1
8 selector:
9 matchLabels:
10 app: nfs-client-provisioner
11 strategy:
12 type: Recreate
13 template:
14 metadata:
15 labels:
16 app: nfs-client-provisioner
17 spec:
18 serviceAccountName: nfs-client-provisioner
19 containers:
20 - name: nfs-client-provisioner
21 image: sig-storage/nfs-subdir-external-provisioner:v4.0.2
22 volumeMounts:
23 - name: nfs-client-root
24 mountPath: /persistentvolumes
25 env:
26 - name: PROVISIONER_NAME
27 value: k8s-sigs.io/nfs-subdir-external-provisioner
28 - name: NFS_SERVER
29 value: 192.168.153.30
30 - name: NFS_PATH
31 value: /share
32
33 volumes:
34 - name: nfs-client-root
35 nfs:
36 server: 192.168.153.30
37 path: /share

创建项目并上传镜像
- NFS Client Provisioner 所需的镜像链接:registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2

[root@master-n1 class]# cat storage-dep.yml | grep -n image
[root@master-n1 class]# docker pull registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
[root@master-n1 class]# docker tag registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 reg.timinglee.org/sig-storage/nfs-subdir-external-provisioner:v4.0.2
[root@master-n1 class]# docker push reg.timinglee.org/sig-storage/nfs-subdir-external-provisioner:v4.0.2
[root@master-n1 class]# docker rmi registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
[root@master-n1 class]# vim storage-dep.yml
21 image: sig-storage/nfs-subdir-external-provisioner:v4.0.2
[root@master-n1 class]# kubectl apply -f storage-dep.yml
[root@master-n1 class]# kubectl -n nfs-client-provisioner get pods

③ 创建存储类
archiveOnDelete 参数说明
-
"true"表示PVC 删除时,NFS 服务器上对应的目录会被重命名为"archived-*"保留下来
-
"false"表示PVC 删除时,NFS 服务器上对应的目录会被直接删除
[root@master-n1 class]# curl -s https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/class.yaml > storage-class.yml
[root@master-n1 class]# cat storage-dep.yml | grep -n env -A 5
[root@master-n1 class]# vim storage-class.yml
1 apiVersion: storage.k8s.io/v1
2 kind: StorageClass
3 metadata:
4 name: nfs-client
5 provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
6 parameters:
7 archiveOnDelete: "false"
[root@master-n1 class]# kubectl apply -f storage-class.yml
[root@master-n1 class]# kubectl get storageclasses.storage.k8s.io


④ 创建 pvc
[root@master-n1 class]# kubectl get storageclasses.storage.k8s.io
[root@master-n1 class]# vim pvc.yml
1 apiVersion: v1
2 kind: PersistentVolumeClaim
3 metadata:
4 name: test-pvc
5 spec:
6 storageClassName: nfs-client
7 accessModes:
8 - ReadWriteMany
9 resources:
10 requests:
11 storage: 1Gi
[root@master-n1 class]# kubectl get pv
[root@master-n1 class]# kubectl get pvc
[root@master-n1 class]# kubectl apply -f pvc.yml
[root@master-n1 class]# kubectl get pv
[root@master-n1 class]# kubectl get pvc
[root@master-n1 class]# ssh root@192.168.153.30 "ls /share"


测试删除 pvc
[root@master-n1 class]# kubectl delete -f pvc.yml
[root@master-n1 class]# kubectl get pv
[root@master-n1 class]# kubectl get pvc
[root@master-n1 class]# ssh root@192.168.153.30 "ls /share"

⑤ 设置默认存储类
注释"storageClassName",创建 pvc 时,会一直处于 Pending 等待绑定状态
[root@master-n1 class]# vim pvc.yml
6 #storageClassName: nfs-client
[root@master-n1 class]# cat pvc.yml | grep storageClass
[root@master-n1 class]# kubectl apply -f pvc.yml
[root@master-n1 class]# kubectl get pvc
[root@master-n1 class]# kubectl delete -f pvc.yml

设置默认存储类
属性说明:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/

[root@master-n1 class]# kubectl edit sc nfs-client
11 storageclass.kubernetes.io/is-default-class: "true"
[root@master-n1 class]# kubectl get sc
[root@master-n1 class]# kubectl apply -f pvc.yml
[root@master-n1 class]# kubectl get pvc
# 实验结束
[root@master-n1 class]# kubectl delete -f pvc.yml
[root@master-n1 class]# kubectl delete -f storage-class.yml
[root@master-n1 class]# kubectl delete -f storage-dep.yml
[root@master-n1 class]# kubectl delete -f storage-sa.yml


5. StatefulSet 控制器
因为 Deployment 控制器管理的 Pod 是无状态的,每个 Pod 都是完全相同的副本,Pod 的名称是随机生成的,重建后名称和网络标识都会改变,并且所有 Pod 共享同一个存储卷,无法为每个 Pod 单独绑定持久化存储。
所以需要学习 StatefulSet 控制器,它为每个 Pod 分配固定且唯一的名称(从 0 开始编号),Pod 重建后名称和网络标识保持不变,并且每个 Pod 可以绑定独立的持久化存储卷,适合管理需要稳定网络标识和独立存储的有状态应用,如数据库(MySQL、Redis)、消息队列(Kafka)等。
(1)StatefulSet 简介
① StatefulSet 功能
- StatefulSet 是专门为管理有状态服务而设计的工作负载控制器。
- StatefulSet 将应用的状态抽象为以下两种类型:
- 拓扑状态:应用的各个实例必须按照特定的顺序启动,并且新创建的 Pod 需要与之前 Pod 的网络标识保持一致。
- 存储状态:应用的多个实例各自绑定到不同的持久化存储数据,彼此之间不共享。
- StatefulSet 会对它管理的所有 Pod 进行编号,编号规则为:(StatefulSet 名称)-(序号),序号从 0 开始递增。
- 当 Pod 被删除后重建时,重建后的 Pod 会保留原来的网络标识。Pod 的拓扑状态按照"名称 + 编号"的方式固定下来,并且每个 Pod 都拥有一个唯一且固定的访问入口,对应一个稳定的 DNS 记录。
② StatefulSet 组成
- Headless Service:用于定义 Pod 的网络标识,并为每个 Pod 生成可解析的 DNS 记录。
- volumeClaimTemplates:用于创建 PVC,可以指定 PVC 的名称、大小等属性,PVC 会自动创建出来,并由指定的 StorageClass 提供后端存储。
- StatefulSet:用于定义和管理 Pod 的名称、启动顺序等行为。
(2)StatefulSet 部署
① 创建动态持久卷
生成 rbac 权限配置文件
[root@master-n1 class]# mkdir -p /root/storage/stateful
[root@master-n1 class]# cd /root/storage/stateful
[root@master-n1 stateful]# vim storage-sa.yml
1 apiVersion: v1
2 kind: Namespace
3 metadata:
4 name: nfs-client-provisioner
5
6 ---
7
8 apiVersion: v1
9 kind: ServiceAccount
10 metadata:
11 name: nfs-client-provisioner
12 namespace: nfs-client-provisioner
13
14 ---
15
16 kind: ClusterRole
17 apiVersion: rbac.authorization.k8s.io/v1
18 metadata:
19 name: nfs-client-provisioner-runner
20 rules:
21 - apiGroups: [""]
22 resources: ["nodes"]
23 verbs: ["get", "list", "watch"]
24 - apiGroups: [""]
25 resources: ["persistentvolumes"]
26 verbs: ["get", "list", "watch", "create", "delete"]
27 - apiGroups: [""]
28 resources: ["persistentvolumeclaims"]
29 verbs: ["get", "list", "watch", "update"]
30 - apiGroups: ["storage.k8s.io"]
31 resources: ["storageclasses"]
32 verbs: ["get", "list", "watch"]
33 - apiGroups: [""]
34 resources: ["events"]
35 verbs: ["create", "update", "patch"]
36
37 ---
38
39 kind: ClusterRoleBinding
40 apiVersion: rbac.authorization.k8s.io/v1
41 metadata:
42 name: run-nfs-client-provisioner
43 subjects:
44 - kind: ServiceAccount
45 name: nfs-client-provisioner
46 namespace: nfs-client-provisioner
47 roleRef:
48 kind: ClusterRole
49 name: nfs-client-provisioner-runner
50 apiGroup: rbac.authorization.k8s.io
51
52 ---
53
54 kind: Role
55 apiVersion: rbac.authorization.k8s.io/v1
56 metadata:
57 name: leader-locking-nfs-client-provisioner
58 namespace: nfs-client-provisioner
59 rules:
60 - apiGroups: [""]
61 resources: ["endpoints"]
62 verbs: ["get", "list", "watch", "create", "update", "patch"]
63
64 ---
65
66 kind: RoleBinding
67 apiVersion: rbac.authorization.k8s.io/v1
68 metadata:
69 name: leader-locking-nfs-client-provisioner
70 namespace: nfs-client-provisioner
71 subjects:
72 - kind: ServiceAccount
73 name: nfs-client-provisioner
74 namespace: nfs-client-provisioner
75 roleRef:
76 kind: Role
77 name: leader-locking-nfs-client-provisioner
78 apiGroup: rbac.authorization.k8s.io
生成 deployment 控制器配置文件
[root@master-n1 stateful]# vim storage-dep.yml
1 kind: Deployment
2 apiVersion: apps/v1
3 metadata:
4 name: nfs-client-provisioner
5 namespace: nfs-client-provisioner
6 spec:
7 replicas: 1
8 selector:
9 matchLabels:
10 app: nfs-client-provisioner
11 strategy:
12 type: Recreate
13 template:
14 metadata:
15 labels:
16 app: nfs-client-provisioner
17 spec:
18 serviceAccountName: nfs-client-provisioner
19 containers:
20 - name: nfs-client-provisioner
21 image: sig-storage/nfs-subdir-external-provisioner:v4.0.2
22 volumeMounts:
23 - name: nfs-client-root
24 mountPath: /persistentvolumes
25 env:
26 - name: PROVISIONER_NAME
27 value: k8s-sigs.io/nfs-subdir-external-provisioner
28 - name: NFS_SERVER
29 value: 192.168.153.30
30 - name: NFS_PATH
31 value: /share
32
33 volumes:
34 - name: nfs-client-root
35 nfs:
36 server: 192.168.153.30
37 path: /share
生成 storageclass 配置文件
[root@master-n1 stateful]# vim storage-class.yml
1 apiVersion: storage.k8s.io/v1
2 kind: StorageClass
3 metadata:
4 name: nfs-client
5 provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
6 parameters:
7 archiveOnDelete: "false"
创建动态持久卷存储系统
[root@master-n1 stateful]# kubectl apply -f storage-sa.yml
[root@master-n1 stateful]# kubectl apply -f storage-dep.yml
[root@master-n1 stateful]# kubectl apply -f storage-class.yml
[root@master-n1 stateful]# kubectl get sc
② 建立无头服务
[root@master-n1 stateful]# kubectl get svc
[root@master-n1 stateful]# kubectl create service clusterip --help
[root@master-n1 stateful]# kubectl create service clusterip clusterip-test --tcp 80:80 --clusterip="None" --dry-run=client -o yaml> headless.yaml
[root@master-n1 stateful]# vim headless.yaml
1 apiVersion: v1
2 kind: Service
3 metadata:
4 labels:
5 app: clusterip-test
6 name: clusterip-test
7 spec:
8 clusterIP: None
9 ports:
10 - name: webport
11 port: 80
12 protocol: TCP
13 targetPort: 80
14 selector:
15 app: webserver
16 type: ClusterIP
[root@master-n1 stateful]# kubectl apply -f headless.yaml
[root@master-n1 stateful]# kubectl get svc


③ 创建 StatefulSet 控制器
[root@master-n1 stateful]# kubectl get sc
[root@master-n1 stateful]# kubectl get svc
[root@master-n1 stateful]# kubectl create deployment webserver --image nginx:1.23 --replicas 1 --dry-run=client -o yaml > statefulset.yaml
[root@master-n1 stateful]# vim statefulset.yaml
1 apiVersion: apps/v1
2 kind: StatefulSet
3 metadata:
4 labels:
5 app: webserver
6 name: webserver
7 spec:
8 serviceName: "clusterip-test"
9 replicas: 1
10 selector:
11 matchLabels:
12 app: webserver
13 template:
14 metadata:
15 labels:
16 app: webserver
17 spec:
18 containers:
19 - image: nginx:1.23
20 name: nginx
21 volumeMounts:
22 - name: www
23 mountPath: /usr/share/nginx/html
24
25 volumeClaimTemplates:
26 - metadata:
27 name: www
28 spec:
29 storageClassName: nfs-client
30 accessModes:
31 - ReadWriteOnce
32 resources:
33 requests:
34 storage: 1Gi
[root@master-n1 stateful]# kubectl apply -f statefulset.yaml
[root@master-n1 stateful]# kubectl get statefulsets.apps
[root@master-n1 stateful]# kubectl get pods
[root@master-n1 stateful]# kubectl get pvc


④ StatefulSet 扩缩容
扩容 pod
DNS 名称
-
完整格式:{pod名称}.{service名称}.{命名空间}.svc.cluster.local
-
同命名空间简写:{pod名称}.{service名称}
-
跨命名空间:{pod名称}.{service名称}.{命名空间}
扩容pod
[root@master-n1 stateful]# kubectl get statefulsets.apps
[root@master-n1 stateful]# kubectl scale statefulset webserver --replicas 3
[root@master-n1 stateful]# kubectl get pods
[root@master-n1 stateful]# kubectl get pvc生成默认访问文件
[root@worker-n3 ~]# cd /share/
[root@worker-n3 share]# ll
[root@worker-n3 share]# echo web000 > default-www-webserver-0-pvc-561ebf34-53b6-4564-a6d2-b135f686de51/index.html
[root@worker-n3 share]# echo web111 > default-www-webserver-1-pvc-7b7ab1fd-2ce0-4151-8886-0ecbb7c2ba5c/index.html
[root@worker-n3 share]# echo web222 > default-www-webserver-2-pvc-2661247b-ae2b-411f-821a-7f30db8b8094/index.html测试访问域名
[root@master-n1 stateful]# kubectl get svc
[root@master-n1 stateful]# kubectl get pods -o wide
[root@master-n1 stateful]# curl 10.244.1.2
[root@master-n1 stateful]# curl webserver-0.clusterip-test
[root@master-n1 stateful]# kubectl run -it --rm testpod --image busyboxplus
[ root@testpod:/ ] curl 10.244.1.2 [ root@testpod:/ ] curl webserver-0.clusterip-test
[ root@testpod:/ ] curl 10.244.3.4 [ root@testpod:/ ] curl webserver-1.clusterip-test


缩容 pod
[root@master-n1 stateful]# kubectl get pods
[root@master-n1 stateful]# kubectl scale statefulset webserver --replicas 1
[root@master-n1 stateful]# kubectl get pods
[root@master-n1 stateful]# kubectl get pvc

⑤ 删除 StatefulSet 控制器
StatefulSet 控制器注意事项
-
为了保证数据持久性,当 StatefulSet 被删除时,其关联的 PVC 和 PV 通常也会被一起清理,导致后端存储数据丢失。
-
而将副本数缩容为 0,只会删除 Pod 实例,但保留 PVC、PV 以及 NFS 后端存储中的数据。
[root@master-n1 stateful]# kubectl get pvc
[root@master-n1 stateful]# kubectl delete -f statefulset.yaml
[root@master-n1 stateful]# kubectl get pods
[root@master-n1 stateful]# kubectl get pvc
[root@master-n1 stateful]# ssh root@192.168.153.30 "ls /share"实验结束
[root@master-n1 stateful]# kubectl delete -f statefulset.yaml
[root@master-n1 stateful]# kubectl delete -f headless.yaml
[root@master-n1 stateful]# kubectl delete -f storage-class.yml
[root@master-n1 stateful]# kubectl delete -f storage-dep.yml
[root@master-n1 stateful]# kubectl delete -f storage-sa.yml

七、K8S 网络通信
1. K8S 通信架构
k8s通过CNI接口接入其他插件来实现网络通讯。目前比较流行的插件有flannel,calico等。
CNI插件存放位置:# cat /etc/cni/net.d/10-flannel.conflist
插件使用的解决方案如下:
- 虚拟网桥,虚拟网卡,多个容器共用一个虚拟网卡进行通信。
- 多路复用:MacVLAN,多个容器共用一个物理网卡进行通信。
- 硬件交换:SR-IOV,一个物理网卡可以虚拟出多个接口,这个性能最好。
容器间通信:
- 同一个pod内的多个容器间的通信,通过lo即可实现pod之间的通信。
- 同一节点的pod之间通过cni网桥转发数据包。
- 不同节点的pod之间的通信需要网络插件支持。
pod和service通信:通过iptables或ipvs实现通信,ipvs取代不了iptables,因为ipvs只能做负载均衡,而做不了nat转换。
pod和外网通信:通过iptables的MASQUERADE实现源地址转换。
Service与集群外部客户端的通信:通过ingress、nodeport、loadbalancer等方式暴露服务。
2. flannel 网络插件
因为 Kubernetes 集群中的 Pod 分布在不同的节点上,每个 Pod 都有自己的 IP 地址,而默认情况下不同节点之间的 Pod 无法直接通信。
所以需要网络插件来实现跨节点 Pod 之间的网络互通,而 flannel 是一种简单易用的网络插件,它通过 VXLAN 隧道技术为每个节点分配独立的子网,并实现 Pod IP 在不同节点间的路由和转发,使得集群中的所有 Pod 就像在同一个扁平的网络中一样,可以直接通过 IP 地址互相访问。
(1)flannel 插件组成
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 插件 | 功能 |
| VXLAN | - 即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 本身支持的一种网络虚拟化技术。 - VXLAN 可以完全在内核态实现封装和解封装工作,从而通过"隧道"机制构建出覆盖网络(Overlay Network)。 |
| VTEP | - VXLAN Tunnel End Point(虚拟隧道端点),在 Flannel 中 VNI 的默认值是1,这也是为什么宿主机的 VTEP 设备都叫 flannel.1 的原因。 |
| Cni0 | - 网桥设备,每创建一个 Pod 都会创建一对 veth pair。 - 其中一端是 Pod 中的 eth0,另一端是 Cni0 网桥中的端口(网卡)。 |
| Flannel.1 | - TUN 设备(虚拟网卡),用来进行 VXLAN 报文的处理(封包和解包)。 - 不同 Node 之间的 Pod 数据流量都从 overlay 设备以隧道的形式发送到对端。 |
| Flanneld | - Flannel 在每个主机中运行 flanneld 作为 agent,它会为所在主机从集群的网络地址空间中获取一个小的网段 subnet,本主机内所有容器的 IP 地址都将从中分配。 - 同时 Flanneld 监听 K8S 集群数据库,为 flannel.1 设备提供封装数据时必要的 MAC、IP 等网络数据信息。 |
(2)flannel 通信原理

- 当容器发送 IP 包,通过 veth pair 发往 cni0 网桥,再路由到本机的 flannel.1 设备进行处理。
- VTEP 设备之间通过二层数据帧进行通信,源 VTEP 设备收到原始 IP 包后,在上面加上一个目的 MAC 地址,封装成一个内部数据帧,发送给目的 VTEP 设备。
- 内部数据帧并不能在宿主机的二层网络传输,Linux 内核还需要把它进一步封装成为宿主机的一个普通的数据帧,承载着内部数据帧通过宿主机的 eth0 进行传输。
- Linux 会在内部数据帧前面加上一个 VXLAN 头,VXLAN 头里有一个重要的标志叫 VNI ,它是 VTEP 识别某个数据帧是否应该归自己处理的重要标识。
- flannel.1 设备只知道另一端 flannel.1 设备的 MAC 地址,却不知道对应的宿主机地址是什么。在 Linux 内核里面,网络设备进行转发的依据来自 FDB 的转发数据库,这个 flannel.1 网桥对应的 FDB 信息,是由 flanneld 进程维护的。
- Linux 内核在 IP 包前面再加上二层数据帧头,把目标节点的 MAC 地址填进去,MAC 地址从宿主机的 ARP 表获取。
- 此时 flannel.1 设备就可以把这个数据帧从 eth0 发出去,再经过宿主机网络来到目标节点的 eth0 设备。目标主机内核网络栈会发现这个数据帧有 VXLAN Header ,并且 VNI 为 1 ,Linux 内核会对它进行拆包,拿到内部数据帧,根据 VNI 的值交给本机 flannel.1 设备处理。flannel.1 拆包后,根据路由表发往 cni0 网桥,最后到达目标容器。
查看 flannel 的转发
[root@master-n1 ~]# ip r
[root@master-n1 ~]# bridge fdb
[root@master-n1 ~]# arp -n

(3)flannel支持的后端模式
|---------------|------------------------------------------------------------|
| 网络模式 | 功能 |
| vxlan | - 报文封装,默认模式。 |
| Directrouting | - 直接路由,跨网段使用 vxlan ,同网段使用 host-gw 模式。 |
| host-gw | - 主机网关,性能好,但只能在二层网络中,不支持跨网络。 - 如果有成千上万的 Pod ,容易产生广播风暴,不推荐。 |
| UDP | - 性能差,不推荐。 |
修改后端模式
[root@master-n1 ~]# kubectl -n kube-flannel edit cm kube-flannel-cfg
32 "Type": "host-gw"
[root@master-n1 ~]# kubectl -n kube-flannel get pods
[root@master-n1 ~]# kubectl -n kube-flannel delete pods --all
[root@master-n1 ~]# kubectl -n kube-flannel get pods
[root@master-n1 ~]# ip r


3. calico 网络插件
因为 flannel 网络插件只实现了 Pod 之间的基本网络互通,不支持网络策略功能,无法对 Pod 之间的访问进行精细化控制(如拒绝或允许特定 Pod 访问)。
所以需要使用 Calico 网络插件,它除了提供 Pod 之间的网络互通外,还支持丰富的网络策略(Network Policy),可以实现 Pod 之间的访问控制、隔离不同命名空间的服务、以及更精细的安全防护。同时 Calico 采用纯三层的路由转发,性能更好,适用于对网络隔离和安全要求较高的生产环境。
(1)calico 简介
- Calico 采用纯三层的转发机制,中间没有任何 NAT 和 overlay ,转发效率最好。
- Calico 仅依赖三层路由可达,较少的依赖性使它能适配所有 VM 、 Container 、白盒或者混合环境场景。
calico 网络插件:projectcalico/calico: Cloud native networking and network security
(2)calico 网络架构

Felix:
- 监听 etcd 中心的存储获取事件。
- 用户创建 Pod 后,Felix 负责将其网卡、 IP 、 MAC 都设置好,然后在内核的路由表里面写入一条记录,注明这个 IP 应该到达这张网卡。
- 同样,如果用户指定了隔离策略,Felix 也会将该策略创建到 ACL 中,以实现隔离。
BIRD:
- 一个标准的路由程序。
- 它会从内核里面获取哪些 IP 的路由发生了变化,然后通过标准 BGP 的路由协议扩散到整个集群的其他宿主机上,让外界都知道这个 IP 在此节点上,路由时能够正确到达。
(3)部署 calico 网路插件
① 卸载 flannel 网络插件
[root@master-n1 ~]# kubectl -n kube-flannel delete pods --all --force --grace-period=0
[root@master-n1 ~]# kubectl delete -f kube-flannel.yml
[root@master-n1 ~]# for i in 100 10 20 ;do ssh root@192.168.153.$i "ls /etc/cni/net.d" ;done
[root@master-n1 ~]# for i in 100 10 20 ;do ssh root@192.168.153.$i "rm -rf /etc/cni/net.d/10-flannel.conflist" ;done

② 下载 calico 配置文件
calico 配置文件链接:https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/calico-typha.yaml




创建项目上传 calico 镜像

[root@master-n1 ~]# wget -O calico.yaml https://raw.githubusercontent.com/projectcalico/calico/v3.31.5/manifests/calico-typha.yaml
[root@master-n1 ~]# cat calico.yaml | grep -nw image
[root@master-n1 ~]# docker pull quay.io/calico/cni:v3.31.4
[root@master-n1 ~]# docker pull quay.io/calico/node:v3.31.4
[root@master-n1 ~]# docker pull quay.io/calico/kube-controllers:v3.31.4
[root@master-n1 ~]# docker pull quay.io/calico/typha:v3.31.4
[root@master-n1 ~]# docker tag quay.io/calico/cni:v3.31.4 reg.timinglee.org/calico/cni:v3.31.4
[root@master-n1 ~]# docker push reg.timinglee.org/calico/cni:v3.31.4
[root@master-n1 ~]# docker tag quay.io/calico/node:v3.31.4 reg.timinglee.org/calico/node:v3.31.4
[root@master-n1 ~]# docker push reg.timinglee.org/calico/node:v3.31.4
[root@master-n1 ~]# docker tag quay.io/calico/kube-controllers:v3.31.4 reg.timinglee.org/calico/kube-controllers:v3.31.4
[root@master-n1 ~]# docker push reg.timinglee.org/calico/kube-controllers:v3.31.4
[root@master-n1 ~]# docker tag quay.io/calico/typha:v3.31.4 reg.timinglee.org/calico/typha:v3.31.4
[root@master-n1 ~]# docker push reg.timinglee.org/calico/typha:v3.31.4
[root@master-n1 ~]# docker images --format "{{.Repository}}:{{.Tag}}" | grep quay.io | xargs docker rmi
③ 创建 calico 网络
[root@master-n1 ~]# vim calico.yaml
7116 image: calico/cni:v3.31.4
7144 image: calico/cni:v3.31.4
7188 image: calico/node:v3.31.4
7214 image: calico/node:v3.31.4
7447 image: calico/kube-controllers:v3.31.4
7540 - image: calico/typha:v3.31.4
7252 - name: CALICO_IPV4POOL_IPIP
7253 value: "Never"
7281 - name: CALICO_IPV4POOL_CIDR
7282 value: "10.244.0.0/16"
7284 - name: CALICO_AUTODETECTION_METHOD
7285 value: "interface=eth0"
[root@master-n1 ~]# cat calico.yaml | grep -nw image
[root@master-n1 ~]# cat calico.yaml | grep -n CALICO_IPV4POOL_IPIP -A 1
[root@master-n1 ~]# cat calico.yaml | grep -n CALICO_IPV4POOL_CIDR -A 1
[root@master-n1 ~]# cat calico.yaml | grep -n CALICO_AUTODETECTION_METHOD -A 1
[root@master-n1 ~]# kubectl apply -f calico.yaml
[root@master-n1 ~]# kubectl -n kube-system get pods

测试 calico 网络
[root@master-n1 ~]# kubectl -n kube-system get pods
[root@master-n1 ~]# kubectl run testpod --image myapp:v1
[root@master-n1 ~]# kubectl get pods -o wide
[root@master-n1 ~]# curl 10.244.54.195
# 实验结束
[root@master-n1 ~]# kubectl delete pods testpod

八、K8S 调度(Scheduling)
1. K8S 调度简介
(1)调度的作用
- 调度是指将未调度的 Pod 自动分配到集群中的节点的过程。
- 调度器通过 Kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。
- 调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。
(2)调度原理
- 创建 Pod:用户通过 Kubernetes API 创建 Pod 对象,并在其中指定 Pod 的资源需求、容器镜像等信息。
- 调度器监视 Pod:Kubernetes 调度器监视集群中的未调度 Pod 对象,并为其选择最佳的节点。
- 选择节点:调度器通过算法选择最佳的节点,并将 Pod 绑定到该节点上。调度器选择节点的依据包括节点的资源使用情况、Pod 的资源需求、亲和性和反亲和性等。
- 绑定 Pod 到节点:调度器将 Pod 和节点之间的绑定信息保存在 etcd 数据库中,以便节点可以获取 Pod 的调度信息。
- 节点启动 Pod:节点定期检查 etcd 数据库中的 Pod 调度信息,并启动相应的 Pod。如果节点故障或资源不足,调度器会重新调度 Pod,并将其绑定到其他节点上运行。
(3)调度器种类
- 默认调度器(Default Scheduler):是 Kubernetes 中的默认调度器,负责对新创建的 Pod 进行调度,并将 Pod 调度到合适的节点上。
- 自定义调度器(Custom Scheduler):是一种自定义的调度器实现,可以根据实际需求来定义调度策略和规则,以实现更灵活和多样化的调度功能。
- 扩展调度器(Extended Scheduler):是一种支持调度器扩展器的调度器实现,可以通过调度器扩展器来添加自定义的调度规则和策略,以实现更灵活和多样化的调度功能。
- kube-scheduler 是 Kubernetes 中的默认调度器,在 Kubernetes 运行后会自动在控制节点运行。
2. 调度方法
(1)nodeName 调度
因为默认调度器会自动选择节点,这个过程有延迟,且无法强制 Pod 跑到你指定的某台机器上。
所以当需要跳过调度器(比如调试问题、绑定特殊硬件、单节点环境)时,就必须使用 nodeName 调度,用它直接"点名"节点来运行 Pod。
① nodeName 简介
nodeName 是节点选择约束的最简单方法,但一般不推荐使用。
如果 nodeName 在 PodSpec 中指定了,则它优先于其他的节点选择方法。
使用 nodeName 来选择节点时存在以下限制:
- 如果指定的节点不存在,Pod 将无法被调度。
- 如果指定的节点没有足够的资源来容纳 Pod,则 Pod 调度会失败。
- 云环境中的节点名称并非总是可预测或稳定的。
② 部署 nodeName 调度
设置指定运行 pod 节点
[root@master-n1 ~]# mkdir scheduler
[root@master-n1 ~]# cd scheduler/
[root@master-n1 scheduler] kubectl run nodename-pod --image myapp:v1 --dry-run=client -o yaml > nodename-pod.yaml
[root@master-n1 scheduler]# vim nodename-pod.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: nodename-pod
6 name: nodename-pod
7 spec:
8 # nodeName:
9 containers:
10 - image: myapp:v1
11 name: myapp
[root@master-n1 scheduler]# kubectl apply -f nodename-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl delete -f nodename-pod.yaml
[root@master-n1 scheduler]# kubectl get nodes --show-labels
[root@master-n1 scheduler]# vim nodename-pod.yaml
8 nodeName: worker-n1
[root@master-n1 scheduler]# kubectl apply -f nodename-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide


如果节点不存在,pod 会一直处于调度状态
[root@master-n1 scheduler]# kubectl delete -f nodename-pod.yaml
[root@master-n1 scheduler]# vim nodename-pod.yaml
[root@master-n1 scheduler]# cat nodename-pod.yaml | grep -n nodeName
[root@master-n1 scheduler]# kubectl apply -f nodename-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f nodename-pod.yaml

(2)nodeSelector 调度
因为默认调度器只会根据资源数量(如 CPU、内存)做简单分配,无法识别节点的硬件差异(比如哪台节点有 GPU、哪台节点用的是 SSD 硬盘)。
所以当需要将 Pod 只调度到带有特定标签的节点上时,就必须使用 nodeSelector,通过给节点打标签并在 Pod 中指定标签要求,来实现 Pod 与节点的精确匹配。
① nodeSelector 简介
- nodeSelector 是通过标签控制节点
- nodeSelector 是节点选择约束的最简单且推荐的形式。
- 给选择的节点添加标签,例如:kubectl label nodes k8s-node1 lab=lee。
- 可以给多个节点设定相同的标签,实现多个节点同时匹配同一个调度条件。
② 部署 nodeSelector 调度
设置 nodeSelector 参数,添加标签
[root@master-n1 scheduler]# kubectl run nodeselector-pod --image myapp:v1 --dry-run=client -o yaml > nodeselector-pod.yaml
[root@master-n1 scheduler]# vim nodeselector-pod.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: nodeselector-pod
6 name: nodeselector-pod
7 spec:
8 nodeSelector:
9 app: timinglee
10 containers:
11 - image: myapp:v1
12 name: myapp
[root@master-n1 scheduler]# kubectl apply -f nodeselector-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes --show-labels
[root@master-n1 scheduler]# kubectl label nodes worker-n2 app=timinglee
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes --show-labels

删除节点标签
-
标签只在 pod 调度时生效
-
在 pod 正常运行时,标签被删除,也不会影响正在运行的 pod
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes worker-n2 --show-labels
[root@master-n1 scheduler]# kubectl label nodes worker-n2 app-
[root@master-n1 scheduler]# kubectl get nodes worker-n2 --show-labels
[root@master-n1 scheduler]# kubectl get pods -o wide实验结束
[root@master-n1 scheduler]# kubectl delete -f nodeselector-pod.yaml

3. affinity 亲和性
(1)亲和性和反亲和性
- nodeSelector 提供了一种非常简单的方法来将 Pod 约束到具有特定标签的节点上。亲和与反亲和功能极大地扩展了你可以表达约束的类型。
- 使用节点上的 Pod 的标签来约束,而不是使用节点本身的标签,来允许哪些 Pod 可以或者不可以被放置在一起。
(2)nodeAffinity 节点亲和
当调度需求不再只是简单的"Pod 必须跑到某个标签的节点上",而是需要表达"最好跑到 A 类节点,实在不行 B 类也可以"或者"不要跑到 C 类节点上"这类软性、复杂的匹配规则时,就必须使用 nodeAffinity 节点亲和性调度。
① nodeAffinity 简介
#节点亲和性指定满足条件的节点才能运行 Pod。
requiredDuringSchedulingIgnoredDuringExecution:
- 硬性要求,必须满足条件才能调度;
- 如果 Pod 运行期间节点标签发生变化导致条件不满足,Pod 会继续运行,不会受到影响。
preferredDuringSchedulingIgnoredDuringExecution:
- 软性偏好,优先满足条件,但在无法满足时也会调度 Pod。
IgnoreDuringExecution:
- 表示如果在 Pod 运行期间 Node 的标签发生变化,导致亲和性策略不能满足,则继续运行当前的 Pod,不会主动终止或重新调度。
nodeAffinity 还支持多种规则匹配条件的配置,具体如下:
|--------------|----------------------------|
| 匹配规则 | 功能 |
| In | label 的值在列表内 |
| NotIn | label 的值不在列表内 |
| Gt | label 的值大于设置的值,不支持 Pod 亲和性 |
| Lt | label 的值小于设置的值,不支持 Pod 亲和性 |
| Exists | 设置的 label 存在 |
| DoesNotExist | 设置的 label 不存在 |
② required 必须满足
设置 required 必须满足
# 窗口1
[root@master-n1 scheduler]# kubectl run required-pod --image myapp:v1 --dry-run=client -o yaml > required-pod.yaml
[root@master-n1 scheduler]# vim required-pod.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: required-pod
6 name: required-pod
7 spec:
8 containers:
9 - image: myapp:v1
10 name: myapp
11 affinity:
12 nodeAffinity:
13 requiredDuringSchedulingIgnoredDuringExecution:
14 nodeSelectorTerms:
15 - matchExpressions:
16 - key: desk
17 operator: In
18 values:
19 - ssd
20 - iscsi
# 窗口2
[root@master-n1 scheduler]# kubectl explain pod.spec
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.matchExpressions

创建 pod,测试 required 必须满足
[root@master-n1 scheduler]# kubectl apply -f required-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes --show-labels
[root@master-n1 scheduler]# kubectl label nodes worker-n1 desk=haha
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes worker-n1 --show-labels
[root@master-n1 scheduler]# kubectl label nodes worker-n1 desk=ssd --overwrite
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes worker-n1 --show-labels
# 实验结束
[root@master-n1 scheduler]# kubectl label nodes worker-n1 desk-
[root@master-n1 scheduler]# kubectl delete -f required-pod.yaml

③ preferred 倾向满足
设置 preferreed 倾向满足
# 窗口1
[root@master-n1 scheduler]# kubectl run preferred-pod --image myapp:v1 --dry-run=client -o yaml > preferred-pod.yaml
[root@master-n1 scheduler]# vim preferred-pod.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: preferred-pod
6 name: preferred-pod
7 spec:
8 containers:
9 - image: myapp:v1
10 name: myapp
11 affinity:
12 nodeAffinity:
13 preferredDuringSchedulingIgnoredDuringExecution:
14 - weight: 100
15 preference:
16 matchExpressions:
17 - key: desk
18 operator: In
19 values:
20 - ssd
21 - iscsi
# 窗口2
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.preference

创建 pod,测试 preferred 倾向满足
[root@master-n1 scheduler]# kubectl get nodes --show-labels
[root@master-n1 scheduler]# kubectl apply -f preferred-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide

添加标签测试倾向满足
-
在 pod 运行后,即便有更合适的节点也不会重新调度,只有重新创建的 pod 才会调度到更合适的节点上
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl label nodes worker-n1 desk=ssd
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl delete -f preferred-pod.yaml
[root@master-n1 scheduler]# kubectl apply -f preferred-pod.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl get nodes worker-n1 --show-labels实验结束
[root@master-n1 scheduler]# kubectl label nodes worker-n1 desk-
[root@master-n1 scheduler]# kubectl delete -f preferred-pod.yaml

(3)podAffinity 亲和
因为 nodeAffinity 只能根据节点标签决定调度,无法感知 Pod 之间的相对位置,比如哪些 Pod 已经跑在了哪个节点上。
所以当需要让新 Pod 主动"靠近"某个已有的 Pod(比如让缓存 Pod 和数据库 Pod 部署在同一节点以减少网络延迟)时,就必须使用 podAffinity,根据集群中其他 Pod 的标签来决定调度位置。
① podAffinity 简介
- 节点上有符合条件的 Pod,则当前 Pod 就调度到该节点运行。
- podAffinity 主要解决 Pod 可以和哪些 Pod 部署在同一个节点中的问题。
- Pod 间亲和与反亲和通常与更高级别的控制器(例如 ReplicaSets、StatefulSets、Deployments 等)一起使用。
- Pod 间亲和与反亲和需要大量的计算处理,这可能会显著减慢大规模集群中的调度速度。
② 部署 podAffinity 亲和
生成 deployment 控制器配置文件,设置 pod 亲和性
# 窗口1
[root@master-n1 scheduler]# kubectl create deployment podaffinity-dep --image myapp:v1 --replicas 2 --dry-run=client -o yaml > podaffinity-dep.yaml
[root@master-n1 scheduler]# vim podaffinity-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: podaffinity-dep
6 name: podaffinity-dep
7 spec:
8 replicas: 2
9 selector:
10 matchLabels:
11 app: podaffinity-dep
12 template:
13 metadata:
14 labels:
15 app: podaffinity-dep
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20 affinity:
21 podAffinity:
22 requiredDuringSchedulingIgnoredDuringExecution:
23 - labelSelector:
24 matchExpressions:
25 - key: app
26 operator: In
27 values:
28 - podaffinity-dep
29 topologyKey: "kubernetes.io/hostname"
# 窗口2
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAffinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector.matchExpressions
[root@master-n1 scheduler]# kubectl get nodes --show-labels

创建 deployment 控制器,测试 pod 亲和性
[root@master-n1 scheduler]# kubectl apply -f podaffinity-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl scale deployment podaffinity-dep --replicas 3
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f podaffinity-dep.yaml

(4)podAntiAffinity 反亲和
当需要确保某些 Pod 必须分开部署,避免它们跑到同一个节点、机架或可用区时,比如让同一个微服务的多个副本分散在不同节点上以防止单点故障,或者让两个互相干扰的服务绝不能放在一起,就必须使用 podAntiAffinity 反亲和性调度。
① podAntiAffinity 简介
- 与 podAffinity 功能类似,但 podAntiAffinity 是反亲和。
- podAntiAffinity 主要解决 Pod 不能和哪些 Pod 部署在同一个节点中的问题。
- 它们处理的是 Kubernetes 集群内部 Pod 与 Pod 之间的关系。
② 部署 podAntiAffinity 反亲和
# 窗口1
[root@master-n1 scheduler]# kubectl create deployment podantiaffinity-dep --image myapp:v1 --replicas 2 --dry-run=client -o yaml > podantiaffinity-dep.yaml
[root@master-n1 scheduler]# vim podantiaffinity-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: podantiaffinity-dep
6 name: podantiaffinity-dep
7 spec:
8 replicas: 2
9 selector:
10 matchLabels:
11 app: podantiaffinity-dep
12 template:
13 metadata:
14 labels:
15 app: podantiaffinity-dep
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20 affinity:
21 podAntiAffinity:
22 requiredDuringSchedulingIgnoredDuringExecution:
23 - labelSelector:
24 matchExpressions:
25 - key: app
26 operator: In
27 values:
28 - podantiaffinity-dep
29 topologyKey: "kubernetes.io/hostname"
# 窗口2
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAntiAffinity
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector
[root@master-n1 scheduler]# kubectl explain pod.spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector.matchExpressions

创建 deployment 控制器,测试 pod 反亲和性
[root@master-n1 scheduler]# kubectl apply -f podantiaffinity-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl scale deployment podantiaffinity-dep --replicas 3
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f podantiaffinity-dep.yaml

4. Taints 污点模式
因为节点可能因为硬件资源有限、专用于特定任务、或者出现故障而需要"拒绝"某些 Pod 调度上来,但普通的节点标签和亲和性只能从 Pod 角度"主动选择"节点,无法让节点"主动拒绝" Pod。
所以当需要让某些节点只运行具备"容忍能力"的特定 Pod(比如将 GPU 节点只留给需要加速计算的 Pod,或者将故障节点暂时隔离起来不让新 Pod 跑上去)时,就必须使用 Taints(污点)让节点主动"排斥"不符合条件的 Pod,再配合 Toleration(容忍)让符合条件的 Pod 获得"入场资格"。
(1)Taints 简介
Taints(污点)是 Node 的一个属性。设置了 Taints 后,默认情况下 Kubernetes 不会将 Pod 调度到这个 Node 上。
如果为 Pod 设置了 Tolerations(容忍),只要 Pod 能够容忍 Node 上的污点,那么 Kubernetes 就会忽略 Node 上的污点,能够(不是必须)把 Pod 调度过去。
可以使用 kubectl taint 命令给节点增加一个 taint:
kubectl taint nodes <nodename> key=string:effect
effect 值
|------------------|-------------------------------------------|
| effect 值 | 解释 |
| NoSchedule | Pod 不会被调度到标记了该污点的节点 |
| PreferNoSchedule | NoSchedule 的软策略版本,调度器尽量不调度到此节点 |
| NoExecute | 如果节点内正在运行的 Pod 没有对应的 Toleration 设置,会直接被逐出 |
(2)部署污点模式
① NoExecute 污点
创建 deployment 控制器,给节点添加 NoExecute 污点
[root@master-n1 scheduler]# kubectl create deployment dep-test --image myapp:v1 --replicas 2 --dry-run=client -o yaml > dep-test.yaml
[root@master-n1 scheduler]# vim dep-test.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: dep-test
6 name: dep-test
7 spec:
8 replicas: 2
9 selector:
10 matchLabels:
11 app: dep-test
12 template:
13 metadata:
14 labels:
15 app: dep-test
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl taint node worker-n2 nodetype=badnode:NoExecute
[root@master-n1 scheduler]# kubectl get pods -o wide

删除 NoExecute 污点
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl describe nodes | grep Taints -A 5
[root@master-n1 scheduler]# kubectl taint node worker-n2 nodetype-
[root@master-n1 scheduler]# kubectl describe nodes worker-n2 | grep Taints -A 5
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f dep-test.yaml

② NoSchedule 污点
创建控制器,给节点添加 NoSchedule 污点
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl taint node worker-n1 nodetype=badnode:NoSchedule
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl delete -f dep-test.yaml
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl describe nodes worker-n1 | grep Taints
# 实验结束
[root@master-n1 scheduler]# kubectl taint node worker-n1 nodetype-
[root@master-n1 scheduler]# kubectl delete -f dep-test.yaml

③ PreferNoSchedule 污点
创建控制器,给节点添加 PreferNoSchedule 污点
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl taint node worker-n2 nodetype=dabnode:PreferNoSchedule
[root@master-n1 scheduler]# kubectl describe nodes worker-n2 | grep Taints
[root@master-n1 scheduler]# kubectl delete -f dep-test.yaml
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide

创建 pod 反亲和性的 deployment 控制器,测试 PreferNoSchedule 污点
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl delete -f dep-test.yaml
[root@master-n1 scheduler]# vim podantiaffinity-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: dep-test
6 name: dep-test
7 spec:
8 replicas: 3
9 selector:
10 matchLabels:
11 app: dep-test
12 template:
13 metadata:
14 labels:
15 app: dep-test
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20 affinity:
21 podAntiAffinity:
22 requiredDuringSchedulingIgnoredDuringExecution:
23 - labelSelector:
24 matchExpressions:
25 - key: app
26 operator: In
27 values:
28 - dep-test
29 topologyKey: "kubernetes.io/hostname"
[root@master-n1 scheduler]# kubectl apply -f podantiaffinity-dep.yaml
[root@master-n1 scheduler]# kubectl describe nodes worker-n2 | grep Taints
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl taint node worker-n2 nodetype-
[root@master-n1 scheduler]# kubectl delete -f podantiaffinity-dep.yaml

(3)tolerations 污点容忍
① tolerations 简介
tolerations 中定义的 key、value、effect,要与 node 上设置的 taint 保持一致:
- 如果 operator 是 Equal,则 key 与 value 之间的关系必须相等。
- 如果 operator 是 Exists,则 value 可以省略。
- 如果不指定 operator 属性,则默认值为 Equal。
还有两个特殊值:
- 当不指定 key,再配合 Exists 就能匹配所有的 key 与 value,可以容忍所有污点。
- 当不指定 effect,则匹配所有的 effect。
② 精确容忍污点
给节点添加污点
[root@master-n1 scheduler]# kubectl taint node worker-n1 name1=lee1:NoSchedule
[root@master-n1 scheduler]# kubectl taint node worker-n2 name2=lee2:NoSchedule
[root@master-n1 scheduler]# kubectl describe nodes | grep Taints -A 5
[root@master-n1 scheduler]# kubectl apply -f dep-test.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide

添加精确容忍 tolerations
# 窗口1
[root@master-n1 scheduler]# cp dep-test.yaml tolerations-dep.yaml
[root@master-n1 scheduler]# vim tolerations-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: tolerations-dep
6 name: tolerations-dep
7 spec:
8 replicas: 2
9 selector:
10 matchLabels:
11 app: tolerations-dep
12 template:
13 metadata:
14 labels:
15 app: tolerations-dep
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20
21 tolerations:
22 - operator: Equal
23 key: name1
24 value: lee1
25 effect: NoSchedule
# 窗口2
[root@master-n1 scheduler]# kubectl explain pod.spec
[root@master-n1 scheduler]# kubectl explain pod.spec.tolerations
[root@master-n1 scheduler]# kubectl describe nodes | grep Taints -A 5

创建 deployment 控制器
[root@master-n1 scheduler]# kubectl apply -f tolerations-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f tolerations-dep.yaml

③ 容忍指定污点
设置指定控制器容忍 NoSchedule 污点
[root@master-n1 scheduler]# vim tolerations-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: tolerations-dep
6 name: tolerations-dep
7 spec:
8 replicas: 3
9 selector:
10 matchLabels:
11 app: tolerations-dep
12 template:
13 metadata:
14 labels:
15 app: tolerations-dep
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20
21 tolerations:
22 - operator: Exists
23 effect: NoSchedule
[root@master-n1 scheduler]# kubectl describe nodes master-n1 | grep Taints
[root@master-n1 scheduler]# kubectl describe nodes worker-n1 | grep Taints
[root@master-n1 scheduler]# kubectl describe nodes worker-n2 | grep Taints
[root@master-n1 scheduler]# kubectl apply -f tolerations-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide

修改 worker-n1 节点的污点为 PreferNoSchedule
[root@master-n1 scheduler]# kubectl get pods -o wide
[root@master-n1 scheduler]# kubectl delete -f tolerations-dep.yaml
[root@master-n1 scheduler]# kubectl taint node worker-n1 name1-
[root@master-n1 scheduler]# kubectl taint node worker-n1 name1=lee1:PreferNoSchedule
[root@master-n1 scheduler]# kubectl describe nodes worker-n1 | grep Taints
[root@master-n1 scheduler]# kubectl apply -f tolerations-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl delete -f tolerations-dep.yaml

④ 容忍所有污点
设置指定控制器容忍所有污点
[root@master-n1 scheduler]# vim tolerations-dep.yaml
1 apiVersion: apps/v1
2 kind: Deployment
3 metadata:
4 labels:
5 app: tolerations-dep
6 name: tolerations-dep
7 spec:
8 replicas: 3
9 selector:
10 matchLabels:
11 app: tolerations-dep
12 template:
13 metadata:
14 labels:
15 app: tolerations-dep
16 spec:
17 containers:
18 - image: myapp:v1
19 name: myapp
20
21 tolerations:
22 - operator: Exists
[root@master-n1 scheduler]# kubectl describe nodes master-n1 | grep Taints
[root@master-n1 scheduler]# kubectl describe nodes worker-n1 | grep Taints
[root@master-n1 scheduler]# kubectl describe nodes worker-n2 | grep Taints
[root@master-n1 scheduler]# kubectl apply -f tolerations-dep.yaml
[root@master-n1 scheduler]# kubectl get pods -o wide
# 实验结束
[root@master-n1 scheduler]# kubectl taint node worker-n1 name1-
[root@master-n1 scheduler]# kubectl taint node worker-n2 name2-
[root@master-n1 scheduler]# kubectl delete -f tolerations-dep.yaml

九、Kubernetes 认证授权
1. Kubernetes API 访问控制
(1)访问控制机制

Authentication(认证)
- 认证方式现共有8种,可以启用一种或多种认证方式,只要有一种认证方式通过,就不再进行其它方式的认证。通常启用X509 Client Certs和Service Accout Tokens两种认证方式。
- Kubernetes集群有两类用户:由Kubernetes管理的Service Accounts (服务账户)和(Users Accounts) 普通账户。k8s中账号的概念不是我们理解的账号,它并不真的存在,它只是形式上存在。
Authorization(授权)
- 必须经过认证阶段,才到授权请求,根据所有授权策略匹配请求资源属性,决定允许或拒绝请求。授权方式现共有6种,AlwaysDeny、AlwaysAllow、ABAC、RBAC、Webhook、Node。默认集群强制开启RBAC。
Admission Control(准入控制)
- 用于拦截请求的一种方式,运行在认证、授权之后,是权限认证链上的最后一环,对请求API资源对象进行修改和校验。

(2)用户认证和服务认证
|------|-------------------------------------------------|--------------------------------|
| | 用户认证(UserAccount) | 服务认证(ServiceAccount) |
| 使用对象 | 针对人而言的 | 针对运行在 Pod 中的进程而言的 |
| 范围 | 全局性的,名称在集群各 namespace 中都是全局唯一的,不会做 namespace 隔离 | 按 namespace 隔离的 |
| 创建方式 | 可能会从企业数据库进行同步,创建需要特殊权限,涉及复杂的业务流程 | 创建目的是为了更轻量,允许集群用户为了具体的任务创建服务账户 |
| 权限原则 | 通常权限较大 | 遵循权限最小化原则 |
2. ServiceAccount 服务账户
因为 Pod 内部访问 API Server 时(比如查询其他 Pod 状态、动态创建删除资源),如果没有身份认证,API Server 会拒绝请求,而且默认的 default ServiceAccount 权限极小,无法满足多数操作需求。
所以当需要为运行在 Pod 里的应用程序提供一个合法身份,让它可以安全地认证并授权访问 Kubernetes API 时,就必须使用 ServiceAccount,并通过绑定 Role 或 ClusterRole 来赋予 Pod 特定的操作权限(比如读取 Pod 列表、创建 Job 等)。
(1)服务认证简介
服务账户控制器(Service account controller)
- 负责管理各命名空间下的服务账户。
- 在每个活跃的命名空间中,系统都会自动存在一个名为 default 的服务账户。
服务账户准入控制器(Service account admission controller)
- 将 Pod 的 ServiceAccount 默认设置为 default,并确保 Pod 所关联的 ServiceAccount 存在,如果不存在则拒绝该 Pod。
- 如果 Pod 没有设置 ImagePullSecrets,则会自动将 ServiceAccount 中的 ImagePullSecrets 添加到 Pod 中。
- 将挂载于 /var/run/secrets/kubernetes.io/serviceaccount 的 volumeSource 添加到 Pod 下的每个容器中,同时将一个包含用于 API 访问的 token 的 volume 添加到 Pod 中。
(2)ServiceAccount 实验
① 创建 pod
创建 pod,pod会自动挂载 API 访问凭证及默认使用 default ServiceAccount
-
有 API 凭证,pod 就能访问 API Server 的资源
[root@master-n1 ~]# mkdir auth
[root@master-n1 ~]# cd auth/
[root@master-n1 auth]# kubectl run pod-test --image myapp:v1 --dry-run=client -o yaml > pod-test.yaml
[root@master-n1 auth]# vim pod-test.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: myapp:v1
10 name: myapp
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl describe pods pod-test | grep Service
[root@master-n1 auth]# kubectl get sa
[root@master-n1 auth]# kubectl describe sa default

② 私有镜像拉取问题
默认的服务账户 default 没有私有镜像拉取凭证,会导致 pod 拉取私有镜像失败

[root@master-n1 auth]# kubectl get pods -o wide
[root@master-n1 auth]# kubectl delete -f pod-test.yaml
[root@master-n1 auth]# vim pod-test.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 containers:
9 - image: reg.timinglee.org/timinglee/myapp:v1
10 name: myapp
11 imagePullPolicy: Always
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl get pods -o wide
[root@master-n1 auth]# kubectl delete -f pod-test.yaml

③ 创建私有仓库的认证
创建新的服务账户和私有仓库的认证
[root@master-n1 auth]# kubectl create serviceaccount timinglee
[root@master-n1 auth]# ssh root@192.168.153.254 "cat /opt/harbor/harbor.yml | grep -E harbor_admin_password"
[root@master-n1 auth]# kubectl create secret docker-registry timinglee-pull \
--docker-username admin \
--docker-password lee \
--docker-server reg.timinglee.org \
--docker-email admin@timinglee.org
[root@master-n1 auth]# kubectl describe sa timinglee
[root@master-n1 auth]# kubectl edit sa timinglee
1 # Please edit the object below. Lines beginning with a '#' will be ignored,
2 # and an empty file will abort the edit. If an error occurs while saving this file will be
3 # reopened with the relevant failures.
4 #
5 apiVersion: v1
6 kind: ServiceAccount
7 imagePullSecrets:
8 - name: timinglee-pull
9 metadata:
10 creationTimestamp: "2026-05-07T08:29:13Z"
11 name: timinglee
12 namespace: default
13 resourceVersion: "255947"
14 uid: 9a306c15-c0d9-4b17-aab3-c525fe88eebb

设置 pod 只用有私有仓库认证的服务账户
[root@master-n1 auth]# kubectl describe sa timinglee
[root@master-n1 auth]# vim pod-test.yaml
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 labels:
5 run: pod-test
6 name: pod-test
7 spec:
8 serviceAccountName: timinglee
9 containers:
10 - image: reg.timinglee.org/timinglee/myapp:v1
11 name: myapp
12 imagePullPolicy: Always
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl get pods -o wide
# 实验结束
[root@master-n1 auth]# kubectl delete -f pod-test.yaml

3. UserAccount 用户认证
因为 ServiceAccount 是为运行在 Pod 里的程序(非人类)设计的身份,用于让应用访问 API Server,而管理员、开发者、运维人员等真人无法以 ServiceAccount 身份在终端里执行 kubectl 命令,也缺乏对不同用户进行独立审计和权限隔离的能力。
所以当需要让真实的人(比如张三只配查看 Pod、李四能创建 Deployment、王五是集群管理员)安全地访问 Kubernetes 集群,并为每个用户的每一次操作留下审计日志时,就必须使用 UserAccount 用户认证,通常通过 X509 客户端证书、RBAC 绑定等方式实现不同用户的独立身份和权限控制。
(1)用户认证简介
用户认证方式
- Kubernetes 支持多种用户认证方式,包括客户端证书、静态密码、Bearer Token、OpenID Connect 等。
- 通常至少启用一种认证方式,只要有一种认证通过即可。
用户账户管理
- 用户账户是全局性的,针对人而言,其名称在集群各命名空间中都是全局唯一的。
- 集群的用户账户可能会从企业数据库(如 LDAP)进行同步,其创建需要特殊权限,并且涉及复杂的业务流程。
用户认证流程
- 当用户通过 kubectl 或其他客户端工具发起请求时,请求中会携带认证凭证(如客户端证书、Token 等)。
- API Server 验证凭证的有效性,确认用户身份。
- 认证通过后进入授权阶段,认证失败则拒绝请求。
(2)创建 UserAccount
① 建立用户证书
创建秘钥和证书请求,在通过 CA 签发生成证书
-
Kubernetes 集群的证书存储目录:/etc/kubernetes/pki/
[root@master-n1 auth]# cd /etc/kubernetes/pki/
[root@master-n1 pki]# ls
[root@master-n1 pki]# openssl genrsa -out timinglee.key 2048
[root@master-n1 pki]# openssl req -new -key timinglee.key -out timinglee.csr -subj "/CN=timinglee"
[root@master-n1 pki]# openssl x509 -req -in timinglee.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out timinglee.crt -days 365
[root@master-n1 pki]# openssl x509 -in timinglee.crt -text -noout

② 创建用户
创建 timinglee 用户和 timinglee 用户上下文
-
用户上下文是为了方便在不同身份和集群之间快速切换
[root@master-n1 pki]# kubectl config view
[root@master-n1 pki]# find $PWD | grep timinglee
[root@master-n1 pki]# openssl x509 -in timinglee.crt -noout -subject
[root@master-n1 pki]# kubectl config set-credentials timinglee
--client-certificate /etc/kubernetes/pki/timinglee.crt
--client-key /etc/kubernetes/pki/timinglee.key
--embed-certs=true
[root@master-n1 pki]# kubectl config set-context timinglee@kubernetes --cluster kubernetes --user timinglee
[root@master-n1 pki]# kubectl config use-context timinglee@kubernetes
[root@master-n1 pki]# kubectl config view


测试 timinglee 用户的权限
[root@master-n1 pki]# cd /root/auth/
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl get nodes
[root@master-n1 auth]# ls
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl config use-context kubernetes-admin@kubernetes
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl delete -f pod-test.yaml

(3)RBAC 授权
因为即使有了 ServiceAccount 或 UserAccount 认证通过的身份,这个身份默认也是没有任何操作权限的(比如无法查看 Pod、无法创建 Deployment),认证仅仅证明了"你是谁",但没有说明"你能做什么"。
所以当需要为不同用户或服务账号精确控制其能操作哪些资源(比如 Pod、Service、ConfigMap)、能执行哪些动作(get、create、delete)、以及能操作哪个命名空间时,就必须使用 RBAC 授权,通过创建 Role/ClusterRole 定义权限规则,再通过 RoleBinding/ClusterRoleBinding 将权限绑定给具体的用户或服务账号。
① 基于角色访问控制授权

允许管理员通过 Kubernetes API 动态配置授权策略。RBAC(Role Based Access Control)就是用户通过角色与权限进行关联。RBAC 只有授权,没有拒绝授权,所以只需要定义允许该用户做什么即可。
RBAC 的三个基本概念
- Subject(被作用者):表示 Kubernetes 中的三类主体User(用户)、Group(组)、ServiceAccount(服务账户)。
- Role(角色):一组规则,定义了对 Kubernetes API 对象的操作权限。
- RoleBinding(角色绑定):定义了 Subject 和 Role 之间的绑定关系。
RBAC 包括四种类型
- Role
- ClusterRole
- RoleBinding
- ClusterRoleBinding
Role 和 ClusterRole 的区别
|-------------|--------------|--------------------|
| 类型 | 作用范围 | 说明 |
| Role | 单个 namespace | 只能授予特定命名空间中资源的访问权限 |
| ClusterRole | 整个集群 | 可以在集群全局使用,跨命名空间 |
Kubernetes 预定义的 ClusterRole
- cluster-admin:集群管理员权限
- admin:命名空间管理员权限
- edit:命名空间读写权限
- view:命名空间只读权限
② Role 授权
创建 role,给 pod 和 deployment 授权
[root@master-n1 auth]# kubectl create role timinglee-role --verb=get --resource pods --dry-run=client -o yaml > timinglee-role.yaml
[root@master-n1 auth]# vim timinglee-role.yaml
1 apiVersion: rbac.authorization.k8s.io/v1
2 kind: Role
3 metadata:
4 name: timinglee-role
5 rules:
6 - apiGroups:
7 - ""
8 resources:
9 - pods
10 verbs:
11 - get
12 - watch
13 - list
14 - create
15 - update
16 - path
17 - delete
18
19 - apiGroups:
20 - "apps"
21 resources:
22 - deployments
23 verbs:
24 - get
25 - watch
26 - list
27 - create
[root@master-n1 auth]# kubectl api-resources
[root@master-n1 auth]# kubectl apply -f timinglee-role.yaml
[root@master-n1 auth]# kubectl get roles.rbac.authorization.k8s.io
[root@master-n1 auth]# kubectl describe roles.rbac.authorization.k8s.io


生成 rolebinding 配置文件
[root@master-n1 auth]# kubectl get roles.rbac.authorization.k8s.io
[root@master-n1 auth]# kubectl get roles --all-namespaces | grep timinglee-role
[root@master-n1 auth]# openssl x509 -in /etc/kubernetes/pki/timinglee.crt -noout -subject [root@master-n1 auth]# kubectl create rolebinding timinglee-rolebinding \
--role timinglee-role \
--namespace default \
--user timinglee \
--dry-run=client -o yaml > timinglee-rolebinding.yaml
[root@master-n1 auth]# vim timinglee-rolebinding.yaml
1 apiVersion: rbac.authorization.k8s.io/v1
2 kind: RoleBinding
3 metadata:
4 name: timinglee-rolebinding
5 namespace: default
6 roleRef:
7 apiGroup: rbac.authorization.k8s.io
8 kind: Role
9 name: timinglee-role
10 subjects:
11 - apiGroup: rbac.authorization.k8s.io
12 kind: User
13 name: timinglee

创建 rolebinding,测试 timinglee 用户权限
[root@master-n1 auth]# kubectl apply -f timinglee-rolebinding.yaml
[root@master-n1 auth]# kubectl describe rolebindings.rbac.authorization.k8s.io
[root@master-n1 auth]# kubectl describe roles.rbac.authorization.k8s.io
[root@master-n1 auth]# kubectl config use-context timinglee@kubernetes
[root@master-n1 auth]# kubectl apply -f pod-test.yaml
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl get deployments.apps
# 实验结束
[root@master-n1 auth]# kubectl delete -f pod-test.yaml
[root@master-n1 auth]# kubectl config use-context kubernetes-admin@kubernetes
[root@master-n1 auth]# kubectl delete -f timinglee-rolebinding.yaml
[root@master-n1 auth]# kubectl delete -f timinglee-role.yaml

③ ClusterRole 授权
测试 role 能否控制其他命名空间
[root@master-n1 auth]# kubectl config current-context
[root@master-n1 auth]# kubectl apply -f timinglee-role.yaml
[root@master-n1 auth]# kubectl apply -f timinglee-rolebinding.yaml
[root@master-n1 auth]# kubectl describe roles.rbac.authorization.k8s.io
[root@master-n1 auth]# kubectl get roles --all-namespaces | grep timinglee-role
[root@master-n1 auth]# kubectl get namespaces
[root@master-n1 auth]# kubectl config use-context timinglee@kubernetes
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl get pods -n kube-system
[root@master-n1 auth]# kubectl get pods -n kube-public
# 切回管理员上下文,关闭role
[root@master-n1 auth]# kubectl config use-context kubernetes-admin@kubernetes
[root@master-n1 auth]# kubectl delete -f timinglee-role.yaml
[root@master-n1 auth]# kubectl delete -f timinglee-rolebinding.yaml

创建集群级 clusterrole,给 pod、deployment、service 授权
[root@master-n1 auth]# kubectl create clusterrole timinglee-clusterrole --verb=get --resource=pods --dry-run=client -o yaml > timinglee-clusterrole.yaml
[root@master-n1 auth]# vim timinglee-clusterrole.yaml
1 apiVersion: rbac.authorization.k8s.io/v1
2 kind: ClusterRole
3 metadata:
4 name: timinglee-clusterrole
5 rules:
6 - apiGroups:
7 - ""
8 resources:
9 - pods
10 verbs:
11 - get
12 - watch
13 - list
14 - create
15 - update
16 - path
17 - delete
18
19 - apiGroups:
20 - "apps"
21 resources:
22 - deployments
23 verbs:
24 - get
25 - watch
26 - list
27 - create
28
29 - apiGroups:
30 - ""
31 resources:
32 - services
33 verbs:
34 - get
35 - watch
36 - list
37 - create
[root@master-n1 auth]# kubectl api-resources
[root@master-n1 auth]# kubectl apply -f timinglee-clusterrole.yaml
[root@master-n1 auth]# kubectl describe clusterrole timinglee-clusterrole

创建集群级 clusterrolebinding,绑定 clusterrole
[root@master-n1 auth]# kubectl describe clusterrole timinglee-clusterrole
[root@master-n1 auth]# kubectl create clusterrolebinding timinglee-clusterrolebinding --clusterrole timinglee-clusterrole --user timinglee
[root@master-n1 auth]# kubectl describe clusterrolebindings.rbac.authorization.k8s.io timinglee-clusterrolebinding

测试 timinglee 用户的 clusterrole 权限
[root@master-n1 auth]# kubectl config current-context
[root@master-n1 auth]# kubectl describe clusterrole timinglee-clusterrole
[root@master-n1 auth]# kubectl config use-context timinglee@kubernetes
[root@master-n1 auth]# kubectl get namespaces
[root@master-n1 auth]# kubectl get pods
[root@master-n1 auth]# kubectl get pods -n kube-system
[root@master-n1 auth]# kubectl get svc -n kube-system
# 实验结束
[root@master-n1 auth]# kubectl config use-context kubernetes-admin@kubernetes
[root@master-n1 auth]# kubectl delete -f timinglee-clusterrole.yaml
[root@master-n1 auth]# kubectl delete clusterrolebindings.rbac.authorization.k8s.io timinglee-clusterrolebinding

④ 服务账户的自动化
服务账户准入控制器(Service account admission controller)
- 如果 Pod 没有设置 ServiceAccount,则将其 ServiceAccount 设为 default。
- 确保 Pod 所关联的 ServiceAccount 存在,如果不存在则拒绝该 Pod。
- 如果 Pod 没有设置 ImagePullSecrets,则将 ServiceAccount 中的 ImagePullSecrets 添加到 Pod 中。
- 将一个包含用于 API 访问的 Token 的 Volume 添加到 Pod 中。
- 将挂载于 /var/run/secrets/kubernetes.io/serviceaccount 的 volumeSource 添加到 Pod 下的每个容器中。
服务账户控制器(Service account controller)
- 服务账户管理器管理各命名空间下的服务账户,并且保证每个活跃的命名空间下都存在一个名为 default 的服务账户。