Kubernetes 存储管理
第一篇 Secrets 敏感信息管理
第一章 Secrets 概述与创建
1.1 知识点:什么是 Secrets
Secrets 是 Kubernetes 中专门用于管理敏感信息的资源对象,用于存储密码、OAuth Token、SSH Key、TLS 证书等敏感数据。与 ConfigMap 不同,Secrets 的设计目标是安全地管理和传递敏感信息,其核心优势包括:
- 集中管理:将敏感信息从 Pod 定义和容器镜像中分离,避免密码等敏感数据硬编码在代码或配置文件中
- 细粒度权限控制:通过 RBAC 可以精确控制哪些用户/服务账号可以访问特定的 Secret
- 多种类型支持:支持 Opaque(通用)、kubernetes.io/tls(TLS 证书)、kubernetes.io/dockerconfigjson(Docker 认证)等多种类型
- 与 Pod 生命周期解耦:Secret 可以独立于 Pod 存在和更新
安全提示:Secret 数据在 etcd 中以 Base64 编码存储。Base64 是可逆编码而非加密,因此安全性较弱。生产环境建议启用 etcd 静态加密功能,并结合网络策略和 RBAC 进行防护。
Secrets 的类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
| Opaque | 通用类型,数据以 Base64 编码存储 | 用户名/密码、令牌等 |
| kubernetes.io/tls | 存储 TLS 证书和私钥 | HTTPS 服务的证书管理 |
| kubernetes.io/dockerconfigjson | 存储 Docker Registry 认证信息 | 私有镜像仓库拉取镜像 |
| kubernetes.io/service-account-token | 服务账号令牌 | Pod 访问 API Server |
1.2 实验:Secrets 的三种创建方式
方式一:通过命令行创建
知识点串联: kubectl create secret generic 创建通用类型(Opaque)的 Secret,每个 --from-literal 指定一个 key-value 对。通过 kubectl describe 查看 Secret 时,只显示数据的字节数而非明文内容,这是 Secret 与 ConfigMap 在显示层面的安全差异。
bash
# 创建Secret,包含用户名和密码
[root@master storage]# kubectl create secret generic timinglee \
--from-literal userlist=timinglee \
--from-literal password=lee
secret/timinglee created
# 查看Secret列表
[root@master storage]# kubectl get secrets
NAME TYPE DATA AGE
auth-web Opaque 1 5d19h
timinglee Opaque 2 2m48s
web-tls-secret kubernetes.io/tls 2 5d19h
# 查看Secret详情
[root@master storage]# kubectl describe secrets timinglee
Name: timinglee
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 3 bytes
userlist: 9 bytes
bash
# 以YAML格式查看(数据为Base64编码)
[root@master storage]# kubectl get secrets timinglee -o yaml
apiVersion: v1
data:
password: bGVl
userlist: dGltaW5nbGVl
type: Opaque
kind: Secret
metadata:
name: timinglee
namespace: default
# 验证Base64编码(Base64不等于加密,可逆)
[root@master storage]# echo -n "dGltaW5nbGVl" | base64 -d
timinglee

方式二:通过文件创建
知识点串联: 通过 --from-file 从文件创建 Secret 时,文件名作为 key,文件内容作为 value。这种方式适合存储较大的敏感文件(如证书文件),避免在命令行中直接暴露敏感内容。
bash
# 准备密码文件(注意:echo -n 避免写入换行符)
[root@master storage]# echo -n timinglee > username.txt
[root@master storage]# echo -n lee > password.txt
# 通过文件创建Secret
[root@master storage]# kubectl create secret generic userlist \
--from-file=username.txt \
--from-file=password.txt
secret/userlist created
# 查看创建的Secret
[root@master storage]# kubectl get secrets userlist -o yaml
apiVersion: v1
data:
password.txt: bGVl
username.txt: dGltaW5nbGVl
kind: Secret
metadata:
name: userlist
type: Opaque

方式三:通过 YAML 创建
知识点串联: 通过 YAML 文件创建 Secret 时,所有值必须预先 Base64 编码。可以使用 echo -n "value" | base64 命令进行编码。YAML 方式适合将 Secret 纳入版本管理(GitOps 流程),但需注意不要将编码后的敏感数据提交到公开仓库。
bash
# 预先编码值
[root@master storage]# echo -n "timinglee" | base64
dGltaW5nbGVl
[root@master storage]# echo -n "lee" | base64
bGVl
# 编写YAML文件
[root@master storage]# vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: timinglee
data:
username: dGltaW5nbGVl
password: bGVl
# 应用Secret
[root@master storage]# kubectl apply -f secret.yaml
secret/timinglee created
# 验证创建结果
[root@master storage]# kubectl describe secrets timinglee
Name: timinglee
Namespace: default
Type: Opaque
Data
====
password: 3 bytes
username: 9 bytes

