文章目录

  • 一、ConfigMap
  • ConfigMap 的创建
  • 1. 资源清单创建
  • 2. 使用目录创建
  • 3. 使用文件创建
  • 4. 使用字面值创建
  • Pod 中使用 ConfigMap
  • 1. 使用 ConfigMap 来替代环境变量
  • 2. 使用 ConfigMap 设置命令行参数
  • 3. 通过数据卷插件使用ConfigMap
  • 4. ConfigMap 的热更新
  • 二、Secret
  • 1. Service Account(不常用)
  • 2. Opaque Secret
  • (1)创建 Opaque Secret
  • (2)将 Secret 挂载到 Volume 中
  • (3)将 Secret 导出到环境变量中
  • 3. kubernetes.io/dockerconfigjson
  • 三、Volume
  • 1. emptyDir
  • 2. hostPath
  • (1)`type` 的值
  • (2)示例
  • 四、PV/PVC
  • 1. 概念
  • 生命周期
  • PV的分类
  • 绑定:
  • 持久化卷声明的保护
  • 2. PV 一些概念
  • (1)PV 的类型(插件)
  • (2)访问模式
  • (3)回收策略
  • (4)状态
  • (5)模板
  • 3. NFS 持久化示例
  • (1)安装 NFS
  • (2)创建 PV 和 StatefulSet
  • 3. PV 的一些说明


一、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
  1. 创建
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
  1. 创建 /root/k8s/yaml/configmap/ui.properties 文件:
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
  1. 创建 configmap ,--from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容
kubectl create configmap game-config --from-file=../configmap/
  1. 查看创建的 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
  1. 创建 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
  1. 查看日志,可以看到 ConfigMap 中的配置已经注入到了容器中
  2. k8s mysql pv 设置多大 k8s mysql 存储_secret

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

  1. 创建 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  special.how: very
  special.type: charm
  1. 创建 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
  1. 查看日志
$ 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
  1. 创建 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
  1. 查看日志
$ 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
  1. 查看 /etc/config/log_level 文件的内容
$ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level
INFO
  1. 修改 ConfigMap
kubectl edit configmap log-config

k8s mysql pv 设置多大 k8s mysql 存储_volume_02

  1. 稍微等一会儿,再次查看 /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

  1. 使用加密后的用户名和密码创建 Secret
apiVersion: v1			# kubectl explain secret 查看
kind: Secret
metadata:
  name: mysecret		# Secret 名称
type: Opaque			# Secret 的类型
data:
  password: MTIz		# 密码
  username: YWRtaW4=	# 用户名
  1. 查看 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	# 只读
  1. 查看
# 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 在这儿不需要使用明文,稍微安全一点)
  1. 查看环境变量
# 进入容器
$ 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 密码
#    --docker-email=aa@qq.com 				# docker 邮箱
kubectl create secret docker-registry \
    myregistrykey \
    --docker-server=hub.zyx.com \
    --docker-username=admin \
    --docker-password=Harbor12345 \
    --docker-email=aa@qq.com

在创建 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 类型
  1. 可以看到,两个容器都能读取到 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 可以声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)
生命周期

k8s mysql pv 设置多大 k8s mysql 存储_volume_03

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

k8s mysql pv 设置多大 k8s mysql 存储_k8s mysql pv 设置多大_04

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

k8s mysql pv 设置多大 k8s mysql 存储_存储_05

绑定:

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
  1. 在 k8s 每个节点中安装 NFS 客户端:
# 安装依赖
$ yum -y install nfs-utils rpcbind
  1. 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

k8s mysql pv 设置多大 k8s mysql 存储_volume_06

  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
  1. 创建 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
  1. 创建 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
  1. 因为前面只创建了一个 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
  1. 在 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访问成功
  1. 尝试删除 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访问成功
  1. 删除 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