zoukankan      html  css  js  c++  java
  • kubernetes支持local volume

    目录

    • local volume
    • 创建一个storage class
    • 静态创建PV
    • 使用local volume PV
    • 动态创建PV

    local volume

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

    在local volume出现之前,statefulsets也可以利用本地SSD,方法是配置hostPath,并通过nodeSelector或者nodeAffinity绑定到具体node上。但hostPath的问题是,管理员需要手动管理集群各个node的目录,不太方便。

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

    • 数据缓存,应用可以就近访问数据,快速处理。
    • 分布式存储系统,如分布式数据库Cassandra ,分布式文件系统ceph/gluster

    下面会先以手动方式创建PV、PVC、Pod的方式,介绍如何使用local volume,然后再介绍external storage提供的半自动方式,最后介绍社区的一些发展。

    创建一个storage class

    首先需要有一个名为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

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

    静态创建PV

    通过kubectl命令,静态创建一个5GiB的PV;该PV使用node ubuntu-1的 /data/local/vol1 目录;该PV的sc为local-volume。

    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

    使用local volume PV

    接下来创建一个关联 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了。

    动态创建PV

    手工管理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

  • 相关阅读:
    Linux下汇编语言学习笔记71 ---
    Linux下汇编语言学习笔记70 ---
    Linux下汇编语言学习笔记67 ---
    Linux下汇编语言学习笔记66 ---
    Linux下汇编语言学习笔记65 ---
    Linux下汇编语言学习笔记64 ---
    尽量用类型化的常量替代预处理器的 #DEFINE 方法
    尽量用类型化的常量替代预处理器的 #DEFINE 方法
    UILabel How to set background image
    UILabel How to set background image
  • 原文地址:https://www.cnblogs.com/davygeek/p/10944208.html
Copyright © 2011-2022 走看看