持久化存储

一、理解存储卷

Volume(存储卷)是Pod中能够被多个容器访问的共享目录。解决了容器崩溃时文件丢失(容器中的文件默认情况下在磁盘是临时存放)和多容器共享文件的问题。

volume

与 docker 存储卷的区别

docker volume:定义在容器上,生命周期是与容器的生命周期相同;

k8s volume:定义在 pod 上,与 pod 的生命周期相同,容器终止或重启,卷的数据是不会丢失的。

k8s存储卷类型

常用的存储卷类型有:emptyDir、hostPash、NFS、secret、configMap、persistentVolumeClaim。
更多请参考官方链接。

emptyDir

在 Pod 分配到 Node 时创建,初始内容为空,并且无须指定宿主机上对应的目录文件,因为这是 Kubernetes 自动分配的一个目录,当Pod 从 Node 上移除时,emptyDir 中的数据也会被永久删除。

emptyDir 的一些用途如下:

1、临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。例如用于基于磁盘的归并排序。
2、长时间任务的中间过程的临时保存目录。检查点用于从崩溃中恢复的长时间计算。
3、一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。保存内容管理器容器获取的文件,而网络服务器容器提供数据。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

volumes: 指定卷的名称和类型等卷配置;

volumeMounts: 指定挂载那个卷及挂载目录等。

使用数据卷流程:镜像里程序写的目录 -> mountPath 挂载到写的目录 -> 引用的volumes对应的name -> 相关卷的配置

persistentVolumeClaim(重要)

持久卷声明类型。通过这个声明,可以在不知道存储卷类型的情况下进行申请持久化存储。需要和PV一起搭配。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: xxx.xxx.xx.xx
    path: "/"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi
---
...(省略)
spec:
      containers:
      - name: nginx-demo
        image: nginx
        imagePullPolicy: Always
        volumeMounts:
        - name: nfs-pvc-vol
          mountPath: /usr/share/nginx/html
      volumes:
      - name: nfs-pvc-vol
        persistentVolumeClaim:
          claimName: nfs

pv/pvc完成绑定的条件:

  • 第一个条件,检查 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,就必须满足 PVC 的要求。
  • 第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。

hostPath

hostPath 为在 Pod 上挂载宿主机上的文件或目录,它通常可以用于以下几方面:

1、容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。

2、 需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问Docker的文件系统。

注意: 在不同的Node上具有相同配置的Pod,可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致。
官方地址-ctrl+f搜索关键字

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # 宿主上目录位置
      path: /data
      # 此字段为可选
      type: Directory
type取值 行为
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。
Directory 在给定路径上必须存在的目录。
FileOrCreate 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。
File 在给定路径上必须存在的文件。
Socket 在给定路径上必须存在的 UNIX 套接字。
CharDevice 在给定路径上必须存在的字符设备。
BlockDevice 在给定路径上必须存在的块设备

NFS

不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。

volumes:
    - name: nfs-volume
      nfs:
        server: xxx.xx.xxx.xx # 服务地址
        path: /path # 存放路径

secret

secret 卷用来给 Pod 传递敏感信息,例如密码、OAuth 令牌和 SSH 密钥,它们不会被写到持久化的存储器里面,是以文件的形式挂在到 Pod 中,且无需直接与 Kubernetes 耦合。

  • 作为挂载到一个或多个容器上的 卷 中的文件
  • 以环境变量的形式使用 Secrets
  • 由 kubelet 在为 Pod 拉取镜像时使用
secret type类型
  • Service Account:Kubernetes 自动创建包含访问 API 凭据的 secret,并自动修改 pod 以使用此类型的 secret。
  • Opaque:使用base64编码存储信息,可以通过base64 --decode解码获得原始数据,因此安全性弱。
  • kubernetes.io/dockerconfigjson:用于存储docker registry的认证信息。

官方secret地址

configMap

