Kubernetes的存储之Volume

在虚拟化的一系列解决方案中,数据的持久化都是须要咱们很是关心的问题,dokcer是这样,Kubernetes也是这样。不过在Kubernetes中,有一个数据卷的概念。node

1、Volume简介

咱们常常都会说:容器、Pod都是很短暂的!其含义就是容器和Pod的生命周期都是很短暂的,会被频繁地销毁和建立。容器销毁时,保存在容器内部文件系统中的数据都会被清除。mysql

Volume的生命周期独立于容器,Pod中的容器可能被销毁和重启,但Volume会被保留。sql

Kubernetes Volume主要解决了如下两个问题:
1)数据持久性:一般状况下,容器运行起来后,写到其文件系统的文件是暂时性的。当容器崩溃后,kubelet会将这个容器不断的重启,当达到重启的次数后,容器仍然不可用,那么就会将这个容器kill掉,从新生成新的容器。此时,新运行的容器并无原容器中的数据,由于容器是由镜像建立的;
2)数据共享:同一个Pod中运行的容器之间,常常会存在共享文件/共享文件夹的需求;docker

从根本上来讲,一个数据卷仅仅是一个能够被Pod访问的目录或文件。这个目录是怎么来的,取决于该数据卷的类型。同一个Pod中的两个容器能够将一个数据卷挂载到不一样的目录下。数据库

Volume 提供了对各类 backend 的抽象,容器在使用 Volume 读写数据的时候不须要关心数据究竟是存放在本地节点的文件系统中呢仍是云硬盘上。对它来讲,全部类型的 Volume 都只是一个目录。vim

2、Volume之emptyDir

1)emptyDir简介

emptyDir 是最基础的 Volume 类型。正如其名字所示,一个 emptyDir Volume 是 Host 上的一个空目录。api

emptyDir Volume 对于容器来讲是持久的,对于 Pod 则不是。当 Pod 从节点删除时,Volume 的内容也会被删除。但若是只是容器被销毁而 Pod 还在,则 Volume 不受影响。相似于docker数据持久化中的docker manager volume方式!bash

2)emptyDir使用示例

[root@master yaml]# vim emptyDir.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: producer-consumer          #定义Pod的名称
spec:
  containers:
  - image: busybox
    name: producer              #定义容器的名称
    volumeMounts:
    - mountPath: /producer_dir            #指定容器内的路径
      name: shared-volume                 #表示把shared-volume挂载到容器中
    args:               #当容器运行完成后,执行如下的写操做
    - /bin/sh
    - -c
    - echo "hello k8s" > /producer_dir/hello; sleep 30000

  - image: busybox
    name: consumer             #定义容器的名称
    volumeMounts:
    - mountPath: /consumer_dir
      name: shared-volume                     #与上一个容器同样
    args:
    - /bin/sh
    - -c
    - cat /consumer_dir/hello; sleep 30000

  volumes:
  - name: shared-volume           #定义数据卷的名称,必须与以上挂载的数据卷名称一致
    emptyDir: {}             #定义一个类型为emptyDir的数据卷,名称为shared-volume
[root@master yaml]# kubectl apply -f emptyDir.yam        #生成所需的Pod资源
[root@master yaml]# kubectl exec -it producer-consumer -c producer /bin/sh
#进入第一个容器进行验证
/ # cat /producer_dir/hello 
hello k8s
[root@master yaml]# kubectl exec -it producer-consumer -c consumer /bin/sh
#进行第二个容器进行验证
/ # cat /consumer_dir/hello 
hello k8s

到此能够看出这个pod中的两个容器指定的目录内容都是同样的,具体是本地的那个目录还需进一步进行验证。app

[root@master yaml]# kubectl get pod -o wide           
NAME                READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE   READINESS GATES
producer-consumer   2/2     Running   0          7m58s   10.244.2.2   node02   <none>           <none>
#能够看出这个pod是运行在node02上的
[root@node02 ~]# docker ps | grep busybox          #因为容器较多,根据使用的镜像名称进行筛选
4fbd734e1763        busybox                "/bin/sh -c 'cat /co…"    8 minutes ago       Up 8 minutes                            k8s_consumer_producer-consumer_default_003a002d-caec-4202-a020-1ae8d6ff7eba_0
b441c2ff2217        busybox                "/bin/sh -c 'echo \"h…"   8 minutes ago       Up 8 minutes                            k8s_producer_producer-consumer_default_003a002d-caec-4202-a020-1ae8d6ff7eba_0
[root@node02 ~]# docker inspect 4fbd734e1763         #根据容器的ID查看容器的详细信息
#找到Mounts字段,以下:
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/003a002d-caec-4202-a020-1ae8d6ff7eba/volumes/kubernetes.io~empty-dir/shared-volume",
                                #此处指定的即是docker host本地的目录
                "Destination": "/consumer_dir",             #容器中的目录
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
[root@node02 ~]# docker inspect b441c2ff2217
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/003a002d-caec-4202-a020-1ae8d6ff7eba/volumes/kubernetes.io~empty-dir/shared-volume",
                "Destination": "/producer_dir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
