头秃修炼手册:StorageClass+StatefulSet+ETCD 全套渡劫笔记
本篇博客涵盖:Kubernetes动态卷供应(Local‑Path / NFS‑Subdir‑External‑Provisioner)、StatefulSet有状态应用管理、ETCD分布式键值存储部署、集群增删节点、备份恢复。适合云原生实验复习、面试梳理。
目录
- 动态卷供应 StorageClass & Provisioner
- Local‑Path‑Provisioner 本地动态存储
- NFS‑Subdir‑External‑Provisioner NFS动态卷
- StatefulSet 有状态控制器
- StatefulSet 实验:Nginx、ETCD、Redis、Cassandra、MySQL
- ETCD分布式存储系统
- ETCD单节点部署
- ETCD集群部署、节点增删
- etcdctl命令、KV管理、用户角色
- ETCD 备份与恢复
1. 动态卷供应 StorageClass & Provisioner
什么是静态卷
传统方式,PV需要管理员手动预先创建,用户创建PVC再和PV绑定。缺点:管理员需要提前预估、手动维护大量PV。
动态卷供应
- 管理员创建
StorageClass存储类(存储套餐模板),指定制备器provisioner; - 用户创建PVC,指定
storageClassName; - k8s调用对应provisioner自动创建PV,完成PVC与PV绑定,用户直接使用PVC。
StorageClass 核心字段
| 字段 | 说明 |
|---|---|
provisioner |
制备器,造PV的工具;云厂商内置,本地/NFS/Ceph需要第三方 |
reclaimPolicy |
回收策略:Delete删除PVC自动删后端存储;Retain保留数据 |
volumeBindingMode |
Immediate:创建PVC立刻生成PV;WaitForFirstConsumer:等Pod消费才创建PV,适合本地存储 |
allowVolumeExpansion |
是否支持PVC扩容 |
parameters |
给provisioner传递自定义参数 |
部分存储是否内置制备器
存储类型 是否内置provisioner AWS EBS、AzureDisk ✅内置 NFS、Local、Ceph RBD、CephFS、iSCSI、Glusterfs ❌第三方制备器
volumeBindingMode 两个模式区别
Immediate:PVC创建即创建PV,适合网络存储NFS、云盘;WaitForFirstConsumer:等待第一个使用PVC的Pod被调度之后,才创建PV;用于local‑path本地存储,PV绑定Pod所在节点。
2. Local‑Path‑Provisioner 本地动态存储
rancher开源本地存储动态制备器,使用节点本地磁盘实现动态PV。 ⚠数据存在节点本地磁盘;节点故障数据丢失,适合测试、缓存,不适合生产高可用业务。
部署
wget [https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml](https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml)
kubectl apply -f local-path-storage.yaml
# 查看StorageClass
kubectl get sc local-path
# 查看pod
kubectl get all -n local-path-storage
资源清单包含资源:
- Namespace
local-path-storage - ServiceAccount + RBAC权限
- Deployment运行provisioner主程序
- ConfigMap
local-path-config:定义本地存储默认路径/opt/local-path-provisioner - StorageClass
local‑path,volumeBindingMode: WaitForFirstConsumer
验证实验
创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
storageClassName: local-path
PVC刚创建状态
Pending,必须等Pod使用才会变成Bound。
创建Deployment使用PVC
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- image: hub.laoma.cloud/library/nginx
name: nginx
volumeMounts:
- name: webapp-data
mountPath: /usr/share/nginx/html
volumes:
- name: webapp-data
persistentVolumeClaim:
claimName: local-path-pvc
现象:
- Pod调度到某一台worker节点;provisioner在该节点
/opt/local-path-provisioner生成子目录作为PV;- PV自动注入
nodeAffinity节点亲和;同一个PVC的所有Pod,只能跑在这一台节点;- 删除PVC,本地目录自动删除(reclaimPolicy:Delete)。
清理
kubectl delete deployment webapp
kubectl delete pvc local-path-pvc
kubectl delete -f local-path-storage.yaml
3. NFS‑Subdir‑External‑Provisioner NFS动态卷
NFS本身k8s没有内置provisioner;使用
nfs‑subdir‑external‑provisioner,在同一个NFS共享下自动创建子目录作为各个PV。 NFS是网络存储,所有节点都可以挂载同一个PV,Pod可以分散不同节点。
3.1 部署NFS服务端 master30
apt install -y nfs-kernel-server
mkdir -m 777 /shares
cat << EOF > /etc/exports
/shares *(rw,sync,no_root_squash,no_all_squash,insecure)
EOF
systemctl restart nfs-server.service
# 所有worker节点安装nfs客户端
apt install -y nfs-common
3.2 部署nfs‑client‑provisioner
-
创建命名空间
kubectl create ns kube-storage
kubectl config set-context --current --namespace kube-storage
nfs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-runner
rules:
- 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"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: kube-storage
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-storage
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
volumes:
- name: nfs-client-root
nfs:
server: 10.1.8.30
path: /shares
containers:
- name: nfs-client-provisioner
image: hub.laoma.cloud/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 10.1.8.30
- name: NFS_PATH
value: /shares
nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: fuseim.pri/ifs
parameters:
onDelete: retain
archiveOnDelete: "false"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
部署
kubectl apply -f nfs-rbac.yaml
kubectl apply -f nfs-provisioner.yaml
kubectl apply -f nfs-storageclass.yaml
kubectl get sc
3.3 验证NFS动态卷
pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: webclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: "nfs-storage"
Immediate模式,创建PVC立刻Bound,NFS服务器
/shares自动生成子目录。
deployment使用PVC
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- image: hub.laoma.cloud/library/nginx
name: nginx
volumeMounts:
- name: webapp-data
mountPath: /usr/share/nginx/html
volumes:
- name: webapp-data
persistentVolumeClaim:
claimName: webclaim
✔NFS特点:同一个PVC,多个Pod可以调度到不同节点同时挂载;底层数据存放在NFS服务器共享目录。
4. StatefulSet 有状态控制器
无状态 vs 有状态应用
| 维度 | Deployment(无状态) | StatefulSet(有状态) |
|---|---|---|
| 实例身份 | 所有Pod完全等价,随机名称 | 固定序号:sts‑0、sts‑1、sts‑2,身份永久不变 |
| 网络 | 普通Service,共享ClusterIP | Headless Service(clusterIP:None) ,每个Pod拥有固定DNS域名 podname.svcname.ns.svc.cluster.local |
| 扩缩顺序 | 并发创建删除,无顺序 | 扩容从0→N依次启动,必须上一个Ready再创建下一个;缩容从最大序号往0删除 |
| 存储 | PVC手动编写,共享卷 | volumeClaimTemplates PVC模板;每个Pod自动生成独立PVC;删除Pod,PVC保留,重建Pod复用旧PVC,数据不丢失 |
| 适用场景 | web、api微服务 | mysql、redis、etcd、zookeeper、kafka分布式有状态组件 |
StatefulSet核心组件
- Headless Service :
clusterIP: None,不负载均衡,仅DNS域名解析;StatefulSet必须配置。 - volumeClaimTemplates PVC模板:为每一个Pod自动生成独立PVC,Pod删除PVC保留。
- serviceName字段:绑定Headless Service名称。
重要:修改StatefulSet的
volumeClaimTemplates不会更新已经存在的PVC,需要手动删除PVC才会重建。
5. StatefulSet 实验:Nginx、ETCD、Redis、Cassandra、MySQL
5.1 StatefulSet Nginx示例
1. 创建Headless Service
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: statefulset
spec:
selector:
app: nginx
clusterIP: None
ports:
- port: 80
targetPort: 80
name: nginx-port
2. StatefulSet + PVC模板
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
namespace: statefulset
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
minReadySeconds: 10
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: hub.laoma.cloud/library/nginx:latest
ports:
- containerPort: 80
name: nginx
volumeMounts:
- name: nginx-data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: nginx-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-storage"
resources:
requests:
storage: 10Gi
现象:
- Pod顺序启动 nginx‑0 → nginx‑1 → nginx‑2;
- 每个Pod自动生成独立PVC:
nginx‑data‑nginx‑0; - 删除Pod,StatefulSet重建同名Pod,复用原有PVC,数据保留。
访问Pod域名:
nginx‑0.nginx.statefulset.svc.cluster.local
如果需要统一入口访问所有Pod,额外再创建一个普通ClusterIP Service。
5.2 StatefulSet部署ETCD集群
使用Headless Service域名完成etcd节点之间peer通信。 关键点:环境变量利用metadata.name获取pod名称,拼接headless域名完成集群通信。
5.3 StatefulSet部署Redis主从+哨兵
- redis‑0作为master;redis‑1、redis‑2作为replica;sentinel哨兵实现自动故障切换。
- Pod重建后PVC保留,持久化数据。
5.4 StatefulSet Cassandra / MySQL
MySQL示例注意:StatefulSet不会自动配置主从复制,复制逻辑需要自己通过脚本/init‑container实现。
⚠生产注意:密码等敏感配置必须使用Secret,禁止yaml明文写密码。
6. ETCD分布式存储系统
etcd:Go语言开发,基于Raft一致性算法,分布式强一致性键值存储;CNCF毕业项目。 k8s全部对象数据存储在etcd。
ETCD特点
- 强一致性;
- 支持https安全通信;
- 提供watch监听机制;
- 简单http/gRPC API;
- 性能优秀。
典型应用场景
- 服务注册与发现
- 配置中心发布订阅watch
- 分布式协调、选主
- 分布式锁
- Kubernetes存储集群所有资源对象。
6.1 ETCD单节点部署
apt install -y etcd-server etcd-client
配置文件 /etc/default/etcd
ETCD_NAME="default"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_CLIENT_URLS="[http://localhost:2379](http://localhost:2379),[http://10.1.8.10:2379](http://10.1.8.10:2379)"
ETCD_ADVERTISE_CLIENT_URLS="[http://localhost:2379](http://localhost:2379),[http://10.1.8.10:2379](http://10.1.8.10:2379)"
systemctl restart etcd
etcdctl member list
简单KV操作
etcdctl put /demo/key hello
etcdctl get /demo/key
etcdctl del /demo/key
etcdctl watch /demo/key
6.2 ETCD多节点集群部署
关键参数 /etc/default/etcd
| 参数 | 说明 |
|---|---|
ETCD_NAME |
本节点名称,集群唯一 |
ETCD_DATA_DIR |
数据目录 |
ETCD_LISTEN_PEER_URLS |
本节点监听其他节点peer通信地址(2380) |
ETCD_INITIAL_ADVERTISE_PEER_URLS |
广播给集群其他节点的peer通信地址 |
ETCD_LISTEN_CLIENT_URLS |
本节点监听客户端访问地址(2379) |
ETCD_ADVERTISE_CLIENT_URLS |
广播给客户端访问本节点client地址 |
ETCD_INITIAL_CLUSTER |
集群全部节点peer地址清单 |
ETCD_INITIAL_CLUSTER_TOKEN |
集群唯一token,所有节点必须一致,区分不同集群 |
ETCD_INITIAL_CLUSTER_STATE |
new全新集群全部节点一起初始化;existing加入已经存在的集群 |
重点实验流程:
- 全新集群:全部节点配置
ETCD_INITIAL_CLUSTER_STATE="new",同时启动。 - 向已经存在集群添加新节点流程:
- 在集群任意旧节点执行
etcdctl member add 新节点名 --peer‑urls=http://ip:2380 - 输出的环境变量复制给新节点配置;
- 新节点配置
ETCD_INITIAL_CLUSTER_STATE="existing";新节点数据目录必须为空; - 启动新节点。
- 在集群任意旧节点执行
- 删除节点:
etcdctl member remove <member‑id>;删除后建议修改剩余节点配置,去掉被删除节点,重启服务。
坑:删除member只是集群元数据移除节点;不会清理被删除节点本地磁盘数据目录。
6.3 etcdctl工具使用
默认使用API v3;ETCDCTL_API=3。
member集群成员管理
etcdctl member list
etcdctl member add
etcdctl member remove
etcdctl member update
KV操作
# 写入
etcdctl put /users/user1/name zhangsan
# 查询
etcdctl get /users/user1/name
# --prefix前缀查询,--keys-only只打印key
etcdctl get /users --prefix --keys-only
# 删除
etcdctl del /users/user1/name
# watch监听key变化
etcdctl watch /users/user1/name
脚本小工具
etcdls模拟目录ls查看etcd的key,方便学习。
用户、角色权限管理
- user:账号
- role:角色,授予key读写权限;用户绑定角色实现rbac权限控制。
6.4 ETCD备份恢复
两种备份方式
- 物理备份:直接拷贝数据目录,停机备份;适合整体全量备份;恢复全部节点替换数据目录。
- 逻辑快照备份(snapshot):在线备份,不需要停机。
快照备份单节点:
export ETCDCTL_API=3
etcdctl snapshot save snapshot.db
快照恢复:
# 停止etcd,清空旧数据目录
systemctl stop etcd
rm -fr /var/lib/etcd/default
etcdctl snapshot restore snapshot.db --data-dir=/var/lib/etcd/default
chown -R etcd:etcd /var/lib/etcd/default
systemctl start etcd
多节点集群快照恢复:所有节点全部使用同一个快照文件恢复。
Kubernetes内部ETCD备份
k8s静态pod部署etcd,需要指定证书、ca、endpoints。
export ETCDCTL_API=3
etcdctl \
--key=/etc/kubernetes/pki/etcd/server.key \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--endpoints=[https://127.0.0.1:2379](https://127.0.0.1:2379) \
snapshot save snapshot.db
k8s etcd所有资源存放在
/registry前缀下面;etcd v3存储protobuf二进制,直接get拿到是二进制,需要工具解码查看。
知识总结面试速记
-
StorageClass
:动态存储模板,provisioner制备器自动创建PV;
WaitForFirstConsumer:local‑path本地存储,等Pod调度完成创建PV;PV绑定节点亲和。Immediate:NFS网络存储,PVC创建立刻生成PV。
-
Local‑Path:本地磁盘,同一个RWO PVC所有Pod只能运行在同一个节点;节点故障数据丢失。
-
NFS‑subdir‑provisioner:第三方制备器;NFS网络存储,Pod可跨节点挂载同一个PVC。
-
StatefulSet:用于有状态应用;依赖Headless Service;有序扩缩容;
volumeClaimTemplates每个Pod独立PVC,删除Pod保留PVC。 -
ETCD:Raft分布式强一致性KV;k8s后端存储。
new:全新集群初始化;existing加入现存集群。- 添加节点必须先
etcdctl member add;新节点数据目录必须清空。 - 备份:snapshot快照在线备份;恢复集群所有节点使用同一个快照。