Kubernetes(v1.21)配置和存储

GA666666 2022-12-08 AM 27℃ 1条

第一章:概述

1.1 概述

  • 容器的生命周期可能很短,会被频繁的创建和销毁。那么容器在销毁的时候,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器中的数据,Kubernetes 引入了 Volume 的概念。和 Docker 中的卷管理(匿名卷、具名卷、自定义挂载目录,都是挂载在本机,功能非常有限)不同的是,Kubernetes 天生就是集群,所以为了方便管理,Kubernetes 将 抽取为一个对象资源,这样可以更方便的管理和存储数据。
  • Volume 是 Pod 中能够被多个容器访问的共享目录,它被定义在 Pod 上,然后被一个 Pod 里面的多个容器挂载到具体的文件目录下,Kubernetes 通过 Volume 实现同一个 Pod 中不同容器之间的数据共享以及数据的持久化存储。Volume 的生命周期不和 Pod 中的单个容器的生命周期有关,当容器终止或者重启的时候,Volume 中的数据也不会丢失。

1.png

1.2 Kubernetes 支持的 Volume 类型

1.2.1 简版

  • Kubernetes 的 Volume 支持多种类型,如下图所示:

2.png

1.2.2 细分类型

  • Kubernetes 目前支持多达 28 种数据卷类型(其中大部分特定于具体的云环境如 GCE/AWS/Azure 等)
  • 非持久性存储:

    • emptyDir
    • HostPath
  • 网络连接性存储:

    • SAN:iSCSI、ScaleIO Volumes、FC (Fibre Channel)
    • NFS:nfs,cfs
  • 分布式存储

    • Glusterfs
    • RBD (Ceph Block Device)
    • CephFS
    • Portworx Volumes
    • Quobyte Volumes
  • 云端存储

    • GCEPersistentDisk
    • AWSElasticBlockStore
    • AzureFile
    • AzureDisk
    • Cinder (OpenStack block storage)
    • VsphereVolume
    • StorageOS
  • 自定义存储

    • FlexVolume

第二章:配置

2.1 配置最佳实战

  • 云原生应用的 12 要素中,提出了配置分离。
  • 在推送到集群之前,配置文件应该存储在版本控制系统中。这将允许我们在必要的时候快速回滚配置更改,它有助于集群重新创建和恢复。
  • 使用 YAML 而不是 JSON 编写配置文件,虽然这些格式几乎可以在所有场景中互换使用,但是 YAML 往往更加用户友好。
  • 建议相关对象分组到一个文件,一个文件通常比几个文件更容易管理。请参阅 guestbook-all-in-one.yaml 文件作为此语法的示例。
  • 除非必要,否则不指定默认值(简单的最小配置会降低错误的可能性)。
  • 将对象描述放在注释中,以便更好的内省。

2.2 Secret

2.2.1 概述

  • Secret 对象类型用来保存敏感信息,如:密码、OAuth2 令牌以及 SSH 密钥等。将这些信息放到 Secret 中比放在 Pod 的定义或者容器镜像中更加安全和灵活。
  • 由于创建 Secret 可以独立于使用它们的 Pod, 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施,如:避免将机密数据写入非易失性存储。
  • Secret 类似于 ConfigMap 但专门用于保存机密数据。
注意:Secret 和 ConfigMap 是保存在 etcd 中。

2.2.2 Secret 的种类

  • 命令行:
create secret --help

3.png

  • 细分类型:

    内置类型用法
    Opaque用户定义的任意数据
    kubernetes.io/service-account-token服务账号令牌
    kubernetes.io/dockercfg~/.dockercfg
    文件的序列化形式
    kubernetes.io/dockerconfigjson~/.docker/config.json
    文件的序列化形式
    kubernetes.io/basic-auth用于基本身份认证的凭据
    kubernetes.io/ssh-auth用于 SSH 身份认证的凭据
    kubernetes.io/tls用于 TLS 客户端或者服务器端的数据
    bootstrap.kubernetes.io/token启动引导令牌数据

