一、Volume

一般来说,容器一旦被删除后,容器运行时内部产生的所有文件数据也会被清理掉,因此,Docker提供了 Volume 这种方式来将数据持久化存储

可以说,Volume 是Pod与外部存储设备进行数据传递的通道,也是Pod内部容器间、Pod与Pod间、Pod与外部环境进行数据共享的方式。

实际上,这个 Volume 也只是宿主机上本地磁盘中的一个目录,也就是说,volume方式是将容器里面的数据都保存到宿主机上。除此之外,还能保存到外部存储上。

在k8s中,支持多种类型的Volume:本地存储(emptyDir / hostPath)、外部存储(如NFS)。

1、emptyDir 

若pod使用了emptyDir类型的volume,则在创建pod时,emptyDir volume随着pod也会一同被创建出来。emptyDir volume 会在pod所在的node节点上生成一个空目录,而这个空目录的默认路径是在/var/lib/kubelet/pods/下。

emptyDir 类型相当于执行【docker run -v /CONTAINER/DIR】。

emptyDir Volume与Pod生命周期一致,只要Pod一直运行,该Volume就一直存在,而当Pod被删除时,该Volume也同时会删除,即Node上对应目录也会被删掉。

一个Volume可被Pod中的所有容器共享,且可被挂载到容器的指定路径下。

示例:

说明:创建一个Pod,Pod有两个容器,它们共享一个Volume,busybox容器负责往 Volume 中写数据,myapp容器则是从 Volume 读取数据

apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
spec:
  volumes:             # 定义emptyDir类型的Volume
  - name: myweb
    emptyDir: {}
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: myweb
      mountPath: /usr/share/nginx/html/
  - name: busybox
    image: busybox:latest
    volumeMounts:             # 将名为myweb的volume挂载到容器里的/web目录下
    - name: myweb
      mountPath: /web
    command: [ "/bin/sh", "-c", "while true; do echo $(date) >> /web/index.html; done" ]

查看volume信息:

$ docker inspect 020799d427ae -f "{{.Mounts}}"
"Mounts": [
    {
        "Type": "bind",
        "Source": "/var/lib/kubelet/pods/bb66b0cd-979d-4356-92a9-492d420fc613/volumes/kubernetes.io~empty-dir/html",
        "Destination": "/usr/share/nginx/html",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    },

尝试将pod删除,node上的volume目录也会被删除:

$ kubectl delete pod pod-demo
$ ls /var/lib/kubelet/pods/bb66b0cd-979d-4356-92a9-492d420fc613

2、hostPath

该类型是将Node上指定的文件或目录挂载到Pod中。当Pod被删除时,Node上对应的该Volume的文件或目录不会被删除,会保留下来,从这点来看,hostPath的持久性比emptyDir强。不过一旦node节点崩溃了,hostPath也就没法访问了。

hostPath 类型相当于执行【docker run -v /HOST/DIR:/CONTAINER/DIR】。

apiVersion: v1
kind: Pod
metadata:
  name: pod-demo2
spec:
  volumes:
  - name: myweb
    hostPath:
      path: /data/www/     # 指定node上的目录
      type: Directory
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: myweb
      mountPath: /usr/share/nginx/html/     #要挂载到容器的哪个目录下

3、外部存储(以NFS为例)

除了将数据存放在本地node节点上,为了更安全,我们也可以将数据存储到外部的远程磁盘上,比如放到NFS服务器上,IP为192.168.100.172。

1)搭建NFS服务器

yum -y install nfs-utils
mkdir -p /data/testvol
echo "NFS Test Data" > /data/testvol/index.html
echo "/data/testvol 192.168.100.0/24(rw,no_root_squash)" >> /etc/exports
systemctl start nfs

2)创建NFS存储

# 在k8s集群的节点上,安装nfs-utils工具
$ yum -y install nfs-utils

# 验证是否能成功挂载
$ mount -t nfs 192.168.100.172:/data/testvol /mnt

# yaml文件如下:
$ cat vol-nfs-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: vol-nfs
spec:
  volumes:
  - name: myweb
    nfs:
      path: /data/testvol         # NFS共享目录        
      server: 192.168.100.172     # NFS服务器IP
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: myweb
      mountPath: /usr/share/nginx/html/

二、PV与PVC

除了Volume之外,kubernetes 还提供了 Persistent Volume 的方法持久化数据。

它与普通Volume的区别是, 普通Volume和Pod之间是一种静态绑定关系,也就是,在定义pod时,同时要将pod所使用的Volume一并定义好,Volume是Pod的附属品。volume会随着pod创建而被创建,我们无法单独创建一个Volume,因为它不是一个独立的K8S资源对象。

而Persistent Volume则是一个K8S资源对象,它是独立于Pod的,能单独创建。Persistent Volume 不与Pod发生直接关系,而是通过 Persistent Volume Claim(PVC) 来与Pod绑定关系。在定义Pod时,为Pod指定一个PVC,Pod在创建时会根据PVC要求,从现有集群的PV中,选择一个合适的PV绑定,或动态建立一个新的PV,再与其进行绑定。

Persistent Volume(PV):用于定义各种存储资源的配置信息,一个PV对应一个volume,定义一个PV内容包括了 存储类型、存储大小和访问模式等。

Persistent Volume Claim(PVC):描述对PV的一个请求。请求信息包含存储大小、访问模式等。PVC只会选择符合自己要求的PV进行绑定,然后在定义pod时指定使用哪个PVC就可以了。

原理:

PVC和PV的设计,其实跟面向对象的思想完全一致,PVC是面向对象编程中的接口,PV是接口具体的实现。

用户只需通过PVC来声明自己的存储需求,比如存储大小、可读写权限等,类似于调用接口函数并传入属性参数,而不用关心后端存储实现细节,这些都交由运维人员统一管理即可。

Pod是直接与PVC绑定关系,再根据PVC存储需求,去找到对应PV。PVC只有绑定了PV之后才能被Pod使用。

PersistentVolume Controller 会不断地查看当前每一个PVC,是不是已经处于Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的PV,并尝试将其与这个PVC进行绑定。这样,Kubernetes就可以保证用户提交的每一个PVC,只要有合适的PV出现,它就能够很快进入绑定状态。

在提交PVC后,是如何找到对应的PV:先根据PVC的accessModes匹配出PV列表,再根据PVC的Capacity、StorageClassName、Label Selector进一步筛选PV。如果满足条件的PV有多个,选择PV的size最小的,accessmodes列表最短的PV,也即最小适合原则。

也就是说,PVC绑定PV的过程是有一定规则的,以下规则都满足的PV才能被PVC绑定:

VolumeMode:被消费PV的VolumeMode需要和PVC一致。

AccessMode:被消费PV的AccessMode需要和PVC一致。

StorageClassName:如果PVC定义了此字段,则PV也必须有对应字段才能进行绑定。

LabelSelector:通过标签(labels)匹配的方式从PV列表中选择合适的PV绑定。

Size:被消费PV的capacity必须大于或等于PVC的存储容量需求才能被绑定。

PV类型:

一般来说,PV又有多种类型:Static PV (静态)、Dynamic PV (动态)、Local PV (本地)。

Static/Dynamic PV:静态和动态PV

PV创建虽是由运维人员完成的,但在一个大规模的Kubernetes集群里,很可能有成千上万个PVC,这就意味着运维人员必须得事先创建出成千上万个PV,如果单纯靠人工来管理,会存在一定的困难。

Kubernetes提供了一套可以自动创建PV的机制,即Dynamic Volume Provisioning(动态PV)。而手动创建并管理的PV叫做Static Volume Provisioning(静态PV)。

Dynamic PV创建机制的核心,在于一个名为StorageClass的API对象,它是一个用于创建PV的模板。

在YAML文件中定义PVC时,需要指定一个StorageClass名称,然后等到用户要创建这个PVC时,系统会根据PVC定义的需求,并参考StorageClass的存储细节,最后通过调用StorageClass声明的存储插件(Provisioner),动态创建出需要的PV。

所以,在声明一个PVC时,如果在PVC中添加了StorageClassName字段,那就意味着,当PVC在集群中找不到匹配的PV时,它会根据StorageClassName的定义,触发相应的Provisioner插件创建出合适的PV进行绑定。

也就是说,现在无需事先创建好将来要用到的PV,只要通过StorageClass准备好一些PV模板,等到将来要使用时,PVC再直接使用StorageClass定义好的PV模板,调用存储插件将PV一并创建出来就可以了。

Local-PV:本地PV

一是,不应该随便把node上的任何一个目录当作PV使用,因为不安全,应该额外挂载一个外部磁盘到node上,也就是,一个PV对应一块外部数据盘。

二是,调度器要保证Pod始终能被正确地调度到它所请求的Local PV所在的节点上,那调度器就要知道所有node与local pv的关联关系,即PV的位置分布信息(也叫存储拓扑信息),然后根据这个位置信息来调度Pod。

流程图:

