云原生(Kubernetes存储)

实验简介

在企业级云原生架构中,容器的无状态性虽然带来了极致的弹性和扩展能力,但也为配置管理、敏感信息保护以及数据持久化提出了挑战。作为日常 IT 运维工作的重要环节,如何解耦配置与代码、如何安全地分发凭证、以及如何为有状态服务(如数据库、中间件)提供可靠的存储底座,是构建高可用集群的关键。

  1. ConfigMap:实现应用配置与镜像解耦,支持热更新。

  2. Secret:安全管理密码、Token 及私有镜像仓库(Harbor)的认证凭证。

  3. 基础 Volumes :探究 emptyDirhostPath 以及基于网络的 NFS 存储卷。

  4. 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=3306ipaddress=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 标号的绑定关系依旧稳定不乱。

相关推荐
kyriewen2 小时前
你等的Babel编译,够喝三杯咖啡了——用Rust重写的SWC,只需眨个眼
前端·javascript·rust
搬砖码2 小时前
同源多标签页通信 4 种方案,从入门到生产环境
前端·面试
张元清2 小时前
SSR 状态管理陷阱:defineStore vs defineContextStore
前端·javascript·面试
donecoding2 小时前
nrm、corepack、npm registry 三者的爱恨情仇
前端·node.js·前端工程化
小gaigagi3 小时前
从吉客云·奇门到MySQL的完整数据流
前端
悟空瞎说3 小时前
用 Rust 开发 QML 桌面应用(第二篇)—— 日志系统完整搭建
前端
LIO3 小时前
前端开发之Git 代码仓库管理详细教程
前端·git
软件开发技术深度爱好者3 小时前
前端网页开发三剑客快速入门
前端
openKaka_3 小时前
为什么 React 18 之后使用 createRoot,而不是 ReactDOM.render
前端·javascript·react.js