RHEL——Kubernetes容器编排平台(二)

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

创建项目并上传镜像

复制代码
[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 参数说明


④ 创建 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 的服务账户。

相关推荐
Achou.Wang1 小时前
go语言中使用等待组(waitgroups)和内存屏障(barriers)进行同步
开发语言·后端·golang
MATLAB代码顾问1 小时前
【智能优化】鹈鹕优化算法(POA)原理与Python实现
开发语言·python·算法
lsx2024061 小时前
C 标准库 - `<stdio.h>`
开发语言
得闲喝茶1 小时前
JavaScript在数据处理的应用
开发语言·前端·javascript·经验分享·笔记
嵌入式×边缘AI:打怪升级日志1 小时前
转换模块(十二):实现 RGB 转 RGB + 项目整合与上机实验
开发语言·ios·swift
庞轩px1 小时前
第五篇:Spring事务管理——@Transactional的底层实现与失效场景
java·spring·事务管理·spring事务·注解transactional
研究点啥好呢1 小时前
凯捷 自动化测试(Java+Selenium)面试题精选:10道高频考题+答案解析
java·开发语言·python·selenium·测试工具·求职招聘
李白你好1 小时前
一个面向 Java 反序列化测试的桌面 GUI 工具
java
ghie90902 小时前
基于遗传算法的配电网重构
开发语言·重构