zoukankan      html  css  js  c++  java
  • 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
    ------------------------------------------------------------------
    -----------------------------------------------------------
    ---------------------------------------------
    朦胧的夜 留笔~~
  • 相关阅读:
    数据库语句中(+)是什么意思
    MySQL的存储引擎(二)解决Warning Code : 3719 'utf8' is currently an alias for the character set UTF8MB3,...
    MSQL存储引擎(一)
    fastjson的使用,在redis里面存list
    js的发展历史,笔记
    spring的断言工具类Assert的基本使用
    httpclient的使用
    nginx的反向代理
    使用 Redis 连接池的原因
    springboot的yml自定义值的笔记
  • 原文地址:https://www.cnblogs.com/liconglong/p/15091092.html
Copyright © 2011-2022 走看看