我们为啥需要存储卷?
默认的容器里,存储是跟容器绑定的:容器跑着的时候,你能往里面写文件,但是一旦容器崩了、删了,这些文件就全没了 ------ 你要是用容器跑个数据库,重启之后数据全没了
还有个常见的需求:同一个 Pod 里的两个容器,要共享文件。比如一个容器跑应用,生成日志,另一个容器跑日志收集工具,要把日志传到远程存储,这俩容器得能读到同一份日志文件才行。
存储卷就是来解决这两个问题的:把存储的生命周期,和容器的生命周期拆开。容器删了,只要卷还在,数据就还在;同时,卷能被多个容器挂载,实现文件共享。
K8s 里的卷分两大类:临时卷(跟 Pod 绑定,Pod 没了就没了)和持久卷(比 Pod 活得长,Pod 删了数据还在)
第一个:emptyDir,最基础的临时文件夹
emptyDir就是个跟 Pod 绑定的临时文件夹。
它是怎么工作的?
当你的 Pod 被调度到某个节点上的时候,K8s 就会在这个节点上给你建个空目录,这就是 emptyDir。只要 Pod 还在运行,这个目录就一直存在,Pod 里的所有容器,都能挂载这个目录,哪怕它们挂载的路径不一样,读写的都是同一个地方 ------ 这就实现了同一个 Pod 里的容器共享文件。
那数据安全吗?分情况:
-
如果只是容器崩了,Pod 还在节点上,那 emptyDir 里的数据一点事都没有,容器重启了还能读到原来的文件。
-
如果整个 Pod 都被删了,那这个目录还有里面的所有数据,都会被永久删掉,找不回来。
什么时候用它?
它就是个临时存储,适合那些不需要持久化的数据,比如:
-
多容器共享文件:最典型的就是日志收集场景,应用把日志写到 emptyDir,收集工具从这里读,读完就完事了,不用存。
-
临时计算缓存:比如你跑个任务,中间的计算结果,不用存到持久存储,任务跑完 Pod 删了,这些结果就没用了。
-
敏感临时数据 :你还能把它搞成内存盘!把
medium设成Memory,它就用节点的内存来存数据,速度飞快,还不写磁盘,存个临时密码、密钥啥的,Pod 删了数据就没了,不会留在节点上,安全得很。
举例
apiVersion: v1
kind: Pod
metadata:
name: emptydir
spec:
containers:
- image: httpd
imagePullPolicy: IfNotPresent
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
sizeLimit: 2G
我们建了个 Pod,里面跑了个 httpd 容器,我们定义了一个叫cache-volume的 emptyDir 卷,把它挂载到容器的/cache目录,还限制了这个卷最大只能用 2G------ 防止你写太多把节点的磁盘给占满了。
创建完 Pod 之后,你去节点上看,会发现容器里的/cache,实际就是节点上的一个目录:
"containerPath": "/cache",
"hostPath": "/var/lib/kubelet/pods/xxx/volumes/.../cache-volume",
你在容器里写的东西,都写到节点的这个目录里了,删了 Pod,这个目录就自动没了,完美符合临时存储的需求。
第二个:hostPath,直接用节点本地的目录
接下来是hostPath,这个卷的作用很简单:让 Pod 直接访问节点本地的文件和目录。
它是怎么工作的?
把节点上的某个目录,直接挂载到 Pod 里的容器中。比如你把节点的/data目录挂载给 Pod,那 Pod 里的容器,就能直接读写节点/data里的文件,跟你自己在节点上操作一模一样。
而且这些数据是存在节点上的,跟 Pod 一点关系都没有,你删了 Pod,节点上的文件一点变化都没有,还留在那。
注意!这东西不可以乱用!
hostPath 有个大问题:它会把你的 Pod 绑死在这个节点上!
比如你把 Pod 调度到了节点 A,挂载了节点 A 的/data目录,那如果之后 Pod 因为某些原因,被重新调度到了节点 B,节点 B 的/data是空的啊!Pod 就找不到原来的数据了,业务直接崩了。
所以 hostPath 根本不是给普通的业务存储用的!它就是给那些需要访问节点本地资源的特殊场景用的,比如:
-
监控容器:要访问节点的
/var/run/docker.sock,也就是 docker 的套接字,来监控节点上的容器。 -
存储插件:要访问节点的磁盘设备,来做存储的挂载。
-
静态配置:节点本地的一些固定配置文件,Pod 要读这些配置。
举例
把节点的/data目录挂载给 nginx:
apiVersion: v1
kind: Pod
metadata:
name: hostpathtest
spec:
containers:
- image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
type: DirectoryOrCreate
这里的type: DirectoryOrCreate意思是,如果节点的/data不存在,就自动创建它。
创建完 Pod 之后,你直接在节点上往/data里写个网页文件:
echo hello, hostwrite! > /data/index.html
然后访问 Pod 的 IP,就能看到这个文件了!因为 nginx 的网页目录,实际就是节点的/data,删了 Pod,节点上的文件还在,一点事都没有。
最难的部分:PV 和 PVC
管理员就像饭店的后厨,提前做好了一堆菜(这些菜就是 PV,也就是准备好的存储)。 你作为客人,不用管菜是怎么做的,用的什么食材,你只要跟服务员说:我要一份鱼香肉丝,要微辣的(这就是 PVC,也就是你申请存储的要求)。 服务员(也就是 K8s)就会去后厨,找到符合你要求的菜,给你端上来。
这俩是用来解耦的,管理员负责准备存储,不用管谁用;用户负责申请存储,不用管存储是怎么来的。
而且 PV 是集群级别的持久存储,不管你的 Pod 跑到哪个节点,都能访问到同一份数据,Pod 删了,数据还在,这就是我们要的持久化存储
第一步:先搞个共享存储
要搞持久存储,我们得先搞个所有节点都能访问的共享存储,不然 Pod 换了节点就找不到数据了。我们这里用 NFS,也就是网络文件系统,说白了就是个网络文件夹,所有节点都能读写,不管 Pod 在哪,都能读到同一份数据。
我们在 master 节点搭个 NFS 服务,把/nfsshare目录共享出去,所有节点都能访问,这个步骤很简单,装个服务端客户端就行了
第二步:创建 PV,把存储注册给 K8s
现在我们有了 NFS 存储,接下来要把它注册到 K8s 里,告诉 K8s:我这有个存储,你可以用,这就是创建 PV。
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv
labels:
pvname: nfspv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
nfs:
path: /nfsshare
server: 192.168.30.130
就是我们告诉 K8s:我这有个 5G 的存储,用的是 NFS,服务器地址是 master 的 IP,共享路径是/nfsshare,支持读写,存储类叫 slow。
访问模式:这个存储能怎么用?
就是这个存储,支持什么样的挂载权限,不同的存储支持的不一样:
-
ReadWriteOnce:最常用的,这个存储只能被一个节点以读写的方式挂载,同一个节点上的多个 Pod 可以用它。比如云盘默认就只支持这个。 -
ReadOnlyMany:能被很多节点以只读的方式挂载,适合存静态配置。 -
ReadWriteMany:能被很多节点以读写的方式挂载,NFS 就支持这个。 -
ReadWriteOncePod:只能被一个 Pod 用,哪怕同一个节点的其他 Pod 也不能用,用来保证只有一个 Pod 能读写这个存储。
回收策略:删了申请之后,存储怎么办?
就是当绑定这个 PV 的 PVC 被删了之后,这个 PV 要怎么处理:
-
Retain:啥也不做,数据原封不动保留,你要手动去删数据,才能重新用这个 PV,适合存重要数据,防止误删。 -
Recycle:自动把 PV 里的所有文件删掉,然后重置成可用状态,给新的 PVC 用,不过这个现在已经被弃用了,不够安全。 -
Delete:直接把 PV 和对应的后端存储一起删掉,比如云盘,删了 PVC,云盘也自动删了,不用你管。
创建完 PV 之后,你就能看到它的状态是Available,也就是空闲的,等着被绑定。
第三步:创建 PVC,申请存储
现在管理员准备好了存储,用户就可以申请了,也就是创建 PVC,告诉 K8s:我要个什么样的存储。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: slow
selector:
matchLabels:
pvname: "nfspv"
我要一个读写的存储,要 1G 的,存储类是 slow,还要标签是pvname: nfspv的 PV。
K8s 收到之后,就会去集群里找符合要求的 PV,找到我们刚才创建的那个 5G 的 NFS 的 PV,然后把它们绑定在一起,这时候 PVC 的状态就变成Bound了,PV 的状态也变成Bound了,搞定!
第四步:挂载给 Pod
绑定完之后,你就可以把这个 PVC 当成卷,挂载给 Pod 了,跟之前的卷一模一样:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- image: httpd
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/local/apache2/htdocs"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
创建完 Pod 之后,你直接在 NFS 的共享目录里写文件:
echo pvctest! > /nfsshare/index.html
然后不管你在哪个节点,访问 Pod 的 IP,都能看到这个文件,因为 NFS 是共享的,所有节点都能访问,不管 Pod 跑到哪,都能读到同一份数据。
删了 Pod,NFS 里的文件还在,下次你再创建 Pod,挂载同一个 PVC,还是能读到原来的数据,这就是持久存储!跑数据库用这个,再也不怕容器删了数据没了
分清所有卷
|----------|-------|----------------------|-------------------|--------------------|
| 卷类型 | 数据存在哪 | 生命周期 | 跨节点能用吗? | 什么时候用它? |
| emptyDir | 节点本地 | 和 Pod 绑定,Pod 删了数据就没了 | 不行,只能同一个 Pod 的容器用 | 临时存储、多容器共享日志、临时缓存 |
| hostPath | 节点本地 | 独立于 Pod,Pod 删了数据还在节点 | 不行,只能在有这个目录的节点用 | 访问节点本地资源、系统级配置 |
| PV/PVC | 共享存储 | 永久保留,跟 Pod 没关系 | 可以,所有节点都能访问 | 业务持久存储、数据库、要长期存的数据 |