Kubernetes中的PV和PVC概和storage class

发布于 2021-01-05  907 次阅读


参考官方文档:k8s-卷

一、卷的概念

Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用程序带来一些问题。

问题一:当容器崩溃时文件丢失,kubelet 会重新启动容器, 但容器会以干净的状态重启。

问题二:会在同一 Pod 中运行多个容器并共享文件时出现。 Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。

1.1 卷的概念

k8s 的最小单位是pod,pod 是由一个或者多个容器组成的,最终卷还是作用于Container ,Docker 也有卷(Volume) 的概念,但是Docker 卷是磁盘上或者另外一个容器内的一个目录。 Docker 提供卷驱动程序,但是其功能非常有限。

Kubernetes 支持很多类型的卷。 Pod可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。

卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。

1.2 卷类型

数据卷大致被分为以下三种类型:

临时卷(Ephemeral Volume):,pod内的容器发生重启不会造成数据的丢失,但是当pod被重启后,emptyDir数据会丢失,也就是说emptyDir与pod的生命周期是一致的(hostPath,emptyDir等)

持久卷(Persistent Volume):真正的持久化,K8s将自己不擅长的存储实现,交给了别的擅长于存储的服务商或软件;——这也是我们以后学习的重点难点!(NFS,NAS,Ceph,GlusterFS,Iscsi,RDB,local等)

投射卷(Projected Volumes):类似于独立环境变量文件的方式,将文件的键值对挂载到容器内,或者讲一类重要token 密钥存放在容器这种往往是临时存储。(configmap,secret等)

二、持久卷

持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。

通常在企业中,Volume是由存储系统的运维人员来维护,他们来提供pv,pv具有持久性,生命周期独立于Pod;Pod则是由应用的开发人员来维护,如果要进行一卷挂载,那么就写一个pvc来消费pv就可以了,K8s会查找并提供满足条件的pv。

2.1 创建PV

我们这里以NFS 为例,我在一台192.168.232.50 安装安装NFS server,把192.168.232.50:/data-nfs 目录挂载到两个node 节点。

在192.168.232.50上安装nfs server

yum -y install nfs-nutils
mkdir /data-nfs/
echo "/data-nfs/ 192.168.232.0/24(insecure,rw,sync,no_root_squash)" > /etc/exports
systemctl enable --now rpcbind
systemctl enable --now nfs-server
exportfs -r
##exportfs 检查是否生效

在需要共享nfs server的机器上,安装NFS Client

#执行以下命令检查 nfs 服务器端是否有设置共享目录
# showmount -e $(nfs服务器的IP)
showmount -e 192.168.232.50
#执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /data
mkdir /data
mount 192.168.232.50:/data-nfs/ /data

node1 和node2 上我的node节点是两个,这个根据需求来选择

mkdir /data/pv-test01

mkdir /data/pv-test02

image-20221117101717448

[root@k8s-master01 ~]# cat pv-01.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-01
  labels:
    type: test
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /data-nfs/pv-test01
    server: 192.168.232.50
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-02
  labels:
    type: test
spec:
  capacity:
    storage: 50m
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /data-nfs/pv-test02
    server: 192.168.232.50

查看创建pv 并查看信息:

[root@k8s-master01 ~]# kubectl apply -f pv-01.yaml
persistentvolume/pv-01 created
persistentvolume/pv-02 created
[root@k8s-master01 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE## 存活时间
pv-01   1Gi        RWO            Retain          Available           nfs                     21s
pv-02   50m        RWO            Retain          Available           nfs                     21s

2.2 pv 参数

①accessModes:pv访问模式

  • ReadWriteOnce:卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷。

  • ReadOnlyMany:卷可以被多个节点以只读方式挂载。

  • ReadWriteMany:卷可以被多个节点以读写方式挂载。

  • ReadWriteOncePod:卷可以被单个 Pod 以读写方式挂载。 如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式。这只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本

在命令行接口(CLI)中,ACCESS MODES也使用以下缩写形式:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany
  • RWOP - ReadWriteOnceP

② persistentVolumeReclaimPolicy:pv回收策略 **

  • Retain -- 手动回收,清除pv中的数据需要运维手动去操作,最为安全
  • Recycle -- 基本擦除 (rm -rf /thevolume/*)
  • Delete -- 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除,类如删除pvc 对应的pv 也被删除了。

目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。

③STATUS :pv状态

  • Available(可用)– 卷是一个空闲资源,尚未绑定到任何申领;

  • Bound(已绑定)– 该卷已经绑定到某申领;

  • Released(已释放)– 所绑定的PVC已被删除,但是资源尚未被集群回收;

  • Failed(失败)– 卷的自动回收操作失败。

④CLAIM:当前正被哪个PVC绑定 —— 当某个PV被PVC申领之后,该列就会有数值了!

⑤STORAGECLASS:存储类——动态供应的重点,每个StorageClass都对应着自己的一套自己的存储实现

2.3 创建PVC

上面已经说过pv是运维人员创建的持久化卷,而开发要使用这个持久化卷就需要创建这个卷对应的申领/申请,这个就是pvc。以下是具体的调度过程:

image-20221117114240771

手动创建一个pvc ,cat pvc-01.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-01
  namespace: mytest
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 50m
  storageClassName: nfs
  selector:
    matchLabels:
      type: test

[root@k8s-master01 ~]# kubectl apply -f pvc-01.yaml
Error from server (NotFound): error when creating "pvc-01.yaml": namespaces "mytest" not found

查看k8s 集群所有namespace ns 是所以也是使用全称

[root@k8s-master01 ~]# kubectl get ns --show-labels
NAME                   STATUS   AGE    LABELS
default                Active   252d   <none>
ingress-nginx          Active   132d   app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
kube-node-lease        Active   252d   <none>
kube-public            Active   252d   <none>
kube-system            Active   252d   <none>
kubernetes-dashboard   Active   251d   <none>

我们默认没有mytest 的namespace,手动创建一个 kubectl create ns mytest,再次创建pvc

[root@k8s-master01 ~]# kubectl apply -f pvc-01.yaml
persistentvolumeclaim/pvc-01 created
[root@k8s-master01 ~]# kubectl get pvc  --all-namespaces
NAMESPACE   NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mytest      pvc-01   Bound    pv-02    50m        RWO            nfs            7s
[root@k8s-master01 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON   AGE
pv-01   1Gi        RWO            Retain           Available                   nfs                     28s
pv-02   50m        RWO            Retain           Bound       mytest/pvc-01   nfs                     28s

创建一个nginx 来使用使将web 项目放到 nfs 上

#deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: mytest
  labels:
    app: nginx-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/jyl_docker/nginx
        name: nginx-demo
        volumeMounts:    # 我们这里将nginx容器默认的页面目录挂载
          - name: html-files
            mountPath: "/usr/share/nginx/html"
      volumes:
        - name: html-files
          persistentVolumeClaim:  # 卷类型使用pvc,同时下面名称处填先创建好的pvc1
            claimName: pvc-01
---
#service
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo-svc
  namespace: mytest
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx-demo
  type: ClusterIP
---
#ingress 如果没有配置ingress 可以把这部分删除
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-demo
  namespace: mytest
spec:
  rules:
  - host: nginx.jylx.net
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-demo-svc
          servicePort: 80

查看创建信息

[root@k8s-master01 ~]# kubectl get svc -o wide -n mytest
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
nginx-demo-svc   ClusterIP   10.105.164.251   <none>        80/TCP    4m36s   app=nginx-demo
[root@k8s-master01 ~]# curl 10.244.58.211
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.5</center>
</body>
</html>

在 echo "sunccess_web_pv-02" > mkdir /data/pv-test02/index.html

再次测试

[root@k8s-master01 ~]# curl 10.105.164.251
sunccess_web_pv-02
[root@k8s-master01 ~]# kubectl get ingress -n mytest
NAME         CLASS    HOSTS            ADDRESS                         PORTS   AGE
nginx-demo   <none>   nginx.jylx.net   192.168.232.71,192.168.232.72   80      7m4s

当然可以本地配置 hosts解析访问:nginx.jylx.net

2.4 StorageClass 动态存储

上面pv 和pvc 创建我们提到的pv 创建我们是让运维工程师或者存储工程师创建,我们只管使用pvc就好,但是实际业务中这个方式管理起来比较麻烦,运维人员可以提前配置好Stroagreclass,这样开发就可以直接使用pvc从而动态创建pv。

2.4.1 执行整合文件

可以参考文档:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

选择deploy 目录里得class.yaml、deployment.yaml、rbac.yaml ,以下是整合后的文件StorageClass-deploy.yaml

安装后使用案例验证StorageClass 参考: test-pod.yamltest-claim.yaml

修改StorageClass 添加 annotations:storageclass.kubernetes.io/is-default-class: "true" 设置为默认存储

####class
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
  annotations: 
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "true"
---
###deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: mynfs
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: registry.cn-hangzhou.aliyuncs.com/jyl_docker/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root               
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner ##供应者得名称提供storageclass 调用
            - name: NFS_SERVER
              value: 192.168.232.50
            - name: NFS_PATH 
              value: /data-nfs
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.232.50
            path: /data-nfs
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: mynfs
---
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"]
---
###rbac
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: mynfs
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: mynfs
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: mynfs
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: mynfs
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

执行StorageClass-deploy.yaml

[root@k8s-master01 ~]# kubectl apply -f StorageClass-deploy.yaml
storageclass.storage.k8s.io/nfs-client created
deployment.apps/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
[root@k8s-master01 ~]#  kubectl get storageclass
NAME         PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-client   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  2m5                                  

2.4.2 使用StorageClass

创建一个pod 来验证StorageClass

[root@k8s-master01 ~]# cat StorageClass-nginx-test.yaml
##service
apiVersion: v1
kind: Service
metadata:
  name: nginx-sc
  namespace: mytest
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx-sc
  type: ClusterIP
---

##deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-sc
  namespace: mytest
  labels:
    app: nginx-sc
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-sc
  template:
    metadata:
      labels:
        app: nginx-sc
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/jyl_docker/nginx
        name: nginx-sc
        volumeMounts:    # 我们这里将nginx容器默认的页面目录挂载
          - name: html-files
            mountPath: "/usr/share/nginx/html"
      volumes:
        - name: html-files
          persistentVolumeClaim:
            claimName: test-claim ##
---
##StorageClass
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

执行StorageClass-nginx-test.yaml 文件

[root@k8s-master01 ~]# kubectl apply -f StorageClass-nginx-test.yaml
service/nginx-sc created
deployment.apps/nginx-sc created
persistentvolumeclaim/test-claim created

可以看到最终创建对应了pvc

root@k8s-master01 ~]# kubectl get svc -nmytest
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-sc   ClusterIP   10.109.35.206   <none>        80/TCP    24s
[root@k8s-master01 ~]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
pv-02                                      500m       RWO            Retain           Bound    mytest/pvc-01        nfs                     3d15h
pvc-e4318826-a127-4913-839a-b53a79683369   1Mi        RWX            Delete           Bound    default/test-claim   nfs-client              52s
[root@k8s-master01 ~]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-claim   Bound    pvc-e4318826-a127-4913-839a-b53a79683369   1Mi        RWX            nfs-client     72s