Kubernetes 全景解析 (3):存储体系与配置管理深度剖析
前言
在 Kubernetes 的核心能力矩阵中,存储与配置管理是决定应用能否在生产环境稳定运行的关键支柱。容器天生是"用完即弃"的——Pod 被删除后,其文件系统上的所有数据都会随之消失。然而,数据库需要持久化数据文件,日志系统需要保留历史记录,微服务需要在不重新构建镜像的前提下修改配置参数。这些需求共同构成了 K8s 存储体系与配置管理的设计动机。
本文将从存储体系的分层架构出发,逐层深入 Volume、PV/PVC、StorageClass 的设计原理与使用方法,再到 ConfigMap 与 Secret 的配置外部化实践,帮助你建立一套完整的 K8s 存储与配置认知框架。
一、K8s 存储体系概览
1.1 为什么容器需要持久化存储
容器的文件系统由一层层只读的镜像层(Image Layer)和一个可写的容器层(Container Layer)组成。当容器被删除时,可写层也会被一并回收。这意味着:
- 数据库数据会丢失:MySQL、PostgreSQL 等有状态服务将无法正常工作
- 日志会丢失:应用产生的日志随容器消亡而消失
- 文件上传会丢失:用户上传的附件、图片等临时存储在容器内会不可恢复
为了解决这些问题,Kubernetes 引入了 Volume 机制,将外部存储挂载到容器内部的指定路径,使数据独立于容器的生命周期而存在。
1.2 存储体系的分层设计
Kubernetes 的存储体系采用了经典的分层抽象设计,从底层到上层依次为:
| 层级 | 概念 | 作用域 | 核心职责 |
|---|---|---|---|
| L1 | Volume | Pod | 定义 Pod 内部存储的挂载方式 |
| L2 | PV / PVC | 集群 / 命名空间 | 解耦存储的供给与消费 |
| L3 | StorageClass | 集群 | 实现存储的动态供给与分类 |
这种分层设计的核心思想是关注点分离:开发人员只需声明"我需要多少存储"(PVC),运维人员负责"提供什么样的存储"(PV/StorageClass),两者通过标准的接口进行匹配。
K8s 存储体系的设计遵循"供需分离"原则。就像云计算中的 IaaS 层将计算资源虚拟化一样,K8s 将存储资源抽象为标准化的 API 对象,使上层应用无需关心底层存储的具体实现(NFS、Ceph、AWS EBS、GCE PD 等)。
二、Volume:Pod 级存储抽象
2.1 Volume 的基本概念
Volume 是 Kubernetes 中最基础的存储抽象,它定义在 Pod 规格中,与 Pod 的生命周期紧密绑定。当 Pod 被创建时,Kubelet 会负责将 Volume 挂载到容器内指定的路径;当 Pod 被删除时,Volume 的处理方式取决于其类型——临时 Volume 会被清除,而持久化 Volume 中的数据则会被保留。
Volume 的几个关键特性:
- Volume 定义在 Pod 级别:同一个 Pod 中的多个容器可以共享同一个 Volume
- Volume 的生命周期与 Pod 绑定:Pod 不存在时,Volume 也不存在(临时类型)
- Volume 支持多种后端:K8s 内置了数十种 Volume 类型,同时通过 CSI(Container Storage Interface)支持第三方存储
2.2 常见 Volume 类型
| Volume 类型 | 持久性 | 适用场景 | 说明 |
|---|---|---|---|
| emptyDir | 临时 | Pod 内容器间共享数据、缓存 | Pod 删除后数据丢失,可选用内存作为介质 |
| hostPath | 节点级 | 开发调试、访问节点文件 | 直接挂载宿主机路径,存在安全风险 |
| nfs | 持久 | 多 Pod 共享文件存储 | 需要外部 NFS 服务器 |
| configMap | 配置 | 注入配置数据 | 以文件或环境变量形式挂载 |
| secret | 敏感配置 | 注入密钥、证书 | Base64 编码存储,支持加密 |
| persistentVolumeClaim | 持久 | 有状态应用的数据持久化 | 引用 PV,生命周期独立于 Pod |
| csi | 取决于后端 | 接入第三方存储 | 通过 CSI 驱动对接各种存储系统 |
2.3 Volume 生命周期与 Pod 的绑定关系
# Volume 生命周期与 Pod 的关系
# 1. Pod 被调度到某个 Node
# 2. Kubelet 调用 Volume 插件挂载存储到宿主机
# 3. Kubelet 将宿主机上的挂载点绑定到容器内
# 4. Pod 运行期间,容器读写 Volume 中的数据
# 5. Pod 被删除时,Kubelet 执行卸载操作
# 6. 临时 Volume 数据被清除,持久化 Volume 数据被保留
emptyDir 的生命周期严格绑定到 Pod。即使 Pod 被驱逐(Eviction)或节点维护导致 Pod 被重新调度到其他节点,原节点上的 emptyDir 数据也会丢失。
2.4 完整 YAML 示例
以下示例展示了如何在 Pod 中使用多种 Volume:
apiVersion: v1
kind: Pod
metadata:
name: volume-demo
labels:
app: volume-demo
spec:
containers:
# 容器 1:写入数据到共享 Volume
- name: writer
image: busybox:1.36
command: ["/bin/sh", "-c"]
args:
- |
while true; do
date >> /data/log.txt
echo "Writer: data written" >> /data/log.txt
sleep 5
done
volumeMounts:
- name: shared-data
mountPath: /data
# 容器 2:从共享 Volume 读取数据
- name: reader
image: busybox:1.36
command: ["/bin/sh", "-c"]
args: ["tail -f /data/log.txt"]
volumeMounts:
- name: shared-data
mountPath: /data
# 容器 3:使用 hostPath 访问宿主机时间
- name: time-checker
image: busybox:1.36
command: ["/bin/sh", "-c"]
args: ["while true; do cat /host-time/hostname; sleep 10; done"]
volumeMounts:
- name: host-time
mountPath: /host-time
volumes:
# emptyDir:Pod 内容器间共享的临时存储
- name: shared-data
emptyDir:
sizeLimit: 256Mi # 可选:限制存储大小
# hostPath:挂载宿主机目录(仅用于开发/调试)
- name: host-time
hostPath:
path: /etc
type: Directory # Directory/DirectoryOrCreate/File/FileOrCreate/Socket/CharDevice/BlockDevice
三、PV 与 PVC:持久化存储的供需模型
3.1 PV(PersistentVolume):集群级存储资源
PersistentVolume 是集群级别的一块存储资源,由集群管理员创建,或者由 StorageClass 动态供给。PV 独立于 Pod 的生命周期存在,即使使用它的 Pod 被删除,PV 中的数据仍然保留。
PV 的核心属性包括:
- 容量(Capacity):存储大小,如
10Gi - 访问模式(Access Modes):定义如何访问存储
- 回收策略(Reclaim Policy):定义 PVC 释放后 PV 的处理方式
- 存储类(StorageClass):定义存储的类型和参数
- 挂载选项(Mount Options):传递给挂载命令的额外参数
3.2 PVC(PersistentVolumeClaim):命名空间级存储请求
PersistentVolumeClaim 是用户对存储资源的"申请单"。PVC 在命名空间内创建,用户通过 PVC 声明所需的存储大小、访问模式等需求,K8s 控制平面会自动将满足条件的 PV 绑定到该 PVC。
可以将 PV 理解为"机房里的一块硬盘",PVC 理解为"申请一块硬盘的工单"。管理员提前准备硬盘(静态供给),或者设置自动采购流程(动态供给),用户只需提交工单即可。
3.3 供给模式:静态供给 vs 动态供给
静态供给(Static Provisioning)
管理员预先创建 PV,用户创建 PVC 后,K8s 控制平面根据容量和访问模式进行匹配绑定。这种方式适合存储资源有限、需要精确控制的场景。
动态供给(Dynamic Provisioning)
管理员预先创建 StorageClass,用户在 PVC 中指定 StorageClass,K8s 自动调用对应的 Provisioner 创建 PV 并绑定到 PVC。这种方式是生产环境的主流选择,极大简化了存储管理。
3.4 访问模式
| 访问模式 | 缩写 | 说明 |
|---|---|---|
| ReadWriteOnce | RWO | 存储可被单个节点以读写模式挂载 |
| ReadOnlyMany | ROX | 存储可被多个节点以只读模式挂载 |
| ReadWriteMany | RWX | 存储可被多个节点以读写模式挂载 |
| ReadWriteOncePod | RWOP | 存储可被单个 Pod 以读写模式挂载(K8s 1.27+ 稳定) |
并非所有存储后端都支持所有访问模式。例如,AWS EBS、GCE PD、Azure Disk 等块存储通常只支持 RWO;而 NFS、CephFS、GlusterFS 等文件存储支持 RWX。在选择存储后端时,务必确认其支持的访问模式是否满足你的需求。
3.5 回收策略
| 回收策略 | 说明 | 适用场景 |
|---|---|---|
| Retain | PVC 释放后,PV 被保留但标记为 Released,数据不丢失 | 需要手动处理数据的场景 |
| Delete | PVC 释放后,PV 和底层存储资源被自动删除 | 动态供给的云盘等临时存储 |
| Recycle | 执行 rm -rf 清空数据后重新可用 | 已废弃,不建议使用 |
官方明确说明:The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning.
Recycle 策略仅对支持该操作的底层卷插件有效,执行基本的 rm -rf /thevolume/* 清理操作。在生产环境中,推荐使用以下替代方案:
- 动态供给(Dynamic Provisioning):通过 StorageClass 自动创建和管理 PV,设置
reclaimPolicy: Delete或reclaimPolicy: Retain - Retain + 手动管理:设置
reclaimPolicy: Retain,由管理员手动清理和复用 PV
3.6 PV/PVC 绑定流程
3.7 Storage Object in Use Protection
Storage Object in Use Protection(存储对象使用中保护)机制确保正在被 Pod 使用的 PVC 和已绑定到 PVC 的 PV 不会被意外删除,从而防止数据丢失。
PVC 保护机制:
当 PVC 正在被 Pod 使用时,如果用户删除 PVC,PVC 不会立即被删除。PVC 的删除会被推迟,直到它不再被任何 Pod 使用。
PV 保护机制:
当管理员删除已绑定到 PVC 的 PV 时,PV 不会立即被删除。PV 的删除会被推迟,直到它不再绑定到 PVC。
查看保护状态:
# 查看 PVC 的保护状态
kubectl describe pvc hostpath
输出示例:
Name: hostpath
Namespace: default
StorageClass: example-hostpath
Status: Terminating
Finalizers: [kubernetes.io/pvc-protection] # PVC 保护 finalizer
# 查看 PV 的保护状态
kubectl describe pv task-pv-volume
输出示例:
Name: task-pv-volume
Labels: type=local
Finalizers: [kubernetes.io/pv-protection] # PV 保护 finalizer
StorageClass: standard
Status: Terminating
Claim: default/hostpath
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 1Gi
Finalizer 是 Kubernetes 中的一种机制,用于确保资源在被删除前完成必要的清理操作。kubernetes.io/pvc-protection 和 kubernetes.io/pv-protection 这两个 finalizer 确保存储资源在使用中时不会被意外删除。
3.8 PV 删除保护 Finalizer(v1.33 Stable)
FEATURE STATE: Kubernetes v1.33 [stable](默认启用)
除了基础的 Storage Object in Use Protection,Kubernetes v1.33 引入了更强大的 PV 删除保护 finalizer 机制,确保具有 Delete 回收策略的 PV 仅在底层存储被删除后才从 Kubernetes 中移除。
两种 Finalizer:
| Finalizer 名称 | 引入版本 | 适用范围 | 说明 |
|---|---|---|---|
external-provisioner.volume.kubernetes.io/finalizer | v1.31 | CSI 卷(动态和静态供给) | 确保底层存储删除后才移除 PV 对象 |
kubernetes.io/pv-controller | v1.31 | In-tree 插件卷(仅动态供给) | 确保底层存储删除后才移除 PV 对象 |
CSI 卷示例:
kubectl describe pv pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d
输出示例:
Name: pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d
Annotations: pv.kubernetes.io/provisioned-by: csi.vsphere.vmware.com
Finalizers: [kubernetes.io/pv-protection external-provisioner.volume.kubernetes.io/finalizer]
StorageClass: fast
Status: Bound
Claim: demo-app/nginx-logs
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 200Mi
Source:
Type: CSI
Driver: csi.vsphere.vmware.com
In-tree 插件卷示例:
kubectl describe pv pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
输出示例:
Name: pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Annotations: kubernetes.io/provisioned-by: kubernetes.io/vsphere-volume
Finalizers: [kubernetes.io/pv-protection kubernetes.io/pv-controller]
StorageClass: vcp-sc
Status: Bound
Claim: default/vcp-pvc-1
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Source:
Type: vSphereVolume
这些 finalizer 确保:
- PV 对象仅在底层存储被删除后才从 Kubernetes 中移除
- 无论 PV 和 PVC 的删除顺序如何,都能确保底层存储被正确删除
- 当启用
CSIMigration{provider}特性门控时,kubernetes.io/pv-controller会被external-provisioner.volume.kubernetes.io/finalizer替代
3.9 PVC 扩容(v1.24 Stable)
FEATURE STATE: Kubernetes v1.24 [stable](默认启用)
PVC 扩容功能允许用户在 PVC 创建后增加其存储容量,而无需重新创建 PVC。
支持扩容的卷类型:
- CSI 卷(包括部分迁移的卷类型)
- flexVolume(已废弃)
- portworxVolume(已废弃)
前提条件:
PVC 的 StorageClass 必须设置 allowVolumeExpansion: true:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: example-vol-default
provisioner: vendor-name.example/magicstorage
parameters:
resturl: "http://192.168.10.100:8080"
allowVolumeExpansion: true # 允许扩容
扩容步骤:
- 编辑 PVC 对象,增加
spec.resources.requests.storage的值 - Kubernetes 控制平面触发底层卷的扩容操作
- 不会创建新的 PV,而是扩展现有卷
不要直接编辑 PV 的容量。如果先编辑 PV 的容量,再编辑 PVC 使其与 PV 匹配,Kubernetes 控制平面会认为卷已被手动扩容,不会触发自动扩容操作。
3.10 CSI 卷扩容(v1.24 Stable)
FEATURE STATE: Kubernetes v1.24 [stable](默认启用)
CSI 卷扩容功能默认启用,但需要特定的 CSI 驱动程序支持卷扩容。请参考具体 CSI 驱动程序的文档以获取更多信息。
3.11 VolumeAttributesClasses(VAC)
VolumeAttributesClasses(VAC)是 Kubernetes 中用于管理存储属性的新 API,允许在不重新创建卷的情况下修改存储的某些属性(如性能级别、IOPS、吞吐量等)。
VAC 的作用:
- 修改存储卷的性能参数(IOPS、吞吐量)
- 切换存储的备份策略
- 调整存储的加密级别
- 其他由存储后端支持的属性
VAC 示例:
apiVersion: storage.k8s.io/v1alpha1
kind: VolumeAttributesClass
metadata:
name: fast-ssd
driverName: csi.example.com
parameters:
iops: "5000"
throughput: "500"
encryption: "true"
VAC 目前仍处于 Alpha 阶段,具体支持程度取决于 CSI 驱动程序的实现。在生产环境使用前,请确认您的存储后端是否支持 VAC。
3.12 完整 YAML 示例
# ============================================
# 1. 静态供给:先创建 PV,再创建 PVC
# ============================================
# PersistentVolume:集群级存储资源
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
nfs:
server: 10.0.0.100
path: /data/mysql
# 可选:挂载选项
mountOptions:
- hard
- nfsvers=4.1
---
# PersistentVolumeClaim:用户存储请求
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: database
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: manual # 必须与 PV 的 storageClassName 一致
---
# 使用 PVC 的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: database
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
四、StorageClass:存储的"类"与动态供给
4.1 StorageClass 的作用与参数
StorageClass 为管理员提供了一种描述存储"类别"的方式。不同的 StorageClass 可以映射到不同的存储后端、不同的性能等级、不同的备份策略等。用户在创建 PVC 时只需指定 StorageClass,K8s 就会自动完成存储的创建和绑定。
StorageClass 的核心参数:
| 参数 | 说明 | 示例 |
|---|---|---|
| provisioner | 存储供给插件 | kubernetes.io/aws-ebs |
| parameters | 供给插件的具体参数 | type: gp3, iopsPerGB: "50" |
| reclaimPolicy | 默认回收策略 | Delete |
| volumeBindingMode | 卷绑定模式 | WaitForFirstConsumer |
| allowVolumeExpansion | 是否允许扩容 | true |
4.2 动态供给工作流
动态供给的核心流程如下:
- 用户创建 PVC,指定
storageClassName - PVC Controller 发现没有现成的 PV 可匹配
- PVC Controller 调用对应 StorageClass 的 Provisioner
- Provisioner 调用底层存储 API(如 AWS CreateVolume)创建实际的存储卷
- Provisioner 创建 PV 对象并关联到该 PVC
- PVC 状态变为 Bound,Pod 可以使用该 PVC
对于拓扑感知的存储(如 AWS EBS、GCE PD),建议设置 volumeBindingMode: WaitForFirstConsumer。这样 PV 不会在 PVC 创建时立即分配,而是等到第一个使用该 PVC 的 Pod 被调度后,再在与 Pod 相同的可用区(AZ)创建存储卷,避免跨可用区挂载的问题。
4.3 常见 Provisioner
| Provisioner | 存储类型 | 支持的访问模式 | 说明 |
|---|---|---|---|
kubernetes.io/aws-ebs | AWS EBS | RWO | AWS 弹性块存储 |
kubernetes.io/gce-pd | GCE Persistent Disk | RWO/RWX | Google Cloud 持久磁盘 |
kubernetes.io/azure-disk | Azure Disk | RWO | Azure 磁盘 |
kubernetes.io/azure-file | Azure File | ROX/RWX | Azure 文件存储 |
nfs.csi.k8s.io | NFS | ROX/RWX | NFS CSI 驱动 |
cephfs.csi.ceph.com | CephFS | ROX/RWX | Ceph 文件系统 |
rbd.csi.ceph.com | Ceph RBD | RWO | Ceph 块存储 |
local-path-provisioner | 本地路径 | RWO | 轻量级本地存储,适合开发测试 |
4.4 完整 YAML 示例
# ============================================
# StorageClass:定义存储类别
# ============================================
# 高性能 SSD 存储类(AWS EBS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iopsPerGB: "50"
throughput: "250"
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# 标准 HDD 存储类(AWS EBS st1)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard-hdd
provisioner: kubernetes.io/aws-ebs
parameters:
type: st1
fsType: ext4
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false
---
# 使用动态供给的 PVC(无需预先创建 PV)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd # 指定 StorageClass,触发动态供给
resources:
requests:
storage: 50Gi
---
# 本地路径 Provisioner(适合开发/测试环境)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
parameters:
archiveOnDelete: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
五、ConfigMap:配置外部化
5.1 ConfigMap 的创建方式
ConfigMap 用于将非敏感的配置数据从容器镜像中解耦出来,使配置可以在不重新构建镜像的情况下被修改和更新。ConfigMap 支持以下几种创建方式:
方式一:字面量创建
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100 \
--from-literal=FEATURE_FLAGS=dark-mode,experimental-api
方式二:从文件创建
kubectl create configmap nginx-config \
--from-file=nginx.conf=./configs/nginx.conf \
--from-file=default.conf=./configs/default.conf
方式三:从目录创建
# 将目录下所有文件作为 ConfigMap 的键值对
kubectl create configmap app-settings \
--from-file=./configs/
方式四:从 env-file 创建
kubectl create configmap app-env \
--from-env-file=./.env
5.2 在 Pod 中使用 ConfigMap
ConfigMap 可以通过三种方式注入到 Pod 中:
| 注入方式 | 适用场景 | 热更新 | 说明 |
|---|---|---|---|
| 环境变量 | 简单配置项 | 不支持 | Pod 重启后生效 |
| Volume 挂载 | 配置文件 | 支持 | 文件内容实时更新 |
| 命令行参数 | 启动参数 | 不支持 | 通过 $(ENV_VAR) 引用 |
5.3 ConfigMap 热更新机制与限制
当通过 Volume 挂载方式使用 ConfigMap 时,K8s 的 kubelet 会定期(默认每 60 秒)检查 ConfigMap 的更新,并将最新的内容同步到挂载的文件中。应用可以通过监听文件变化来重新加载配置。
- 通过环境变量注入的 ConfigMap 不支持热更新,必须重启 Pod 才能生效
- 已挂载的文件内容会延迟更新,更新间隔由 kubelet 的 sync period 决定(默认 60 秒)
- 应用需要自行实现配置重载逻辑,K8s 只负责更新文件内容,不会自动通知应用
- subPath 挂载不支持热更新,使用
subPath时,ConfigMap 更新不会反映到容器中
5.4 完整 YAML 示例
# ============================================
# ConfigMap:配置外部化
# ============================================
# 创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
# 简单键值对
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
CACHE_TTL: "300"
# 完整配置文件
application.yml: |
server:
port: 8080
shutdown: graceful
spring:
datasource:
url: jdbc:postgresql://db:5432/mydb
hikari:
maximum-pool-size: 20
logging:
level:
root: INFO
com.example: DEBUG
# Redis 配置
redis.conf: |
bind 0.0.0.0
port 6379
maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly yes
---
# Pod 中使用 ConfigMap(三种方式)
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo
namespace: production
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 8080
# 方式 1:作为环境变量
env:
- name: APP_LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: APP_MAX_CONNECTIONS
valueFrom:
configMapKeyRef:
name: app-config
key: MAX_CONNECTIONS
# 方式 1 补充:一次性导入所有键为环境变量
envFrom:
- configMapRef:
name: app-config
# 可选:前缀
prefix: APP_
# 方式 2:作为 Volume 挂载(支持热更新)
volumeMounts:
- name: config-vol
mountPath: /etc/app/config
readOnly: true
- name: redis-config
mountPath: /etc/redis/redis.conf
subPath: redis.conf # 注意:subPath 不支持热更新
# 方式 3:作为命令行参数
args: ["--log-level", "$(APP_LOG_LEVEL)"]
volumes:
- name: config-vol
configMap:
name: app-config
# 可选:指定要挂载的键
items:
- key: application.yml
path: application.yml
- key: redis.conf
path: redis.conf
# 可选:设置文件权限
defaultMode: 0644
- name: redis-config
configMap:
name: app-config
六、Secret:敏感信息管理
6.1 Secret 的类型
Secret 与 ConfigMap 类似,但专门用于存储敏感信息。Secret 中的数据以 Base64 编码存储(并非加密),在生产环境中应结合加密机制使用。
| Secret 类型 | 用途 | 说明 |
|---|---|---|
| Opaque | 通用类型 | 默认类型,存储任意键值对 |
| kubernetes.io/dockerconfigjson | Docker 镜像仓库认证 | 用于拉取私有镜像 |
| kubernetes.io/tls | TLS 证书 | 存储 TLS 证书和私钥 |
| kubernetes.io/basic-auth | 基本认证 | 存储用户名和密码 |
| kubernetes.io/ssh-auth | SSH 认证 | 存储 SSH 私钥 |
| kubernetes.io/service-account-token | ServiceAccount Token | 自动为 SA 生成 Token |
6.2 与 ConfigMap 的区别
| 对比维度 | ConfigMap | Secret |
|---|---|---|
| 数据编码 | 明文存储 | Base64 编码 |
| 大小限制 | 1 MiB | 1 MiB |
| etcd 加密 | 通常不加密 | 可配置 EncryptionConfiguration 加密 |
| 访问控制 | 标准 RBAC | 更严格的 RBAC 审计 |
| 使用场景 | 应用配置、环境变量 | 密码、密钥、证书、Token |
Secret 的 Base64 编码不是加密,任何人只要有访问 etcd 的权限就能解码。在生产环境中,务必启用 etcd 静态加密(Encryption at Rest),并严格限制 Secret 的访问权限。
6.3 生产环境 Secret 加密架构与最佳实践
由于 K8s 的 Base64 编码毫无机密性可言(类似于把中文字符翻译成拼音,任何人都能还原),一旦黑客获取了 etcd 数据库的备份文件、或者通过提权直接读取了物理机的磁盘,所有的密码与证书都将彻底暴露。
因此,在生产环境中,必须结合真正的加密机制来保护 Secret 数据。目前主流的三种架构方案如下:
方案一:etcd 静态加密与 KMS (Key Management Service) 插件
Kubernetes 原生支持在存储层进行加密(Encryption at Rest),即 kube-apiserver 在将 Secret 数据写入 etcd 前先将其加密,在客户端读取时再进行解密。
最基础的静态加密是利用本地密钥文件:
# /etc/kubernetes/pki/secrets-encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key> # 本地对称加密密钥
- identity: {} # 兜底:未加密的 Secret 仍可读取
进阶:结合 KMS 插件(生产强烈推荐)
将主密钥放在 apiserver 的本地配置文件中仍有泄漏风险,因此大型生产环境通常会接入云厂商的 KMS(如 AWS KMS、阿里云 KMS) 或 HashiCorp Vault。
在这种架构下,K8s 采用信封加密(Envelope Encryption):
kube-apiserver随机生成一个 DEK(数据加密密钥)把 Secret 加密。- API Server 通过 Unix Socket 调用 KMS 插件,KMS 插件将 DEK 发给外部密钥管理系统。
- 外部 KMS 使用它的 KEK(密钥加密密钥)把 DEK 加密后返回给 K8s。
- K8s 将加密后的 DEK和加密后的 Secret一并存入 etcd,实现了彻头彻尾的解耦和高安全性。
方案二:GitOps 工作流与 Sealed Secrets
如果在团队中实施 GitOps(如搭配 ArgoCD / FluxCD),所有的 K8s YAML 配置都要提交到 Git 仓库进行版本管理。但如果你将带有 Base64 编码的 Secret YAML 直推到 Github,无异于公开密码。
Bitnami Sealed Secrets 完美解决了这个问题:
- 集群中部署一个
Sealed Secrets Controller,它会在集群内部生成一对非对称的公私钥,并严格保护私钥。 - 开发者在本地使用
kubeseal命令行工具,结合前面获取的公钥,将原本的 Secret 加密为一个名为SealedSecret的自定义资源(CRD)。 - 无论谁拿到这个
SealedSecret的密文内容都无法解密,因此可以放心地将其公开 Push 到 Git 仓库。 - ArgoCD 将其同步进 K8s 集群后,处于集群内的 Controller 识别到该对象,利用极密私钥将其解密,并在集群内存中隐式还原为一个标准的 Kubernetes
Secret对象,供 Pod 挂载消费。
方案三:密码外置化与 External Secrets Operator (ESO)
对于多微服务多集群的企业级架构,将机密信息散落在各个 K8s 集群中维护是非常痛苦的。更现代的做法是引入外置机密治理中心。
工作原理:
- 真正的数据库密码、API Key 等完全不用
kubectl create secret来建,而是由安全团队统一录入类似 AWS Secrets Manager, Google Secret Manager 或 HashiCorp Vault 这种专门的极高安全等级保险箱内。 - 随后在 K8s 集群部署
External Secrets Operator。 - 运维人员在 K8s 声明一个
ExternalSecret对象,指明要去远程保险箱获取哪一个 Key。 - 部署的控制器拿着具备精细权限配比的凭证去远程保险箱调接口,读取到数据以后,动态地在 K8s 中生成一个原生
Secret给应用使用。并在云端密码发生变更时自动回滚和同步(甚至触发目标 Pod 重启)。
这实现了机密数据的"唯一真实数据源 (Single Source of Truth)"理念。
方案四:严格的 RBAC 控制(防线兜底)
不论采用何种加密架构,K8s 内部的 kube-apiserver 依然会自动向有权限的请求方分发解密好的数据。因此,防备越权读取的最后一公里,永远是最小权限原则(Principle of Least Privilege)。
# 仅允许特定 ServiceAccount 读取某个指定的 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: specific-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-keys"] # 切忌使用 * 或者留空
verbs: ["get"]
方案五:实操演练(直接窥探 etcd 证明 Base64 脆弱性)
为了更直观地理解为什么必须搭配 KMS/Sealed Secrets 等加密机制,你可以通过 etcdctl 直接查看集群底层的原生数据:
1. 创建一个普通的 Secret
kubectl create secret generic my-secret \
--from-literal=password=super_secret_pwd
2. 登录到 master 节点上,使用 etcdctl 直接读取数据:
ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret
3. 如果未开启静态加密,你会直接看到明文 base64 payload:
/registry/secrets/default/my-secret
... "password": "c3VwZXJfc2VjcmV0X3B3ZA==" ...
任何人只要拿到这串字符,便可用 echo "c3Vw...==" | base64 -d 变为 super_secret_pwd。
4. 如果配置了 etcd 静态加密(aescbc),底层返回的将是彻底的密文乱码:
/registry/secrets/default/my-secret
k8s:enc:aescbc:v1:key1:Q@c*a`X&_...
6.4 完整 YAML 示例
# ============================================
# Secret:敏感信息管理
# ============================================
# 1. Opaque Secret:通用密钥
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
# 值必须为 Base64 编码
# echo -n 'my-password' | base64
username: bXktdXNlcm5hbWU= # my-username
password: bXktcGFzc3dvcmQ= # my-password
connection-string: amRiYzpwb3N0Z3Jlc3FsOi8vZGItaG9zdDo1NDMyL215ZGI= # jdbc:postgresql://db-host:5432/mydb
---
# 2. Docker Registry Secret:镜像仓库认证
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
namespace: production
type: kubernetes.io/dockerconfigjson
data:
# 通过命令创建:kubectl create secret docker-registry ...
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJ1c2VyIiwicGFzc3dvcmQiOiJwYXNzIiwiYXV0aCI6ImRYTmxjanB3WVhOeiJ9fX0=
---
# 3. TLS Secret:HTTPS 证书
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
namespace: production
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
---
# 4. 在 Pod 中使用 Secret
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
# 拉取私有镜像
imagePullSecrets:
- name: docker-registry-secret
containers:
- name: api
image: my-registry.com/api-server:v2.1.0
ports:
- containerPort: 8080
# 方式 1:作为环境变量
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# 方式 2:作为 Volume 挂载
volumeMounts:
- name: db-secret-vol
mountPath: /etc/secrets/db
readOnly: true
- name: tls-vol
mountPath: /etc/tls
readOnly: true
volumes:
- name: db-secret-vol
secret:
secretName: db-credentials
defaultMode: 0400 # 仅所有者可读
- name: tls-vol
secret:
secretName: tls-secret
defaultMode: 0400
七、存储选型决策指南
7.1 存储类型对比
| 存储类型 | 持久性 | 性能 | 共享能力 | 适用场景 | 推荐度 |
|---|---|---|---|---|---|
| emptyDir | 临时 | 高(内存)/ 中(磁盘) | Pod 内 | 缓存、临时文件、容器间数据共享 | 中 |
| hostPath | 节点级 | 高 | 同节点 Pod | 开发调试、DaemonSet 日志收集 | 低 |
| PV/PVC + NFS | 持久 | 中 | 多节点 | 共享文件存储、内容管理系统 | 高 |
| PV/PVC + 云盘 | 持久 | 高 | 单节点 | 数据库、高性能单实例应用 | 高 |
| PV/PVC + Ceph | 持久 | 高 | 多节点 | 企业级存储、大规模集群 | 高 |
| CSI 第三方存储 | 取决于后端 | 取决于后端 | 取决于后端 | 特殊存储需求(对象存储、SAN 等) | 中 |
| ConfigMap | 配置 | - | - | 应用配置、环境变量、配置文件 | 高 |
| Secret | 敏感配置 | - | - | 密码、密钥、证书、Token | 高 |
7.2 存储选型决策树
- 有状态服务(数据库、消息队列):使用云盘 SSD + StorageClass 动态供给,设置
volumeBindingMode: WaitForFirstConsumer - 共享文件(上传文件、静态资源):使用 NFS / CephFS / 对象存储(通过 CSI)
- 应用配置:ConfigMap + Volume 挂载,配合 Reloader 等工具实现自动滚动更新
- 敏感信息:Secret + etcd 加密 + External Secrets Operator(对接 Vault 或云厂商密钥管理服务)
- 日志与监控数据:使用 emptyDir + sidecar 模式收集,或直接写入外部存储
八、本章小结
本文深入剖析了 Kubernetes 存储体系与配置管理的核心机制,让我们回顾一下关键要点:
存储体系的三层架构
- Volume 是最基础的存储抽象,定义在 Pod 级别,生命周期与 Pod 绑定
- PV/PVC 实现了存储的供需分离,PV 是集群级资源,PVC 是命名空间级请求
- StorageClass 实现了存储的动态供给,用户无需预先创建 PV
存储保护与安全机制
- Storage Object in Use Protection:通过
kubernetes.io/pvc-protection和kubernetes.io/pv-protectionfinalizer 保护正在使用的存储资源 - PV 删除保护 Finalizer(v1.33 Stable):
external-provisioner.volume.kubernetes.io/finalizer和kubernetes.io/pv-controller确保底层存储被正确删除后才移除 PV 对象 - Recycle 回收策略已废弃:推荐使用动态供给替代
存储扩容与属性管理
- PVC 扩容(v1.24 Stable):需要 StorageClass 设置
allowVolumeExpansion: true - CSI 卷扩容(v1.24 Stable):需要 CSI 驱动程序支持
- VolumeAttributesClasses(VAC):新 API,允许在不重建卷的情况下修改存储属性
配置管理的两大工具
- ConfigMap 用于管理非敏感配置,支持环境变量注入和 Volume 挂载(支持热更新)
- Secret 用于管理敏感信息,生产环境必须配合 etcd 加密和严格 RBAC 使用
选型的核心原则
- 根据数据的持久性需求选择临时存储或持久化存储
- 根据数据的共享需求选择块存储(RWO)或文件存储(RWX)
- 根据数据的敏感性选择 ConfigMap 或 Secret
- 优先使用 StorageClass 动态供给,减少运维负担
系列导航
章节 主题 状态 0 架构设计与核心概念 ✅ 已发布 1 工作负载与 Pod 生命周期深度解析 ✅ 已发布 2 网络模型与服务发现全链路解析 ✅ 已发布 3 存储体系与配置管理深度剖析 ✅ 已发布 4 调度器、资源管理与弹性伸缩 ✅ 已发布 5 安全体系与可观测性全景 ✅ 已发布 6 生产级微服务架构实战 ✅ 已发布 7 有状态应用与 Operator 模式实战 ✅ 已发布
相关阅读: