然后说一下这个,7-Kubernetes入门基础之存储Volume介绍

[TOC]

0x00 前言简述

描述: 我们知道在Docker中可以通过Volume将宿主机文件(配置文件、数据库等等)映射到Container内部供其容器内的应用程序使用。在Kubernrtes中我们可以采用ConfigMap控制器创建共享应用配置,亦可采用Kubernetes中的volume(卷)在一个Pod内多个Container之间进行文件共享;

Q: K8s 与 Docker 两者之间卷的区别?

  • 1) K8S 的 Volume (卷) 定义在Pod之上被同一个Pod内的多个容器挂载到具体文件之下便于文件的共享;
  • 2) K8S 的 Volume (卷) 与 Pod 的生命周期相同(持久卷除外),即Pod销毁后Volume也会被销毁,但Pod中容器重启或者终止并不会导致Volumes中数据的丢失;
  • 3) K8S 的 VOlume (卷) 支持多种分布式文件系统,例如GlusterFS、NFS、Ceph、

Volume 使用流程

  • 1) 声明一个Volume(可以是多种类型)
  • 2) 在容器内引用该Volume并Mount到Pod容器里的某个指定文件目录之中;

k8s持久化存储方案
描述: 共享存储为分布式系统非常重要的一部分,存储一般要求稳定、可用、性能、可靠。

  • 1.从用户角度看
    存储就是一块盘或者一个目录,用户不关心盘或者目录如何实现,用户要求非常“简单”,就是稳定,性能好。为了能够提供稳定可靠的存储产品,各个厂家推出了各种各样的存储技术和概念。为了能够让大家有一个整体认识,本文先介绍存储中的这些概念。
  • 2.从存储介质角度
    存储介质分为机械硬盘和固态硬盘(SSD)。机械硬盘泛指采用磁头寻址的磁盘设备,包括SATA硬盘和SAS硬盘。由于采用磁头寻址,机械硬盘性能一般,随机IOPS一般在200左右,顺序带宽在150MB/s左右。固态硬盘是指采用Flash/DRAM芯片+控制器组成的设备,根据协议的不同,又分为SATA SSD,SAS SSD,PCIe SSD和NVMe SSD。
  • 3.从产品定义角度
    存储分为本地存储(DAS),网络存储(NAS),存储局域网(SAN)和软件定义存储(SDS)四大类

    • DAS就是本地盘,直接插到服务器上
    • NAS是指提供NFS协议的NAS设备,通常采用磁盘阵列+协议网关的方式
    • SAN跟NAS类似,提供SCSI/iSCSI协议,后端是磁盘阵列
    • SDS是一种泛指,包括分布式NAS(并行文件系统),ServerSAN等
    • 4.从应用场景角度
      存储分为文件存储(Posix/MPI),块存储(iSCSI/Qemu)和对象存储(S3/Swift)三大类。

Kubernetes是如何给存储定义和分类呢?

  • 1.Kubernetes中跟存储相关的概念有PersistentVolume (PV)和PersistentVolumeClaim(PVC),PV又分为静态PV和动态PV。静态PV方式如下:

PV-PVC

  • 2.动态PV需要引入StorageClass的概念,使用方式如下:

StorageClass

Kubernetes 常见类型以及支持的卷

  • configMap (常用) secret (常用) emptyDir (常用) hostPath (常用) nfs (常用) persistentVolumeClaim (常用) cephfs (常用)
  • awsElasticBlockStore azureDisk azureFile csi downwardAPI
  • fc flocker gcePersistentDisk gitRepo glusterfs iscsi local
  • projected portworxVolume quobyte rbd scaleI0
  • storageos vsphereVolume

0x01 Storage – 存储

描述: 以下都是 statefulSet 控制器涉及有状态服务必须拥有的一些资源对象

  • 1) configMap : 在k8s中专门用来存储配置文件。
  • 2) Secret : 有一些需要加密的信息,例如密钥、用户名密码信息在Secret中可以被加密,是k8s中加密的解决方案【base64】
  • 3) Volume : 用于赋予k8s中pod共享存储卷的能力,例如可以通过nfs共享,本地磁盘目录共享等等。
  • 4) Persistent Volume : 简称PV【持久卷】,还包含一个PVC,通过服务进行持久卷的构建。
  • 5) StorageClass : 存储类可以动态的绑定PV(持久卷)和创建PVC(持久卷要求)。
  • 6) Nfs / Cephfs : 常用的分布式共享存储解决方案。

1.ConfigMap

描述: 在部署应用时常常需要给应用程序提供一些配置信息,比如Database的IP地址和开放端口以及用户密码等;

常用的简单方法有如下几种:

  • 1) 通过构建镜像时(Build)将应用配置文件打入Docker Images : 不灵活且以明文不安全,并且修改配置还需重新构建打包Image;
  • 2) 通过environment(环境环境)传参 : 不灵活且明文传入,而且更新环境变量时资源对象将需要重新部署;
  • 3) 配置管理中心 : 例如 百度 Disconf 、360 的qconf配置管理,在大规模的分布式系统之中将分布式应用所需的配置信息与应用程序进行分离及其重要,它可以极大的简化应用配置管理工作;
  • 4) 通过K8s提供的 ConfigMap 资源对象进行实现;

Tips : DisConf 将 X/Y/Z 多个业务平台进行配置统一管理, 其主要实现目标是对于同一个上线包,无需改动配置,即可在多个环境(DEV/TEST/STAGE/PRODUCTION)上线,简单的说其实现了配置信息与应用程序进行解耦操作;

- 1) 动态化更改配置部署 : 当更改配置后无需重新打包或重启,即可实时生效;
- 2) 配置统一管理 : 提供Web平台统一管理多个配置的应用环境(`DEV/TEST/STAGE/PRODUCTION`),以及多个产品的所有配置;
- 3) 核心目标 : 一个 Jar 包到处运行;

configMap – 介绍

描述: ConfigMap 功能在 Kubernetes 1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制(即配置的一种统一管理方法,使得应用程序与配置信息进行解耦),ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON 二进制大对象。

简单的说: 通过ConfigMap能够把配置以Key-Value对的形式供其它资源对象消费;

Q: configMap 配置文件注册中心

A: 在集群中有一个配置文件注册中心,集群中的节点向配置中心索要属于自身的配置,配置中心根据各节点的主机名或ip地址进行分发配置。
作用: k8s创建configMap,pod去引用,来达到类似于配置文件注册中心的效果。

Tips : 不同集群获取到的信息是不一样的需要使用者自己配置;

configMap – 创建

描述: configMap的几种创建方式进行实践演示基本创建格式:kubectl create configmap [map-name] [data-source]

0) 通过--from-file参数进行目录批量&单一文件configMap创建实践流程:

# (1) 查看目录文件清单 & 内容
cat > ~/K8s/Day8/demo1/dir1.properties <<'EOF'
dir.number=1
dir.name=demo1
dir.path=~/K8s/Day8/demo1
EOF
cat > ~/K8s/Day8/demo1/dir2.properties <<'EOF'
dir.number=2
dir.name=demo2
dir.path=~/K8s/Day8/demo2
EOF
cat > ~/K8s/Day8/demo2/file.properties <<'EOF'
file.number=2
file.name=file.properties
file.path=~/K8s/Day8/demo2/file.properties
file.html=configMap Test - WeiyiGeek
EOF

# (2) 目录批量创建 configMap (注意路径为全路径)
~/K8s/Day8/demo1$ ls
  # dir1.properties  dir2.properties
~/K8s/Day8/demo1$ kubectl create configmap dir-config --from-file=/home/weiyigeek/K8s/Day8/demo1/
  # configmap/dir-config created
# - dir-config : 指创建的configMap的名称
# - --from-file : 指定该目录下的所有文件都会被用在ConfigMap里面创建一个键值对, 注意目录可以是在本地或者远程地址中;


# (3) 文件创建 configMap
~/K8s/Day8/demo1$ kubectl create configmap file-config --from-file=/home/weiyigeek/K8s/Day8/demo2/file.properties
  # configmap/file-config created
# - --from-file : 在Volume中使用时键的名字就是文件名,值就是对应文件的内容【K/V结构】


# (4) 我们也可以通过Key制定文件创建,通过使用Key制定文件创建的好处是可以使用Key替代文件名称(类似 Alias )可以更改增强文件的可读性;
~/K8s/Day8/demo1$ kubectl create configmap file-config-aliase --from-file file-key=~/K8s/Day8/demo2/file.properties
  # configmap/file-config-aliase created  


# (5) 信息查看示例关键点
~/K8s/Day8/demo2$ kubectl get cm file-config-aliase
  # NAME                 DATA   AGE
  # file-config-aliase   1      109s

~/K8s/Day8/demo2$ kubectl get cm file-config-aliase -o yaml   
  # apiVersion: v1
  # data:
  #   file-key: |   # 别名
  #     file.number=2
  #     file.name=file.properties
  #     file.path=~/K8s/Day8/demo2/file.properties
  #     file.html=configMap Test - WeiyiGeek
  # kind: ConfigMap

Tips : 如需要通过多个文件创建configMap可以分别通过多个 –from-file 参数进行制定对应的配置文件;

  • 1) 通过 --from-env-file 参数进行创建configMap与上面的方式不同,此种形式对env-file的内容格式有则严格的要求,并且如果您提供了多个env-file文件仅最后一个文件的内容生效;
# (1) env-file.properties
cat > ~/K8s/Day8/demo2/env-file.properties <<'END'
name=weiyigeek
age=88
allowed="True"
END


# (2) 进行创建configMap
kubectl create configmap env-file --from-env-file env-file.properties
# configmap/env-file created

# (3) 查看 env-file 的configMap
~/K8s/Day8/demo2$ kubectl get cm env-file -o yaml
  # apiVersion: v1
  # data:
  #   age: "88"
  #   allowed: '"True"'
  #   name: weiyigeek
  # kind: ConfigMap
  # metadata:
  #   creationTimestamp: "2021-02-07T01:11:48Z"
  #   managedFields:
  #   - apiVersion: v1
  #     fieldsType: FieldsV1
  #     fieldsV1:
  #       f:data:
  #         .: {}
  #         f:age: {}
  #         f:allowed: {}
  #         f:name: {}
  #     manager: kubectl-create
  #     operation: Update
  #     time: "2021-02-07T01:11:48Z"
  #   name: env-file
  #   namespace: default
  #   resourceVersion: "19745894"
  #   selfLink: /api/v1/namespaces/default/configmaps/env-file
  #   uid: 9fb64e82-3ab1-4bcf-b528-c1f8b9aef026

2) 字面值创建简单示例:

# (1) 使用文字值创建利用--from-literal 参数传递配置信息,该参数可以使用多次;
~/K8s/Day8/demo2 $ kubectl create configmap text-config --from-literal=my.name=weiyigeek --from-literal=my.age=18 --from-literal=my.site=http://www.weiyigeek.top
  # configmap/text-config created

Tips :通过字面值创建的configMap不便于配置管理的记录所以不推荐使用;

3) 以资源清单创建

# (1) 资源清单
mkdir -pv ~/K8s/Day8/demo3/
cat  > ~/K8s/Day8/demo3/configmap.yaml <<'EOF'
apiVersion: v1 
kind: ConfigMap 
metadata:
  name: configmap-demo
  namespace: default 
data:
  special.name: weiyigeek
  special.site: www.weiyigeek.top
EOF

# (2) 创建
~/K8s/Day8/demo3$ kubectl create -f configmap.yaml
  configmap/configmap-demo created

configMap – 操作

# 1.查看创建configMap信息
~/K8s/Day8/demo3$ kubectl api-resources | grep "configmap"
  # configmaps    cm(缩写)    true         ConfigMap  #  没有 APIGROUP 其资源版本都是 v1

~/K8s/Day8/demo3$ kubectl get cm  # 创建的configmap-demo
  # NAME             DATA   AGE
  # configmap-demo   2      3m3s
  # dir-config       2      23m
  # flie-config      1      16m
  # text-config      3      10m