提供了向 Pod 注入配置数据的方法,用来将非机密性的数据保存到键值对中,比如存储卷中的配置文件。 ConfigMap 对象中存储的数据可以被 configMap 类型的卷引用,然后被 Pod 中运行的 容器化应用使用(以key-value的形式)。

官方configMap地址

示例实践:configMap引用

apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  special.how: very
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: log-config
  namespace: default
data:
  log_level: INFO
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
  namespace: default
data:
  log_level: INFO
---
apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level

configMap

实践一:【挂载NFS卷】

  • step1:在master节点执行 NFS 工具包安装命令:
yum install -y nfs-utils
systemctl enable rpcbind
systemctl enable nfs

systemctl start rpcbind
systemctl start nfs
  • step2: 创建 NFS 目录并授权:
mkdir -p /data/nfs/pv-1 && mkdir -p /data/nfs/pv-2 && mkdir -p statefulSet-1
chmod -R 755 /data/nfs
  • step3:配置NFS目录权限(这里我们直接设置成可读写):
vim /etc/exports
加入下面两行配置
/data/nfs/pv-1     *(rw,sync,no_root_squash,no_all_squash)
/data/nfs/pv-2     *(rw,sync,no_root_squash,no_all_squash)
/data/nfs/statefulSet-1     *(rw,sync,no_root_squash,no_all_squash)


# 立即生效
exportfs -rv

step3参数exports说明:

rw 读写
ro 只读
sync 数据直接写入磁盘
async 数据先写入内存
no_root_squash 对root用户不压制,在服务端都映射为服务端的root用户,即仅使用普通用户授权。
root_squash 如果客户端是用户root操作,会被压制成nobody用户,即可以使用 root 授权。
all_squash 不管客户端的使用nfs的用户是谁,都会压制成nobody用户
nonuid=uid: 指定uid的值
anongid=gid:指定gid的值
  • step4: 查看NFS服务的挂载卷列表
[root@master configMap]# showmount -e
Export list for master:
/data/nfs/pv-2 *
/data/nfs/pv-1 *

在使用的node节点上分别安装nfs工具包

yum install nfs-utils

在node节点上检查master节点的nfs servcie服务是否可用

[root@node2 config]# showmount -e 192.168.23.10
Export list for 192.168.23.10:
/data/nfs/pv-2 *
/data/nfs/pv-1 *
  • step5:创建一个pod应用来引用上面的nfs进行挂载
    nfs-kdemo.yaml
kind: Pod
apiVersion: v1
metadata:
  name: nfs-pod-test
spec:
  volumes:
    - name: nfs-volume
      nfs:
        server: 192.168.23.10
        path: /data/nfs/pv-1
  containers:
    - name: nfs-kdemo-test
      image: registry.cn-hangzhou.aliyuncs.com/liwb/kdemo:1.9
      volumeMounts:
        - name: nfs-volume
          mountPath: /var/nfs/pv-1
      command: ["/bin/sh"]
      args: ["-c", "while true; do date >> /var/nfs/pv-1/dates.txt; sleep 5; done"]

不需要进入pod,我们就可以发现在外面的挂载目录下就能看到同步了pod中输出的数据

[root@master nfs]# kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
nfs-pod-test     1/1     Running   0          4s
[root@master nfs]# tail -f /data/nfs/pv-1/dates.txt 
Wed Dec 15 08:42:28 UTC 2021
Wed Dec 15 08:42:33 UTC 2021
Wed Dec 15 08:42:38 UTC 2021
Wed Dec 15 08:42:43 UTC 2021
Wed Dec 15 08:42:48 UTC 2021
Wed Dec 15 08:42:53 UTC 2021
Wed Dec 15 08:42:58 UTC 2021
Wed Dec 15 08:43:03 UTC 2021
Wed Dec 15 08:43:08 UTC 2021
Wed Dec 15 08:43:13 UTC 2021
Wed Dec 15 08:43:18 UTC 2021
Wed Dec 15 08:43:23 UTC 2021
^C