第二章 Secrets 的使用方式
2.1 知识点:Secrets 的三种注入方式
Secrets 在 Pod 中有三种主要使用方式,每种方式适用于不同的场景:
| 注入方式 | 实现机制 | 适用场景 | 是否支持热更新 |
|---|---|---|---|
| 数据卷挂载 | 将 Secret 挂载为容器内的文件 | TLS 证书、配置文件 | 是(自动更新) |
| 环境变量注入 | 通过 secretKeyRef 注入为环境变量 |
数据库密码等简单敏感值 | 否(需重启 Pod) |
| 镜像拉取认证 | 使用 imagePullSecrets 引用 |
私有镜像仓库认证 | 否 |
核心设计原则:
- 数据卷方式将 Secret 以文件形式挂载,容器通过读取文件获取敏感信息,适合需要文件形式的证书
- 环境变量方式将 Secret 值直接设置为环境变量,适合简单的键值对敏感信息
- 镜像拉取认证方式让 kubelet 在拉取镜像时自动提供认证信息,对应用容器透明
2.2 实验:将 Secret 以数据卷方式注入 Pod
实验一:注入 TLS 证书到 Pod
知识点串联: 使用 kubernetes.io/tls 类型的 Secret 可以存储 TLS 证书和私钥。Kubernetes 会自动将证书和私钥分别存储为 tls.crt 和 tls.key 两个文件。将 Secret 挂载到 Pod 后,容器可以直接使用这些证书配置 HTTPS 服务。
bash
# 生成自签名TLS证书
[root@k8s-master secrets]# openssl req -newkey rsa:2048 -nodes \
-sha512 \
-keyout timinglee.org.key \
-x509 -days 365 -out timinglee.org.crt
# 填写证书信息
Country Name (2 letter code) [XX]:CN
State or Province Name: Shaanxi
Locality Name: xi'an
Organization Name: timinglee
Organizational Unit Name: example
Common Name: www.timinglee.org
Email Address: admin@timinglee.org
# 创建TLS类型的Secret
[root@k8s-master secrets]# kubectl create secret tls webtlskey \
--key timinglee.org.key \
--cert timinglee.org.crt
secret/webtlskey created
# 创建Pod,将Secret挂载到容器
[root@k8s-master secrets]# vim web.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx:latest
name: nginx
volumeMounts:
- name: webtls
mountPath: /secret
readOnly: true
volumes:
- name: webtls
secret:
secretName: webtlskey
# 启动并验证文件已挂载
[root@k8s-master secrets]# kubectl apply -f web.yaml
pod/nginx created
[root@k8s-master secrets]# kubectl exec -it pods/nginx -c nginx -- /bin/bash
root@nginx:/# ls /secret
tls.crt tls.key
root@nginx:/# cat /secret/tls.crt | head -5
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL...
-----END CERTIFICATE-----


实验二:指定挂载路径和文件名
知识点串联: 通过 items 字段可以精确控制 Secret 中每个 key 挂载到容器中的文件路径和文件名。这在需要将证书文件命名为特定名称(如服务所需的证书文件名)时非常有用。
bash
# 指定每个key的挂载路径和文件名
[root@k8s-master secrets]# vim web.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx:latest
name: nginx
volumeMounts:
- name: webtls
mountPath: /secret
readOnly: true
volumes:
- name: webtls
secret:
secretName: webtlskey
items:
- key: tls.crt
path: timinglee.org.crt # 自定义文件名
- key: tls.key
path: timinglee.org.key
# 验证自定义路径
[root@k8s-master secrets]# kubectl apply -f web.yaml
pod/nginx created
[root@k8s-master secrets]# kubectl exec -it pods/nginx -c nginx -- /bin/bash
root@nginx:/# cd /secret/
root@nginx:/secret# ls
timinglee.org.crt timinglee.org.key

2.3 实验:将 Secret 设置为环境变量
实验一:通过 secretKeyRef 注入数据库密码
知识点串联: 使用 env + valueFrom.secretKeyRef 将 Secret 中的值注入为环境变量,是注入数据库密码等敏感信息的最常用方式。与 ConfigMap 的 configMapKeyRef 类似,但专门用于敏感数据。
bash
# 创建Pod,通过secretKeyRef注入用户名和密码
[root@master storage]# vim testpod.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: busybox
name: busybox
spec:
containers:
- image: busybox
name: busybox
command:
- /bin/sh
- -c
- env
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: timinglee
key: userlist
- name: PASSWD
valueFrom:
secretKeyRef:
name: timinglee
key: password
restartPolicy: Never
# 验证环境变量已注入
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl logs pods/busybox busybox
...
USERNAME=timinglee
PASSWD=lee

实验二:为 MySQL 注入 Root 密码
知识点串联: 在部署数据库应用时,通常通过环境变量传递 Root 密码。使用 Secret 注入密码可以避免在 Pod 定义中明文写入密码,提高安全性。MySQL 镜像通过 MYSQL_ROOT_PASSWORD 环境变量接收密码。
bash
# 创建密码Secret
[root@k8s-master secrets]# echo -n "lee" | base64
bGVl
[root@k8s-master secrets]# cat passwd.yaml
apiVersion: v1
kind: Secret
metadata:
name: passwd
data:
pass: bGVl
# 创建MySQL Pod,使用Secret中的密码
[root@k8s-master secrets]# vim mysql.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: mysql
name: mysql
spec:
containers:
- image: mysql:8.0
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: passwd
key: pass
# 验证MySQL可以使用注入的密码登录
[root@k8s-master secrets]# kubectl apply -f mysql.yaml
pod/mysql created
[root@k8s-master secrets]# kubectl exec -it pods/mysql -c mysql -- /bin/sh
sh-5.1# mysql -uroot -plee
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
mysql>