2.2.3 Pod 如何引用

  • 要使用 Secret,Pod 需要引用 Secret。 Pod 可以用三种方式之一来使用 Secret :

  • Secret 对象的名称必须是合法的 DNS 子域名。 在为创建 Secret 编写配置文件时,你可以设置 data 与/或 stringData 字段。 datastringData 字段都是可选的。data 字段中所有键值都必须是 base64 编码的字符串。如果不希望执行这种 base64 字符串的转换操作,你可以选择设置 stringData 字段,其中可以使用任何字符串作为其取值。

2.2.4 创建 Secret

  • 命令行创建 Secret :
kubectl create secret generic secret-1 \
   --from-literal=username=admin \
   --from-literal=password=123456

4.gif

  • 使用 base64 对数据进行编码:
# 准备username YWRtaW4=
echo -n "admin" | base64
# MTIzNDU2
echo -n "123456" | base64

5.png

  • 使用 yaml 格式创建 Secret :
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: k8s-secret
  namespace: default
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU2
kubectl apply -f k8s-secret.yaml

6.gif

  • 有的时候,觉得手动将数据编码,再配置到 yaml 文件的方式太繁琐,那么也可以将数据编码交给 Kubernetes :
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: k8s-secret-string-data
  namespace: default
type: Opaque
stringData: 
  username: admin
  password: "123456"
kubectl apply -f k8s-secret.yaml
注意:如果同时使用 data 和 stringData ,那么 data 会被忽略。

7.gif

  • 根据文件创建 Secret :
echo -n 'admin' > username.txt
echo -n '123456' > password.txt
kubectl create secret generic k8s-secret-file \
  --from-file=username.txt \
  --from-file=password.txt
注意:密钥 的 Key 默认是文件名的名称。

8.gif

  • 根据文件创建 Secret(自定义密钥 的 Key )
echo -n 'admin' > username.txt
echo -n '123456' > password.txt
kubectl create secret generic k8s-secret-file \
  --from-file=username=username.txt \
  --from-file=password=password.txt

9.gif

2.2.5 查看 Secret

  • 示例:以 JOSN 的形式提取 data
kubectl get secret k8s-secret-file  -o jsonpath='{.data}'

10.gif

  • 示例:以 yaml 的格式
kubectl get secret k8s-secret-file  -o yaml

11.gif

2.2.6 使用 Secret 之环境变量引用

  • 示例:
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
  namespace: default
type: Opaque
stringData: 
  username: admin
  password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: default
  labels:
    app: pod-secret
spec:
  containers:
  - name: alpine
    image: alpine
    command: ["/bin/sh","-c","sleep 3600"]
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    env:
    - name: SECRET_USERNAME # 容器中的环境变量名称
      valueFrom:
        secretKeyRef: 
          name: my-secret #  指定 secret 的名称
          key: username # secret 中 key 的名称,会自动 base64 解码
    - name: SECRET_PASSWORD # 容器中的环境变量名称
      valueFrom:
        secretKeyRef:
          name: my-secret #  指定 secret 的名称
          key: password # secret 中 key 的名称   
    - name: POD_NAME
      valueFrom: 
        fieldRef:  # 属性引用
          fieldPath: metadata.name
    - name: POD_LIMITS_MEMORY
      valueFrom:
        resourceFieldRef:  # 资源限制引用 
          containerName: alpine  
          resource: limits.memory       
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
  restartPolicy: Always
kubectl apply -f k8s-secret.yaml

12.gif

注意:环境变量引用的方式不会被自动更新。

2.2.7 使用 Secret 之 卷挂载

  • 示例:
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
  namespace: default
type: Opaque
stringData: 
  username: admin
  password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: default
  labels:
    app: pod-secret
spec:
  containers:
  - name: alpine
    image: alpine
    command: ["/bin/sh","-c","sleep 3600"]
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi     
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: app
      mountPath: /app  
  volumes:
    - name: app
      secret:
        secretName: my-secret # secret 的名称,Secret 中的所有 key 全部挂载出来
  restartPolicy: Always
kubectl apply -f k8s-secret.yaml

13.gif

