一、PV&PVC
(一)说明
对于有状态服务,使用Volume挂载,会存在数据丢失的问题,因此K8S使用数据持久卷(PV、PVC)来做容器的编排。
PV(PersistentVolume--持久卷)是一种特殊的Volume,其是一种Volume插件,其存在与集群内,是由管理员提供存储的一部分。它的生命周期和使用它的Pod相互独立。其是对抽象资源创建和使用的抽象。
PVC(PersistentVolumeClaim--持久卷声明)是一种用户的存储请求。
PV和PVC是K8S提供的两种API资源,用于抽象存储细节。管理员关注如何通过PV提供存储功能,而不需要关注用户如何使用,用户只需要挂载PVC到容器中,而不需要关注存储卷使用核中技术实现。
(二)生命周期
PV和PVC应遵循供给、绑定、使用、释放、回收这样的生命周期。
1、供给
供给有两种:静态和动态。
静态:集群管理员创建多个PV,他们携带着真是存储的详细信息,这些存储对于集群用户是可用的,它们存在于Kubernetes API中,并可用于存储使用。
动态:当管理员创建的静态PV都不匹配用户的PVC是,集群会尝试专门的供给Volume给PVC,这种供给基于StrorageClass。PVC请求的等级必须是管理员已经创建和配置过的登记,以防止这种动态供给的发生。如果请求是一个登记配置为空的PVC,那么说明禁止了动态供给。
2、绑定
就是PVC和PV的绑定,当用户发起一个PVC请求时,master中有一个控制回路用于监控新的PVC请求,然后根据PVC请求(要求的存储大小和访问模式)来寻找PV,如果寻找到相匹配的PV,则将PVC和PV进行绑定,一旦绑定后,该PV就只属于这一个PVC。如果没有找到对应的PV,那么该PVC会一直处于unbond(未绑定)的状态,直到出现与PVC匹配的PV。举个栗子,一个提供了很多50G存储的PV集群,如果一个PVC请求的容量是100G,则不会被匹配成功,直到有100G的PV加入到该集群。
3、使用
Pod使用PVC和使用Volume一样,集群检查PVC,查找绑定的PV,将PV映射给Pod。对于支持多种访问模式的PV,用户可以指定访问模式。一旦用户拥有了PVC且PVC已经绑定了PV,那么这个PV就一致属于该用户。用户调度Pod的Volume块中包含的PVC来访问PV。
4、释放
当用户使用PV完成后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为已经released了,但是此时还不能给其他的PVC使用,因为原来的PVC还存在该PV中,必须使用策略将其删除,该PV才可以被其他PVC使用
5、回收
PV的回收策略是告诉集群当PV被释放后应该如何处理该PV,当前的PV可以被设置为Retained(保留)、Recycled(再利用)、Deleted(删除)。保留允许手动的再次声明资源,对于支持删除操作的PV卷,删除操作会从K8S中移除PV对象及对应的外部存储。动态供给的卷一定会被删除。
(三)Pod和PVC
1、提前创建好pv,以及挂载目录
这里使用nfs作为文件服务器,nfs服务的搭建可以参看 NFS网络存储,我这里已经搭建好,ip为192.168.124.16
创建两个pv的配置文件如下所示(注意一点,挂载目录/opt/k8s/demo1和/opt/k8s/demo2要提前在nfs上创建好),然后在K8S中创建如下PV
apiVersion: v1 kind: PersistentVolume metadata: name: my-pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteMany nfs: path: /opt/k8s/demo1 server: 192.168.124.16
# vim pv2.yaml apiVersion: v1 kind: PersistentVolume metadata: name: my-pv2 spec: capacity: storage: 2Gi accessModes: - ReadWriteMany nfs: path: /opt/k8s/demo2 server: 192.168.124.16
配置解释:
kind表明了这是要创建一个PV
name是PV的名称
spec是描述信息
capacity.storage是PV的存储大小
accessModes中的值表示允许多节点访问
nfs表示持久化存储使用的是nfs
nfs下面的内容就是对应nfs服务器的ip和目录
上面的配置就是将实际的持久化存储抽象成为一个PV。
创建PV:
kubectl apply -f pv1.yaml kubectl apply -f pv2.yaml
2、然后现在创建一下我们的pod和pvc,这里我写在一起了
# vim pod.yaml apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: nginx image: nginx ports: - containerPort: 80 volumeMounts: - name: www # 使用名字为www的volume,挂载到下面的目录中 mountPath: /usr/share/nginx/html volumes: - name: www # 创建一个名字为www的volume persistentVolumeClaim: claimName: my-pvc #使用的是名为my-pvc的PVC --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc # pvc的名字 spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi #对应PV的选择条件
配置文件解释:
首先创建了一个pod,名字为my-pod,容器端口为80,挂载名称为www,容器中的挂载目录为/usr/share/nginx/html
然后创建了一个名为www的volume,类型是PVC,名字为my-pvc
然后创建了一个PVC,名字为my-pvc,对应的PV的条件为1G
执行创建命令
kubectl apply -f pod.yaml
查看pvc和pod
可以看到pvc对应的是my-pv1(使用大小选择到my-pv1),my-pv1使用的是nfs服务器的/opt/k8s/demo1目录,pod中的目录是/usr/share/nginx/html,可以在nfs的/opt/k8s/demo1目录中创建一个文件,然后在pod中查看
二、statefulset
有状态服务器本身就是有实时的数据需要存储,那么现在数据存储的问题已经解决了,现在就来一个statefulset的实例部署
(一)headless services
Headless Services是一种特殊的service,其spec:clusterIP表示为None,这样在实际运行时就不会被分配ClusterIP。
Service的主要作用就是对一组Pod做负载均衡,但是有的场景不需要Service来做负责均衡,例如部署Kafka集群时,客户端需要的是每一个Kafka的IP,而不需要Service做负载均衡,因此K8S提供了headless Service。字面意思就是无service,其实就是改service对外无提供IP。
1、普通的service创建
# 创建一个service资源对象 # kubectl expose deployment xxName –port=80 –target-port=80 k8s指令模式创建一个service # k8s部署yaml方式 apiVersion: v1 kind: Service metadata: name: nginx namespace: default spec: selector: app: nginx ports: - port: 80 targetPort: 80 --- # 部署deployment对象,k8s创建3个资源对象:Deployment,ReplicaSet,POD apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: default spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.16 ports: - containerPort: 80
2、headless service创建
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx-demo ports: - port: 80 name: nginx clusterIP: None --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx-dp spec: selector: matchLabels: app: nginx-demo replicas: 2 template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx ports: - containerPort: 80 name: web
实际上,Headless service和普通的Service的yaml文件唯一的区别就是Headless中需要设置clusterIP为None,而普通的需要设置type(NodePort或ClusterIP)
可以看一下service:
可以看到,普通的sarvice有ClusterIP,而Headless Service没有。
可以再看一下详细的差异:
这里可以详细看到,headless service没有clusterIP,直接使用的是Pod的ip,而普通的service使用的是ClusterIP进行负载均衡。这里也可以进入headless Service的pod中,去ping另外一个pod,都是可以ping通的。
(二)使用statefulSet部署有状态服务
1、有状态服务
有状态服务使用statefulSet + PVC进行创建,其中PVC对应PV,而PV则对应文件服务器。在使用statefulSet创建pod时,pod的退出和进入都是有序的,pod的名字为 statefulSetName + 有序数字(0-N)。
StatefulSet: 是一种给Pod提供唯一标志的控制器,它可以保证部署和扩展的顺序。
Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关。
稳定的次序:对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的。
稳定的网络:Pod的hostname模式为(statefulset名称)- (序号)。 稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。
2、先创建5个PV
这个没什么好说的,就是创建pod的准备,和之前的一样,需要在nfs服务器上提前创建好相应的挂载文档。
apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: name: pv001 spec: nfs: path: /opt/k8s/v1 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv002 labels: name: pv002 spec: nfs: path: /opt/k8s/v2 server: 172.20.10.6 accessModes: ["ReadWriteOnce"] capacity: storage: 2Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv003 labels: name: pv003 spec: nfs: path: /opt/k8s/v3 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv004 labels: name: pv004 spec: nfs: path: /opt/k8s/v4 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv005 labels: name: pv005 spec: nfs: path: /opt/k8s/v5 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi
3、使用statefulSet创建服务----手动指定PVC进行创建pod
# 部署stateful类型的有状态服务, 指定pvc apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumes: - name: www persistentVolumeClaim: claimName: my-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi
配置说明:
等待 terminationGracePeriodSeconds 这么长的时间。(默认为30秒)、超过terminationGracePeriodSeconds等待时间后, K8S 会强制结束老POD
4、使用statefulSet创建服务----使用volumeClaimTemplates直接指定pvc
在上面创建statefulset的配置文件中,需要手动的创建pvc的配置,但是如果当pvc越来越多,则会管理越来越困难,这时就可以用到使用volumeClaimTemplates直接指定pvc,其可以帮助我们自动创建pvc,不需要我们手动定义pvc
# 使用volumeClaimTemplates直接指定pvc,申请pv apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: nginx replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "my-storage-class" resources: requests: storage: 1Gi
配置说明:
PVC和PV是通过StorageClassName进行绑定,如果定义PVC时没有指定StorageClassName,就要看admission插件是否开启了DefaultStorageClass功能:
如果开启了DefaultStorageClass功能:那么此PVC的StorageClassName就会被指定为DefaultStorageClass。(在定义StorageClass时,可以在Annotation中添加一个键值对storageclass.kubernetes.io/is-default-class: true,那么此StorageClass就变成默认的StorageClass了)
如果没有开启DefaultStorageClass功能:没有指定StorageClassName的PVC只能被绑定到同样没有指定StorageClassName的PV上。
可以对比一下配置文件
可以看到,两者的配置文件,差一点在一个使用手动创建PVC,然后使用volumes进行挂载,而另外一个使用volumeClaimTemplates自动创建PVC,并进行绑定。
三、StorageClass
(一)StorageClass简述
简单地说,StorageClass就是用来自动创建PV的。
在一个大规模的K8S集群中,可能有成千上万的PVC,随着时间的推移,可能还会有很多的PVC被不断提交,那么就需要运维人员人工的进行创建PV,如果创建的不及时,就会导致PVC绑定不到PV而导致Pod创建失败,同时,通过PVC请求到的存储空间也很有可能不满足需求。
K8S提供了一套可以自动创建PV的机制,即Dynamic Provisioning,而这个机制的核心在于StorageClass这个API对象
StorageClass对象会定义以下两部分内容:
1、PV的属性,例如存储类型,Volume的大小等
2、创建这种PV需要用到的插件
有了这两部分信息后,K8S就能根据用户提交的PVC,找到一个对应的StorageClass,之后K8S会调用该StorageClass声明的存储插件,进而创建出需要的PV。而实际使用的话,只是需要根据自己的需求,编写yaml文件,然后使用kubectl create命令执行即可。(应用程序对存储的性能要求也可能不同,例如读写速度、并发性能等,在StorageClass中可以定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储,慢速存储等,然后用户根据自己的需求进行申请)
(二)运行原理及部署流程
要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner,这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。
自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中。而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上。
搭建StorageClass+NFS,大致有以下几个步骤:
1、创建一个可用的NFS Server
2、创建Service Account,这是用来管控NFS provisioner在k8s集群中运行的权限
3、创建StorageClass,负责建立PVC并调用NFS provisioner进行预定的工作,并让PV与PVC建立管理
4、创建NFS provisioner,有两个功能,一个是在NFS共享目录下创建挂载点(volume),另一个则是建了PV并将PV与NFS的挂载点建立关联
(三)StrogeClass&statefulset实践
使用StrogeClass和StatefulSet创建服务
1、首先创建权限
# rbac.yaml:#唯一需要修改的地方只有namespace,根据实际情况定义 apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-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"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner namespace: default rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
2、创建StrogeClass
# 创建NFS资源的StorageClass apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: qgg-nfs-storage parameters: archiveOnDelete: "false" --- # 创建NFS provisioner apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner namespace: default spec: replicas: 1 selector: matchLabels: app: nfs-client-provisioner 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: nfs-client-provisioner volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: qgg-nfs-storage - name: NFS_SERVER value: 172.20.10.6 - name: NFS_PATH value: /opt/k8s volumes: - name: nfs-client-root nfs: server: 172.20.10.6 path: /opt/k8s
3、创建pod进行测试
# 创建pod进行测试 kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi # 创建测试pod,查看是否可以正常挂载 kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: busybox:1.24 command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && exit 0 || exit 1" #创建一个SUCCESS文件后退出 volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim #与PVC名称保持一致 # StateFulDet+volumeClaimTemplates自动创建PV --- apiVersion: v1 kind: Service metadata: name: nginx-headless labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: ikubernetes/myapp:v1 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi