K8s学习笔记(十二) volume存储卷

在 Kubernetes 中,Volume(存储卷) 是解决容器存储问题的核心组件。它的核心作用是:为 Pod 中的容器提供 "持久化" 或 "共享" 的存储空间,弥补容器文件系统的临时性(容器重启后数据丢失)和隔离性(同一 Pod 内容器无法直接共享文件)的缺陷。

1 为什么需要 Volume?

容器的文件系统是 "临时的、隔离的",这会导致 3 个问题:

  1. 数据丢失:容器重启后,内部文件会被重置(比如 Nginx 容器重启,之前上传的静态文件会消失)。
  2. 跨容器共享:同一 Pod 内的多个容器(如 "应用容器 + 日志收集容器")需要共享数据(如日志文件),但容器间默认无法直接访问对方的文件系统。
  3. 持久化存储:需要将数据长期保存(如数据库数据),即使 Pod 被删除,数据也不能丢失。

Volume 正是为解决这些问题而生:

  • 它是Pod 级别的存储资源(与 Pod 生命周期绑定,Pod 删除后 Volume 才会被清理,除非是持久化存储)。
  • 可以被 Pod 内的多个容器同时挂载,实现数据共享。
  • 支持多种存储类型(本地磁盘、网络存储、配置文件等),灵活满足不同场景。

2 Volume 的核心概念(必懂)

  1. 生命周期:与 Pod 一致(Pod 创建时 Volume 被创建,Pod 删除时 Volume 被清理)。但注意:若 Volume 使用的是外部存储(如 NFS、PV),则底层数据会保留(仅 K8s 的 Volume 对象被删除)。
  2. 定义方式:
    • 在 Pod 的spec.volumes中定义 "卷"(说明卷的类型、来源等)。
    • 在容器的spec.containers.volumeMounts中定义 "挂载"(说明将卷挂载到容器内的哪个路径)。
  3. 核心关联volumes.namevolumeMounts.name必须一致,用于绑定卷和挂载点。

3 常见 Volume 类型

K8s 支持数十种 Volume 类型,这里按 "使用场景" 挑出最常用的 6 种,掌握它们就能应对 80% 的需求。

3.1 emptyDir:Pod 内临时共享存储(最基础)

  • 特点 :在 Pod 创建时自动创建的临时目录,存储在节点的本地磁盘(或内存),Pod 删除后数据丢失
  • 适用场景:同一 Pod 内容器间临时共享数据(如日志缓存、临时计算结果)。
示例:两个容器共享 emptyDir

创建一个 Pod,包含 "写文件容器" 和 "读文件容器",通过 emptyDir 共享数据:

yaml 复制代码
# pod-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-emptyDir
spec:
  containers:
  - name: writer  # 写文件的容器
    image: busybox:1.35
    command: ["sh", "-c", "while true; do echo $(date) > /shared/data.txt; sleep 5; done"]  # 每5秒写当前时间到文件
    volumeMounts:
    - name: shared-vol  # 挂载名为shared-vol的卷
      mountPath: /shared  # 挂载到容器内的/shared目录

  - name: reader  # 读文件的容器
    image: busybox:1.35
    command: ["sh", "-c", "while true; do cat /shared/data.txt; sleep 5; done"]  # 每5秒读取文件
    volumeMounts:
    - name: shared-vol  # 必须和volumes.name一致
      mountPath: /shared  # 挂载到容器内的/shared目录

  volumes:  # 定义卷
  - name: shared-vol  # 卷的名字,与上面的volumeMounts.name对应
    emptyDir:
      medium: ""  # 存储介质:默认磁盘;设为"Memory"则使用内存(tmpfs,速度快但容量受限于内存)
操作与验证:
bash 复制代码
# 创建Pod
kubectl apply -f pod-emptyDir.yaml

# 查看reader容器的输出(会每隔5秒显示当前时间)
kubectl logs -f pod-emptyDir -c reader
# 输出示例:
# Fri Oct  2 15:30:00 UTC 2025
# Fri Oct  2 15:30:05 UTC 2025

# 验证数据共享:进入writer容器,查看/shared目录
kubectl exec -it pod-emptyDir -c writer -- sh
ls /shared  # 会看到data.txt,内容与reader输出一致

3.2 hostPath:挂载节点本地目录(测试用)

  • 特点 :将 Pod 所在节点的本地文件或目录 挂载到容器内,数据会保留在节点上(Pod 删除后数据不丢,但 Pod 调度到其他节点时无法访问)。
  • 适用场景 :测试环境中需要持久化数据(如本地数据库测试),或需要访问节点文件(如挂载节点的/var/log收集日志)。