注意:

  • 如果 Secret 以卷挂载的方式,Secret 里面的所有 key 都是文件名,内容就是 key 对应的值。
  • 如果 Secret 以卷挂载的方式,Secret 的内容更新,那么容器对应的值也会被更新(subPath 引用除外)。
  • 如果 Secret 以卷挂载的方式,默认情况下,挂载出来的文件是只读的。
  • 示例:指定挂载的文件名
vi k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
  namespace: default
type: Opaque
stringData: 
  username: admin
  password: "1234556"
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: default
  labels:
    app: pod-secret
spec:
  containers:
  - name: alpine
    image: alpine
    command: ["/bin/sh","-c","sleep 3600"]
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi     
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: app
      mountPath: /app  
  volumes:
    - name: app
      secret:
        secretName: my-secret # secret 的名称,Secret 中的所有 key 全部挂载出来
        items:
          - key: username # secret 中 key 的名称,Secret 中的 username 的内容挂载出来
            path: username.md # 在容器内挂载出来的文件的路径
  restartPolicy: Always
kubectl apply -f k8s-secret.yaml

2.3 ConfigMap

2.3.1 概述

  • ConfigMap 和 Secret 非常类似,只不过 Secret 会将信息进行 base64 编码和解码,而 ConfigMap 却不会。

2.3.2 创建 ConfigMap、环境变量引用、卷挂载

  • 示例:
vi k8s-cm.yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: cm
  namespace: default
data:
  # 类属性键;每一个键都映射到一个简单的值
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"
  # 类文件键
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true 
---
apiVersion: v1
kind: Pod
metadata:
  name: "pod-cm"
  namespace: default
  labels:
    app: "pod-cm"
spec:
  containers:
  - name: alpine
    image: "alpine"
    command: ["/bin/sh","-c","sleep 3600"]
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    env:
    - name: PLAYER_INITIAL_LIVES
      valueFrom:
        configMapKeyRef:
          name: cm
          key: player_initial_lives
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: app
      mountPath: /app  
  volumes:
    - name: app
      configMap:
        name: cm # secret 的名称,Secret 中的所有 key 全部挂载出来
        items:
          - key: ui_properties_file_name # secret 中 key 的名称,Secret 中的 ui_properties_file_name 的内容挂载出来
            path: ui_properties_file_name.md # 在容器内挂载出来的文件的路径
  restartPolicy: Always
kubectl apply -f k8s-cm.yaml

15.gif

注意:

  • ConfigMap 和 Secret 一样,环境变量引用不会热更新,而卷挂载是可以热更新的。
  • 最新版本的 ConfigMap 和 Secret 提供了不可更改的功能,即禁止热更新,只需要在 Secret 或 ConfigMap 中设置 immutable = true

2.3.3 ConfigMap 结合 SpringBoot 做到生产配置无感知

16.png

第三章:临时存储

3.1 概述

  • Kubernetes 为了不同的目的,支持几种不同类型的临时卷:

  • emptyDirconfigMapdownwardAPIsecret 是作为 本地临时存储 提供的。它们由各个节点上的 kubelet 管理。
  • CSI 临时卷 必须 由第三方 CSI 存储驱动程序提供。
  • 通用临时卷 可以 由第三方 CSI 存储驱动程序提供,也可以由支持动态配置的任何其他存储驱动程序提供。 一些专门为 CSI 临时卷编写的 CSI 驱动程序,不支持动态供应:因此这些驱动程序不能用于通用临时卷。
  • 使用第三方驱动程序的优势在于,它们可以提供 Kubernetes 本身不支持的功能, 例如,与 kubelet 管理的磁盘具有不同运行特征的存储,或者用来注入不同的数据。

3.2 emptyDir

  • Pod 分派到某个 Node 上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。
  • 就像其名称表示的那样,卷最初是空的。
  • 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir 卷中相同的文件。
  • 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。
注意: 容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的。
  • emptyDir 的一些用途:

    • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
    • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
  • emptyDir 卷存储在该节点的磁盘或内存中,如果设置 emptyDir.medium = Memory ,那么就告诉 Kubernetes 将数据保存在内存中,并且在 Pod 被重启或删除前,所写入的所有文件都会计入容器的内存消耗,受到容器内存限制约束。

