K8s 本地储存

时间:2024-04-03 22:04:58

 

1. 基本概念

kubernetes从1.10版本开始支持local volume(本地卷)可以充分利用本地快速SSD,从而获取比remote volume(如cephfs、RBD)更好的性能.

下面两种类型应用适合使用local volume。

数据缓存,应用可以就近访问数据,快速处理。

分布式存储系统,如分布式数据库Cassandra ,分布式文件系统ceph/gluster

2.创建StorageClass

手动创建local-pv 需要创建storageclass, pv. 如果你的资源申请pv使用使用的persistvolumetemplate, pvc 会自动创建.

 

首先需要有一个名为local-volume的sc。

kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: local-volume

provisioner: kubernetes.io/no-provisioner

volumeBindingMode: WaitForFirstConsumer

sc的provisioner是 kubernetes.io/no-provisioner

volumeBindingMode必须是WaitForFirstConsumer(延迟绑定策略),否则就无法绑定localPV。

2.1 PVC绑定策略

K8s 本地储存
PersistentVolume Controller会遍历现有PV和可以动态创建的StorageClass,找到满足条件(访问权限、容量等)进行绑定,Scheduler基于资源要求找到匹配的节点,以过滤和打分的方式选出“匹配度”最高的Node。流程如下图所示:

PVC绑定在Pod调度之前,PersistentVolume Controller不会等待Scheduler调度结果,在Statefulset中PVC先于Pod创建,所以PVC/PV绑定可能完成在Pod调度之前。

Scheduler不感知卷的“位置”,仅考虑存储容量、访问权限、存储类型、还有第三方CloudProvider上的限制(譬如在AWS、GCE、Aure上使用Disk数量的限制)

当应用对卷的“位置”有要求,比如使用localPV,可能出现Pod被Scheduler调度到NodeB,但PersistentVolume Controller绑定了在NodeD上的本地卷,以致PV和Pod不在一个节点,如下图所示:

K8s 本地储存

 

2.2 延时绑定策略

为了解决此问题,k8s调度器采用一下策略:

  1. 预分配使用本地卷的PV
  2. 通过NodeAffinity方式标记PV位置
  3. 创建StorageClass,通过StorageClass间接标记PVC的延时绑定
  4. 标记该PVC需要延后到Node选择出来之后再绑定

依照原有流程创建PVC和Pod,但对于需要延时绑定的PVC,PersistentVolume Controller不再参与。通过PVC.StorageClass,PersistentVolume Controller得知PVC是否需要延时绑定。

PV不立即绑定PVC,而是直到有Pod需要用PVC的时候才绑定。调度器会在调度时综合考虑选择合适的local PV,这样就不会导致跟Pod资源设置,selectors,affinity and anti-affinity策略等产生冲突。如果PVC先跟local PV绑定了,由于local PV是跟node绑定的,这样selectors,affinity等等就基本没用了,所以更好的做法是先根据调度策略选择node,然后再绑定local PV。

K8s 本地储存

3.创建localPV

 

创建以下yaml文件

persistentVolumeReclaimPolicy: Retain表示pv删除是不删除磁盘上存储的数据,如果设置为Delete表示当pv删除时清理磁盘上数据

其中nodeAffinity表示节点亲和性,即localPV磁盘所在机器

path: /mnt/test表示挂载目录,默认格式为filesystem

apiVersion: v1

kind: PersistentVolume

metadata:

  name: example-local-pv

spec:

  capacity:

    storage: 5Gi

  accessModes:

  - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

  storageClassName: local-volume

  local:

    path: /mnt/test

  nodeAffinity:

    required:

      nodeSelectorTerms:

      - matchExpressions:

        - key: kubernetes.io/hostname

          operator: In

          values:

          - 192.168.198.138

4.挂载目录

这里使用实际存储是192.168.198.138 上的/data/local/vol1 目录.

所以要手动建立这些目录给pv挂载使用,可以使用下面的方式使用挂载的方式挂载到目录.

#!/bin/bash

# Usage: sudo loopm ount file size m ount-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

 

lpfs.sh  /data0/k8spv/data0 5G /mnt/test

5.绑定pv,使用pvc

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:least

      volumeMounts:

      - mountPath: "/usr/share/nginx/html"

        name: mypd

  volumes:

    - name: mypd

      persistentVolumeClaim:

        claimName: myclaim

 

6.如何在生产环境中使用localPV

6.1 磁盘挂载

volumeMode可以是FileSystem(Default)和Block。

一般使用节点上local volume的初始化需要完成如下操作(local disk需要pre-partitioned, formatted, and mounted. 共享存储对应的Directories也需要pre-created)。在生产环境中,一般使用独立的磁盘作为应用的挂载目录,有利于提升性能。

执行如下命令查看磁盘分区

fdisk /dev/sda

在提示输出命令时输入p查看分区,执行结果如下:

K8s 本地储存

 

Blocks表示磁盘大小。

使用local volume时,磁盘格式仅支持xfs和ext4,不支持其他文件系统,做分区时需要注意。使用如下命令查看文件系统格式