#能够看出这两个容器的源目录是同样,挂载的是docker host本地的同一个目录                       
[root@node02 ~]# cd /var/lib/kubelet/pods/003a002d-caec-4202-a020-1ae8d6ff7eba/volumes/kubernetes.io~empty-dir/shared-volume
[root@node02 shared-volume]# cat hello 
hello k8s
#验证内容

因为是Kubernetes集群的环境,删除一个容器比较麻烦,直接将pod删除,查看docker host本地的数据是否存在!ide

[root@master yaml]# kubectl delete -f emptyDir.yaml
#master节点将pod删除
[root@node02 ~]# cd /var/lib/kubelet/pods/003a002d-caec-4202-a020-1ae8d6ff7eba/volumes/kubernetes.io~empty-dir/shared-volume
-bash: cd: /var/lib/kubelet/pods/003a002d-caec-4202-a020-1ae8d6ff7eba/volumes/kubernetes.io~empty-dir/shared-volume: 没有那个文件或目录
#node02进行验证,发现目录已经消失

3)emptyDir总结

emptyDir 是Docker Host 上建立的临时目录,其优势是可以方便地为 Pod 中的容器提供共享存储,不须要额外的配置。但它不具有持久性,若是 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器须要临时共享存储空间的场景!

简单来讲就是,若是容器被删除,数据依然存在;若是Pod被删除,数据将不会存在!

3、Volume之HostPath

hostPath Volume 的做用是将 Docker Host 文件系统中已经存在的目录 mount 给 Pod 的容器。大部分应用都不会使用 hostPath Volume,由于这实际上增长了 Pod 与节点的耦合,限制了 Pod 的使用。不过那些须要访问 Kubernetes 或 Docker 内部数据(配置文件和二进制库)的应用则须要使用 hostPath。相似于docker数据持久化中的bind mount方式!

固然也能够进行建立,这里就偷个懒,使用Kubernetes集群自带的YAML文件进行介绍!

[root@master yaml]# kubectl edit --namespace=kube-system pod kube-apiserver-master 
#查看apiserver组件的yaml文件

如图:
Kubernetes的存储之Volume

若是 Pod 被销毁了,hostPath 对应的目录也还会被保留,从这点看,hostPath 的持久性比 emptyDir 强。不过一旦 Host 崩溃,hostPath 也就无法访问了。

因为使用场景较少,以上两种方式这里就不详细介绍了!

4、Volume之Persistent Volume

Persistent Volume概述

普通Volume和使用它的Pod之间是一种静态绑定关系,在定义Pod的文件里,同时定义了它使用的Volume。Volume 是Pod的附属品,咱们没法单首创建一个Volume,由于它不是一个独立的K8S资源对象。

而Persistent Volume 简称PV是一个K8S资源对象,因此咱们能够单首创建一个PV。它不和Pod直接发生关系,而是经过Persistent Volume Claim,简称PVC来实现动态绑定。Pod定义里指定的是PVC,而后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用。

既然有了PV这个概念,那么PVC(PersistentVolumeClaim)这个概念也不得不说一下,PVC表明用户使用存储的请求,应用申请PV持久化空间的一个申请、声明。K8s集群可能会有多个PV,你须要不停的为不一样的应用建立多个PV。

如图:

Kubernetes的存储之Volume
1)PV是集群中的存储资源,一般由集群管理员建立和管理;
2)StorageClass用于对PV进行分类,若是配置正确,Storage也能够根据PVC的请求动态建立PV;
3)PVC是使用该资源的请求,一般由应用程序提出请求,并指定对应的StorageClass和需求的空间大小;
4)PVC能够做为数据卷的一种,被挂载到Pod中使用;

PV与PVC的管理过程以下:
1)在主机上划分出一个单独的目录用于PV使用,而且定义其可用大小;
2)建立PVC这个资源对象,便于申请PV的存储空间;
3)Pod中添加数据卷,数据卷关联到PVC;
4)Pod中包含容器,容器挂载数据卷;

下面经过一个案例来详细了解一下Persistent Volume!

案例实现过程:
1)底层采用NFS存储,而后再NFS的目录下划分1G的容量供PV调度;
2)建立PVC来申请PV的存储空间;
3)建立Pod,使用PVC申请的存储空间来实现数据的持久化;

1)搭建NFS存储

本次案例直接在master节点上建立NFS存储!

