
做了这么多年运维,K8s的存储这块一直是个让人又爱又恨的东西。说它复杂吧,确实概念挺多的,PV、PVC、StorageClass这些名词听起来就头大;说它简单吧,一旦搞明白了原理,用起来还是很顺手的。
今天我就把自己这些年在生产环境中踩过的坑、总结的经验分享给大家,让你们少走点弯路。
容器本身是无状态的,这个大家都知道。Pod一重启,里面的数据就没了,这对于数据库、文件存储这些有状态应用来说简直是灾难。
我记得刚开始用K8s的时候,有次MySQL的Pod重启了,结果所有数据都丢了,那个心情...简直想死的心都有。从那以后我就深刻认识到,持久化存储在K8s中有多重要。
K8s的持久化存储主要解决这么几个问题:
这三个概念的关系,我用一个比较接地气的比喻来解释:
**PV(PersistentVolume)**就像是一个仓库,管理员提前准备好的存储空间。 **PVC(PersistentVolumeClaim)**就像是租仓库的申请单,用户说我要多大的空间、什么类型的。 StorageClass就像是仓库的分类标准,比如高性能仓库、普通仓库、便宜仓库等。
具体来说:
PV是集群级别的资源,由管理员创建,代表集群中的一块存储。它包含存储实现的具体细节,比如NFS、iSCSI、云存储等。
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.1.100
path: /data/nfs这个配置我在生产环境用过很多次,需要注意几个点:
capacity定义存储大小accessModes定义访问模式,RWO(ReadWriteOnce)表示只能被一个节点挂载persistentVolumeReclaimPolicy定义回收策略PVC是用户对存储的请求,它会去寻找合适的PV进行绑定。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi这里有个坑要注意,PVC请求的存储大小必须小于等于PV提供的大小,而且访问模式也要匹配。
StorageClass提供了动态创建PV的方式,不需要管理员手动创建PV。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
zone: us-west-2a静态供应就是管理员提前创建好PV,然后用户创建PVC去绑定。这种方式比较传统,适合小规模或者对存储有特殊要求的场景。
我在一个项目中就是用的静态供应,因为客户要求数据必须存储在指定的NFS服务器上,所以只能手动创建PV。
动态供应通过StorageClass自动创建PV,用户只需要在PVC中指定StorageClass即可。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi这种方式更灵活,特别适合云环境。我现在基本都用动态供应,省事多了。
PV和PVC的生命周期有几个关键阶段:
PV已创建,但还没有被PVC绑定。
PV已经被PVC绑定,正在使用中。
PVC被删除,但PV还没有被回收。
自动回收失败。
这里要特别说一下回收策略,有三种:
我在生产环境中一般用Retain策略,因为数据安全第一,宁可手动清理也不能误删数据。
K8s支持三种访问模式:
只能被一个节点以读写方式挂载。这是最常用的模式,适合大部分应用。
可以被多个节点以只读方式挂载。适合配置文件、静态资源等场景。
可以被多个节点以读写方式挂载。这个模式对存储系统要求比较高,不是所有存储都支持。
我遇到过一个坑,就是用了不支持RWX的存储系统,结果Pod一直处于Pending状态。后来查了半天才发现是访问模式的问题。
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysql-storage
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
encrypted: "true"
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: mysql-storage
resources:
requests:
storage: 100Gi
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "password123"
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc这个配置我用了好几年,非常稳定。关键点是:
有时候需要多个Pod共享同一份数据,比如Web应用的静态文件。
# 使用NFS的PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: shared-pv
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteMany
nfs:
server: nfs-server.example.com
path: /shared/data
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 50Gi这种场景下必须使用支持RWX的存储系统,NFS是个不错的选择。
不同的存储类型性能差异很大:
我的经验是,数据库一定要用SSD,日志可以用HDD,配置文件用网络存储。
存储大小不是越大越好,要根据实际需求来:
我一般会用Prometheus监控存储使用率:
# 存储使用率告警规则
groups:
- name: storage.rules
rules:
- alert: PVCStorageUsageHigh
expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) * >
for: 5m
labels:
severity: warning
annotations:
summary: "PVC storage usage is high"这是最常见的问题,可能的原因:
排查方法:
kubectl describe pvc my-pvc
kubectl get pv
kubectl get storageclass挂载失败通常是因为:
我遇到过一次EBS卷挂载失败,最后发现是AWS的配额限制导致的。
这个问题最严重,预防措施:
根据我这些年的经验,总结几个最佳实践:
如果觉得这篇文章对你有帮助,别忘了点个赞、转发一下,让更多的运维小伙伴看到。你们的支持是我持续输出干货的动力!
关注@运维躬行录,获取更多实战干货,我们一起在运维的道路上躬行实践!
公众号:运维躬行录 个人博客:躬行笔记