k8s-存储

一、ConfigMap

ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象

ConfigMap 的创建

1. 资源清单创建

  1. 创建 ConfigMap 的资源清单:

    apiVersion: v1			# 版本,通过 kubectl explain cm 可以查看
    kind: ConfigMap
    metadata:
      name: special-config	# ConfigMap 的名字
      namespace: default	# 名称空间
    data:					# key: value 结构,配置数据
      special.how: very
      special.type: charm
    
  2. 创建

    kubectl apply -f comfigmap.yaml
    

2. 使用目录创建

  1. 创建 /root/k8s/yaml/configmap/game.properties 文件:

    enemies=aliens
    lives=3
    enemies.cheat=true
    enemies.cheat.level=noGoodRotten
    secret.code.passphrase=UUDDLRLRBABAS
    secret.code.allowed=true
    secret.code.lives=30
    
  2. 创建 /root/k8s/yaml/configmap/ui.properties 文件:

    color.good=purple
    color.bad=yellow
    allow.textmode=true
    how.nice.to.look=fairlyNice
    
  3. 创建 configmap ,--from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容

    kubectl create configmap game-config --from-file=../configmap/
    
  4. 查看创建的 configmap(可简写为 cm):

    $ kubectl get cm
    NAME          DATA   AGE
    game-config   2      6m40s
    
    # 查看详细信息
    kubectl get cm game-config -o yaml
    kubectl describe cm game-config
    

3. 使用文件创建

通过 --from-file 参数只要指定为一个文件就可以从单个文件中创建 ConfigMap

--from-file 这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的

kubectl create configmap game-config-2 --fromfile=game.properties

kubectl get configmaps game-config-2 -o yaml

4. 使用字面值创建

使用文字值创建,利用 --from-literal 参数传递配置信息,该参数可以使用多次,格式如下

kubectl create configmap special-config --from-literal=special.how=very --fromliteral=special.type=charm

kubectl get configmaps special-config -o yaml

Pod 中使用 ConfigMap

1. 使用 ConfigMap 来替代环境变量

  1. 创建两个 ConfigMap(configmap.yaml):

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: env-config
      namespace: default
    data:
      log_level: INFO
    
  2. 创建 Pod,并引入

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
      - name: test-container
        image: wangyanglinux/myapp:v1
        command: [ "/bin/sh", "-c", "env" ]			# 打印 env
        env:										# 从 ConfigMap 中选择读取的键,并起个别名
        - name: SPECIAL_LEVEL_KEY					# 键别名,在这值应该是 very
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名称
              key: special.how						# 上句指定 ConfigMap 中的键名
        - name: SPECIAL_TYPE_KEY					# 键别名,在这值应该是 charm
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名称
              key: special.type						# 上句指定 ConfigMap 中的键名
        envFrom:									# 直接从 ConfigMap 中读取全部配置
        - configMapRef:
            name: env-config						# ComfigMap 的名称
      restartPolicy: Never
    
  3. 查看日志,可以看到 ConfigMap 中的配置已经注入到了容器中
    在这里插入图片描述