17.png

  • 示例:
vi k8s-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-empty-dir
  namespace: default
  labels:
    app: nginx-empty-dir
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi    
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: app
      mountPath: /usr/share/nginx/html
  - name: alpine
    image: alpine
    command: ["/bin/sh","-c","while true;do sleep 1; date > /app/index.html;done"]
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi    
    volumeMounts:
    - name: app
      mountPath: /app     
  volumes:
    - name: app
      emptyDir: {} # emptyDir 临时存储
  restartPolicy: Always
kubectl apply -f k8s-emptyDir.yaml

18.gif

3.3 hostPath

  • emptyDir 中的数据不会被持久化,它会随着 Pod 的结束而销毁,如果想要简单的将数据持久化到主机中,可以选择 hostPath 。
  • hostPath 就是将 Node 主机中的一个实际目录挂载到 Pod 中,以供容器使用,这样的设计就可以保证 Pod 销毁了,但是数据依旧可以保存在 Node 主机上。
注意:hostPath 之所以被归为临时存储,是因为实际开发中,我们一般都是通过 Deployment 部署 Pod 的,一旦 Pod 被 Kubernetes 杀死或重启拉起等,并不一定会部署到原来的 Node 节点中。
  • 除了必需的 path 属性之外,用户可以选择性地为 hostPath 卷指定 type

    取值行为
    空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。
    DirectoryOrCreate如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。
    Directory在给定路径上必须存在的目录。
    FileOrCreate如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。
    File在给定路径上必须存在的文件。
    Socket在给定路径上必须存在的 UNIX 套接字。
    CharDevice在给定路径上必须存在的字符设备。
    BlockDevice在给定路径上必须存在的块设备。
  • hostPath 的典型应用就是时间同步:通常而言,Node 节点的时间是同步的(云厂商提供的云服务器的时间都是同步的),但是,Pod 中的容器的时间就不一定了,有 UTC 、CST 等;同一个 Pod ,如果部署到中国,就必须设置为 CST 了。
  • 示例:
vi k8s-hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-host-path
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx    
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
  volumes:
    - name: localtime
      hostPath: # hostPath
        path: /usr/share/zoneinfo/Asia/Shanghai
  restartPolicy: Always
kubectl apply -f k8s-hostPath.yaml

19.gif

第四章:持久化存储

4.1 VOLUME

4.1.1 基础

  • Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。
  • 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。
  • 当 Pod 不再存在时,Kubernetes 也会销毁临时卷。
  • Kubernetes 不会销毁 持久卷
  • 对于给定 Pod 中 任何类型的卷 ,在容器重启期间数据都不会丢失。
  • 使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。

4.1.2 subPath

  • 有时,在单个 Pod 中共享卷以供多方使用是很有用的。 volumeMounts.subPath 属性可用于指定所引用的卷内的子路径,而不是其根路径。
注意:ConfigMap 和 Secret 使用子路径挂载是无法热更新的。

4.1.3 安装 NFS

  • NFS 的简介:网络文件系统,英文Network File System(NFS),是由 SUN 公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。

20.png

注意:实际开发中,不建议使用 NFS 作为 Kubernetes 集群持久化的驱动。
  • 本次以 Master (192.168.65.100)节点作为 NFS 服务端:
yum install -y nfs-utils

21.gif

  • 在 Master(192.168.65.100)节点创建 /etc/exports 文件:
# * 表示暴露权限给所有主机;* 也可以使用 192.168.0.0/16 代替,表示暴露给所有主机
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

22.gif

  • 在 Master(192.168.65.100)节点创建 /nfs/data/ (共享目录)目录,并设置权限:
mkdir -pv /nfs/data/
chmod 777 -R /nfs/data/

23.gif

  • 在 Master(192.168.65.100)节点启动 NFS :
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server

24.gif

  • 在 Master(192.168.65.100)节点加载配置:
exportfs -r

25.gif

  • 在 Master(192.168.65.100)节点检查配置是否生效:
exportfs

26.gif

  • 在 Node(192.168.65.101、192.168.65.102)节点安装 nfs-utils :
# 服务器端防火墙开放111、662、875、892、2049的 tcp / udp 允许,否则远端客户无法连接。
yum install -y nfs-utils

27.gif

  • 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令检查 nfs 服务器端是否有设置共享目录:
# showmount -e $(nfs服务器的IP)
showmount -e 192.168.65.100

28.gif

  • 在 Node(192.168.65.101、192.168.65.102)节点,执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nd
mkdir /nd
# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount
mount -t nfs 192.168.65.100:/nfs/data /nd

29.gif

  • 在 Node (192.168.65.101)节点写入一个测试文件:
echo "hello nfs server" > /nd/test.txt

30.gif

  • 在 Master(192.168.65.100)节点验证文件是否写入成功:
cat /nfs/data/test.txt

31.gif

  • 示例:
vi k8s-nginx-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html  
      mountPath: /usr/share/nginx/html/ # / 一定是文件夹
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html 
      nfs: # 使用 nfs 存储驱动
        path: /nfs/data  # nfs 共享的目录
        server:  192.168.65.100  # nfs 服务端的 IP 地址或 hostname 
  restartPolicy: Always
kubectl apply -f k8s-nginx-nfs.yaml

32.gif

4.2 PV 和 PVC

4.2.1 概述

  • 前面我们已经学习了使用 NFS 提供存储,此时就要求用户会搭建 NFS 系统,并且会在 yaml 配置 NFS,这就带来的一些问题:

    • ① 开发人员对 Pod 很熟悉,非常清楚 Pod 中的容器那些位置适合挂载出去。但是,由于 Kubernetes 支持的存储系统非常之多,开发人员并不清楚底层的存储系统,而且要求开发人员全部熟悉,不太可能(术业有专攻,运维人员比较熟悉存储系统)。
    • ② 在 yaml 中配置存储系统,就意味着将存储系统的配置信息暴露,非常不安全(容易造成泄露)。
  • 为了能够屏蔽底层存储实现的细节,方便用户使用,Kubernetes 引入了 PV 和 PVC 两种资源对象。

33.png

  • PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下 PV 由 Kubernetes 管理员进行创建和配置,它和底层具体的共享存储技术有关,并通过插件完成和共享存储的对接。
  • PVC(Persistent Volume Claim)是持久化卷声明的意思,是用户对于存储需求的一种声明。换言之,PVC 其实就是用户向Kubernetes 系统发出的一种资源需求申请。

PV 的缺点:

  • ① 需要运维事先准备好 PV 池。
  • ② 资源浪费:没有办法预估合适的 PV,假设运维向 k8s 申请了 20m 、50m、10G 的 PV,而开发人员申请 2G 的 PVC ,那么就会匹配到 10G 的PV ,这样会造成 8G 的空间浪费。

也有人称 PV 为静态供应。

4.2.2 PVC 和 PV 的基本演示

  • 创建 PV (一般是运维人员操作):
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-10m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 10m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-20m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
--- 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-500m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 500m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-1g
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
kubectl apply -f k8s-pv.yaml

34.gif

  • 创建 PVC (一般是开发人员):
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc-500m
  namespace: default
  labels:
    app: nginx-pvc-500m
spec:
  storageClassName: nfs-storage
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 500m
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html
      mountPath: /usr/share/nginx/html/ 
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html    
      persistentVolumeClaim:
        claimName:  nginx-pvc-500m
        readOnly: false  
  restartPolicy: Always
kubectl apply -f k8s-pvc.yaml

35.gif

注意:

  • pv 和 pvc 的 accessModes 和 storageClassName 必须一致。
  • pvc 申请的空间大小不小于 pv 的空间大小。
  • storageClassName 就相当于分组的组名,通过 storageClassName 可以区分不同类型的存储驱动,主要是为了方便管理。

36.png

注意:

  • Pod 的删除,并不会影响 PVC;换言之,PVC 可以独立于 Pod 存在,PVC 也是 K8s 的系统资源。不过,推荐将 PVC 和 Pod 也在一个 yaml 文件中。
  • PVC 删除会不会影响到 PV,要根据 PV 的回收策略决定。

