命令记不住

登陆

k8s 存储 pv 和 pvc 介绍

14.Kubernetes 存储体系PV和PVC

1.存储控制器

  • PV Controller: 负责 PV/PVC 的绑定、生命周期管理,并根据需求进行数据卷的 Provision/Delete 操作;
  • AD Controller: 负责存储设备的 Attach/Detach 操作,将设备挂载到目标节点;
  • Volume Manager: 管理卷的 Mount/Unmount 操作、卷设备的格式化以及挂载到一些公用目录上的操作;
  • Volume Plugins:它主要是对上面所有挂载功能的实现。

PV Controller、AD Controller、Volume Manager 主要是进行操作的调用,而具体操作则是由 Volume Plugins 实现的。

  • Scheduler: 实现对 Pod 的调度能力,会根据一些存储相关的的定义去做一些存储相关的调度。

2.PV Controller

  • Persistent Volume (PV): 持久化存储卷,详细定义了预挂载存储空间的各项参数。

例如,我们去挂载一个远端的 NAS 的时候,这个 NAS 的具体参数就要定义在 PV 中。一个 PV 是没有 NameSpace 限制的,它一般由 Admin 来创建与维护;

  • Persistent Volume Claim (PVC):持久化存储声明。

它是用户所使用的存储接口,对存储细节无感知,主要是定义一些基本存储的 Size、AccessMode 这些参数在里面,并且它是属于某个 NameSpace 内部的。

  • StorageClass:存储类。

一个动态存储卷会按照 StorageClass 所定义的模板来创建一个 PV,其中定义了创建模板所需要的一些参数和创建 PV 的一个 Provisioner(就是由谁去创建的)

PV Controller 的主要任务就是完成 PV、PVC 的生命周期管理,比如创建、删除 PV 对象,负责 PV、PVC 的状态迁移;另一个任务就是绑定 PVC 与 PV 对象,一个 PVC 必须和一个 PV 绑定后才能被应用使用,它们是一一绑定的,一个 PV 只能被一个 PVC 绑定,反之亦然。

创建好一个 PV 以后,我们就处于一个 Available 的状态,当一个 PVC 和一个 PV 绑定的时候,这个 PV 就进入了 Bound 的状态,此时如果我们把 PVC 删掉,Bound 状态的 PV 就会进入 Released 的状态。

一个 Released 状态的 PV 会根据自己定义的 ReclaimPolicy 字段来决定自己是进入一个 Available 的状态还是进入一个 Deleted 的状态。如果 ReclaimPolicy 定义的是 "recycle" 类型,它会进入一个 Available 状态,如果转变失败,就会进入 Failed 的状态。

绑定条件
  • 首先它会检查 VolumeMode 这个标签,PV 与 PVC 的 VolumeMode 标签必须相匹配。VolumeMode 主要定义的是我们这个数据卷是文件系统 (FileSystem) 类型还是一个块 (Block) 类型;
  • 第二个部分是 LabelSelector。当 PVC 中定义了 LabelSelector 之后,我们就会选择那些有 Label 并且与 PVC 的 LabelSelector 相匹配的 PV 进行绑定;
  • 第三个部分是 StorageClassName 的检查。如果 PVC 中定义了一个 StorageClassName,则必须有此相同类名的 PV 才可以被筛选中。

这里再具体解释一下 StorageClassName 这个标签,该标签的目的就是说,当一个 PVC 找不到相应的 PV 时,我们就会用该标签所指定的 StorageClass 去做一个动态创建 PV 的操作,同时它也是一个绑定条件,当存在一个满足该条件的 PV 时,就会直接使用现有的 PV,而不再去动态创建。

  • 第四个部分是 AccessMode 检查。

AccessMode 就是平时我们在 PVC 中定义的如 "ReadWriteOnce"、"RearWriteMany" 这样的标签。该绑定条件就是要求 PVC 和 PV 必须有匹配的 AccessMode,即 PVC 所需求的 AccessMode 类型,PV 必须具有。

3.volumes介绍

