K8S--有状态服务存储及部署(PV&PVC&StatefulSet&StrogeClass)

一、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
------------------------------------------------------------------
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~
原文地址:https://www.cnblogs.com/liconglong/p/15091092.html