2.4 实验:使用 Secrets 管理 Docker 私有仓库认证
知识点串联: 当 Kubernetes 节点需要从私有镜像仓库(如 Harbor)拉取镜像时,需要配置 imagePullSecrets 提供认证信息。使用 kubectl create secret docker-registry 可以自动生成包含 .dockerconfigjson 的 Secret,格式符合 Docker 认证要求。
bash
# 前提:已在Harbor私有仓库上传镜像
# docker login reg.timinglee.org
# docker tag nginx:latest reg.timinglee.org/timinglee/nginx:latest
# docker push reg.timinglee.org/timinglee/nginx:latest
# 问题演示:未配置认证时拉取镜像失败
[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
[root@master storage]# kubectl apply -f testpod.yml
[root@master storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp 0/1 ImagePullBackOff 0 26s
# 查看错误信息
[root@master storage]# kubectl describe pods myapp
...
Warning Failed kubelet Failed to pull image: unauthorized to access repository

错误分析:
ImagePullBackOff表示镜像拉取失败,原因是私有仓库需要认证。Kubernetes 节点没有该仓库的访问凭证。
bash
# 创建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
# 验证Secret内容
[root@master storage]# kubectl get secrets docker-auth -o yaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJyZWcudGltaW5nbGVlLm9yZyI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJsZWUiLCJlbWFpbCI6InRpbWluZ2xlZUB0aW1pbmdsZWUub3JnIiwiYXV0aCI6IllXUnRhVzQ2YkdWbCJ9fX0=
type: kubernetes.io/dockerconfigjson
# 解码查看内容
[root@master storage]# echo -n "eyJhdXRocyI6eyJyZWcudGltaW5nbGVlLm9yZyI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJsZWUiLCJlbWFpbCI6InRpbWluZ2xlZUB0aW1pbmdsZWUub3JnIiwiYXV0aCI6IllXUnRhVzQ2YkdWbCJ9fX0=" | base64 -d
{"auths":{"reg.timinglee.org":{"username":"admin","password":"lee","email":"timinglee@timinglee.org","auth":"YWRtaW46bGVl"}}}


关键类型说明:
kubernetes.io/dockerconfigjson是专门用于 Docker 镜像仓库认证的 Secret 类型,会自动生成符合 Docker 格式的认证配置文件(.dockerconfigjson)。
bash
# 使用imagePullSecrets引用认证Secret
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
# 验证镜像成功拉取
[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

第二篇 Volume 卷管理
第三章 emptyDir 临时卷
3.1 知识点:emptyDir 卷
emptyDir 是 Kubernetes 中最基础的卷类型,其核心特性如下:
- 生命周期绑定:emptyDir 卷在 Pod 分配到节点时创建,在 Pod 删除时永久销毁
- 数据存储位置 :默认使用宿主机磁盘空间;设置
medium: Memory后使用 tmpfs(内存文件系统) - 容器间共享:同一 Pod 中的多个容器可以挂载同一个 emptyDir 卷进行数据交换
- 数据易失性:Pod 删除或容器崩溃重启后,emptyDir 中的数据会丢失
设计意图:emptyDir 适用于临时存储场景,如缓存、临时文件、容器间数据传递等不需要持久化的场景。
3.2 实验:emptyDir 内存卷的使用与容量限制
知识点串联: 本实验创建一个包含两个容器的 Pod:busybox 用于写入数据,nginx 用于提供 Web 服务,两者共享同一个 emptyDir 卷。通过 medium: Memory 将卷存储在内存中,并通过 sizeLimit 限制内存使用量,验证超出限制时的行为。
bash
# 创建包含两个容器的Pod,共享emptyDir内存卷
[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 # 限制最大100MB
# 启动Pod并验证nginx可访问
[root@master volumes]# kubectl apply -f empty.yml
[root@master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
empty 2/2 Running 0 66s 10.244.1.16 node1
[root@master volumes]# curl 10.244.1.16
<html><head><title>403 Forbidden</title></head>

bash
# 通过busybox容器写入数据
[root@master volumes]# kubectl exec -it pods/empty -c busybox -- /bin/sh
/ # cd cache/
/cache # echo hello timinglee > index.html
# 验证nginx可以读取busybox写入的文件(容器间共享验证通过)
[root@master volumes]# curl 10.244.1.16
hello timinglee
# 测试内存限制:写入99MB成功,写入100MB失败
/cache # dd if=/dev/zero of=bigfile bs=1M count=99
99+0 records in
103809024 bytes (99.0MB) copied, 0.246898 seconds, 401.0MB/s
/cache # dd if=/dev/zero of=bigfile bs=1M count=100
dd: error writing 'bigfile': No space left on device
100+0 records in
104853504 bytes (100.0MB) copied, 0.029183 seconds, 3.3GB/s

关键参数说明:
medium: Memory将卷存储在内存中(tmpfs),读写速度极高但数据易失sizeLimit: 100Mi限制卷的最大容量,超出时会触发No space left on device错误- 两个容器通过同一个
cache-vol名称挂载同一卷,实现容器间数据共享
第四章 hostPath 卷
4.1 知识点:hostPath 卷
hostPath 卷将宿主机文件系统上的文件或目录挂载到 Pod 中。其核心特性与风险:
- 与节点强绑定:Pod 被调度到指定节点后,才能访问该节点上的 hostPath 路径
- 数据持久性:Pod 删除后数据保留在宿主机上,但 Pod 重建后可能调度到其他节点导致无法访问
- 安全隐患:允许 Pod 访问宿主机文件系统,可能泄露敏感数据或破坏宿主机
适用场景:调试、日志收集(如挂载 /var/log)、需要访问宿主机特定文件(如 Docker 引擎的 /var/lib/docker)等。生产环境应谨慎使用,配合 Pod 调度策略确保 Pod 始终运行在指定节点上。
hostPath 的 type 参数
| type 值 | 说明 |
|---|---|
| DirectoryOrCreate | 如果路径不存在则自动创建 |
| Directory | 路径必须存在且为目录 |
| FileOrCreate | 如果路径不存在则自动创建文件 |
| File | 路径必须存在且为文件 |
| Socket | 路径必须存在且为 Unix 套接字 |
| CharDevice | 路径必须存在且为字符设备 |
| BlockDevice | 路径必须存在且为块设备 |
4.2 实验:hostPath 基本使用
知识点串联: 创建 Pod 将宿主机的 /data 目录挂载到容器的 nginx 网页目录,通过在宿主机写入文件来验证数据共享。使用 type: DirectoryOrCreate 确保即使宿主机目录不存在也会自动创建。
bash
# 创建Pod,使用hostPath挂载宿主机目录
[root@master volumes]# kubectl run hostpath --image nginx --dry-run=client -o yaml > hostpath.yml
[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
[root@master volumes]# kubectl apply -f hostpath.yml
pod/hostpath created
[root@master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hostpath 1/1 Running 0 14s 10.244.1.17 node1

bash
# 在宿主机上写入网页文件
ssh -l root node1
[root@node1 ~]# mkdir -p /data
[root@node1 ~]# echo hello timinglee > /data/index.html
# 验证通过Pod的IP可以访问宿主机写入的内容
[root@master volumes]# curl 10.244.1.17
hello timinglee
# 验证Pod删除后数据仍保留在宿主机
[root@master volumes]# kubectl delete pod hostpath
pod/hostpath deleted
[root@node1 ~]# ls /data/
index.html # 数据依然存在


4.3 实验:企业级 MySQL 初始化(initContainer + hostPath + emptyDir)
知识点串联: 本实验展示了一个企业级场景:使用 initContainer 实现 MySQL 数据库的初始化。checkdata 容器负责检查数据是否已存在,若不存在则从备份文件恢复数据;storedata 容器负责初始化 MySQL 并导入数据。两个容器通过 emptyDir 共享临时备份文件,MySQL 数据目录通过 hostPath 持久化到宿主机。
bash
# 企业级MySQL初始化配置
[root@master storage]# vim mysql.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: mysql-1
name: mysql-1
spec:
initContainers:
# 第一个initContainer:检查数据是否已存在
- name: checkdata
image: mysql:8.0
command: ["/bin/sh", "-c"]
args:
- |
DATADIR=/var/lib/mysql
if [ -f "${DATADIR}/ibdata1" ]
then
echo "skip dump data"
exit 0
else
mysqldump -h10.244.1.28 -uroot -plee -B --all-databases > /tmp/dump/allbase.sql && {
echo data storedata
exit 0
}
fi
volumeMounts:
- name: mysqldata
mountPath: /var/lib/mysql
- name: mysqldump
mountPath: /tmp/dump
# 第二个initContainer:初始化数据库并导入数据
- name: storedata
image: mysql:8.0
command: ["/bin/sh", "-c"]
args:
- |
DATADIR=/var/lib/mysql
if [ -f "/tmp/dump/allbase.sql" ]
then
chown -R 999:999 ${DATADIR}
mysqld --initialize-insecure --user mysql --datadir=/var/lib/mysql
mysqld --skip-networking --socket=/var/lib/mysql/mysqld.sock &
PID=$!
sleep 8
mysql -uroot -S /var/lib/mysql/mysqld.sock < /tmp/dump/allbase.sql
rm -rf /tmp/dump/allbase.sql
kill ${PID}
wait ${PID} || true
echo "dump successful"
else
echo "not need dump"
exit 0
fi
volumeMounts:
- name: mysqldata
mountPath: /var/lib/mysql
- name: mysqldump
mountPath: /tmp/dump
# 主容器:MySQL服务
containers:
- image: mysql:8.0
name: mysql-1
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: passwd
key: passwd
volumeMounts:
- name: mysqldata
mountPath: /var/lib/mysql
volumes:
- name: mysqldump
emptyDir:
medium: Memory # 临时备份文件存储在内存中
- name: mysqldata
hostPath:
path: /data # 数据库数据持久化到宿主机
type: DirectoryOrCreate
关键设计说明:
- initContainer 按定义顺序依次执行,
checkdata先执行检查,storedata后执行初始化- emptyDir 用于临时备份文件,不占用磁盘空间
- hostPath 确保 MySQL 数据在 Pod 重建后不丢失
- 使用
secretKeyRef注入 Root 密码,避免明文暴露
第五章 NFS 卷
5.1 知识点:NFS 卷
NFS(Network File System)卷允许 Kubernetes Pod 挂载远程 NFS 共享存储。其核心特性:
- 共享存储:多个 Pod(甚至跨节点)可以挂载同一个 NFS 共享目录
- 数据持久化:数据存储在 NFS 服务器上,Pod 删除后数据不丢失
- 访问模式:支持 ReadWriteOnce(RWO)、ReadWriteMany(RWX)、ReadOnlyMany(ROX)等多种访问模式
- 前置条件 :NFS 服务器需提前配置,且所有需要挂载 NFS 的节点上需安装
nfs-utils
NFS 访问模式对比
| 访问模式 | 全称 | 说明 |
|---|---|---|
| ReadWriteOnce (RWO) | 单节点读写 | 该卷只能被单个节点以读写方式挂载 |
| ReadOnlyMany (ROX) | 多节点只读 | 多个节点可以以只读方式挂载 |
| ReadWriteMany (RWX) | 多节点读写 | 多个节点可以同时以读写方式挂载 |
5.2 实验:NFS 共享存储的搭建与使用
步骤一:搭建 NFS 服务器
知识点串联: 在节点上安装 NFS 服务软件包,配置共享目录和访问权限,启动 NFS 服务。/etc/exports 文件定义共享目录和访问规则,sync 参数确保写入操作同步到磁盘,no_root_squash 允许 root 用户以 root 权限访问远程目录。
bash
# 在NFS服务器上操作
[root@node3 ~]# mkdir /share
[root@node3 ~]# dnf install nfs-utils -y
[root@node3 ~]# systemctl enable --now nfs-server.service
# 配置共享目录(允许所有客户端以sync,rw,no_root_squash方式访问)
[root@node3 ~]# vim /etc/exports
/share *(sync,rw,no_root_squash)
# 刷新NFS共享配置
[root@node3 ~]# exportfs -rv
exporting *:/share
[root@node3 ~]# showmount -e
Export list for node3:
/share *
步骤二:在所有工作节点安装 NFS 客户端
bash
# 在所有work节点安装nfs-utils
[root@master volumes]# for i in 10 20; do
ssh -l root 172.25.254.$i dnf install nfs-utils -y
done
# 验证各节点可以访问NFS服务器
[root@master volumes]# for i in 10 20; do
ssh -l root 172.25.254.$i showmount -e 172.25.254.30
done
Export list for 172.25.254.30:
/share *
步骤三:创建使用 NFS 卷的 Pod
知识点串联: 在 Pod 的 volumes 中指定 nfs 类型,提供 NFS 服务器地址和共享路径,即可将远程 NFS 目录挂载到容器中。NFS 卷的核心优势是跨节点数据共享------无论 Pod 被调度到哪个节点,只要能够访问 NFS 服务器,就可以访问同一份数据。
bash
# 创建Pod,挂载NFS共享目录
[root@master volumes]# kubectl run web --image nginx --dry-run=client -o yaml >> nfs.yml
[root@master volumes]# vim nfs.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: web1
name: web1
spec:
nodeName: 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 # NFS服务器地址
path: /share # 共享目录路径
# 启动Pod
[root@master volumes]# kubectl apply -f nfs.yml
pod/web1 created
[root@master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
web1 1/1 Running 0 7s 10.244.1.18 node1
# 在NFS服务器上写入网页文件
[root@node3 ~]# echo hello timinglee > /share/index.html
# 验证Pod可以访问NFS上的文件
[root@master volumes]# curl 10.244.1.18
hello timinglee

bash
# 验证跨节点共享:将Pod调度到另一个节点,仍然可以访问同一NFS
[root@master volumes]# vim nfs.yml
apiVersion: v1
kind: Pod
metadata:
labels:
run: web1
name: web1
spec:
nodeName: node2 # 更改运行节点
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
[root@master volumes]# kubectl apply -f nfs.yml
pod/web1 created
[root@master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
web1 1/1 Running 0 13s 10.244.2.5 node2
[root@master volumes]# curl 10.244.2.5
hello timinglee # 跨节点访问成功

第六章 PersistentVolume 持久卷
6.1 知识点:PV 与 PVC 架构
PersistentVolume(PV) 是集群中由管理员预配置的一块存储资源,是集群中的全局资源,不隶属于任何命名空间。它抽象了底层存储实现(NFS、ISCSI、云存储等)的细节。
PersistentVolumeClaim(PVC) 是用户对存储的请求,指定需要的存储大小和访问模式。Pod 通过引用 PVC 来使用存储。
PV 与 PVC 的工作流程
- 管理员创建 PV(静态供给)或配置 StorageClass(动态供给)
- 用户创建 PVC 声明需要的存储
- Kubernetes 通过绑定机制将 PV 与 PVC 配对(一对一映射)
- Pod 通过引用 PVC 来使用存储
PV 的回收策略(ReclaimPolicy)
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Retain | 保留数据,需管理员手动清理 | 需要保留数据以备恢复 |
| Delete | 自动删除 PV 和底层存储 | 临时数据,无需保留 |
| Recycle | 清除数据后重新可用(已废弃) | 不推荐使用 |
PV 的状态说明
| 状态 | 说明 |
|---|---|
| Available | 空闲资源,尚未绑定到任何 PVC |
| Bound | 已绑定到某个 PVC |
| Released | 绑定的 PVC 已被删除,但存储资源尚未被集群回收 |
| Failed | 自动回收操作失败 |
6.2 实验:静态持久卷的创建与使用
步骤一:在 NFS 服务器上创建存储目录
bash
# 在NFS服务器上创建PV对应的目录
[root@node3 ~]# mkdir /share/pv{1..3} -p
[root@node3 ~]# dnf install nfs-utils -y
[root@node3 ~]# systemctl enable --now nfs-server
[root@node3 ~]# vim /etc/exports
/share *(sync,rw,no_root_squash)
[root@node3 ~]# exportfs -rv
exporting *:/share
步骤二:创建 PV 和 PVC
知识点串联: 创建三个 PV,分别使用不同的容量和访问模式(RWO/RWX/ROX),然后创建对应的 PVC 进行绑定。PVC 的 storageClassName、accessModes 和 resources.requests.storage 必须与 PV 匹配才能成功绑定。PVC 请求的容量必须小于或等于 PV 的容量。
bash
# 创建三个PV
[root@master volumes]# vim pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce # 单节点读写
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /share/pv1
server: 172.25.254.30
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany # 多节点读写
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /share/pv2
server: 172.25.254.30
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv3
spec:
capacity:
storage: 15Gi
volumeMode: Filesystem
accessModes:
- ReadOnlyMany # 多节点只读
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /share/pv3
server: 172.25.254.30
# 应用PV
[root@master volumes]# kubectl apply -f pv.yml
[root@master storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS STORAGECLASS AGE
pv1 5Gi RWO Retain Available nfs 117s
pv2 10Gi RWX Retain Available nfs 29s
pv3 15Gi ROX Retain Available nfs 29s

bash
# 创建对应的PVC
[root@master volumes]# vim pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
storageClassName: nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc2
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc3
spec:
storageClassName: nfs
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 15Gi
# 应用PVC(自动绑定到匹配的PV)
[root@master volumes]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc1 Bound pv1 5Gi RWO nfs 66s
pvc2 Bound pv2 10Gi RWX nfs 66s
pvc3 Bound pv3 15Gi ROX nfs 66s
# 验证PV状态从Available变为Bound
[root@master storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv1 5Gi RWO Retain Bound default/pvc1 nfs 5m
pv2 10Gi RWX Retain Bound default/pvc2 nfs 5m
pv3 15Gi ROX Retain Bound default/pvc3 nfs 5m


步骤三:在 Pod 中使用 PVC
知识点串联: Pod 通过 persistentVolumeClaim.claimName 引用 PVC 来使用持久卷。PVC 必须在与 Pod 相同的命名空间中创建。
bash
# 创建Pod,通过PVC使用持久卷
[root@master volumes]# vim pod.yml
apiVersion: v1
kind: Pod
metadata:
name: timinglee
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: vol1
volumes:
- name: vol1
persistentVolumeClaim:
claimName: pvc1
# 启动Pod并验证
[root@master volumes]# kubectl apply -f pod.yml
pod/timinglee created
[root@master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
timinglee 1/1 Running 0 83s 10.244.2.54 k8s-node2
# 在NFS服务器上写入文件
[root@node3 ~]# echo "PV test data" > /share/pv1/index.html
# 验证Pod可以读取持久卷中的数据
[root@master volumes]# curl 10.244.2.54
PV test data



第七章 StorageClass 动态供给
7.1 知识点:StorageClass 动态供给
StorageClass 提供了一种描述存储类(class)的方法,不同的 class 可能会映射到不同的服务质量等级、备份策略或其他策略。每个 StorageClass 包含以下关键属性:
- Provisioner(存储分配器) :决定使用哪个卷插件分配 PV。可以指定内部分配器(如
kubernetes.io/aws-ebs)或外部分配器(如 NFS Client Provisioner) - Reclaim Policy(回收策略) :通过
reclaimPolicy字段指定创建的 PV 的回收策略(Delete 或 Retain),未指定时默认为 Delete - Volume Binding Mode:控制 PV 的绑定时机(Immediate 或 WaitForFirstConsumer)
NFS Client Provisioner
NFS Client Provisioner 是一个 automatic provisioner,使用 NFS 作为存储后端,自动创建 PV 和对应的 PVC。PV 以 ${namespace}-${pvc name}-${pv name} 的格式在 NFS 服务器上创建子目录,实现多租户隔离。
7.2 实验:StorageClass 动态供给完整流程
步骤一:创建 RBAC 授权
知识点串联: NFS Client Provisioner 需要 ServiceAccount 和相应的 RBAC 权限来操作 Kubernetes 的 PV、PVC 和 StorageClass 资源。

bash
# 创建RBAC配置
[root@master storageclass]# vim rbac.yml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-client-provisioner
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-client-provisioner
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-client-provisioner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-client-provisioner
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
# 应用RBAC配置
[root@master storageclass]# kubectl apply -f rbac.yml
namespace/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created

步骤二:部署 NFS Provisioner 控制器
知识点串联: 部署 NFS Client Provisioner 的 Deployment,通过环境变量指定 NFS 服务器地址和共享路径。Provisioner 会自动监听 PVC 请求并创建对应的 PV。
bash
# 部署NFS外部Provisioner
[root@master storageclass]# vim provisioner.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.25.254.250
- name: NFS_PATH
value: /nfsdata
volumes:
- name: nfs-client-root
nfs:
server: 172.25.254.250
path: /nfsdata
# 应用并验证
[root@master storageclass]# kubectl apply -f provisioner.yml
deployment.apps/nfs-client-provisioner created
[root@master storageclass]# kubectl -n nfs-client-provisioner get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6d4b8c7f9-x2k4l 1/1 Running 0 30s

步骤三:创建 StorageClass
bash
# 创建StorageClass
[root@master storageclass]# vim storageclass.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate
# 应用StorageClass
[root@master storageclass]# kubectl apply -f storageclass.yml
storageclass.storage.k8s.io/nfs-client created

步骤四:动态创建 PVC 验证
知识点串联: 创建 PVC 时指定 storageClassName: nfs-client,Provisioner 会自动创建对应的 PV,无需手动预配置 PV。PVC 绑定后,Pod 即可使用该 PVC 进行数据存储。
bash
# 创建PVC(无需手动创建PV)
[root@master storageclass]# vim pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-claim
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
# 应用PVC
[root@master storageclass]# kubectl apply -f pvc.yml
persistentvolumeclaim/test-claim created
[root@master storageclass]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-7782a006-381a-440a-addb-e9d659b8fe0b 1Gi RWX nfs-client 21m
# 验证PV已自动创建
[root@master storageclass]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pvc-7782a006-381a-440a-addb-e9d659b8fe0b 1Gi RWX Delete Bound default/test-claim nfs-client 21m

bash
# 创建测试Pod验证数据持久化
[root@master storageclass]# vim pod.yml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox
command: ["/bin/sh"]
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: /mnt
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
# 验证Pod运行成功
[root@master storageclass]# kubectl apply -f pod.yml
pod/test-pod created
[root@master storageclass]# kubectl logs test-pod
# 无错误输出表示成功
步骤五:设定默认 StorageClass
知识点串联: 将 StorageClass 标记为默认后,创建 PVC 时可以不指定 storageClassName,系统会自动使用默认的 StorageClass 进行动态供给。这在大多数场景都使用同一种存储类型时非常方便。
bash
# 将nfs-client设为默认StorageClass
[root@master storageclass]# kubectl edit storageclass nfs-client
# 在annotations中添加: storageclass.kubernetes.io/is-default-class: "true"
# 验证(显示default标记)
[root@master storageclass]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 2d
# 不指定storageClassName的PVC会自动使用默认StorageClass
[root@master storageclass]# vim pvc-default.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
# 不指定storageClassName,自动使用nfs-client
# 验证自动绑定
[root@master storageclass]# kubectl apply -f pvc-default.yml
persistentvolumeclaim/pvc-default created
[root@master storageclass]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-default Bound pvc-xxx-xxx-xxx-xxx 5Gi RWX nfs-client 5s


第八章 StatefulSet 与动态卷整合
8.1 知识点:StatefulSet 有状态应用管理
StatefulSet 是 Kubernetes 中用于管理有状态应用的控制器,解决了 Deployment 无法有效管理有状态应用的问题。其核心特性:
拓扑状态
应用实例必须按照某种顺序启动。新创建的 Pod 必须和原来 Pod 的网络标识一致。StatefulSet 为每个 Pod 分配编号(从 0 开始),Pod 名称格式为 $(statefulset名称)-$(序号)。
存储状态
应用的多个实例分别绑定不同的存储数据。通过 volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC,实现每个实例拥有独立的持久化存储。
固定网络标识
Pod 被删除后重建,重建 Pod 的网络标识不会改变。StatefulSet 为每个 Pod 提供固定且唯一的 DNS 记录,格式为 $(pod-name).$(service-name).$(namespace).svc.cluster.local。
8.2 实验:StatefulSet + Headless Service + 动态 PV 完整实战
步骤一:创建 Headless Service(无头服务)
知识点串联: Headless Service(clusterIP: None)不会分配 Cluster IP,而是直接返回 Pod 的 IP 地址。StatefulSet 依赖 Headless Service 来为每个 Pod 生成 DNS 记录,实现稳定的网络标识。
bash
# 创建Headless Service
[root@master statefulset]# vim headless.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 无头服务,不分配Cluster IP
selector:
app: nginx
# 应用Headless Service
[root@master statefulset]# kubectl apply -f headless.yml
service/nginx-svc created
步骤二:创建 StatefulSet
知识点串联: StatefulSet 通过 volumeClaimTemplates 为每个 Pod 自动创建独立的 PVC。PVC 名称格式为 $(volumeClaimTemplateName)-$(pod-name),自动使用指定的 StorageClass 进行动态供给。
bash
# 创建StatefulSet,使用volumeClaimTemplates自动创建PVC
[root@master statefulset]# vim statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx-svc
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
# 应用StatefulSet
[root@master statefulset]# kubectl apply -f statefulset.yml
statefulset.apps/web created
# 验证Pod和PVC的创建(按顺序创建:web-0, web-1, web-2)
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m26s
web-1 1/1 Running 0 3m22s
web-2 1/1 Running 0 3m18s
# 验证PVC自动创建
[root@master statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-xxx-xxx-xxx-xxx 1Gi RWO nfs-client 3m
www-web-1 Bound pvc-yyy-yyy-yyy-yyy 1Gi RWO nfs-client 3m
www-web-2 Bound pvc-zzz-zzz-zzz-zzz 1Gi RWO nfs-client 3m
# 在NFS服务器上查看自动创建的目录
[root@reg nfsdata]# ls /nfsdata/
default-www-web-0-xxx default-www-web-1-yyy default-www-web-2-zzz
步骤三:有序扩缩容验证
知识点串联: StatefulSet 支持有序扩缩容。缩容时从最大编号开始删除,扩容时从最小编号开始创建。每个 Pod 都有独立的 PVC,扩缩容不影响其他 Pod 的数据。
bash
# 扩缩到2个副本
[root@master statefulset]# kubectl scale statefulset web --replicas 2
statefulset.apps/web scaled
# 验证web-2被删除
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10m
web-1 1/1 Running 0 10m
# 扩缩到3个副本(web-2重新创建,保留之前的PVC)
[root@master statefulset]# kubectl scale statefulset web --replicas 3
statefulset.apps/web scaled
# 验证web-2重新创建并绑定到之前的PVC
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 12m
web-1 1/1 Running 0 12m
web-2 1/1 Running 0 15s
# 也可以通过编辑配置改变副本数
[root@master statefulset]# kubectl edit statefulsets.apps web
# 将 replicas: 3 改为 replicas: 5
步骤四:验证数据持久化和网络稳定性
知识点串联: 为每个 Pod 写入不同的数据,然后通过 Headless Service 的 DNS 名称访问每个 Pod,验证数据持久化和网络标识的稳定性。即使删除 StatefulSet 后重建,Pod 名称和 DNS 记录也不会改变。
bash
# 为每个Pod写入不同的数据
[root@master statefulset]# kubectl exec -it web-0 -c nginx -- /bin/sh -c "echo 'web-0' > /usr/share/nginx/html/index.html"
[root@master statefulset]# kubectl exec -it web-1 -c nginx -- /bin/sh -c "echo 'web-1' > /usr/share/nginx/html/index.html"
[root@master statefulset]# kubectl exec -it web-2 -c nginx -- /bin/sh -c "echo 'web-2' > /usr/share/nginx/html/index.html"
# 通过Headless Service的DNS名称访问每个Pod
[root@master statefulset]# kubectl run testpod --image busybox --restart=Never -it --rm -- /bin/sh
/ # curl web-0.nginx-svc
web-0
/ # curl web-1.nginx-svc
web-1
/ # curl web-2.nginx-svc
web-2
# 删除StatefulSet后重建,数据依然保留
[root@master statefulset]# kubectl delete -f statefulset.yml
statefulset.apps "web" deleted
# 重新创建StatefulSet
[root@master statefulset]# kubectl apply -f statefulset.yml
statefulset.apps/web created
# 重建后通过DNS仍然可以访问,且数据不变
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10s
web-1 1/1 Running 0 8s
web-2 1/1 Running 0 6s
# 验证数据保留
kubectl run testpod --image busybox --restart=Never -it --rm -- /bin/sh
/ # curl web-0.nginx-svc
web-0 # 数据依然保留
/ # curl web-1.nginx-svc
web-1 # 数据依然保留
/ # curl web-2.nginx-svc
web-2 # 数据依然保留
关键设计说明:
- StatefulSet 的 Pod 名称格式为
$(name)-$(ordinal),从 0 开始编号volumeClaimTemplates为每个 Pod 自动创建独立的 PVC,格式为$(templateName)-$(podName)- Headless Service 提供稳定的 DNS 记录:
$(podName).$(serviceName).$(namespace).svc.cluster.local- 删除 StatefulSet 后重建,Pod 名称、PVC 和 DNS 记录均保持不变,实现有状态应用的可靠管理
附录:K8s 存储方案速查表
存储方案对比
| 存储方案 | 适用场景 | 数据持久性 | 跨节点共享 | 性能 | 推荐度 |
|---|---|---|---|---|---|
| emptyDir | 临时缓存、容器间共享 | 否(Pod删除即失) | 是(同Pod内) | 高(内存) | 临时数据 |
| hostPath | 调试、日志收集 | 是(宿主机) | 否(绑定节点) | 高 | 不推荐生产 |
| NFS | 多Pod共享文件 | 是 | 是 | 中 | 中小型共享存储 |
| PV/PVC(静态) | 需要固定存储资源 | 是 | 取决于后端 | 高 | 固定资源场景 |
| StorageClass(动态) | 需要自动供给存储 | 是 | 取决于后端 | 高 | 云原生推荐 |
| StatefulSet | 有状态应用(DB/中间件) | 是 | 独立存储 | 高 | 有状态应用首选 |
典型场景推荐
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| Web 应用静态文件 | NFS + PV/PVC | 多副本共享、数据持久化 |
| 数据库(MySQL/PostgreSQL) | StatefulSet + StorageClass | 独立存储、稳定网络标识 |
| 日志收集 | hostPath | 直接访问宿主机日志目录 |
| 缓存/临时数据 | emptyDir(内存) | 高性能、无需持久化 |
| 多应用共享配置 | ConfigMap | 与镜像解耦、热更新 |
| 数据库密码管理 | Secret(环境变量) | 安全注入、避免明文 |
| 私有镜像拉取 | Secret(dockerconfigjson) | 自动认证、无需手动配置 |
生产环境最佳实践
安全
- 启用 etcd 静态加密存储 Secret 数据
- 使用 RBAC 限制 Secret 的访问权限
- 避免在日志和事件中包含敏感信息
性能
- 对性能敏感的存储使用本地磁盘(local PV)
- NFS 存储避免小文件密集场景
- 合理设置
sizeLimit防止内存卷耗尽节点内存
高可用
- 使用多副本 StatefulSet 保证服务可用性
- 选择支持多节点访问的存储后端(RWX)
- 定期备份持久化数据
监控
- 监控 PV/PVC 的使用率和容量
- 设置存储告警阈值
- 监控 NFS 服务器的连接数和吞吐量