2. 使用 ConfigMap 设置命令行参数

  1. 创建 ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    
  2. 创建 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
      - name: test-container
        image: wangyanglinux/myapp:v1
        command: [ "/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]	#可以调整启动Pod时的命令
        env:										# 从 ConfigMap 中选择读取的键,并起个别名
        - name: SPECIAL_LEVEL_KEY					# 键别名,在这值应该是 very
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名称
              key: special.how						# 上句指定 ConfigMap 中的键名
        - name: SPECIAL_TYPE_KEY					# 键别名,在这值应该是 charm
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名称
              key: special.type
      restartPolicy: Never
    
  3. 查看日志

    $ kubectl logs dapi-test-pod
    very charm
    

3. 通过数据卷插件使用ConfigMap

通过 Volume 方式挂载,ConfigMap 中的键名就是 文件名,键值就是 文件内容

  1. 创建 ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    
  2. 创建 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
        - name: test-container
          image: wangyanglinux/myapp:v1
          command: ["/bin/sh", "-c", "cat /etc/config/special.how"]    # 打印挂载目录下的文件内容
          volumeMounts:                 # volume 挂载
            - name: config-volume       # 挂载下面指定的 volume
              mountPath: /etc/config    # 挂载到的目录(容器内路径,该目录下,文件名就里键名,文件内容就是键值)
      volumes:
        - name: config-volume           # volume 名称
          configMap:                    # 来自 ConfigMap
            name: special-config        # ConfigMap 名字
      restartPolicy: Never
    
  3. 查看日志

    $ kubectl logs dapi-test-pod
    very
    

4. ConfigMap 的热更新

  1. 创建一个 ConfigMapDeployment

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: log-config
      namespace: default
    data:
      log_level: INFO
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
            - name: my-nginx
              image: wangyanglinux/myapp:v1
              ports:
                - containerPort: 80
              volumeMounts:					# 这块儿不懂看上一节《通过数据卷插件使用ConfigMap》
                - name: config-volume
                  mountPath: /etc/config	# 容器内这个目录下会有 log_level 这个文件,内容为 INFO
          volumes:
            - name: config-volume
              configMap:
                name: log-config
    
  2. 查看 /etc/config/log_level 文件的内容

    $ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level
    INFO
    
  3. 修改 ConfigMap

    kubectl edit configmap log-config
    

    在这里插入图片描述

  4. 稍微等一会儿,再次查看 /etc/config/log_level 文件的内容,可以看到,Pod 中的配置也改了

    $ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level
    DEBUG
    
  • 注意:更新 ConfigMap 后:
    • 使用该 ConfigMap 挂载的 Env 不会同步更新
    • 使用该 ConfigMap 挂载的 Volume 中的数据需要一段时间(实测大概10秒)才能同步更新
  1. Pod 滚动更新

    ConfigMap 更新后,并不会让相应的文件重载。例如,Nginx 在启动时,会加载一次配置文件(配置文件中有 ConfigMap 的相关参数),加载完成后,无论这个配置文件再怎么变化,Nginx 都不会再加载它。因此需要 ConfigMap 更新后滚动更新 Pod

    • 可以通过修改 pod annotations 的方式强制触发滚动更新
    • 这里我们在 .spec.template.metadata.annotations 中添加 version/config ,每次通过修改 version/config 的时间来触发滚动更新
    kubectl patch deployment my-nginx --patch \
    '{"spec": {"template": {"metadata": {"annotations":{"version/config": "20201110" }}}}}'
    

二、Secret

Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用

Secret 有三种类型:

  • Service Account:用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod/run/secrets/kubernetes.io/serviceaccount 目录中
  • Opaquebase64 编码格式的 Secret,用来存储密码、密钥等。加密程度不高
  • kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息

1. Service Account(不常用)

Service Account 用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod/run/secrets/kubernetes.io/serviceaccount 目录中

# 1. 随便找一个需要访问 Kubernetes API 的 Pod
$ kubectl get pod -n kube-system
NAME                                   READY   STATUS    RESTARTS   AGE
kube-proxy-2pqkk                       1/1     Running   6          40d

# 2. 查看该 Pod 中 /run/secrets/kubernetes.io/serviceaccount 目录下的文件
$ kubectl exec kube-proxy-2pqkk -n kube-system -it -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt:访问 API Service 时的证书
namespace:名称空间
token:认证的密钥信息

2. Opaque Secret

Opaque 类型的数据是一个 map 类型,要求 valuebase64 编码格式:

(1)创建 Opaque Secret

  1. 给用户名和密码用 base64 加密

    $ echo -n admin | base64
    YWRtaW4=
    $ echo -n 123 | base64
    MTIz
    

    base64 解码:

    $ echo -n YWRtaW4= | base64 -d
     admin 
    
  2. 使用加密后的用户名和密码创建 Secret

    apiVersion: v1			# kubectl explain secret 查看
    kind: Secret
    metadata:
      name: mysecret		# Secret 名称
    type: Opaque			# Secret 的类型
    data:
      password: MTIz		# 密码
      username: YWRtaW4=	# 用户名
    
  3. 查看 Secret

    $ kubectl get secret
    NAME                  TYPE                                  DATA   AGE
    default-token-fm46c   kubernetes.io/service-account-token   3      40d
    mysecret              Opaque                                2      12s
    
    • default-token-xxxxx:k8s 默认会在每个名称空间下都创建一个,用于 Pod 的挂载

(2)将 Secret 挂载到 Volume 中

  1. 创建 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        name: secret-test
      name: secret-test
    spec:
      volumes:			# 创建一个卷
        - name: secrets		# 卷名
          secret:			# 卷使用的方案
            secretName: mysecret	# 来自于上一节创建的 mysecret
      containers:
        - image: wangyanglinux/myapp:v1
          name: db
          volumeMounts:		# 卷挂载
            - name: secrets		# 挂载的是上面声明的 secrets
              mountPath: "/etc/secrets"		# 挂载的目录(容器内目录)
              readOnly: true	# 只读 
    
  2. 查看

    # Opaque Secret 中的用户名和密码都已经挂载进来了
    $ kubectl exec secret-test -it -- ls /etc/secrets
    password  username
    
    # 查看内容,发现内容已经自动被解密
    $ kubectl exec secret-test -it -- cat /etc/secrets/password
    123
    $ kubectl exec secret-test -it -- cat /etc/secrets/username
    admin
    

(3)将 Secret 导出到环境变量中

  1. 创建 Deployment:

    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: pod-deployment
    spec:
      replicas: 2
      template:
        metadata:
          labels:
            app: pod-deployment
        spec:
          containers:
            - name: pod-1
              image: wangyanglinux/myapp:v1
              ports:
                - containerPort: 80
              env:
                - name: TEST_USER			# 环境变量名
                  valueFrom:
                    secretKeyRef:			# 从 Secret 中获取
                      name: mysecret		# Secret 的名字
                      key: username		# Secret 中的键名
                - name: TEST_PASSWORD		# 环境变量名
                  valueFrom:
                    secretKeyRef:			# 从 Secret 中获取
                      name: mysecret		# Secret 的名字
                      key: password		# Secret 中的键名(相比 configmap,Secret 在这儿不需要使用明文,稍微安全一点)
    
  2. 查看环境变量

    # 进入容器
    $ kubectl exec pod-deployment-747f78bc67-2w9wk -it -- /bin/sh
    
    # 查看环境变量
    $ echo $TEST_USER
    admin
    $ echo $TEST_PASSWORD
    123
    

3. kubernetes.io/dockerconfigjson

使用 Kuberctl 创建 docker registry 认证的 secret

# kubectl create secret docker-registry \	# 创建 Secret 的类型
#    myregistrykey \  						# Secret 的名称
#    --docker-server=hub.zyx.com \			# docker server 的地址
#    --docker-username=admin \				# docker 用户名
#    --docker-password=Harbor12345 \		# docker 密码
#    [email protected] 				# docker 邮箱
kubectl create secret docker-registry \
    myregistrykey \
    --docker-server=hub.zyx.com \
    --docker-username=admin \
    --docker-password=Harbor12345 \
    --docker-[email protected]

在创建 Pod 的时候,通过 imagePullSecrets 来引用刚创建的 myregistrykey,来拉取私有仓库的镜像

apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
    - name: foo
      image: hub.zyx.com/zyx/myapp:v1
  imagePullSecrets:			# 当去私有仓库拉取时的认证信息
    - name: myregistrykey	# 认证信息,上一步创建的 docker registry

三、Volume

容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。

  1. 首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。

  2. 其次,在 Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。

Kubernetes 中的 Volume 抽象就很好的解决了这些问题

Kubernetes 中的卷有明确的寿命 —— 与封装它的 Pod 相同。所以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes 支持多种类型的卷,Pod 可以同时使用任意数量的卷

Kubernetes 支持以下类型的卷:

  • awsElasticBlockStore azureDisk azureFile cephfs csi downwardAPI emptyDir
  • fc flocker gcePersistentDisk gitRepo glusterfs hostPath iscsi local nfs
  • persistentVolumeClaim projected portworxVolume quobyte rbd scaleIO secret
  • storageos vsphereVolume

1. emptyDir

Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。该卷可以挂载到 Pod 每个容器中的相同或不同路径上,并且每个容器都可以读取和写入 emptyDir 卷中的文件。当出于任何原因从节点中删除 Pod 时, emptyDir 中的数据将被永久删除

注意:容器崩溃不会从节点中移除 pod,因此 emptyDir 卷中的数据在容器时是安全的

emptyDir 的用法有:

  • 暂存空间,例如用于基于磁盘的合并排序
  • 用作长时间计算崩溃恢复时的检查点
  • Web 服务器容器提供数据时,保存内容管理器容器提取的文件
  1. 创建一个 Pod,里面有两个容器,都挂载同一个 emptyDir

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-pd
    spec:
      containers:
        - image: wangyanglinux/myapp:v1
          name: test-container		# 容器名
          volumeMounts:				
            - mountPath: /cache		# 挂载到容器的哪个目录下
              name: cache-volume	# 通过哪个 volume 挂载
        - name: test2-container
          image: busybox 
          args: 					# 运行命令,睡眠,防止容器退出
            - /bin/sh 
            - -c 
            - sleep 6000s
          volumeMounts:				
            - mountPath: /test		# 挂载到容器的哪个目录下
              name: cache-volume	# 通过哪个 volume 挂载
      volumes:
        - name: cache-volume		# volume 名称
          emptyDir: {
          
          }				# volume 类型
    
  2. 可以看到,两个容器都能读取到 emptyDir 中的数据

    # 在容器2的 /test 目录下,创建一个 index.html 文件
    $ kubectl exec test-pd -c test2-container -it -- touch /test/index.html
    
    # 查看容器1的 /cache 目录
    $ kubectl exec test-pd -c test-container -it -- ls /cache
    index.html
    

2. hostPath

hostPath 卷将主机节点的文件系统中的文件或目录挂载到集群中

hostPath 的用途如下:

  • 运行需要访问 Docker 内部的容器;使用 /var/lib/dockerhostPath
  • 在容器中运行 cAdvisor;使用 /dev/cgroupshostPath

注意事项

  1. 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的 pod 在不同节点上的行为可能会有所不同。
  2. Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源
  3. 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入 hostPath

(1)type 的值

行为
空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。
Directory 给定的路径下必须存在目录
FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
File 给定的路径下必须存在文件
Socket 给定的路径下必须存在 UNIX 套接字
CharDevice 给定的路径下必须存在字符设备
BlockDevice 给定的路径下必须存在块设备

(2)示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
    - image: wangyanglinux/myapp:v1
      name: test-container
      volumeMounts:
        - mountPath: /test-pd			# 挂载到的容器内路径
          name: test-volume				# 选择下面声明的 volume 进行挂载
  volumes:
    - name: test-volume					# volume名称
      hostPath:							# volume 类型
        path: /data						# 本机的 /data 目录(Pod 运行在集群的哪个节点,就是哪个节点上的 /data 目录)
        type: Directory					# 类型(如果不存在 /data 会报错)

注意:要确保每个 k8s 节点上都存在 /data 这个目录

四、PV/PVC

1. 概念

  • PersistentVolume (PV):

    是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PVVolume 之类的卷插件,但具有独立于使用 PVPod 的生命周期。此 API 对象包含存储实现的细节,即 NFSiSCSI 或特定于云供应商的存储系统

  • PersistentVolumeClaim (PVC):

    是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。PVC 可以声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)

