实验简介
在企业级云原生架构中,容器的无状态性虽然带来了极致的弹性和扩展能力,但也为配置管理、敏感信息保护以及数据持久化提出了挑战。作为日常 IT 运维工作的重要环节,如何解耦配置与代码、如何安全地分发凭证、以及如何为有状态服务(如数据库、中间件)提供可靠的存储底座,是构建高可用集群的关键。
-
ConfigMap:实现应用配置与镜像解耦,支持热更新。
-
Secret:安全管理密码、Token 及私有镜像仓库(Harbor)的认证凭证。
-
基础 Volumes :探究
emptyDir、hostPath以及基于网络的NFS存储卷。 -
PV/PVC 与动态存储 :从静态 PV 绑定到基于
StorageClass的动态存储分配(NFS Provisioner),并结合StatefulSet实现有状态集群的自动化存储挂载。
一、 ConfigMap:应用配置的解耦与热更新
ConfigMap 主要用于存储非机密性的配置信息(如环境变量、配置文件等),使得应用镜像与配置彻底解耦。
1. ConfigMap 的四种建立方式
1.1 通过字符方式(Literal)建立
可以直接在命令行中通过键值对创建:
[root@master storage]# kubectl create cm timinglee --from-literal fname=timing --from-literal lname=lee
configmap/timinglee created
[root@master storage]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 20d
timinglee 2 13s
# 查看详细信息
[root@master storage]# kubectl describe cm timinglee
Name: timinglee
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
fname:
----
timing
lname:
----
lee
# 导出为 yaml 查看
[root@master storage]# kubectl get cm timinglee -o yaml
apiVersion: v1
data:
fname: timing
lname: lee
kind: ConfigMap
metadata:
creationTimestamp: "2026-04-18T01:53:54Z"
name: timinglee
namespace: default
resourceVersion: "198048"
uid: 317a3043-d4bd-4d13-8ad7-376daf66e3ba
1.2 通过文件方式建立
将文件内容整体作为 ConfigMap 的 value,文件名默认作为 key:
[root@master storage]# echo hello timinglee > timinglee
[root@master storage]# kubectl create cm timinglee2 --from-file timinglee
configmap/timinglee2 created
[root@master storage]# kubectl describe cm timinglee2
Name: timinglee2
Namespace: default
# ...省略部分输出...
Data
====
timinglee:
----
hello timinglee
1.3 通过目录方式建立
将目录下所有文件批量转化为 ConfigMap:
[root@master storage]# mkdir test
[root@master storage]# echo timinglee > test/tfile
[root@master storage]# echo lee > test/lfile
[root@master storage]# ls test/
lfile tfile
[root@master storage]# cat test/*
lee
timinglee
[root@master storage]# kubectl create cm timinglee3 --from-file test/
configmap/timinglee3 created
[root@master storage]# kubectl describe cm timinglee3
# ...省略部分输出...
Data
====
lfile:
----
lee
tfile:
----
timinglee
1.4 通过 YAML 声明式建立(推荐)
在日常运维中,我们最常使用的是 YAML 文件管理,方便版本控制:
[root@master storage]# kubectl create cm timinglee4 --from-literal timinglee=abc --dry-run=client -o yaml > timinglee4.yaml
[root@master storage]# vim timinglee4.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: timinglee4
data:
timinglee: abc
lee: def
2. ConfigMap 的使用方法与实战场景
2.1 场景一:使用 ConfigMap 填充 Pod 环境变量
自定义变量注入: 先准备一个包含 IP 和端口的 ConfigMap timinglee。
apiVersion: v1
kind: ConfigMap
metadata:
name: timinglee
data:
ipaddress: "172.25.254.50"
port: "3306"
创建 Pod 时,使用 valueFrom 引入:
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: testpod
name: testpod
spec:
containers:
- image: busybox
name: testpod
command:
- /bin/sh
- -c
- env
env:
- name: key1 #设定key1的值
valueFrom:
configMapKeyRef:
name: timinglee
key: ipaddress
- name: key2 #设定key2的值
valueFrom:
configMapKeyRef:
name: timinglee
key: port
restartPolicy: Never
测试输出:
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl logs pods/testpod
# ...环境变量输出...
key1=172.25.254.50 #key1成功注入
key2=3306 #key2成功注入
# ...
[root@master storage]# kubectl delete pods testpod
批量导入系统变量(envFrom):
[root@master storage]# vim testpod.yml
# ...
envFrom:
- configMapRef:
name: timinglee
# ...
此时日志会直接输出 port=3306 和 ipaddress=172.25.254.50。
在命令中引用变量:
# ...
command:
- /bin/sh
- -c
- echo ${ipaddress}_${port}
# ...
日志输出:172.25.254.50_3306
2.2 场景二:通过数据卷 (Volume) 挂载 ConfigMap
可以将 ConfigMap 以文件的形式挂载进容器的特定目录:
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: testpod
name: testpod
spec:
containers:
- image: busybox
name: testpod
command:
- /bin/sh
- -c
- sleep 100000
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: timinglee
restartPolicy: Never
验证挂载结果:
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl exec -it pods/testpod -- /bin/sh
/ # cd config/
/config # ls
ipaddress port
2.3 实战:使用 ConfigMap 托管 Nginx 配置并实现热更新
将 nginx.conf 放入 ConfigMap:
[root@master storage]# vim nginx.conf
server {
listen 8000;
server_name _;
root /usr/share/nginx/html;
index index.html;
}
[root@master storage]# kubectl create cm nginx --from-file nginx.conf
创建 Nginx Pod 并挂载该配置:
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume
configMap:
name: nginx
restartPolicy: Never
启动并测试,此时服务在 8000 端口提供:
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 4s 10.244.1.8 node1
[root@master storage]# curl 10.244.1.8:8000
<!DOCTYPE html>
<html>...
更新配置: 使用 kubectl edit cm nginx 将监听端口从 8000 修改为 8080。由于 Kubernetes 同步 ConfigMap 挂载文件会有一定的延迟,通常在生产环境中配合重载脚本使用,这里我们重建 Pod 以立即生效验证。
[root@master storage]# kubectl edit cm nginx
[root@master storage]# kubectl delete -f testpod.yml
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# curl 10.244.1.9:8080 # 访问新端口成功
<!DOCTYPE html>
<html>...
二、 Secrets:敏感信息的加密管理与认证传递
对于密码、Token、密钥等敏感信息,Kubernetes 提供了 Secret 对象进行管理。其数据默认经过 Base64 编码。
1. Secrets 的建立方式
1.1 命令行建立
[root@master storage]# kubectl create secret generic timinglee --from-literal userlist=timinglee --from-literal password=lee
[root@master storage]# kubectl get secrets timinglee -o yaml
apiVersion: v1
data:
password: bGVl
userlist: dGltaW5nbGVl # base64 编码结果
kind: Secret
metadata:
name: timinglee
type: Opaque
# 解码验证
[root@master storage]# echo -n "dGltaW5nbGVl" | base64 -d
timinglee
1.2 文件方式建立
[root@master storage]# echo timinglee > userlist
[root@master storage]# echo lee > password
[root@master storage]# kubectl create secret generic timinglee --from-file userlist --from-file password
1.3 YAML 方式建立(需手动进行 Base64 编码)
[root@node1 ~]# echo -n "timinglee" | base64
dGltaW5nbGVl
[root@node1 ~]# echo -n "123" | base64
MTIz
[root@master storage]# vim timinglee_secrets.yml
apiVersion: v1
kind: Secret
metadata:
name: timinglee
data:
userlist: dGltaW5nbGVl
password: MTIz
2. Secrets 的用法实战
与 ConfigMap 类似,Secret 也可以被挂载为文件或注入为环境变量。
注入到 Pod 指定文件中:
# ...部分截取
volumeMounts:
- name: config-volume
mountPath: /userlist
volumes:
- name: config-volume
secret:
secretName: timinglee
items:
- key: userlist
path: my-users/username # 映射为指定路径
验证:cat /userlist/my-users/username 输出 timinglee。
设置为环境变量: 使用 secretKeyRef 提取:
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: timinglee
key: userlist
3. 企业实战:配置 Docker 私有仓库认证 (ImagePullSecrets)
在搭建内部 Harbor 仓库后,往往需要鉴权才能拉取镜像。如果我们直接拉取私有镜像,会触发 ImagePullBackOff 错误。
现象还原:
# 将镜像推送到私有仓库
[root@master storage]# docker tag timinglee/myapp:v1 reg.timinglee.org/timinglee/myapp:v1
[root@master storage]# docker push reg.timinglee.org/timinglee/myapp:v1
# 尝试在 Pod 中拉取(不带认证)
[root@master storage]# vim testpod.yml
# ...
- image: reg.timinglee.org/timinglee/myapp:v1
# ...
[root@master storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp 0/1 ImagePullBackOff 0 26s
# 报错信息提示 unauthorized: unauthorized to access repository
解决方案:制作与挂载 docker-registry Secret
# 1. 创建特殊的 docker-registry 类型的 Secret
[root@master storage]# kubectl create secret docker-registry docker-auth --docker-server reg.timinglee.org --docker-username admin --docker-password lee --docker-email timinglee@timinglee.org
secret/docker-auth created
# 2. 修改 Pod 配置,加入 imagePullSecrets
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: myapp
name: myapp
spec:
containers:
- image: reg.timinglee.org/timinglee/myapp:v1
name: myapp
imagePullSecrets:
- name: docker-auth # 引用刚建立的 Secret
# 3. 验证
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp 1/1 Running 0 3s
三、 Volumes:容器的数据持久化方案
容器的文件系统是临时的,为了保证数据不丢失且能在容器间共享,需要引入持久化 Volume。
1. emptyDir (临时共享目录)
emptyDir 卷在 Pod 分配到节点时创建,随着 Pod 的删除而销毁。非常适合同一个 Pod 中多个容器之间共享数据。
[root@master volumes]# vim empty.yml
kind: Pod
metadata:
labels:
run: empty
name: empty
spec:
containers:
- image: busybox
name: busybox
command: ["/bin/sh", "-c", "sleep 100000"]
volumeMounts:
- mountPath: /cache
name: cache-vol
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: cache-vol
volumes:
- name: cache-vol
emptyDir:
medium: Memory # 使用内存作为介质提升速度
sizeLimit: 100Mi # 限制大小
空间限制测试: 我们在 busybox 容器内写入超过 100Mi 的数据,系统将报错 No space left on device,同时写入的数据能被同一 Pod 的 Nginx 容器立即读取(可通过 Nginx 访问测试:curl 10.244.1.16)。
2. hostPath (主机路径挂载)
将 Node 宿主机的文件系统路径直接挂载到 Pod 内。
[root@master volumes]# vim hostpath.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: hostpath
name: hostpath
spec:
containers:
- image: nginx
name: hostpath
volumeMounts:
- mountPath: /usr/share/nginx/html
name: timinglee
volumes:
- name: timinglee
hostPath:
path: /data
type: DirectoryOrCreate
测试中,Pod 调度到了 node1。我们通过 SSH 登录 node1,在 /data 目录下创建 index.html,随后直接 curl 访问 Pod 的 IP 即可看到内容。注意:此方案下若 Pod 发生节点漂移,数据不会随之迁移。
3. NFS 网络文件共享卷
为解决跨节点数据共享,引入 NFS 作为后端存储。
3.1 后端 NFS Server 准备 (在 Node3 上):
[root@node3 ~]# mkdir /share
[root@node3 ~]# dnf install nfs-utils -y
[root@node3 ~]# systemctl enable --now nfs-server.service
[root@node3 ~]# vim /etc/exports
/share *(sync,rw,no_root_squash)
[root@node3 ~]# exportfs -rv
3.2 集群 Worker 节点安装工具:
[root@master volumes]# for i in 10 20; do ssh -l root 172.25.254.$i dnf install nfs-utils -y; done
3.3 部署使用 NFS 卷的 Pod:
[root@master volumes]# vim nfs.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: web1
name: web1
spec:
nodeName: node1 # 指定调度到 node1
containers:
- image: nginx
name: web1
volumeMounts:
- mountPath: /usr/share/nginx/html
name: cache-vol
volumes:
- name: cache-vol
nfs:
server: 172.25.254.30
path: /share
漂移测试: 修改 YAML 将 nodeName 改为 node2 重建 Pod。即使 Pod 在不同节点间迁移,依然可以访问相同的 NFS 后端数据。
四、 持久卷高级架构:PV、PVC 与 StorageClass 动态供应
为了彻底解耦存储基础设施(管理员负责)和应用需求(开发者负责),K8s 引入了 PV (PersistentVolume) 和 PVC (PersistentVolumeClaim) 体系。
1. 静态持久卷构建
创建 PV (管理员侧): 预先准备好后端的存储目录(如 /share/pv1),并定义不同规格的 PV:
[root@master volumes]# vim pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce # RWO
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /share/pv1
server: 172.25.254.30
# 依次配置 pv2(RWX, 10G), pv3(ROX, 15G)...
申请 PVC (研发侧):
[root@master volumes]# vim pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
storageClassName: nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
创建后,K8s 控制面会自动撮合匹配的 PV 和 PVC 发生绑定 (Bound 状态)。此时,在 Pod 中只需引用 pvc1 即可使用底层分配好的 pv1。
2. 动态持久卷 (Dynamic Provisioning) 实战
在大规模集群中手动创建 PV 效率极低。我们通过部署 nfs-client-provisioner 实现存储自动化按需供给。
2.1 环境清理与镜像准备 清理旧的 PV/PVC。随后导入 provisioner 镜像:
[root@master volumes]# docker load -i /root/nfs-subdir-external-provisioner-4.0.2.tar
[root@master volumes]# 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 volumes]# docker push reg.timinglee.org/sig-storage/nfs-subdir-external-provisioner:v4.0.2
2.2 部署授权与 Provisioner 控制器 配置所需的 RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding 等,位于 storagesa.yml 中),随后部署 Provisioner Deployment:
[root@master volumes]# vim storageclassdep.yml
# ...Deployment 核心配置
containers:
- name: nfs-client-provisioner
image: sig-storage/nfs-subdir-external-provisioner:v4.0.2
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.25.254.30
- name: NFS_PATH
value: /share
# ...
2.3 创建存储类 (StorageClass) 并在应用中消耗 声明一个对接到上述 Provisioner 的存储类,并设为默认:
[root@master volumes]# vim storageclass.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "false"
使用 kubectl edit sc nfs-client 加入注解 storageclass.kubernetes.io/is-default-class: "true"。 此后,哪怕我们在 PVC 中不显式指定 storageClassName,集群也会自动在 NFS 后端创建目录并分配 PV!
3. 终极结合:StatefulSet 与动态卷的完美搭配
在部署数据库集群或高并发架构时,每个实例通常需要自己独立的数据目录。我们可以利用 StatefulSet + Headless Service + volumeClaimTemplates。
# 1. 建立无头服务
[root@master ~]# vim headless.yml
apiVersion: v1
kind: Service
metadata:
name: timinglee
spec:
clusterIP: None
ports:
- port: 80
selector:
app: webserver
# 2. 创建 StatefulSet
[root@master ~]# vim statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: webserver
spec:
serviceName: "timinglee"
replicas: 1
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
# 核心:自动为每个 Pod 根据 StorageClass 生成专用的 PVC
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
弹性扩缩容与存储隔离验证: 我们执行 kubectl scale statefulset webserver --replicas 3 将集群扩容到三个节点。 观察 NFS 服务端(node3)的目录,可以看到 Provisioner 自动为每一个 Pod 动态创建了相互隔离的目录!
[root@node3 share]# ll
drwxrwxrwx 2 root root 6 4月 19 14:22 default-www-webserver-0-pvc-...
drwxrwxrwx 2 root root 6 4月 19 14:24 default-www-webserver-1-pvc-...
drwxrwxrwx 2 root root 6 4月 19 14:25 default-www-webserver-2-pvc-...
我们在服务端向这三个目录分别写入 webserver1, webserver2, webserver3。 在集群内启动一个测试工具舱:
[root@master ~]# kubectl run -it testpod --image busyboxplus
[ root@testpod:/ ]$ curl webserver-0.timinglee
webserver1
[ root@testpod:/ ]$ curl webserver-1.timinglee
webserver2
[ root@testpod:/ ]$ curl webserver-2.timinglee
webserver3
验证了 StatefulSet 实现了稳定的网络标识解析与完全隔离的自动化持久化存储!即使删除了整个 StatefulSet 重新应用,数据与 Pod 标号的绑定关系依旧稳定不乱。