深入了解K8S持久化存储解决方案

1. 前言

经过上一篇K8S实战部署SpringBoot项目的解读,我相信很多老铁都已经对K8S部署项目有了一个初步的认识,我们了解了K8S的Ingress网络,但简单的SpringBoot项目部署只是K8S使用的基础,我们使用K8S另一个绕不开的话题就是K8S持久化存储。什么是持久化存储? For Example: 我们做系统迁移,要把原来的服务迁移到K8S中,系统用的Mysql数据库,也要迁移到K8S,我们知道,K8S运行的是一个一个Pod,K8S对Pod自动化管理,一个Pod挂了,另外一个Pod就会马上拉起来,假如运行Mysql的Pod挂了,马上重新拉起来,那原来Pod中存储的数据还会存在吗?或者说新拉起来的Pod会进行数据恢复吗?答案是:NO! 如果没有持久化存储,那兄弟,你真正的做到了从删库到跑路!从这样一个真实的场景,我们应该认识到K8S持久化存储的重要性,可以说,没有持久化技术,K8S就没有任何发展的前景!今天,我就深入的和大家聊一聊K8S中做持久化存储的几种解决方案,并用实操让大家玩转K8S!话不多说,撸起袖子,干就完了!

这里没有任何马后炮套话,只有粗暴的干货。写大家看得懂、用得着、赚得到的文章是唯一宗旨!

2. Storage

2.1. Volume

官方定义:On-disk files in a Container are ephemeral, which presents some problems for non-trivial applications when running in Containers. First, when a Container crashes, kubelet will restart it, but the files will be lost – the Container starts with a clean state. Second, when running Containers together in a Pod it is often necessary to share files between those Containers. The Kubernetes Volume abstraction solves both of these problems.
容器中的磁盘上文件是短暂的,这给在容器中运行的非平凡应用程序带来了一些问题。首先,当一个容器崩溃时,kubelet将重新启动它,但文件将丢失-容器以一个干净的状态开始。其次,在Pod中一起运行容器时,常常需要在这些容器之间共享文件。Kubernetes卷抽象解决了这两个问题。

2.2. Host类型volume实战

假如我们来设计K8S的Storage 方案,我们会怎么做?如果你想到了这样一个解决方案:在Pod所在的宿主机上,我有一个数据存储目录,和Mysql Pod的数据存储目录做一下关联,让后者共享宿主机的存储目录,那在Pod挂掉之后,重新拉取还是同样的共享目录,那数据不久能恢复了?这样听起来是不是和docker的 -v 设置目录挂载有异曲同工之妙!那我们来实操一下!

2.2.1. 创建Nginx Pod的yaml文件

我们首先创建一个nginx的pod的yaml文件,volumeMounts 定义了pod内的存储目录是根目录的nginx-volume文件夹下,volumes 定义了的挂载类型是hostPath , 目录是 /tmp/volume-pod,我们都是有经验的开发人员,我想这个文件应该很好理解。

apiVersion: v1
kind: Pod
metadata:
  name: volume-pod
spec:
  containers:
  - name: nginx-container
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: volume-pod
      mountPath: /nginx-volume
  volumes:
  - name: volume-pod
    hostPath:
      path: /tmp/volume-pod 

我们在集群中master节点保存这个yaml文件,起名字为volume-pod.yaml,然后执行

$ kubectl apply -f volume-pod.yaml

然后查看详细信息,我们发现它在worker02节点创建了pod。

$ kubectl get pod -o wide
在这里插入图片描述

2.2.2. 验证

接下来就是验证:我们要查看worker02节点和pod内目录是否进行了关联
宿主机:
在这里插入图片描述
Pod内:
在这里插入图片描述
果然,宿主机和Pod内都存在!那么接下来我们验证一下,我在宿主机这个目录建个文件,Pod内会不会出现这个文件。我在宿主机上建了一个 index.html文件,在Pod查看。
在这里插入图片描述
果然,同步了!
那接下来,我们删除Pod,重新拉起一个Pod,看会不会还存在这个index.html文件
在这里插入图片描述
果然,index.html存在!

重点来了: 这种共享宿主机存储的方法似乎可以解决Mysql数据库数据恢复的场景,我们似乎可以万事大吉了!But,有的老铁会问:如果我得宿主机挂了怎么办?或者Pod没有在上一次节点上拉起,而是在新的节点上拉起,那数据都不在一个宿主机上,还恢复个锤子! 听起来有点儿道理啊,确实存在这个问题那怎么解决呢?还是那句话 :总有”好事者”替我们解决了这个问题!

2.3. PersistentVolumes

官方文档
既然Host类型的持久化存储无法解决节点宕机或者pod在另外的机器上拉起导致数据无法恢复的Bug,那我们就应该思考一个问题:既然我无法在宿主机持久化,那我在集群之外的服务器上存储数据,让我的Pod关联到这个数据存储服务器上,同时我对这个数据存储服务器做高可用,岂不美哉? 此处应该有掌声,三分钟!
想法很好,那我们来介绍一下K8S给我们的解决方案: PersistentVolumes 简称PV
PV 是什么?它是一种插件,它能够支持多种数据存储服务器,通过PV,我们能在K8S集群中,把我们的数据持久化到外部的服务器中。下图是PV能够支持的数据存储服务类型
在这里插入图片描述
我们可以看到,它能支持这么多种数据存储服务,那我们来实战一下:选择NFS来作为我们的数据存储服务。

2.3.1. NFS服务器搭建

NFS 是什么? nfs(network file system) 网络文件系统,是FreeBSD支持的文件系统中的一种,允许网络中的计算机之间通过TCP/IP网络共享资源

  1. 找一台centos 7机器,执行以下脚本,搭建 NFS服务器:
	# 安装nfs
	yum install -y nfs-utils
	# 创建nfs目录
	mkdir -p /nfs/data/
	mkdir -p /nfs/data/mysql
	# 授予权限
	chmod -R 777 /nfs/data
	# 编辑export文件
	vi /etc/exports
	  /nfs/data *(rw,no_root_squash,sync)
	# 使得配置生效
	exportfs -r
	# 查看生效
	exportfs
	# 启动rpcbind、nfs服务
	systemctl restart rpcbind && systemctl enable rpcbind
	systemctl restart nfs && systemctl enable nfs
	# 查看rpc服务的注册情况
	rpcinfo -p localhost
	# showmount测试
	showmount -e ip(ip地址)
  1. 在K8S集群所有节点上安装NFS客户端
	yum -y install nfs-utils
	systemctl start nfs && systemctl enable nfs

2.3.2. PV定义

NFS 服务器有了,那我们如何让PV和NFC关联上呢?看下面代码:

# 定义PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv
spec:
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 2Gi    
  nfs:
    path: /nfs/data/nginx     
    server: 10.6.229.62

PV也作为一种K8S的资源,被K8S管理,所以它的定义肯定也是yaml。上述代码我们定义了一个nginx-pv,accessModes权限是ReadWriteMany读写权限,capacity storage 定义了2G的存储空间,挂载目录是/nfs/data/nginx,NFS 服务器IP是10.6.229.62,好了,我们这样就定义了一个PV。
定义完了我们就能用了吗?
我们考虑一个问题: 假如你作为这一块儿的设计者,每次想使用外部存储服务,都要自己创建一个PV,这样做麻烦吗?一个公司中不仅有开发,还有运维,如果我们想用外部存储服务,直接告诉运维,让运维给我们在K8S中准备一些PV,我们自己挑选着用,这岂不是方便很多。
所以,“好事者” 想的比较周到。我们的PV是和外部的服务器相连,Pod是不能使用的,我们要用另外一种叫做PersistentVolumeClaim 简称 PVC 的资源来连接PV,我们的Pod连接PVC 就可以使用了。类似于消息中间件的生产者和消费者的关系。PV是生产者,PVC是消费者。

2.4. PersistentVolumeClaim

官方文档
PVC定义:

# 定义PVC,用于消费PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi

简单明了,我定义一个名字叫 nginx-pvc的PVC,权限是ReadWriteMany读写权限,需要的存储空间是 2G。
那我们思考一个问题:PVPVC我们都定义好了,我们在PVC定义中并没有看到和PV绑定的相关配置,那他们两个是怎么绑定的呢?
其实,内部是配对是这样的:通过PVC定义的 accessModes 读写权限,和storage定义的2G内存,PVC会自动找到符合这些配置的PV进行绑定。一个PV被PVC绑定后,不能被别的PVC绑定。

2.5. PV、PVC实战

我们还来定义一个Nginx Pod,使用PVPVCNFS来做持久化存储。

# 定义PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv
spec:
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 2Gi    
  nfs:
    path: /nfs/data/nginx     
    server: 10.6.229.62 
    
---
# 定义PVC,用于消费PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
  
