目录html
kubernetes从1.10版本开始支持local volume(本地卷),workload(不只是statefulsets类型)能够充分利用本地快速SSD,从而获取比remote volume(如cephfs、RBD)更好的性能。node
在local volume出现以前,statefulsets也能够利用本地SSD,方法是配置hostPath,并经过nodeSelector或者nodeAffinity绑定到具体node上。但hostPath的问题是,管理员须要手动管理集群各个node的目录,不太方便。nginx
下面两种类型应用适合使用local volume。git
下面会先以手动方式建立PV、PVC、Pod的方式,介绍如何使用local volume,而后再介绍external storage提供的半自动方式,最后介绍社区的一些发展。github
首先须要有一个名为local-volume
的sc。shell
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: local-volume provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer
sc的provisioner是 kubernetes.io/no-provisioner
。数据库
WaitForFirstConsumer
表示PV不要当即绑定PVC,而是直到有Pod须要用PVC的时候才绑定。调度器会在调度时综合考虑选择合适的local PV,这样就不会致使跟Pod资源设置,selectors,affinity and anti-affinity策略等产生冲突。很明显:若是PVC先跟local PV绑定了,因为local PV是跟node绑定的,这样selectors,affinity等等就基本没用了,因此更好的作法是先根据调度策略选择node,而后再绑定local PV。ubuntu
经过kubectl命令,静态建立一个5GiB的PV;该PV使用node ubuntu-1的 /data/local/vol1
目录;该PV的sc为local-volume。api
apiVersion: v1 kind: PersistentVolume metadata: name: example-local-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-volume local: path: /data/local/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - ubuntu-1
Retain(保留)是指,PV跟PVC释放后,管理员须要手工清理,从新设置该卷。缓存
须要指定PV对应的sc;目录/data/local/vol1
也须要建立。
kubectl get pv example-local-pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-local-pv 5Gi RWO Retain Available local-volume 8d
接下来建立一个关联 sc:local-volume的PVC,而后将该PVC挂到nginx容器里。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: local-volume --- kind: Pod apiVersion: v1 metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/usr/share/nginx/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim
进入到容器里,会看到挂载的目录,大小其实就是上面建立的PV所在磁盘的size。
/dev/sdb 503G 235M 478G 1% /usr/share/nginx/html
在宿主机的/data/local/vol1
目录下建立一个index.html
文件:
echo "hello world" > /data/local/vol1/index.html
而后再去curl容器的IP地址,就能够获得刚写入的字符串了。
删除Pod/PVC,以后PV状态改成Released,该PV不会再被绑定PVC了。
手工管理local PV显然是很费劲的,社区提供了external storage能够动态的建立PV(实际仍然不够自动化)。
local volume provisioner的官方编排在local-volume/provisioner/deployment/kubernetes/example/default_example_provisioner_generated.yaml
目录里,不过官方文档一会fast-disk,一会local-storage,有点混乱。我这里统一都用local-volume
。
--- apiVersion: v1 kind: ConfigMap metadata: name: local-provisioner-config namespace: default data: storageClassMap: | local-volume: hostDir: /data/local mountDir: /data/local blockCleanerCommand: - "/scripts/shred.sh" - "2" volumeMode: Filesystem fsType: ext4 --- apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: local-volume-provisioner namespace: default labels: app: local-volume-provisioner spec: selector: matchLabels: app: local-volume-provisioner template: metadata: labels: app: local-volume-provisioner spec: serviceAccountName: local-volume-admin containers: - image: "silenceshell/local-volume-provisioner:v2.1.0" imagePullPolicy: "Always" name: provisioner securityContext: privileged: true env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName volumeMounts: - mountPath: /etc/provisioner/config name: provisioner-config readOnly: true - mountPath: /data/local name: local mountPropagation: "HostToContainer" volumes: - name: provisioner-config configMap: name: local-provisioner-config - name: local hostPath: path: /data/local --- apiVersion: v1 kind: ServiceAccount metadata: name: local-volume-admin namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-volume-provisioner-pv-binding namespace: default subjects: - kind: ServiceAccount name: local-volume-admin namespace: default roleRef: kind: ClusterRole name: system:persistent-volume-provisioner apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: local-volume-provisioner-node-clusterrole namespace: default rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-volume-provisioner-node-binding namespace: default subjects: - kind: ServiceAccount name: local-volume-admin namespace: default roleRef: kind: ClusterRole name: local-volume-provisioner-node-clusterrole apiGroup: rbac.authorization.k8s.io
kubectl建立后,因为是daemonset类型,每一个节点上都会启动一个provisioner。该provisioner会监视 “discovery directory”,即上面配置的/data/local
。
$ kubectl get pods -o wide|grep local-volume local-volume-provisioner-rrsjp 1/1 Running 0 5m 10.244.1.141 ubuntu-2 <none> local-volume-provisioner-v87b7 1/1 Running 0 5m 10.244.2.69 ubuntu-3 <none> local-volume-provisioner-x65k9 1/1 Running 0 5m 10.244.0.174 ubuntu-1 <none>
前面mypod/myclaim
已经删除了,咱们从新建立一个,此时pvc myclaim是Pending状态,provisoner并无自动供给存储。为何呢?
原来external-storage
的逻辑是这样的:其Provisioner自己其并不提供local volume,但它在各个节点上的provisioner会去动态的“发现”挂载点(discovery directory),当某node的provisioner在/data/local/
目录下发现有挂载点时,会建立PV,该PV的local.path
就是挂载点,并设置nodeAffinity为该node。
那么如何得到挂载点呢?
直接去建立目录是行不通的,由于provsioner但愿PV是隔离的,例如capacity,io等。试着在ubuntu-2上的/data/local/
下建立一个xxx
目录,会获得这样的告警。
discovery.go:201] Path "/data/local/xxx" is not an actual mountpoint
目录不是挂载点,不能用。
该目录必须是真材实料的mount才行。一个办法是加硬盘、格式化、mount,比较麻烦,实际能够经过本地文件格式化(loopfs)后挂载来“欺骗”provisioner,让它觉得是一个mount的盘,从而自动建立PV,并与PVC绑定。
以下。
将下面的代码保存为文件 loopmount
,加执行权限并拷贝到/bin
目录下,就可使用该命令来建立挂载点了。
#!/bin/bash # Usage: sudo loopmount file size mount-point touch $1 truncate -s $2 $1 mke2fs -t ext4 -F $1 1> /dev/null 2> /dev/null if [[ ! -d $3 ]]; then echo $3 " not exist, creating..." mkdir $3 fi mount $1 $3 df -h |grep $3
使用脚本建立一个6G的文件,并挂载到/data/local
下。之因此要6G,是由于前面PVC须要的是5GB,而格式化后剩余空间会小一点,因此设置文件更大一些,后面才好绑定PVC。
# loopmount xxx 6G /data/local/xxx /data/local/xxx not exist, creating... /dev/loop0 5.9G 24M 5.6G 1% /data/local/x1
查看PV,可见Provisioner自动建立了PV,而kubernetes会将该PV供给给前面的PVC myclam,mypod也run起来了。
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-600377f7 5983Mi RWO Delete Bound default/myclaim local-volume 1s
可见,目前版本的local volume还没法作到像cephfs/RBD同样的全自动化,仍然须要管理员干涉,显然这不是一个好的实现。
社区有人提交了基于LVM作local volume动态供给的Proposal,不过进展很缓慢。做者是huawei的员工,应该huawei已经实现了。
除了基于LVM,也能够基于
ext4 project quota
来实现LV的动态供给。
除了使用磁盘,还能够考虑使用内存文件系统,从而获取更高的io性能,只是容量就没那么理想了。一些特殊的应用能够考虑。
mount -t tmpfs -o size=1G,nr_inodes=10k,mode=700 tmpfs /data/local/tmpfs
总的来讲,local volume本地卷目前不支持动态供给,还没法真正推广使用,但能够用来解决一些特定问题。
Ref:
参考文档:
https://kubernetes.io/blog/2019/04/04/kubernetes-1.14-local-persistent-volumes-ga/
https://ieevee.com/tech/2019/01/17/local-volume.html