K8s基础10——数据卷、PV和PVC、StorageClass动态补给、StatefulSet控制器

news2024/11/26 0:40:15

文章目录

  • 一、数据卷类型
    • 1.1 临时数据卷(节点挂载)
    • 1.2 节点数据卷(节点挂载)
    • 1.3 网络数据卷NFS
      • 1.3.1 效果测试
    • 1.4 持久数据卷(PVC/PV)
      • 1.4.1 效果测试
      • 1.4.2 测试结论
  • 二、PV、PVC生命周期
    • 2.1 各阶段工作原理
      • 2.1.1 资源供应
      • 2.1.2 资源绑定
      • 2.1.3 .资源使用
      • 2.1.4 资源回收
      • 2.1.5 PVC资源扩容
    • 2.2 测试PV回收策略
      • 2.2.1 Retain保留策略
      • 2.2.2 Recycle回收策略
    • 2.3 StorageClass动态供给
      • 2.3.1 部署存储插件
      • 2.3.2 使用插件
  • 三、StatefulSet
    • 3.1 控制器介绍
    • 3.2 部署实践
    • 3.3 集群部署流程
      • 3.3.1 etcd案例
      • 3.3.2 zookeeper示例

一、数据卷类型

为什么需要数据卷?

  • 容器中的文件在磁盘上是临时存放的,这给容器中运行比较重要的应用程序带来一些问题。
    1. 当容器升级或者崩溃时,kubelet会重建容器,容器内文件会丢失。
    2. 一个Pod中运行多个容器时,需要共享文件。
  • 而K8s 数据卷就可以解决这两个问题。

Volume概念:

  • Volume是与Pod绑定的(独立于容器)与Pod具有相同生命周期的资源对象。
  • 可以将Volume的内容理解为目录或文件,容器若需使用某个Volume,则仅需设置volumeMounts将一个或多个Volume挂载为容器中的目录或文件,即可访问Volume中的数据。

常用的数据卷类型:

  1. 节点本地(hostPath,emptyDir)
  2. 网络(NFS,Ceph,GlusterFS)
  3. 公有云(AWS EBS)
  4. K8S资源(configmap,secret)

概念图:
在这里插入图片描述

1.1 临时数据卷(节点挂载)

概念:

  • emptyDir卷是一个临时存储卷,与Pod生命周期绑定一起,如果Pod删除了卷也会被删除。

应用场景

  • Pod中容器之间数据共享,是从Pod层面上提供的技术方案。
  • 当一个Pod内有多个容器,且都分布在同一个节点上时,则数据共享;若pod内的多个容器不在同一个节点上时,数据不共享。

特点:

  1. kubelet会在Node的工作目录下为Pod创建EmptyDir目录。
  2. 可以将该节点上的某pod工作目录EmptyDir下的数据挂载到该pod容器里,从而实现本地数据共享。
  3. 只有在pod所在的节点上才能看到本地数据。
  4. Pod删除后,本地数据也会被删除。
  5. 此种模式没有参数应用,其他模式都有参数应用。

缺点:

  • 不能持久化。当node1节点上的Pod删除后,会触发健康检查重新拉起容器,此时新容器在node2节点,node2节点上看不到之前Node1节点上容器数据,因为被删除了。

参考地址:

  • K8S官网地址
    在这里插入图片描述

1.编辑yaml文件,创建pod容器。

[root@k8s-master bck]# cat my-pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: my-pod    ##容器名称
spec:
  containers:
  - name: write
    image: centos
    command: ["bash","-c","for i in {1..100};do echo $i >> /data/hello;sleep 1;done"]
    volumeMounts:        ##定义引用数据卷
      - name: data          ##引用哪个数据卷,通过数据卷名称来引用。
        mountPath: /data      ##将本地数据卷挂载到容器里哪个路径下。
  - name: read
    image: centos
    command: ["bash","-c","tail -f /data/hello"]
    volumeMounts:
      - name: data
        mountPath: /data
  volumes:          ##定义数据卷
  - name: data        ##数据卷名称
    emptyDir: {}      ##数据卷类型

[root@k8s-master bck]# kubectl  apply -f my-pod.yaml 

2.进入容器验证数据是否共享。

在这里插入图片描述
3.查看该pod容器在哪个节点上,进入该节点查找本地数据。

[root@k8s-node1 ~]# cd /var/lib/kubelet/pods/cdacb40e-3e2f-4c57-97bc-1fc81c446685/volumes/kubernetes.io~empty-dir

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4.删除pod,数据目录也会被删除。
在这里插入图片描述

1.2 节点数据卷(节点挂载)

概念:

  • hostPath卷:挂载Node文件系统(Pod所在节点)上文件或者目录到Pod中的容器。
  • 和 emptyDir数据卷一样,只能在pod容器所在的node节点上查看到挂载目录文件数据,但区别是hostPath数据卷挂载不会因为删除pod而导致宿主机上的挂载目录文件消失。

缺点:

  • 不能持久化。当node1节点上的Pod删除后,会触发健康检查重新拉起容器,此时新容器在node2节点,node2节点上看不到之前Node1节点上容器数据,因为被删除了。

应用场景

  • 容器应用的关键数据需要被持久化到宿主机上。
  • 需要使用Docker中的某些内部数据,可以将主机的/var/lib/docker目录挂载到容器内。
  • 监控系统,例如cAdvisor需要采集宿主机/sys目录下的内容。
  • Pod的启动依赖于宿主机上的某个目录或文件就绪的场景。

type字段的取值类型:

  • htstPath数据卷有个可选字段type。
  • FileOrCreate 模式不会负责创建文件的父目录。 如果欲挂载的文件的父目录不存在,Pod 启动会失败。 为了确保这种模式能够工作,可以尝试把文件和它对应的目录分开挂载
    在这里插入图片描述

注意事项:

  • HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。
  • 如果通过 AdmissionPolicy 限制 HostPath 对特定目录的访问,则必须要求 volumeMounts 使用 readOnly 挂载以使策略生效。

1.编辑yaml文件,将宿主机上的/tmp目录挂载到test容器里的/data目录下,数据卷为data。

