k8s 存储机制

一、Volume 的类型

Kubernetes 的 Volume 有非常多的类型,在实际使用中使用最多的类型如下。

  • emptyDir: 一种简单的空目录,主要用于临时存储。
  • hostPath: 将主机某个目录挂载到容器中。
  • ConfigMap、Secret: 特殊类型,将 Kubernetes 特定的对象类型挂载到Pod,
  • persistentVolumeClaim : Kubernetes 的持久化存储类型

1.EmptyDir

EmptyDir 是最简单的一种 Volume 类型,根据名字就能看出,这个 Volume 挂载后就是一个空目录,应用程序可以在里面读写文件,emptyDir Volume 的生命周期与 Pod 相同,Pod 删除后 Volume 的数据也同时删除掉。

emptyDir 的一些用途

  • 缓存空间,例如基于磁盘的归并排序。

  • 为耗时较长的计算任务提供检查点,以便任务能从崩溃前状态恢复执行

2. HostPath

HostPath 是一种持久化存储,emptyDir 里面的内容会随着 Pod 的删除而消失,但 HostPath 不会,如果对应的 Pod 删除,HostPath Volume 里面的内容依然存在于节点的目录中,如果后续重新创建 Pod 并调度到同一个节点,挂载后依然可以读取到之前 Pod 写的内容。

HostPath 存储的内容与节点相关,所以它不适合像数据库这类的应用,想象下如果数据库的 Pod 被调度到别的节点了,那读取的内容就完全不一样了

yaml 复制代码
apiVersion: v1 
kind: Pod 
metadata:
  name: test-hostpath 
spec:
  containers:
  - image: nginx:alpine
    name: hostpath-container 
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume 
  volumes:
  - name: test-volume 
    hostPath:
      path: /data

PV & PVC & StorageClass

详细介绍见下节

二、PV & PVC

1. PV 和 PVC

PV 描述的是持久化存储卷,主要定义的是一个持久化存储在宿主机上的目录,比如一个NFS的挂载目录。

PVC 描述的是 Pod 所希望使用的持久化存储的属性,比如,Volume 存储的大小、可读写权限等等。

Kubernetes 管理员设置好网络存储的类型,提供对应的PV描述符配置到 Kubernetes, 使用者需要存储的时候只需要创建 PVC,然后在 Pod 中使用 Volume 关联 PVC,即可让 Pod使用到存储资源,它们之间的关系如下图所示。

PV是集群级别的资源,并不属于某个命名空间

而PVC是命名空间级别 的资源,PV可以与任何命名空间的PVC资源绑定

上节说的PV和PVC方法虽然能实现屏蔽底层存储,但是存在缺点如下:

  • 但是PV创建比较复杂,通常都是由集群管理员管理,这非常不方便
  • 并且这种方式使得集群管理员通常需要提前创建好多个PV,比较不太方便,因为手动创建再多的PV如果没被使用很浪费空间,但是如果创建少了又会导致不够用

Kubernetes 解决这个问题的方法是提供动态配置PV的方法,可以自动创PV,也就是 StorageClass

2.StorageClass

管理员可以部署PV配置器(provisioner),然后定义对应的 StorageClass,这样开发者在创建 PVC的时候就可以选择需要创建存储的类型,PVC 会把 StorageClass 传递给 PV provisioner,由 provisioner 自动创建 PV

我们参考创建一个 local-path 类型的 Provisioner

Local Path Provisioner provides a way for the Kubernetes users to utilize the local storage in each node. Based on the user configuration, the Local Path Provisioner will create either hostPath or local based persistent volume on the node automatically. It utilizes the features introduced by Kubernetes Local Persistent Volume feature, but makes it a simpler solution than the built-in local volume feature in Kubernetes.

1.创建 provisioner

bash 复制代码
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml

2.验证创建PV&PVC

创建 PVC

yaml 复制代码
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: local-path-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 2Gi

创建 Pod

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: volume-test
  namespace: default
spec:
  containers:
  - name: volume-test
    image: nginx:stable-alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: volv
      mountPath: /data
    ports:
    - containerPort: 80
  volumes:
  - name: volv
    persistentVolumeClaim:      # 使用PVC之后这里只需要填写PVC的名字
      claimName: local-path-pvc

可以看到PV和PVC都已经创建出来

lua 复制代码
➜  ~ k get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
local-path-pvc   Bound    pvc-c049abb3-76c4-41c2-a5cf-a54045c542a1   2Gi        RWO            local-path     21h