示例:挂载节点的/opt/hostpath-data目录
yaml 复制代码
# pod-hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-hostPath
spec:
  containers:
  - name: test-container
    image: busybox:1.35
    command: ["sh", "-c", "while true; do echo $(date) >> /data/hostpath.log; sleep 10; done"]
    volumeMounts:
    - name: hostpath-vol
      mountPath: /data  # 容器内的/data目录映射到节点的/opt/hostpath-data

  volumes:
  - name: hostpath-vol
    hostPath:
      path: /opt/hostpath-data  # 节点上的目录
      type: DirectoryOrCreate  # 目录不存在则自动创建(支持File、Directory等类型)
操作与验证:
bash 复制代码
# 创建Pod
kubectl apply -f pod-hostPath.yaml

# 找到Pod所在节点
kubectl get pod pod-hostPath -o wide
# 输出示例(NODE字段为节点名):
# NAME           READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE
# pod-hostPath   1/1     Running   0          1m    10.200.166.182   192.168.121.111     <none>

# 登录节点k8s-node-1,查看/opt/hostpath-data目录
ssh node1
cat /opt/hostpath-data/hostpath.log  # 会看到持续追加的时间日志(证明数据持久化到节点)

注意:hostPath 的局限性 ------ 多节点集群中,若 Pod 调度到其他节点,会访问新节点的/opt/hostpath-data(数据不共享),生产环境慎用!

3.3 nfs:跨节点共享的网络存储

  • 特点 :挂载 NFS 服务器的共享目录,支持多节点 Pod 共享数据(无论 Pod 调度到哪个节点,都能访问同一 NFS 目录)。
  • 适用场景:需要跨节点共享数据的场景(如多 Pod 写日志、静态资源共享)。
部署 NFS 服务器

NFS 服务器 IP 为192.168.121.109,共享目录为/data/k8sdata/chenjun666(需配置允许 K8s 节点访问)。

bash 复制代码
apt install nfs-server

mkdit -p /data/k8sdata/chenjun666

vim /etc/export
/data/k8sdata *(rw,no_root_squash)

systemctl restart nfs-server.service

systemctl enable nfs-server.service

systemctl status nfs-server.service

# 检查挂载
root@master1:~/yaml/volume_pod# showmount -e 192.168.121.109
Export list for 192.168.121.109:
/data/k8sdata *
示例:Pod 挂载 NFS 共享目录
yaml 复制代码
# pod-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nfs
spec:
  containers:
  - name: nfs-container
    image: busybox:1.35
    command: ["sh", "-c", "while true; do echo $(date) >> /nfs/data.log; sleep 10; done"]
    volumeMounts:
    - name: nfs-vol
      mountPath: /nfs  # 容器内挂载点

  volumes:
  - name: nfs-vol
    nfs:
      server: 192.168.121.109  # NFS服务器IP
      path: /data/k8sdata/chenjun666  # NFS共享目录
验证:

在 NFS 服务器上查看/nfs/shared/data.log,会看到 Pod 持续写入的日志(即使 Pod 调度到其他节点,数据仍会写入同一文件)。

3.4 configMap:挂载配置文件(非敏感配置)

  • 特点 :将 ConfigMap(K8s 的配置资源)中的键值对或文件,以文件形式挂载到容器内,方便配置管理(无需修改镜像即可更新配置)。
  • 适用场景 :应用的配置文件(如 Nginx 的nginx.conf、应用的app.properties)。
步骤 1:创建 ConfigMap(配置源)
yaml 复制代码
# configmap-demo.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # 键值对形式(会被挂载为文件,文件名=键,内容=值)
  app.properties: |
    env=production
    log_level=info
  # 直接定义文件内容(如nginx.conf)
  nginx.conf: |
    server {
      listen 80;
      root /usr/share/nginx/html;
    }
bash 复制代码
kubectl apply -f configmap-demo.yaml
步骤 2:Pod 挂载 ConfigMap
yaml 复制代码
# pod-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-configmap
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    volumeMounts:
    - name: config-vol  # 卷名
      mountPath: /etc/config  # 挂载到容器内的/etc/config目录
      readOnly: true  # 配置文件通常设为只读

  volumes:
  - name: config-vol
    configMap:
      name: app-config  # 关联的ConfigMap名称
      # 可选:只挂载部分键(默认挂载所有)
      # items:
      # - key: nginx.conf
      #   path: my-nginx.conf  # 重命名为my-nginx.conf