[root@k8s-master bck]# cat hostPath.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod2
spec:
  containers:
  - name: test
    image: centos
    command: ["bash","-c","for i in {1..1000};do echo $i >> /data/hello;sleep 1;done"]
    volumeMounts:
      - name: data
        mountPath: /data
  volumes:
  - name: data
    hostPath:
      path: /tmp
      type: Directory

2.导入yaml,进入容器查看/data目录已经把节点机器上的/tmp目录映射进来,我这里的pod2部署在node1节点上的,就去node1节点上看,其他节点不共享数据看不到。
在这里插入图片描述
3. 修改yaml,同时挂载2个目录,根据数据卷的名称识别一一挂载。

[root@k8s-master bck]# cat hostPath.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: centos
    command: ["bash","-c","for i in {1..1000};do echo $i >> /data/hello;sleep 1;done"]
    volumeMounts:
      - name: data
        mountPath: /data
      - name: qingjun
        mountPath: /opt
  volumes:
  - name: data
    hostPath:
      path: /tmp
      type: Directory
  - name: qingjun
    hostPath:
      path: /
      type: Directory

4.进入Pod所在节点,进入容器,查看。
在这里插入图片描述

1.3 网络数据卷NFS

概念:

  • NFS是一个主流的文件共享服务器,NFS卷提供对NFS挂载支持,可以自动将NFS共享路径挂载到Pod中。

注意事项:

  1. 每个Node上都要安装nfs-utils包。

概念图:
在这里插入图片描述

1.选择一台服务器作为NFS服务器。我这里选择的是node2。

1.安装nfs服务。
[root@k8s-node2 ~]# yum -y install nfs-utils

2.创建共享目录,名字自取,并编辑共享规则:只能是192.168.130.0网段的机器上的root用户访问,具备读写权限。
[root@k8s-node2 ~]# mkdir -p /nfs/k8s
[root@k8s-node2 ~]# cat /etc/exports
/nfs/k8s 192.168.130.0/24(rw,no_root_squash)

3.启动服务,并设置开机自启。
[root@k8s-node2 ~]# systemctl  start nfs
[root@k8s-node2 ~]# systemctl  enable nfs

2.在其他所有节点上安装nfs客户端,不然无法挂载。

[root@k8s-master bck]# yum -y install nfs-utils
[root@k8s-node1 ~]# yum -y install nfs-utils

#挂载。在其他工作节点上挂载,将192.168.130.147上的/nfs/k8s目录挂载到本地的/mnt/目录下。
[root@k8s-node1 ~]# mount -t nfs 192.168.130.147:/nfs/k8s /mnt/

#取消挂载。
[root@k8s-node1 ~]# umount /mnt/

在这里插入图片描述
3.此时在node1节点上的/mnt/下操作,就相当于在NFS服务器上的/nfs/k8s目录下操作。
在这里插入图片描述
4.查看容器内的挂载情况。
在这里插入图片描述

1.3.1 效果测试

1.编辑yaml文件,创建pod容器。

[root@k8s-master bck]# cat qingjun.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: baimu
  name: baimu
spec:
  replicas: 3
  selector:
    matchLabels:
      app: baimu
  template:
    metadata:
      labels:
        app: baimu
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:     ##定义挂载规则。
        - name: mq            ##指定挂载哪个数据卷。
          mountPath: /usr/share/nginx/html       ##指定将数据挂载到容器里的哪个目录下。
      volumes:        ##定义挂载卷。
      - name: mq        ##挂载卷名称。
        nfs:
          server: 192.168.130.147     ##指定nfs服务器地址,网络能通。
          path: /nfs/k8s         ##指定nfs服务器上的共享目录。

2.数据共享测试。

  • 创建一组pod,内有3个容器,查看进入容器验证。
    1. 先进入第一个容器的挂载目录,查看已经将nfs服务器上的/nfs/k8s目录下的内容挂载进来。
    2. 创建888目录,nfs服务器上查看888目录被创建。
    3. 退出第一个容器,在nfs服务器共享目录下创建22222目录,再进入第二个容器查看22222目录被同步创建。

在这里插入图片描述
3.重建pod,新pod数据共享测试。

  1. 删除podl里的第三个容器,等待新容器被创建运行。
  2. 进入新容器挂载目录,查看该目录下也共享nfs服务器上的共享目录。

在这里插入图片描述
4.扩容新pod数据共享测试。

  1. 扩容副本到5个。
  2. 进入新容器的挂载目录,查看该目录下也共享nfs服务器上的共享目录。

在这里插入图片描述

1.4 持久数据卷(PVC/PV)

为什么会有PVC、PV?

  1. 提高安全性。上文我们使用nfs挂载出来的信息都是记录在yaml文件中,安全性低。
  2. 职责分离。当后端需要用到存储时,作为非专业人士来说,存储这块的工作量是需要专门的运维大佬来做的,而后端只需要简单的提交你程序所需要的存储大小即可,这样一来就可以职责分离。

概念:

  • PersistentVolume(PV):由管理员创建和配置,将存储定义为一种容器应用可以使用的资源,使得存储作为集群中的资源管理。
  • PersistentVolumeClaim(PVC):用户来操作,是对存储资源的一个申请,让用户不需要关心具体的Volume实现细节。

概念图:

  1. 管理员需要提前定义pv,是手动的,也能自动创建需要依赖过StorageClass资源,后面讲。
  2. 用户在创建pod时,需要在yaml里定义pvc,内容包括pvc名称、申请资源大小、访问模式。
  3. K8s通过PVC查找匹配到合适的PV,并挂载到Pod容器里。
    在这里插入图片描述

1.用户定义pod和pvc。

[root@k8s-master bck]# cat web1.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web1
  name: web1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web1
  template:
    metadata:
      labels:
        app: web1
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: mq
          mountPath: /usr/share/nginx/html
      volumes:
      - name: mq
        persistentVolumeClaim:
          claimName: qingjun        ##这里的名称需要与PVC名称一致。
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: qingjun                ##PVC名称
spec:
  accessModes:
    - ReadWriteMany    ##访问模式,ReadWriteOnce、ReadOnlyMany或ReadWriteMany。
  resources:
    requests:
      storage: 5Gi           ##程序要申请的存储资源。