➜  ~ k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
pvc-c049abb3-76c4-41c2-a5cf-a54045c542a1   2Gi        RWO            Delete           Bound    default/local-path-pvc   local-path              21h

这里的 default/local-path-pvc 就表示这个pvc绑定了default空间下的local-path-pvc的pvc

3.StatefulSet 有状态应用

1. 什么是 StatefulSet?

StatefulSet 是用来管理有状态应用的对象。和 Deployment 相同的是,StatefulSet管理了基于相同容器定义的一组Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个Pod维护了一个固定的ID。这些Pod是基于相同的声明来创建的,但是不能相互替换,无论怎么调度,每个Pod都有一个永久不变的ID

特点:

  • 部署有状态应用
  • 解决 Pod 独立生命周期,保持 Pod 启动顺序和唯一性
  • 有序,优雅的部署和扩展、删除和终止(例如: mysql 主从关系,先启动主,再启动从)

应用场景:数据库

附:有状态和无状态的区别:

  • 无状态:1. deployment认为所有的pod都是一样的 2.不用考虑顺序的要求 3.不用考虑在哪个node节点上运行 4. 可以随意扩容和缩容

  • 有状态:1.实例之间有差别,每个实例都有自己的独特性,元数据不同,例如 etcd, zookeeper 2.实例之间不对等的关系,以及依靠外部存储的应用

2. StatefulSet怎么运作

  • StatefulSet 给每个 Pod 提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新调度后 Pod 名称和 HostName不变。
  • StatefulSet 通过Headless Service 给每个Pod提供固定的访问域名,Service的概念会在后面章节中详细介绍
  • StatefulSet 通过创建 固定标识的 PVC ****保证Pod重新调度后还是能访问到相同的持久化数据

总结就是使用了 headless Service 和 PVC

3. 实践

1. 什么是 headless service?

前面讲的Service解决了Pod的内外部访问问题,但还有下面这些问题没解决。

  • 同时访问所有Pod
  • 一个Service内部的Pod互相访问

Headless Service 正是解决这个问题的,Headless Service不会创建ClusterIP,并且查询会返回所有 Pod 的DNS记录,这样就可查询到所有Pod的IP地址

总结:也就是 headless 可以理解为比 service 更加细粒度,使用 headless service 可以按照 pod 的域名(比如下面的 nginx-0.nginx [pod名+service名] )访问到一个具体的 pod,这就是为什么 headless service 为什么总是和 StatefulSet 一起使用,因为 StatefulSet 创建出来的 pod 的名称总是有序并且固定的,即使 pod 发生了重启也不会改表

2. 创建 headless service 和 StatefulSet

yaml 复制代码
apiVersion: v1 
kind: Service 
metadata:
  name: nginx 
  labels:
    app: nginx 
spec:
  ports:
  - name: nginx
    port: 80 
  selector:
    app: nginx 
  clusterIP: None # headless service和普通的servide的区别就是这里ClusterIP定义为None
yaml 复制代码
apiVersion: apps/v1 
kind: StatefulSet 
metadata:
  name: nginx 
spec:
  serviceName: nginx 
  replicas: 3
  selector:
    matchLabels: 
      app: nginx
  template: 
    metadata:
      labels:
        app: nginx
    spec: 
      containers:
      - name: container-0 
        image: nginx:alpine 
        resources:
          limits:
            cpu: 100m 
            memory: 200Mi
          requests:
            cpu: 100m 
            memory: 200Mi
        volumeMounts:       # Pod挂载的存储
        - name: data
          mountPath: /usr/share/nginx/html
      imagePullSecrets:
      - name: default-secret
  volumeClaimTemplates: 
  - metadata:
      name: data 
    spec:
      accessModes:
      - ReadWriteOnce 
      resources:
        requests: 
          storage: 1Gi
      storageClassName: local-path  # 指定storageclass

创建一个Pod来查询DNS,可以看到能返回所有Pod的记录

注意:不能在StatefulSet创建出来的pod里查DNS记录,必须要新建一个其他的pod查询

Headless Service创建后,每个Pod的IP都会有下面格式的域名

...svc.cluster.local 例如上面的三个Pod的域名就是:

  • nginx-0.nginx.default.svc.cluster.local
  • nginx-1.nginx.default.svc.cluster.local
  • nginx-2.nginx.default.svc.cluster.local

实际访问时可以省略后面的 ..svc.cluster.local

进入容器查看容器的hostname,可以看到同样是nginx-0、nginx-1和nginx-2

bash 复制代码
➜  ~ k exec -it nginx-0 -- /bin/sh -c 'hostname'
nginx-0

