在虚拟化的一系列解决方案中,数据的持久化都是须要咱们很是关心的问题,dokcer是这样,Kubernetes也是这样。不过在Kubernetes中,有一个数据卷的概念。node
咱们常常都会说:容器、Pod都是很短暂的!其含义就是容器和Pod的生命周期都是很短暂的,会被频繁地销毁和建立。容器销毁时,保存在容器内部文件系统中的数据都会被清除。mysql
Volume的生命周期独立于容器,Pod中的容器可能被销毁和重启,但Volume会被保留。sql
Kubernetes Volume主要解决了如下两个问题:
1)数据持久性:一般状况下,容器运行起来后,写到其文件系统的文件是暂时性的。当容器崩溃后,kubelet会将这个容器不断的重启,当达到重启的次数后,容器仍然不可用,那么就会将这个容器kill掉,从新生成新的容器。此时,新运行的容器并无原容器中的数据,由于容器是由镜像建立的;
2)数据共享:同一个Pod中运行的容器之间,常常会存在共享文件/共享文件夹的需求;docker
从根本上来讲,一个数据卷仅仅是一个能够被Pod访问的目录或文件。这个目录是怎么来的,取决于该数据卷的类型。同一个Pod中的两个容器能够将一个数据卷挂载到不一样的目录下。数据库
Volume 提供了对各类 backend 的抽象,容器在使用 Volume 读写数据的时候不须要关心数据究竟是存放在本地节点的文件系统中呢仍是云硬盘上。对它来讲,全部类型的 Volume 都只是一个目录。vim
emptyDir 是最基础的 Volume 类型。正如其名字所示,一个 emptyDir Volume 是 Host 上的一个空目录。api
emptyDir Volume 对于容器来讲是持久的,对于 Pod 则不是。当 Pod 从节点删除时,Volume 的内容也会被删除。但若是只是容器被销毁而 Pod 还在,则 Volume 不受影响。相似于docker数据持久化中的docker manager volume方式!bash
[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进行验证,发现目录已经消失
emptyDir 是Docker Host 上建立的临时目录,其优势是可以方便地为 Pod 中的容器提供共享存储,不须要额外的配置。但它不具有持久性,若是 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器须要临时共享存储空间的场景!
简单来讲就是,若是容器被删除,数据依然存在;若是Pod被删除,数据将不会存在!
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文件
如图:
若是 Pod 被销毁了,hostPath 对应的目录也还会被保留,从这点看,hostPath 的持久性比 emptyDir 强。不过一旦 Host 崩溃,hostPath 也就无法访问了。
因为使用场景较少,以上两种方式这里就不详细介绍了!
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。
如图:
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申请的存储空间来实现数据的持久化;
本次案例直接在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 *
[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才可正常使用
[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——>自动回收失败;
[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
[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删除了
可能经过步骤四并不能真正的理解Persistent volume的做用,下面经过建立mysql容器的方式来验证Persistent volume的做用!
本次案例直接在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 *
[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
[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
[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
[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) #数据依旧存在
——————————————本次到此结束,感谢阅读——————————————