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

2023-11-15

一、数据卷类型

为什么需要数据卷?

  • 容器中的文件在磁盘上是临时存放的,这给容器中运行比较重要的应用程序带来一些问题。
    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节点上容器数据,因为被删除了。

参考地址:

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 部署存储插件

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案例

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示例

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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 如何在特定 systemd 服务重新启动时触发自定义脚本运行

    我想知道如何安排自定义脚本在重新启动服务时运行 我的用例是 每当重新启动 Tomcat 服务时 我都必须运行多个命令 我想知道是否有一种方法可以编写脚本并安排它在重新启动 Tomcat 服务时运行 我已将 tomcat 脚本设置为 syst
  • 如何使用 sed 仅删除双空行?

    我找到了这个问题和答案 https stackoverflow com questions 4651591 howto use sed to remove only triple empty lines关于如何删除三重空行 但是 我只需要对
  • SONAR - 使用 Cobertura 测量代码覆盖率

    我正在使用声纳来测量代码质量 我不知道的一件事是使用 Cobertura 测量代码覆盖率的步骤 我按照以下步骤操作http cobertura sourceforge net anttaskreference html http cober
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • diff 文件仅比较每行的前 n 个字符

    我有2个文件 我们将它们称为 md5s1 txt 和 md5s2 txt 两者都包含a的输出 find type f print0 xargs 0 md5sum sort gt md5s txt 不同目录下的命令 许多文件被重命名 但内容保
  • PHP 致命错误:未找到“MongoClient”类

    我有一个使用 Apache 的网站 代码如下 当我尝试访问它时 我在 error log 中收到错误 PHP Fatal Error Class MongoClient not found 以下是可能错误的设置 但我认为没有错误 php i
  • 如何更改 Ubuntu 14.04 上的 php-cli 版本?

    我是 Linux 新手 在篡改时破坏了一些 php 设置 如果我执行一个包含以下内容的 php 脚本 phpinfo 它显示 php 版本为 5 6 但通过命令行 如果我运行php v它返回 7 0 版本 我想让两个版本匹配 我怎样才能修复
  • 嵌入式Linux poll()不断返回

    我有一个特别的问题 当我知道没有什么可读时 民意调查不断返回 因此设置如下 我有 2 个文件描述符 它们构成fd设置民意调查监视 一种用于引脚从高到低的变化 GPIO 另一个用于代理输入 代理输入出现问题 处理的顺序是 启动main函数 然
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte
  • 拆分字符串以仅获取前 5 个字符

    我想去那个地点 var log src ap kernelmodule 10 001 100 但看起来我的代码必须处理 ap kernelmodule 10 002 100 ap kernelmodule 10 003 101 等 我想使用
  • 从 PL/SQL 调用 shell 脚本,但 shell 以 grid 用户而非 oracle 身份执行

    我正在尝试使用 Runtime getRuntime exec 从 Oracle 数据库内部执行 shell 脚本 在 Red Hat 5 5 上运行的 Oracle 11 2 0 4 EE CREATE OR REPLACE proced
  • 让我们加密证书颁发

    我正在尝试获取 Let s Encrypt 颁发的证书 已经过去了 3 个半小时 我不小心最初将我的 SecretName 设置为 echo tls 然后将其切换到我想使用的正确的 pandaist tls 我目前有这个 kubectl g
  • Bash 解析和 shell 扩展

    我对 bash 解析输入和执行扩展的方式感到困惑 对于输入来说 hello world 作为 bash 中的参数传递给显示其输入内容的脚本 我不太确定 Bash 如何解析它 Example var hello world displaywh
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 如何通过替换为空页映射来取消映射 mmap 文件

    Linux 用户空间有没有办法用空页面 映射自 dev null 或者可能是一个空页面 重复映射到从文件映射的页面的顶部 对于上下文 我想找到这个 JDK bug 的修复 https bugs openjdk java net browse
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • 多个值文件中包含多个列表的 Helm 图表

    我有一个包含以下内容的values yaml abc env name name01 value value01 name name02 value value02 我有另一个值文件values dev yaml 我在使用 f安装时添加它
  • Kubernetes / kubectl - “必须指定容器名称”,但看起来确实如此?

    我正在调试 kubectl 的日志输出 其中指出 Error from server BadRequest a container name must be specified for pod postgres operator 49202
  • 如何从清单文件(通常是清单文件与 kubectl 运行)使用 kubectl port-forward

    我正在尝试在本地使用 Kubernetes 运行我的第一个应用程序 或者我应该说 minikube 我有一个非常基本的 Web 服务器 一个本地 docker 镜像 和官方 mongodb 我想从 dockerhub 中提取 镜像 我并不是