# 2.详细信息(通过信息展示的内容与通过cat命令查看一致)
~/K8s/Day8/demo3$ kubectl describe cm file-config
# kubectl get cm file-config -o yaml

  # Name:         file-config
  # Namespace:    default
  # Labels:       <none>
  # Annotations:  <none>

  # Data
  # ====
  # file.properties:  # Key
  # ----
  # file.number=2     # Value
  # file.name=file.properties
  # file.path=~/K8s/Day8/demo2/file.properties
  # file.html=configMap Test - WeiyiGeek

  # Events:  <none>


# 3.删除指定名称的configMap的配置
~/K8s/Day8/demo3$ kubectl delete cm flie-config
  # configmap "flie-config" deleted

configMap – 使用

(1) 创建Pod资源控制器并以ConfigMap作为环境变量&命令行参数(非常值得学习)

cat > configmap-use-1.yaml<<'EOF'
# Pod Controller
apiVersion: v1 
kind: Pod 
metadata:
  name: configmap-env-demo
spec:
  containers:
    - name: configmap-env-demo
      image: harbor.weiyigeek.top/test/busbox:latest
      imagePullPolicy: IfNotPresent
      command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME_KEY}, Site: ${SITE_KEY}" ]
      env:                         # 关键点     
        - name: USERNAME_KEY
          valueFrom:
            configMapKeyRef:       # 关键点key来源设置
              name: configmap-demo
              key: special.name    # Value
        - name: SITE_KEY 
          valueFrom:
            configMapKeyRef:
              name: configmap-demo
              key: special.site    # Value
      envFrom:
        - configMapRef:
            name: configmap-demo   # Key
  restartPolicy: Never
EOF

configMap使用操作流程:

# 1.上传镜像到私有仓库
~/K8s/Day8/demo3$ docker tag busybox:latest harbor.weiyigeek.top/test/busbox:latest
~/K8s/Day8/demo3$ docker push harbor.weiyigeek.top/test/busbox:latest
  # The push refers to repository [harbor.weiyigeek.top/test/busbox]
  # d2421964bad1: Pushed
  # latest: digest: sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc size: 527

# 2.部署Pod
~$ kubectl create -f configmap-use-1.yaml

# 3.查看
~$ kubectl get pod
  # NAME                 READY   STATUS      RESTARTS   AGE
  # configmap-env-demo   0/1     Completed   0          157m
$ kubectl logs configmap-env-demo
  # KUBERNETES_PORT=tcp://10.96.0.1:443
  # KUBERNETES_SERVICE_PORT=443
  # HOSTNAME=configmap-env-demo
  # SHLVL=1
  # HOME=/root
  # SITE_KEY=www.weiyigeek.top      # 关键点
  # special.site=www.weiyigeek.top  # 关键点
  # 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_PORT_443_TCP=tcp://10.96.0.1:443
  # KUBERNETES_SERVICE_PORT_HTTPS=443
  # KUBERNETES_SERVICE_HOST=10.96.0.1
  # PWD=/
  # USERNAME_KEY=weiyigeek      # 关键点
  # special.name=weiyigeek      # 关键点
  # Name: weiyigeek, Site: www.weiyigeek.top   # 输出

(2) 创建Pod资源控制器并通过数据卷(Volume)插件使用ConfigMap对象&热更新

# (1) 在数据卷里面使用这个ConfigMap,有不同的选项。
# 最基本的就是将文件填入数据卷,在这个文件中键就是文件名键值就是文件内容;
cat > configmap-use-3.yaml<<'EOF'
# Deployment Controller
apiVersion: apps/v1 
kind: Deployment 
metadata:
  name: configmap-update-demo
spec:
  replicas: 1
  selector:                  # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: configmap-update-demo  # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: configmap-update-demo
    spec:
      containers:
        - name: configmap-update-demo
          image: harbor.weiyigeek.top/test/nginx:v3.0
          imagePullPolicy: IfNotPresent
          volumeMounts:
          - name: config-volume 
            mountPath: /usr/share/nginx/html/
      volumes:
        - name: config-volume 
          configMap:
            name: file-config
EOF
$  kubectl create -f configmap-use-3.yaml
  # deployment.apps/configmap-update-demo created


# (2) 查看
~/K8s/Day8/demo4$ kubectl get pod -o wide --show-labels
  # NAME                                     READY   STATUS      RESTARTS   AGE   IP             NODE         LABELS
  # configmap-env-demo                       0/1     Completed   0          25h   10.244.1.129   k8s-node-4   <none>
  # configmap-update-demo-597f67f4df-v82j6   1/1     Running     0          56s   10.244.1.131   k8s-node-4   app=configmap-update-demo,pod-template-hash=597f67f4df


~/K8s/Day8/demo4$ kubectl describe cm file-config  # file-config 的ConfifMap信息
  # Name:         file-config
  # Namespace:    default
  # Labels:       <none>
  # Annotations:  <none>
  # Data
  # ====
  # file.properties:
  # ----
  # file.number=2
  # file.name=file.properties
  # file.path=~/K8s/Day8/demo2/file.properties
  # file.html=configMap Test - WeiyiGeek
  # Events:  <none>


# (3) 验证
~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "ls /usr/share/nginx/html"
file.properties
~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties
  # file.number=2
  # file.name=file.properties
  # file.path=~/K8s/Day8/demo2/file.properties
  # file.html=configMap Test - WeiyiGeek


# (4) 热更新修改ConfigMap的值【使用该命令打开yaml文件进行修改】
~/K8s/Day8/demo4$ kubectl edit cm file-config    # file.add=append Text with me, and hot update!

# configmap/file-config edited
~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties
~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "cat /usr/share/nginx/html/file.properties"
  # file.number=2
  # file.name=file.properties
  # file.path=/K8s/Day8/demo2/file.properties
  # file.html=configMap Test - WeiyiGeek
  # file.add=append Text with me, and hot update!
~/K8s/Day8/demo4$ kubectl logs configmap-update-demo 


# (5) 手动更新
# PS: ConfigMap更新后Pod并不会重载这个文件,更新 ConfigMap目前并不会触发相关Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发滚动更新
# 例子中我们在 .spec.template.metadata.annotations 中添加 version/config,每次通过修改 version/config 来触发滚动更新
# 未复现成功: kubectl patch deployment my-nginx --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "20200430"}}}}}' 
# 错误的例子: kubectl patch deployment configmap-update-demo--patch ' {"metadata": {"annotations": {"deployment.kubernetes.io/revision": "2"}}}'

Tips : configMap 资源对象消费常用于Pod环境变量消费、Pod command 中消费、Volume 中消费;

Tips : 非常注意更新 ConfigMap 后数据同步会有一定延迟

  • 1.使用该 ConfigMap 挂载的Env不会同步更新
  • 2.使用该 ConfigMap 挂载的Volume中的数据需要一段时间(实测大概10秒)才能同步更新

2.Secret

描述: 前面我们说过在k8s中利用ConfigMap控制器可以去保存配置文件以及一些数据, 这些数据可以被导入到Pod内部成为环境变量或者文件,从而可以达到热更新的目的, 带来便利的同时却有一定的安全问题(保存的配置以明文的形式保存的),所以密码文件、密钥文件类型的文件通过ConfigMap去保存就不是很合适,在k8s还有一种保存机制即Secret

简单的说: Secret 主要用于保存需要进行加密访问的的配置,解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。

在k8s资源清单中

$ kubectl api-resources | grep "secret"
NAME     NAMESPACED   KIND
secrets  true         Secret      # kubectl api-versions 由于没有APIGROUP则默认为v1

Q: Secret的应用场景

答: Secret 可以Volume卷或者环境变量的方式进行消费使用。

Secret 的三种类型

  • (1) Service Account : 由 Kubernetes 自动创建用来访问Kubernetes API (所以其访问安全至关重要常规的应用一般不涉及配置它),并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中
  • (2) Opaque : Base64编码格式的Secret 用来存储密码、密钥等,注意加密程度并不高。
  • (3) Kubernetes.io/dockerconfigjson : 将私有仓库的登陆认证信息进行存储;

Service Account

描述: 由Kubernetes 自动创建, 它用于访问我们的 Kubernetes API 并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中注意其并不需要我们手动去管理和创建

实践演示:

# (0) 查看 service-account-token 
$ kubectl get secret
  # NAME                  TYPE                                  DATA   AGE
  # default-token-zglkd   kubernetes.io/service-account-token   3      11d

# (1) 查看现有的 pod
$ kubectl get pod configmap-update-demo-597f67f4df-v82j6
  # NAME                                     READY   STATUS    RESTARTS   AGE
  # configmap-update-demo-597f67f4df-v82j6   1/1     Running   0          18h

# (2) 进入 Pod 容器内部
$ kubectl exec configmap-update-demo-597f67f4df-v82j6 -it -- bash

# (3) 验证 /run/secrets/kubernetes.io/serviceaccount 目录中存在的文件
root@configmap-update-demo-597f67f4df-v82j6:/$ ls /run/secrets/kubernetes.io/serviceaccount/ & cd $_
  # ca.crt      # 访问API所需的证书文件
  # namespace   # 当前Pod的名称空间
  # token       # 认证鉴权相关Token
root@configmap-update-demo-597f67f4df-v82j6:$ /run/secrets/kubernetes.io/serviceaccount# cat namespace
  # default

Opaque Secret

描述: Opaque 英 /əʊˈpeɪk/ Secret 类型的数据是一个 map 类型,并且要求 Value 是 Base64 编码格式;

资源清单示例:

cat > Opaque-Secret.yaml<<'EOF'
apiVersion: v1 
kind: Secret 
metadata:
  name: secret-test
type: Opaque 
data:
  name: dXNlcm5hbWU=
  pass: d2VpeWlnZWVr
EOF

# - 指定字符的base64加密
~$ echo -n "username" | base64
dXNlcm5hbWU=
~$ echo -n "weiyigeek" | base64
d2VpeWlnZWVr

# - 输出指定字符的base64解密形式
echo -n "d2VpeWlnZWVr" | base64 -d  # weiyigeek

Opaque Secret 创建与查看:

# 1.创建
~/K8s/Day8/demo5$ kubectl create -f Opaque-Secret.yaml
  # secret/secret-test created

# 2.查看当前系统中已创建的secret
~/K8s/Day8/demo5$ kubectl get secret
  # NAME                  TYPE                                  DATA   AGE
  # basic-auth            Opaque                                1      2d1h
  # default-token-zglkd   kubernetes.io/service-account-token   3      11d
  # secret-test           Opaque                                2      63s

# 3.查看
~/K8s/Day8/demo5$ kubectl describe secret secret-test
  # Name:         secret-test
  # Namespace:    default
  # Labels:       <none>
  # Annotations:  <none>

  # Type:  Opaque

  # Data
  # ====
  # name:  8 bytes
  # pass:  9 bytes

下面演示 Opaque Secret 在实际环境中的两种使用方式;

  • 1) 将Secret挂载到Volume中将会生成文件
  • 2) 将Secret导出到环境变量(env)中以供脚本使用

资源清单示例:

cat > opaque-Secret-1.yaml<<'EOF'
# 1) Volume 挂载使用
apiVersion: v1   # 注意点: Pod 是 v1
kind: Pod 
metadata:
  name: seret-test-1
  labels:
    name: seret-test-1 