4.2.3 PV 的回收策略

  • 目前的回收策略有:

    • Retain:手动回收(默认)。
    • Recycle:基本擦除 (rm -rf /thevolume/*)。
    • Delete:诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除。
  • 目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。
  • 示例:演示 Retain
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-10m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 10m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-20m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
--- 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-500m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 500m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-1g
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
kubectl apply -f k8s-pv.yaml
echo 111 > /nfs/data/500m/index.html
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc-500m
  namespace: default
  labels:
    app: nginx-pvc-500m
spec:
  storageClassName: nfs-storage
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 500m
kubectl apply -f k8s-pvc.yaml
kubectl delete -f k8s-pvc.yaml

37.gif

  • 示例:演示 Recycle
mkdir -pv /nfs/data/10m
mkdir -pv /nfs/data/20m
mkdir -pv /nfs/data/500m
mkdir -pv /nfs/data/1Gi
vi k8s-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-10m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 10m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/10m # nfs 共享的目录,mkdir -pv /nfs/data/10m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
  persistentVolumeReclaimPolicy: Recycle  # 回收策略
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-20m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/20m # nfs 共享的目录,mkdir -pv /nfs/data/20m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
  persistentVolumeReclaimPolicy: Recycle  # 回收策略  
--- 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-500m
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 500m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/500m # nfs 共享的目录,mkdir -pv /nfs/data/500m
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
  persistentVolumeReclaimPolicy: Recycle  # 回收策略  
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-1g
spec:
  storageClassName: nfs-storage # 用于分组
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: # 使用 nfs 存储驱动
    path: /nfs/data/1Gi # nfs 共享的目录,mkdir -pv /nfs/data/1Gi
    server: 192.168.65.100 # nfs 服务端的 IP 地址或 hostname
  persistentVolumeReclaimPolicy: Recycle  # 回收策略
kubectl apply -f k8s-pv.yaml
echo 111 > /nfs/data/500m/index.html
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc-500m
  namespace: default
  labels:
    app: nginx-pvc-500m
spec:
  storageClassName: nfs-storage
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 500m
kubectl apply -f k8s-pvc.yaml
kubectl delete -f k8s-pvc.yaml

38.gif

4.2.4 PV 的生命周期

  • 一个 PV 的生命周期,可能会处于 4 种不同的阶段:

    • Available(可用):表示可用状态,还未被任何 PVC 绑定。
    • Bound(已绑定):表示 PV 已经被 PVC 绑定。
    • Released(已释放):表示 PVC 被删除,但是资源还没有被集群重新释放。
    • Failed(失败):表示该 PV 的自动回收失败。

4.2.5 PV 的访问模式

  • 访问模式(accessModes):用来描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

    • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。
    • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。
    • ReadWriteMany(RWX):读写权限,可以被多个节点挂载。

4.2.6 生命周期

39.png

  • PVC 和 PV 是一一对应的,PV 和 PVC 之间的相互作用遵循如下的生命周期:
  • ① 资源供应:管理员手动创建底层存储和 PV。
  • ② 资源绑定:

    • 用户创建 PVC ,Kubernetes 负责根据 PVC 声明去寻找 PV ,并绑定在用户定义好 PVC 之后,系统将根据 PVC 对存储资源的请求在以存在的 PV 中选择一个满足条件的。

      • 一旦找到,就将该 PV 和用户定义的 PVC 进行绑定,用户的应用就可以使用这个 PVC 了。
      • 如果找不到,PVC 就会无限期的处于 Pending 状态,直到系统管理员创建一个符合其要求的 PV 。
    • PV 一旦绑定到某个 PVC 上,就会被这个 PVC 独占,不能再和其他的 PVC 进行绑定了。
  • ③ 资源使用:用户可以在 Pod 中像 Volume 一样使用 PVC ,Pod 使用 Volume 的定义,将 PVC 挂载到容器内的某个路径进行使用。
  • ④ 资源释放:

    • 用户删除 PVC 来释放 PV 。
    • 当存储资源使用完毕后,用户可以删除 PVC,和该 PVC 绑定的 PV 将会标记为 已释放 ,但是还不能立刻和其他的 PVC 进行绑定。通过之前 PVC 写入的数据可能还留在存储设备上,只有在清除之后该 PV 才能再次使用。
  • ⑤ 资源回收:

    • Kubernetes 根据 PV 设置的回收策略进行资源的回收。
    • 对于 PV,管理员可以设定回收策略,用于设置与之绑定的 PVC 释放资源之后如何处理遗留数据的问题。只有 PV 的存储空间完成回收,才能供新的 PVC 绑定和使用。

4.3 动态供应

4.3.1 概述

  • 静态供应:集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
  • 动态供应:集群自动根据 PVC 创建出对应 PV 进行使用。

40.png

4.3.2 动态供应的完整流程

41.png

  • ① 集群管理员预先创建存储类(StorageClass)。
  • ② 用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim)。
  • ③ 存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume)。
  • ④ 系统读取存储类的信息。
  • ⑤ 系统基于存储类的信息,在后台自动创建 PVC 需要的 PV 。
  • ⑥ 用户创建一个使用 PVC 的 Pod 。
  • ⑦ Pod 中的应用通过 PVC 进行数据的持久化。
  • ⑧ PVC 使用 PV 进行数据的最终持久化处理。

4.3.3 设置 NFS 动态供应

注意:不一定需要设置 NFS 动态供应,可以直接使用云厂商提供的 StorageClass 。
vi k8s-nfs-provisioner.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 指定一个供应商的名字 
# or choose another name, 必须匹配 deployment 的 env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false" # 删除 PV 的时候,PV 中的内容是否备份
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  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: ccr.ccs.tencentyun.com/gcr-containers/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: 192.168.65.100 # NFS 服务器的地址
            - name: NFS_PATH
              value: /nfs/data # NFS 服务器的共享目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.65.100
            path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["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
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f k8s-nfs-provisioner.yaml

42.gif

  • 示例:测试动态供应
vi k8s-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
  namespace: default
  labels:
    app: nginx-pvc
spec:
  storageClassName: nfs-client # 注意此处
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html
      mountPath: /usr/share/nginx/html/ 
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html    
      persistentVolumeClaim:
        claimName:  nginx-pvc
        readOnly: false  
  restartPolicy: Always
kubectl apply -f k8s-pvc.yaml

43.gif

4.3.4 设置 SC 为默认驱动

  • 命令:
kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
  • 设置 SC 为默认驱动:
kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
  • 示例:测试默认驱动
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
  namespace: default
  labels:
    app: nginx-pvc
spec:
  # storageClassName: nfs-client 不写,就使用默认的
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.20.2
    resources:
      limits:
        cpu: 200m
        memory: 500Mi
      requests:
        cpu: 100m
        memory: 200Mi
    ports:
    - containerPort:  80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html
      mountPath: /usr/share/nginx/html/ 
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html    
      persistentVolumeClaim:
        claimName:  nginx-pvc
        readOnly: false  
  restartPolicy: Always

4.3.5 展望

  • 目前,只需要运维人员部署好各种 storageclass,开发人员在使用的时候,创建 PVC 即可;但是,存储系统太多太多,运维人员也未必会一一掌握,此时就需要 Rook 来统一管理了。

44.png

标签: none

非特殊说明,本博所有文章均为博主原创。

评论啦~



唉呀 ~ 仅有一条评论


  1. 云原生 - GA666666 Blog ~ 个人博客
    云原生 - GA666666 Blog ~ 个人博客

    [...]Kubernetes 概念 Kubernetes(v1.21)工作负载 Kubernetes(v1.21)配置和存储Kubernetes(v1.21)网络Kubernetes(v1.21)调度原理Kubernetes(v1.21)安全K8s - Ingress 限流K8s-Pod重启策略Kubernetes滚动更新解决Mac/Windows版Desktop Docker中自带的K8s无法访问pod[...]

    回复 2024-02-21 11:07