二、【pv/pvc/storageClass】挂载实践

pv/pvc/storageClass概念

从yaml文件来解释,如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: xxx.xxx.xx.xx
    path: "/"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pv
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi

PVC 描述的:是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。

PV 描述的:则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。

PVC 和 PV ,是Kubernetes对于有状态的容器应用或者对数据需要持久化的应用场景设计的。
PV是对底层网络共享存储的抽象,将共享存储定义为一种“资源”,PVC则是用户对存储资源的一个“申请”。

其实跟“面向对象”的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的“实现“部分则由 PV 负责完成。

pv常用配置

存储能力(Capacity):
描述存储设备具备的能力,目前仅支持对存储空间的设置(storage=xx),未来可能加入IOPS、吞吐率等指标的设置。

访问模式(Access Modes):
对PV进行访问模式的设置,用于描述用户的应用对存储资源的访问权限。访问模式如下()里的缩写,是在CLI中的访问模式缩写。

  • ◎ ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂载。
  • ◎ ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。
  • ◎ ReadWriteMany(RWX):读写权限,允许被多个Node挂载。
  • ◎ ReadWriteOncePod(RWOP):读写权限,允许单个pod挂载,这仅支持 CSI 卷和 Kubernetes 1.22+ 版。如果您想确保整个集群中只有一个 Pod 可以读取该 PVC 或写入该 PVC,可使用 ReadWriteOncePod 访问模式。

存储类别(Class):
用于绑定PVC.具有特定类别的PV只能与请求了该类别的PVC进行绑定。未设定类别的PV则只能与不请求任何类别的PVC进行绑定。其他还有存储卷模式(Volume Mode)、回收策略(Reclaim Policy)、挂载参数(Mount Options)等。

StorageClass
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,
一方面减少了用户对于存储资源细节的关注;另一方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定(动态资源供应模式),实现了动态的资源供应。

关键配置参数:

提供者(Provisioner):

描述存储资源的提供者,也可以看作后端存储驱动。目前Kubernetes支持的Provisioner都以 kubernetes.io/ 为开头,用户也可以使用自定义的后端存储提供者。为了符合StorageClass的用法,自定义Provisioner需要符合存储卷的开发规范。我们用的是阿里云的 alicloud/nas

sc

Reclaim Policy的三种状态

  • 保持(Retain):删除PV后后端存储上的数据仍然存在,如需彻底删除则需要手动删除后端存储volume
  • 删除(Delete):删除被PVC释放的PV和后端存储volume
  • 回收(Recycle):保留PV,但清空PV上的数据(已废弃)

pod启动的时候pv/pvc做了什么?
1、根据pod声明的pvc找是否存在符合条件的pv,完成绑定(PV 和 PVC 的 spec 字段相同、PV 和 PVC 的 storageClassName 字段必须一样);
2、为虚拟机挂载远程磁盘到宿主机(attach);
3、格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上。

pv-pv-pvc-start-doing

PV生命周期
PV生命周期的各个阶段某个PV在生命周期中可能处于以下4个阶段(Phaes)之一。
Available:可用状态,还未与某个PVC绑定。
Bound:已与某个PVC绑定。
Released:绑定的PVC已经删除,资源已释放,但没有被集群回收。
Failed:自动资源回收失败。

生命周期

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

在启用动态供应模式的情况下,一旦用户删除了PVC,与之绑定的PV也将根据其默认的回收策略“Delete”被删除。如果需要保留PV(用户数据),则在动态绑定成功后,用户需要将系统自动生成PV的回收策略从“Delete”改成“Retain”。

三、pv/pvc实践

1、声明一个 pvc,执行kubectl apply -f nfs-pvc.yaml,内容为:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "rwx-nfs-pvc"
  resources:
    requests:
      storage: 20Mi