2.导入yaml,查看pod和pvc都处于等待状态,是因为此时还没有关联到pv。
在这里插入图片描述
3.管理员定义创建pv,导入yaml,pvc和Pod状态改变。

[root@k8s-master bck]# cat pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: text            ##PV名称,自定义。
spec:
  capacity:
    storage: 5Gi           ##容量。
  accessModes:
    - ReadWriteMany
  nfs:
    path: /nfs/k8s           ##定义nfs挂载卷共享目录。
    server: 192.168.130.147      ##指定nfs服务器地址。

在这里插入图片描述
4.进入Pod容器验证数据共享。Pod滚动升级、扩容也都会共享数据,测试方法同上文的nfs测试流程。
在这里插入图片描述

1.4.1 效果测试

1.先在nfs服务器上创建多个目录,作为多个pv挂载目录。

[root@k8s-node2 k8s]# pwd
/nfs/k8s
[root@k8s-node2 k8s]# mkdir pv0001
[root@k8s-node2 k8s]# mkdir pv0002
[root@k8s-node2 k8s]# mkdir pv0003

2.创建3个pv,分别为:

  1. pv0001,内存5G,挂载nfs服务器为192.168.130.147,挂载目录为/nfs/k8s/pv0001。
  2. pv0002,内存25G,挂载nfs服务器为192.168.130.147,挂载目录为/nfs/k8s/pv0002。
  3. pv0003,内存50G,挂载nfs服务器为192.168.130.147,挂载目录为/nfs/k8s/pv0003。
[root@k8s-master bck]# cat pv-all.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /nfs/k8s/pv0001
    server: 192.168.130.147
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0002
spec:
  capacity:
    storage: 25Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /nfs/k8s/pv0002
    server: 192.168.130.147
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /nfs/k8s/pv0003
    server: 192.168.130.147

[root@k8s-master bck]# kubectl  apply -f pv-all.yaml

在这里插入图片描述
3.创建第一个pvc,名称为web1-pvc,申请资源10G。此时pv和内存为25G的pv0002绑定在一起。

[root@k8s-master bck]# cat web1.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web1
  name: web1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web1
  template:
    metadata:
      labels:
        app: web1
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: mq
          mountPath: /usr/share/nginx/html

      volumes:
      - name: mq
        persistentVolumeClaim:
          claimName: web1-pvc

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web1-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi

在这里插入图片描述
4.创建第二个pvc,名称为web2-pvc,申请资源25G。此时pv和内存为50G的pv0003绑定在一起。

[root@k8s-master bck]# cat web2.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web2
  name: web2
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web2
  template:
    metadata:
      labels:
        app: web2
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: mq
          mountPath: /usr/share/nginx/html

      volumes:
      - name: mq
        persistentVolumeClaim:
          claimName: web2-pvc

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web2-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 25Gi


[root@k8s-master bck]# kubectl  apply -f web2.yaml 

在这里插入图片描述
5.创建第三个pvc,名称为web3-pvc,申请资源6G。此时pv没有和剩余的pv0001绑定。为什么呢?

[root@k8s-master bck]# cat web3.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web3
  name: web3
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web3
  template:
    metadata:
      labels:
        app: web3
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: mq
          mountPath: /usr/share/nginx/html

      volumes:
      - name: mq
        persistentVolumeClaim:
          claimName: web3-pvc

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: web3-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 6Gi

在这里插入图片描述

1.4.2 测试结论

pvc与pv怎么匹配的?

  • 主要根据存储容量和访问模式。我们定义pvc、pv的yaml文件里,都有这两个字段,并不是说创建了3个pv,再创建的3个pvc就能一一对上,还要根据第二点,存储容量。

存储容量怎么匹配的 ?

  • 向上就近容量匹配。比如上文的web1-pvc,它的申请容量为10G,那再已有的三个pvc里面,容量分别为5G、25G、50G,就去匹配向上的、匹配就近的25G;当web2-pvc来匹配时,它的申请容量为25G,再剩下的两个pvc里面,就去匹配向上就近的50G;剩下的web3-pvc匹配时,它的申请容量为6G,再仅剩的一个pvc5G,是满足不了“向上就近原则”,所以就没匹配中。

pv与pvc的关系

  • 一对一

容量能不能限制?

  • 目前容量主要用作pvc与pv匹配的,具体的限制取决于后端存储。

