Kubernetes(k8s)存储管理之数据卷volumes(一):volumes的引入和emptyDir数据卷

发布时间 2023-03-22 19:06:56作者: 人生的哲理

一.系统环境

服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架构
CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 v1.21.9 x86_64

Kubernetes集群架构:k8scloude1作为master节点,k8scloude2,k8scloude3作为worker节点

服务器 操作系统版本 CPU架构 进程 功能描述
k8scloude1/192.168.110.130 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico k8s master节点
k8scloude2/192.168.110.129 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点
k8scloude3/192.168.110.128 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点

二.前言

Kubernetes(k8s)数据卷volumes类型众多,本文介绍数据卷volumes之一emptyDir数据卷

使用数据卷volumes的前提是已经有一套可以正常运行的Kubernetes集群,关于Kubernetes(k8s)集群的安装部署,可以查看博客《Centos7 安装部署Kubernetes(k8s)集群》https://www.cnblogs.com/renshengdezheli/p/16686769.html

三.Docker数据卷volumes

Docker 也有卷(Volume) 的概念,但对它只有少量且松散的管理。 Docker 卷是磁盘上或者另外一个容器内的一个目录。 Docker 提供卷驱动程序,但是其功能非常有限。

创建卷

[root@k8scloude1 ~]# docker volume create my-vol

查看卷

[root@k8scloude1 ~]# docker volume ls
local               my-vol

查看卷属性

[root@k8scloude1 ~]# docker volume inspect my-vol
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

删除卷

[root@k8scloude1 ~]# docker volume rm my-vol

运行一个带有卷的容器,可以使用--mount参数或者-v参数。

注意:如果您使用尚不存在的卷启动容器,Docker 会为您创建该卷。

[root@k8scloude1 ~]# docker run -d --name devtest --mount source=my-vol,target=/app nginx:latest
或者
[root@k8scloude1 ~]# docker run -d --name devtest -v my-vol:/app nginx:latest
  
 #查看Mounts部分,可以看到数据卷已经正确挂载