2、查看 pvc 状态:
kubectl describe pvc nfs
...
Events:
  Type    Reason         Age               From                         Message
  ----    ------         ----              ----                         -------
  Normal  FailedBinding  1s (x4 over 42s)  persistentvolume-controller  no persistent volumes available for this claim and no storage class is set
3、创建一个 statefulSet,执行kubectl apply -f nfs-deploy.yaml,内容为:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx-demo
        image: nginx
        imagePullPolicy: Always
        volumeMounts:
        - name: nfs-pvc-vol
          mountPath: /usr/share/nginx/html
      volumes:
      - name: nfs-pvc-vol
        persistentVolumeClaim:
          claimName: nfs-pvc
4、观察 pod 的状态,可以看到: pvc 在没有绑定pv的情况会导致pod一直处于pending;
$ kubectl describe pod 
5、创建一个 pv,执行kubectl apply -f pv.yaml,内容为:
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 20Mi
  accessModes:
    - ReadWriteMany
  storageClassName: "rwx-nfs-pvc"
  nfs:
    server: 192.168.23.10
    path: "/data/nfs/pv-2"
6、观察pod状态,可以看到,容器正常运行。

这里有个PersistentVolumeController的控制器, 会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。

7、进入容器,执行:
cd /usr/share/nginx/html ** echo "hello nfs!" > index.html
8、退出容器,执行以下命令暴露服务:
kubectl expose deployment nginx-demo --port=80 --type=NodePort --target-port=80 --name=nginx-demo
9、浏览器查看页面。
10、模拟容器挂掉,重启pod后数据是否会丢失。
11、尝试删除pv、pvc,看是否成功。
kubectl delete pvc nfs
kubectl delete pv nfs
12、删除deploy后再尝试删除pvc/pv;

四、使用StatefulSet创建有状态的应用

StatefulSet 有以下几个特点

  • 稳定,并且具有唯一的网络标识。
  • 稳定,并且可以持久存储。
  • 有序,可优雅的部署和伸缩。
  • 有序,可优雅的删除和停止。
  • 有序,可自动滚动更新。

即,我们的应用如有以上的任何一个特点,我们就可以使用stateFulSet控制器来完成应用的部署。

statefulSet

statefulSet的一些概念

1.网络标识

  • Pod 名:唯一且不会发生变化,由$(stateFulSet name)-(0-N)组成,N为一个无限大的正整数,从0开始。
  • 容器主机名:与Pod名称一致且不会发生变化。
  • A记录:每一个副本都拥有唯一且不变的一条A记录制定自己,格式为:(service name).$(namespace name).svc
  • Pod标签,通过StateFulSet创建的每个Pod都拥有一个唯一的标签,格式为:stateFulSet.kubernetes.io/pod-name=$(pod name),
    通常可以将新的service单独关联到此标签,来解决某个Pod的问题等。

2.存储(pvc)

每个Pod都对应一个PVC,PVC的名称格式:(pod name)-(0-N),N为一个无限大的正整数,从0开始。

当Pod被删除的时候不会自动删除对应的PVC,需要手动删除。
每次Pod升级,重启,删除重新创建后都可以保证使用的是首次使用的PVC,保证数据的唯一性与持久化。

3.有序

  • 当部署有N个副本的StatefulSet的时候,严格按照从0到N的顺序创建,并且下一个Pod创建的前提是上一个Pod已经running状态。
  • 当删除有N个副本的stateFulSet的时候,严格按照从N到0的顺序删除,并且下一个Pod删除的前提是上一个Pod已经完全Delete。
  • 当扩容StateFulSet副本的时候,每增加一个Pod的前提是上一个Pod已经running状态。
  • 当减少stateFulSet副本的时候,每删除一个Pod的前提是上一个Pod已经完全delete。
  • 当升级statefulSet的时候,严格按照从N到1的顺序升级,并且下一个Pod升级的前提是上一个Pod已经running状态。