parted –l

K8s 本地储存

 

6.2 创建pvc

与上面案例类似,我们可以创建本地卷为容器提供存储服务

kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: local-storage

provisioner: kubernetes.io/no-provisioner

volumeBindingMode: WaitForFirstConsumer

---

kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: local-storage1

provisioner: kubernetes.io/no-provisioner

volumeBindingMode: WaitForFirstConsumer

---

apiVersion: v1

kind: PersistentVolume

metadata:

  name: local-pv-sda1

spec:

  capacity:

    storage: 6Gi

  accessModes:

  - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

  storageClassName: local-storage1

  local:

    path: /dev/sda5

  nodeAffinity:

    required:

      nodeSelectorTerms:

      - matchExpressions:

        - key: kubernetes.io/hostname

          operator: In

          values:

          - 192.168.198.139

---

apiVersion: v1

kind: PersistentVolume

metadata:

  name: local-pv-sda

spec:

  capacity:

    storage: 10Gi

  accessModes:

  - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

  storageClassName: local-storage

  local:

    path: /dev/sda5

  nodeAffinity:

    required:

      nodeSelectorTerms:

      - matchExpressions:

        - key: kubernetes.io/hostname

          operator: In

          values:

          - 192.168.198.139

---

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: local-pvc-sda

spec:

  accessModes:

  - ReadWriteOnce

  storageClassName: local-storage

  resources:

    requests:

      storage: 5Gi

---

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: local-pvc-sda1

spec:

  accessModes:

  - ReadWriteOnce

  storageClassName: local-storage1

  resources:

    requests:

      storage: 5Gi

通过yaml文件可以看到,我创建了两套pv、pvc和sc,但是pv绑定的磁盘分区都是同一个,一般生产环境中不推荐此种方式,此处为了演示一个deployment可以绑定多个磁盘。

如果一个deployment绑定的两个pvc不是同一个node上的磁盘,会导致调度失败。

6.3 创建hadoop节点绑定多个localpv

 

---

apiVersion: v1

kind: Service

metadata:

  name: bd-master-via

  labels:

    app: bd-master

spec:

  type: NodePort

  ports:

  - port: 50070

    name: hdfsweb

    nodePort: 30070

  - port: 9000

    name: hdfs

    nodePort: 30000

  - port: 50010

    name: hdfs2

    nodePort: 30010

  - port: 50020

    name: hdfs3

    nodePort: 30020

  - port: 8088

    name: yarn

    nodePort: 32088

  selector:

    app: bd-master

---

apiVersion: v1

kind: Service

metadata:

  name: bd-master

  labels:

    app: bd-master

spec:

  clusterIP: None

  ports:

  - port: 9000

    name: hdfs

  - port: 50010

    name: hdfs2

  - port: 50020

    name: hdfs3

  - port: 50070

    name: hdfsweb

  - port: 50075

    name: hdfs5

  - port: 50090

    name: hdfs6

  - port: 31010

    name: hdfs7

  - port: 8020

    name: hdfs8

  - port: 10020

    name: mapred2   

  - port: 19888

    name: jobhistory

  - port: 8088

    name: yarn

  - port: 8030

    name: yarn1

  - port: 8031

    name: yarn2

  - port: 8032

    name: yarn3

  - port: 8033

    name: yarn4

  - port: 8040

    name: yarn5

  - port: 8042

    name: yarn6

  selector:

    app: bd-master

---

apiVersion: v1

kind: ConfigMap

metadata:

  name: hdp-cm

data:

  master: "bd-master"

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: bd-master