[root@k8scloude1 ~]# docker inspect devtest
......
"Mounts": [
    {
        "Type": "volume",
        "Name": "my-vol",
        "Source": "/var/lib/docker/volumes/my-vol/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

四.Kubernetes 数据卷volumes

4.1 有状态容器和无状态容器

无状态容器应用stateless,意味着容器上应用所使用的历史数据或运行状态不需要进行持久化,重新拉起这个应用时,无需关注这些历史输入。简单来说,例如你要运行一个计算器(而且这个计算器不需要支持历史记录功能),当你重新拉起这个计算器时,之前的数据不需要重新被加载上来,计算器可以认为是一个无状态应用。其它类似的无状态容器应用还包括一些协议转换、请求转发等应用,大体都可以认为是无状态的。

有状态容器应用stateful,有状态容器应用的特征是应用中处理的历史请求或操作,对现在或未来的操作是有影响的,那历史数据就必须被记录下来,这种应用就被称之为有状态容器应用。最典型的有状态应用莫过于数据库了,当数据库重新拉起时,你当然“要求”之前写入数据库的记录必须能被正确无误地加载出来。其实同样地,很多的消息队列(例如RabbitMQ等),为了不使消息丢失,消息队列中间件也会将消息进行持久化。当然,随着容器应用的边界越发广阔,越来越多的有状态应用正在容器化,我们看到很多的AI、自动驾驶、HPC任务都在进行容器化。

4.2 Kubernetes 数据卷volumes解决的问题

容器Container 中的文件在磁盘上是临时存放的,这给 容器Container 中运行的较重要的应用程序带来一些问题。

  • 问题之一是当容器崩溃时文件丢失。 kubelet 会重新启动容器,但容器会以干净的状态重启。
  • 第二个问题会在同一 Pod 中运行多个容器并共享文件时出现。

Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。

4.3 Kubernetes 数据卷volumes概述

Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。

卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。

使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。 容器中的进程看到的文件系统视图是由它们的容器镜像 的初始内容以及挂载在容器中的卷(如果定义了的话)所组成的。 其中根文件系统同容器镜像的内容相吻合。 任何在该文件系统下的写入操作,如果被允许的话,都会影响接下来容器中进程访问文件系统时所看到的内容。

卷挂载在镜像中的指定路径下。 Pod 配置中的每个容器必须独立指定各个卷的挂载位置。

卷不能挂载到其他卷之上(不过存在一种使用 subPath 的相关机制),也不能与其他卷有硬链接

五.临时卷emptyDir

5.1 临时卷emptyDir使用场景

有些应用程序需要额外的存储,但并不关心数据在重启后是否仍然可用。 例如,缓存服务经常受限于内存大小,而且可以将不常用的数据转移到比内存慢的存储中,对总体性能的影响并不大。临时卷 就是为此类用例设计的。因为卷会遵从 Pod 的生命周期,与 Pod 一起创建和删除, 所以停止和重新启动 Pod 时,不会受持久卷在何处可用的限制。

5.2 临时卷emptyDir简介

当 Pod 分派到某个节点上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。 就像其名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。

说明:容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的。

emptyDir 的一些用途:

  • 缓存空间,例如基于磁盘的归并排序。
  • 为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
  • 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。

emptyDir.medium 字段用来控制 emptyDir 卷的存储位置。 默认情况下,emptyDir 卷存储在该节点所使用的介质上; 此处的介质可以是磁盘、SSD 或网络存储,这取决于你的环境。 你可以将 emptyDir.medium 字段设置为 "Memory", 以告诉 Kubernetes 为你挂载 tmpfs(基于 RAM 的文件系统)。 虽然 tmpfs 速度非常快,但是要注意它与磁盘不同:tmpfs 在节点重启时会被清除, 并且你所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。

你可以通过emptyDir.sizeLimit为默认介质指定大小限制,来限制 emptyDir 卷的存储容量。 此存储是从节点临时存储中分配的。 如果来自其他来源(如日志文件或镜像分层数据)的数据占满了存储,emptyDir 可能会在达到此限制之前发生存储容量不足的问题。

说明:
当启用 SizeMemoryBackedVolumes 特性门控时, 你可以为基于内存提供的卷指定大小。 如果未指定大小,则基于内存的卷的大小为 Linux 主机上内存的 50%。

5.3 创建普通的pod

查看k8s状态

[root@k8scloude1 ~]# kubectl get nodes
NAME         STATUS   ROLES                  AGE   VERSION
k8scloude1   Ready    control-plane,master   8d    v1.21.0
k8scloude2   Ready    <none>                 8d    v1.21.0
k8scloude3   Ready    <none>                 8d    v1.21.0

创建一个目录存放yaml文件

[root@k8scloude1 ~]# mkdir volume

创建一个namespace

[root@k8scloude1 ~]# kubectl create ns volume
namespace/volume created

切换namespace到volume

[root@k8scloude1 ~]# kubens volume
Context "kubernetes-admin@kubernetes" modified.
Active namespace is "volume".

生成创建pod的yaml文件

[root@k8scloude1 ~]# cd volume/

[root@k8scloude1 volume]# pwd
/root/volume

#生成创建pod的yaml文件
[root@k8scloude1 volume]# kubectl run pod --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml >pod.yaml

[root@k8scloude1 volume]# vim pod.yaml 

[root@k8scloude1 volume]# cat pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod
  name: pod
spec:
  terminationGracePeriodSeconds: 0
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: pod
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

创建pod

[root@k8scloude1 volume]# kubectl apply -f pod.yaml 
pod/pod created

#可以看到pod运行在k8scloude2节点
[root@k8scloude1 volume]# kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
pod    1/1     Running   0          11s   10.244.112.180   k8scloude2   <none>           <none>

进入pod里面,创建container.txt文件

[root@k8scloude1 volume]# kubectl exec -it pod -- bash
root@pod:/# which nginx
/usr/sbin/nginx
root@pod:/# touch container.txt
root@pod:/# exit
exit

在pod里创建container.txt文件,对应的在k8scloude2节点上也有相应的文件

[root@k8scloude2 ~]# find / -name container.txt
/var/lib/docker/overlay2/909e91a50b6d90577e21c6c1ccb97d4571dddeecc296323934c28dfa3a26acf7/diff/container.txt
/var/lib/docker/overlay2/909e91a50b6d90577e21c6c1ccb97d4571dddeecc296323934c28dfa3a26acf7/merged/container.txt

删除pod

[root@k8scloude1 volume]# kubectl delete pod pod 
pod "pod" deleted

k8scloude2节点上对应的文件也没了

[root@k8scloude2 ~]# find / -name container.txt

5.4 创建有emptyDir卷的pod

当我们删除pod之后,我们往pod里写的数据都被删除了,这样的容器不存储任何数据,称为无状态的stateless,有时候我们需要pod能够存储数据,这样的容器称为有状态的statefull,docker 配置数据卷为:docker run xxx -v /xxx:/yyy

下面配置emptyDir数据卷,emptyDir类型的卷在物理机随机生成一个目录

[root@k8scloude1 volume]# vim pod-emptydir.yaml 

[root@k8scloude1 volume]# cat pod-emptydir.yaml 
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod
  name: pod
spec:
  terminationGracePeriodSeconds: 0
  #指定卷
  volumes:
  #卷的名字
  - name: v1
    #卷的类型为emptyDir
    emptyDir: {}
  - name: v2
    emptyDir: {}
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: pod
    resources: {}
    #卷挂载,把v1卷挂载到/xx目录
    volumeMounts:
    - name: v1
      mountPath: /xx
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

创建pod

[root@k8scloude1 volume]# kubectl apply -f pod-emptydir.yaml 
pod/pod created

[root@k8scloude1 volume]# kubectl get pod -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
pod    1/1     Running   0          5s    10.244.112.181   k8scloude2   <none>           <none>

进入pod,在挂载目录/xx/下写入数据

[root@k8scloude1 volume]# kubectl exec -it pod -- bash
root@pod:/# ls /xx/

root@pod:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         150G  4.2G  146G   3% /
tmpfs            64M     0   64M   0% /dev
tmpfs          1018M     0 1018M   0% /sys/fs/cgroup
/dev/sda1       150G  4.2G  146G   3% /xx
shm              64M     0   64M   0% /dev/shm
tmpfs          1018M   12K 1018M   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs          1018M     0 1018M   0% /proc/acpi
tmpfs          1018M     0 1018M   0% /proc/scsi
tmpfs          1018M     0 1018M   0% /sys/firmware

root@pod:/# echo "hello w" >/xx/h.txt
root@pod:/# cat /xx/h.txt 
hello w
root@pod:/# exit
exit

在k8scloude2节点上查看对应的docker容器,可以看到pod的容器名为82df167460ae

[root@k8scloude2 ~]# docker ps | grep pod
82df167460ae   605c77e624dd                                          "/docker-entrypoint.…"   About a minute ago   Up About a minute             k8s_pod_pod_volume_d8fda77c-1554-4110-9ca1-29b8f177cd1b_0
056dfa5153a8   registry.aliyuncs.com/google_containers/pause:3.4.1   "/pause"                 About a minute ago   Up About a minute             k8s_POD_pod_volume_d8fda77c-1554-4110-9ca1-29b8f177cd1b_0

查看容器属性的Mounts部分,可以看到emptyDir卷的目录和挂载目录

[root@k8scloude2 ~]# docker inspect 82df167460ae | grep -A10 Mounts
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/d8fda77c-1554-4110-9ca1-29b8f177cd1b/volumes/kubernetes.io~empty-dir/v1",
                "Destination": "/xx",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "bind",

查看对应的物理机目录:/var/lib/kubelet/pods/d8fda77c-1554-4110-9ca1-29b8f177cd1b/volumes/kubernetes.io~empty-dir/v1

#也存在相应的文件
[root@k8scloude2 ~]# cat /var/lib/kubelet/pods/d8fda77c-1554-4110-9ca1-29b8f177cd1b/volumes/kubernetes.io~empty-dir/v1/h.txt 
hello w

删除pod

[root@k8scloude1 volume]# kubectl delete pod pod --force
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted

当pod被删除之后,物理机上文件也没了

[root@k8scloude2 ~]# ls /var/lib/kubelet/pods/d8fda77c-1554-4110-9ca1-29b8f177cd1b/volumes/kubernetes.io~empty-dir/v1/
ls: 无法访问/var/lib/kubelet/pods/d8fda77c-1554-4110-9ca1-29b8f177cd1b/volumes/kubernetes.io~empty-dir/v1/: 没有那个文件或目录

emptyDir类型的数据卷默认是以内存为介质的,删除pod之后,物理机上对应的目录也没了

5.5 sidecar使用emptyDir卷共享数据

emptyDir一般用来共享数据,比如:一个pod里运行了两个容器,一个为nginx容器,nginx容器挂载一个目录a,日志生成了之后就放在目录a,另一个fluentd容器读取目录a进行日志分析,如下实例:

[root@k8scloude1 volume]# vim sharedir.yaml 

#v1卷分别挂载到n1和s1容器
[root@k8scloude1 volume]# cat sharedir.yaml 
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod
  name: pod
spec:
  terminationGracePeriodSeconds: 0
  volumes:
  - name: v1
    emptyDir: {}
  - name: v2
    emptyDir: {}
  containers:
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: n1
    resources: {}
    volumeMounts:
    - name: v1
      mountPath: /xx
  - image: nginx
    imagePullPolicy: IfNotPresent
    name: s1
    resources: {}
    command: ["sh","-c","sleep 1000"]
    volumeMounts:
    - name: v1
      mountPath: /yy
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

创建pod

[root@k8scloude1 volume]# kubectl apply -f sharedir.yaml 
pod/pod created

[root@k8scloude1 volume]# kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
pod    2/2     Running   0          13s   10.244.112.182   k8scloude2   <none>           <none>

进入pod里的容器n1,创建文件

[root@k8scloude1 volume]# kubectl exec -it pod -c n1 -- bash
root@pod:/# echo "nginx log" >/xx/nginx.log
root@pod:/# exit
exit

进入pod里的容器s1,可以看到文件nginx.log

[root@k8scloude1 volume]# kubectl exec -it pod -c s1 -- bash
root@pod:/# cat /yy/nginx.log 
nginx log
root@pod:/# exit
exit

在k8scloude2节点上查看对应的容器

[root@k8scloude2 ~]# docker ps | grep pod
eb17cb0232c3   605c77e624dd                                          "sh -c 'sleep 1000'"     47 seconds ago   Up 46 seconds             k8s_s1_pod_volume_69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf_0
0537eb92e1dc   605c77e624dd                                          "/docker-entrypoint.…"   47 seconds ago   Up 46 seconds             k8s_n1_pod_volume_69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf_0
210302b1861f   registry.aliyuncs.com/google_containers/pause:3.4.1   "/pause"                 47 seconds ago   Up 47 seconds             k8s_POD_pod_volume_69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf_0

查看两个容器的属性,可以看到/xx /yy 对应的物理机的目录是一样的

[root@k8scloude2 ~]# docker inspect eb17cb0232c3 | grep -A10 Mounts
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf/volumes/kubernetes.io~empty-dir/v1",
                "Destination": "/yy",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "bind",

[root@k8scloude2 ~]# docker inspect 0537eb92e1dc | grep -A10 Mounts
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf/volumes/kubernetes.io~empty-dir/v1",
                "Destination": "/xx",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "bind",
                
[root@k8scloude2 ~]# cat /var/lib/kubelet/pods/69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf/volumes/kubernetes.io~empty-dir/v1/nginx.log 
nginx log                

删除pod

[root@k8scloude1 volume]# kubectl delete pod pod --force
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted

删除pod之后,目录也消失了,emptyDir卷可以共享数据但是不可以永久存储,持久化存储请查看博客《Kubernetes(k8s)存储管理之数据卷volumes(二):hostPath数据卷 《Kubernetes(k8s)存储管理之数据卷volumes(三):NFS数据卷《Kubernetes(k8s)存储管理之数据卷volumes(四):持久卷Persistent Volume《Kubernetes(k8s)存储管理之数据卷volumes(五):动态制备-存储类StorageClass

[root@k8scloude2 ~]# cat /var/lib/kubelet/pods/69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf/volumes/kubernetes.io~empty-dir/v1/nginx.log 
cat: /var/lib/kubelet/pods/69ebce38-cfc6-4fdb-bd83-a3b8ddf034cf/volumes/kubernetes.io~empty-dir/v1/nginx.log: 没有那个文件或目录