Kubernetes Local Persistent Volume使用

hostPath volume存在的问题

过去我们经常会通过hostPath volume让Pod能够使用本地存储,将Node文件系统中的文件或者目录挂载到容器内,但是hostPath volume的使用是很难受的,并不适合在生产环境中使用,但为什么说不适合在生产环境中使用呢?

  • 由于集群内每个节点的差异化,要使用hostPath Volume,我们需要通过NodeSelector等方式进行精确调度,这种事情多了,你就会不耐烦了。
  • 注意DirectoryOrCreateFileOrCreate两种类型的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 时需要按如下流程执行操作:

  1. 删除使用这个 PV 的 Pod;
  2. 从宿主机移除本地磁盘(比如,umount 它);
  3. 删除 PVC;
  4. 删除 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

参考文献

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值