K8S存储

ConfigMap配置管理:                                       

configmap介绍:

        Configmap用于保存配置数据,以键值对形式存储。

        configMap 资源提供了向 Pod 注入配置数据的方法。

        旨在让镜像和配置文件解耦,以便实现镜像的可移植性和可复用性。

典型的使用场景:

        填充环境变量的值

        设置容器内的命令行参数

        填充卷的配置文件

[root@k8s1 ~]# kubectl get secrets
NAME                  TYPE                                  DATA   AGE
basic-auth            Opaque                                1      44h
db-user-pass          Opaque                                2      15h
default-token-67c64   kubernetes.io/service-account-token   3      11d
myregistrykey         kubernetes.io/dockerconfigjson        1      14h
mysecret              Opaque                                2      15h
tls-secret            kubernetes.io/tls                     2      44h

创建ConfigMap的方式有4种:

        1,使用字面值创建:

此时创建了两对字面值:

key1        config1

key2        config2

kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

[root@k8s1 ~]# kubectl describe cm my-config
Name:         my-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
key1:
----
config1
key2:
----
config2

BinaryData
====

Events:  <none>

        2,使用文件创建:

kubectl create configmap myconfig --from-file=/etc/resolv.conf

#此时所用的参数是 --from-file

[root@k8s1 ~]# kubectl describe cm myconfig
Name:         myconfig
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
resolv.conf:
----

nameserver 114.114.114.114


BinaryData
====

Events:  <none>

        3,使用目录创建:

此时目录内:

key是passwd        value是/etc/passwd的内容

key是fstab              value是/etc/fstab的内容

mkdir configmap
cd configmap
mkdir test

cp /etc/passwd test/
cp /etc/fstab test/


kubectl create configmap myconfig --from-file=test/

[root@k8s1 configmap]# kubectl describe cm myconfig
Name:         myconfig
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
passwd:
----
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin

fstab:
----

#
# /etc/fstab
# Created by anaconda on Thu Sep 21 00:44:34 2023
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/rhel-root   /                       xfs     defaults        0 0
UUID=e222bb4b-df02-4be8-848d-5efa8f729cbd /boot                   xfs     defaults        0 0
#/dev/mapper/rhel-swap   swap                    swap    defaults        0 0


BinaryData
====

Events:  <none>

        4,编写configmap的yaml文件创建:

vim cm1.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: cm1-config
data:
  db_host: "172.25.0.250"
  db_port: "3306"
[root@k8s1 configmap]# kubectl describe cm
cm1-config        kube-root-ca.crt  nginxconf
[root@k8s1 configmap]# kubectl describe cm cm1-config
Name:         cm1-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
db_host:
----
172.25.0.250
db_port:
----
3306

BinaryData
====

Events:  <none>

如何使用configmap:

        1,通过环境变量的方式直接传递给pod

vim pod1.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
    - name: pod1
      image: busybox
      command: ["/bin/sh", "-c", "env"]
      env:
        - name: key1
          valueFrom:
            configMapKeyRef:
              name: cm1-config
              key: db_host
        - name: key2
          valueFrom:
            configMapKeyRef:
              name: cm1-config
              key: db_port
  restartPolicy: Never

此pod运行完直接退出

vim pod2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod2
spec:
  containers:
    - name: pod2
      image: busybox
      command: ["/bin/sh", "-c", "env"]
      envFrom:
        - configMapRef:
            name: cm1-config
  restartPolicy: Never
[root@k8s1 configmap]# kubectl apply -f pod2.yaml
pod/pod1 created
[root@k8s1 configmap]# kubectl logs pod1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=pod1
SHLVL=1
db_port=3306
HOME=/root
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
db_host=172.25.0.250

        2,通过在pod的命令行下运行的方式

通过调用命令行的方式,直接调出cm中的key和value,不用再次创建key或拿name

        3,作为volume的方式挂载到pod内

apiVersion: v1
kind: Pod
metadata:
  name: pod3
spec:
  containers:
    - name: pod3
      image: busybox
      command: ["/bin/sh", "-c", "cat /config/db_host"]
      volumeMounts:
#挂接
      - name: config-volume
        mountPath: /config
#挂接路径
  volumes:
    - name: config-volume
      configMap:
        name: cm1-config
#挂接的cm的名字
  restartPolicy: Never

查看挂接的cm1-config里面的内容是什么:

数据卷示例:

我们创建一个nginx的配置文件:

vim nginx.conf

server {
    listen       8000;
    server_name  _;

    location / {
        root /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

通过创建文件的方式创建configmap:

kubectl create configmap nginxconf --from-file=nginx.conf

运行一个由deployment控制的nginx容器:

vim pod4.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 1
#副本数为1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
#标签是nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
          - name: config-volume
            mountPath: /etc/nginx/conf.d
      volumes:
        - name: config-volume
          configMap:
            name: nginxconf

运行之后,我们访问pod时,此时需要以800的端口来访问

在热更新更改访问端口时,我们需要先更改cm中的配置文件:

kubectl edit cm nginxconf

修改之后我们会发现,cm中已经更改,但是nginx.conf的配置文件依然还是8000,(静态文件修改配置之后,需要重启服务),此时是deployment控制器创建,所以直接delete pod,然后等待pod重新创建,此时配置文件生效:


Secret配置管理:

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 ssh key。

敏感信息放在 secret 中比放在 Pod 的定义或者容器镜像中来说更加安全和灵活。

Pod 可以用两种方式使用 secret:

        作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里。

        当 kubelet 为 pod 拉取镜像时使用。

        

Secret的类型:

        Service Account:Kubernetes 自动创建包含访问 API 凭据的 secret,并自动修改pod 以使用此类型的 secret。

        Opaque:使用base64编码存储信息,可以通过base64 --decode解码获得原始数据, 因此安全性弱。

        kubernetes.io/dockerconfigjson:用于存储docker registry的认证信息

        serviceaccout 创建时 Kubernetes 会默认创建对应的 secret。对应的 secret 会自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount 目录中。

        每个namespace下有一个名为default的默认的ServiceAccount对象

        ServiceAccount里有一个名为Tokens的可以作为Volume一样被Mount到Pod里的Secret,当 Pod启动时这个Secret会被自动Mount到Pod的指定目录下,用来协助完成Pod中的进程访 问API Server时的身份鉴权过程。

示例:

mkdir    secret

echo -n 'admin' > ./username.txt
echo -n 'westos' > ./password.txt

kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
#此时创建了一个通用型secret

#此时查看被加密
[root@k8s1 secret]# kubectl describe secrets db-user-pass
Name:         db-user-pass
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
username.txt:  5 bytes
password.txt:  6 bytes

#用base64解码:
[root@k8s1 secret]# kubectl get secrets db-user-pass -o yaml

#先转换成yaml文件
apiVersion: v1
data:
  password.txt: d2VzdG9z
  username.txt: YWRtaW4=
kind: Secret
metadata:
  creationTimestamp: "2024-01-24T12:13:35Z"
  name: db-user-pass
  namespace: default
  resourceVersion: "424610"
  uid: 4a2cff95-c690-4102-83c3-3ee67295bf1c
type: Opaque

#解码:
[root@k8s1 secret]# echo d2VzdG9z|base64 -d
westos[root@k8s1 secret]#

转换成yaml文件,对于secret文件不能以明文存放信息,此时存放的是经过base64编码之后的信息:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: d2VzdG9z

将secret挂载到volume中:

创建yaml文件:

apiVersion: v1
kind: Pod
metadata:
  name: mysecret
spec:
  containers:
  - name: nginx
#容器名称
    image: nginx
#镜像
    volumeMounts:
    - name: secrets
      mountPath: "/secret"
#这里挂载路径不同与configma,cm中不需要加引号,secret需要加上引号
      readOnly: true
#开启只读
  volumes:
  - name: secrets
    secret:
      secretName: mysecret

运行:

[root@k8s1 secret]# kubectl exec mysecret -- ls /secret
password
username
[root@k8s1 secret]#


向指定路径映射 secret 密钥:

示例:

apiVersion: v1
kind: Pod
metadata:
  name: mysecret
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: secrets
      mountPath: "/secret"
      readOnly: true
#挂载的内容只可读
  volumes:
  - name: secrets
    secret:
      secretName: mysecret
#挂接的secret的名称,通过kubectl get secret可以得知
      items:
#选择其中
      - key: username
#挂接的是user中的内容
        path: my-group/my-username
#即将挂接的相对路径,相对于"/secret"
#完整路径为/secret/my-group/my-username
#通过path来重新修改挂载点

将Secret设置为环境变量,和cm的使用方法类似:

apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  containers:
  - name: demo
    image: nginx
    env:
      - name: key1
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: key2
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password

kubernetes.io/dockerconfigjson用于存储docker registry的认证信息

我们现在harbor仓库创建一个私有仓库:

kubectl create secret docker-registry myregistrykey --docker-server=reg.westos.org --docker-username=admin --docker-password=westos --docker-email=yakexi007@westos.org
后面的邮件地址随意

创建pod3.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: game2048
      image: reg.westos.org/westos/nginx:latest
  imagePullSecrets:
#指定拉取的仓库认证信息
    - name: myregistrykey

当我们不指定认证信息时,

创建pod会起不来:

拉取镜像会失败:

指定之后,此时拉取下来镜像之后running:


Volume配置:

        容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些 问题。首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失, 因为容器会以干净的状态重建。其次,当在一个 Pod 中同时运行多个容器时,常 常需要在这些容器之间共享文件。 Kubernetes 抽象出 Volume 对象来解决这两 个问题。

        Kubernetes 卷具有明确的生命周期,与包裹它的 Pod 相同。 因此,卷比 Pod 中 运行的任何容器的存活期都长,在容器重新启动时数据也会得到保留。 当然,当 一个 Pod 不再存在时,卷也将不再存在。也许更重要的是,Kubernetes 可以支 持许多类型的卷,Pod 也能同时使用任意数量的卷。

        卷不能挂载到其他卷,也不能与其他卷有硬链接。 Pod 中的每个容器必须独立地 指定每个卷的挂载位置。

Kubernetes 支持下列类型的卷:

        awsElasticBlockStore 、azureDisk、azureFile、cephfs、cinder、configMap、csi

        downwardAPI、emptyDir、fc (fibre channel)、flexVolume、flocker

        gcePersistentDisk、gitRepo (deprecated)、glusterfs、hostPath、iscsi、local、

        nfs、persistentVolumeClaim、projected、portworxVolume、quobyte、rbd

        scaleIO、secret、storageos、vsphereVolume

emptyDir卷:

        当 Pod 指定到某个节点上时,首先创建的是一个 emptyDir 卷,并且只要 Pod 在 该节点上运行,卷就一直存在。 就像它的名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,但是这些容器都可 以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时, emptyDir 卷中的数据也会永久删除。

emptyDir 的使用场景:

        缓存空间,例如基于磁盘的归并排序。

        为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。

        在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。

        默认情况下, emptyDir 卷存储在支持该节点所使用的介质上;这里的介质可以是磁盘 或 SSD 或网络存储,这取决于您的环境。 但是,您可以将 emptyDir.medium 字段设 置为 "Memory",以告诉 Kubernetes 为您安装 tmpfs(基于内存的文件系统)。 虽然 tmpfs 速度非常快,但是要注意它与磁盘不同。 tmpfs 在节点重启时会被清除,并且 您所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。


临时卷:

vim emptydir.yaml

apiVersion: v1
kind: Pod
metadata:
  name: vol1
spec:
  containers:
  - image: busyboxplus
    name: vm1
    command: ["sleep", "300"]
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  - name: vm2
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
#挂载位置(容器内的目录位置)
      name: cache-volume
#需要挂载的卷的名称

  volumes:
#卷的定义
  - name: cache-volume
#卷的名称
    emptyDir:
#定义卷
      medium: Memory
#卷的类型
      sizeLimit: 100Mi
#定义卷的大小

运行之后,进入容器vm1:

此时实现的是容器内的数据共享

再次进入容器,

dd命令:Linux dd 命令 | 菜鸟教程

临时卷随着容器的删除而删除,它的生命周期随着容器的结束而结束。


本地卷:

        hostPath 卷能将主机节点文件系统上的文件或目录挂载到您的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。

hostPath 的一些用法有:

        运行一个需要访问 Docker 引擎内部机制的容器,挂载 /var/lib/docker 路径。

        在容器中运行 cAdvisor 时,以 hostPath 方式挂载 /sys。

        允许 Pod 指定给定的 hostPath 在运行 Pod 之前是否应该存在,是否应该创建以及应该以 什么方式存在。

除了必需的 path 属性之外,用户可以选择性地为 hostPath 卷指定 type。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /test
#挂载路径
      name: test-volume
#名称
  volumes:
  - name: test-volume
    hostPath:
      path: /data
#创建的路径为pod所调度到的节点的宿主机的路径
      type: DirectoryOrCreate
#七种类型

运行之后查看pod所在节点:

此时在k8s2上data目录被创建

在pod的/test目录中,创建demo目录,此时在k8s2主机中此目录被创建

当pod的生命周期结束之后,所创建的pod的卷不会随之删除,而是会被持久化

当使用这种类型的卷时要小心,因为:

        具有相同配置(例如从 podTemplate 创建)的多个 Pod 会由于节点上文件的不同而在不同节点上有不同的行为。

        当 Kubernetes 按照计划添加资源感知的调度时,这类调度机制将无法考虑由 hostPath 使用的资源。

        基础主机上创建的文件或目录只能由 root 用户写入。您需要在 特权容器 中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath 卷。


网络卷:

在三个节点安装nfs-utils

示例:通过nfs的方式来共享,在如果pod会漂移,可以使用此种方式:

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      server: 172.25.0.10
      path: /nfsdata

运行之后,发现pod运行在k8s2上

此时进入容器内部的nfs挂载目录:/usr/share/nginx/html

原来在该目录下会有首页,但是在挂载之后,由于nfsdata目录为空,所以ls为空

在harbor创建首页:

关于nfs:NFS详解(概念+实验演示)-CSDN博客


持久卷:

        PersistentVolume(持久卷,简称PV)是集群内,由管理员提供的网络存储的一部分。就像集群 中的节点一样,PV也是集群中的一种资源。它也像Volume一样,是一种volume插件,但是它的 生命周期却是和使用它的Pod相互独立的。PV这个API对象,捕获了诸如NFS、ISCSI、或其他 云存储系统的实现细节。

        PersistentVolumeClaim(持久卷声明,简称PVC)是用户的一种存储请求。它和Pod类似,Pod 消耗Node资源,而PVC消耗PV资源。Pod能够请求特定的资源(如CPU和内存)。PVC能够请 求指定的大小和访问的模式(可以被映射为一次读写或者多次只读)。

有两种PV提供的方式:静态和动态

        静态PV:集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用 户是可用的。它们存在于Kubernetes API中,并可用于存储使用。

        动态PV:当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给 volume给PVC。这种供给基于StorageClass。

PVC与PV的绑定是一对一的映射。没找到匹配的PV,那么PVC会无限期得处于unbound未绑定 状态。

        pod在调用卷时,调用的是pvc(持久卷声明),pvc包含的就是pod所需要的卷的各种信息,pvc可以含有多种不同类型的卷;来支持pod调用,在pvc内的某一种卷被pod调用后,该pvc会自动匹配合适或对应的pv(持久卷),形成绑定关系,从而调度在该pod上。

使用:

        Pod使用PVC就像使用volume一样。集群检查PVC,查找绑定的PV,并映射PV给Pod。对 于支持多种访问模式的PV,用户可以指定想用的模式。一旦用户拥有了一个PVC,并且 PVC被绑定,那么只要用户还需要,PV就一直属于这个用户。用户调度Pod,通过在Pod的 volume块中包含PVC来访问PV。

释放:

        当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就 被认为是已经是“released”了,但还不能再给另外一个PVC使用。前一个PVC的属于还存 在于该PV中,必须根据策略来处理掉。

回收:

        PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。当前,PV可以被 Retained(保留)、 Recycled(再利用)或者Deleted(删除)。保留允许手动地再次声明 资源。对于支持删除操作的PV卷,删除操作会从Kubernetes中移除PV对象,还有对应的外 部存储(如AWS EBS,GCE PD,Azure Disk,或者Cinder volume)。动态供给的卷总是 会被删除。

访问模式:

        ReadWriteOnce -- 该volume只能被单个节点以读写的方式映射

        ReadOnlyMany -- 该volume可以被多个节点以只读方式映射

        ReadWriteMany -- 该volume可以被多个节点以读写的方式映射

在命令行中,访问模式可以简写为:

        RWO - ReadWriteOnce

        ROX - ReadOnlyMany

        RWX - ReadWriteMany

回收策略:

        Retain:保留,需要手动回收

        Recycle:回收,自动删除卷中数据

        Delete:删除,相关联的存储资产,如AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷都会被删除

当前,只有NFS和HostPath支持回收利用,AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷支持删除操作。

状态:

        Available:空闲的资源,未绑定给PVC

        Bound:绑定给了某个PVC

        Released:PVC已经删除了,但是PV还没有被集群回收

        Failed:PV在自动回收中失败了 • 命令行可以显示PV绑定的PVC名称。

示例:

apiVersion: v1
kind: PersistentVolume
#持久卷
metadata:
  name: pv1
spec:
  capacity:
    storage: 5Gi
#存储大小
  volumeMode: Filesystem
#卷模式
  accessModes:
#访问模式 ———有三种
    - ReadWriteOnce
#单节点读写
  persistentVolumeReclaimPolicy: Recycle
#回收策略    Delete是把卷一并删除
  storageClassName: demodemo
#标识卷类型
  nfs:
#卷类型:
    path: /nfsdata
    server: 172.25.0.10

运行之后查看:

这是一份持久卷,名称是pv1,卷大小为5G,回收策略是Recycle,状态是Available

再写一份持久卷声明:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: demodemo
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

此时pvc和pv绑定在一起了。我们查看pv:

        此时pv被绑定在了default域中的pvc1,虽然pvc声明需要的是1G,但是pv的5G远远符合1G。所以可以绑定,有兴趣的朋友可以试试,当pvc声明的是1G时,pv中有两个符合pvc的卷出现时,一个是1G,一个是2G,此时pvc会与哪一个绑定?

        在pv和pvc有了绑定关系之后,我们可以通过pod的方式来调用pvc,从而实现持久卷:

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: demo1
  volumes:
  - name: demo1
    persistentVolumeClaim:
      claimName: pvc1

此时我们在写入卷的pvc时,就可以指定哪一个pvc,从而实现数据卷的持久化。

PS:如果需要在某某个节点的某个目录下,挂载多个卷,那么我们需要在该目录下创建相应的文件夹以作区分,例如在刚刚使用nfs时,pv的挂载路径是/nfstada,那么当多个pv创建时,应该在nfstada下创建多个目录来分配pv:

此时我们的pv就可以这样写:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: demodemo
  nfs:
    path: /nfsdata/pv1
    server: 172.25.0.10

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /nfsdata/pv3
    server: 172.25.0.10


---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv3
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadOnlyMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /nfsdata/pv3
    server: 172.25.0.10

而此时的pvc声明就可以这样写来满足pod的需求:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: demodemo
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi


---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc2
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi


---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc3
spec:
  storageClassName: nfs
  accessModes:
    - ReadOnlyMany
  resources:
    requests:
      storage: 5Gi

在不需要pvc之后delete pvc时,此时的pv会被回收再利用

        在pv根据回收策略被回收再利用时,会拉取到busybox:1.27,此时在busybox中会将挂载的nfs数据删除,为了方便,我们可以在harbor仓库拉取该镜像,然后上传到library中,此时会快一些回收释放pv。

在创建的时候,应该是: pv-->pvc-->pod

在删除的时候,应该是:pod-->pvc-->pv


存储类:

关于存储类:存储类 | Kubernetes

首先需要部署存储分配器(NFS):

参考github的项目:

https://github.com/kubernetes-retired/external-storage/tree/master/nfs-client

在docker官方拉取镜像:

docker pull sig-storage/nfs-subdir-external-provisioner:v4.0.2

更改镜像名称,上传到harbor仓库:

新建namespace:

kubectl create namespace  nfs-client-provisioner
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: nfs-client-provisioner
#因为这里有新建namespace,当前域中没有,所以需要新建
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - 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
    # replace with namespace where provisioner is deployed
    namespace: nfs-client-provisioner
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
  # replace with namespace where provisioner is deployed
  namespace: nfs-client-provisioner
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
  # replace with namespace where provisioner is deployed
  namespace: nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: nfs-client-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: nfs-client-provisioner
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: sig-storage/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: 172.25.0.10
#根据之前使用的nfs服务器地址来填写
            - name: NFS_PATH
              value: /nfsdata
#路径也是
      volumes:
        - name: nfs-client-root
          nfs:
            server: 172.25.0.10
#这里还有一处需要修改
            path: /nfsdata
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
#存储类的名字(自定义)
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
#分配器的名字(自定义)
parameters:
  archiveOnDelete: "true"

运行yaml文件:

查看nfs-client-provisioner:

[root@k8s1 volumes]# kubectl get all -n nfs-client-provisioner
NAME                                          READY   STATUS    RESTARTS   AGE
pod/nfs-client-provisioner-68478b85f9-9vl9w   1/1     Running   0          3h20m

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nfs-client-provisioner   1/1     1            1           3h20m

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/nfs-client-provisioner-68478b85f9   1         1         1       3h20m

查看存储类:

root@k8s1 volumes]# kubectl get storageclasses.storage.k8s.io
NAME                  PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  3h22m

创建pvc:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-calim
spec:
  storageClassName: managed-nfs-storage
#对应的是存储类的名字
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 100Mi

运行pvc文件:

此时会发现pvc和pv已经自动创建并且已经绑定好了,创建的pv的目录在:

此时/nfadata就是pv所在的目录,然后创建pod来调用pvc:

kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: nfs-pvc
      mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-calim

此时pod会在挂载的目录下touch一个文件

当pod被删除之后,pvc仍然存在,当pvc被删除之后,pv也会被delete:

此时pv会被打包,卷内的信息会被保存:

如果不想保存内容,则在最开始创建分配器的yaml文件中,将true改为false:

已经知道在配置分配器之后,只要创建pvc,集群会分配pv来进行绑定,此时我们可以更改之前做的pvc文件:

将存储的classname改为managed-nfs-storage存储类,再运行文件:

此时集群自动创建好了相对应的pv

在nfs服务器端查看:

当用户不知道存储类的名称时,我们应该创建一个默认类:

        默认的 StorageClass 将被用于动态的为没有特定 storage class 需求的 PersistentVolumeClaims 配置存储:(只能有一个默认StorageClass) • 如果没有默认StorageClass,PVC 也没有指定storageClassName 的值,那么意 味着它只能够跟 storageClassName 也是“”的 PV 进行绑定。

kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

#annotations(属性):默认类为default类,开启

此时用户在创建pvc的收不用指定存储类的名字,如果加上了名字就必须找到对应的资源,在没有加上存储类名字也没有指定默认类的情况下,pod不工作。

设置好之后,默认类会优先寻找集群内静态pv来进行匹配,如果没有才会创建动态pv

在设置默认类之后,运行该pod(没有指定存储类名字)


StatefulSet:

statefulset主要针对有状态应用(MySQL/Redis):运行一个有状态的应用程序 | Kubernetes

StatefulSet将应用状态抽象成了两种情况:

        拓扑状态:应用实例必须按照某种顺序启动。新创建的Pod必须和原来Pod的网络标识一样

        存储状态:应用的多个实例分别绑定了不同存储数据

StatefulSet给所有的Pod进行了编号,编号规则是:$(statefulset名称)-$(序号),从0开始。

Pod被删除后重建,重建Pod的网络标识也不会改变,Pod的拓扑状态按照Pod的“名字+编号”的方 式固定下来,并且为每个Pod提供了一个固定且唯一的访问入口,即Pod对应的DNS记录。

拓扑状态通过headless实现:

先创建一个无头服务:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
#直接解析到pod的IP地址
  selector:
    app: nginx

生成一个statefulset控制器:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx-svc"
#使用headless来实现网络拓扑状态,拥有一个唯一且有序的标识
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
     labels:
       app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx

此时pod的标识是有序的标识,编号规则是:$(statefulset名称)-$(序号),从0开始

此时三个pod都有解析,我们可以加上持久卷来挂载到statefulset创建的pod中,挂载到nginx首页中,来进行访问首页区分。在回收pod时,应该先将副本数改为0,按顺序回收,再delete yaml文件。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx-svc"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
     labels:
       app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        storageClassName: managed-nfs-storage
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi

此时在/nfsdata中有了三个动态存储卷:

然后使用可回收的pod demo来访问这三个pod:

这仅作为了解该控制器的入门点,更多内容请参考 :运行一个有状态的应用程序 | Kubernetes

官方文档编写了一个经典的mysql主从来进行了解该控制器,本人实际不足以讲述好该实例。


Kubernetes调度:

        调度器通过 kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。

        kube-scheduler 是 Kubernetes 集群的默认调度器,并且是集群控制面的一部分.如果你真的希望或者有这方面的需求,kube-scheduler 在设计上是允许你自己写一 个调度组件并替换原有的 kube-scheduler。

        在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限 制、亲和以及反亲和要求、数据局域性、负载间的干扰等等。

nodename:

        nodeName 是节点选择约束的最简单方法,但一般不推荐。如果 nodeName 在 PodSpec 中指定了,则它优先于其他的节点选择方法。

使用 nodeName 来选择节点的一些限制:

        如果指定的节点不存在。

        如果指定的节点没有资源来容纳 pod,则pod 调度失败。

        云环境中的节点名称并非总是可预测或稳定的。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: server3
#指定node指向节点

结果显而易见,处于pending状态,因为我们的集群节点上没有主机名为server3的节点:

当修改成集群内的节点主机名时此时成功运行:

nodeSelector:

nodeSelector 是节点选择约束的最简单推荐形式。给节点添加标签,然后在nodeSelector中选择标签即可(集群内没有标签也调度不到):

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
#选择标签:
    disktype: ssd

当pod被调度到拥有标签的节点时,此时在该节点删除标签,对pod无影响:

亲和与反亲和:

        nodeSelector 提供了一种非常简单的方法来将 pod 约束到具有特定标签的节点上。亲和/反亲和功能极大地扩展了你可以表达约束的类型。

        你可以发现规则是“软”/“偏好”,而不是硬性要求,因此,如果调度器无法满足该要求,仍然调度该 pod

        你可以使用节点上的 pod 的标签来约束,而不是使用节点本身的标签,来允许哪些 pod 可以或者不可以被放置在一起。

节点亲和:

        requiredDuringSchedulingIgnoredDuringExecution 必须满足                preferredDuringSchedulingIgnoredDuringExecution 倾向满足

IgnoreDuringExecution 表示如果在Pod运行期间Node的标签发生变化,导致亲和性策略不能满足,则继续运行当前的Pod

示例:

piVersion: v1
kind: Pod
metadata:
  name: node-affinity
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:
#亲和性
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
#必须满足
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
#磁盘类型
            operator: In
#在列表中即可,符合其中一个即可
            values:
              - ssd
#磁盘为ssd
              - sata
#磁盘为sata

此时运行:

再加上倾向满足:

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
              - ssd
              - sata
      preferredDuringSchedulingIgnoredDuringExecution:
#倾向满足
      - weight: 1
#可以通过权重来控制,权重越高,优先级越高,可以定义多个权重
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
#通过主机名
            operator: NotIn
#不要在这个列表上
            values:
            - k8s3

nodeaffinity还支持多种规则匹配条件的配置如

        In:label 的值在列表内

        NotIn:label 的值不在列表内

        Gt:label 的值大于设置的值,不支持Pod亲和性

        Lt:label 的值小于设置的值,不支持pod亲和性

        Exists:设置的label 存在

        DoesNotExist:设置的 label 不存在

pod 亲和性和反亲和性

        podAffinity 主要解决POD可以和哪些POD部署在同一个拓扑域中的问题

(拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone等。)

       podAntiAffinity主要解决POD不能和哪些POD部署在同一个拓扑域中的问题。它们处理的是Kubernetes集群内部POD和POD之间的关系。

        Pod 间亲和与反亲和在与更高级别的集合(例如 ReplicaSets,StatefulSets, Deployments 等)一起使用时,它们可能更加有用。可以轻松配置一组应位 于相同定义拓扑(例如,节点)中的工作负载。

对于两个或者多个有密切联系的pod, 分布在不同节点上来进行数据交互速度显然会降低,此时使用pod的亲和性,使得pod分布在同一节点更合理

亲和性示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx

---
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  containers:
  - name: mysql
    image: mysql:5.7
    env:
    - name: "MYSQL_ROOT_PASSWORD"
      value: "westos"
  affinity:
    podAntiAffinity:
#亲和性
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
#标签选择
          matchExpressions:
#匹配规则
          - key: app
#使用标签匹配
            operator: In
            values:
            - nginx
#匹配标签为nginx
        topologyKey: kubernetes.io/hostname
#范围(域)

此时发现两个pod都在同一节点,则证明成功亲和

如果需要不处于同一节点,我们只需要改动一处:

此时再运行文件:

当需要在不同节点部署同一pod时:

示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: demo
  name: demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      hostNetwork: true
#使用主机网络时,此时宿主机只能承载一个pod,否则会发生端口冲突,产生报错
      containers:
      - image: nginx
        name: nginx
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
                - demo
#此时需要寻找没有demo标签的节点来运行
            topologyKey: kubernetes.io/hostname

此时副本数为3,但是k8s1上有污点不参与调度,所以会有一个pod处于pending:


污点:

        NodeAffinity节点亲和性,是Pod上定义的一种属性,使Pod能够按我们的要求调度到 某个Node上,而Taints则恰恰相反,它可以让Node拒绝运行Pod,甚至驱逐Pod。

        Taints(污点)是Node的一个属性,设置了Taints后,所以Kubernetes是不会将Pod调度到这个Node上的,于是Kubernetes就给Pod设置了个属性Tolerations(容忍),只要 Pod能够容忍Node上的污点,那么Kubernetes就会忽略Node上的污点,就能够(不是 必须)把Pod调度过去。

可以使用命令 kubectl taint 给节点增加一个 taint:

        kubectl taint nodes node1 key=value:NoSchedule //创建污点

        kubectl describe nodes server1 |grep Taints //查询节点污点情况

        kubectl taint nodes node1 key:NoSchedule- //删除某节点污点

其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]

        NoSchedule:POD 不会被调度到标记为 taints 节点。

        PreferNoSchedule:NoSchedule 的软策略版本。

        NoExecute:该选项意味着一旦 Taint 生效,如该节点内正在运行的 POD 没有对应 Tolerate 设置,会直接被逐出。

