3.4 配置与存储
3.4.1 配置管理
Kubernetes(K8s)的配置管理是其核心能力之一,它旨在将应用配置(如配置文件、环境变量、命令行参数等)从容器镜像中解耦,从而实现 "一次构建,到处部署" 的云原生理念
在传统模式下,配置通常被"硬编码"到应用代码或Docker镜像中,导致不同环境(开发、测试、生产)需要不同的镜像,管理复杂且易出错。K8s的配置管理通过一系列原生API对象解决了这个问题。
K8s的配置管理的核心是:将配置数据作为独立的K8s资源进行管理,并通过卷(Volume)或环境变量等方式动态注入到Pod容器中,实现配置与应用的分离。
3.4.1.1 configMap
管理非敏感的配置数据,例如配置文件、命令行参数、环境变量、端口号等。
-
数据以键值对(key-value)形式存储。
-
Value可以是简单的字符串,也可以是完整的配置文件内容(如JSON、XML、Properties、YAML等)。
-
存储大小有限制(默认约1MB in etcd)。
配置configMap的资源
-
命令行形式 : kubectl creat configmap <资源名称> --from-literal=< ? 文件别名>=文件地址/文件夹路径(整个文件夹下所有文件)
-
配置形式:
apiVersion: v1
kind: ConfigMap
metadata:
name: game-config
namespace: default
labels:
app: game
tier: backend
data:简单键值对
game.type: "mmorpg"
game.difficulty: "hard"配置文件内容(整个文件作为值)
server.properties: |
enemy.types=aliens,monsters
player.maximum-lives=5
level.complexity=high另一个配置文件
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=trueJSON 配置
config.json: |
{
"logging": {
"level": "DEBUG",
"file": "/var/log/app.log"
},
"features": {
"enableCache": true,
"maxItems": 100
}
}在 Pod 中使用 configmap
- 作为环境变量 : 将整个 configmap 的特定键注入容器环境变量
- 作为文件挂载 : 支持热更新
- 作为命令行参数 : 通过环境变量间接传递apiVersion: v1
kind: Pod
metadata:
name: configmap-env-pod
spec:
containers:- name: test-container
image: busybox
command: [ "/bin/sh", "-c", "env" ]
env:定义环境变量 LOG_LEVEL,值来自 ConfigMap
- name: LOG_LEVEL
valueFrom: # 将整个 configmap 的特定键注入容器环境变量
configMapKeyRef:
name: app-config # ConfigMap 名称 ,单写这一个,指将所有键值对作为环境变量
key: log-level # ConfigMap 中的键
optional: false # 可选,如果为 true,ConfigMap 不存在时 Pod 也能启动
- name: LOG_LEVEL
apiVersion: v1
kind: Pod
metadata:
name: configmap-volume-pod
spec:
containers:- name: test-container
image: busybox
command: [ "/bin/sh", "-c", "ls -la /etc/config && cat /etc/config/*" ]
volumeMounts:- name: config-volume
mountPath: /etc/config # ConfigMap 中的每个键都会成为一个文件
readOnly: true
volumes: # 作为文件挂载
- name: config-volume
- name: config-volume
configMap:
name: game-config # 引用的 ConfigMap
挂载效果
/etc/config/
├── game.type # 文件内容: "mmorpg"
├── game.difficulty # 文件内容: "hard"
├── server.properties # 文件内容: "enemy.types=aliens..."
└── ui.properties # 文件内容: "color.good=purple..." - name: test-container
3.4.1.2 Secret
专门用于管理和存储敏感信息 ,如密码、OAuth令牌、SSH密钥、TLS证书等。
使用方式与ConfigMap几乎完全相同(也支持环境变量和Volume挂载)。
K8s会对Secret数据进行Base64编码(仅是一种简单的混淆,并非加密)。在Master节点上,etcd中存储的数据可以启用静态加密。
Secret有三种主要类型:
Opaque(通用,默认)kubernetes.io/tls(TLS证书)kubernetes.io/dockerconfigjson(镜像仓库认证)。
| 场景 | 使用 ConfigMap | 使用 Secret |
|---|---|---|
| 数据库连接字符串 | ❌ 敏感 | ✅ 使用 Secret |
| 日志级别 | ✅ 非敏感 | ❌ |
| API 端点 URL | ✅ 非敏感 | ❌ |
| TLS 证书 | ❌ 敏感 | ✅ 使用 Secret |
| 特性开关 | ✅ 非敏感 | ❌ |
3.4.1.3 subPath
使用 ConfigMap 或 Secret 挂载到目录的时候,会将容器中源目录给覆盖掉,此时我们可能只想覆盖目录中的某一个文件,但是这样的操作会覆盖整个文件,因此需要使用到 SubPath
# 假设你有一个容器镜像,其目录结构如下:
/opt/app/
├── app.jar
├── config/
│ ├── application.yaml # 需要被替换的配置
│ └── security.yaml # 需要保留的默认配置
└── logs/
# 直接将 ConfigMap 挂载到 `/opt/app/config`
volumeMounts:
- name: config-volume
mountPath: /opt/app/config # 这会覆盖整个 config 目录!
# `security.yaml` 文件会消失!因为整个目录被 ConfigMap 的内容覆盖了。
# 这时候subPath就派出用场了:
volumeMounts:
- name: config-volume
mountPath: /opt/app/config/application.yaml # 挂载到特定文件路径
subPath: application.yaml # 只挂载这个文件 volume 的 path 配置中 不要以 / 开头,且
3.4.1.4 配置热更新
我们通常会将项目的配置文件作为 configmap 然后挂载到 pod,那么如果更新 configmap 中的配置,会不会更新到 pod 中呢?这得分成几种情况:
默认方式:会更新,更新周期是更新时间 + 缓存时间
subPath:不会更新
变量形式:如果 pod 中的一个变量是从 configmap 或 secret 中得到,同样也是不会更新的
对于 subPath 的方式,我们可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置,但是如果目标位置原本就有文件,可能无法创建软链接,此时可以基于 postStart 操作执行删除命令,将默认的吻技安删除即可
- 热更新方式
- 通过 edit 命令直接修改 configmap
- 通过 replace 替换
- 如果 configmap 基于文件创建,不会有 yaml 文件,无法直接替换.
- kubectl create cm --from-file=nginx.conf<文件> --dry-run -oyaml | kubelctl replace -f- # f 后面的 - 表示接收控制台的输出变成另一个命令的输入.
- 该命令重点在于 --dry-run 参数(打印 yaml 文件单不将文件发给 apiserver 再结合 -oyaml 输出 yaml 文件,就得到一个配置好但没发送给 apiserver 的文件,然后再结合 replace 监听控制台输出得到 yaml 数据可实现替换)
3.4.1.5 不可变的 Secrte 和 configmap
对于一些敏感的服务配置文件,在线上是不允许被修改的,此时配置 configmap 可以设置 immutable:true 来禁止修改.
3.4.2 持久化存储
3.4.2.1 Volumes
HostPath : 将节点上的文件或目录挂载到 Pod 上,此时该目录会变成持久化存储目录,即使 Pod 被删除后重启,也可以加载到该目录,该目录下文件不会丢失
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: nginx-volume
volumeMounts:
- mountPath: /test-pd # 挂载到容器的哪个目录
name: test-volume # 挂载哪个 volume
volumes:
- name: test-volume
hostPath: # 与主机共享目录,加载主机中指定目录到容器
path: /data # 节点中的目录
type: Directory # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查
类型:
空字符串:默认类型,不做任何检查
DirectoryOrCreate:如果给定的 path 不存在,就创建一个 755 的空目录
Directory:这个目录必须存在
FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限为 644
File:这个文件必须存在
Socket:UNIX 套接字,必须存在
CharDevice:字符设备,必须存在
BlockDevice:块设备,必须存在
emptyDir : 临时目录,Pod删除数据就没了,管理同一个 Pod 的不同容器之间的文件管理
# 定义一个使用 EmptyDir 临时存储卷的 Pod 资源
apiVersion: v1 # API版本:Pod 属于核心组资源,v1 是稳定版本
kind: Pod # 资源类型:声明该YAML文件定义的是 Pod 资源
metadata: # 元数据段:用于描述 Pod 的基础信息(名称、标签、注解等)
name: test-pd # Pod名称:在所属命名空间内唯一标识该Pod
spec: # 规格段:定义 Pod 的核心配置(容器、存储、网络、调度等)
containers: # 容器列表:一个Pod可包含多个容器,此处是数组形式(- 开头表示元素)
- image: nginx # 容器镜像:使用官方 nginx 镜像(默认拉取最新版)
name: nginx-emptydir # 容器名称:在Pod内唯一标识该容器
volumeMounts: # 容器挂载配置:定义该容器需要挂载的 Volume 列表
- mountPath: /cache # 挂载路径:将 Volume 挂载到容器内的 /cache 目录
name: cache-volume # 挂载的Volume名称:关联下方 spec.volumes 中定义的 cache-volume(名称必须一致)
volumes: # Pod级Volume列表:定义该Pod可用的存储卷(所有容器可共享)
- name: cache-volume # Volume名称:与容器 volumeMounts.name 对应,作为挂载关联标识
emptyDir: {} # Volume类型:emptyDir(空目录卷),{} 表示使用默认配置
# emptyDir特性:Pod调度到节点后自动创建,Pod删除/迁移时数据永久丢失
# 默认存储在节点磁盘,可配置 medium: Memory 改为内存存储(tmpfs)
3.4.2.2 NFS 网络文件系统
nfs 卷能将 NFS (网络文件系统)挂载到你的 Pod 中,不像 emptyDir 那样会被当 Pod 删除时也会别删除, NFS 卷的内容在删除 Pod 时会被保存,卷只是被卸载.
独立服务器部署
# 在专用服务器上(假设IP:192.168.1.100)
# 1. 安装NFS服务
sudo apt-get update
sudo apt-get install nfs-kernel-server -y # Ubuntu/Debian
# 或
sudo yum install nfs-utils -y # CentOS/RHEL
# 2. 创建共享目录
sudo mkdir -p /data/nfs-share
sudo chmod 777 /data/nfs-share # 简化权限,生产环境需细化
# 3. 配置共享目录
sudo vim /etc/exports
在 K8S 上部署
# nfs-server.yaml
apiVersion: apps/v1 # 使用Deployment资源的API版本
kind: Deployment # 定义资源类型为部署控制器
metadata: # 资源的元数据部分
name: nfs-server # 部署的名称,用于标识和引用
spec: # 部署的具体规格配置
replicas: 1 # Pod副本数量,设为1表示只运行一个NFS服务器实例
selector: # 标签选择器,用于选择要管理的Pod
matchLabels: # 匹配标签的条件
app: nfs-server # 必须匹配标签app=nfs-server的Pod
template: # Pod模板定义,描述如何创建Pod
metadata: # Pod的元数据
labels: # 给Pod设置的标签
app: nfs-server # 标签键值对,与上面的选择器匹配
spec: # Pod的具体规格
containers: # 容器定义列表
- name: nfs-server # 容器名称
image: gcr.io/google_containers/volume-nfs:0.8 # NFS服务器容器镜像
ports: # 容器暴露的端口列表
- name: nfs # 端口名称,便于识别
containerPort: 2049 # NFS协议主端口,用于文件传输
- name: mountd # 端口名称
containerPort: 20048 # NFS挂载守护进程端口,处理挂载请求
- name: rpcbind # 端口名称
containerPort: 111 # RPC绑定端口,NFS依赖的服务端口
securityContext: # 容器的安全上下文配置
privileged: true # 授予容器特权模式,NFS服务需要访问内核功能
volumeMounts: # 容器内的卷挂载配置
- mountPath: /exports # 容器内的挂载点路径,NFS将共享此目录
name: nfs-data # 引用的卷名称,必须与下面的volumes名称匹配
volumes: # Pod级别的存储卷定义
- name: nfs-data # 卷名称,供容器引用
hostPath: # 卷类型:使用宿主机路径
path: /data/nfs # 宿主机上的实际目录路径
---
apiVersion: v1 # 使用Service资源的API版本
kind: Service # 定义资源类型为服务
metadata: # 服务的元数据
name: nfs-server # 服务的名称
spec: # 服务的规格配置
ports: # 服务暴露的端口列表
- name: nfs # 端口名称
port: 2049 # 服务端口,集群内部访问时使用的端口
targetPort: 2049 # 容器端口,流量转发到容器的这个端口
- name: mountd # 端口名称
port: 20048 # 服务端口
targetPort: 20048 # 容器端口
- name: rpcbind # 端口名称
port: 111 # 服务端口
targetPort: 111 # 容器端口
selector: # 标签选择器,指定后端Pod
app: nfs-server # 选择标签为app=nfs-server的Pod作为后端
clusterIP: None # 设置为Headless Service,不分配集群IP
# 这样Pod可以直接通过DNS记录访问,适合有状态服务
宿主机存储 Kubernetes集群 DNS查询 返回Pod IP 读写数据 宿主机 /data/nfs nfs-server Service NFS Server Pod NFS客户端Pod
使用 NFS
# nfs-pod-direct.yaml
apiVersion: v1
kind: Pod
metadata:
name: nfs-web
spec:
containers:
- name: web-server
image: nginx
volumeMounts:
- name: nfs-storage
mountPath: /usr/share/nginx/html # Nginx默认网页目录
readOnly: false
volumes:
- name: nfs-storage
nfs:
server: 192.168.1.100 # NFS服务器IP
path: /data/nfs-share # 共享目录
readOnly: false # 读写模式
-----------------------------------------------------------------
# 高性能NFS配置示例
volumes:
- name: nfs-optimized
nfs:
server: 192.168.1.100
path: /data/nfs
readOnly: false
mountOptions:
- hard
- intr
- noatime
- nodiratime
- nfsvers=4.1
- rsize=1048576 # 1MB读缓冲
- wsize=1048576 # 1MB写缓冲
- tcp # 使用TCP协议
- timeo=600 # 60秒超时
- retrans=2 # 重试2次
3.4.2.3 PV 与 PVC