spec:
  volumes:    
  - name: secrets    # 关键点
    secret:
      secretName: secret-test 
  containers:
  - name: seret-test-1  
    image: harbor.weiyigeek.top/test/busbox:latest
    command: [ "/bin/sh", "-c", "ls /etc/secrets;  sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: secrets 
      mountPath: "/etc/secrets"  # 关键点挂载到此目录之中
      readOnly: true
---
# 2) enviroment 环境变量使用
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: seret-test-2      # 元数据名称
spec:
  replicas: 2 
  selector:                  # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: seret-test-2  # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: seret-test-2
    spec:
      containers:
      - name: seret-test-2
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: [ "/bin/sh", "-c", "env; echo Name: ${USERNAME}, Pass: ${PASSWORD}; sleep 700" ]
        env:              # 环境变量设置
        - name: USERNAME
          valueFrom:      # Value 来源
            secretKeyRef:   # 关键点:上面我们接触过 configMapKeyRef ,此处是 secretKeyRef
              name: secret-test 
              key: name 
        - name: PASSWORD 
          valueFrom:
            secretKeyRef:
              name: secret-test
              key: pass
EOF

操作流程:

# (1) 创建
~/K8s/Day8/demo5$ kubectl create -f opaque-Secret-1.yaml
  # pod/seret-test-1 created
  # deployment.apps/seret-test-2 created

# (2) 查看
~/K8s/Day8/demo5$ kubectl get pod -o wide --show-labels
  # NAME                            READY   STATUS    RESTARTS   AGE   IP             NODE         LABELS
  # seret-test-1                    1/1     Running   0          24s   10.244.2.6     k8s-node-5   name=seret-test-1
  # seret-test-2-5bffbffc7d-444db   1/1     Running   0          24s   10.244.1.134   k8s-node-4   app=seret-test-2,pod-template-hash=5bffbffc7d
  # seret-test-2-5bffbffc7d-c5mqv   1/1     Running   0          24s   10.244.2.5     k8s-node-5   app=seret-test-2,pod-template-hash=5bffbffc7d

# (3) 验证
# - Volument
~/K8s/Day8/demo5$ kubectl logs seret-test-1
  # name
  # pass
~/K8s/Day8/demo5$ kubectl exec seret-test-1 -- sh -c "cat /etc/secrets/pass"
  # weiyigeek
~/K8s/Day8/demo5$ kubectl exec -it seret-test-1 -- sh
  # / # cd /etc/secrets/ &  ls
  # name  pass                      # 关键点: Key成为了文件名称,而Value成为的文件内容
  # /etc/secrets # cat name
  # /etc/secrets # cat pass
  # weiyigeek

# - environment
~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-444db
  # KUBERNETES_SERVICE_PORT=443
  # KUBERNETES_PORT=tcp://10.96.0.1:443
  # HOSTNAME=seret-test-2-5bffbffc7d-444db
  # SHLVL=1
  # HOME=/root
  # USERNAME=username
  # 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=/
  # PASSWORD=weiyigeek
  # Name: username, Pass: weiyigeek
~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-c5mqv | egrep "username|weiyigeek"
  # USERNAME=username
  # PASSWORD=weiyigeek
  # Name: username, Pass: weiyigeek

kubernetes.io/dockerconfigjson

描述: 它可以设置我们私有镜像仓库信息帮助我们在没有进行docker login的Node节点上也可以拉取私有镜像;

演示示例:

# (1) 使用Kuberctl 创建docker registry 认证的 secret
kuberctl create secret docker-registry private-registry-key \
--docker-server=harbor.weiyigeel.top \
--docker-username=weiyigeek \
--docker-password=Harbor12345 \
--docker-email=master@weiyigeek.top

# (2) 资源清单: kubernetes.io/dockerconfigjson 之 Secret 的 引用;
apiVersion: v1 
kind: Pod 
metadata:
  name: docker-config-json
spec:
  containers:
    - name: busybox
      image: harbor.weiyigeel.top/private/busybox:v1.0  # 拉取的私有镜像
  imagePullSecrets:               # 关键点: 通过 imagePullSecrets 来引用刚创建的`private-registry-key `
    - name: private-registry-key  # 创建的 docker-registry 类型 secret 的名称


3.Volume

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

首先当容器崩溃时kubelet会重启它,但是容器中的文件将丢失,容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。

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

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

PS : 在 Docker 中如果 restartPolicy 设置为always时容器因docker崩溃重启时将会保留数据,但是在K8s中并不会这样所以我们需要用到持久卷保证容器中指定数据的留存;

下面实践中讲解一些经常使用以及后续遇到的一些卷配置使用:

emptyDir – 空卷

描述: 正如卷的名字所述它最初是空的,其作用是可以在不同的容器中相同或者不同路径进行文件共享,当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行该卷就会存在,Pod中的容器可以读取和写入 emptyDir 卷中的相同文件,但是当出于任何原因从节点中删除Pod时,emptyDir 中的数据也将被永久删除;

emptyDir 使用场景:

  • 1.Pod 内多容器共享目录,例如一个Pod内的多个Container共享一个目录(生产者 <-> 消费者)
  • 2.暂存空间(临时目录), 例如用于基于磁盘的合并排序,用作长时间计算崩溃恢复时的检查点(即该目录无需持久化保留)
  • 3.Web服务器容器提供数据时,保存内容管理器容器提取的文件

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

资源清单示例:

cat > emptydir-demo-1.yaml<<'EOF'
# 1) Volume 挂载使用
apiVersion: v1   # 注意点: Pod 是 v1
kind: Pod 
metadata:
  name: emptydir-test-1
  labels:
    name: emptydir-test-1 
spec:
  volumes:    
  - name: cachedir          # 关键点卷名称
    emptyDir: {}            # 使用空卷
  containers:
  - name: emptydir-pod-1  
    image: harbor.weiyigeek.top/test/busbox:latest
    command: [ "/bin/sh", "-c","sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: cachedir            # 引用卷配置名称
      mountPath: "/cache/pod1"  # 挂载空卷到此目录之中
  - name: emptydir-pod-2  
    image: harbor.weiyigeek.top/test/busbox:latest
    command: [ "/bin/sh", "-c","sleep 700" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: cachedir 
      mountPath: "/cache/pod2"  # 挂载空卷到此目录之中
  restartPolicy: Never  
EOF

操作流程:

# (1) 创建
~/K8s/Day8/demo6$ kubectl create -f emptydir-demo-1.yaml
  # pod/emptydir-test-1 created

# (2) 查看
~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels
  # NAME                            READY   STATUS    RESTARTS   AGE   IP             NODE         LABELS
  # emptydir-test-1                 2/2     Running   0          16s   10.244.2.9     k8s-node-5   name=emptydir-test-1

# (3) 验证(分别在一个Pod的两个容器中查看创建追加的内容)
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh # emptydir-pod-1
/ # echo $HOSTNAME
  # emptydir-test-1
/ # echo $HOSTNAME-$(date) > /cache/pod1/time.log
/ # cat /cache/pod1/time.log
emptydir-test-1-Wed Nov 18 03:43:48 UTC 2020
/ # exit
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "echo $HOSTNAME-$(date) >> /cache/pod2/time.log" # emptydir-pod-2

# 在两个容器不同的目录实际指向的是同一个文件(内部通过 `/cache/pod1/``/cache/pod2/` 进行消费)
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh -c "cat /cache/pod1/time.log"
weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "cat /cache/pod2/time.log"
  # emptydir-test-1-Wed Nov 18 03:43:48 UTC 2020
  # ubuntu-Wed 18 Nov 2020 11:44:41 AM CST

# (4) 删除pod则emptyDir失效(正对于deployment创建的Pod是在deployment删除时候失效)
$ kubectl delete pod emptydir-test-1
pod "emptydir-test-1" deleted

hostPath – 主机路径卷

描述:与emptyDir方式直接将目录或者文件写在Container内部不同,hostPath 卷将主机节点(宿主机)的文件系统(FileSystem)中的文件或目录挂载到集群(Cluster)中类似于docker中使用的 -v 宿主机目录:容器挂载目录, 使用其的好处是可以与任何存储进行对接使用;

hostPath 与 emptyDir 比较

  • (1) emptyDir 直接将目录或者文件写在Container内部, 而 HostPath 可以使用宿主机的高速文件系统以及更好的持久化;
  • (2) emptyDir 被限制在一个Pod共享文件或目录中, 而 HostPath 可以实现跨Pod共享;

hostPath 使用场景:

  • 1.运行需要访问 Docker 内部的容器(即容器内部使用容器), 例如使用 /var/lib/docker 以及 /var/run/docker.sock 的 hostPath以及依赖挂载的方式;
  • 2.在容器中运行 cAdvisor, 使用 /dev/cgroups 的 hostPath;
  • 3.同一个宿主机跨Pod共享文件或者;

Tips: 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建以及它应该以什么形式存在(除了所需的 path 属性之外,用户还可以为 hostPath 卷指定 type);

hostPath 卷指定 type 类型说明:

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

注意事项:

  • 1.由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的pod在不同节点上的行为可能会导致访问的结果不一致
  • 2.当Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源;
  • 3.在底层主机上创建的文件或目录只能由 root 写入或者其他可读可写用户。您需要在特权容器中以 root 身份运行进程或修改主机上的文件权限以便写入 hostPath 卷;
  • 4.hostPath使用宿主机Qutoa不会纳入或记录到k8s的资源管理配额管理中;

资源清单示例:

cat > hostPath-demo.yaml<<'EOF'
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: hostpath-test-1      # 元数据名称
spec:
  replicas: 2 
  selector:                  # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: hostpath-test-1   # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: hostpath-test-1
    spec:
      volumes:                # volumes 关键点
      - name: test-volume 
        hostPath:             # 采用 hostPath 类型的卷
          type: DirectoryOrCreate    # 卷类型此处选择如果DirectoryOrCreate如何子节点上没有该目录便会进行创建
          path: /data                # 各主机节点上已存在的目录
      containers:
      - name: hostpath-test
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: [ "/bin/sh", "-c", "sleep 700" ]
        volumeMounts:     
        - name: test-volume  # 挂载指定卷目录
          mountPath: /disk/hostpath/
EOF

Tips : Deployment 不支持 restartPolicy: Never 其参数选项必须是 Always

操作流程:

# (1) 创建Pod资源
$ kubectl create -f hostPath-demo.yaml
  # deployment.apps/hostpath-test-1 created

# (2) 查看创建的Pod
~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels
  # NAME                               READY   STATUS    RESTARTS   AGE   IP             NODE        LABELS
  # hostpath-test-1-785b9544b9-n5vcz   1/1     Running   0          43s   10.244.1.137   k8s-node-4  app=hostpath-test-1,pod-template-hash=785b9544b9
  # hostpath-test-1-785b9544b9-sv2wh   1/1     Running   0          44s   10.244.2.11    k8s-node-5  app=hostpath-test-1,pod-template-hash=785b9544b9
~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-785b9544b9-sv2wh
  # Events:
  #   Type    Reason     Age   From               Message
  #   ----    ------     ----  ----               -------
  #   Normal  Scheduled  102s  default-scheduler  Successfully assigned default/hostpath-test-1-785b9544b9-sv2wh to k8s-node-5
  #   Normal  Pulled     102s  kubelet            Container image "harbor.weiyigeek.top/test/busbox:latest" already present on machine
  #   Normal  Created    101s  kubelet            Created container hostpath-test
  #   Normal  Started    101s  kubelet            Started container hostpath-test

# (3) 测试Pod与宿主机间共享的目录和文件
$ kubectl exec -it hostpath-test-1-785b9544b9-n5vcz -- sh   # Node-4
/ $ echo $(id)-$(date)-$(pwd) > /disk/hostpath/hostpath.log
/ $ cat /disk/hostpath/hostpath.log
  # uid=0(root) gid=0(root) groups=0(root)-Wed 18 Nov 2020 01:34:51 PM CST-/root

$ kubectl exec -it hostpath-test-1-785b9544b9-sv2wh -- sh    # Node-5
/ $ echo $HOSTNAME-$(date)-$(pwd) >> /disk/hostpath/hostpath.log
/ $ cat /disk/hostpath/hostpath.log
  # hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/

# (4) 验证(由于hostPath是各个节点的指定目录他们是独立存在的,除非所有的节点挂载的同一块NFS指向同一个目录除外)
# Pod 容器
kubectl exec pod/hostpath-test-1-785b9544b9-n5vcz -it -- sh -c "cat /disk/hostpath/hostpath.log"  # Node 4
# uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
kubectl exec pod/hostpath-test-1-785b9544b9-sv2wh -it -- sh  -c "cat /disk/hostpath/hostpath.log"   # Node 5
# hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/

# 主机文件(各节点主机如果目录不存在分别创建一个目录存放分配Pod的hostpath卷的数据文件)
weiyigeek@Ubuntu-PC:~$ ansible k8s-node-4 -a "cat /data/hostpath.log"
  # k8s-node-4 | CHANGED | rc=0 >>
  # uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
weiyigeek@Ubuntu-PC:~$ ansible k8s-node-5 -a "cat /data/hostpath.log"
  # k8s-node-5 | CHANGED | rc=0 >>
  # hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/


# (5) 测试删除(重启)Pod中Volume卷中的数据是否保留
$ kubectl delete pod --all
  # pod "hostpath-test-1-785b9544b9-n5vcz" deleted
  # pod "hostpath-test-1-785b9544b9-sv2wh" deleted
$ kubectl get pod -o wide  # 由于我们设置的replicas副本期望值为2所以又会进行重建Pod
  # NAME                               READY   STATUS    RESTARTS   AGE     IP             NODE         NOMINATED NODE   READINESS GATES
  # hostpath-test-1-785b9544b9-h5dbw   1/1     Running   0          3m11s   10.244.1.138   k8s-node-4   <none>           <none>
  # hostpath-test-1-785b9544b9-hnngk   1/1     Running   0          3m11s   10.244.2.12    k8s-node-5   <none>           <none>
$ kubectl get pod -o name
kubectl exec pod/hostpath-test-1-785b9544b9-h5dbw -it -- sh -c "cat /disk/hostpath/hostpath.log" 0 # 数据依然存在
  # uid=0(root) gid=0(root) groups=10(wheel)-Wed Nov 18 06:40:20 UTC 2020-/disk/hostpath
kubectl exec pod/hostpath-test-1-785b9544b9-hnngk -it -- sh -c "cat /disk/hostpath/hostpath.log"  # 数据依然存在
  # hostpath-test-1-785b9544b9-sv2wh-Wed Nov 18 08:20:42 UTC 2020-/


# (6) 删除deployment资源控制器,此时各节点中/data的数据并不会随着资源控制器的删除而删除;
$ kubectl delete deploy hostpath-test-1
# deployment.apps "hostpath-test-1" deleted

Tips : Deployment 中只有在副本数为1的运行时候此种方式持久化是没有没有问题的因为一把情况下它会运行在初次运行的节点之上,但是如果多个副本数则需要下面的PVC持久化卷才能进行数据的持久化与一致性;

Tips : 通过控制器创建的Pod共享了宿主机上的目录,做到了Node层面的数据持久化,但需要注意删除Pod后宿主机上的Volume仍然存在需要按需清理;

Tips : 通过 docker 的 voulme 命令选择查看与删除未使用的voulme或者通过docker volume rm -force 强制删除一个护或者多个Volume;

$ docker volume ls
  # DRIVER    VOLUME NAME
  # local     1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80
  # local     4d655ca0a6fe6eb6df6616b141ba16e9e7e3900d63ae53ea35879d999a061596
  # local     05d7960fecfe1ce5810e37f61a179ec0f3e50b4bf7f36b494346715ff0bd60c4
  # local     8b2ccd3e2e9707970c61ddaea96e3ed02c83770f560b18591fa4fad1c08f7a8e
  # local     73b0aee0ffbe7d467ec5744690b167f2e57496d08fea93a3c0992bb701af4ff0
  # local     78f74566d76be4cea00378cb0bfc70f18a3b2592ccf7fda90bf7a584964fd733
  # local     3512ea706cfc7f289e3945eaa220e51d91274057cd8463a6b94b52738019bafa
  # local     4438e2bfff84a56f1fa321789924701f7c44af3f8e3f199972adfa5151bf9f83
  # local     8210de4bab78e36f455772caeb261ebd81e32ea9f6753d717f86e44f579a3baa
  # local     831413f617021be764d8b18ca72d6d50381bc7c1816af7da20b7f065eed41c5d
  # local     b97aa9d44e9bf45e38275eec57f3073ab8e0687580b1f34edce12c8ca1f81fe3
  # local     b64726c080fd675bde2199a6845702d19ccea2ffda97a1a63e4ffb0ed3c1ec7b
  # local     bcb02978c32a5f284f675ac2a8088b356a97da5c767c4209f559ac2f11d79f09
  # local     c3407cff74c46e9672758dc9e17b4e6f01aafa982e18abb3ec7dc201975f506b
  # local     d1b64baa6fb899ad03117957839deeed722d2214ab2a59f0abc444bb3d292f5b
  # local     df9e564daff5f45f3c37e38490f8c653b8fc0e0fe7bdd2ee0224e46915cad8f0
  # local     ecd52129f73d26d3028ae84a3a690a3b0012341c1b25c53010e1b4a491313adb
  # local     fd4f5c7b9ca9dafec3f2ec8b54e9da9d87f9468b67d40afec94bdc2ebd9a0a69

$ docker volume inspect  1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80
  # [
  #   {
  #     "CreatedAt": "2021-02-03T10:55:08Z",
  #     "Driver": "local",
  #     "Labels": null,
  #     "Mountpoint": "/var/lib/docker/volumes/1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80/_data",
  #     "Name": "1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80",
  #     "Options": null,
  #     "Scope": "local"
  #   }
  # ]

~$ docker volume prune
  # WARNING! This will remove all local volumes not used by at least one container.
  # Are you sure you want to continue? [y/N] y
  # Deleted Volumes:
  # 05d7960fecfe1ce5810e37f61a179ec0f3e50b4bf7f36b494346715ff0bd60c4
  # 4438e2bfff84a56f1fa321789924701f7c44af3f8e3f199972adfa5151bf9f83

入坑计:

问题1.配置Volume时各node节点可能没有/data目录此时需要type指定为DirectoryOrCreate,即目录不存在时创建目录(巨坑);

~/K8s/Day8/demo6$ kubectl get pod
  # NAME                               READY   STATUS              RESTARTS   AGE
  # hostpath-test-1-565b986876-qgfhk   0/1     ContainerCreating   0          4m59s

~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-565b986876-qgfhk
  # Events:
  #   Type     Reason       Age                From               Message
  #   ----     ------       ----               ----               -------
  #   Normal   Scheduled    29s                default-scheduler  Successfully assigned default/hostpath-test-1-565b986876-qgfhk to k8s-node-5
  #   Warning  FailedMount  14s (x6 over 30s)  kubelet            MountVolume.SetUp failed for volume "test-volume" : hostPath type check failed: /data is not a directory

nfs – 分布式共享存储卷

描述: 我们可以采用nfs直接给我们的Pod定义一个NFS类型的卷Volume示例如下;

cat > nfs-volume-demo.yaml<<'EOF'
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: nfs-test-1      # 元数据名称
spec:
  replicas: 1                # 副本数量为1,否则大家都写进同一个目录可能会导致日志记录混乱;
  selector:                  # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: nfs-test-1   # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: nfs-test-1
    spec:
      volumes:                         # volumes 关键点
      - name: test-volume 
        nfs:                           # 采用 nfs 类型的卷
          server: 192.168.1.253        # nfs 主机地址
          path: '/data'                # nfs 共享目录
      containers:
      - name: nfs-test
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: [ "/bin/sh", "-c", "sleep 700" ]
        volumeMounts:     
        - name: test-volume       
          mountPath: /app/tools/    # 指定卷挂载到Pod指定的目录路径之中
EOF

Tips : 此处针对于无状态服务创建,如果是有状态服务请采用statefulset控制创建;


4.PersistentVolume & PersistentVolumeClaim

描述: 默认情况下对于运行的容器,其对文件系统的写入都发生在其分层文件系统的可写层的(Copy-On-Write), 当迁移应用程序从开发到生产环境时,开发与运维面临着挑战,为了防止容器挂掉、崩溃或者运行结束时,任何与之相关的数据都将会丢失,为了解决这个问题引发的数据丢失,所以我们需要将数据存储持久化也就是本章主题(Persistent Volume [Claim]);

基础概念

Q: 什么是PersistentVolume(PV)?

答: PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期、不支持命名空间划分。它是由管理员设置的存储并且是属于群集的一部分(资源)
此API对象包含存储实现的细节,即NFS、iSCSl或特定于云供应商的存储系统。

Q: 什么是PersistentVolumeClaim (PVC) ?

答: PVC 是用户存储的请求它与Pod比较相似,支持命名空间的划分,例如 Pod 消耗节点资源,而PVC消耗PV资源
又例如Pod可以请求特定级别的资源(CPU和内存),而PVC 声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)

PV/PVC生命周期流程
答: 供应准备 -> 绑定 -> 使用 -> 释放 -> 回收(Reclaiming)

持久卷(PV)插件类型
描述: PersistentVolume类型以插件形式实现下面是K8s目前支持的一些插件类型;

  • ·GCEPersistentDisk AWSElasticBlockStore AzureFile AzureDisk FC(Fibre Channel)
  • ·FlexVolume Flocker NFS iSCSI RBD(Ceph Block Device)CephFS
  • ·Cinder(OpenStack block storage)Glusterfs VsphereVolume Quobyte Volumes
  • ·HostPath VMware Photon Portworx Volumes Scalelo Volumes StorageOS
# 卷插件	ReadWriteOnce	ReadOnlyMany	ReadWriteMany
AWSElasticBlockStore	✓	-	-
AzureFile	✓	✓	✓
AzureDisk	✓	-	-
CephFS	✓	✓	✓
Cinder	✓	-	-
CSI	取决于驱动	取决于驱动	取决于驱动
FC	✓	✓	-
FlexVolume	✓	✓	取决于驱动
Flocker	✓	-	-
GCEPersistentDisk	✓	✓	-
Glusterfs	✓	✓	✓
HostPath	✓	-	-
iSCSI	✓	✓	-
Quobyte	✓	✓	✓
NFS	✓	✓	✓
RBD	✓	✓	-
VsphereVolume	✓	-	- (Pod 运行于同一节点上时可行)
PortworxVolume	✓	-	✓
ScaleIO	✓	✓	-
StorageOS	✓	-	-

参考地址: https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#access-modes

PV 分类

描述: 供应准备通过集群外的存储系统或者云平台来提供存储持久化支持;

  • 1) 静态pv : 由集群管理员创建一些PV。它们带有可供群集用户使用的实际存储的细节。它们存在于KubernetesAPl中可用于消费。
  • 2) 动态pv : 当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim 时,集群可能会尝试动态地为PVC创建卷。此配置基于StorageClasse:PVC必须请求[存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为空可以有效地禁用其动态配置;要启用基于存储级别的动态存储配置,集群管理员需要启用API server上的DefaultStorageClass[准入控制器]
    • 例如,通过确保 DefaultStorageClass位于API server 组件的–admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作。

PV 绑定

Q: 什么是绑定PV以及为啥需要绑定?

答: 由于 PVC 需要对接绑定一个PV才能够正常使用, 由于 master 中的控制环路监视新的PVC,寻找匹配的PV(如果可能),并将它们绑定在一起。
如果为新的PVC动态调配PV,则该环路将始终将该PV绑定到PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦PV和PVC绑定后 PersistentVolumeClaim 绑定是排他性的不管它们是如何绑定的。简单的说PVC跟PV绑定是一对一的映射一旦PV绑定后便不可与其它PVC绑定。

持久卷(PV)状态
描述: PV卷可以处于以下的某种状态;

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

PV 使用

描述: 用户可在Pod中像Volume一样使用PVC;

持久卷(PV)声明的保护
描述: PVC保护的目的是确保由pod正在使用的PVC不会从系统中移除,因为如果被移除的话可能会导致数据丢失当启用PVC保护 alpha功能时,如果用户删除了一个pod 正在使用的PVC,则该PVC不会被立即删除。PVC的删除将被推迟,直到PVC不再被任何 pod 使用;

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

  • ·RWO (Cli 格式简写)-ReadWriteOnce 该卷可以被单个节点以读/写模式挂载
  • ·ROX-ReadOnlyMany – 该卷可以被多个节点以只读模式挂载
  • ·RWX-ReadWriteMany – 该卷可以被多个节点以读/写模式挂载

PS : 每个卷只能同一时刻只能以一种访问模式挂载,即使该卷能够支持 多种访问模式。例如GCEPersistentDisk可以由单个节点作为ReadWriteOnce模式挂载,或由多个节点以ReadOnlyMany模式挂载,但不能同时挂载 ;

PV 释放

描述: 用户删除PVC来回收存储资源此时PV将变成Released状态,由于其还保留着之前的数据,该数据需要根据不同的策略来处理,否则这些存储资源无法被其它PVC使用;

PV 回收

描述: 持久卷(PV) 回收的几种策略

  • ·Retain(保留)– 允许人工处理保留的数据;
  • ·Recycle (deprecated) – 基本擦除将删除PV与外部关联的存储资源 (rm -rf /thevolume/*),注意:需要插件支持
  • ·Delete(删除)– 关联的存储资产(例如AWS EBS、GCE PD、Azure Disk 和OpenStack Cinder卷)将被删除, 注意:需要插件支持

PS :当前只有 NFS 和 HostPath 支持回收策略基本不用(Recycle已被废弃)。AWS EBS、GCE PD、Azure Disk 和Cinder 卷支持删除策略;

PV/PVC 举例

Volume API 资源清单:

~/K8s/Day8/demo6$ kubectl api-resources | grep "volume"
  # persistentvolumeclaims            pvc                                         true         PersistentVolumeClaim
  # persistentvolumes                 pv                                          false        PersistentVolume

PV(持久卷)NFS 类型 与 HostPath 类型资源清单:

# (1)NFS类型的PV示例
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: nfs-test
spec:
  capacity:
    storage: 5Gi 
  volumeMode: Filesystem 
  accessModes:
    - ReadWriteOnce                       # 单节点读写
  persistentVolumeReclaimPolicy: Delete   # 回收策略
  storageClassName: slow                  # 此持久卷所属的StorageClass的名称,空值意味着此卷不属于任何存储类。
  mountOptions:
    - hard 
    - nfsvers=4.1 
  nfs:             #PV 类型
    path: /tmp 
    server: 172.17.0.2
---
# (2) 以hostpath类型的PV示例
apiVersion: v1
kind: PersistentVolume
metadata:
  name: hostpath-test
spec:
  capacity:
    storage: 2Gi 
  accessModes:
    - ReadWriteOnce                       # 单节点读写
  hostPath:
    path: /tmp/local

PS : storageClassName 表示所属卷的 StorageClass 的名称,空值意味着此卷不属于任何存储类, 他是PVC绑定到PV卷的重要指标。

PVC(持久卷)NFS 类型 与 HostPath 类型资源清单:

# PVC(持久卷)NFS 类型 
apiVersion: v1 
kind: PersistentVolumeClaim
metadata:
  name: nfs-test-claim
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: slow       # 关键点(与上面的NFS创建的PV进行关联)
  volumeMode: Filesystem 
  resources:
    requests:
      storage: 5Gi
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}
---
# PVC(持久卷)HostPath 类型 
apiVersion: v1 
kind: PersistentVolumeClaim
metadata:
  name: hostpath-test-claim
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi

Pod 消费 PV 卷资源清单示例:

apiVersion: v1
kind: Pod
metadata:
  name: pv-consumer
spec:
  containers:
    - name: myfrontend
      image: nginx:latest
      imagePullPolicy: ifNotPresent
      ports:
      - containerPort: 80
        name: web 
      volumeMounts:     # 卷挂载
      - name: pv-claim  # 卷名称
        mountPath: /usr/share/nginx/html  # 卷挂载路径
  volumes:
    - name: pv-claim
      persistentVolumeClaim: 
        claimName: nfs-test-claim

PV/PVC 实例

NFS 持久化卷配置演示

Step 1.NFS 服务端与客户端配置

# 在主节点上安装nfs-server
> ansible admin -a "sudo apt install -y nfs-kernel-server rpcbind"
sudo mkdir -pv /nfs/data{1..4} /nfs/datamkdir 
  # mkdir: created directory '/nfs'
  # mkdir: created directory '/nfs/data1'
  # mkdir: created directory '/nfs/data2'
  # mkdir: created directory '/nfs/data3'
sudo chmod -R 777 /nfs
sudo chown -R nobody /nfs
sudo tee /etc/exports <<'EOF'
/nfs/data *(rw,no_root_squash,no_all_squash,sync)
/nfs/data1 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data2 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data3 *(rw,no_root_squash,no_all_squash,sync)
/nfs/data4 *(rw,no_root_squash,no_all_squash,sync)
EOF
sudo systemctl restart rpcbind 
sudo systemctl restart nfs-server.service
sudo exportfs -r && sudo exportfs -av

# Node 节点上安装nfs-client以及mount.nfs
> ansible k8s-node-4 -a "sudo apt install -y nfs-common rpcbind"
> ansible k8s-node-5 -a "sudo apt install -y nfs-common rpcbind"

# 节点上挂载测试
showmount -e 10.10.107.202
mount -t nfs 10.10.107.202:/nfsdata /test/
cd /test/ && ls
umount /test/

Step 2.PV 创建

cat > pv-test-demo-1.yaml<<'EOF'
# 1.访问模式为 ReadWriteOnce - pv-test-1
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-1
spec:
  capacity:              # 容量
    storage: 1Gi 
  accessModes:
    - ReadWriteOnce 
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs  # 非常重要 (后面PVC将会根据访问模式以及存储类名称进行绑定PV)
  nfs:
   path: /nfs/data1
   server: 10.10.107.202
---
# 1.访问模式为 ReadWriteOnce - pv-test-2
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-2
spec:
  capacity:            #容量
    storage: 1Gi 
  accessModes:
    - ReadWriteOnce 
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs  # 非常重要 (后面PVC将会根据访问模式以及存储类名称进行绑定PV)
  nfs:
   path: /nfs/data2
   server: 10.10.107.202
---
# 2.访问模式为 ReadOnlyMany 
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-4
spec:
  capacity:            #容量
    storage: 2Gi 
  accessModes:
    - ReadOnlyMany 
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-retain   # 非常重要
  nfs:
   path: /nfs/data4
   server: 10.10.107.202
---
# 3.访问模式为 ReadWriteMany 回收策略为 Delete
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-5
spec:
  capacity:            #容量
    storage: 3 Gi 
  accessModes:
    - ReadWriteMany 
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs-delete     # 非常重要
  nfs:
   path: /nfs/data
   server: 10.10.107.202
EOF

Step 3.操作流程&查看

# 1) 部署PV
~/K8s/Day8/demo7$ kubectl create -f pv-test-demo-1.yaml
  # persistentvolume/pv-test-1 created
  # persistentvolume/pv-test-2 created
  # persistentvolume/pv-test-4 created
  # persistentvolume/pv-test-5 created


# 2) 查看PV
~/K8s/Day8/demo7$ kubectl get pv -o wide
# NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE     VOLUMEMODE
  # pv-test-1   1Gi        RWO            Retain           Available           nfs                     23s   Filesystem
  # pv-test-2   1Gi        RWO            Retain           Available           nfs                     23s   Filesystem
  # pv-test-4   2Gi        ROX            Retain           Available           nfs-retain              22s   Filesystem
  # pv-test-5   3Gi        RWX            Delete           Available           nfs-delete              22s   Filesystem


# 3) 查看PV-TEST-1详情
~/K8s/Day8/demo7$ kubectl describe pv pv-test-1
  # Name:            pv-test-1
  # Labels:          <none>
  # Annotations:     <none>
  # Finalizers:      [kubernetes.io/pv-protection]
  # StorageClass:    nfs
  # Status:          Available
  # Claim:
  # Reclaim Policy:  Retain
  # Access Modes:    RWO
  # VolumeMode:      Filesystem
  # Capacity:        1Gi
  # Node Affinity:   <none>
  # Message:
  # Source:
  #     Type:      NFS (an NFS mount that lasts the lifetime of a pod)
  #     Server:    10.10.107.202
  #     Path:      /nfs/data1
  #     ReadOnly:  false
  # Events:        <none>

Step 4.Service 和 PV 绑定 与 PVC 创建

cat > pvc-demo1.yaml<<'EOF'
apiVersion: v1 
kind: Service 
metadata:
  name: nginx-pvc-demo    # SVC服务的元数据名称
  labels:
    app: nginx
spec:
  clusterIP: None    # 关键点: 无头服务可以通过集群CoreDNS解析指向Pod中服务
  selector:
    app: nginx
  ports:
  - port: 80 
    name: web
---
apiVersion: apps/v1
kind: StatefulSet 
metadata:
  name: web-pvc-demo
spec:
  serviceName: nginx-pvc-demo    # 绑定Service服务的名称
  replicas: 3                    # 三个副本数
  selector:
    matchLabels:
      app: nginx                 # 选择器匹配的标签
  template:
    metadata:
      labels:
        app: nginx               # 模板标签 
    spec:
      containers:
      - name: nginx 
        image: harbor.weiyigeek.top/test/nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web 
        volumeMounts:    # 卷挂载
        - name: diskpv   # 卷名称
          mountPath: /usr/share/nginx/html 
  volumeClaimTemplates:   # 持久卷模板引擎
  - metadata: 
      name: diskpv        # 卷名称元数据与上进行对应
    spec: 
      accessModes: [ "ReadWriteOnce" ]  # 访问模式 与 PV 卷的属性与之对应
      storageClassName: "nfs"           # 存储类名 与 PV 卷的属性与之对应
      resources:
        requests:
          storage: 1Gi                  # 大小可以进行设置
EOF

Step 5.部署PVC与查看

# 1) 部署 Server 与 PVC
~/K8s/Day8/demo7$ kubectl create -f pvc-demo1.yaml
  # service/nginx-pvc-demo created
  # statefulset.apps/web-pvc-demo created


# 2) 查看Service Headless
~/K8s/Day8/demo7$ kubectl get svc -o wide --show-labels | grep "nginx-pvc-demo" # 无头服务
  # NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR    LABELS
  # nginx-pvc-demo   ClusterIP   None         <none>        80/TCP    84s   app=nginx   app=nginx


# 3) 查看statefulset 与 Pod
~/K8s/Day8/demo7$ kubectl get statefulset -o wide  # statefulset控制器查看
  # NAME           READY   AGE    CONTAINERS   IMAGES
  # web-pvc-demo   2/3     24m   nginx        harbor.weiyigeek.top/test/nginx:latest

~/K8s/Day8/demo7$ kubectl get pod -o wide
  # NAME             READY   STATUS    RESTARTS   AGE     IP             NODE         NOMINATED NODE   READINESS GATES
  # web-pvc-demo-0   1/1     Running   0          27m     10.244.2.15    k8s-node-5   <none>           <none>
  # web-pvc-demo-1   1/1     Running   0          8m45s   10.244.1.140   k8s-node-4   <none>           <none>
  # web-pvc-demo-2   0/1     Pending   0          2m11s   <none>         <none>       <none>           <none>
# PS : web-pvc-demo-2  未成功启动得原因是由于没得持久卷给Pod3使用
# Events:
#   Type     Reason            Age    From               Message
#   ----     ------            ----   ----               -------
#   Warning  FailedScheduling  2m25s  default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
#   Warning  FailedScheduling  2m25s  default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.


# 4) 创建一个给web-pvc-demo-2使用得存储卷
cat > web-pvc-demo-2.yaml<<'EOF'
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-3
spec:
  capacity:             #容量
    storage: 1Gi 
  accessModes:
    - ReadWriteOnce 
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs    # 非常重要
  nfs:
   path: /nfs/data3
   server: 10.10.107.202
EOF
~/K8s/Day8/demo7$ kubectl create -f web-pvc-demo-2.yaml
# persistentvolume/pv-test-3 created


# (5) 创建完成后可以看见所有卷都已经ok
~/K8s/Day8/demo7$ kubectl get pvc -o wide # PVC 绑定的PV 查看
  # NAME                    STATUS   VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   AGE   VOLUMEMODE
  # diskpv-web-pvc-demo-0   Bound    pv-test-1   1Gi        RWO            nfs            93m   Filesystem
  # diskpv-web-pvc-demo-1   Bound    pv-test-2   4Gi        RWO            nfs            93m   Filesystem
  # diskpv-web-pvc-demo-2   Bound    pv-test-3   1Gi        RWO            nfs            93m   Filesystem

~/K8s/Day8/demo7$ kubectl get pv -o wide  #命令行会显示PV状态以及绑定到PVPVC的名称
  # NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                           STORAGECLASS   REASON   AGE    VOLUMEMODE
  # pv-test-1   1Gi        RWO            Retain           Bound       default/diskpv-web-pvc-demo-1   nfs                     73m   Filesystem
  # pv-test-2   1Gi        RWO            Retain           Bound       default/diskpv-web-pvc-demo-0   nfs                     73m   Filesystem
  # pv-test-3   1Gi        RWO            Delete           Bound       default/diskpv-web-pvc-demo-2   nfs                     11s   Filesystem
  # pv-test-4   2Gi        ROX            Retain           Available                                   nfs-retain              73m   Filesystem
  # pv-test-5   3Gi        RWX            Delete           Available                                   nfs-delete              73m   Filesystem

6.PVC卷使用验证

# (1) 查看Pod容器->PVC->PV绑定的目录
pv-test-1   1Gi        RWO            Retain           Bound       default/diskpv-web-pvc-demo-1   nfs                     73m   Filesystem
pv-test-2   1Gi        RWO            Retain           Bound       default/diskpv-web-pvc-demo-0   nfs                     73m   Filesystem
pv-test-3   1Gi        RWO            Delete           Bound       default/diskpv-web-pvc-demo-2   nfs                     11s   Filesystem
---
# 三个Pod的/usr/share/nginx/html指向不同的卷以及
NAME             IP             NODE       
web-pvc-demo-0   10.244.2.15    k8s-node-5   /nfs/data2
web-pvc-demo-1   10.244.1.140   k8s-node-4   /nfs/data1
web-pvc-demo-2   10.244.2.16    k8s-node-5   /nfs/data3

# (2) 创建html文件(重点) - 此步骤在NFS服务器上执行此处由于我们是在master上建立的
cd /nfs/data1 && echo "web-pvc-demo-1 - $(pwd) -- $(date)" > /nfs/data1/index.html
cd /nfs/data2 && echo "web-pvc-demo-0 - $(pwd) -- $(date)" > /nfs/data2/index.html
cd /nfs/data3 && echo "web-pvc-demo-2 - $(pwd) -- $(date)" > /nfs/data3/index.html

# (3) 访问 Pod 服务查看(由于是无头服务我们可以采用域名方式进行访问)
/nfs/data2$ curl http://10.244.2.15
  # web-pvc-demo-0 - /nfs/data2 -- Thu 19 Nov 2020 02:53:47 PM CST
/nfs/data1$ curl http://10.244.1.140
  # web-pvc-demo-1 - /nfs/data1 -- Thu 19 Nov 2020 02:53:47 PM CST

# (4) 采用coreDNS域名解析方式的访问
$ kubectl get pod -n kube-system -o wide | grep "coredns"
  # NAME                                  READY   STATUS    RESTARTS   AGE    IP              NODE          NOMINATED NODE   READINESS GATES
  # coredns-6c76c8bb89-8cgjz              1/1     Running   2          13d    10.244.0.7      ubuntu   <none>           <none>
  # coredns-6c76c8bb89-wgbs9              1/1     Running   2          13d    10.244.0.8      ubuntu   <none>           <none>
$ cat /etc/resolv.conf
# nameserver 10.244.0.7
$ dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8    # nginx-pvc-demo.default
# ;; ANSWER SECTION:
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.1.140
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.2.16
# nginx-pvc-demo.default.svc.cluster.local. 30 IN A 10.244.2.15
/nfs/data3$ curl http://nginx-pvc-demo.default.svc.cluster.local
  web-pvc-demo-2 - /nfs/data3 -- Thu 19 Nov 2020 02:53:48 PM CST

7.PV 与 PVC 删除 & 回收策略查看

# (1) statefulSet 资源控制器删除
~/K8s/Day8/demo7$ kubectl delete statefulset --all
  # statefulset.apps "web-pvc-demo" deleted
~/K8s/Day8/demo7$ kubectl get pod -w  # 关键点:删除时候是有序的
  # NAME             READY   STATUS        RESTARTS   AGE
  # web-pvc-demo-0   0/1     Terminating   0          105m
  # web-pvc-demo-1   0/1     Terminating   0          105m
  # web-pvc-demo-2   0/1     Terminating   0          105m


# (2) 删除指定的PVC验证PV其Delete的回收策略
$ kubectl delete pvc diskpv-web-pvc-demo-2
  # persistentvolumeclaim "diskpv-web-pvc-demo-2" deleted
# 此时pv-test-3无法进行delete回收 (未能解决后续有能力时候补充)
pv-test-3   1Gi        RWO            Delete           Failed      default/diskpv-web-pvc-demo-2   nfs                     34m

# (3) 删除所有的PVC
$ kubectl delete pvc --all 
  # persistentvolumeclaim "diskpv-web-pvc-demo-0" deleted
  # persistentvolumeclaim "diskpv-web-pvc-demo-1" deleted

# (4) 手动回收PV卷
$ kubectl get pv   # 查看到PVSTATUS以及CLAIM并未随着PVC删除而变化此时需要进行手动回收
  # NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                           STORAGECLASS   REASON   AGE
  # pv-test-1   1Gi        RWO            Retain           Released    default/diskpv-web-pvc-demo-1   nfs                     116m
  # pv-test-2   1Gi        RWO            Retain           Released    default/diskpv-web-pvc-demo-0   nfs                     116m
  # pv-test-3   1Gi        RWO            Delete           Failed      default/diskpv-web-pvc-demo-2   nfs                     42m

# (5) 删除或者nfs共享目录中的数据然后编辑pv删除如下片段
~/K8s/Day8/demo7$ kubectl edit pv pv-test-1
# 删除 yaml 中claimRef对象key-value
# ---
# claimRef:
#   apiVersion: v1
#   kind: PersistentVolumeClaim
#   name: diskpv-web-pvc-demo-0
#   namespace: default
#   resourceVersion: "2830220"
#   uid: 02359666-0fcb-4529-8a28-a2fb1dd18008
# ---
  # persistentvolume/pv-test-1 edited
$ kubectl edit pv pv-test-2
  # persistentvolume/pv-test-2 edited
$ kubectl edit pv pv-test-3
  # persistentvolume/pv-test-3 edited
$ kubectl get pv   # 然后您将发现PV一切又恢复到正常的可用状态
  # NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
  # pv-test-1   1Gi        RWO            Retain           Available           nfs                     119m
  # pv-test-2   1Gi        RWO            Retain           Available           nfs                     119m
  # pv-test-3   1Gi        RWO            Delete           Available           nfs                     46m
  # pv-test-4   2Gi        ROX            Retain           Available           nfs-retain              119m
  # pv-test-5   3Gi        RWX            Delete           Available           nfs-delete              119m


# (6) 删除指定的 PV 
~/K8s/Day8/demo7$ kubectl delete pv pv-test-3
# persistentvolume "pv-test-3" deleted

Step 8.至此持久化数据卷演示结束;

5.StorageClass – 动态分配持久卷

描述: StorageClass 被用于描述存储分类(存储类),不同的StorageClass会被关系的服务质量层面或者备份策略;

基础概念

描述: 前面我们说过 Persistent Volume 简称PV是一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过 Persistent Volume Claim,简称PVC来实现动态绑定。Pod定义里指定的PVC绑定关键字,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用

Tips : 比如一个配置了许多 50Gi PV 的集群不会匹配到一个要求 100Gi 的PVC。只有在 100Gi PV被加到集群之后,该PVC才可以被绑定。

Q: 什么是 storageClass?

答: 前面我们说过创建PV有静态(即手动创建一堆PV组成一个PV池,供PVC来绑定)和动态两种方式, 而动态是指在现有PV不满足PVC的请求时,即通过一个叫StorageClass(存储类)的对象由存储系统根据PVC的要求自动创建来完成的。动态卷供给能力让管理员不必进行预先创建存储卷,而是随用户需求进行创建。
所以说 storageclass 是一个存储类,k8s集群管理员通过创建storageclass可以动态生成一个存储卷供k8s用户使用。这是一种新的存储供应方式。

Q: 使用StorageClass有什么好处呢?

答: 除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度即StorageClassName参数。

资源定义

Q: 什么是 storageClass 的资源定义?

答: 每一个StorageClass都包含了Provisioner、Parameters、ReclaimPolicy等几个字段,当需要动态配置属于该类的PersistentVolume时使用这些字段。
描述具体过程为: PV先创建分类,PVC请求绑定某个已创建的类(StorageClass)的资源,同时由 Controller控制器 创建的资源绑定到PVC之中,这样就达到动态配置的效果。

Tips : StorageClass对象的名称很重要,是用户可以请求特定类的方式。管理员在首次创建StorageClass对象时设置类的名称和其他参数,注意在创建对象后无法更新这些对象

Tips : 管理员可以为不请求任何特定类绑定的PVC指定默认的StorageClass;

yaml文件示例说明:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
mountOptions:
  - debug
volumeBindingMode: Immediate

Provisoner – 供应商

Q: 什么是供应商 (Provisoner)?
描述: storageclass需要有一个供应者,用来确定我们使用什么样的存储方式来创建pv;

Tips : provisioner 供应者它既可以是内部供应程序,也可以由外部供应商提供; 如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/ 下提供的方法创建storageclass的provisioner;

例如,NFS不提供内部配置程序,但可以使用外部配置程序。

常见的provisioner供应者列表:

WeiyiGeek.provisioner

Tips : nfs 的 provisioner 部署资源清单文件示例 https://github.com/kubernetes-incubator/external-storage/tree/master/nfs/deploy/kubernetes

【2022年6月15日 20:27:10】补充: 注意 nfs-client-provisioner 已经不在更新维护建议使用 nfs-subdir-external-provisioner (https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)

Parameters – 参数

描述: 其功能与标签类似,但其是为了给外部插件提供某些自定义参数;

Reclaim Policy – 回收策略

描述: 由一个存储类动态创建持久化卷(Persistent Volumes-PV), 可以通过ReclaimPolicy指定回收策略而该策略可以是删除Delete(默认)或者保持Retain;

Mount Options – 挂载选项

描述: 如果Volume Plugin不支持这个挂载选项,但是指定了就会使provisioner创建StorageClass失败

Volume Binding Mode – 卷绑定模式

描述: 该字段用来说明什么时候进行卷绑定和动态配置,默认情况下立即模式表示一旦创建了PersistentVolumeClaim,就会发生卷绑定和动态配置。
对于受拓扑约束且无法从群集中的所有节点全局访问的存储后端,将在不知道Pod的调度要求的情况下绑定或配置PersistentVolumes, 这可能导致不可调度的Pod。

集群管理员可以通过指定WaitForFirstConsumer模式来解决此问题,该模式将延迟绑定和配置PersistentVolume,直到创建使用PersistentVolumeClaim的Pod。将根据Pod的调度约束指定的拓扑选择或配置PersistentVolumes。这些包括但不限于资源需求,节点选择器,pod亲和力和反亲和力,以及污点和容忍度

基础实例

环境构建流程

Step 1.设置查看当NFS目录以及定义nfs驱动进行编排以下是nfs-client-provisioner部署文件;

# (1) 当前 NFS 共享目录
showmount -e
  # /nfs/data4 *
  # /nfs/data3 *
  # /nfs/data2 *
  # /nfs/data1 *
  # /nfs/data  *  # 下面案例将使用该目录

# (2) NFS 驱动编排的资源清单
cat > nfs-client-provisioner.yaml  <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:          # 策略
    type: Recreate   # 再生(循环使用)
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner   # 服务帐户名称
      containers:
      - name: nfs-client-provisioner
        image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner 
        volumeMounts:
        - name: timezone
          mountPath: /etc/localtime                # 时区设置
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME                   # StorageClass 对象中定义的 provisioner 键需要保持一致
          value: fuseim.pri/ifs
        - name: NFS_SERVER
          value: 10.10.107.202
        - name: NFS_PATH
          value: /nfs/data
      volumes:
      - name: timezone                            # 时区定义
        hostPath:
          path: /usr/share/zoneinfo/Asia/Shanghai   
      - name: nfs-client-root                     # 存储卷
        nfs:
          server: 10.10.107.202
          path: /nfs/data
EOF

Step 2.Storageclass 部署文件

cat > nfs-client-storageclass.yaml <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage    # 重要 StorageClass Name绑定
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"   #设置其为默认存储后端
provisioner: fuseim.pri/ifs   # 或选择另一个名称,必须与NFS 驱动编排匹配部署的 env PROVISIONER_NAME
parameters:
  archiveOnDelete: "false"    # 删除pvc后,后端存储上的pv也自动删除
EOF

Step 3.rbac授权文件

cat > nfs-client-rbac.yaml<<'EOF'
kind: ServiceAccount
apiVersion: v1
metadata:
  name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
- 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"]
- apiGroups: [""]
  resources: ["services", "endpoints"]
  verbs: ["get"]
- apiGroups: ["extensions"]
  resources: ["podsecuritypolicies"]
  resourceNames: ["nfs-provisioner"]
  verbs: ["use"]
---
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
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
subjects:
- kind: ServiceAccount
  name: nfs-client-provisioner
  namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
EOF

Step 4.准备好以上三个文件后,使用kubectl apply命令应用即可完成nfs-client-provisioner的部署。

# (1) 部署
/nfs/data~/K8s/Day7/demo3$ ls
  # nfs-client-provisioner.yaml  nfs-client-rbac.yaml  nfs-client-storageclass.yaml
/nfs/data~/K8s/Day7/demo3$ kubectl create -f .
  # deployment.apps/nfs-client-provisioner created   【前排】
  # serviceaccount/nfs-client-provisioner created    【权限绑定】
  # clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
  # clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
  # role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
  # rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
  # storageclass.storage.k8s.io/managed-nfs-storage created  【存储类创建】

# (2) 查看pod、Deployment运行状态和sc
~/K8s/Day7/demo3$ kubectl get pod,deployment,sc -o wide
  # NAME                                          READY   STATUS    RESTARTS   AGE   IP             NODE         NOMINATED NODE   READINESS GATES
  # pod/nfs-client-provisioner-58b5dc958d-5fwl9   1/1     Running   0          9m    10.244.1.172   k8s-node-4   <none>           <none>

  # NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS               IMAGES                                                              SELECTOR
  # deployment.apps/nfs-client-provisioner   1/1     1            1           9m1s   nfs-client-provisioner   registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner   app=nfs-client-provisioner

  # NAME                                                        PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
  # storageclass.storage.k8s.io/managed-nfs-storage (default)   fuseim.pri/ifs   Delete          Immediate           false                  9m1s

动态存储类验证
  • Step 5.创建的 nfs-client-provisioner(StorageClass动态存储卷)已经正常运行, 我们现在部署几个PVC来测试验证
cat > logstash-pvc.yaml<<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: log-01-pvc
  annotations:   # 空间标注
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: log-02-pvc
spec:
  accessModes: ["ReadWriteMany"]   # 注意由于设置默认的存储此次不加注解也是使用的 managed-nfs-storage StorageClass 资源
  resources:
    requests:
      storage: 1Gi
EOF

部署与查看PVC持久卷

~/K8s/Day7/demo3$ kubectl create -f logstash-pvc.yaml
  # persistentvolumeclaim/log-01-pvc created
  # persistentvolumeclaim/log-02-pvc created

~/K8s/Day7/demo3$ kubectl get pv,pvc -o wide
  # NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE    VOLUMEMODE
  # persistentvolume/pvc-e76d332e-52bf-470a-a362-48742c03ab5f   1Gi        RWX            Delete           Bound    default/log-02-pvc   managed-nfs-storage            4m5s   Filesystem
  # persistentvolume/pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b   1Gi        RWX            Delete           Bound    default/log-01-pvc   managed-nfs-storage            4m6s   Filesystem

  # NAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE    VOLUMEMODE
  # persistentvolumeclaim/log-01-pvc   Bound    pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b   1Gi        RWX            managed-nfs-storage   4m6s   Filesystem
  # persistentvolumeclaim/log-02-pvc   Bound    pvc-e76d332e-52bf-470a-a362-48742c03ab5f   1Gi        RWX            managed-nfs-storage   4m6s   Filesystem

Step 6.pvc已经创建成功,并自动创建了一个关联的pv资源对象,我们再查看后端存储目录里面是否生成了对应命名格式的 pv;

# 文件夹的命名方式规则:${namespace}-${pvcName}-${pvName}
/nfs/data$ ls -alh | grep "pvc"
drwxrwxrwx 2 root   root 4.0K Dec  9 20:57 default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b
drwxrwxrwx 2 root   root 4.0K Dec  9 20:57 default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f

动态存储类使用
  • Step 7.在实际工作中,使用 StorageClass 更多的是 StatefulSet 类型的服务,StatefulSet 类型的服务我们也可以通过一个 volumeClaimTemplates 属性来直接使用 StorageClass;

实战利用StatefulSet控制器部署Nginx以及StorageClass动态PV的使用示例如下:

cat > nginx-use-storageClass.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  type: NodePort
  ports:
  - name: web
    port: 80
    targetPort: 80
    nodePort: 30001
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
  labels:
    app: nginx
spec:
  serviceName: "nginx"
  replicas: 8
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: harbor.weiyigeek.top/test/nginx:v3.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: webroot
          mountPath: /usr/share/nginx/html
        - name: weblog
          mountPath: /var/log/nginx
        - name: timezone
          mountPath: /etc/localtime
      volumes:
      - name: timezone     # 容器时区设置
        hostPath:
          path: /usr/share/zoneinfo/Asia/Shanghai
  volumeClaimTemplates:    # 持久卷模板
  - metadata:              # 根据模板自动创建PVPVC并且进行一一对应绑定;
      name: webroot       
    spec:
      accessModes: [ "ReadWriteOnce" ]  # 绑定PV关键点(1) - 访问模式
      resources:
        requests:
          storage: 1Gi                  # 绑定PV关键点(2) - 存储大小
  - metadata:
      name: weblog
    spec:
      accessModes: [ "ReadWriteMany" ]
      storageClassName: managed-nfs-storage   # 动态存储类绑定PV/PVC关键点(3)
      resources:
        requests:
          storage: 1Gi        
EOF

Step 8.查看创建的SVC以及StatefulSet、pod状态;

~/K8s/Day7/demo3$ kubectl get svc,sts,pod -o wide --show-labels
  # NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE    SELECTOR    LABELS
  # service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        34d    <none>      component=apiserver,provider=kubernetes
  # service/nginx        NodePort    10.110.119.27   <none>        80:30001/TCP   7m6s   app=nginx   app=nginx

  # NAME                   READY   AGE    CONTAINERS   IMAGES                                 LABELS
  # statefulset.apps/web   8/8     7m6s   nginx        harbor.weiyigeek.top/test/nginx:v3.0   <none>

  # NAME                                          READY   STATUS    RESTARTS   AGE     IP             NODE         LABELS
  # pod/nfs-client-provisioner-58b5dc958d-5fwl9   1/1     Running   0          5h43m   10.244.1.172   k8s-node-4   app=nfs-client-provisioner,pod-template-hash=58b5dc958d
  # pod/web-0                                     1/1     Running   0          2m24s   10.244.1.173   k8s-node-4   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-0
  # pod/web-1                                     1/1     Running   0          2m20s   10.244.2.73    k8s-node-5   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-1
  # pod/web-2                                     1/1     Running   0          2m15s   10.244.1.174   k8s-node-4   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-2
  # pod/web-3                                     1/1     Running   0          2m10s   10.244.2.74    k8s-node-5   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-3
  # pod/web-4                                     1/1     Running   0          2m6s    10.244.1.175   k8s-node-4   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-4
  # pod/web-5                                     1/1     Running   0          117s    10.244.2.75    k8s-node-5   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-5
  # pod/web-6                                     1/1     Running   0          107s    10.244.1.176   k8s-node-4   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-6
  # pod/web-7                                     1/1     Running   0          96s     10.244.2.76    k8s-node-5   app=nginx,controller-revision-hash=web-79545bdb6d,statefulset.kubernetes.io/pod-name=web-7

Step 9.访问我们nginx并设置查日志数据存储目录

# (1) 访问 Nginx Pod(轮询机制RR/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
  # Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
  # Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
  # Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
  # Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
  # Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
  # Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
/nfs/data$ curl http://10.110.119.27/host.html && curl http://10.10.107.202:30001/host.html
  # Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4
  # Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4


# (2) 查看持久卷数据存放目录(可以看出可以自动动态的分配nfs存储卷)
/nfs/data$ ls -alh | grep "web" | cut -f 13 -d " "
# 此处目录定义的格式为 {nameSpace}-{pvcName}-{pvName}
  # default-weblog-web-0-pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190
  # ....
  # default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9
  # default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf
  # ...
  # default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84

# {pvcName} <==> {volumeName}-{statefulSetName}-{replicasSerial}
/nfs/data$ kubectl get pvc,pv | grep "pvc-102f5ada-5599-4152-bae4-968670a0d9a8"
  # persistentvolumeclaim/weblog-web-1    Bound    pvc-102f5ada-5599-4152-bae4-968670a0d9a8   1Gi        RWX            managed-nfs-storage   22m
  # persistentvolume/pvc-102f5ada-5599-4152-bae4-968670a0d9a8   1Gi        RWX            Delete           Bound    default/weblog-web-1    managed-nfs-storage            22m


# (3) 查看web访问的页面以及日志数据
/nfs/data$ find . -name "host.html" -exec grep -H "Hostname" {} \;  # 可以看到各个 Pod 的 host.html 是不一致的;
  # ./default-webroot-web-5-pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae/host.html:Hostname: web-5 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-4-pvc-c0c41528-85c0-4881-b05b-8e236bc77a47/host.html:Hostname: web-4 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf/host.html:Hostname: web-0 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-6-pvc-9b482936-5252-43b2-b3a0-22bfee032dfb/host.html:Hostname: web-6 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-3-pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352/host.html:Hostname: web-3 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-2-pvc-d8685582-3122-4eb7-997f-b3d9c729918d/host.html:Hostname: web-2 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84/host.html:Hostname: web-7 ,Image Version: 3.0, Nginx Version: 1.19.4
  # ./default-webroot-web-1-pvc-c7baf155-3514-4dce-83ed-6d33362864b4/host.html:Hostname: web-1 ,Image Version: 3.0, Nginx Version: 1.19.4

# (4) 同样访问日志也是独立的每一个Pod都有自己的日志存储的PVC
/nfs/data$ find . -name "*.log" -exec grep -H "200" {} \;
  # ./default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a/access.log:10.244.0.0 - - [09/Dec/2020:21:49:21 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a/access.log:10.244.0.0 - - [09/Dec/2020:21:49:21 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09/access.log:10.244.0.0 - - [09/Dec/2020:21:49:24 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09/access.log:10.244.0.0 - - [09/Dec/2020:21:49:24 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83/access.log:10.244.0.0 - - [09/Dec/2020:21:49:26 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83/access.log:10.244.0.0 - - [09/Dec/2020:21:49:26 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9/access.log:10.244.0.0 - - [09/Dec/2020:21:49:17 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"
  # ./default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9/access.log:10.244.0.0 - - [09/Dec/2020:21:49:17 +0800] "GET /host.html HTTP/1.1" 200 59 "-" "curl/7.68.0" "-"

动态存储类扩容缩与删除

Step 10.查看StatefulSet扩容缩对及删除资源时候对StorageClass的影响

# (1) 验证: 扩容缩
/nfs/data$ kubectl scale statefulset --replicas=3 web  # Pod 收缩
  # statefulset.apps/web scaled
# 结论:Pod数量的减少并会影响PVC,它会一直存储在动态卷里除非手动的进行操作;
/nfs/data$ kubectl get pod,pv,pvc
  # NAME                                          READY   STATUS    RESTARTS   AGE
  # pod/nfs-client-provisioner-58b5dc958d-5fwl9   1/1     Running   0          6h21m
  # pod/web-0                                     1/1     Running   0          40m
  # pod/web-1                                     1/1     Running   0          40m
  # pod/web-2                                     1/1     Running   0          40m

  # NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS          REASON   AGE
  # persistentvolume/pvc-102f5ada-5599-4152-bae4-968670a0d9a8   1Gi        RWX            Delete           Bound    default/weblog-web-1    managed-nfs-storage            40m
  # .....
  # persistentvolume/pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09   1Gi        RWX            Delete           Bound    default/weblog-web-5    managed-nfs-storage            39m

  # NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
  # persistentvolumeclaim/log-01-pvc      Bound    pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b   1Gi        RWX            managed-nfs-storage   75m
  # persistentvolumeclaim/log-02-pvc      Bound    pvc-e76d332e-52bf-470a-a362-48742c03ab5f   1Gi        RWX            managed-nfs-storage   75m
  # persistentvolumeclaim/weblog-web-0    Bound    pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190   1Gi        RWX            managed-nfs-storage   45m
  # ....
  # persistentvolumeclaim/weblog-web-7    Bound    pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9   1Gi        RWX            managed-nfs-storage   39m
  # persistentvolumeclaim/webroot-web-0   Bound    pvc-f709d766-1061-4703-9c18-c2a814d450bf   1Gi        RWO            managed-nfs-storage   45m
  # persistentvolumeclaim/webroot-web-1   Bound    pvc-c7baf155-3514-4dce-83ed-6d33362864b4   1Gi        RWO            managed-nfs-storage   40m
  # ....
  # persistentvolumeclaim/webroot-web-7   Bound    pvc-a7d6783b-2d91-4839-9a5b-d84436018f84   1Gi        RWO            managed-nfs-storage   39m


# (2) 验证:删除StatefulSet控制器创建的资源
/nfs/data$ kubectl delete sts web
  # statefulset.apps "web" deleted
/nfs/data$ kubectl get pod
  # NAME                                      READY   STATUS        RESTARTS   AGE
  # nfs-client-provisioner-58b5dc958d-5fwl9   1/1     Running       0          6h28m
  # web-0                                     1/1     Terminating   0          47m # 终止中
  # web-1                                     1/1     Terminating   0          47m # 终止中
  # web-2                                     1/1     Terminating   0          47m # 终止中
# 结论: StatefulSet 资源删除后动态持久卷任然存储在我们的nfs服务上并未被清空(你可以手动清空或者)
/nfs/data$ kubectl get pvc
  # NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
  # log-01-pvc      Bound    pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b   1Gi        RWX            managed-nfs-storage   84m
  # log-02-pvc      Bound    pvc-e76d332e-52bf-470a-a362-48742c03ab5f   1Gi        RWX            managed-nfs-storage   84m
  # weblog-web-0    Bound    pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190   1Gi        RWX            managed-nfs-storage   54m
  # ...
  # weblog-web-7    Bound    pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9   1Gi        RWX            managed-nfs-storage   49m
  # webroot-web-0   Bound    pvc-f709d766-1061-4703-9c18-c2a814d450bf   1Gi        RWO            managed-nfs-storage   54m
  # ...
  # webroot-web-7   Bound    pvc-a7d6783b-2d91-4839-9a5b-d84436018f84   1Gi        RWO            managed-nfs-storage   49m

~/K8s/Day7/demo3$ kubectl delete pv,pvc --all
persistentvolume "pvc-102f5ada-5599-4152-bae4-968670a0d9a8" deleted
persistentvolume "pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83" deleted
persistentvolume "pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352" deleted
persistentvolume "pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9" deleted
persistentvolume "pvc-9b482936-5252-43b2-b3a0-22bfee032dfb" deleted
persistentvolume "pvc-a7d6783b-2d91-4839-9a5b-d84436018f84" deleted
persistentvolume "pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190" deleted
persistentvolume "pvc-bf4734e3-a010-4336-a190-6a13527d9672" deleted
persistentvolume "pvc-c0c41528-85c0-4881-b05b-8e236bc77a47" deleted
persistentvolume "pvc-c7baf155-3514-4dce-83ed-6d33362864b4" deleted
persistentvolume "pvc-cb930d15-558b-4ede-a087-4f0ee001826a" deleted
persistentvolume "pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54" deleted
persistentvolume "pvc-d8685582-3122-4eb7-997f-b3d9c729918d" deleted
persistentvolume "pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae" deleted
persistentvolume "pvc-f709d766-1061-4703-9c18-c2a814d450bf" deleted
persistentvolume "pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09" deleted
persistentvolumeclaim "weblog-web-0" deleted
persistentvolumeclaim "weblog-web-1" deleted
persistentvolumeclaim "weblog-web-2" deleted
persistentvolumeclaim "weblog-web-3" deleted
persistentvolumeclaim "weblog-web-4" deleted
persistentvolumeclaim "weblog-web-5" deleted
persistentvolumeclaim "weblog-web-6" deleted
persistentvolumeclaim "weblog-web-7" deleted
persistentvolumeclaim "webroot-web-0" deleted
persistentvolumeclaim "webroot-web-1" deleted
persistentvolumeclaim "webroot-web-2" deleted
persistentvolumeclaim "webroot-web-3" deleted
persistentvolumeclaim "webroot-web-4" deleted
persistentvolumeclaim "webroot-web-5" deleted
persistentvolumeclaim "webroot-web-6" deleted
persistentvolumeclaim "webroot-web-7" deleted
~/K8s/Day7/demo3$ ls /nfs/data
data/  data1/ data2/ data3/ data4/
~/K8s/Day7/demo3$ ls /nfs/data/
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-337180d4-e12a-4933-bbe7-bc9eea8d068a  default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-6c445381-1a64-40e4-a412-493e691d8a22  default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9
archived-default-elasticsearch-master-elasticsearch-master-0-pvc-ad986a1f-f36e-4223-af17-e4100ca2a587  default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf
archived-default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b                                   default-webroot-web-1-pvc-c7baf155-3514-4dce-83ed-6d33362864b4
archived-default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f                                   default-webroot-web-2-pvc-d8685582-3122-4eb7-997f-b3d9c729918d
archived-default-weblog-web-0-pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190                                 default-webroot-web-3-pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352
archived-default-weblog-web-1-pvc-102f5ada-5599-4152-bae4-968670a0d9a8                                 default-webroot-web-4-pvc-c0c41528-85c0-4881-b05b-8e236bc77a47
archived-default-weblog-web-2-pvc-bf4734e3-a010-4336-a190-6a13527d9672                                 default-webroot-web-5-pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae
archived-default-weblog-web-3-pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54                                 default-webroot-web-6-pvc-9b482936-5252-43b2-b3a0-22bfee032dfb
archived-default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83                                 default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84
archived-default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09

WeiyiGeek.Nginx网站与日志数据依然存储

  • Step 11.以上即为k8s持久化存储之storageclass实践。
    以上就是使用storageclass实现动态pv的具体步骤,内容较为全面而且我也相信有相当的一些工具可能是我们日常工作可能会见到或用到的。

实际案例

MySQL数据库使用StorageClass对数据的持久存储

描述: 接下来我们部署一个mysql应用,测试下 StorageClass 方式声明的 PVC 对象

Step 1.MySQL在K8s集群中部署的资源清单

cat mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  custom.cnf: |
    [mysqld]
    default_storage_engine=innodb
    skip_external_locking
    skip_host_cache
    skip_name_resolve
    default_authentication_plugin=mysql_native_password

cat mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-user-pwd
data:
  mysql-root-pwd: cGFzc3dvcmQ=

cat mysql-deploy.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: NodePort
  ports:
  - port: 3306
    nodePort: 30006
    protocol: TCP
    targetPort: 3306 
  selector:
    app: mysql

cat mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  annotations:                      
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"    # 空间标注
spec:
  accessModes: ["ReadWriteMany"]
  storageClassName: managed-nfs-storage
  resources:
    requests:
      storage: 2Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql
        name: mysql
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-user-pwd
              key: mysql-root-pwd
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-config
          mountPath: /etc/mysql/conf.d/
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
        - name: timezone
          mountPath: /etc/localtime
      volumes:
      - name: mysql-config
        configMap:
          name: mysql-config
      - name: timezone
        hostPath:
          path: /usr/share/zoneinfo/Asia/Shanghai
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc

  • Step 2.按照资源清单进行部署构建响应的Pod以及Services
$ kubectl apply -f .
  configmap/mysql-config created
  service/mysql created
  deployment.apps/mysql created
  secret/mysql-user-pwd created

Step 3.查看创建的资源

$ kubectl get pod,svc
NAME                                        READY   STATUS    RESTARTS   AGE
pod/mysql-7c5b5df54c-vrnr8                  1/1     Running   0          83s
pod/nfs-client-provisioner-c676947d-pfpms   1/1     Running   0          30m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
service/kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP          93d
service/mysql        NodePort    10.0.0.19    <none>        3306:30006/TCP   83s

Step 4.可以看到mysql应用已经正常运行,我们通过任意一个node节点的ip和30006端口连接mysql数据库测试

[root@localhost ~]# mysql -uroot -h292.168.248.134 -P30006 -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)

MySQL [(none)]>

Step 5.可以看到mysql数据库连接正常,此时查看nfs存储mysql数据库数据已经持久化到nfs服务器 /data/nfs/default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc目录中

$ du -sh *
177M    default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc

$ cd default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc/

$ ls
auto.cnf       binlog.index  client-cert.pem  ibdata1      ibtmp1        mysql.ibd           public_key.pem   sys
binlog.000001  ca-key.pem    client-key.pem   ib_logfile0  innodb_temp  performance_schema  server-cert.pem  undo_001
binlog.000002  ca.pem        ib_buffer_pool   ib_logfile1  mysql         private_key.pem     server-key.pem   undo_002

关于 StatefulSet 补充使用PV/PVC或者StorageClass的补充

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

# 无论 Pod 删除后重建的容器任然可以通过以下方式访问;
ping web-pvc-demo-0.nginx-pvc-demo
ping web-pvc-demo-1.nginx-pvc-demo
ping web-pvc-demo-2.nginx-pvc-demo

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

3) StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的FQDN为:(servicename).$(namespace).svc.cluster.local,其中"cluster.local"指的是集群的域名

dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8

4) 根据volumeClaimTemplates为每个Pod 创建一个PVC,其的命名规则匹配模式:(volumeClaim Templates.name-pod_name), 比如上面的volumeMounts.name=diskpv 因此创建出来的PVC是

diskpv-web-pvc-demo-1
diskpv-web-pvc-demo-2
diskpv-web-pvc-demo-3

5) 删除 Pod 不会删除其pvc,需要手动删除 pvc 将自动释放 pv,查看上面的步骤6.PV 与 PVC 删除 & 回收策略查看

正文完