PV&PVC

概念

PersistentVolume(PV,持久卷):对存储抽象实现,使得存储作为集群中的资源。PV 由管理员进行创建和配置,它和具体的底层共享存储技术的实现方式有关,比如 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接。PV 作为存储资源,主要包括存储能力、访问模式、存储类型、回收策略等信息。

PersistentVolumeClaim(PVC,持久化卷声明):PVC 是用户存储的一种声明,消费 PV 的资源。PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式。

工作方式:
Pod申请PVC作为卷来使用,集群通过PVC查找绑定的PV,并Mount给Pod。

状态说明:
一个 PV 的生命周期中,可能会处于4中不同的阶段:

  • Available(可用):还未被任何 PVC 绑定的可用状态
  • Bound(已绑定):PVC 已经被 PVC 绑定
  • Released(已释放):PVC 被删除,PV 进入释放状态,等待回收处理
  • Failed(失败): PV 执行自动清理回收策略失败

基于NFS的PV环境实现

NFS 环境实现

  1. #服务端上操作:192.168.10.11
  2. $ yum -y install nfs-utils rpcbind
  3. $ mkdir -p /nfsdata/k8s && chmod 755 /nfsdata/k8s
  4. $ echo '/nfsdata/k8s *(insecure,rw,sync,no_root_squash)'>>/etc/exports
  5. $ systemctl enable rpcbind && systemctl start rpcbind
  6. $ systemctl enable nfs && systemctl start nfs
  7. #客户端挂载使用
  8. $ yum -y install nfs-utils rpcbind #所有要使用nfs作为存储资源的机器节点都需要安装
  9. $ mkdir /nfsdata
  10. $ mount -t nfs 192.168.10.11:/nfsdata/k8s /nfsdata

pv1-demo.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name:  pv1
spec:
  capacity: 
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /nfsdata/k8s
    server: 192.168.10.11
$ kubectl apply -f pv1.yaml 

$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Available                                   5s

# 可以看到 pv1 已经创建成功了,状态是 Available,表示 pv1 就绪,可以被 PVC 申请。

选项配置:

  • capacity:存储能力, 存储空间的设置,即 storage=1Gi
  • accessModes:访问模式,用于描述用户对存储资源的访问权限,包括:
    • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
    • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
    • ReadWriteMany(RWX):读写权限,可以被多个节点挂载
  • persistentVolumeReclaimPolicy:回收策略,包括:
    • Retain(保留)- 保留数据,需要手工清理数据
    • Recycle(回收)- 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
    • Delete(删除)- 与 PV 相连的后端存储完成 volume 删除操作,常见于云服务商的存储服务

创建 PVC

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc1-nfs
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Available                                   20m

$ kubectl apply -f pvc1.yaml

$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Bound    default/pvc-nfs                           21m

使用 PVC

根目录挂载

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-pvc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nfs-pvc
  template:
    metadata:
      labels:
        app: nfs-pvc
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
      volumes:
      - name: www
        persistentVolumeClaim:
          claimName: pvc1-nfs

---

apiVersion: v1
kind: Service
metadata:
  name: nfs-pvc
  labels:
    app: nfs-pvc
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: web
  selector:
    app: nfs-pvc
$ kubectl apply -f nfs-pvc-deploy.yaml

$ kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        7d1h
nfs-pvc      NodePort    10.101.150.196   <none>        80:32481/TCP   6m58s

访问
image.png

分析
根据资源清单内容描述,服务使用 nginx 镜像,将 pvc1-nfs 挂载到容器的 /usr/share/nginx/html 目录,因为 /nfsdata/k8s 目录下没有任何数据,故出现 403。

添加内容后再次访问:
$ echo “

Hello Kubernetes~

“ > /nfsdata/k8s/index.html

image.png

子目录挂载

以上操作容器中的数据是直接放到共享数据目录根目录下面,当有新的 nginx 容器也做数据目录挂载就会有冲突。可以使用 subPath 属性解决。

更新 (nfs-pvc-deploy.yaml) 文件部分内容:

...
volumeMounts:
- name: www
  subPath: nginxpvc-test
  mountPath: /usr/share/nginx/html
...
$ kubectl apply -f nfs-pvc-deploy.yaml

$ ls /nfsdata/k8s/
index.html  ngxpvc-test

#目录ngxpvc-test是自动生成的容器根目录

当把 deploy 资源删除后,所有 ng 容器将被删除,存储目录下数据会保留,实现数据持久化。

StorageClass

概念

StorageClass 可以实现 PVC 自动创建并绑定 PV 。使用 StorageClass,要安装对应的自动配置程序(Provisioner),比如存储后端是 nfs,就要使用到 nfs-client 自动配置程序,这个程序使用已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动创建 PV。