4.无状态服务(Stateless Service)

该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的,比如kdemo,我们可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的,因为他唯一需要持久化的数据是存储在MySQL数据库中的,所以我们可以说kdemo这个应用是无状态服务,但是MySQL数据库就不是了,因为他需要把数据持久化到本地。

5.有状态服务(Stateful Service)

就和上面的概念是对立的了,该无法运行的实例需要在本地存储持久化数据,比如上面的MySQL数据库,你现在运行在节点Node-1,那么他的数据就存储在Node-1上面的,如果这个时候你把该服务迁移到节点Node-2去的话,那么久没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。

6.无头服务(Headless Service)

Headless Service 在定义上和普通的 Service 几乎一致, 只是他的 clusterIP: None,所以,这个 Service 被创建后并不会被分配一个 cluster IP,而是会以 DNS 记录的方式暴露出它所代理的 Pod,而且还有一个非常重要的特性,对于 Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:

...svc.cluster.local

7.volumeClaimTemplates

用于给StatefulSet的Pod申请PVC,称为卷申请模板,它会为每个Pod生成不同的PVC,关绑定到PV,从而实现Pod的专有存储。

实践四:使用pv/pvc部署statefulSet类型的Deployment

1.准备一个pvc用于绑定pv
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: kdemo-statefulset-pv
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: "rwx-statefulset-pvc"
  resources:
    requests:
      storage: 20Mi
2.准备用于挂载的存储卷pvpv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: kdemo-statefulset-pv
spec:
  capacity:
    storage: 20Mi
  accessModes:
    - ReadWriteMany
  storageClassName: "rwx-statefulset-pvc"
  nfs:
    server: 192.168.23.10
    path: "/data/nfs/statefulSet-1"
3.准备用于statefulset绑定的无头服务headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: kdemo-statefulset
  namespace: default
  labels:
    app: kdemo-statefulset
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  clusterIP: None
  selector:
    app: kdemo-statefulset
4.statefulset使用的deployment文件statefulSet.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kdemo-statefulset
  namespace: default
spec:
  serviceName: "nginx-demo"
  replicas: 1
  selector:
    matchLabels:
      app: kdemo-statefulset
  template:
    metadata:
      labels:
        app: kdemo-statefulset
    spec:
      containers:
      - name: kdemo-statefulset
        image: registry.cn-hangzhou.aliyuncs.com/liwb/kdemo:1.9
        imagePullPolicy: Always
        volumeMounts:
        - name: nfs-statefulset-vol
          mountPath: /webser/www/kdemo/web
      volumes:
      - name: nfs-statefulset-vol
        persistentVolumeClaim:
          claimName: kdemo-statefulset-pv
依次执行部署
kubectl apply -f pvc.yaml
kubectl apply -f pv.yaml 

kubectl get pv

NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
nfs-pv-2   1Gi        RWO            Recycle          Available           slow                    8s

# 可以看到成功创建了一个 PV 对象,状态是:`Available`。

kubectl apply -f headless-svc.yaml 

kubectl get svc

kubectl apply -f statefulSet.yaml

kubectl get pvc

通过 Headless Service,StatefulSet 就保证了 Pod 网络标识的唯一稳定性,由于 Pod IP 并不是固定的,所以我们访问有状态应用实例的时候,就必须使用 DNS 记录的方式来访问

最后我们可以通过删除 StatefulSet 对象来删除所有的 Pod,Pod是按照倒序的方式进行删除的

kubectl delete statefulsets kdemo-statefulset

kubectl get pods -w

五、思考

k8s明明已经有了多种存储卷方式实现持久化存储了。为什么还要增加pv/pvc的方式?

从上面的流程看通过pv/pvc的方式其实挺复杂的,这种方式是否过度设计?

六、作业

使用StorageClass的特性实现动态创建pv。

你可能感兴趣的:(持久化存储)