简单存储
EmptyDir
EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。
EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时,EmptyDir中的数据也会被永久删除。
EmptyDir的用途如下:
1、临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
2、一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
接下来,通过一个容器之间的共享案例来使用描述一个EmptyDir。
在一个Pod中准备两个容器nginx和busybox,然后声明一个volume分别挂载到两个容器的目录中,然后nginx容器负责向volume中写日志,busybox中通过命令将日志内容读到控制台。
我们创建一个Pod,资源清单文件如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","tail -f /logs/access.log"]
volumeMounts: # 将logs-volume挂载到busybox对应的目录,该目录为/logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume,name为logs-volume,类型emptyDir
- name: logs-volume
emptyDir: {}
创建后我们进行查看Pod:
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE ...
volume-emptydir 2/2 Running 0 3m37s 192.168.166.130 node1 ...
我们可以看到暴露出来的IP,我们进行访问:
# curl 192.168.166.130
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
我们可以看到我们能正常访问Nginx,那么我们查看下busybox的输出:
# kubectl logs -f volume-emptydir -c busybox
192.168.219.64 - - [23/Dec/2021:03:38:21 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
发现Nginx的访问记录的日志,被busybox正常输出。
HostPath
我们已经知道EmptyDir中的数据不会被持久化,它会随着Pod的结束而销毁,如果想要简单的将数据持久化到主机中,可以选择HostPath。
HostPath就是将Node主机中的一个实际目录挂载到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依旧可以保存在Node主机上。
同样我们使用Nginx和Busybox来演示:
apiVersion: v1
kind: Pod
metadata:
name: volume-hostpath
spec:
containers:
- name: nginx
image: nginx:1.17.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","tail -f /logs/access.log"]
volumeMounts: # 将logs-volume挂载到busybox对应的目录,该目录为/logs
- name: logs-volume
mountPath: /logs
volumes: # 声明volume,name为logs-volume,类型emptyDir
- name: logs-volume
hostPath:
path: /root/logs
type: DirectoryOrCreate # 目录存在就直接使用,不存在则先创建
type说明:
- DirectoryOrCreate:目录存在就使用,不存在就先创建后使用。
- Directory:目录必须存在。
- FileOrCreate:文件存在就使用,不存在就先创建后使用。
- File:文件必须存在。
- Socket:unix套接字必须存在。
- CharDevice:字符设备必须存在。
- BlockDevice:块设备必须存在。
不难发现,这个资源文件和上面的资源文件差别只是在于volumes的类型不同。
同样,我们创建后进行查看:
# kubectl get pod volume-hostpath -o wide
NAME READY STATUS RESTARTS AGE IP NODE ...
volume-hostpath 2/2 Running 0 13s 192.168.166.131 node1 ...
同样,我们进行访问,然后在node1节点上查看是否有文件生成:
# curl 192.168.166.131
# 下面在node1上进行查看
# cat /root/logs/access.log
192.168.219.64 - - [23/Dec/2021:03:57:20 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
Local本地存储
前面我们有通过 hostPath
或者 emptyDir
的方式来持久化我们的数据,但是显然我们还需要更加可靠的存储来保存应用的持久化数据,这样容器在重建后,依然可以使用之前的数据。但是存储资源和 CPU 资源以及内存资源有很大不同,为了屏蔽底层的技术实现细节,让用户更加方便的使用,Kubernetes 便引入了 PV
和 PVC
两个重要的资源对象来实现对存储的管理。
PV、PVC和StorageClass
PV
的全称是:PersistentVolume
(持久化卷),是对底层共享存储的一种抽象,PV 由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph
、GlusterFS
、NFS
、hostPath
等,都是通过插件机制完成与共享存储的对接。
PVC
的全称是:PersistentVolumeClaim
(持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。
但是通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass
,通过 StorageClass
的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass
的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了,此外 StorageClass
还可以为我们自动生成 PV,免去了每次手动创建的麻烦。
hostPath
上面提到了 PV 是对底层存储技术的一种抽象,PV 一般都是由管理员来创建和配置的,Kubernetes 支持 hostPath 类型的 PersistentVolume 使用节点上的文件或目录来模拟附带网络的存储,但是需要注意的是在生产集群中,我们不会使用 hostPath,集群管理员会提供网络存储资源,比如 NFS 共享卷或 Ceph 存储卷,集群管理员还可以使用 StorageClasses
来设置动态提供存储。因为 Pod 并不是始终固定在某个节点上面的,所以要使用 hostPath 的话我们就需要将 Pod 固定在某个节点上,这样显然就大大降低了应用的容错性。
比如我们这里将测试的应用固定在节点 node1 上面,首先在该节点上面创建一个 /data/k8s/test/hostpath
的目录,然后在该目录中创建一个 index.html
的文件:
$ mkdir -p k8s/hostpath
$ echo 'Hello from Kubernetes hostpath storage' > /root/k8s/hostpath/index.html
接下来我们创建一个hostPath类型的PV资源对象:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/root/k8s/hostpath"
配置文件中指定了该卷位于集群节点上的 k8s/hostpath
目录,还指定了 10G 大小的空间和 ReadWriteOnce
的访问模式,这意味着该卷可以在单个节点上以读写方式挂载,另外还定义了名称为 manual
的 StorageClass
,该名称用来将 PersistentVolumeClaim
请求绑定到该 PersistentVolum
。下面是关于 PV 的这些配置属性的一些说明:
- Capacity(存储能力):一般来说,一个 PV 对象都要指定一个存储能力,通过 PV 的
capacity
属性来设置的,目前只支持存储空间的设置,就是我们这里的storage=10Gi
,不过未来可能会加入IOPS
、吞吐量等指标的配置。 - AccessModes(访问模式):用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
ReadWriteMany(RWX):读写权限,可以被多个节点挂载
创建上面的资源对象后,我们进行查看PersistentVolume
的信息,输出结果显示该PersistenVolume
的状态(STATUS)为Available
,这意味着它还没有被绑定给PersistentVolumeClaim
:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-hostpath 10Gi RWO Retain Available manual 5s
其中有一项 RECLAIM POLICY
的配置,同样我们可以通过 PV 的 persistentVolumeReclaimPolicy
(回收策略)属性来进行配置,目前 PV 支持的策略有三种:
- Retain(保留):保留数据,需要管理员手工清理数据
- Recycle(回收):清除 PV 中的数据,效果相当于执行
rm -rf /thevoluem/*
- Delete(删除):与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。
不过需要注意的是,目前只有 NFS
和 HostPath
两种类型支持回收策略,当然一般来说还是设置为 Retain
这种策略保险一点。
Recycle
策略会通过运行一个 busybox 容器来执行数据删除命令,默认定义的 busybox 镜像是:gcr.io/google_containers/busybox:latest
,并且imagePullPolicy: Always
,如果需要调整配置,需要增加kube-controller-manager
启动参数:--pv-recycler-pod-template-filepath-hostpath
来进行配置。
关于 PV 的状态,实际上描述的是 PV 的生命周期的某个阶段,一个 PV 的生命周期中,可能会处于4种不同的阶段:
- Available(可用):表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PVC 已经被 PVC 绑定
- Released(已释放):PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的自动回收失败
现在我们创建完成了 PV,如果我们需要使用这个 PV 的话,就需要创建一个对应的 PVC 来和它进行绑定了,就类似于我们的服务是通过 Pod 来运行的,而不是 Node,只是 Pod 跑在 Node 上而已。
现在我们来创建一个 PersistentVolumeClaim
,Pod 使用 PVC 来请求物理存储,我们这里创建的 PVC 请求至少 3G 容量的卷,该卷至少可以为一个节点提供读写访问,下面是 PVC 的配置文件:(pvc-hostpath.yaml)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
创建 PVC 之后,Kubernetes 就会去查找满足我们声明要求的 PV,比如 storageClassName
、accessModes
以及容量这些是否满足要求,如果满足要求就会将 PV 和 PVC 绑定在一起。
注意:
需要注意的是目前 PV 和 PVC 之间是一对一绑定的关系,也就是说一个 PV 只能被一个 PVC 绑定。
现在我们再次查看PV的信息:
$ kubectl get pv -l type=local
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-hostpath 10Gi RWO Retain Bound default/pvc-hostpath manual 8m25s
现在输出的 STATUS 为 Bound
,查看 PVC 的信息:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-hostpath Bound pv-hostpath 10Gi RWO manual 2m19s
输出结果表明我们创建的PVC(pvc-hostpath)和创建的PV(pv-hostpath)互相绑定了,我们这里虽然声明的3G的容量,但是由于 PV 里面是 10G,所以显然也是满足要求的。
接下来我们就可以来创建 Pod 了,该 Pod 使用上面我们声明的 PVC 作为存储卷:(pv-hostpath-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pv-hostpath-pod
spec:
volumes:
- name: pv-hostpath
persistentVolumeClaim:
claimName: pvc-hostpath
nodeSelector:
kubernetes.io/hostname: node1
containers:
- name: task-pv-container
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-hostpath
这里需要注意的是,由于我们创建的 PV 真正的存储在节点 node1 上面,所以我们这里必须把 Pod 固定在这个节点下面,另外可以注意到 Pod 的配置文件指定了 PersistentVolumeClaim
,但没有指定 PersistentVolume
,对 Pod 而言,PVC
就是一个存储卷。直接创建这个 Pod 对象即可:
$ kubectl apply -f pv-hostpath-pod.yaml
pod/pv-hostpath-pod created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pv-hostpath-pod 1/1 Running 0 4s
运行成功后,我们访问 Pod 中的容器:
$ kubectl exec -it pv-hostpath-pod -- /bin/bash
root@pv-hostpath-pod:/# apt-get update
root@pv-hostpath-pod:/# apt-get install curl -y
root@pv-hostpath-pod:/# curl localhost
Hello from Kubernetes hostpath storage
我们可以看到输出结果是我们前面写到 hostPath 卷种的 index.html 文件中的内容,同样我们可以把 Pod 删除,然后再次重建再测试一次,可以发现内容还是我们在 hostPath 中设置的内容。
$ kubectl delete -f pv-hostpath-pod.yaml
pod "pv-hostpath-pod" deleted
$ kubectl apply -f pv-hostpath-pod.yaml
pod/pv-hostpath-pod created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pv-hostpath-pod 1/1 Running 0 5s
$ kubectl exec -it pv-hostpath-pod -- /bin/bash
root@pv-hostpath-pod:/# cat /usr/share/nginx/html/index.html
Hello from Kubernetes hostpath storage
我们之前直接在 Pod 下面也可以使用 hostPath 来持久化数据,为什么还要费劲去创建 PV、PVC 对象来引用呢?
PVC 和 PV 的设计,其实跟“面向对象”
的思想完全一致,PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的实现部分则由 PV 负责完成。这样做的好处是,作为应用开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 hostPath、NFS 还是 Ceph。毕竟这些存储相关的知识太专业了,应该交给专业的人去做,这样对于我们的 Pod 来说就不用管具体的细节了,你只需要给我一个可用的 PVC 即可了,这样是不是就完全屏蔽了细节和解耦了啊,所以我们更应该使用 PV、PVC 这种方式。
Local PV
使用 hostPath 有一个局限性就是,Pod需要固定到一个节点上,因为一旦漂移到其他节点上,宿主机上面就没有对应的数据了,所以我们在使用 hostPath 的时候都会搭配 nodeSelector 来进行使用。但是使用 hostPath 明显也有一些好处的,因为 PV 直接使用的是本地磁盘,尤其是 SSD 盘,它的读写性能相比于大多数远程存储来说,要快的多,所以对于一些对磁盘 IO 要求比较高的应用,比如 etcd 就非常实用了。不过呢,相比于正常的 PV 来说,使用了 hostPath 的这些节点一旦宕机数据就可能丢失,所以这就要求使用 hostPath 的应用必须具备数据备份和恢复的能力,允许你把这些数据定时备份在其他位置。
所以在 hostPath 的基础上,Kubernetes 依靠 PV、PVC 实现了一个新的特性,这个特性的名字叫作:Local Persistent Volume
,也就是我们说的 Local PV
。
其实 Local PV
实现的功能就非常类似于 hostPath
加上 nodeAffinity
,比如,一个 Pod 可以声明使用类型为 Local 的 PV,而这个 PV 其实就是一个 hostPath 类型的 Volume。如果这个 hostPath 对应的目录,已经在节点 A 上被事先创建好了,那么,我只需要再给这个 Pod 加上一个 nodeAffinity=nodeA
,不就可以使用这个 Volume 了吗?理论上确实是可行的,但是事实上,我们绝不应该把一个宿主机上的目录当作 PV 来使用,因为本地目录的存储行为是完全不可控,它所在的磁盘随时都可能被应用写满,甚至造成整个宿主机宕机。所以,一般来说 Local PV
对应的存储介质是一块额外挂载在宿主机的磁盘或者块设备,我们可以认为就是“一个 PV 一块盘”
。
另外一个 Local PV
和普通的 PV 有一个很大的不同在于 Local PV
可以保证 Pod 始终能够被正确地调度到它所请求的 Local PV
所在的节点上面,对于普通的 PV 来说,Kubernetes 都是先调度 Pod 到某个节点上,然后再持久化节点上的 Volume 目录,进而完成 Volume 目录与容器的绑定挂载,但是对于 Local PV
来说,节点上可供使用的磁盘必须是提前准备好的,因为它们在不同节点上的挂载情况可能完全不同,甚至有的节点可以没这种磁盘,所以,这时候,调度器就必须能够知道所有节点与 Local PV
对应的磁盘的关联关系,然后根据这个信息来调度 Pod,实际上就是在调度的时候考虑 Volume 的分布。
接下来我们来测试下 Local PV
的使用,当然按照上面我们的分析我们应该给宿主机挂载并格式化一个可用的磁盘,我们这里就暂时将 node1 节点上的 /root/k8s/localpv
这个目录看成是挂载的一个独立的磁盘。现在我们来声明一个 Local PV
类型的 PV,如下所示:(pv-local.yaml)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /root/k8s/localpv
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
和前面我们定义的 PV 不同,我们这里定义了一个 local
字段,表明它是一个 Local PV
,而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/root/k8s/localpv
,这也就意味着如果 Pod 要想使用这个 PV,那它就必须运行在 node1 节点上。所以,在这个 PV 的定义里,添加了一个节点亲和性 nodeAffinity
字段指定 node1 这个节点。这样,调度器在调度 Pod 的时候,就能够知道一个 PV 与节点的对应关系,从而做出正确的选择。
创建上面的这个资源对象后,我们查看状态:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Delete Available local-storage 35s
可以看到,这个 PV 创建后,进入了 Available
(可用)状态。这个时候如果按照前面提到的,我们要使用这个 Local PV
的话就需要去创建一个 PVC 和他进行绑定:(pvc-local.yaml)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
同样要注意声明的这些属性需要和上面的 PV 对应,直接创建这个资源对象:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Bound pv-local 5Gi RWO local-storage 56s
可以看到现在 PVC 和 PV 已经处于 Bound
绑定状态了。但实际上这是不符合我们的需求的,比如现在我们的 Pod 声明使用这个 pvc-local,并且我们也明确规定,这个 Pod 只能运行在 node2 这个节点上,按照上面的操作,这个 pvc-local 就和 pv-local 这个 Local PV
绑定在一起了,但是这个 PV 的存储卷又在 node1 这个节点上,显然就会出现冲突了,那么这个 Pod 的调度肯定就会失败了,所以我们在使用 Local PV
的时候,必须想办法延迟这个“绑定”
操作。
为了解决上面的问题,可以通过创建 StorageClass
来指定这个动作,在 StorageClass 中有一个 volumeBindingMode=WaitForFirstConsumer
的属性,就是告诉 Kubernetes 在发现这个 StorageClass 关联的 PVC 与 PV 可以绑定在一起,但不要现在就立刻执行绑定操作(即:设置 PVC 的 VolumeName 字段),而是要等到第一个声明使用该 PVC 的 Pod 出现在调度器之后,调度器再综合考虑所有的调度规则,当然也包括每个 PV 所在的节点位置,来统一决定,这个 Pod 声明的 PVC,到底应该跟哪个 PV 进行绑定。通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。
所以我们需要创建对应的 StorageClass
对象:(local-storageclass.yaml)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
这个 StorageClass
的名字,叫作 local-storage,也就是我们在 PV 中声明的,需要注意的是,在它的 provisioner
字段,我们指定的是 no-provisioner
。因为我们这里是手动创建的 PV,所以不需要动态来生成 PV,另外这个 StorageClass 还定义了一个 volumeBindingMode=WaitForFirstConsumer
的属性,它是 Local PV
里一个非常重要的特性,即:延迟绑定。通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。
现在我们来创建这个 StorageClass 资源对象后对上面的PVC进行删除,重新创建操作:
$ kubectl apply -f local-storageclss.yaml
storageclass.storage.k8s.io/local-storage created
$ kubectl delete -f pvc-local.yaml
persistentvolumeclaim "pvc-local" deleted
$ kubectl apply -f pvc-local.yaml
persistentvolumeclaim/pvc-local created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Pending local-storage 5s
这时候我们发现,集群中即使已经存在了一个可以与 PVC 匹配的 PV 了,但这个 PVC 依然处于 Pending
状态,也就是等待绑定的状态,这就是因为上面我们配置的是延迟绑定,需要在真正的 Pod 使用的时候才会来做绑定。
同样我们声明一个 Pod 来使用这里的 pvc-local 这个 PVC,资源对象如下所示:(pv-local-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: pv-local-pod
spec:
volumes:
- name: example-pv-local
persistentVolumeClaim:
claimName: pvc-local
containers:
- name: example-pv-local
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: example-pv-local
创建完成pod后,我们已经去查看我们之前创建的PVC,会发现立即变成Bound
状态,与前面定义的PV绑定在了一起:
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Bound pv-local 5Gi RWO local-storage 23m
这时候,我们可以尝试在这个 Pod 的 Volume 目录里,创建一个测试文件,比如:
$ kubectl exec -it pv-local-pod /bin/bash
root@pv-local-pod:/# cd /usr/share/nginx/html/
root@pv-local-pod:/usr/share/nginx/html# echo "Hello from Kubernetes local pv storage" > test.txt
然后,登录到 node1 这台机器上,查看一下它的 /root/k8s/localpv
目录下的内容,你就可以看到刚刚创建的这个文件:
$ cat /root/k8s/localpv/test.txt
Hello from Kubernetes local pv storage
到这里就说明基于本地存储的 Volume 是完全可以提供容器持久化存储功能的,对于 StatefulSet 这样的有状态的资源对象,也完全可以通过声明 Local 类型的 PV 和 PVC,来管理应用的存储状态。
需要注意的是,我们上面手动创建 PV 的方式,即静态的 PV 管理方式,在删除 PV 时需要按如下流程执行操作:
1、删除使用这个 PV 的 Pod
2 、从宿主机移除本地磁盘
3、删除 PVC
4、删除 PV
如果不按照这个流程的话,这个 PV 的删除就会失败。