1:先准备好外部存储资源;
2:然后通过static或dynamic方式,将存储资源定义成PV;
3:定义PVC资源请求,PVC会根据配置描述选择合适的PV;
4:最后Pod指定使用哪个PVC,最终是由PVC将Pod与匹配的PV绑定在一起;

————————————————————————————————
|namespace                     |
|                              |
|    [pod1]     [pod2]         |
|      ↓          ↓            |
|   [volume1]  [volume2]       |
|      ↑          ↑            |
|      |         /             |
|      ↓        ↓              |
|    [pvc]    [pvc]    [pvc]   |
——————↑—————————↑————————↑——————
     /           \       |_ _ _ _ _ __
   /              \                   ↓
  ↓                ↓                  ↓
[pv]  [pv] |    | [pv] [pv] [pv]  | [pv] [pv] [pv]
           |    |                 |
static     |    | storageClass    | storageClass
———————————      ——————————————    ————————————————
                      ↑↑                ↑↑
                      ↑↑                ↑↑
                ————————————————  ——————————————————————
                 [NFS] [ISCSI]    [Ceph RDB] [Glusterfs]

PV状态:

Create PV ---> pending ---> available ---> bound ---> released ---> deleted或failed

Available:创建PV后,会短暂处于pending状态,等真正创建好后,就会进入available状态,只有处于该状态下的PV才能够被PVC绑定。

Bound:用户在提交PVC后,并找到相应PV,此时PV与PVC已绑在一起,两者都处于BOUND状态。

Released:如果PV设置了ReclaimPolicy策略为retain,也就是当用户在使用完PVC,将其删除后,对应的这个PV就会处于released状态。

当PV已经处在released状态时,它是无法直接回到available状态,也就是说,接下来这个PV无法被一个新的PVC去做绑定。

有两种方式复用处于released状态的PV:

一种是对之前released的PV做好数据备份,然后重新创建一个PV,并将之前released的PV相关字段的信息填到这个PV中。另一种是在删除Pod后,不要删除PVC,将PVC保留下来供其他Pod直接复用。

1、Static PV

这里还是以NFS服务器作为PV存储为例。

1)先搭建好NFS服务器(192.168.100.172)

创建4个NFS共享目录:

yum -y install nfs-utils
mkdir -p /data/volumes/v{1..5}

echo "<h1>NFS stor 01</h1>" > /data/volumes/v1/index.html
echo "<h1>NFS stor 02</h1>" > /data/volumes/v2/index.html
echo "<h1>NFS stor 03</h1>" > /data/volumes/v3/index.html
echo "<h1>NFS stor 04</h1>" > /data/volumes/v4/index.html

cat > /etc/exports << EOF
/data/volumes/v1 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.100.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.100.0/24(rw,no_root_squash)
EOF

systemctl start nfs
exportfs -rv         # 重新加载配置
showmount -e         # 查看本地有哪些共享目录

2)创建PV

创建4个pv,都使用nfs共享的目录,存储大小各不相同,是否可读也不相同。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
  labels:
    name: pv0001
spec:
  nfs:
    path: /data/volumes/v1
    server: 192.168.100.172
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0002
  labels:
    name: pv0002
spec:
  nfs:
    path: /data/volumes/v2
    server: 192.168.100.172
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 7Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
  labels:
    name: pv0003
spec:
  nfs:
    path: /data/volumes/v3
    server: 192.168.100.172
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0004
  labels:
    name: pv0004
spec:
  nfs:
    path: /data/volumes/v4
    server: 192.168.100.172
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 15Gi

字段说明:

capacity:设置PV的存储属性,比如存储大小。

accessModes:设置对Volume的访问模式
ReadWriteOnce – the volume can be mounted as read-write by a single node
ReadOnlyMany – the volume can be mounted read-only by many nodes
ReadWriteMany – the volume can be mounted as read-write by many nodes

persistentVolumeReclaimPolicy:当PVC被删除时,对应PV的回收策略
Retain - 当PVC被删除时,PV会保留,但被标识为released状态
Delete - 当PVC被删除时,PV也同时被删除
Recycle - 已废弃

查看PV信息:

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM    STORAGECLASS   REASON   AGE
pv0001   2Gi        RWO,RWX        Retain           Available                                    6s
pv0002   7Gi        RWO            Retain           Available                                    6s
pv0003   10Gi       RWO,RWX        Retain           Available                                    6s
pv0004   15Gi       RWO,RWX        Retain           Available                                    6s

3)创建PVC

创建一个PVC,创建好后,该PVC会根据要求请求合适的PV资源。