自动创建的 PV 以 ${namespace}-${pvcName}-${pvName} 命名格式创建在 NFS 服务器上的共享数据目录中。当 PV 被回收后会以 archieved-${namespace}-${pvcName}-${pvName} 命名格式存在 NFS 服务器上。

自动分配PV流程:
image.png

基于NFS的SC实现

创建StorageClass

配置 Deployment(nfs-client.yaml)

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
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: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 192.168.10.11
            - name: NFS_PATH
              value: /nfsdata/k8s
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.10.11
            path: /nfsdata/k8s

创建 sa 并绑定对应权限 (nfs-client-sa.yaml)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - 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: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

创建StorageClass对象(nfs-client-class.yaml)

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: course-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
$ kubectl create -f nfs-client.yaml
$ kubectl create -f nfs-client-sa.yaml
$ kubectl create -f nfs-client-class.yaml

$ kubectl get po
NAME                                    READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-ccf554b8-x2l5p   1/1     Running   0          5m22s

$ kubectl get sc
NAME                 PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
course-nfs-storage   fuseim.pri/ifs   Delete          Immediate           false                  4m49s

动态PV

StorageClass 资源对象已创建成功,现在只需定义 PVC 对象,就会自动产生对应的 PV 。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

test-pvc.yaml 中声明了一个 PVC 对象,但直接创建 PVC 不能自动绑定上合适的 PV 对象,因为目前是没有合适的 PV。

利用 StorageClass 对象自动创建一个合适的 PV,解决方式有三种:

方式一:在 PVC 对象中用 annotations 属性来声明 StorageClass 对象的标识

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: "course-nfs-storage"  # 声明使用
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

方式二:设置 course-nfs-storage 的 StorageClass 为 Kubernetes 默认存储后端,会影响系统的默认行为,不推荐使用

# 用 kubectl patch 命令来更新
$ kubectl patch storageclass course-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

方式三:在 spec 中声明 storageClassName ,推荐使用

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
  storageClassName: course-nfs-storage  # 声明使用
$ kubectl apply -f test-pvc.yaml

$ kubectl get pvc
NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS         AGE                              5h34m
test-pvc   Bound    pvc-a0fe83f0-6122-44d2-801e-cd0f493c5a9c   1Mi        RWX            course-nfs-storage   3s

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS         REASON   AGE
pvc-a0fe83f0-6122-44d2-801e-cd0f493c5a9c   1Mi        RWX            Delete           Bound    default/test-pvc   course-nfs-storage            16s

Pod使用PVC

kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox
    imagePullPolicy: IfNotPresent
    command:
    - "/bin/sh"
    args:
    - "-c"
    - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: nfs-pvc
      mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-pvc

验证方式:
busybox 容器在 /mnt 目录下面新建一个 SUCCESS 的文件,然后把 /mnt 目录挂载到 test-pvc 上。应用资源文件后去 nfs 服务器的共享数据目录能查看到 SUCCESS 文件,则说明使用成功。

$ kubectl apply -f test-pod.yaml

$ ls /nfsdata/k8s/
default-test-pvc-pvc-a0fe83f0-6122-44d2-801e-cd0f493c5a9c

$ ls /nfsdata/k8s/default-test-pvc-pvc-a0fe83f0-6122-44d2-801e-cd0f493c5a9c/
SUCCESS

StatefulSet使用PVC

实际工作中使用 StorageClass 场景较多的是 StatefulSet 类型的服务。可以通过 volumeClaimTemplates 属性来使用 StorageClass 。volumeClaimTemplates 包含的内容是一个 PVC 对象模板。这种方式在 StatefulSet 类型的服务下使用得非常多。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nfs-web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nfs-web
  template:
    metadata:
      labels:
        app: nfs-web
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
      annotations:
        volume.beta.kubernetes.io/storage-class: course-nfs-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
$ kubectl  apply -f test-statefulset-nfs.yaml

$ kubectl get po
NAME                                    READY   STATUS      RESTARTS   AGE
nfs-client-provisioner-ccf554b8-x2l5p   1/1     Running     0          41m
nfs-web-0                               1/1     Running     0          17s
nfs-web-1                               1/1     Running     0          12s
nfs-web-2                               1/1     Running     0          7s

$ kubectl get pvc
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS         AGE
www-nfs-web-0   Bound    pvc-b22c2756-032d-4c6f-9472-b940a143b100   1Gi        RWO            course-nfs-storage   97s
www-nfs-web-1   Bound    pvc-ab2b4c12-4d97-40ec-9605-8dacfb514d76   1Gi        RWO            course-nfs-storage   92s
www-nfs-web-2   Bound    pvc-35532544-dcbc-46c7-acab-a06c0d287ca1   1Gi        RWO            course-nfs-storage   87s

$ ls /nfsdata/k8s/
...
default-www-nfs-web-2-pvc-35532544-dcbc-46c7-acab-a06c0d287ca1
default-www-nfs-web-0-pvc-b22c2756-032d-4c6f-9472-b940a143b100
default-www-nfs-web-1-pvc-ab2b4c12-4d97-40ec-9605-8dacfb514d76
...