首先创建一个副本数为2的控制器:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

此时发现两个pod被调度在了不同节点:

此时我们在k8s2上添加污点(以kv对的形式):

此时使用的是NoSchedule

kubectl taint node k8s2 key1=v1:NoSchedule

然后再将副本数拉伸为4,重新运行yaml文件:

此时新添加的两个副本数在k8s3上,因为k8s2上已经有污点,但已经运行的pod不会被影响,所以三个在k8s3,一个在k8s1上。

我们再对k8s3使用NoExecute

kubectl taint node k8s3 key1=v1:NoExecute

此时:

此时k8s三上的pod被逐出,且无任何节点可以运行,所以pending

yaml文件中容忍的语法格式:

tolerations示例:

tolerations:
- key: "key"
 operator: "Equal"
 value: "value"
 effect: "NoSchedule"


tolerations:
- key: "key"
 operator: "Exists"
 effect: "NoSchedule"


tolerations中定义的key、value、effect,要与node上设置的taint保持一直:
• 如果 operator 是 Exists ,value可以省略。
• 如果 operator 是 Equal ,则key与value之间的关系必须相等。
• 如果不指定operator属性,则默认值为Equal。

还有两个特殊值:
• 当不指定key,再配合Exists 就能匹配所有的key与value ,可以容忍所有污点。
• 当不指定effect ,则匹配所有的effect。