spec:

  selector:

    matchLabels:

      app: bd-master

  replicas: 1

  template:

    metadata:

      labels:

        app: bd-master

    spec:

      hostname: bd-master

      containers:

      - name: bd-master

        image: bdhadoophive64:0.0.3

        securityContext:

          privileged: true

        ports:

        - containerPort: 9000

          name: hdfs

        - containerPort: 50010

          name: hdfs2

        - containerPort: 50020

          name: hdfs3

        - containerPort: 50070

          name: hdfsweb

        - containerPort: 50075

          name: hdfs5

        - containerPort: 50090

          name: hdfs6

        - containerPort: 31010

          name: hdfs7

        - containerPort: 8020

          name: hdfs8

        - containerPort: 10020

          name: mapred2

        - containerPort: 19888

          name: jobhistory

        - containerPort: 8088

          name: yarn

        - containerPort: 8030

          name: yarn1

        - containerPort: 8031

          name: yarn2

        - containerPort: 8032

          name: yarn3

        - containerPort: 8033

          name: yarn4

        - containerPort: 8040

          name: yarn5

        - containerPort: 8042

          name: yarn6    

        env:

        - name: MASTER

          valueFrom:

            configMapKeyRef:

                name: hdp-cm

                key: master

        volumeMounts:

        - name: logs

          mountPath: /hadoop/logs    

        - name: data1

          mountPath: /hadoop/data1

        - name: data2

          mountPath: /hadoop/data2

        - name: data3

          mountPath: /hadoop/data3

        - name: data4

          mountPath: /hadoop/data4

        - name: data5

          mountPath: /hadoop/data5

        #- name: data6

        #  mountPath: /hadoop/data6

        #- name: data7

        #  mountPath: /hadoop/data7

        #- name: data8

        #  mountPath: /hadoop/data8

        #- name: data9

        #  mountPath: /hadoop/data9

        #- name: data10

        #  mountPath: /hadoop/data10           

        - name: namenode

          mountPath: /hadoop/namenode  

        - name: opendir

          mountPath: /root/opendir

        - mountPath: /usr/local/hadoop/etc/hadoop/

          name: config         

      volumes:

      - name: logs

        hostPath:

          path: /data1/bd-master/logs

      - name: data1

        persistentVolumeClaim:

          claimName: local-pvc-sda

      - name: data2

        persistentVolumeClaim:

          claimName: local-pvc-sda1

      - name: data3

        hostPath:

          path: /data3/bd-master/datanode

      - name: data4

        hostPath:

          path: /data4/bd-master/datanode

      - name: data5

        hostPath:

          path: /data5/bd-master/datanode

      #- name: data6

      #  hostPath:

      #    path: /data6/bd-master/datanode

      #- name: data7

      #  hostPath:

      #    path: /data7/bd-master/datanode

      #- name: data8

      #  hostPath:

      #    path: /data8/bd-master/datanode   

      #- name: data9

      #  hostPath:

      #    path: /data9/bd-master/datanode

      #- name: data10

      #  hostPath:

      #    path: /data10/bd-master/datanode             

      - name: namenode

        hostPath:

          path: /data1/bd-master/namenode        

      - name: opendir

        hostPath:

          path: /usr/local/opendir

      - hostPath:

          path: /home/hadoop/

        name: config

执行如下命令创建hadoopmaster节点

Kubectl create –f  bd-master.yaml

查看节点运行状态

K8s 本地储存

 

7. localPV的局限

7.1 无法动态创建PV

Local PV 的本地持久存储允许直接使用节点上的一块磁盘、一个分区或者一个目录作为持久卷的存储后端,但暂时还不提供动态配置支持,必须先把PV 创建好。

PV需要直接挂载本地磁盘或者分区,针对不同型号/不同环境的服务器分区与磁盘io性能并不一致,需要手动配置。

7.2 稳定性受Node本身影响

由于localPV和磁盘强绑定关系以及nodeAffinity影响,导致如果pod所在物理机出现问题或者磁盘出现问题,会直接导致服务不可用,且无法重新调度服务。

目前解决方案:

  1. Node不可用后,等待阈值超时,确定Node无法恢复
  2. 确认Node不可恢复,删除PVC,通过解除PVC和PV绑定的方式,解除Pod和Node的绑定
  3. 使用动态卷快照/克隆卷重新创建PV绑定PVC,或者使用主从模式,重新创建PV,PVC进行数据同步
  4. scheduler将Pod调度到其他可用Node,PVC重新绑定到可用Node的PV

 

7.3 资源利用率降低

一旦本地存储使用完,即使CPU、Memory剩余再多,该节点也无法提供服务。

目前解决方案:

需要做好本地存储规划,譬如每个节点Volume的数量、容量等,使用存储时需要把磁盘规划好,在一个大规模运行的环境,存在落地难度。

8.localPV与hostPath比较

相比 hostPath 卷,local 卷可以以持久和可移植的方式使用,而无需手动将 Pod 调度到节点,因为系统通过查看 PersistentVolume 所属节点的亲和性配置,就能了解卷的节点约束.

 

Both use local disks available on a machine. But! Imagine you have a cluster of three machines and have a Deployment with a replica of 1. If your pod is scheduled on node A, writes to a host path, then the pod is destroyed. At this point the scheduler will need to create a new pod, and this pod might be scheduled to node C which doesn't have the data.

Local volumes fix this by ensuring a pod is scheduled to the machine where the data exists.

 

两者都使用计算机上可用的本地磁盘。但是!假设你有一个由三台机器组成的集群,并且有一个副本为1的Deployment。如果你的pod被调度在到节点A上,使用host path,然后pod被销毁。这时调度器会创建一个新的pod,这个pod可能被调度到没有数据的节点C。

localPV通过确保将pod调度到数据所在的计算机来解决此问题。

 

 

hostPath

localPV

支持类型

Directory、File、Socket、CharDevice、BlockDevice

Filesystem、BlockDevice

调度策略

不提供node亲和性支持,可以通过pod配置亲和性

根据PV节点亲和性调度

储存管理

PV、PVC和StorageClass完整实现

PV、PVC和StorageClass,对sc只支持延时绑定

 

9.未来功能

  • 本地块设备直接作为卷源,使用时具有分区和fs格式(当前需要预分区格式化和挂载)
  • 动态配置共享本地持久存储
  • LocalPV健康监测,污染和耐受性
  • InlinePV使用专用本地磁盘作为临时存储

项目github地址

https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner#future-features