基于CEPH的SC实现

CEPH环境

Ceph 安装及使用参考 http://docs.ceph.org.cn/start/intro/
单点快速安装 https://blog.csdn.net/h106140873/article/details/90201379

# CephFS需要使用两个Pool来分别存储数据和元数据
ceph osd pool create cephfs_data 128
ceph osd pool create cephfs_meta 128
ceph osd lspools

# 创建一个CephFS
ceph fs new cephfs cephfs_meta cephfs_data

# 查看
ceph fs ls

# ceph auth get-key client.admin
client.admin
        key: AQBPTstgc078NBAA78D1/KABglIZHKh7+G2X8w==
# 挂载
$ mount -t ceph 172.21.51.55:6789:/ /mnt/cephfs -o name=admin,secret=AQBPTstgc078NBAA78D1/KABglIZHKh7+G2X8w==

动态PV

apiVersion: v1
kind: ServiceAccount
metadata:
  name: cephfs-provisioner
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cephfs-provisioner
rules:
  - 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"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "delete"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cephfs-provisioner
subjects:
  - kind: ServiceAccount
    name: cephfs-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cephfs-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cephfs-provisioner
  namespace: kube-system
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cephfs-provisioner
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cephfs-provisioner
subjects:
- kind: ServiceAccount
  name: cephfs-provisioner
  namespace: kube-system

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cephfs-provisioner
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cephfs-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: cephfs-provisioner
    spec:
      containers:
      - name: cephfs-provisioner
        image: "quay.io/external_storage/cephfs-provisioner:latest"
        env:
        - name: PROVISIONER_NAME
          value: ceph.com/cephfs
        imagePullPolicy: IfNotPresent
        command:
        - "/usr/local/bin/cephfs-provisioner"
        args:
        - "-id=cephfs-provisioner-1"
        - "-disable-ceph-namespace-isolation=true"
      serviceAccount: cephfs-provisioner
# 在ceph monitor机器中查看admin账户的key
$ ceph auth list
$ ceph auth get-key client.admin
AQBPTstgc078NBAA78D1/KABglIZHKh7+G2X8w==
# 创建secret
$ echo -n AQBPTstgc078NBAA78D1/KABglIZHKh7+G2X8w==|base64
QVFCUFRzdGdjMDc4TkJBQTc4RDEvS0FCZ2xJWkhLaDcrRzJYOHc9PQ==

$ cat ceph-admin-secret.yaml
apiVersion: v1
data:
  key: QVFCUFRzdGdjMDc4TkJBQTc4RDEvS0FCZ2xJWkhLaDcrRzJYOHc9PQ==
kind: Secret
metadata:
  name: ceph-admin-secret
  namespace: kube-system
type: Opaque
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: dynamic-cephfs
provisioner: ceph.com/cephfs
parameters:
    monitors: 172.21.51.55:6789
    adminId: admin
    adminSecretName: ceph-admin-secret
    adminSecretNamespace: "kube-system"
    claimRoot: /volumes/kubernetes

Pod使用PVC

创建pvc

$ cat cephfs-pvc-test.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cephfs-claim
spec:
  accessModes:     
    - ReadWriteOnce
  storageClassName: dynamic-cephfs
  resources:
    requests:
      storage: 2Gi

$ kubectl create -f cephfs-pvc-test.yaml

$ kubectl get pv
pvc-2abe427e-7568-442d-939f-2c273695c3db   2Gi        RWO            Delete           Bound      default/cephfs-claim   dynamic-cephfs            1s

创建Pod使用pvc

$ cat test-pvc-cephfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    name: nginx-pod
spec:
  containers:
  - name: nginx-pod
    image: nginx:alpine
    ports:
    - name: web
      containerPort: 80
    volumeMounts:
    - name: cephfs
      mountPath: /usr/share/nginx/html
  volumes:
  - name: cephfs
    persistentVolumeClaim:
      claimName: cephfs-claim

$ kubectl create -f test-pvc-cephfs.yaml

查看nginx-pod的挂载盘

# Pod 挂载盘通常格式
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>

$ df -TH
/var/lib/kubelet/pods/61ba43c5-d2e9-4274-ac8c-008854e4fa8e/volumes/kubernetes.io~cephfs/pvc-2abe427e-7568-442d-939f-2c273695c3db/

$ findmnt /var/lib/kubelet/pods/61ba43c5-d2e9-4274-ac8c-008854e4fa8e/volumes/kubernetes.io~cephfs/pvc-2abe427e-7568-442d-939f-2c273695c3db/
172.21.51.55:6789:/volumes/kubernetes/kubernetes/kubernetes-dynamic-pvc-ffe3d84d-c433-11ea-b347-6acc3cf3c15f