如:下面的PVC会绑定到名为pv0003的PV上

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  accessModes: ["ReadWriteMany"]      #匹配PV的accessModes要包含ReadWriteMany
  resources:
    requests:
      storage: 6Gi                    #且匹配PV要大于6G

4)创建Pod

创建一个Pod,并指定使用哪个PVC,PVC会决定将哪个PV绑定到此Pod上。

apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
spec:
  volumes:
  - name: myvol
    persistentVolumeClaim:
      claimName: mypvc             #为此Pod指定使用哪个PVC
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: myvol
      mountPath: /usr/share/nginx/html/

创建Pod后,查看PV和PVC状态:

$ kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS       CLAIM          STORAGECLASS   REASON   AGE
pv0001   2Gi        RWO,RWX        Retain           Available                                     	    18m
pv0002   7Gi        RWO            Retain           Available                                           18m
pv0003   10Gi       RWO,RWX        Retain           Bound        default/mypvc                          18m
pv0004   15Gi       RWO,RWX        Retain           Available                                           18m

$ kubectl get pvc
NAME                STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc               Bound    pv0003   10Gi       RWO,RWX                       17s

测试访问:

$ kubectl get pods pod-demo -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP            NODE
pod-demo   1/1     Running   0          3m21s   10.244.1.73   node1

$ curl 10.244.1.73
<h1>NFS stor 03</h1>

2、Dynamic PV

步骤1:定义2个StorageClass(创建生成PV的模板文件),一个为普通磁盘,一个为SSD磁盘。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/aws-ebs    # 指定一个volume plugin,即应该用哪个存储插件来去创建PV
parameters:
  type: pd-standard
reclaimPolicy: Delete                 # PV的回收策略,默认为delete
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/gce-pd     
parameters:
  type: pd-ssd
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-disk
parameters:
  regionId: cn-hangzhou
  zoneId: cn-hangzhou-b
  fsType: ext4
  type: cloud_ssd
provisioner: diskplugin.csi.alibabacloud.com
reclaimPolicy: Delete

步骤2:创建PVC,并指定storageClassName名,即到底用哪一个模板文件来生成PV,Kubernetes只会将StorageClass相同的PVC和PV绑定起来。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: csi-disk     # 为PVC指定使用哪个StorageClass
  resources:
    requests:
      storage: 30Gi

步骤3:创建Pod,并指定要调用的PVC

kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: dockerfile/nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: disk-pvc

三、部署NFS动态存储卷(案例)

1、master和node节点安装nfs服务

yum -y install nfs-utils rpcbind
systemctl start nfs && systemctl enable nfs
systemctl start rpcbind && systemctl enable rpcbind

# master上创建并配置好共享挂载目录(node节点不需要,只需安装好nfs服务就行)
mkdir -pv /data/volumes/{v1,v2,v3}
cat > /etc/exports <<EOF
/data/volumes/v1  *(rw,no_root_squash,no_all_squash)
/data/volumes/v2  *(rw,no_root_squash,no_all_squash)
/data/volumes/v3  *(rw,no_root_squash,no_all_squash)
EOF

# 发布并查看
exportfs -arv
showmount -e

2、部署 NFS Provisioner插件(master)

git clone https://github.com/kubernetes-incubator/external-storage.git
cd external-storage/nfs-client/deploy/
cp class.yaml deployment.yaml rbac.yaml test-claim.yaml /var/lib/k8s/storage

cd /var/lib/k8s/storage

a. 配置rbac授权(默认是存放在default空间,可修改为kube-system)
kubectl apply -f rbac.yaml

b. 部署 NFS Provisioner插件

# 修改deployment.yaml文件(设置nfs服务器相关信息)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  namespace: kube-system                #默认是default,修改为kube-sytem
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
          image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-client           #可自定义,要与class.yaml中的provisioner的名称一致,否则部署不成功
            - name: NFS_SERVER
              value: x.x.x.x              #修改为nfs服务器地址
            - name: NFS_PATH
              value: /data/volumes/v1     #NFS服务器中的共享挂载目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: x.x.x.x               #修改为nfs服务器地址
            path: /data/volumes/v1        #NFS服务器中的共享挂载目录

# 部署NFS Provisioner插件
kubectl apply -f deployment.yaml

# 查看安装情况
kubectl get pod -o wide -n kube-system -l app=nfs-client-provisioner

[root@jdmaster ~]# kubectl get pod -o wide -n kube-system -l app=nfs-client-provisioner
NAME                                      READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nfs-client-provisioner-7f75997fb6-tchxb   1/1     Running   0          21h   10.244.1.8   jdnode   <none>           <none>

