一、存储卷的概念和类型

为了保证数据的持久性,必须保证数据在外部存储在docker容器中。在宿主机和容器内做映射,可以保证在容器的生命周期结束时,数据依旧可以实现持久性存储。

但是在k8s中,由于pod分布在各个不同的节点之上,并不能实现不同节点之间持久性数据的共享,并且,在节点故障时,可能会导致数据的永久性丢失。为此,k8s就引入了外部存储卷的功能。

k8s的存储卷类型:

[root@master ~]# kubectl explain pod.spec.volumes  # 查看k8s支持的存储卷类型
常用分类:
emptyDir(临时目录):Pod删除,数据也会被清除,这种存储成为emptyDir,用于数据的临时存储;
hostPath(宿主机目录映射);
本地的SAN(iSCSI,FC)和NAS(nfs,cifs,http)存储;
分布式存储(glusterfs,rbd,cephfs);
云存储(EBS,Azure Disk);

 

二、emptyDir存储卷演示 

一个 emptyDir 第一次创建是在一个pod被指定到具体node的时候,并且会一直存在在pod的生命周期当中,正如它的名字一样,它初始时是一个空目录,pod中的容器都可以读写这个目录,这个目录可以被挂在到各个容器相同或者不相同的的路径下。当一个pod因为任何原因被移除的时候,这些数据会被永久删除。

注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除pod。

emptyDir 磁盘的作用:

  • 普通空间,基于磁盘的数据存储;
  • 作为从崩溃中恢复的备份点;
  • 存储那些那些需要长久保存的数据,例如web服务器中的数据;

默认情况下,emptyDir 磁盘会存储在主机所使用的媒介上,可能是SSD,或者网络硬盘,这主要取决于你的环境。当然,我们也可以将emptyDir.medium的值设置为Memory来告诉Kubernetes 来挂在一个基于内存的目录tmpfs,因为tmpfs速度会比硬盘块度了,但是,当主机重启的时候所有的数据都会丢失。

[root@master volumes]# cat emptyDir-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod-demo
  namespace: default
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:  # 在容器内定义挂载存储名称和挂载路径
    - name: html
      mountPath: /usr/share/nginx/html/
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /data/  # 在容器内定义挂载存储名称和挂载路径
    command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
  volumes:  # 定义存储卷
  - name: html  # 定义存储卷名称  
    emptyDir: {}  # 定义存储卷类型
[root@master volumes]# kubectl get pods -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
emptydir-pod-demo   2/2     Running   0          16m   10.101.231.177   node02   <none>           <none>
[root@master volumes]# curl 10.101.231.177  # 访问验证
Thu Sep 12 03:01:28 UTC 2019
Thu Sep 12 03:01:30 UTC 2019
Thu Sep 12 03:01:32 UTC 2019
Thu Sep 12 03:01:34 UTC 2019
Thu Sep 12 03:01:36 UTC 2019
Thu Sep 12 03:01:38 UTC 2019

 

三、hostPath存储卷演示

hostPath宿主机路径,就是把pod所在的宿主机之上的某一目录(脱离pod中容器名称空间的之外的宿主机的文件系统中的某一目录)和pod建立关联关系,在pod删除时,存储数据不会丢失。

查看hostPath存储类型定义

[root@master volumes]# kubectl explain pods.spec.volumes.hostPath  
KIND:     Pod
VERSION:  v1

RESOURCE: hostPath <Object>

DESCRIPTION:
     HostPath represents a pre-existing file or directory on the host machine
     that is directly exposed to the container. This is generally used for
     system agents or other privileged things that are allowed to see the host
     machine. Most containers will NOT need this. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

     Represents a host path mapped into a pod. Host path volumes do not support
     ownership management or SELinux relabeling.

FIELDS:
   path <string> -required-  #指定宿主机的路径
     Path of the directory on the host. If the path is a symlink, it will follow
     the link to the real path. More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

   type <string>
     Type for HostPath Volume Defaults to "" More info:
     https://kubernetes.io/docs/concepts/storage/volumes#hostpath

   type:
     DirectoryOrCreate  宿主机上不存在创建此目录  
     Directory          必须存在挂载目录  
     FileOrCreate       宿主机上不存在挂载文件就创建  
     File               必须存在文件  