Pod Volumes 的常见类型:

  1. 本地存储,常用的有 emptydir/hostpath;
  2. 网络存储:网络存储当前的实现方式有两种,一种是 in-tree,它的实现的代码是放在 K8s 代码仓库中的,随着k8s对存储类型支持的增多,这种方式会给k8s本身的维护和发展带来很大的负担;而第二种实现方式是 out-of-tree,它的实现其实是给 K8s 本身解耦的,通过抽象接口将不同存储的driver实现从k8s代码仓库中剥离,因此out-of-tree 是后面社区主推的一种实现网络存储插件的方式;
  3. Projected Volumes:它其实是将一些配置信息,如 secret/configmap 用卷的形式挂载在容器中,让容器中的程序可以通过POSIX接口来访问配置数据;
  4. PV 与 PVC,K8s 集群中的管控组件,会结合 PVC 和 StorageClass 的信息动态,生成用户所需要的存储(PV),将 PVC 和 PV 进行绑定后,pod 就可以使用 PV 了。通过 StorageClass 配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV 对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。

emptyDir、hostPath 都是本地存储,它们之间有什么细微的差别呢?emptyDir 其实是在 pod 创建的过程中会临时创建的一个目录,这个目录随着 pod 删除也会被删除,里面的数据会被清空掉;hostPath 顾名思义,其实就是宿主机上的一个路径,在 pod 删除之后,这个目录还是存在的,它的数据也不会被丢失。这就是它们两者之间一个细微的差别。

4.pod volumes语法

Pod Volumes的使用

spec:
containers:
  - name: container-1
    volumeMounts:
      - name: cache-volumes
      mountpath: /cache #容器内挂载路径
      subPath: cache1 #在cache-volume建立子目录cache1
      readOnly :true #只读挂载
 volumes:
  - name: cache-volume
    emptyDir: {}   #宿主机上路径

volumeMounts字段里面指定的name其实就是它使用的哪个卷,mountPath就是容器中的挂载路径

5.静态pv

PersistentVolume(PV):对存储资源创建和使用的抽象,使得存储作为集群中的资源管理

PersistentVolumeClaim(PVC):让用户不需要关心具体的Volume实现细节

静态 PV 的话,首先是由管理员来创建的,管理员我们这里以 NAS,就是阿里云文件存储为例。我需要先在阿里云的文件存储控制台上去创建 NAS 存储,然后把 NAS 存储的相关信息要填到 PV 对象中,这个 PV 对象预创建出来后,用户可以通过 PVC 来声明自己的存储需求,然后再去创建 pod。创建 pod 还是通过我们刚才讲解的字段把存储挂载到某一个容器中的某一个挂载点下面。

capacity,即创建的这个存储的大小

accessModes,创建出来的这个存储它的访问方式

ReclaimPolicy 的意思就是:这块存储在被使用后,等它的使用方 pod 以及 PVC 被删除之后,这个 PV 是应该被删掉还是被保留呢?其实就是PV的回收策略

当用户在提交 PVC 的时候,K8s 集群相关的组件就会把 PV 的 PVC bound 到一起。之后,用户在提交 pod yaml 的时候,可以在卷里面写上 PVC声明,在 PVC声明里面可以通过 claimName 来声明要用哪个 PVC。

缺点:维护成本高

6.动态Pv

动态 Provisioning ,系统管理员不再预分配 PV,而只是创建一个模板文件。

在StorageClass里面,我们需要填的重要信息:第一个是 provisioner,provisioner 是什么?它其实就是说我当时创建 PV 和对应的存储的时候,应该用哪个存储插件来去创建。

这些参数是通过k8s创建存储的时候,需要指定的一些细节参数。对于这些参数,用户是不需要关心的,像这里 regionld、zoneld、fsType 和它的类型。ReclaimPolicy跟我们刚才讲解的 PV 里的意思是一样的,就是说动态创建出来的这块 PV,当使用方使用结束、Pod 及 PVC 被删除后,这块 PV 应该怎么处理,我们这个地方写的是 delete,意思就是说当使用方 pod 和 PVC 被删除之后,这个 PV 也会被删除掉。