PV : 资源存储池 ->
-
管理员创建:运维人员提前准备好
-
集群级别:整个K8s集群都能用
-
独立存在:不依赖任何Pod
-
多种类型:NFS、云硬盘、本地磁盘等
-
状态 :
- Available : 空闲未被绑定
- bound : 已经被 PVC 绑定
- Released : PVC 被删除,资源已回收,但 PV 未被重新使用
- Failed : 自动回失败
-
存储资源池(PV们):
├── PV1:100G SSD云盘(快速)
├── PV2:500G HDD本地盘(慢速)
├── PV3:1TB NFS共享存储(共享)
└── PV4:50G 高速SSD(超快)\
PVC : 资源申请单
-
开发人员创建:应用需要存储时申请
-
命名空间级别:只在当前项目/命名空间有效
-
声明需求:只要说"我要多大的、什么性能的"
-
自动匹配:系统自动找合适的PV给你
-
存储申请(PVC们):
├── 申请1:我要30G,快点就行(给MySQL)
├── 申请2:我要100G,能共享的(给文件服务器)
└── 申请3:我要10G,最便宜的(给测试环境)
3.4.2.3.1 部署一个容器实现示例
-
管理员准备创建
管理员准备存储(创建PV)
运维人员创建两个"饭菜"(PV)
apiVersion: v1
kind: PersistentVolume
metadata:
name: fast-ssd-pv # PV名字:高速SSD
spec:
capacity:
storage: 100Gi # 容量:100GB
accessModes:
- ReadWriteOnce # 访问模式:一次只能一个Pod读写
storageClassName: fast # 存储类型标签:快速型
hostPath:
path: "/data/ssd" # 实际存储位置
apiVersion: v1
kind: PersistentVolume
metadata:
name: slow-hdd-pv # PV名字:慢速HDD
spec:
capacity:
storage: 500Gi # 容量:500GB
accessModes:
- ReadWriteMany # 访问模式:多个Pod可以同时读写
storageClassName: slow # 存储类型标签:慢速型
nfs:
server: 192.168.1.100
path: "/data/nfs" -
开发人员申请 pvc
WordPress开发人员申请:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-storage-pvc # PVC名字:WordPress存储申请
spec:
accessModes:
- ReadWriteOnce # 需求:单Pod读写 / 权限需要与对应的 pv 相同
resources:
requests:
storage: 20Gi # 需求:20GB空间
storageClassName: fast # 需求:快速存储(匹配fast标签)

-
系统自动匹配
K8s系统自动匹配过程:
- 收到PVC申请:"要20G,快速,单Pod读写"
- 查找PV池:
- fast-ssd-pv:100G,fast类型,单Pod读写 ✅ 匹配!
- slow-hdd-pv:500G,slow类型,多Pod读写 ❌ 类型不匹配
- 绑定:把fast-ssd-pv分配给wp-storage-pvc
- 状态变更:
- fast-ssd-pv状态:Available → Bound(已绑定)
- wp-storage-pvc状态:Pending → Bound(已满足)
-
应用使用存储 (Pod 挂载)
apiVersion: v1
kind: Pod
metadata:
name: wordpress-pod
spec:
containers:- name: wordpress
image: wordpress:latest
volumeMounts:- name: data-volume # 给卷起个名字
mountPath: /var/www/html # 挂载到容器这个路径
volumes:
- name: data-volume # 给卷起个名字
- name: data-volume # 卷的名字
persistentVolumeClaim: # 卷类型:使用PVC
claimName: wp-storage-pvc # 使用哪个PVC(重点是这一行!)
- name: wordpress
3.4.2.3.2 重要概念详解
- 访问模式 : 就像文件的打开方式
| 模式 | 说明 | 比喻 | 适用场景 |
|---|---|---|---|
| ReadWriteOnce (RWO) | 单节点读写 | 单人间,一次只能一个人住 | 数据库(MySQL) |
| ReadOnlyMany (ROX) | 多节点只读 | 图书馆,很多人可以同时看 | 配置文件、静态资源 |
| ReadWriteMany (RWX) | 多节点读写 | 会议室白板,多人可写可读 | 文件共享、用户上传目录 |
- 回收策略 : 用完后怎么处理
| 策略 | 说明 | 比喻 | 何时使用 |
|---|---|---|---|
| Retain | 保留数据 | 退房后房间保持原样 | 重要数据(数据库) |
| Delete | 删除数据 | 退房后清空房间 | 临时数据(测试环境) |
| Recycle | 清理数据(已废弃) | 退房后简单打扫 | 基本不用了 |
- StorageClass : 代购,自动售货机
每次都要管理员提前创建PV,太麻烦了!StorageClass应运而生,他相当于一个自动创建PV的模板
# StorageClass定义:一个"自动创建PV的模板"
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd-class # 存储类名字
provisioner: kubernetes.io/aws-ebs # 提供者:AWS云硬盘 / 制备器 : 用什么形式来创建 PV
parameters:
type: gp3 # 硬盘类型:通用型SSD
fsType: ext4 # 文件系统:ext4
archiveOnDelete # 是否存档(不存档 会删除 old 下面的数据,存档 会重命名路径)
reclaimPolicy: Ratain #
实际应用中云厂商的 StorageClass 已经内置好了,不用自己创建
# 前提:管理员已经创建了StorageClass
#(通常云厂商的K8s已经内置了)
# 开发人员一步到位:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-auto-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard # 使用标准的StorageClass
resources:
requests:
storage: 20Gi
3.4.2.3.3 常见问题
- pv 与 PVC 一定要异议对应吗
- 是的!一个PVC只能绑定一个PV,一个PV也只能绑定一个PVC。就像:一个停车位(PV)只能停一辆车(PVC),一辆车也只能占一个停车位。
- PVC 申请的容量可以大于 PV 吗
- 不可以
- 删除 Pod 后数据会丢失吗?
- 不会,数据在 PV 里面,不在 Pod.
- 删除 PVC 后 pv 会怎样
- 看配置的回收策略,保留还是删除
- SorageClass 是必须的吗?
- 不是,但推荐.
3.4.2.3.4 常见命令
# 1. 创建PV
kubectl apply -f mysql-pv.yaml
# 2. 查看PV状态
kubectl get pv
# 应该看到:STATUS = Available(可用)
# 3. 创建PVC
kubectl apply -f mysql-pvc.yaml
# 4. 查看绑定情况
kubectl get pv
kubectl get pvc
# 应该看到:STATUS = Bound(已绑定)
# 5. 创建MySQL Pod
kubectl apply -f mysql-pod.yaml
# 6. 测试数据持久化
kubectl exec -it mysql -- mysql -ppassword -e "CREATE DATABASE test;"
kubectl delete pod mysql
kubectl apply -f mysql-pod.yaml
kubectl exec -it mysql -- mysql -ppassword -e "SHOW DATABASES;"
# 应该还能看到test数据库