随机推荐

  • layui添加菜单和动态操作tab

    layui添加菜单和动态操作tab 代码一 使用模板引擎渲染菜单 代码二修改版 代码一 使用模板引擎渲染菜单
  • JQ奇偶选择

    table tr even click function console log 选择奇数行 表示获取一个table 所有的索引为偶数的行 其中索引index 从0开始算起 0算偶数 table tr odd click function
  • Java运行时一个类是什么时候被加载的?

    A question a day get offer every day 未来的魔法师 一个类在什么时候开始被加载 Java虚拟机规范 中并没有进行强制约束 交给了虚拟机自己去自由实现 HotSpot虚拟机是按需加载 在需要用到该类的时候加
  • ThreadLocal源码分析,线程局部变量,内存泄漏?

    ThreadLocal作为线程局部变量 线程级的 单个线程内共享的 一般来说可以有两方面的用途 作为共享变量 在某些方法计算的结果 要共享到其他方法 在使用时 通过threadLocal set 设置值 通过threadLocal get
  • 源码断点分析Spring的占位符(Placeholder)是怎么工作的

    项目中经常需要使用到占位符来满足多环境不同配置信息的需求 比如
  • 国内可用的ntp服务器地址

    ntp sjtu edu cn 202 120 2 101 上海交通大学网络中心NTP服务器地址 s1a time edu cn 北京邮电大学 s1b time edu cn 清华大学 s1c time edu cn 北京大学 s1d ti
  • 程序员常用的计算机cmd指令

    windows cmd 查看command命令帮助说明 calc 计算器 mspaint 图画 notepad 记事本 dir 遍历当前目录 cd 路径名 进入该目录 cd 返回上级目录 netstat ano 查看端口占用 netstat
  • java中获取比毫秒更为精确的时间

    关键词 java 毫秒 微秒 纳秒 System currentTimeMillis 误差 在对新写的超快xml解析器和xpath引擎进行效率测试时 为获取执行时间 开始也没多想就用了System currentTimeMillis 来做的
  • 【Metashape精品教程17】导出产品和报告

    Metashape精品教程17 导出产品和报告 文章目录 Metashape精品教程17 导出产品和报告 前言 一 导出空三 二 导出DEM 三 导出DOM 四 导出点云 五 生成报告 前言 本章是整套教程的终结 简单介绍一下Metasha
  • 声学测试软件手机版_最新手机性能排名:小米84万分拿到第一,iQOO5Pro第五,华为?...

    华为Mate40 Pro首发麒麟9000处理器 安兔兔跑分高达69 是今年最强旗舰 不过在此之前还是以骁龙865 麒麟990 5G为主 鲁大师发布了2020年Q3季度手机性能排行榜 第一名的跑分高达84万 第一名 小米10至尊纪念版 84
  • 超详细webpack的plugin讲解,看不懂算我输,案例>原理>实践

    前言 本篇文章为webpack系列文章的第三篇 主要内容是对webpack的plugin进行详细的讲解 从使用 到原理 再到自己开发一个plugin 对每个过程都会进行详细的分析介绍 如果你对webpack了解的还比较少 建议你先阅读以下往
  • SpringMVC使用HttpClient实现文件上传

    HttpClient httpclient new DefaultHttpClient int statusCode 0 1 使用Get方式访问 HttpGet get null HttpParams conParams httpclien
  • 关于ip定位

    其实这种通过ip抓的方式并不认可因为对他人毕竟有点不尊重 希望大家不要恶搞 我这边暂时不提供抓ip的软件 给大家提供一个网址 至于如何抓ip大家自行百度 我就不进行讲解了 csdn不允许 虚拟位置 其实方案有很多 因为这种方法比较简单 可以
  • Mojibakes来自哪里? 编码要点

    This article explores the basic concepts behind character encoding and then takes a dive deeper into the technical detai
  • html表格嵌套

    table width 560 height 300 border 1 cellspacing 0 align center thead tr height 70 td width 160 网站logo td td width 400 网站
  • Android开发常用开源框架:图片处理

    1 图片加载 缓存 处理 框架名称 功能描述 Android Universal Image Loader 一个强大的加载 缓存 展示图片的库 已过时 Picasso 一个强大的图片下载与缓存的库 Fresco 一个用于管理图像和他们使用的
  • 【python数据挖掘课程】十八.线性回归及多项式回归分析四个案例分享

    这是 Python数据挖掘课程 系列文章 也是我这学期大数据金融学院上课的部分内容 本文主要讲述和分享线性回归作业中 学生们做得比较好的四个案例 经过我修改后供大家学习 内容包括 1 线性回归预测Pizza价格案例 2 线性回归分析波士顿房
  • C++ Windows API IsDebuggerPresent的作用

    IsDebuggerPresent 是 Windows API 中的一个函数 它用于检测当前运行的程序是否正在被调试 当程序被如 Visual Studio 这样的调试器附加时 此函数会返回 TRUE 否则 它会返回 FALSE 这个函数经
  • Android SeekBar使用 监听方法

    1 SeekBar 是一个可以拖动的控件 需要实现 seekbar setOnSeekBarChangeListener new SeekBar OnSeekBarChangeListener Override public void on
  • K8s基础10——数据卷、PV和PVC、StorageClass动态补给、StatefulSet控制器

    文章目录 一 数据卷类型 1 1 临时数据卷 节点挂载 1 2 节点数据卷 节点挂载 1 3 网络数据卷NFS 1 3 1 效果测试 1 4 持久数据卷 PVC PV 1 4 1 效果测试 1 4 2 测试结论 二 PV PVC生命周期 2