PVC 的文件里存储的大小、访问模式是不变的。现在需要新加一个字段,叫 StorageClassName,它的意思是指定动态创建PV的模板文件的名字,这里StorageClassName填的就是上面声明的csi-disk。

在提交完 PVC之后,K8s 集群中的相关组件就会根据 PVC 以及对应的 StorageClass 动态生成这块 PV 给这个 PVC 做一个绑定,之后用户在提交自己的 yaml 时,用法和接下来的流程和前面的静态使用方式是一样的,通过 PVC 找到我们动态创建的 PV,然后把它挂载到相应的容器中就可以使用了

1.创建动态PV步骤

class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
 app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
 type: Recreate
selector:
 matchLabels:
   app: nfs-client-provisioner
template:
 metadata:
   labels:
     app: nfs-client-provisioner
 spec:
   serviceAccountName: nfs-client-provisioner
   containers:
        - name: nfs-client-provisioner
          image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: 192.168.122.71
            - name: NFS_PATH
              value: /data/5
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.122.71 
            path: /data/5

rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

查看状体

#kubectl get storageclass
NAME                  PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  4m30s
#kubectl get pods
nfs-client-provisioner-f98b788f8-mfbjg   1/1     Running   0          12m

创建一个Pod

apiVersion: v1
kind: Pod
metadata:
  name: nginx-temple
spec:
  containers:
    - name: nginxr-temple
      image: nginx:latest
      ports:
      - containerPort: 80
      volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumes:
    - name: www
      persistentVolumeClaim:
        claimName: my-pvc-temple

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc-temple
spec:
  storageClassName: "managed-nfs-storage" 
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 11Gi
# 指定上面定义的storageClassName

查看动态创建的资源

#kubectl get pvc
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
my-pvc-temple   Bound    pvc-e8330c1d-c895-4533-bfed-ee66e9d883a1   11Gi       RWX            managed-nfs-storage   35s

#kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                   STORAGECLASS          REASON   AGE
pvc-e8330c1d-c895-4533-bfed-ee66e9d883a1   11Gi       RWX            Delete           Bound       default/my-pvc-temple   managed-nfs-storage            40s
2.修改为归档存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "true"

查看删除pod之后的存储目录

#ll /data/5/
total 0
archived-default-my-pvc-temple-1-pvc-1a0922c0-d090-4f85-9043-940ee598c642
default-my-pvc-temple-pvc-e8330c1d-c895-4533-bfed-ee66e9d883a1

7.pv重要字段解析

Capacity:存储总空间
AccessModes: PV访问策略控制列表,必须同PVC的访问策略控制列表匹配才能绑定
ReadWriteOnce 只允许单node访问
ReadOnlyMany允许多个node只读访问
ReadWriteMany允许多个node读写访问
一个PV可以设置多个访问策略,PVC与PV bound时,PV Controller会优先找到AccessMode列表最短并且匹配PVC AccessModes列表的PV集合,然后从该集合中找到Capacity最小且符合PVC size需求的PV对象
PersistentVolumeReclaimPollcy: PV被release之后回收再利用策略
Recycle 
Delete: volume被released之后直接delete,需要volume plugin支持
Retain: 默认策略,由系统管理员手动管理
StorageClassName: PVC可通过该字段找到相同值的PV,也可以通过该字段对应的storageclass从而动态provisioning新PV对象
NodeAffinity: 限制可以访问该volume的nodes,对使用该volume的pod的调度有影响
  • Capacity:这个很好理解,就是存储对象的大小;
  • **AccessModes:**也是用户需要关心的,就是说我使用这个 PV 的方式。它有三种使用方式。
    • 一种是单 node 读写访问;
    • 第二种是多个 node 只读访问,是常见的一种数据的共享方式;
    • 第三种是多个 node 上读写访问。