➜  ~ k exec -it nginx-1 -- /bin/sh -c 'hostname'
nginx-1

➜  ~ k exec -it nginx-2 -- /bin/sh -c 'hostname'
nginx-2

同时可以看一下 StatefulSet 创建的PVC,可以看到这些 PVC,都以"PVC名称- StatefulSet名称-编号"的方式命名,并且处于 Bound 状态

lua 复制代码
➜  ~ k get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-nginx-0     Bound    pvc-9d1f461e-46ed-437b-977c-6b50de279dab   1Gi        RWO            local-path     7h50m
data-nginx-1     Bound   pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6   1Gi        RWO            local-path     7h50m
data-nginx-2     Bound    pvc-699c9134-ef29-476a-812a-7012197c6611   1Gi        RWO            local-path     7h50m

pvc 创建出的 pv 的默认回收策略是 Delete,即删除PVC时删除对应的PV

sql 复制代码
➜  ~ k get pv pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   REASON   AGE
pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6   1Gi        RWO            Delete           Bound    default/data-nginx-1   local-path              21d
➜  ~

3. 存储状态

上面说了 StatefulSet 可以通过 PVC 做持久化存储,保证 Pod 重新调度后还是能访问到相同的持久化数据,在删除Pod 时,PVC 不会被删除

执行下面的命令,在 nginx-1 的目录 /usr/share/nginx/html 中写入一些内容,例如将 index.html 的内容修改为"hello world"

sql 复制代码
k exec nginx-1 -- sh -c 'echo hello world > /usr/share/nginx/html/index.html' 

修改后再访问就会返回 hello world
➜  ~ k exec -it nginx-1 -- curl localhost
hello world

删除这个nginx-1的pod
➜  ~ k delete pod nginx-1
pod "nginx-1" deleted

再次访问,还是会得到 "hello world"
➜  ~ k exec -it nginx-1 -- curl localhost
hello world

删除 StatefulSet 中的 pod 时,其创建的 PVC 是不会自动删除的,必须手动删除;但是 k8s 1.24 版本引入了一个新的特性支持删除 pod 时自动删除对应的 PVC

4. 手动清理PVC

删除掉 StatefulSet 之后对应的 PVC 还是存在

lua 复制代码
➜  ~ k get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-nginx-0     Bound    pvc-9d1f461e-46ed-437b-977c-6b50de279dab   1Gi        RWO            local-path     8h
data-nginx-1     Bound    pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6   1Gi        RWO            local-path     8h
data-nginx-2     Bound    pvc-699c9134-ef29-476a-812a-7012197c6611   1Gi        RWO            local-path     8h

找到对应的 PV

查看对应目录的文件,可以发现之前写入的数据仍然在对应的 index.html 里面,并没有随着pod的删除而被清理掉

bash 复制代码
➜  pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6_default_data-nginx-1 ls
index.html
➜  pvc-4aedc304-b5d4-4bab-adb4-ffe837fa02d6_default_data-nginx-1 cat index.html
hello world

为什么创建出来的 volume 都是在 /opt/local-path-provisioner 目录,是因为在创建 provisioner 时会创建出一个 configmap,这个 cm 指定了对应的挂载路径

参考文章

github.com/rancher/loc...

Kubernetes 1.23: StatefulSet PVC 自动删除 (alpha)

相关推荐
Hfc.4 小时前
ubuntu20.04系统搭建k8s1.28集群-docker作为容器运行时
ubuntu·kubernetes
alden_ygq11 小时前
Kubernetes Horizontal Pod Autosscaler(HPA)核心机制解析
云原生·容器·kubernetes
格桑阿sir11 小时前
Kubernetes控制平面组件:Kubelet详解(三):CRI 容器运行时接口层
docker·kubernetes·containerd·kubelet·cri-o·容器运行时·cri
hwj运维之路1 天前
k8s监控方案实践(三):部署与配置Grafana可视化平台
云原生·kubernetes·grafana
和计算机搏斗的每一天1 天前
k8s之探针
云原生·容器·kubernetes
项目題供诗1 天前
黑马k8s(四)
云原生·容器·kubernetes
杰克逊的日记1 天前
大项目k8s集群有多大规模,多少节点,有多少pod
云原生·容器·kubernetes
小张童鞋。1 天前
k8s之k8s集群部署
云原生·容器·kubernetes
long_21451 天前
k8s中ingress-nginx介绍
kubernetes·ingress-nginx
luck_me51 天前
k8s v1.26 实战csi-nfs 部署
linux·docker·云原生·容器·kubernetes