[root@master ~]# yum -y install nfs-utils rpcbind
[root@master ~]# vim /etc/exports
/nfsdata *(rw,sync,no_root_squash)
[root@master ~]# systemctl start nfs-server
[root@master ~]# systemctl start rpcbind
[root@master ~]# showmount -e
Export list for master:
/nfsdata *

2)建立PV资源对象

[root@master ~]# vim test-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: test-pv
spec:
  capacity:
    storage: 1Gi          #指定该PV资源分配的容器为1G
  accessModes:         #指定访问模式
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle       #指定回收策略(实验环境,实际环境不多会这样作)
  storageClassName: nfs           #指定存储类名字
  nfs:                          #须要与存储类名字一致
    path: /nfsdata/test-pv                //指定NFS的目录
    server: 192.168.1.4                  //指定NFS的IP地址

上述yaml文件中,主要字段的解释:
1)accessModes(访问模式):

  • ReadWriteOnce:以读写的方式挂载到单个节点;
  • ReadWriteMany:以读写的方式挂载到多个节点 ;
  • ReadOnlyMany:以只读的方式挂载到多个节点;
    2)persistentVolumeReclaimPolicy(PV的回收策略):
  • Recycle:自动清除PV中的数据,自动回收;
  • Retain:须要手动回收;
  • Delete:删除云存储资源(云存储专用);
[root@master ~]# kubectl apply -f test-pv.yaml
[root@master ~]# kubectl get pv         #查看PV的状态
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
test-pv   1Gi        RWO            Recycle          Available           nfs                     21s
#注意其PV的状态必须是 Available才可正常使用

3)建立PVC资源对象

[root@master ~]# vim test-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:             #定义访问模式,必须与PV定义的访问模式一致
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi            #直接请求i使用最大的容量
  storageClassName: nfs           #定义的名称需与PV定义的名称一致
[root@master ~]# kubectl apply -f test-pvc.yaml
[root@master ~]# kubectl get pvc          #查看PVC的状态
NAME       STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-pvc   Bound    test-pv   1Gi        RWO            nfs            102s
[root@master ~]# kubectl get pv           #查看PV的状态
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
test-pv   1Gi        RWO            Recycle          Bound    default/test-pvc   nfs                     14m
#注意PV与PVC的状态都是Bound,表示PV与PVC的关联成功

PV和PVC能够经过storageClassName或accessModes进行关联的!

常见的状态有:
1)Available——>闲置状态,没有被绑定到PVC;
2)Bound——>绑定到PVC;
3)Released——>PVC被删除,资源没有被利用;
4)Failed——>自动回收失败;

4)建立一个Pod

[root@master ~]# vim test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 300000
    volumeMounts:
    - mountPath: /testdata            #定义容器中的目录
      name: volumedata               #保证与卷的名称一致
  volumes:
    - name: volumedata               #定义卷的名称
      persistentVolumeClaim:
        claimName: test-pvc            #指定逻辑卷对应的PVC名称
[root@master ~]# kubectl apply -f  test-pod.yaml 
[root@master ~]# kubectl get pod      #查看pod的状态
NAME       READY   STATUS              RESTARTS   AGE
test-pod   0/1     ContainerCreating   0         6m26s
#注意其状态为 ContainerCreating,表示容器正在建立,可是查看时间,这么长时间没有建立好,不太正常

当pod状态不正常时,通常咱们能够采用如下三种方式进行排错:
1)使用“ kubectl describe pod pod名称”查看pod的详细信息;
2)使用“kubectl logs pod名称“查看pod的日志信息;
3)使用“cat /var/log/messages”查看系统日志;

本次采用第一种方式排错!

[root@master ~]# kubectl describe pod test-pod 
#最后的一条的信息以下:
mount.nfs: mounting 192.168.1.4:/nfsdata/test-pv failed, reason given by server: No such file or directory
#根据消息提示,指定本地须要挂载的目录不存在
[root@master ~]# mkdir  -p  /nfsdata/test-pv
#建立完成目录后,建议查看pod生成的容器运行的节点,将节点上的kubelet服务进行重启,重启完成后,再次查看Pod的状态
[root@master ~]# kubectl get pod        //再次查看pod的状态
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          32m

5)测试其数据持久化的效果

[root@master ~]# kubectl exec -it test-pod /bin/sh
/ # echo "test pv pvc" > /testdata/test.txt
#进入容器,建立文件进行测试
[root@master ~]# cat /nfsdata/test-pv/test.txt 
test pv pvc
#确认这个文件本地是存在的
[root@master ~]# kubectl delete -f test-pod.yaml 
#将pod进行删除
[root@master ~]# cat /nfsdata/test-pv/test.txt 
test pv pvc
#再次查看发现pod的测试文件依然存在
[root@master ~]# kubectl delete -f test-pvc.yaml 
#将PVC进行删除
[root@master ~]# cat /nfsdata/test-pv/tes
cat: /nfsdata/test-pv/tes: 没有那个文件或目录
#再次查看发现pod的测试文件不见了,由于将PVC删除了