用户在提交 PVC 的时候,最重要的两个字段 —— Capacity 和 AccessModes。在提交 PVC 后,k8s 集群中的相关组件是如何去找到合适的 PV 呢?首先它是通过为 PV 建立的 AccessModes 索引找到所有能够满足用户的 PVC 里面的 AccessModes 要求的 PV list,然后根据PVC的 Capacity,StorageClassName, Label Selector 进一步筛选 PV,如果满足条件的 PV 有多个,选择 PV 的 size 最小的,accessmodes 列表最短的 PV,也即最小适合原则。

  • ReclaimPolicy:这个就是刚才提到的,我的用户方 PV 的 PVC 在删除之后,我的 PV 应该做如何处理?常见的有三种方式。
    • 第一种方式我们就不说了,现在 K8s 中已经不推荐使用了;
  • 第二种方式 delete,也就是说 PVC 被删除之后,PV 也会被删除;
  • 第三种方式 Retain,就是保留,保留之后,后面这个 PV 需要管理员来手动处理。
  • StorageClassName:StorageClassName 这个我们刚才说了,我们动态 Provisioning 时必须指定的一个字段,就是说我们要指定到底用哪一个模板文件来生成 PV ;
  • NodeAffinity:就是说我创建出来的 PV,它能被哪些 node 去挂载使用,其实是有限制的。然后通过 NodeAffinity 来声明对node的限制,这样其实对 使用该PV的pod调度也有限制,就是说 pod 必须要调度到这些能访问 PV 的 node 上,才能使用这块 PV。

8.PV生命周期

AccessModes(访问模式)

AccessModes是用来对PV进行访问模式的设置,用于描述用户对存储资源的访问权限,访问权限包括下面几种方式:

  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
  • ReadOnlyMany(ROX):只读模式,可以被多个节点挂载
  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载
RECLAIM POLICY(回收策略):

目前PV支持的策略有三种:

  • Retain(保留):保留数据,需要管理员手工清理数据
  • Recycle(回收):清除PV中的数据,效果相当于执行rm -fr /data/*
  • Delete(删除):与PV相连的后端存储同时删除
STATUS(状态)

一个PV的生命周期中,可能会处于4种不同的阶段

  • Available(可用):表示可用状态,还未被任何PVC绑定
  • Bound(已绑定):表示PV已经被PVC绑定
  • Released(已释放):PVC被删除,但是资源还未被集群重新声明
  • Failed(失败):表示该PV的自动回收失败

9.Volume挂载流程

Kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-disk
spec:
accessModes:
    - ReadWriteOnce
  storageClassName: alicloud-disk-ssd
  resource:
    requests:
      storage: 20Gi

1.用户第一步创建一个包含PVC的Pod

2.PV Controller会不断观察ApiServer,如果它发现一个PVC已经创建完成但是未绑定状态,它就会试图把一个PV和PVC绑定:

PV Controller 首先会在集群内部找到一个适合的 PV 进行绑定,如果未找到相应的 PV,就调用 Volume Plugin 去做 Provision。Provision 就是从远端上一个具体的存储介质创建一个 Volume,并且在集群中创建一个 PV 对象,然后将此 PV 和 PVC 进行绑定;

3.通过Scheduler完成一个调度功能:

4.如果有一个 Pod 调度到某个节点之后,它所定义的 PV 还没有被挂载(Attach),此时 AD Controller 就会调用 VolumePlugin,把远端的 Volume 挂载到目标节点中的设备上(如:/dev/vdb)

5.当 Volum Manager 发现一个 Pod 调度到自己的节点上并且 Volume 已经完成了挂载,它就会执行 mount 操作,将本地设备(也就是刚才得到的 /dev/vdb)挂载到 Pod 在节点上的一个子目录中。同时它也可能会做一些像格式化、是否挂载到 GlobalPath 等这样的附加操作。

6.绑定操作,就是将已经挂载到本地的 Volume 映射到容器中

10.PV和PVC使用流程

容器应用

apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
    - name: nginx1
      image: nginx:latest
      ports:
      - containerPort: 80
      volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumes:
    - name: www
      persistentVolumeClaim:
        claimName: my-pvc

卷需求模板

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

数据卷定义

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /data/1
    server: 192.168.122.71

11.存储volumeClaimTemplates

为StatefulSet 提供的存储

volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi

存储通过编号维护,解决每个pod都有独立的存储,持久化存储

推荐阅读