为什么要持久化储存
因为 Pod 是“临时工”,数据不能跟着它一起消失。
Pod 一重启/删除/漂移,容器里的数据就没了 容器文件系统随 Pod 生命周期走,Pod 挂了数据就丢。
但很多数据必须活下来 比如 MySQL 数据、用户上传的文件、日志、配置。
多个 Pod 可能还要共享同一份数据 比如多个副本同时读写同一个存储。
所以要把数据放在 Pod 之外 用 PV/PVC/StorageClass 把外部存储挂进来,Pod 怎么换,数据都还在。
一句话:Pod 可以死,数据不能死,所以 K8s 要做持久化。
储存卷 emptyDir
emptyDir是一种共享pod生命周期的"临时存储"类型(pod被删除的话,临时目录里面数据也会被删除)。
nginx的Deployment内容 (前置测试Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: default
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80操作
# 创建deploy
kubectl apply -f deploy.yaml
kubectl get pod -o wide
# 进入pod
kubectl exec -it <pod-name> -- bash
# 改写index.html文件的内容
echo 'laoli' > /usr/share/nginx/html/index.html
# 退出后访问pod, 测试改写效果
curl <pod-ip>
# 重启pod
kubectl rollout restart deploy deploy-nginx
# 再次访问pod, 测试改写效果是否保留查看存储卷信息
kubectl explain pod.spec.volumesemptyDir 使用格式
spec:
volumes:
- name: vol-emptydir # 存储卷名字,必须
emptyDir: # emptyDir类型的存储卷,必须
sizeLimit: 200Mi # 存储卷的容量限制,非必须
containers:
volumeMounts:
- name: vol-emptydir # 引用的存储卷名字
mountPath: /usr/share/nginx/html # 容器挂载路径实验实际操作
# 创建 emptyDir.yaml
vim emptyDir.yaml
# 填写以下内容
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: default
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: vol-emptydir
emptyDir:
sizeLimit: 200Mi
containers:
- name: container-alpine
image: alpine:3.7
command: ["bin/sh", "-c", "sleep 3600"]
volumeMounts:
- name: vol-emptydir
mountPath: /home
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80
volumeMounts:
- name: vol-emptydir
mountPath: /usr/share/nginx/html
# 创建deploy并查看pod调度的节点信息
kubectl apply -f emptyDir.yaml
kubectl get pod -o wide
# 查看pod的UID(UID是Pod实例的唯一标识符)
kubectl get pod <pod-name> -o yaml
# 在pod调度的节点上查看
ls /var/lib/kubelet/pods/<Pod-UID>/volumes/kubernetes.io~empty-dir/<存储卷名字>
# 进入nginx容器,查看挂载路径
kubectl exec -it <pod-name> -c container-nginx -- ls /usr/share/nginx/html
# 进入alpine容器,查看挂载路径
kubectl exec -it <pod-name> -c container-alpine -- sh
ls /home
# alpine容器的挂载路径下新增index.html
echo 'laoli' > /home/index.html
# 退出容器查看nginx首页
curl <pod-ip>
# 查看pod调度的节点存储卷
ls /var/lib/kubelet/pods/<Pod-UID>/volumes/kubernetes.io~empty-dir/<存储卷名字>
# 删除pod
kubectl delete -f emptyDir.yaml
或者
kubectl delete pod <pod-name>
# 查看pod调度的节点存储卷
ls /var/lib/kubelet/pods/<Pod-UID>储存卷 hostPath
hostPath这种类型的存储卷,允许将节点的文件系统上的文件或目录挂载到Pod中。与上面的emptyDir其实存在相似之处,都属于"local级别"的数据持久化方案。两者不同的地方在于;
hostPath类型即使Pod被删除,数据也持久地保存在节点上
hostPath类型可挂载单个文件或整个目录,而emptyDir只能挂载目录
容器时间跟宿主机时间不一致可以读取系统配置实现时间一致
文件 hostPath.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: default
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: vol-hostpath
hostPath:
path: /root/nginx
type: DirectoryOrCreate
containers:
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80
volumeMounts:
- name: vol-hostpath
mountPath: /usr/share/nginx/html实验操作
# 创建hostPath.yaml
kubectl apply -f hostPath.yaml
# 查看创建的pod
kubectl get pod -o wide
# 查看pod挂载路径
kubectl exec -it <pod-name> -- ls /usr/share/nginx/html
# 节点上创建index.html
echo 'laoli' > /root/nginx/index.html
# 查看pod挂载路径下的index.html
kubectl exec -it <pod-name> -- cat /usr/share/nginx/html/index.html
# 进入pod修改index.html文件内容
kubectl exec -it <pod-name> -- sh
echo 'hello' > /usr/share/nginx/html/index.html
# 返回节点上查看index.html的内容
cat /root/nginx/index.htmlhostPath 使用格式
spec:
volumes:
- name: vol-hostpath
hostPath:
# 挂载卷的类型:包含""(默认值)、Directory、File、DirectoryOrCreate(权限设置为755)、FileOrCreate(权限设置为644)等。
type: DirectoryOrCreate
# hostPath卷的节点路径
path: /root/nginx
containers:
volumeMounts:
# 引用的hostPath卷的名字
- name: vol-hostpath
# 容器中的挂载路径
mountPath: /usr/share/nginx/html存储卷 nfs
NFS即"网络文件系统",全称是Network File System。它将文件系统挂载到远程服务器上,客户端和服务器之间通过 RPC(Remote Procedure Call)协议实现访问。NFS独立于 K8s 集群,即使整个集群崩溃数据也不受影响。NFS基于TCP/IP协议且独立于操作系统,可以跨平台使用,这使得不同系统的计算机可以共享文件。
小结:NFS简单易用,支持跨平台,数据集中存储,这些都是它的优点。不足之处体现在
高并发场景下性能不佳
未提供数据备份需要额外配置
pod可以任意的向NFS写数据,存储资源的使用不可控,可能导致NFS服务端崩溃
操作服务端 (安装单独的NFS服务器)
# 安装rpcbind和nfs-utils(基于Ubuntu)
sudo apt update
sudo apt install nfs-kernel-server -y
# 创建nfs服务器的共享数据目录
sudo mkdir -p /root/nfs/data
# 编辑exports配置文件,参数含义:
0. *代表所有客户端地址,也可以指定,例如:192.168.1.50
1. insecure-使用1024以上的端口,secure则使用1024以下的端口
2. rw-可读写,ro只读
3. sync-数据同步写入内存和硬盘(效率低不丢数据),async数据先缓存到内存,待硬盘空闲再写入硬盘
4. no_root_squash-客户端用root访问共享文件夹时不压缩root权限。root_squash则将root访问转为匿名访问。all_squash则将所有用户转为匿名。
5. no_subtree_check-关闭子树检查。默认的subtree_check会验证客户端请求的文件确实位于导出的目录之下,防止越界访问;关闭后性能更好,且目录内文件被重命名/移动时不易出现stale file handle错误,是NFSv3后期及NFSv4推荐的默认选项。
echo "/root/nfs/data *(insecure,rw,sync,no_root_squash,no_subtree_check)" | sudo tee /etc/exports
# 启动服务及开机自启
sudo systemctl start rpcbind
sudo systemctl start nfs-kernel-server
sudo systemctl enable rpcbind
sudo systemctl enable nfs-kernel-server
# 重新载入
sudo exportfs -ra操作客户端 (安装工作节点上)
注意:客户端上的挂载目录不允许直接删除,除非卸载(解除挂载)
# 安装客户端(安装后只需启动rpcbind服务,不需要启动nfs服务,所有节点执行)
sudo apt update
sudo apt install rpcbind nfs-common -y
sudo systemctl start rpcbind && sudo systemctl enable rpcbind
# 查看服务器的共享资源列表
showmount -e <nfs服务器ip>
# 客户端挂载到nfs服务器
sudo mkdir -p /root/nfs
sudo mount -t nfs <nfs服务器ip>:/root/nfs/data /root/nfs
# 查看挂载点
mount | grep nfs # 或者 df -t nfs4
# 客户端上创建文件hello.txt
echo 'laoli' | sudo tee /root/nfs/hello.txt
# nfs服务器上查看hello
cat /root/nfs/data/hello.txt
# 卸载(-f 表示强制卸载)
sudo umount -f /root/nfs
# 查看挂载点(确认已卸载,应无输出)
df -t nfs4
# 删除客户端上的挂载目录
sudo rm -rf /root/nfsNFS使用格式
spec:
volumes:
# 存储卷的名字
- name: vol-nfs
nfs:
# nfs-server的IP地址
server: 192.168.7.70
# nfs-server的共享目录路径
path: /root/nfs/data
containers:
volumeMounts:
# 引用的nfs卷的名字
- name: vol-nfs
# 容器中的挂载路径
mountPath: /usr/share/nginx/html文件 nfs.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: vol-nfs
nfs:
path: /root/nfs/data
server: 192.168.7.70
containers:
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80
volumeMounts:
- name: vol-nfs
mountPath: /usr/share/nginx/html实验操作
# 创建
kubectl apply -f nfs.yaml
# 查看pod
kubectl get pod -o wide
# 查看节点的nfs挂载点
df -t nfs4
# nfs服务器的共享目录创建index.html
echo 'laoli' | sudo tee /root/nfs/data/index.html
# 查看nginx首页
curl <pod-ip>
# 删除然后返回节点再次查看挂载点
kubectl delete -f nfs.yaml
df -t nfs4PV和PVC
总起概括:PV、PVC都是K8S用来做存储管理的资源对象,它们让存储资源的使用变得可控,从而保障系统。
pv操作
使用格式
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
labels:
pv: nfs-pv1
spec:
# 1.存储能力
capacity:
# 存储大小(Mi、Gi)
storage: 200Mi
# 2.访问模式(HostPath仅支持ReadWriteOnce,NFS除ReadWriteOncePod外的都支持)
# https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
accessModes:
# RWO访问模式。只允许被单个节点读写挂载
- ReadWriteOnce
# RWOP访问模式。只允许被单个Pod读写挂载
- ReadWriteOncePod
# ROX访问模式。允许被多个节点只读挂载
- ReadOnlyMany
# RWX访问模式。允许被多个节点读写挂载
- ReadWriteMany
# 3.回收策略
# Retain 保留。删除PVC不会同步删除PV,等管理员手动去处理PV里的数据。NFS、HostPath都支持该项
# Delete 删除。删除PVC同步删除PV。NFS、HostPath都不支持该项
# Recycle 回收。删除PVC同步删除PV且重回可用状态。K8s v1.9后该回收策略标记为过时
persistentVolumeReclaimPolicy: Retain
# nfs服务器设置
nfs:
path: /root/nfs/data/pv1 # 确保/root/nfs/data/pv1事先存在
server: 192.168.7.70文件pv.yaml (文件内容是两个容器)
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
labels:
pv: nfs-pv1
spec:
capacity:
storage: 200Mi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /root/nfs/data/pv1
server: 192.168.7.70
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
labels:
pv: nfs-pv2
spec:
capacity:
storage: 500Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /root/nfs/data/pv2
server: 192.168.7.70操作
# nfs-server上创建相应共享目录
mkdir /root/nfs/data/{pv1,pv2} -p
# 创建pv
kubectl apply -f pv.yaml
# 查看pv
kubectl get pvpvc操作
PVC 即Persistent Volume Claim,中文叫做"持久化卷声明"。Pod通过 PVC向PV 申请磁盘空间。如果 PVC一直无法寻找到匹配的Py对象,则 PVC进入"Pending"状态。 值得注意的是:PVC支持命名空间,Pod在使用 PVC 时必须与 PVC在同一个namespace 中。
文件pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: default
labels:
pv: nfs-pvc
spec:
resources:
requests:
storage: 200Mi # 小于等于PV对象的容量
accessModes:
- ReadOnlyMany # 匹配PV对象的访问模式
selector:
matchLabels:
pv: nfs-pv1 # 匹配PV对象的标签操作
# 创建pvc
kubectl apply -f pvc.yaml
# 查看pv和pvc
kubectl get pv,pvc
# 在nfs-server上创建文件
echo 'I am pv2' > /root/nfs/data/pv1/index.html
cat /root/nfs/data/pv1/index.html
# 删除pvc(pv对象进入Released状态)
kubectl delete pvc nfs-pvc
# 重新创建pvc(无法与Released状态的pv对象绑定)
kubectl apply -f pvc.yaml
# 删除pv
kubectl delete pv nfs-pv1
# 重新创建pv
kubectl apply -f pv.yaml创建pod 使用 PVC 格式
spec:
volumes:
- name: vol-pvc
persistentVolumeClaim:
claimName: nfs-pvc # 引用名为 nfs-pvc 的 PVC
containers:
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80
volumeMounts:
- name: vol-pvc # 和上面 volumes 的 name 对应
mountPath: /usr/share/nginx/html # 把 PVC 挂到 nginx 的网页根目录文件 deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeploy
namespace: default
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
volumes:
- name: vol-pvc
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: container-nginx
image: nginx:1.26.2
ports:
- containerPort: 80
volumeMounts:
- name: vol-pvc
mountPath: /usr/share/nginx/html验证
# 创建pod
kubectl apply -f deploy.yaml
# 查看pod
kubectl get pod -o wide
# 访问pod
curl <pod-ip>
# 查看节点上nfs挂载信息
df -t nfs4
# 注意:卷访问模式不会在存储已经被挂载的情况下为其实施写保护!
# 进入pod修改index.html内容
kubectl exec -it <pod-ip> -- sh
echo 'hello' > /usr/share/nginx/html/index.html
# 再次测试访问pod,并查看nfs-server共享目录下的文件
cat /root/nfs/data/pv1/index.htmlStorageClass
K8s的存储资源有两种供应模式:静态模式和动态模式。静态模式下,管理员预先创建许多各种各样的PV,等待PVC申请使用。
动态模式下,管理员无须预先创建PV,而是通过 StorageClass 自动完成PV的创建以及与PVC的绑定。
部署NFS服务(独立的NFS服务器)测试下可以用K8s节点
# 安装NFS服务
sudo apt update && sudo apt install -y nfs-kernel-server
# 若为CentOS/RHEL
# sudo yum install -y nfs-utils
# 创建共享目录
sudo mkdir -p /data/nfs-share
sudo chmod 777 /data/nfs-share
sudo chown nobody:nogroup /data/nfs-share 2>/dev/null || sudo chown nobody:nobody /data/nfs-share
# 配置NFS共享
sudo vim /etc/exports
# 添加如下内容(测试环境允许所有IP)
/data/nfs-share *(rw,sync,no_subtree_check,no_root_squash)
# 生效exports配置
sudo exportfs -r
# 重启NFS服务
sudo systemctl restart nfs-kernel-server # Ubuntu/Debian
# sudo systemctl restart nfs-server # CentOS/RHEL
# 确认共享生效
showmount -e localhost
# 输出应包含:/data/nfs-share *K8s节点安装NFS客户端
所有K8s节点(包括控制平面和工作节点)必须安装NFS客户端,否则无法挂载NFS共享:
# Ubuntu/Debian
sudo apt install -y nfs-common
# CentOS/RHEL
# sudo yum install -y nfs-utils
# 验证连通性(替换<NFS服务器IP>)
showmount -e <NFS服务器IP>
# 若输出共享目录,则说明网络和权限正常部署部署 NFS Provisioner(master 节点执行)
# 创建安装目录
mkdir -p ~/nfs-subdir && cd ~/nfs-subdir
# 创建 rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
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: default
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: default
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: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
# 创建 deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: default
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
# 国内环境用 DaoCloud 同步镜像,避免 registry.k8s.io 被墙
image: m.daocloud.io/registry.k8s.io/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 # 固定值,需与 StorageClass 匹配
- name: NFS_SERVER
value: 192.168.7.70 # 替换为你的NFS服务器IP
- name: NFS_PATH
value: /data/nfs-share # 替换为NFS共享目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.7.70 # 替换为你的NFS服务器IP
path: /data/nfs-share # 替换为NFS共享目录
# 创建 storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storageclass
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须与 env 中 PROVISIONER_NAME 完全一致
reclaimPolicy: Retain
volumeBindingMode: Immediate
# 一键应用
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml
kubectl apply -f storageclass.yaml
# 查看nfs-client-provisioner资源的状态(应为 Running)
kubectl get pod -l app=nfs-client-provisioner
# 查看storageclass资源的状态
kubectl get sc创建测试 PVC
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
storageClassName: nfs-storageclass
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
EOF
kubectl get pvc test-pvc -w
# STATUS 应从 Pending 变为 Bound验证
# PV 自动创建
kubectl get pv
# 应出现自动创建的 PV,名字类似 pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 验证 NFS 目录
ls /data/nfs-share
# 应多出类似 default-test-pvc-pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 的目录
# 清理测试资源
kubectl delete pvc test-pvc
# 因 reclaimPolicy: Retain,PV 会变为 Released,不会自动删除
kubectl get pv | grep Released
kubectl delete pv <那个PV名字>K8s持久化储存
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法