总结:因为咱们在建立pv这个资源对象时,采用的回收策略是清除PV中的数据,而后自动回收,而PV这个资源对象是由PVC来申请使用的,因此不论是容器也好,pod也好,它们的销毁并不会影响用于实现数据持久化的nfs本地目录下的数据,可是,一旦这个PVC被删除,那么本地的数据就会随着PVC的销毁而不复存在,也就是说,采用PV这种数据卷来实现数据的持久化,它这个数据持久化的生命周期是和PVC的生命周期是一致的。

5、mysql对数据持久化的应用

可能经过步骤四并不能真正的理解Persistent volume的做用,下面经过建立mysql容器的方式来验证Persistent volume的做用!

1)搭建NFS共享存储

本次案例直接在master节点上建立NFS存储!

[root@master ~]# yum -y install nfs-utils rpcbind
[root@master ~]# vim /etc/exports
/nfsdata *(rw,sync,no_root_squash)
[root@master ~]# systemctl start nfs-server
[root@master ~]# systemctl start rpcbind
[root@master ~]# showmount -e
Export list for master:
/nfsdata *

2)建立PV资源对象

[root@master ~]# vim mysql-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain                  #注意指定的回收策略为手动回收
  storageClassName: nfs
  nfs:
    path: /nfsdata/mysql-pv
    server: 192.168.1.4
[root@master ~]# kubectl apply -f mysql-pv.yaml 
[root@master ~]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
test-pv   1Gi        RWO            Retain           Available           nfs                     15s
[root@master ~]# mkdir -p /nfsdata/mysql-pv

3)建立PVC资源对象

[root@master ~]# vim mysql-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
[root@master ~]# kubectl apply -f mysql-pvc.yaml 
[root@master ~]# kubectl get pvc
NAME        STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pvc   Bound    mysql-pv   1Gi        RWO            nfs            13s
[root@master ~]# kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
mysql-pv   1Gi        RWO            Retain           Bound    default/mysql-pvc   nfs                     8m14s

4)建立pod资源

[root@master ~]# vim mysql-pod.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mysql-pod
spec:
  selector:                     #设置给予等值的标签选择器(也可省略)
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:                      #设置环境变量,数据库root用户的密码
        - name: MYSQL_ROOT_PASSWORD
          value: 123.com
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql               #这个目录是数据库存放数据的目录(指定的是容器中的目录)
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc
[root@master ~]# kubectl apply -f mysql-pod.yaml 
[root@master ~]# kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
mysql-pod-6cc889468b-gq4qz   1/1     Running   0          3s

5)测试数据持久化效果

[root@master ~]# kubectl exec -it mysql-pod-6cc889468b-gq4qz -- mysql -u root -p123.com
#直接登陆运行mysql数据库的pod中的mysql
#插入数据进行测试
mysql> create database lzj;
mysql> use lzj;
mysql> create table my_id( id int(4) );
mysql> insert my_id values (9527);
mysql> select * from my_id;
+------+
| id   |
+------+
| 9527 |
+------+
[root@master ~]# ls /nfsdata/mysql-pv/
auto.cnf  ibdata1  ib_logfile0  ib_logfile1  lzj  mysql  performance_schema
#查看pod对应的NFS的目录,确实有了数据
[root@master ~]# kubectl get pod -o wide             #查看pod的详细信息
NAME                         READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
mysql-pod-6cc889468b-gq4qz   1/1     Running   0          23m   10.244.2.6   node02   <none>           <none>
#查看到pod是运行在node02节点上的
#模拟node02宕机,步骤省略,关机、挂起均可以!
[root@node01 ~]# systemctl restart kubelet
#重启node01的kubelet服务
#接下来耐心等待pod的转移,可能须要差很少5分钟
^C[root@master ~]# kubectl get pod  -o wide                                #能够看到pod已经运行在node01上
NAME                         READY   STATUS        RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
mysql-pod-6cc889468b-gdf7k   1/1     Running       0          66s   10.244.1.6   node01   <none>           <none>
mysql-pod-6cc889468b-gq4qz   1/1     Terminating   0          32m   10.244.2.6   node02   <none>           <none>
[root@master ~]# kubectl exec -it mysql-pod-6cc889468b-gdf7k  -- mysql -u root -p123.com
#再次登陆到pod中运行的mysql(注意:pod的名称)
mysql> select * from lzj.my_id;                     #再次数据是否存在
+------+
| id   |
+------+
| 9527 |
+------+
1 row in set (0.01 sec)
#数据依旧存在

——————————————本次到此结束,感谢阅读——————————————

相关文章
相关标签/搜索