---
# 定义Pod,指定需要使用的PVC
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels: 
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-persistent-storage
          mountPath: /usr/share/nginx/html
      volumes:
      - name: nginx-persistent-storage
        persistentVolumeClaim:
          claimName: nginx-pvc

我们可以看到 使用 persistentVolumeClaim:claimName: nginx-pvc 定义了我我们要使用名字为nginx-pvc的 PVC
注意:我们在NFS服务器上定义的路径是/nfs/data/nginx 首先我们需要去 nfs/data下创建 nginx的文件夹,不然你的pod是启动不起来的,这个坑我替大家踩了!
接下来,在master节点上启动。

$ kubectl apply -f nginx-pv-demo.yaml 

查看PV,PVC

$ kubectl get pv,pvc

在这里插入图片描述
查看pod

$ kubectl get pod -o wide

在这里插入图片描述
我们发现,Nginx Pod创建成功了,它在worker01节点启动了。那接下来就是要验证。
验证:

  1. 我在pod中数据存储目录创建一个index.html,然后到nfs服务器上岸看有没有。
    在这里插入图片描述
    在这里插入图片描述
  2. 我在nfs中数据存储目录创建一个hello.html,然后到pod中看有没有。
    在这里插入图片描述
    在这里插入图片描述
  3. 我把pod删了,重新拉起之后看有没有index.html,hello.html文件。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    结论: 全部验证成功!此处应该有掌声,5分钟!
    重点又来了: 作为一个开发人员,我想要用一个持久性存储服务,要写PV,PVC,如果我的需求多了,那维护起来似乎有点儿麻烦了,尽管我们的PV可以让运维事先准备很多供我们挑选,但是如果没有我们的需要的怎么办?这种手动管理的做法是不是有点儿low! 我们都是有经验的开发人员,我的意思是:假如我们有一个模板,我想要多少资源,我直接在模板里配置好,启动的时候自动给我创建对应的资源,运维也不用需要太关注PV,那岂不是挺好的?K8S能有这种机制吗?答案是yes!,秉承:W r i t e T h e C o d e , C h a n g e T h e W o r l d ! \color{#FF3030}{Write The Code,Change The World!}WriteTheCodeChangeTheWorld!的理念, 好事者 已经帮我们做了!此处应该有掌声,8分钟!

2.6. StorageClass

官方文档

官方定义: A StorageClass provides a way for administrators to describe the “classes” of storage they offer. Different classes might map to quality-of-service levels, or to backup policies, or to arbitrary policies determined by the cluster administrators. Kubernetes itself is unopinionated about what classes represent. This concept is sometimes called “profiles” in other storage systems

StorageClass为管理员提供了一种描述其提供的存储“类”的方法。不同的类可能映射到服务质量级别、备份策略或由群集管理员确定的任意策略。Kubernetes本身并不关心类代表什么。在其他存储系统中,此概念有时称为“配置文件”

2.6.1. 官方支持的StorageClass

在这里插入图片描述
图中✔的是官方已经实现的StorageClass,我们可以直接用,我们看到NFS是不支持的!它竟然不支持!那岂不是我们束手无策?非也!,我们要感谢 好事者 帮我们做了这个支持!
NFS StorageClass : https://github.com/kubernetes-incubator/external-storage/tree/master/nfs
既然我们知道了 StorageClass是干嘛的,那我们来实操一下!

2.6.2. NFS StorageClass 实战

Each StorageClass contains the fields provisioner, parameters, and reclaimPolicy, which are used when a PersistentVolume belonging to the class needs to be dynamically provisioned.

The name of a StorageClass object is significant, and is how users can request a particular class. Administrators set the name and other parameters of a class when first creating StorageClass objects, and the objects cannot be updated once they are created.

每个StorageClass都包含字段provisioner、参数和reclaimPolicy,这些字段在属于该类的PersistentVolume需要动态配置时使用。

StorageClass对象的名称很重要,它是用户请求特定类的方式。管理员在第一次创建StorageClass对象时设置类的名称和其他参数,这些对象一旦创建就无法更新。

StorageClass声明存储插件,用于自动创建PV。
说白了就是创建PV的模板,其中有两个重要部分:PV属性和创建此PV所需要的插件。这样PVC就可以按“Class”来匹配PV。可以为PV指定storageClassName属性,标识PV归属于哪一个Class。

01 对于PV或者StorageClass只能对应一种后端存储
02 对于手动的情况,一般我们会创建很多的PV,等有PVC需要使用的时候就可以直接使用了
03 对于自动的情况,那么就由StorageClass来自动管理创建
04 如果Pod想要使用共享存储,一般会在创建PVC,PVC中描述了想要什么类型的后端存储、空间等,K8s从而会匹配对应的PV,如果没有匹配成功,Pod就会处于Pending状态。Pod中使用只需要像使用volumes一样,指定名字就可以使用了
05 一个Pod可以使用多个PVC,一个PVC也可以给多个Pod使用
06 一个PVC只能绑定一个PV,一个PV只能对应一种后端存储

(1)准备好NFS服务器[并且确保nfs可以正常工作],创建持久化需要的目录。

path: /nfs/data/class
server: 10.6.229.62

(2)创建rbac.yaml

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-runner
rules:
  - 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"]
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get"]
  - apiGroups: ["extensions"]
    resources: ["podsecuritypolicies"]
    resourceNames: ["nfs-provisioner"]
    verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
     # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-provisioner
  apiGroup: rbac.authorization.k8s.io

这个文件是创建授权账户。为什么要授权?在K8S中,我们知道有 ApiServer 组件,它可以管理我们创建的 deployment, podservice等资源,但是有些资源它是管不到的,比如说 K8S本身运行需要的组件等等,同样StorageClass这种资源它也管不到,所以,需要授权账户。
我们在master节点执行。

$ kubectl apply -f rbac.yaml
在这里插入图片描述

(3)根据deployment.yaml文件创建资源

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner
      containers:
        - name: nfs-provisioner
          image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: example.com/nfs
            - name: NFS_SERVER
              value: 10.6.229.62  
            - name: NFS_PATH
              value: /nfs/data/class
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.6.229.62 
            path: /nfs/data/class

在master上创建

$ kubectl apply -f deployment.yaml
在这里插入图片描述

(4)根据class.yaml创建资源

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: example-nfs
provisioner: example.com/nfs

在master上创建

$ kubectl apply -f class.yaml
在这里插入图片描述

(5)根据my-pvc.yaml创建资源

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
    # 我们定义了1M的PVC,storageClass会自动给我们创建这个资源,不用再去手动创建1M的PV
      storage: 1Mi
  # 这个名字要和上面创建的storageclass名称一致
  storageClassName: example-nfs

在 master上创建

$ kubectl apply -f my-pvc.yaml
$ kubectl get pvc
在这里插入图片描述

(6)根据nginx-pod.yaml创建资源

kind: Pod
apiVersion: v1
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
      - name: my-pvc
        mountPath: "/usr/class"
  restartPolicy: "Never"
  volumes:
    - name: my-pvc
      persistentVolumeClaim:
        claimName: my-pvc

在master上执行

$ kubectl apply -f nginx-pod.yaml

在这里插入图片描述
完事!剩下就是验证!

2.6.3. 验证

和上面验证方案一样。需要注意一点的是,NFS服务器下的/nfs/data/class并不是数据存储的根目录,pod起来之后,会自动在里面创建一个文件夹来存放数据。
在这里插入图片描述
pod内:
在这里插入图片描述
完美!

3. 番外篇

3.1. PV的状态和回收策略

  • PV的状态

Available:表示当前的pv没有被绑定

Bound:表示已经被pvc挂载

Released:pvc没有在使用pv, 需要管理员手工释放pv

Failed:资源回收失败

  • PV回收策略

Retain:表示删除PVC的时候,PV不会一起删除,而是变成Released状态等待管理员手动清理

Recycle:在Kubernetes新版本就不用了,采用动态PV供给来替代

Delete:表示删除PVC的时候,PV也会一起删除,同时也删除PV所指向的实际存储空间

注意:目前只有NFS和HostPath支持Recycle策略。AWS EBS、GCE PD、Azure Disk和Cinder支持Delete策略

4. 总结

以上内容,我们徐徐渐进地深入探讨了K8S为我们提供的三种持久化存储方案,我相信读过这个篇文章的老铁肯定有很大的收获,企业级存储大多数用的是StorageClass解决方案,运维帮我们搞了存储服务器,StorageClass这一块儿的内容,我们开发只需关注PVC,配置自己的需求就可以了,是不是很简单!在大厂,我们基本是不会操作这些内容的,真实的部署都是一键部署,具体实现都是基础设施帮我们做了,这篇文章希望对你了解K8S有所帮助!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注