hostPath volume存在的问题
过去我们经常会通过hostPath volume让Pod能够使用本地存储,将Node文件系统中的文件或者目录挂载到容器内,但是hostPath volume的使用是很难受的,并不适合在生产环境中使用,但为什么说不适合在生产环境中使用呢?
- 由于集群内每个节点的差异化,要使用hostPath Volume,我们需要通过NodeSelector等方式进行精确调度,这种事情多了,你就会不耐烦了。
- 注意DirectoryOrCreate和FileOrCreate两种类型的hostPath,当Node上没有对应的File/Directory时,你需要保证kubelet有在Node上Create File/Directory的权限。
- 另外,如果Node上的文件或目录是由root创建的,挂载到容器内之后,你通常还要保证容器内进程有权限对该文件或者目录进行写入,比如你需要以root用户启动进程并运行于privileged容器,或者你需要事先修改好Node上的文件权限配置。
- Scheduler并不会考虑hostPath volume的大小,hostPath也不能申明需要的storage size,这样调度时存储的考虑,就需要人为检查并保证。
- StatefulSet无法使用hostPath volume,已经写好的使用共享存储的Helm Chart不能兼容hostPath volume,需要修改的地方还不少,这也挺难受的。
Local Persistent Volume
Local persistent volume就是用来解决hostPath volume面临的portability, disk accounting, and scheduling
的缺陷。PV Controller和Scheduler会对local PV做特殊的逻辑处理,以实现Pod使用本地存储时发生Pod re-schedule的情况下能再次调度到local volume所在的Node。
local pv在生产中使用,也是需要谨慎的,毕竟它本质上还是使用的是节点上的本地存储,如果没有相应的存储副本机制,那意味着一旦节点或者磁盘异常,使用该volume的Pod也会异常,甚至出现数据丢失,除非你明确知道这个风险不会对你的应用造成很大影响或者允许数据丢失。
Local Persistent Volume 的设计,主要面临两个难点
- 第一个难点在于:如何把本地磁盘抽象成 PV
- 第二个难点在于:调度器如何保证 Pod 始终能被正确地调度到它所请求的 Local Persistent Volume 所在的节点上
- Kubernetes使用PersistentVolume的.spec.nodeAffinityfield来描述local volume与Node的绑定关系
- 使用volumeBindingMode: WaitForFirstConsumer的local-storage StorageClass来实现PVC的延迟绑定,使得PV Controller并不会立刻为PVC做Bound,而是等待某个需要使用该local pv的Pod完成调度后,才去做Bound
而在我们部署的私有环境中,你有两种办法来完成这个步骤。
- 第一种,当然就是给你的宿主机挂载并格式化一个可用的本地磁盘,这也是最常规的操作;
- 第二种,对于实验环境,你其实可以在宿主机上挂载几个 RAM Disk(内存盘)来模拟本地磁盘。
接下来,我会使用第二种方法,在我们之前部署的 Kubernetes 集群上进行实践。
首先,在名叫 node-1 的宿主机上创建一个挂载点,比如 /mnt/disks;然后,用几个 RAM Disk 来模拟本地磁盘,如下所示:
sudo mkdir /mnt/disks
sudo mkdir /mnt/disks/vol1
sudo mount -t tmpfs vol1 /mnt/disks/vol1
#for vol in vol1 vol2 vol3; do
# sudo mkdir /mnt/disks/$vol
# sudo mount -t tmpfs $vol /mnt/disks/$vol
#done
接下来,我们就可以为这些本地磁盘定义对应的 local-pv.yaml 了,如下所示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv-0
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-001
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv-1
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-002
接下来,我们就可以使用 kubect create 来创建这个 PV,如下所示:
$ kubectl create -f local-pv.yaml
persistentvolume/example-pv created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 1Gi RWO Delete Available local-storage 16s
创建一个 StorageClass 来描述这个 PV,如下所示:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
我们就可以创建 StorageClass 了,如下所示:
$ kubectl create -f local-sc.yaml
storageclass.storage.k8s.io/local-storage created
接下来,我们只需要定义一个非常普通的 PVC,就可以让 Pod 使用到上面定义好的 Local Persistent Volume 了,如下所示:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-storage
现在,我们来创建这个 PVC:
$ kubectl create -f local-pvc.yaml
persistentvolumeclaim/example-local-claim created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Pending local-storage 7s
然后,我们编写一个 Pod 来声明使用这个 PVC,如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-pv-pod
labels:
app.kubernetes.io/name: example-pv-pod
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: example-pv-pod
template:
metadata:
labels:
app.kubernetes.io/name: example-pv-pod
spec:
volumes:
- name: example-pv-storage
persistentVolumeClaim:
claimName: example-local-claim
containers:
- name: example-pv-container
image: nginx
imagePullPolicy: IfNotPresent
command: ["nginx", "-g", "daemon off;"]
ports:
- name: "http-server"
containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: example-pv-storage
这个 Pod 没有任何特别的地方,你只需要注意,它的 volumes 字段声明要使用前面定义的、名叫 example-local-claim 的 PVC 即可。
而我们一旦使用 kubectl create 创建这个 Pod,就会发现,我们前面定义的 PVC,会立刻变成 Bound 状态,与前面定义的 PV 绑定在了一起,如下所示:
$ kubectl create -f local-pod.yaml
pod/example-pv-pod created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Bound example-pv 5Gi RWO local-storage 6h
也就是说,在我们创建的 Pod 进入调度器之后,“绑定”操作才开始进行。
这时候,我们可以尝试在这个 Pod 的 Volume 目录里,创建一个测试文件,比如:
$ kubectl exec -it example-pv-pod -- /bin/sh
# cd /usr/share/nginx/html
# touch test.txt
然后,登录到 winex-k8s-node-001 这台机器上,查看一下它的 /mnt/disks/vol1 目录下的内容,你就可以看到刚刚创建的这个文件:
# 在winex-k8s-node-001上
$ ls /mnt/disks/vol1
test.txt
而如果你重新创建这个 Pod 的话,就会发现,我们之前创建的测试文件,依然被保存在这个持久化 Volume 当中:
$ kubectl delete -f local-pod.yaml
$ kubectl create -f local-pod.yaml
$ kubectl exec -it example-pv-pod -- /bin/sh
# ls /usr/share/nginx/html
# touch test.txt
需要注意的是,我们上面手动创建 PV 的方式,即 Static 的 PV 管理方式,在删除 PV 时需要按如下流程执行操作:
- 删除使用这个 PV 的 Pod;
- 从宿主机移除本地磁盘(比如,umount 它);
- 删除 PVC;
- 删除 PV。
多副本下使用Local Persistent Volume
StatefulSet在多副本情况下我们需要考虑一下几个问题:
- 多副本情况下怎么动态声明PVC
- 动态声明的PVC怎么与PV绑定
有了上面的演示,下面给出一个多副本的StatefulSet yaml配置:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: example-pv-statefulset
labels:
app.kubernetes.io/name: example-pv-statefulset
spec:
serviceName: "local-service"
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: example-pv-statefulset
template:
metadata:
labels:
app.kubernetes.io/name: example-pv-statefulset
spec:
containers:
- name: example-pv-statefulset-container
image: nginx
imagePullPolicy: IfNotPresent
command: ["nginx", "-g", "daemon off;"]
ports:
- name: "http-server"
containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: local-vol
volumeClaimTemplates:
- metadata:
name: local-vol
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-storage"
resources:
requests:
storage: 1G
- 通过配置文件可以看出使用
volumeClaimTemplates
模板可以动态声明PVC - PVC与PV必须要延迟绑定,上面提到的Pod在进入调度之后,"绑定"操作才开始
执行上面yaml文件,如下所示:
$ kubectl apply -f local-statefulset.yaml
statefulset.apps/example-pv-statefulset created
$ kubectl get pods
example-pv-statefulset-0 1/1 Running 0 8s
example-pv-statefulset-1 1/1 Running 0 5s
$ kubectl get pvc
local-vol-example-pv-statefulset-0 Bound example-pv-0 1Gi RWO local-storage 15s
local-vol-example-pv-statefulset-1 Bound example-pv-1 1Gi RWO local-storage 12s
参考文献