清单定义

[root@master volumes]# cat hostPath-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod-demo
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      hostPath:
        path: /data/pod/volume1
        type: DirectoryOrCreate
[root@node01 ~]# mkdir -p /data/pod/volume1
[root@node01 ~]# vim /data/pod/volume1/index.html
node1
[root@node02 ~]# mkdir -p /data/pod/volume1
[root@node02 ~]# vim /data/pod/volume1/index.html
node2
[root@master volumes]# kubectl apply -f hostPath-demo.yaml 
pod/hostpath-pod-demo created
[root@master volumes]# kubectl get pods -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
hostpath-pod-demo   1/1     Running   0          38s   10.101.231.178   node02   <none>           <none>
[root@master volumes]# curl 10.101.231.178
node02
[root@master volumes]# kubectl delete -f hostPath-demo.yaml      
pod "hostpath-pod-demo" deleted
[root@master volumes]# kubectl apply -f hostPath-demo.yaml       
pod/hostpath-pod-demo created
[root@master volumes]# kubectl get pods -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
hostpath-pod-demo   1/1     Running   0          15s   10.101.231.179   node02   <none>           <none>
[root@master volumes]# curl 10.101.231.179
node02

hostPath可以实现持久存储,但是在node节点故障时,也会导致数据的丢失。

 

四、nfs共享存储卷演示

我们可以将nfs共享存储卷挂载到Pod中。和emptyDir不同的是,Pod被删除的时候,emptyDir也会被删除,但nfs不会被删除,仅仅是解除挂载状态而已。这就意味着nfs能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递。并且,nfs可以同时被多个pod挂载并进行读写。

注意:在我们挂载nfs的时候,必须先保证nfs服务器正常运行。

1、在nfs节点上安装nfs,并配置nfs服务:

[root@nfs ~]# yum install -y nfs-utils
[root@nfs ~]# mkdir /data/volumes -pv
[root@nfs ~]# vim /etc/exports
/data/volumes 192.168.3.0/24(rw,no_root_squash)
[root@nfs ~]# systemctl start nfs
[root@nfs ~]# showmount -e
Export list for nfs:
/data/volumes 192.168.3.0/24

2、在node01和node02节点上安装nfs-utils,并测试挂载:

 ~]# yum install -y nfs-utils
 ~]# mount -t nfs nfs:/data/volumes /mnt
 ~]# mount
...
nfs:/data/volumes on /mnt type nfs4 (rw,relatime,vers=4.1,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.3.131,local_lock=none,addr=192.168.3.136
 ~]# umount /mnt/

3、创建nfs存储卷的使用清单

[root@master volumes]# vim nfs-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod-demo
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      nfs:
        path: /data/volumes
        server: nfs
[root@master volumes]# kubectl apply -f nfs-demo.yaml 
pod/nfs-pod-demo created
[root@master volumes]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
nfs-pod-demo   1/1     Running   0          13s   10.101.231.180   node02   <none>           <none>

4、测试

[root@nfs ~]# cd /data/volumes/
[root@nfs volumes]# vim index.html
<h1> nfs volumes </h1>
[root@master volumes]# curl 10.101.231.180
<h1> nfs volumes </h1>

 删除pod后重新创建

[root@master volumes]# kubectl delete -f nfs-demo.yaml 
pod "nfs-pod-demo" deleted
[root@master volumes]# kubectl apply -f nfs-demo.yaml       
pod/nfs-pod-demo created
[root@master volumes]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
nfs-pod-demo   1/1     Running   0          17s   10.101.231.181   node02   <none>           <none>
[root@master volumes]# curl 10.101.231.181
<h1> nfs volumes </h1>

 

五、PV和PVC