kubectl get deployment -n kube-system

[root@jdmaster ~]# kubectl get deployment -n kube-system
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
coredns                  2/2     2            2           3d
nfs-client-provisioner   1/1     1            1           21h

3、创建storageclass

# 修改class.yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"      #设置为默认的storageclass(如果不设置默认,在创建pvc时,需要手动指定storageclass的名称,否则会处于pending)
provisioner: nfs-client 		                             #可自定义,要与 deployment 中 env PROVISIONER_NAME 一致
parameters:
  archiveOnDelete: "false"


# 也命令方式设置默认的StorageClass(NAME后面会多出一个default字样)
# kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# 创建storageclass
kubectl apply -f class.yaml
# 查看(storageclass所有命名空间都可以看到,不需要指定某个namespace)
kubectl get sc

[root@jdmaster ~]# kubectl get sc
NAME                            PROVISIONER   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage (default)   nfs-client    Delete          Immediate           false                  21h

4、创建PVC

# 修改test-claim.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  #annotations:
  #  volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"       #若这里指定storageclass了,可以不用手动指定storageClassName字段
spec:
  storageClassName: managed-nfs-storage         #若没有设置默认的storageclass,必须手动指定使用哪个storageclass
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

# 查看pvc状态(会处于Bound状态,若处于pending是不正常的)
kubectl get pvc

[root@jdmaster ~]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
test-claim   Bound    pvc-bcbea3c8-4a37-47c1-8aa6-2c2da6964108   1Gi        RWX            managed-nfs-storage   20h

# 只要创建好NFS Provisioner deployment 和 storageclass后,再创建PVC时,就会自动创建出PV
kubectl get pv

[root@jdmaster ~]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS          REASON   AGE
pvc-bcbea3c8-4a37-47c1-8aa6-2c2da6964108   1Gi        RWX            Delete           Bound    default/test-claim     managed-nfs-storage            20h

5、创建一个测试pod

kubectl apply -f test-pod.yaml

cat > test-pod.yaml <<EOF
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox:latest
    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-claim
EOF

# POD会成功创建好SUCCESS文件,就退出了,状态为Completed
# 进入到NFS共享目录(/data/volumes/v1/<namespace名称>-<pvc名称>-<pv名称>/),查看SUCCESS文件是否存在

四、架构设计


PV和PVC的处理流程:
【配图-待补充】


csi全称是container storage interface,它是K8s社区后面对存储插件实现(out of tree)的官方推荐方式。

csi的实现大体可分为两部分:

第一部分:是由k8s社区驱动实现的通用的部分,如图中的csi-provisioner和csi-attacher controller;

第二部分:由云存储厂商实践的,对接云存储厂商的OpenApi,主要是实现真正的create/delete/mount/unmount 存储的相关操作,对应到图中的csi-controller-server和csi-node-server。

用户在提交PVC yaml时,首先会在集群中生成一个PVC对象,然后PVC对象会被csi-provisioner controller watch到,csi-provisioner会结合PVC对象及PVC对象中声明的 storageClass,通过GRPC调用csi-controller-server。然后,到云存储服务这边去创建真正的存储,并最终创建出来PV对象。最后,由集群中的PV controller将PVC和PV对象做bound 之后,这个PV就可以被使用了。

用户在提交pod之后,首先会被scheduler调度选中某一个合适的node,然后node上的kubelet在创建pod时,会先通过csi-node-server将之前创建的PV挂载到pod指定路径。
然后kubelet开始 create && start pod 中的所有container。


PV、PVC及通过csi使用存储流程:
【配图-待补充】

第一个阶段:create阶段,主要是创建存储

用户提交完PVC,由csi-provisioner创建存储,并生成PV对象,之后PV controller将PVC及生成的PV对象做bound,bound之后,create阶段就完成了。

第二个阶段:attach阶段,将对应的存储挂载到node上。

用户在提交pod之后,首先会被scheduler调度选中某一个合适的node,node被选出来后,AD Controller会watch到该node,它会根据Pod使用了哪些PV,生产一个内部的VolumeAttachment对象,从而去触发csi-attacher去调用csi-controller-server去做真正的attach操作,attach操作调到云存储厂商OpenAPI,并将存储attach到pod将会运行的node上面。

第三个阶段:mount阶段,将对应的存储进一步挂载到pod里。

在kubelet创建pod的过程中,会先做一个mount操作,这是为了将已经attach到这个node上的那块盘,进一步mount到pod可使用的一个具体路径,之后kubelet才开始创建并启动容器。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