生命周期

生命周期

PV的分类
  • 静态 pv:

    集群管理员创建一些 PV。它们带有可供群集用户使用的实际存储的细节,一般保存访问至后端存储的细节(怎么连接,地址多少…)。它们存在于 Kubernetes API 中,可用于消费

静态pv

  • 动态 pv:

    当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试动态地为 PVC 创建卷。此配置基于 StorageClasses :PVC 必须请求 [存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为 "" 可以有效地禁用其动态配置

    要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的 DefaultStorageClass [准入控制器]。例如,通过确保 DefaultStorageClass 位于 API server 组件的 --admission-control 标志,使用逗号分隔的有序值列表中,可以完成此操作

动态pv

绑定:

master 中的控制环路监视新的 PVC,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。

一旦 PV 和 PVC 绑定后, PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVCPV 绑定是一对一的映射

持久化卷声明的保护

PVC 保护的目的是确保由 Pod 正在使用的 PVC 不会从系统中移除,因为如果被移除的话可能会导致数据丢失。

注意:当 Pod 状态为 Pending 并且 Pod 已经分配给节点或者 Pod 为 Running 状态时,PVC 处于活动状态。

当启用PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用

2. PV 一些概念

(1)PV 的类型(插件)

PersistentVolume 类型以插件形式实现。Kubernetes 目前支持以下插件类型:

  • GCEPersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC (Fibre Channel)
  • FlexVolume、Flocker、NFS、iSCS、RBD(Ceph Block Device)、CephFS
  • Cinder(OpenStack block storage)、Glusterfs、VsphereVolume、Quobyte、Volumes
  • HostPath、VMware、Photon、Portworx、Volumes、ScaleIO、Volumes、StorageOS

(2)访问模式

PersistentVolume 可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个 PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式

  • ReadWriteOnce——该卷可以被单个节点以读/写模式挂载
  • ReadOnlyMany——该卷可以被多个节点以只读模式挂载
  • ReadWriteMany——该卷可以被多个节点以读/写模式挂载
    在命令行中,访问模式缩写为:
  • RWO:ReadWriteOnce
  • ROX:ReadOnlyMany
  • RWX:ReadWriteMany

注意:一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。例如,GCEPersistentDisk 可以由单个节点作为 ReadWriteOnce 模式挂载,或由多个节点以 ReadWriteMany 模式挂载,但不能同时挂载

Volume 插件 ReadWriteOnce ReadOnlyMany ReadWriteMany
AWSElasticBlockStoreAWSElasticBlockStore - -
AzureFile
AzureDisk - -
CephFS
Cinder - -
FC -
FlexVolume -
Flocker - -
GCEPersistentDisk -
Glusterfs
HostPath - -
iSCSI -
PhotonPersistentDisk - -
Quobyte
NFS
RBD -
VsphereVolume - -
PortworxVolume -
ScaleIO -
StorageOS - -

(3)回收策略

  • Retain(保留):手动回收
  • Recycle(回收):基本擦除( 相当于执行了 rm -rf /thevolume/*
  • Delete(删除):关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除

当前,只有 NFSHostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略

(4)状态

卷可以处于以下的某种状态:

  • Available(可用):一块空闲资源还没有被任何声明绑定
  • Bound(已绑定):卷已经被声明绑定
  • Released(已释放):声明被删除,但是资源还未被集群重新声明
  • Failed(失败):该卷的自动回收失败

命令行会显示绑定到 PV 的 PVC 的名称

(5)模板

apiVersion: v1
kind: PersistentVolume		# 类型:PV
metadata:
  name: pv0003				# 名称
spec:
  capacity:
    storage: 5Gi			# 卷的大小:5G
  volumeMode: Filesystem	# 文件类型
  accessModes:				# 访问策略
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle	# 回收策略
  storageClassName: slow	# 存储类的一个名称
  mountOptions:				# 其它说明,也可以不指定,让他自己做判断
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp				# 挂载到哪个目录下
    server: 172.17.0.2		# 挂载到哪个服务器

3. NFS 持久化示例

(1)安装 NFS

  1. 新建一台虚拟机,安装 NFS,我的虚拟机 IP 为 192.168.66.20

    yum install -y nfs-common nfs-utils rpcbind
    mkdir /nfs
    chmod 777 /nfs
    chown nfsnobody /nfs
    
    vim /etc/exports	# 文件中写入以下内容
    	/nfs *(rw,no_root_squash,no_all_squash,sync)
    
    systemctl start rpcbind
    systemctl start nfs
    
  2. 在 k8s 每个节点中安装 NFS 客户端:

    # 安装依赖
    $ yum -y install nfs-utils rpcbind
    
  3. NFS 的一些操作

    # 查看共享目录
    $ showmount -e 192.168.66.20
    Export list for 192.168.66.20:
    /nfs *
    
    # 共享目录与本地目录挂载
    $ mkdir ~/test
    $ mount -t nfs 192.168.66.20:/nfs ~/test
    
    # 解除挂载
    $ umount ~/test
    

(2)创建 PV 和 StatefulSet

在这里插入图片描述

  1. 创建一个 pv.yaml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: nfspv1
    spec:
      capacity:				# 容量
        storage: 10Gi
      accessModes:			# 访问模式
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Retain		# 回收策略
      storageClassName: nfs
      nfs:						# nfs服务器配置
        path: /nfs				# 目录
        server: 192.168.66.20	# IP
    
  2. 创建 PV

    $ kubectl apply -f nfspv.yaml
    persistentvolume/nfspv1 created
    
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Available           nfs                  7s
    
  3. 创建 Service 和 StatefulSet

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      ports:
        - port: 80
          name: web
      clusterIP: None
      selector:
        app: nginx
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      selector:
        matchLabels:
          app: nginx
      serviceName: "nginx"		# 指定 Service 名称(上面创建的,一定要是个无头服务)
      replicas: 3		# 副本数
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:		# 容器信息
            - name: nginx
              image: wangyanglinux/myapp:v2
              ports:
                - containerPort: 80		# 释放的端口
                  name: web		# 端口名字
              volumeMounts:		# 挂载
                - name: www
                  mountPath: /usr/share/nginx/html	 # 容器内目录
      volumeClaimTemplates:		# 卷请求声明模板(pvc模板)
        - metadata:
            name: www
          spec:
            accessModes: [ "ReadWriteOnce" ]	# 指定要请求的卷的访问模式
            storageClassName: "nfs"		# 指定要请求的卷的类名,只有与 PV 中的storageClassName 相同时,才会匹配
            resources:
              requests:
                storage: 1Gi	# 指定要请求的卷大小必须满足 1G
    
  4. 因为前面只创建了一个 pv,所以 StatefulSet 只有一个 Pod 可以匹配,查看 Pod:

    # 查看pod,只有一个因为pv,所以只有一个pod匹配成功后正常运行
    # 因为是 StatefulSet,第二个没能正常启动,所以第三个Pod不会创建
    $ kubectl get pod
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          57s
    web-1   0/1     Pending   0          54s
    
    # 查看 pv,可以看到只有一个绑定成功
    # 第二个绑定不成功是因为访问模式不匹配
    # 第三个绑定不成功是因为 storageClass 不匹配
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Bound       default/www-web-0   nfs                     3m35s
    nfspv2   5Gi        ROX            Retain           Available                       nfs                     34m
    nfspv3   5Gi        RWO            Retain           Available                       nfs1                    34m
    
    # 查看 pvc,每个Pod有一个pvc
    $ kubectl get pvc
    NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    www-web-0   Bound     nfspv1   10Gi       RWO            nfs            6m6s
    www-web-1   Pending                                      nfs            6m3s
    
  5. 在 NFS 服务器的 /nfs 目录中创建 index.html,写入“/nfs访问成功”,然后通过 nginx 来访问:

    # 获取IP
    $ kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          27m   10.244.1.58   k8s-node01   <none>           <none>
    
    $ curl 10.244.1.58
    /nfs访问成功
    
  6. 尝试删除 Pod,pv 中的数据不会丢失

    $ kubectl delete pod web-0
    pod "web-0" deleted
    
    # 可以看到 IP 已经变了,说明上一个Pod删除后又建了个新的(Pod 的 name 一致)
    $ kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          15s   10.244.2.60   k8s-node02   <none>           <none>
    
    # 可以看到仍然可以成功访问,数据不会丢失
    $ curl 10.244.2.60
    /nfs访问成功
    
  7. 删除 StatefulSet 后,pvc 不会自动删除,pv也不会自动释放,需要手动删除

    # 删除 StatefulSet 后,pvc 仍然存在
    $ kubectl delete statefulset web
    $ kubectl get pvc
    NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    www-web-0   Bound     nfspv1   10Gi       RWO            nfs            13h
    
    # 删除 pvc 后,pv 没有自动释放
    $ kubectl delete pvc www-web-0
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Released    default/www-web-0   nfs                     13h
    
    # 手动释放 pv
    $ kubectl edit pv nfspv1
    # 将下面的 spec.claimRef 删除
    	spec:
    	  claimRef:
    	    apiVersion: v1
    	    kind: PersistentVolumeClaim
    	    name: www-web-0
    	    namespace: default
    	    resourceVersion: "619064"
    	    uid: 99cea07e-339e-431c-bcb6-c398c884b29c
    
    # 再次查看 pv 已经得到释放
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Available           nfs                     13h
    

3. PV 的一些说明

  • 匹配 Pod name ( 网络标识 ) 的模式为:$(StatefulSet名称)-$(序号),比如上面的示例:web-0,web-1,web-2

  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名(意味着其它 Pod 可以通过这个域名来访问),这个域名的格式为: $(podname).(headless server name),也就意味着服务间是通过 Pod 域名来通信而非 Pod IP,因为当 Pod 所在 Node 发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化

    # 随便进入一个以前的 Pod,没有就新建一个,然后ping域名,可以成功 ping 通
    $ ping web-0.nginx
    PING web-0.nginx (10.244.2.60): 56 data bytes
    64 bytes from 10.244.2.60: seq=0 ttl=62 time=0.388 ms
    64 bytes from 10.244.2.60: seq=1 ttl=62 time=0.263 ms
    
  • StatefulSet 使用 Headless 服务来控制 Pod 的域名(意味着 Pod 外部可以通过这个域名来访问),这个域名的 FQDN(完全现定域名) 为:$(service name).(namespace).svc.cluster.local,其中,“cluster.local” 指的是集群的域名

    # 1. 查看 DNS 服务器 coredns 的 ip
    $ kubectl get pod -n kube-system -o wide
    NAME                                   READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
    coredns-5c98db65d4-5ztqn               1/1     Running   10         46d   10.244.0.19     k8s-master01   <none>           <none>
    coredns-5c98db65d4-pc62t               1/1     Running   10         46d   10.244.0.18     k8s-master01   <none>           <none>
    
    # 2. 通过 coredns 来解析域名,可以看到解析后的域名对应 StatefulSet 下的 Pod 的 IP
    # 用 dig 解析域名(没有 dig 要安装:yum -y install bind-utils)
    # 命令格式:dig -t A 域名 @DNS服务器IP
    $ dig -t A nginx.default.svc.cluster.local @10.244.0.19
    ...省略
    nginx.default.svc.cluster.local. 30 IN	A	10.244.2.60
    ...省略
    
  • 根据 volumeClaimTemplates,为每个 Pod 创建一个 pvcpvc 的命名规则匹配模式:
    (volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Pod
    name=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2

  • 删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv

猜你喜欢

转载自blog.csdn.net/zyx1260168395/article/details/109275625