我们前面提到kubernetes提供那么多存储接口,但是首先kubernetes的各个Node节点要能管理这些存储,而且各种存储参数也需要专业的存储工程师才能了解,由此我们的kubernetes管理变的更加复杂。由此kubernetes提出了PV和PVC的概念,这样开发人员和使用者就不需要关注后端存储是什么,使用什么参数等问题。如下图:

 PersistentVolume(PV)是集群中已由管理员配置的一段网络存储。 集群中的PV资源是一个集群资源,类似于节点一样。 PV是诸如卷之类的卷插件,但是较Pod具有更长的生命周期。

PersistentVolumeClaim(PVC)是用户存储的请求。PVC的使用逻辑:在pod中定义一个存储卷(该存储卷类型为PVC),定义的时候直接指定大小,pvc必须与对应的pv建立关系,pvc会根据定义去pv申请,而pv是由存储空间创建出来的。pv和pvc是kubernetes抽象出来的一种存储资源。

虽然PVC允许用户使用抽象存储资源,但是常见的需求是,用户需要根据不同的需求去创建PV,用于不同的场景。而此时需要集群管理员提供不同需求的PV,而不仅仅是PV的大小和访问模式,但又不需要用户了解这些卷的实现细节。 对于这样的需求,此时可以采用StorageClass资源。

PV是集群中的资源。 PVC是对这些资源的请求,也是对资源的索赔检查。 PV和PVC之间的相互作用遵循这个生命周期:

Provisioning(配置)---> Binding(绑定)--->Using(使用)---> Releasing(释放) ---> Recycling(回收)

 

 六、NFS使用PV和PVC

实验图如下:

 

[root@master ~]# kubectl explain pv  # 查看pv的定义方式
FIELDS:
    apiVersion
    kind
    metadata
    spec
[root@master ~]# kubectl explain pv.spec  # 查看pv定义的规格
spec:
  nfs  # 定义存储类型
    path  # 定义挂载卷路径
    server  # 定义服务器名称
  accessModes  # 定义访问模型,有以下三种访问模型,以列表的方式存在,也就是说可以定义多个访问模式
    ReadWriteOnce(RWO)  # 单节点读写
    ReadOnlyMany(ROX)  # 多节点只读
    ReadWriteMany(RWX)  # 多节点读写
  capacity  # 定义PV空间的大小
    storage  # 指定大小
[root@master volumes]# kubectl explain pvc  # 查看PVC的定义方式
KIND:     PersistentVolumeClaim
VERSION:  v1
FIELDS:
   apiVersion   <string>
   kind <string>  
   metadata <Object>
   spec <Object>
[root@master volumes]# kubectl explain pvc.spec
spec:
  accessModes  # 定义访问模式,必须是PV的访问模式的子集
  resources  # 定义申请资源的大小
    requests:
      storage: 

 1、配置nfs存储

[root@nfs volumes]# mkdir v{1,2,3,4,5}
[root@nfs volumes]# vim /etc/exports
/data/volumes/v1 192.168.3.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.3.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.3.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.3.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.3.0/24(rw,no_root_squash)
[root@nfs volumes]# exportfs -arv
exporting 192.168.3.0/24:/data/volumes/v5
exporting 192.168.3.0/24:/data/volumes/v4
exporting 192.168.3.0/24:/data/volumes/v3
exporting 192.168.3.0/24:/data/volumes/v2
exporting 192.168.3.0/24:/data/volumes/v1
[root@nfs volumes]# showmount -e
Export list for nfs:
/data/volumes/v5 192.168.3.0/24
/data/volumes/v4 192.168.3.0/24
/data/volumes/v3 192.168.3.0/24
/data/volumes/v2 192.168.3.0/24
/data/volumes/v1 192.168.3.0/24

2、定义PV 

[root@master volumes]# vim pv-nfs-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: nfs
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
  labels:
    name: pv004
spec:
  nfs:
    path: /data/volumes/v4
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv005
  labels:
    name: pv005