此时改写当前yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 4
  template:
    metadata:
      labels:
        app: nginx
    spec:
      tolerations:
      - operator: Exists
#容忍所有污点
      containers:
      - name: nginx
        image: nginx

重新运行:

此时pod在所有节点均running,此时创建pod demo:

因为两个worker节点均有污点,所以无法运行


        影响Pod调度的指令还有:cordon、drain、delete,后期创建的pod都不会被调度到该节点上,但操作的暴力程度不一样。

 cordon 停止调度

        影响最小,只会将node调为SchedulingDisabled,新创建pod,不会被调度到该节点,节点原有pod不受影响,仍正常对外提供服务。

drain 驱逐节点

        首先驱逐node上的pod,在其他节点重新创建,然后将节点调为SchedulingDisabled。

delete 删除节点

        最暴力的一个,首先驱逐node上的pod,在其他节点重新创建,然后,从master节点删除该node,master失去对其控制,如要恢复调度,需进入node节点,重启kubelet服务。

cordon:

停止调度之后,原有的pod不会被停止,新建的pod不会调度到该节点,所以k8s2停止调度之后,k8s1不参与调度,此时pod集中在k8s3上

解除节点保护之后,节点正常参与调度

drain:

此时驱离该节点pod之后,pod被转移在了k8s2上(记得解除节点保护)

此时就可以对节点进行操作,在对节点进行更改之后,重启k8s3的kubelet(自注册功能)即可重新加入集群

在日常维护中,我们使用drain驱离pod即可,然后对节点进行维护和重启,此时节点保护依然存在,我们取消即可。

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值