二、PV、PVC生命周期

  1. AccessModes(访问模式):AccessModes 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
    • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。常用于块设备存储(云硬盘)。
    • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。常用于数据共享(文件系统存储)。
    • ReadWriteMany(RWX):读写权限,可以被多个节点挂载
  2. RECLAIM POLICY(回收策略):指PVC删除之后,PV是去是留的一种策略。
    目前 PV 支持的策略有三种:
    • Retain(保留): 保留数据,需要管理员手工清理数据。
    • Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /ifs/kuberneres/*
    • Delete(删除):与 PV 相连的后端存储同时删除
  3. STATUS(状态):
    一个 PV 的生命周期中,可能会处于4中不同的阶段:
    • Available(可用):表示可用状态,还未被任何 PVC 绑定。
    • Bound(已绑定):表示 PV 已经被 PVC 绑定。
    • Released(已释放):PVC 被删除,但是资源还未被集群重新声明。
    • Failed(失败): 表示该 PV 的自动回收失败。

2.1 各阶段工作原理

生命周期阶段:

  • 我们可以将PV看作可用的存储资源,PVC则是对存储资源的需求。
  • PV和PVC的生命周期包括资源供应(Provisioning)、资源绑定(Binding)、资源使用(Using)、资源回收(Reclaiming)几个阶段。
    在这里插入图片描述

2.1.1 资源供应

  • K8s支持两种资源供应模式:静态模式(Static)和动态模式(Dynamic),资源供应的结果就是将适合的PV与PVC成功绑定。
    • 静态模式:运维预先创建许多PV,在PV的定义中能够体现存储资源的特性。
    • 动态模式:运维无须预先创建PV,而是通过StorageClass的设置对后端存储资源进行描述,标记存储的类型和特性。用户通过创建PVC对存储类型进行申请,系统将自动完成PV的创建及与PVC的绑定。如果PVC声明的Class为空"",则说明PVC不使用动态模式。另外,Kubernetes支持设置集群范围内默认的StorageClass设置,通过kube-apiserver开启准入控制器DefaultStorageClass,可以为用户创建的PVC设置一个默认的存储类StorageClass。

静态模式工作原理图:
在这里插入图片描述
动态模式原理图:
在这里插入图片描述

2.1.2 资源绑定

  • 当用户定义PVC后,系统将根据PVC对存储资源的请求(存储空间和访问模式)在提前创建好的PV中选择一个满足要求的PV,并与PVC绑定。
  • 若系统中没有满足要求的PV,PVC则会无限期处于Pending状态,直到系统管理员创建了一个符合其要求的PV。
  • PV只能一个PVC绑定,绑定关系是一对一的,不会存在一对多的情况。
  • 若PVC申请的存储空间比PV拥有的空间少,则整个PV的空间都能为PVC所用,可能会造成资源的浪费。
  • 若资源供应使用的是动态模式,则系统在为PVC找到合适的StorageClass后,将自动创建一个PV并完成与PVC的绑定。

2.1.3 .资源使用

  • 若Pod需要使用存储资源,则需要在yaml里定义Volume字段引用PVC类型的存储卷,将PVC挂载到容器内的某个路径下进行使用。
  • 同一个PVC还可以被多个Pod同时挂载使用,在这种情况下,应用程序需要处理好多个进程访问同一个存储的问题。

使用中的存储对象保护机制:

  • PV、PVC存储资源可以单独删除。当删除时,系统会检测存储资源当前是否正在被使用,若仍被使用,则对相关资源对象的删除操作将被推迟,直到没被使用才会执行删除操作,这样可以确保资源仍被使用的情况下不会被直接删除而导致数据丢失。
  • 若删除的PVC有被使用时,则会等到使用它的Pod被删除之后再执行,此时PVC状态为Terminating。
  • 若删除的PV有被使用时,则会等到绑定它的PVC被删除之后再执行,此时PV状态为Terminating。

2.1.4 资源回收

  • 回收策略,指PVC删除之后,PV是去是留的一种策略。目前 PV 支持的策略有三种:
    • Retain(保留): 保留数据,需要管理员手工清理数据。
    • Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /ifs/kuberneres/*
    • Delete(删除):与 PV 相连的后端存储同时删除

2.1.5 PVC资源扩容

  • PVC在首次创建成功之后,还可以在使用过程中进行存储空间的扩容。
  • 支持PVC扩容的存储类型有:AWSElasticBlockStore、AzureFile、AzureDisk、Cinder、FlexVolume、GCEPersistentDisk、Glusterfs、Portworx Volumes、RBD和CSI等。

扩容步骤:

  1. 先在PVC对应的StorageClass中设置参数“allowVolumeExpansion=true”。
  2. 修改pvc.yaml,将resources.requests.storage设置为一个更大的值。

扩容失败恢复步骤:

  1. 设置与PVC绑定的PV资源的回收策略为“Retain”。
  2. 删除PVC,此时PV的数据仍然存在。
  3. 删除PV中的claimRef定义,这样新的PVC可以与之绑定,结果将使得PV的状态为“Available”。
  4. 新建一个PVC,设置比PV空间小的存储空间申请,同时设置volumeName字段为PV的名称,结果将使得PVC与PV完成绑定。
  5. 恢复PVC的原回收策略

2.2 测试PV回收策略

2.2.1 Retain保留策略

  • Retain策略表示在删除PVC之后,与之绑定的PV不会被删除,仅被标记为已释放(released)。PV中的数据仍然存在,在清空之前不能被新的PVC使用,需要管理员手工清理之后才能继续使用。
  • 清理步骤:
    1. 删除PV资源对象,此时与该PV关联的某些外部存储提供商(例如AWSElasticBlockStore、GCEPersistentDisk、AzureDisk、Cinder等)的后端存储资产(asset)中的数据仍然存在。
    2. 手工清理PV后端存储资产(asset)中的数据。
    3. 手工删除后端存储资产。如果希望重用该存储资产,则可以创建一个新的PV与之关联。

1.如图。已有一个pv和pvc关联绑定,进入容器查看验证成功。

在这里插入图片描述
2.删除pvc,查看pv状态变为Released已释放状态。此时的pv不可用,需要把pv里面的数据转移到其他机器做备份。
在这里插入图片描述
3.也就是nfs机器上的共享目录下的数据。
在这里插入图片描述

2.2.2 Recycle回收策略

  • Recycle和Delete策略都是和存储类配合使用才能测出效果。
  • 回收策略 Recycle 已被废弃,取而代之的建议方案是使用动态制备,单独创建一个pod来进行删除操作。
  • 目前只有HostPort和NFS类型的Volume支持Recycle策略,其实现机制为运行rm-rf/thevolume/*命令,删除Volume目录下的全部文件,使得PV可以被新的PVC使用。
  • 删除模板:
    在这里插入图片描述

1.创建pv时,添加策略参数。

[root@k8s-master bck]# cat pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: text1            ##PV名称,自定义。
spec:
  persistentVolumeReclaimPolicy: Recycle   ##Recycle回收策略。
  capacity:
    storage: 10Gi           ##容量。
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /nfs/k8s           
    server: 192.168.130.147

2.导入pv.yaml后,再创建pvc与之绑定。
在这里插入图片描述
3.删除pvc,pv的数据被删除,也就是nfs服务器的共享目录下的数据被删除。
在这里插入图片描述
在这里插入图片描述

4.这里没有被删除是因为删除操作要拉取国外的一个镜像生成一个新容器,这个容器去删除pv里的数据,所以这里就拉取失败了。若要使用该策略,需要提前拉取这个镜像。
在这里插入图片描述
在这里插入图片描述

2.3 StorageClass动态供给

什么是静态供给?

  • 我们前面演示时,都是手动写pv.yaml文件提前创建好存储大小不同的pv,当有pvc需求时就会根据需求大小去匹配对应的pvc从而绑定。关键词是手动创建,所以维护成本高。

什么是动态供给?

  • 为了解决静态供给的缺点,K8s开始支持PV动态供给,使用StorageClass对象实现。
  • 当有pvc需求时,就可以自动创建pv与之绑定。关键词是自动生成。

静态供给概念图:
在这里插入图片描述
动态供给概念图:
在这里插入图片描述
实现PV动态补给流程步骤:

  1. 部署动态供给程序,是以容器方式部署的,调取api server 获取指定自己的pvc。
  2. 创建pod时,在pod.yaml里定义pvc资源,并在pvc资源下面指定存储类。
  3. 调取后端nfs服务器创建共享目录,再调取api server创建pv,从而实现自动创建pv动作。
  4. 流程图:
    在这里插入图片描述

2.3.1 部署存储插件

  • 支持动态补给的存储插件
    在这里插入图片描述
  • 社区获取各存储插件yanl文件
    在这里插入图片描述
  • nfs获取地址路径
  • 文件地址,下载这三个yaml文件上传到服务器。
    在这里插入图片描述
    在这里插入图片描述

1.将这个三个文件上传至服务器,修改其中两个yaml文件参数。

[root@k8s-master storageclass]# cat class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage     ##自定义名称。
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner 
parameters:
  archiveOnDelete: "false"



[root@k8s-master storageclass]# cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default
spec:
  replicas: 1
  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: lizhenliang/nfs-subdir-external-provisioner:v4.0.1
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner      ##与class.yaml文件里的名称保持一致。
            - name: NFS_SERVER
              value: 192.168.130.147       ##修改nfs服务器地址
            - name: NFS_PATH
              value: /nfs/k8s         ##修改nfs服务器共享目录。
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.130.147         ##修改nfs服务器地址
            path: /nfs/k8s                  ##修改nfs服务器共享目录。

2.导入yaml文件,查看存储类。

  • kubectl apply -f rbac.yaml # 授权访问apiserver
  • kubectl apply -f deployment.yaml # 部署插件,需修改里面NFS服务器地址与共享目录。
  • kubectl apply -f class.yaml # 创建存储类。
  • kubectl get sc # 查看存储类

在这里插入图片描述

在这里插入图片描述

2.3.2 使用插件

1.编辑yaml文件创建pvc,指定使用哪个存储类,也就是我们上面创建的sc。

[root@k8s-master bck]# cat pvc.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web1
  name: web1
spec:
  selector:
    matchLabels:
      app: web1
  template:
    metadata:
      labels:
        app: web1
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: mq
          mountPath: /usr/share/nginx/html
      volumes:
      - name: mq
        persistentVolumeClaim:
          claimName: qingjun        
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: qingjun                
spec:
  storageClassName: "managed-nfs-storage"      ##添加此行,指定使用已创建的sc。
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

2.创建pvc后会自动创建一个pv,并且是绑定状态,带有storage标识,最后成功创建了deploy。
在这里插入图片描述
3.进入pod检查。会在共享目录里随机生成一个目录,

/nfs/k8s/default-qingjun-pvc-d207b4d4-8966-43d0-b32c-6caf45ee54b8

default:代表命名空间。
qingjun:代表pvc名称。
pvc-d207b4d4-8966-43d0-b32c-6caf45ee54b8:代表pv名称。

在这里插入图片描述
4.此时删除pvc,会发现pv及其数据也会被删除,是因为默认的回收策略是“Delete”。
在这里插入图片描述
在这里插入图片描述

5.修改归档删除策略。

[root@k8s-master storageclass]# cat class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner 
parameters:
  archiveOnDelete: "true"     ##默认为flase,代表直接删除不备份。修改成true代表先备份再删除。



[root@k8s-master storageclass]#  kubectl  delete -f class.yaml   #删除原来的。
[root@k8s-master storageclass]#  kubectl  apply -f class.yaml    #再导入新的,更新。

在这里插入图片描述

三、StatefulSet

3.1 控制器介绍

StatefulSet控制器作用:

  • StatefulSet控制器用于部署有状态应用,满足一些有状态应用的需求。

控制器特点:

  • Pod有序的部署、扩容、删除和停止。
  • Pod分配一个稳定的且唯一的网络标识。
  • Pod分配一个独享的存储。

无状态与有状态:

  • 无状态:Deployment控制器设计原则是,管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容。这种应用称为“无状态”。例如Web服务集群,每个工作节点上都有一个nginx容器,它们之间不需要互相业务“交流”,具备这个特点的应用就是“无状态”。
  • 有状态:像分布式应用这种,需要部署多个实例,实例之间有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群。

无状态应用特点:

  • 每个pod一模一样。
  • 每个pod之间没有连接关系。
  • 使用共享存储。

有状态应用特点:

  • 每个pod不对等,承担的角色不同。
  • pod之间有连接关系。
  • 每个pod有独立的存储。

StatefulSet与Deployment区别:

  • 前者有身份,后者没有。
  • 身份三要素:域名、主机名、存储(PVC)

3.2 部署实践

  1. 需要稳定的网络ID(域名):

    • 使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份。
    • 并且添加serviceName:“nginx”字段指定StatefulSet控制器要使用这个Headless Service。
    • DNS解析名称:< statefulsetName-index >.< service-name > .< namespace-name >.svc.cluster.local
  2. 需要稳定的存储:

  • StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。
  • 第一步,先创建正常的svc,名为web。

1.创建一个deployment和svc。

[root@k8s-master bck]# kubectl  create deployment web --image=nginx
[root@k8s-master bck]# kubectl  expose deployment web --port=80 --target-port=80

2.导出svc的yaml文件,可以看出这里的ClusterIP,这种情况下容器内的通信都是以这个ClusterIP来进行。
在这里插入图片描述

  • 第二步,再创建statefulset,引用第二个状态为None的svc。

3.创建statefulset。yaml中是先创建了第二个svc,状态为None,再创建statefulset指定储存类和这个None状态的svc。

[root@k8s-master bck]# cat statefulset.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None    ##添加此行,使其变成一种识别标签,供后面的pod使用。
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx 
  serviceName: "nginx"          ##指定哪个svc的名称。
  replicas: 3 
  minReadySeconds: 10 
  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:           ##statefulset独有配置,deployment不支持此配置。是给每个pod分配各自的pv、pvc。
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"      ##指定哪个存储类。使用kubectl get sc查看。
      resources:
        requests:
          storage: 1Gi

5.导入yaml,查看会依次创建三个Pod,因为yaml里指定的就是3个副本。

[root@k8s-master bck]# kubectl  apply -f statefulset.yaml

在这里插入图片描述
6.对比两个svc。
在这里插入图片描述

7.创建测试容器bs。

[root@k8s-master bck]# kubectl  run bs --image=busybox:1.28.4 -- sleep 240h
[root@k8s-master bck]# kubectl  exec -it bs -- sh

在这里插入图片描述
8.查看nfs服务器共享目录,进入对应目录,创建一个index.html文件,再次就能访问了。
在这里插入图片描述

在这里插入图片描述

3.3 集群部署流程

  1. 集群一般都是三个节点,也就是需要提前写三个yaml文件。
  2. 需要写一些脚本。
  3. 通过配置文件中的节点名称、IP、存储目录区分3个pod的角色,。

部署一个etcd集群思路:

  1. 需要有3个副本,每个pod里只能指定一个镜像,所以需要保证启动的3个副本容器,每一个都能按照自己的角色 (配置文件) 启动。
  2. 根据当前主机名可以判定用户当前启动的是第几个容器,就用哪个配置文件后动。比如etcd-0.conf、etcd-1.conf、etcd-2.conf。

在这里插入图片描述

3.3.1 etcd案例

  • etcd集群yaml文件实例
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    k8s-app: infra-etcd-cluster
    app: etcd
  name: infra-etcd-cluster
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      k8s-app: infra-etcd-cluster
      app: etcd
  serviceName: infra-etcd-cluster
  template:
    metadata:
      labels:
        k8s-app: infra-etcd-cluster
        app: etcd
      name: infra-etcd-cluster
    spec:
      containers:
      - image: lizhenliang/etcd:v3.3.8
        imagePullPolicy: Always
        command:
        - /bin/sh
        - -ec
        - |
          HOSTNAME=$(hostname)
          echo "etcd api version is ${ETCDAPI_VERSION}"
          # 生成连接etcd集群节点字符串
          # 例如http://etcd-0.etcd.default:2379,http://etcd-1.etcd.default:2379,http://etcd-2.etcd.default:2379
          eps() {
              EPS=""
              for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                  EPS="${EPS}${EPS:+,}http://${SET_NAME}-${i}.${SET_NAME}.${CLUSTER_NAMESPACE}:2379"
              done
              echo ${EPS}
          }
          # 获取etcd节点成员hash值,例如740e031d5b17222d
          member_hash() {
              etcdctl member list | grep http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 | cut -d':' -f1 | cut -d'[' -f1
          }
          # 生成初始化集群节点连接字符串
          # 例如etcd-0=http://etcd-0.etcd.default:2380,etcd-1=http://etcd-1.etcd.default:2380,etcd-2=http://etcd-1.etcd.default:2380
          initial_peers() {
                PEERS=""
                for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                PEERS="${PEERS}${PEERS:+,}${SET_NAME}-${i}=http://${SET_NAME}-${i}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380"
                done
                echo ${PEERS}
          }
          # etcd-SET_ID
          SET_ID=${HOSTNAME##*-}
          # 向已有集群添加成员 (假设所有pod都初始化完成)
          if [ "${SET_ID}" -ge ${INITIAL_CLUSTER_SIZE} ]; then
              export ETCDCTL_ENDPOINTS=$(eps)
              # 判断成员是否添加
              MEMBER_HASH=$(member_hash)
              if [ -n "${MEMBER_HASH}" ]; then
                  # 成员hash存在,但由于某种原因失败
                  # 如果datadir目录没创建,可以删除该成员
                  # 检索新的hash值
                  if [ "${ETCDAPI_VERSION}" -eq 3 ]; then
                      ETCDCTL_API=3 etcdctl --user=root:${ROOT_PASSWORD} member remove ${MEMBER_HASH}
                  else
                      etcdctl --username=root:${ROOT_PASSWORD} member remove ${MEMBER_HASH}
                  fi
              fi
              echo "添加成员"
              rm -rf /var/run/etcd/*
              # 确保etcd目录存在
              mkdir -p /var/run/etcd/
              # 休眠60s,等待端点准备好
              echo "sleep 60s wait endpoint become ready,sleeping..."
              sleep 60
              if [ "${ETCDAPI_VERSION}" -eq 3 ]; then
                  ETCDCTL_API=3 etcdctl --user=root:${ROOT_PASSWORD} member add ${HOSTNAME} --peer-urls=http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 | grep "^ETCD_" > /var/run/etcd/new_member_envs
              else
                  etcdctl --username=root:${ROOT_PASSWORD} member add ${HOSTNAME} http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 | grep "^ETCD_" > /var/run/etcd/new_member_envs
              fi
              if [ $? -ne 0 ]; then
                  echo "member add ${HOSTNAME} error."
                  rm -f /var/run/etcd/new_member_envs
                  exit 1
              fi
              cat /var/run/etcd/new_member_envs
              source /var/run/etcd/new_member_envs
              # 启动etcd
              exec etcd --name ${HOSTNAME} \
                  --initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 \
                  --listen-peer-urls http://0.0.0.0:2380 \
                  --listen-client-urls http://0.0.0.0:2379 \
                  --advertise-client-urls http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2379 \
                  --data-dir /var/run/etcd/default.etcd \
                  --initial-cluster ${ETCD_INITIAL_CLUSTER} \
                  --initial-cluster-state ${ETCD_INITIAL_CLUSTER_STATE}
          fi
          # 检查前面etcd节点是否启动,启动后再启动本节点
          for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
              while true; do
                  echo "Waiting for ${SET_NAME}-${i}.${SET_NAME}.${CLUSTER_NAMESPACE} to come up"
                  ping -W 1 -c 1 ${SET_NAME}-${i}.${SET_NAME}.${CLUSTER_NAMESPACE} > /dev/null && break
                  sleep 1s
              done
          done
          echo "join member ${HOSTNAME}"
          # 启动etcd节点
          exec etcd --name ${HOSTNAME} \
              --initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 \
              --listen-peer-urls http://0.0.0.0:2380 \
              --listen-client-urls http://0.0.0.0:2379 \
              --advertise-client-urls http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2379 \
              --initial-cluster-token etcd-cluster-1 \
              --data-dir /var/run/etcd/default.etcd \
              --initial-cluster $(initial_peers) \
              --initial-cluster-state new
        env:
        - name: INITIAL_CLUSTER_SIZE  # 初始集群节点数量
          value: "3"
        - name: CLUSTER_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: ETCDAPI_VERSION
          value: "3"
        - name: ROOT_PASSWORD
          value: '@123#'
        - name: SET_NAME
          value: "infra-etcd-cluster"
        - name: GOMAXPROCS
          value: "4"

        # 关闭pod,自动清理该节点信息
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -ec
              - |
                HOSTNAME=$(hostname)
                member_hash() {
                    etcdctl member list | grep http://${HOSTNAME}.${SET_NAME}.${CLUSTER_NAMESPACE}:2380 | cut -d':' -f1 | cut -d'[' -f1
                }
                eps() {
                    EPS=""
                    for i in $(seq 0 $((${INITIAL_CLUSTER_SIZE} - 1))); do
                        EPS="${EPS}${EPS:+,}http://${SET_NAME}-${i}.${SET_NAME}.${CLUSTER_NAMESPACE}:2379"
                    done
                    echo ${EPS}
                }
                export ETCDCTL_ENDPOINTS=$(eps)
                SET_ID=${HOSTNAME##*-}
                # 从集群中移出etcd节点成员
                if [ "${SET_ID}" -ge ${INITIAL_CLUSTER_SIZE} ]; then
                    echo "Removing ${HOSTNAME} from etcd cluster"
                    if [ "${ETCDAPI_VERSION}" -eq 3 ]; then
                        ETCDCTL_API=3 etcdctl --user=root:${ROOT_PASSWORD} member remove $(member_hash)
                    else
                        etcdctl --username=root:${ROOT_PASSWORD} member remove $(member_hash)
                    fi
                    if [ $? -eq 0 ]; then
                        # 删除数据目录
                        rm -rf /var/run/etcd/*
                    fi
                fi
        name: infra-etcd-cluster
        ports:
        - containerPort: 2380
          name: peer
          protocol: TCP
        - containerPort: 2379
          name: client
          protocol: TCP
        resources:
          limits:
            cpu: "1"
            memory: 1Gi
          requests:
            cpu: "0.3"
            memory: 300Mi
        volumeMounts:
        - mountPath: /var/run/etcd
          name: datadir
  updateStrategy:
    type: OnDelete
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      storageClassName: "managed-nfs-storage" 
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: infra-etcd-cluster
    app: infra-etcd
  name: infra-etcd-cluster
  namespace: default
spec:
  clusterIP: None
  ports:
  - name: infra-etcd-cluster-2379
    port: 2379
    protocol: TCP
    targetPort: 2379
  - name: infra-etcd-cluster-2380
    port: 2380
    protocol: TCP
    targetPort: 2380
  selector:
    k8s-app: infra-etcd-cluster
    app: etcd
  type: ClusterIP

3.3.2 zookeeper示例

  • zookeeper参考地址
apiVersion: v1
kind: Service
metadata:
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zk
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  selector:
    matchLabels:
      app: zk
  maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zk
spec:
  selector:
    matchLabels:
      app: zk
  serviceName: zk-hs
  replicas: 3
  updateStrategy:
    type: RollingUpdate
  podManagementPolicy: OrderedReady
  template:
    metadata:
      labels:
        app: zk
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"
      containers:
      - name: kubernetes-zookeeper
        imagePullPolicy: Always
        image: "registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"
        resources:
          requests:
            memory: "1Gi"
            cpu: "0.5"
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        command:
        - sh
        - -c
        - "start-zookeeper \
          --servers=3 \
          --data_dir=/var/lib/zookeeper/data \
          --data_log_dir=/var/lib/zookeeper/data/log \
          --conf_dir=/opt/zookeeper/conf \
          --client_port=2181 \
          --election_port=3888 \
          --server_port=2888 \
          --tick_time=2000 \
          --init_limit=10 \
          --sync_limit=5 \
          --heap=512M \
          --max_client_cnxns=60 \
          --snap_retain_count=3 \
          --purge_interval=12 \
          --max_session_timeout=40000 \
          --min_session_timeout=4000 \
          --log_level=INFO"
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/zookeeper
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/519375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为机试真题 数组奇偶排序

人寄语: 准备面试华为外包德科,记录一下一些面试题; 牛客网代码提交的坑,可以看一下下面的第一道题,ide本地编译通过,牛客网死活不通过,提交代码提示:返回非0。原因分析   查询得知,结果非零的意思的代码退出的时候不是以正常的0退出的,而是非0状态,也就是代码出错…

操作系统进程线程(三)—进程状态、同步互斥、锁、死锁

Linux下同步机制 POSIX信号量&#xff1a;可用于进程同步&#xff0c;也可用于线程同步POSIX互斥锁条件变量&#xff1a;只能用于线程同步。 进程同步的四种方法 临界区 对临界资源进行访问。 同步和互斥 同步&#xff1a;多个进程因为合作产生直接制约关系&#xff0c;使…

教你如何正确使用ChatGPT

目录 前言 一、ChatGPT Sidebar 二、免费镜像 三、共享账号 总结 前言 ChatGPT 是一种基于深度学习技术的自然语言处理工具&#xff0c;能够用于文本生成、语言翻译等任务。然而&#xff0c;其使用需要一定的技术基础和相关知识&#xff0c;不少用户可能会遇到一些问题。…

从功能到自动化,4个月时间我是如何从点工进入互联网大厂的

1、知识体系化 不知不觉&#xff0c;入行软件测试也有五个年头。待过创业公司也待过上市公司。做过功能测试、自动化测试也做过性能测试。做过测试新人也做过测试组长。如果要是从这5年中说出最宝贵的经验&#xff0c;我想应该是知识体系化。那么什么是知识体系化&#xff0c;…

SViT 实验记录

目录 一、网络的搭建 1、Conv Stem 2、各阶段的模块 3、3X3卷积 二、前向传播过程 1、Stem 2、各阶段中的基本模块STT Block 1&#xff09;CPE模块 2&#xff09;STA模块 网络结构 一、网络的搭建 论文中的结构原图 基本模块 1、Conv Stem (patch_embed): PatchEmbed…

算法修炼之练气篇——练气十三层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 目录 题目 1023: [编程入门]选择排序 题目描述 输入格式 输出格式 样例输入 样例输出 题目 1065: 二级C语言-最小绝对值 题目描述 输入格式 输出格式 样例输入 样例输出 题目 1021: [编程入门]迭代法求…

【Selenium上】——全栈开发——如桃花来

目录索引 Selenium是什么&#xff1a;下载和配置环境变量&#xff1a;1. 基本使用&#xff1a;导入五个常用包&#xff1a;基本代码&#xff1a; 实例引入&#xff1a;声明不同浏览器对象&#xff1a;访问页面&#xff1a; Selenium是什么&#xff1a; Selenium是一个用于Web应…

Cesium入门之四:基于Vue3+Vite+Cesium构建三维地球场景

Cesium官网中提供了基于webpack配置Cesium的方法&#xff0c;但是这种方法太繁琐&#xff0c;而且使用webpack时程序启动没有Vite启动快&#xff0c;因此&#xff0c;这里选择vite创建vue3cesium构建项目 创建vue3项目 新建CesiumProject文件夹&#xff0c;在该文件夹上点击右…

clang-format configurator - 交互式创建 clang-format 格式配置文件

clang-format configurator - 交互式创建 clang-format 格式配置文件 clang-format configurator https://zed0.co.uk/clang-format-configurator/ clang-format-configurator https://github.com/zed0/clang-format-configurator Interactively create a clang-format confi…

minikube,搭建+镜像加速,坚持 3 分钟,带你玩的明明白白

一、 安装 cri-docker 下载安装 # 在 https://github.com/Mirantis/ 下载 https://github.com/Mirantis/tar -xvf cri-dockerd-0.3.1.amd64.tgzcp cri-dockerd/cri-dockerd /usr/bin/chmod x /usr/bin/cri-dockerd# 确认已安装版本 cri-dockerd --version配置启动文件 cri-do…

一篇让你精通JWT,妥妥的避坑指南~

视频教程传送门&#xff1a;JWT 两小时极简入门&#xff1a;JWT实战应用与防坑指南~_哔哩哔哩_bilibiliJWT 两小时极简入门&#xff1a;JWT实战应用与防坑指南~共计12条视频&#xff0c;包括&#xff1a;01.课程介绍与前置知识点、02.JWT概念、03.JWT组成等&#xff0c;UP主更多…

一个例子让你彻底弄懂分布式系统的CAP理论

1 推荐的文章 下面这篇知乎文章是我见过的最简单易懂的一篇&#xff0c;把CAP定义以及为什么AP和CP只能二选一以及场景特定下选AP还是CP作为系统目标等讲解明明白白 谈谈分布式系统的CAP 2 个人对上面这篇文章的的一些补充 可用性可以人为设置一个阈值&#xff0c;比如用户体…

openPOWERLINK源码(最新)在stm32单片机上的移植指南

最近着了powerlink的道&#xff0c;连续几晚十二点前没睡过觉。不得不说兴趣这东西劲太大了&#xff0c;让人睡不着。喜欢上研究POWERLINK&#xff0c;最新版的源码结构挺清晰的&#xff0c;移植并测试了嵌入式linux作为从站和电脑主站之间的通信&#xff0c;挺有趣的。接下来想…

路由器配置方法(固定地址)

前言 由于学校给分配了IP地址&#xff0c;因此我们的路由器接入的时候不能选择自动接入方式&#xff0c;而要选择固定地址方式。 step 1 我们首先先将路由器接上网线&#xff0c;这里注意一定要接wan口 因为路由器分为两个口&#xff0c;wan口是入口&#xff0c;lan口是出口…

第十二届蓝桥杯青少组国赛Python真题,包含答案

第十二届蓝桥杯青少组国赛Python真题 一、选择题 第 1 题 单选题 设sHi LanQiao&#xff0c;运行以下哪个选项代码可以输出“LanQiao”子串 () 答案&#xff1a;A 第 2 题 单选题 已知a-2021.0529&#xff0c;运行以下哪个选项代码可以输出“2021.05”() 答案&#xff1a;…

2023.05.12 C高级 day4

有m1.txt m2.txt m3.txt m4.txt&#xff0c;分别创建出对应的目录&#xff0c;m1 m2 m3 m4 并把文件移动到对应的目录下 #!/bin/bash for i in 1 2 3 4 dotouch m$i.txtmkdir m$imv m$i.txt ./m$i/m$i.txt done 运行结果 2. 使用break关键字打印九九乘法表&#xff0c;提示&am…

【2023/05/12】Z3

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第7天。 Share Listen,my heart,to the whispers of the world with which it makes love to you. 译文&#xff1a; 静静的听&#xff0c;我的心呀&#xff0c;听那世界的低语&#xff0c;这是它对你求…

黑客必备工具:Hydra的完整安装和使用指南

安装Hydra 1.安装必要的依赖库 在终端中执行以下命令&#xff0c;安装Hydra所需的依赖库&#xff1a; sudo apt-get install build-essential checkinstall libssl-dev libssh-dev libidn11-dev libpcre3-dev libgtk2.0-dev libmysqlclient-dev libpq-dev libsvn-dev firebi…

经典HTML前端面试题总结

经典HTML前端面试题总结 1. 1简述一下你对 HTML 语义化的理解&#xff1f;.1.2 标签上 title 与 alt 属性的区别是什么&#xff1f;1.3 iframe的优缺点&#xff1f;1.4 href 与 src&#xff1f;1.5 HTML、XHTML、XML有什么区别1.6 知道img的srcset的作用是什么&#xff1f;1.7 …

代码随想录算法训练营第五十九天

代码随想录算法训练营第五十九天| 503.下一个更大元素II&#xff0c;42. 接雨水 503.下一个更大元素II42. 接雨水复杂单调栈整合单调栈 503.下一个更大元素II 题目链接&#xff1a;下一个更大元素II 因为可以循环&#xff0c;直接拼一个nums在nums后面就行了。 class Solutio…