spec:
  nfs:
    path: /data/volumes/v5
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi
[root@master volumes]# kubectl apply -f pv-nfs-demo.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created
[root@master volumes]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   1Gi        RWO,RWX        Retain           Available                                   34s
pv002   2Gi        RWO            Retain           Available                                   34s
pv003   2Gi        RWO,RWX        Retain           Available                                   34s
pv004   4Gi        RWO,RWX        Retain           Available                                   34s
pv005   5Gi        RWO,RWX        Retain           Available                                   34s

3、定义PVC

这里定义了pvc的访问模式为多路读写,该访问模式必须在前面pv定义的访问模式之中。定义PVC申请的大小为2Gi,此时PVC会自动去匹配多路读写且大小为2Gi的PV,匹配成功后,被绑定PV的状态为Bound。

[root@master volumes]# vim pvc-nfs-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pvc-nfs-pod
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      persistentVolumeClaim:
        claimName: mypvc
[root@master volumes]# kubectl apply -f pvc-nfs-demo.yaml 
persistentvolumeclaim/mypvc created
pod/pvc-nfs-pod created
[root@master volumes]# kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc   Bound    pv003    2Gi        RWO,RWX                       29s
[root@master volumes]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON   AGE
pv001   1Gi        RWO,RWX        Retain           Available                                           7m3s
pv002   2Gi        RWO            Retain           Available                                           7m3s
pv003   2Gi        RWO,RWX        Retain           Bound       default/mypvc                           7m3s
pv004   4Gi        RWO,RWX        Retain           Available                                           7m3s
pv005   5Gi        RWO,RWX        Retain           Available                                           7m3s

4、测试访问

在存储服务器上创建index.html,并写入数据,通过访问Pod进行查看,可以获取到相应的页面。

[root@nfs volumes]# cd v3
[root@nfs v3]# echo "welcome to use pv3" > index.html
[root@master volumes]# kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP               NODE     NOMINATED NODE   READINESS GATES
pvc-nfs-pod   1/1     Running   0          5m59s   10.101.231.182   node02   <none>           <none>
[root@master volumes]# curl 10.101.231.182
welcome to use pv3

 

七、StorageClass

在pvc申请存储空间时,未必就有现成的pv符合需求,上面的pvc申请存储空间可以成功因为我们做了指定的需求处理。那么当pvc申请的存储空间不一定有满足要求的pv时,又该如何处理呢???为此,Kubernetes为管理员提供了描述存储"class"的方法(StorageClass)。

举个例子,在存储系统中划分一个1TB的存储空间提供给Kubernetes使用,当用户需要一个10G的PVC时,会立即通过存储系统的restful接口发送请求,从而让存储空间创建一个10G的image,然后在我们的集群中定义10G的PV供给当前的PVC作为挂载使用。所以我们的存储系统必须支持restful接口,比如ceph分布式存储,而glusterfs则需要借助第三方接口完成这样的请求。

如图:

 

   storageclass 也是 k8s 上的资源

[root@master ~]# kubectl explain storageclass
KIND:     StorageClass
VERSION:  storage.k8s.io/v1
FIELDS:
   allowVolumeExpansion <boolean>     
   allowedTopologies    <[]Object>   
   apiVersion   <string>   
   kind <string>     
   metadata <Object>     
   mountOptions <[]string>  # 挂载选项
   parameters   <map[string]string>  # 参数,取决于分配器,可以接受不同的参数。例如,参数type 的值io1和参数iopsPerGB特定于EBS PV。当参数被省略时,会使用默认值。  
   provisioner  <string> -required-  # 存储分配器,用来决定使用哪个卷插件分配PV,该字段必须指定。
   reclaimPolicy    <string>  # 回收策略,可以是Delete或者Retain,如果StorageClass对象被创建时没有指定reclaimPolicy,它将默认为Delete。 
   volumeBindingMode    <string>  # 卷的绑定模式

StorageClass 中包含 provisioner、parameters 和 reclaimPolicy 字段,当 class 需要动态分配 PersistentVolume 时会使用到。由于StorageClass需要一个独立的存储系统,此处就不再演示。从其他资料查看定义StorageClass的方式如下:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
mountOptions:
  - debug

 

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