验证:
bash 复制代码
kubectl apply -f pod-configmap.yaml

# 进入容器,查看挂载的配置文件
kubectl exec -it pod-configmap -- sh
ls /etc/config  # 会看到app.properties和nginx.conf
cat /etc/config/app.properties  # 输出配置内容

注意:ConfigMap 更新后,挂载的文件会在几秒内自动同步(无需重启 Pod)。

3.5 secret:挂载敏感信息(密码、证书)

  • 特点 :与 ConfigMap 类似,但用于存储敏感信息(密码、Token、证书),数据会被 Base64 编码(非加密,需配合 RBAC 控制访问)
  • 适用场景:数据库密码、API 密钥、TLS 证书等。
步骤 1:创建 Secret(敏感信息)
yaml 复制代码
# secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque  # 通用类型
data:
  # 注意:值必须是Base64编码(echo -n "mypass123" | base64 生成)
  db-password: bXlwYXNzMTIz  # 原始值:mypass123
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tC...  # 证书内容的Base64编码
bash 复制代码
kubectl apply -f secret-demo.yaml
步骤 2:Pod 挂载 Secret
yaml 复制代码
# pod-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
spec:
  containers:
  - name: app
    image: busybox:1.35
    command: ["sleep", "3600"]
    volumeMounts:
    - name: secret-vol
      mountPath: /etc/secret  # 挂载到容器内的/etc/secret
      readOnly: true  # 敏感信息通常只读

  volumes:
  - name: secret-vol
    secret:
      secretName: app-secret  # 关联的Secret名称
验证:
bash 复制代码
kubectl apply -f pod-secret.yaml

# 进入容器,查看解密后的敏感信息
kubectl exec -it pod-secret -- sh
cat /etc/secret/db-password  # 输出:mypass123(自动解码Base64)

4 pv/pvc

在 Kubernetes 中,PV(PersistentVolume,持久卷)PVC(PersistentVolumeClaim,持久卷声明) 是解决 "持久化存储" 问题的核心机制,核心目标是解耦存储资源的 "供应" 和 "使用"------ 让运维人员负责提供存储(PV),开发人员只需声明存储需求(PVC),无需关心底层存储细节。

4.1 一句话理清关系

  • PV :相当于 "提前准备好的仓库"(由运维创建),是集群中的存储资源实体(比如一块云硬盘、一个 NFS 目录),属于 "集群级资源"(不绑定命名空间)。
  • PVC :相当于 "仓库使用申请单"(由开发创建),是 Pod 对存储的需求声明(比如 "要 10GB 空间、可读写"),属于 "命名空间级资源"(和 Pod 在同一命名空间)。
  • 绑定:PVC 会自动匹配满足条件的 PV(容量、访问模式等一致),绑定后一对一专属使用,其他 PVC 无法占用。

4.2 PV:运维视角的 "存储资源包"

PV 是对底层存储(如本地磁盘、NFS、云硬盘)的 "封装",每个 PV 都对应一块实际存储。创建时必须明确其核心属性,这些属性直接决定能否被 PVC 匹配。

