Skip to content

Kubernetes(v1.21)配置和存储

第一章:概述

1.1 概述

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

1648538838112-21115f5c-610d-4765-9636-9f281d564d4b.png

1.2 Kubernetes 支持的 Volume 类型

1.2.1 简版

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

1648538843251-3e1b92fa-f4a9-43ad-87e8-306789b291b3.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 的种类

  • 命令行:
kubectl create secret --help

1648538854640-4bac1758-5fae-4fea-a281-832ffc62f8f7.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

1648538882422-6547a530-10bf-426f-8986-ef3ab9232d90.gif

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

1648538888482-d55014d8-093c-44eb-80b2-e206fd99df1d.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

1648538894064-edc373fa-e06e-43e1-8272-fd4e9181204c.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 会被忽略。

1648538900416-71ab061b-b58d-4877-b0d5-1e1534212807.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 默认是文件名的名称。

1648538907139-d2225678-2d8b-4ed4-9f41-6caf923b3867.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

1648538912153-d646e07b-4278-4843-8fa7-8b9c6a4f16b2.gif

2.2.5 查看 Secret

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

1648538917267-ce6c97cd-6860-4f6c-a9d3-e9af05c48131.gif

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

1648538922215-8160aec6-bf49-43c8-98b7-0c7df1f886c6.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

1648538930574-f182faf1-72e6-4359-822d-1aaee84eed31.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

1648538939934-2a0872a5-6246-4ed9-be3c-0607b7265711.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

1648538954791-b7cf4b3d-df2e-4910-9d36-5fa1e8b1050c.gif

注意:

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

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

1648538977212-715a4ba9-6e3a-45ba-aecc-dd7541dbf870.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 被重启或删除前,所写入的所有文件都会计入容器的内存消耗,受到容器内存限制约束。

1648538985888-b939e85b-0b3f-45c3-9325-de103cf95452.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

1648538993617-1fd46674-4eb4-42e7-95c9-7727d376ebf6.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

1648539004875-42ff0283-212b-407c-aa1d-7cda9512bf93.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),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。

1648539022632-fd301755-4510-45e3-b047-a3a1e6916aff.png

注意:实际开发中,不建议使用 NFS 作为 Kubernetes 集群持久化的驱动。

  • 本次以 Master (192.168.65.100)节点作为 NFS 服务端:
yum install -y nfs-utils

1648539028783-7fb47517-2d21-4992-8db2-f75bb7fb8fe0.gif

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

1648539033729-6b343965-c3dc-499c-85a9-091600878927.gif

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

1648539043851-587bb321-b321-43e6-968d-3641077377bb.gif

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

1648539050825-c0b9503d-d505-44db-82f1-6463c1e7da63.gif

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

1648539057305-42adf58d-3986-4a5c-a1f6-a4e435381262.gif

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

1648539062629-f9e6cb6e-f2d8-402c-b812-2adedec301d7.gif

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

1648539068204-64111820-8812-4087-ab3f-adaca25789e5.gif

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

1648539075024-98745fac-4558-42a3-8b0d-df6df055a99c.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

1648539080884-3ad63f96-8cec-48bb-94c3-9c5c891debc4.gif

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

1648539086448-56dec778-fe79-4c5b-b3d3-2f9cb83e665a.gif

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

1648539092407-c7e57419-6379-4c86-a11c-23d449da22af.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

1648539099242-3d5459e5-4561-4eba-bd9c-4cd16b006b18.gif

4.2 PV 和 PVC

4.2.1 概述

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

1648539110775-c090d1e7-d19d-4bdd-9776-ac1895d5956f.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

1648539126302-41900c58-d047-4b63-8905-220b4616cc81.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

1648539133239-75114df5-7b3f-43d3-bcb7-c7f1059c23e5.gif

注意:

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

1648539143062-f339da24-4f86-489f-bba0-53916fae789c.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: 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
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

1648539168066-b64267f0-2b8a-4b8c-be38-289723d62ff0.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

1648539176824-14b81f18-6d37-483a-81f3-8e84383eb75d.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 生命周期

1648539183133-b087587e-2fba-4f90-b4c0-07d62f088488.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 进行使用。

1648539190719-9b6bc9f5-abd0-435e-a518-18fab195e712.png

4.3.2 动态供应的完整流程

1648539197723-ab90990e-7dab-492e-94ec-4e37d33583b4.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

1648539260825-a65e96b6-d59d-4b71-afb7-7629c0b60214.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

1648539267923-eaa9fc10-dd14-44ee-a5cb-c1abec2ad26a.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 来统一管理了。

1648539275401-cdba0b86-a151-4323-a6cd-792c5daababf.png

更新: 2023-03-24 02:35:59
原文: https://www.yuque.com/fairy-era/yg511q/pyll1k

Comments