4.2.1 PV 核心属性(绑定的关键)
属性 作用 重点值 / 示例
capacity 存储容量(最基础的匹配条件) storage: 10Gi(支持 Gi、Ti)
accessModes 访问模式(决定 PV 能被如何挂载,核心匹配条件 3 种模式(下文详解)
persistentVolumeReclaimPolicy 回收策略(PV 释放后如何处理数据,防数据泄露) Retain(保留数据)、Delete(删除)
storageClassName 存储类(关联 StorageClass,用于动态供应;空则为 "裸 PV") fast(自定义存储类)或空
claimRef 已绑定的 PVC(自动生成,无需手动设置) -
关键:accessModes(访问模式)

决定 PV 的 "共享能力",PVC 的访问模式必须是 PV 的子集 (比如 PV 支持RWO,PVC 只能请求RWO)。

模式 含义(核心!) 适用场景 支持存储类型举例
ReadWriteOnce (RWO) 只允许1 个节点以 "读写" 挂载(同一节点的多个 Pod 可共享) 单实例应用(MySQL、Redis 主节点) 云硬盘(EBS)、本地磁盘
ReadOnlyMany (ROX) 允许多个节点以 "只读" 挂载 多节点共享静态资源(如图片) NFS、GlusterFS
ReadWriteMany (RWX) 允许多个节点以 "读写" 挂载(最灵活,但支持的存储少) 分布式应用(Hadoop、多 Pod 写日志) NFS、CephFS

注意:RWO的 "One" 指 "一个节点",不是 "一个 Pod"------ 同一节点的多个 Pod 可以共享挂载。

4.2.2 静态供应 PV 示例(手动创建)

hostPath(仅测试用,生产不推荐)创建 PV:

yaml 复制代码
# pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-demo
spec:
  capacity:
    storage: 10Gi  # 容量10GB
  accessModes:
    - ReadWriteOnce  # 单节点读写
  persistentVolumeReclaimPolicy: Retain  # 释放后保留数据
  storageClassName: ""  # 不关联存储类(裸PV)
  hostPath:  # 实际存储是节点的/opt/pv-data目录
    path: /opt/pv-data
    type: DirectoryOrCreate  # 目录不存在则自动创建

操作:

bash 复制代码
kubectl apply -f pv-demo.yaml
kubectl get pv pv-demo  # 状态为Available(等待PVC绑定)

4.3 PVC:开发视角的 "存储需求单"

PVC 是对存储的 "需求声明",开发只需定义 "要什么"(容量、访问模式等),K8s 会自动匹配合适的 PV。

4.3.1 PVC 核心属性(匹配 PV 的条件)
属性 作用 示例
resources.requests 请求的容量(必须≤PV 的 capacity) storage: 10Gi
accessModes 请求的访问模式(必须是 PV 访问模式的子集) - ReadWriteOnce
storageClassName 请求的存储类(必须和 PV 的一致;空则匹配 "裸 PV") fast(匹配对应存储类的 PV)
4.3.2 PVC 绑定 PV 示例

创建 PVC 匹配上面的pv-demo

yaml 复制代码
# pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-demo
  namespace: default  # 必须和Pod同命名空间
spec:
  accessModes:
    - ReadWriteOnce  # 和PV的访问模式一致
  resources:
    requests:
      storage: 10Gi  # 和PV的容量一致
  storageClassName: ""  # 和PV的存储类一致(空)

操作:

bash 复制代码
kubectl apply -f pvc-demo.yaml
kubectl get pvc pvc-demo  # 状态变为Bound(绑定成功)
kubectl get pv pv-demo    # PV状态也变为Bound,CLAIM列显示绑定的PVC
4.3.3 Pod 如何使用 PVC?

Pod 通过volumes引用 PVC,将 PV 挂载到容器内:

yaml 复制代码
# pod-using-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-using-pvc
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    volumeMounts:
    - name: data-vol  # 卷名(和下面volumes.name对应)
      mountPath: /usr/share/nginx/html  # PV挂载到容器内的目录
  volumes:
  - name: data-vol
    persistentVolumeClaim:
      claimName: pvc-demo  # 引用的PVC名称

验证:在 Pod 内创建文件,会同步到 PV 对应的节点目录(/opt/pv-data)。

4.4 动态供应:用 StorageClass 自动创建 PV

静态供应(手动创建 PV)适合少量存储,生产环境更推荐动态供应------ 通过 StorageClass 自动创建 PV,无需运维手动干预。

4.4.1 StorageClass 的作用
  • 作为 "PV 模板":定义存储类型(如 NFS、云盘)、访问模式、回收策略等。
  • 动态创建 PV:当 PVC 请求某个 StorageClass 时,K8s 自动调用存储插件(如 NFS-Provisioner)创建 PV 并绑定。
4.4.2 动态供应示例(NFS 为例)

假设已部署 NFS 服务器(192.168.1.100:/nfs/data)和 NFS 插件(nfs-subdir-external-provisioner)。

创建 StorageClass:

yaml 复制代码
# storageclass-nfs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-sc
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner  # NFS插件的provisioner
parameters:
  archiveOnDelete: "true"  # 删除PV时归档数据
reclaimPolicy: Delete  # 动态PV的回收策略
allowVolumeExpansion: true  # 允许PVC扩容

创建 PVC 引用 StorageClass:

yaml 复制代码
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs
spec:
  accessModes:
    - ReadWriteMany  # NFS支持RWX
  resources:
    requests:
      storage: 5Gi
  storageClassName: nfs-sc  # 引用上面的StorageClass

操作:

bash 复制代码
kubectl apply -f storageclass-nfs.yaml
kubectl apply -f pvc-nfs.yaml
kubectl get pvc pvc-nfs  # 会快速Bound(自动创建PV)
kubectl get pv  # 能看到一个自动生成的PV(名称类似pvc-xxx)

4.5 PV/PVC 生命周期(从创建到释放)

  1. 供应 :静态(手动创 PV)或动态(StorageClass 自动创 PV),状态为Available
  2. 绑定 :PVC 匹配到 PV 后,二者状态变为Bound(一对一绑定)。
  3. 使用:Pod 引用 PVC 挂载 PV,数据写入底层存储。
  4. 释放:删除 PVC 后,PV 状态变为Released,数据处理由reclaimPolicy决定:
    • Retain:保留数据,PV 需手动清理后才能复用。
    • Delete:自动删除 PV 和底层存储(慎用,数据会丢失)。

4.6 常见问题与排错

  1. PVC 一直 Pending
    • 原因:无匹配的 PV(容量不足、访问模式不兼容、存储类不匹配)。
    • 排查:kubectl describe pvc 你的PVC名,看 Events(如 "no PV matches access modes [RWX]")。
  2. PV 绑定后无法使用
    • 原因:底层存储不可用(如 NFS 服务器宕机、云盘未挂载)。
    • 排查:检查存储后端状态,或查看 Pod 事件(kubectl describe pod 你的Pod名)。
  3. PVC 扩容失败
    • 前提:StorageClass 开启allowVolumeExpansion: true,且底层存储支持扩容。
    • 操作:编辑 PVC 的resources.requests.storage(如从 10Gi 改为 20Gi)。

总结

  • 核心分工:运维管 PV/StorageClass(供应存储),开发管 PVC/Pod(使用存储)。
  • 绑定关键:容量、访问模式、存储类必须匹配。

5 Volume vs 容器文件系统 vs PV/PVC(关键区别)

对比项 容器文件系统 Volume(如 emptyDir、hostPath) PV/PVC(通过 persistentVolumeClaim 类型)
生命周期 与容器一致(容器重启丢失) 与 Pod 一致(Pod 删除后清理) 独立于 Pod(PV 由集群管理,数据长期保留)
数据持久化能力 有限(hostPath 仅节点内持久化) 强(依赖外部存储,如 NFS、云盘)
跨节点共享 不支持 hostPath 不支持,nfs 支持 支持(取决于 PV 的存储类型)
适用场景 临时缓存(无需持久化) 同一 Pod 内共享、节点级临时持久化 生产环境持久化存储(数据库、业务数据)

6 常见问题与注意事项

  1. Volume 挂载后权限问题 :容器内挂载目录的权限可能与预期不符(如 root 权限),可通过volumeMounts.readOnly: true设为只读,或在 Pod 的securityContext中调整用户 ID(runAsUser)。
  2. emptyDir 占满磁盘 :emptyDir 默认使用节点磁盘,若写入大量数据可能占满磁盘,建议重要数据用 PV 或 NFS,临时数据可设medium: Memory(但受限于内存大小)。
  3. ConfigMap/Secret 更新延迟 :更新后通常 10 秒内同步到容器,但如果是 "子路径挂载"(subPath),则不会自动同步(需重启 Pod)。

7 总结

  • 临时共享数据(同一 Pod 内)→ emptyDir
  • 节点级测试持久化 → hostPath
  • 跨节点共享数据 → nfs
  • 非敏感配置文件 → configMap
  • 敏感信息 → secret
  • 生产环境持久化存储 → persistentVolumeClaim(关联 PV)
相关推荐
m0_598250003 小时前
串扰12-串扰对信号的影响
笔记·嵌入式硬件·硬件工程
拧之3 小时前
✅设计模式笔记
笔记·单例模式·设计模式
报错小能手3 小时前
linux学习笔记(13)文件操作
linux·笔记·学习
知识分享小能手4 小时前
微信小程序入门学习教程,从入门到精通,WXML(WeiXin Markup Language)语法基础(8)
前端·学习·react.js·微信小程序·小程序·vue·个人开发
LadyKaka2264 小时前
【IMX6ULL驱动学习】PWM驱动
linux·stm32·单片机·学习
MYX_3094 小时前
第四章 神经网络的学习
python·神经网络·学习
要做朋鱼燕5 小时前
【OpenCV】图像处理实战:边界填充与阈值详解
图像处理·笔记·opencv·计算机视觉
secondyoung6 小时前
Markdown转换为Word:Pandoc模板使用指南
开发语言·经验分享·笔记·c#·编辑器·word·markdown
roman_日积跬步-终至千里6 小时前
【系统架构设计-零】系统架构设计总述与学习线路
学习·系统架构