Docker容器数据卷
特性
- docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。
- 如果运行中的容器修改了现有的一个已经存在的文件,那么该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏。
- 关闭并重启容器,其数据不受影响;但删除Docker容器,则其改变将会全部丢失。
存在的问题
- 存在于联合文件系统(在docker中,只读层以及在顶层的读写层组合被称为联合文件系统)中,不易于宿主机的访问
- 容器间数据共享不便
- 删除容器其数据会丢失
解决方案:“卷”
- “卷”是容器上的一个或多个“目录”,此类目录可绕过联合文件系统,与宿主机上的某目录“绑定”。
- “卷”可以在运行容器时即完成创建与绑定操作。当然,前提需要拥有对应的申明。
- “卷”的初衷就是数据持久化。
创建数据卷
C:\Users\dell>docker volume create -d local volume_demo
volume_demo
C:\Users\dell>docker volume ls
DRIVER VOLUME NAME
local volume_demo
C:\Users\dell>docker volume inspect volume_demo
[
{
"CreatedAt": "2025-07-27T10:54:30Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/volume_demo/_data",//数据卷的存储目录
"Name": "volume_demo",
"Options": null,
"Scope": "local"
}
]
卷的分类
当在创建数据卷的时候,可以在创建容器时将主机本地的路径挂载到容器内作为数据卷。也可以在创建后挂载数据卷到容器使用,可以在运行容器时通过指定-v或–mount参数来使用该volume,并且可以依据数据卷类型不同挂载不同格式的数据卷。
- Volume:普通数据卷,映射到特定位置(/var/lib/docker/volumes/)。
- Bind:绑定数据卷,映射到主机的任意位置。
- tmpfs:临时数据卷,只存在于内存中。
使用默认的Volume数据卷,运行一个Nginx容器,source指定数据卷,destination指定source映射在容器中的目录:
docker run -d \
--name=nginx_demo_docker \
--mount source=nginx_demo_volume,destination=/usr/share/nginx.html \
nginx:latest
可以通过使用-v来指定数据卷信息:
docker run -d \
--name=nginx_demo_docker \
-v nginx_demo_volume,/usr/share/nginx.html \
nginx:latest
使用bind指定Volume数据卷类型,运行一个nginx容器,source指定数据卷,destination指定source映射在容器的目录:
docker run -d \
--name=nginx_demo_docker \
--mount type=bind, source=/usr/local/nginx_demo_volume,destination=/usr/share/ \
nginx:latest
可以通过使用-v来指定数据卷信息:
docker run -d \
--name=nginx_demo_docker \
-v /usr/local/nginx_demo_volume:/usr/share/ \
nginx:latest
挂载成功后,容器从/usr/share/目录下读取或写入数据,实际上是从宿主机的/usr/local/nginx_demo_volume目录中读取或写入数据。
使用Bind mount挂载宿主机目录到一个容器中的非空目录,那么此容器的非空目录中的文件会被隐藏,容器访问这个目录时能够访问到的文件均来自宿主机目录。这也是Bind mount模式和Volume模式在行为上最大的不同。
Volume和Bind mount模式能够在宿主机和容器间共享文件,从而能够将数据持久化到宿主机上,以避免写入容器存储层带来的容器停止后数据丢失的问题。
如果使用Linux运行Docker,那么避免写入数据到容器存储层还有一个方案:tmpfs mount。tmpfs mounts,顾名思义,是一种非持久化的数据存储。它只是将数据保存在宿主机的内存中,一旦容器停止运行,tmpfs mount会被移除,从而造成数据丢失。可以在运行容器时通过指定–tmpfs参数或–mount参数来使用tmpfs mount:
docker run -d --tmpfs /mydata:size=100m --name mycontainer myimage
数据卷容器
如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。
数据卷容器也是一个容器,但是它的目的是专门提供数据卷给其他容器挂载。
新建数据卷容器
docker run -it -v /dbdata --name db_data ubuntu
共享数据卷容器
docker run -it --volumes-from db_data --name db_data_test ubuntu
注意:db_data这个数据卷容器不能随便关闭,如果关闭了,其他挂载了db_data里面数据卷的容器就会用不了。
数据迁移
备份
docker run --rm --volumes-from [数据卷容器id/name] -v [宿主机目录]:[容器目录] [镜像名称] [备份命令] # 创建备份的容器,并且挂载/backup,然后执行备份压缩至/data docker run --rm --volumes-from vol-test -v /backup:/backup nginx tar zcf /backup/data.tar.gz /data
恢复
docker run --rm -itd --volumes-from [数据要到恢复的容器] -v [宿主机备份目录]:[容器备份目录] [镜像名称] [解压命令] docker run --rm --volumes-from vol-test -v /backup:/backup nginx tar xf /backup/data.tar.gz -C /
k8s数据卷
通过emptyDir共享数据
EmptyDir是一个特殊的Volume类型,如果删除Pod,emptyDir卷中的数据也将被删除,所以一般emptyDir用于Pod中的不同Container共享数据,比如一个Pod存在两个容器A和B,容器A需要使用容器B产生的数据,此时可以采用emptyDir共享数据,类似的使用如Filebeat收集容器内程序产生的日志。
使用HostPath挂载宿主机文件
HostPath卷可将节点上的文件或目录挂载到Pod上,用于实现Pod和宿主机之间的数据共享,常用的示例有挂载宿主机的时区至Pod,或者将Pod的日志文件挂载到宿主机等。
以下为使用hostPath卷的示例,实现将主机的/etc/timezone文件挂载到Pod的/etc/timezone(挂载路径可以不一致):
在配置HostPath时,有一个type的参数,用于表达不同的挂载类型,HostPath卷常用的type(类型)如下:- type为空字符串:默认选项,意味着挂载hostPath卷之前不会执行任何检查。
- DirectoryOrCreate:如果给定的path不存在任何东西,那么将根据需要创建一个权限为0755的空目录,和Kubelet具有相同的组和权限。
- Directory:目录必须存在于给定的路径下。
- FileOrCreate:如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设置为0644,和Kubelet具有相同的组和所有权。
- File:文件,必须存在于给定路径中。
- Socket:UNIX套接字,必须存在于给定路径中。
- CharDevice:字符设备,必须存在于给定路径中。
- BlockDevice:块设备,必须存在于给定路径中。
挂载NFS至容器
在生产环境中,考虑到数据的高可用性,仍然不建议使用NFS作为后端存储。因为NFS存在单点故障,很多企业并非对其实现高可用,并且NFS的性能存在很大的瓶颈。所以生产中,建议使用分布式存储,在公有云中建议使用公有云提供的NAS存储来替代NFS,并且NAS性能更好,可用性更高。NFS作为一个比较流行的远端存储工具,在非生产环境中是一个比较好用的选择,可以快速地搭建使用。接下来演示如何使用Volume挂载NFS为容器提供持久化存储。
和 emptyDir 、 HostPath 的 配 置 方 法 类 似 , NFS 的 Volume 配 置 也 是 在Volumes字段中配置的,和emptyDir不同的是,NFS属于持久化存储的一种,在Pod删除或者重启后,数据依旧会存储在NFS节点上。配置NFS也相对简单。
PersistentVolume
虽然Volume已经可以接入大部分存储后端,但是实际使用时还有诸多问题,比如:- 当某个数据卷不再被挂载使用时,里面的数据如何处理?
- 如果想要实现只读挂载如何处理?
- 如果想要只能有一个Pod挂载如何处理?
如上所述,对于很多复杂的需求Volume可能难以实现,并且无法对存储卷的生命周期进行管理。另一个很大的问题是,在企业内使用Kubernetes的不 仅 仅 是 Kubernetes 管 理 员 , 可 能 还 有 开 发 人 员 、 测 试 人 员 以 及 初 学Kubernetes的技术人员,对于Kubernetes的Volume或者相关存储平台的配置参数并不了解,所以无法自行完成存储的配置。
为 此 , Kubernetes 引 入 了 两 个 新 的 API 资 源 : PersistentVolume 和PersistentVolumeClaim。PersistentVolume(简称PV)是由Kubernetes管理员设置的存储,PersistentVolumeClaim(简称PVC)是对PV的请求,表示需要什么类型的PV。它们同样是集群中的一类资源,但其生命周期比较独立,管理员可以单独对PV进行增删改查,不受Pod的影响,生命周期可能比挂载它
的其他资源还要长。如果一个Kubernetes集群的使用者并非只有Kubernetes管理员,那么可以通过提前创建PV,用以解决对存储概念不是很了解的技术人员对存储的需求。和单独配置Volume类似,PV也可以使用NFS、GFS、CEPH等常用的存储后端,并且可以提供更加高级的配置,比如访问模式、空间大小以及回收策略等。目前PV的提供方式有两种:静态或动态。静态PV由管理员提前创建,动态PV无须提前创建。PV回收策略
PV访问策略
基于·NFS的PV
基于HostPath的PV
注意:使用hostPath需要固定Pod所在的节点,防止Pod飘移造成数据丢失。PV的状态
PersistentVolumeClaim
PVC是其他技术人员在Kubernetes上对存储的申请,它可以标明一个程序需要用到什么样的后端存储、多大的空间以及以什么访问模式进行挂载。这一点和Pod的QoS配置类似,Pod消耗节点资源,PVC消耗PV资源,Pod可以请求特定级别的资源(CPU和内存),PVC可以请求特定的大小和访问模式的PV。例如申请一个大小为5Gi且只能被一个Pod只读访问的存储。
在实际使用时,虽然用户通过PVC获取存储支持,但是用户可能需要具有不同性质的PV来解决不同的问题,比如使用SSD硬盘来提高性能。所以集群管理员需要根据不同的存储后端来提供各种PV,而不仅仅是大小和访问模式的区别,并且无须让用户了解这些卷的具体实现方式和存储类型,达到了存储的解藕,降低了存储使用的复杂度。案例如下:
apiVersion: v1 # API 版本 kind: PersistentVolume # 类型为持久卷 metadata: name: jenkins-pv # 持久卷的名称 spec: capacity: storage: 2Gi # 容量为2GB accessModes: #- ReadWriteMany # 访问模式为多路读写 - ReadWriteOnce # HostPath 通常只能支持单节点写入,因此使用 ReadWriteOnce hostPath: path: /data/jenkins #nfs: # server: 192.168.16.2 # NFS 服务器地址 # path: /data/jenkins # 共享的路径 --- kind: PersistentVolumeClaim # 类型为持久卷声明 apiVersion: v1 metadata: name: jenkins-pvc # 持久卷声明的名称 namespace: jenkins # 使用的命名空间 spec: resources: requests: storage: 2Gi # 请求2GB的存储空间 accessModes: - ReadWriteOnce # 访问模式与上面保持一致 --- apiVersion: apps/v1 # 使用的 Kubernetes API 版本 kind: Deployment # 资源类型为 Deployment metadata: name: jenkins # Deployment 的名称为 jenkins namespace: jenkins # 所属的命名空间为 jenkins spec: replicas: 1 # 副本数为 1 selector: matchLabels: app: jenkins # 选择器选择带有标签 app: jenkins 的 Pod template: metadata: labels: app: jenkins # Pod 的标签为 app: jenkins spec: serviceAccountName: jenkins-sa # 使用的服务账户为 jenkins-sa nodeName: node1 dnsConfig: #需要添加域名解析,不然容器内部可能无法访问外网 nameservers: - "8.8.8.8" # Google 的公共 DNS - "8.8.4.4" # Google 的公共 DNS containers: - name: jenkins # 容器的名称为 jenkins image: jenkins/jenkins:2.479.1 # 使用的镜像为 jenkins/jenkins:latest,高版本的jenkins会有跨域问题 ports: - containerPort: 8080 # 容器监听的端口为 8080 name: web # 端口名称为 web protocol: TCP # 使用的协议为 TCP - containerPort: 50000 # 容器监听的端口为 50000 name: agent # 端口名称为 agent protocol: TCP # 使用的协议为 TCP resources: limits: cpu: "1000m" # CPU 的资源限制为 1000m(相当于 1 核) memory: "1Gi" # 内存的资源限制为 1GiB requests: cpu: "500m" # CPU 的资源请求为 500m(相当于 0.5 核) memory: "512Mi" # 内存的资源请求为 512MiB livenessProbe: # 存活探针配置 httpGet: path: /login # 使用 HTTP GET 请求路径 /login port: 8080 # 请求的端口为 8080 initialDelaySeconds: 60 # 初始延迟时间为 60 秒 timeoutSeconds: 5 # 超时时间为 5 秒 failureThreshold: 12 # 失败阈值为 12 readinessProbe: # 就绪探针配置 httpGet: path: /login # 使用 HTTP GET 请求路径 /login port: 8080 # 请求的端口为 8080 initialDelaySeconds: 60 # 初始延迟时间为 60 秒 timeoutSeconds: 5 # 超时时间为 5 秒 failureThreshold: 12 # 失败阈值为 12 volumeMounts: # 挂载卷配置 - name: jenkins-volume # 卷的名称为 jenkins-volume subPath: jenkins-home # 在卷中的子路径为 jenkins-home mountPath: /var/jenkins_home # 挂载到容器中的路径为 /var/jenkins_home volumes: # 卷配置 - name: jenkins-volume # 卷的名称为 jenkins-volume #emptyDir: {} persistentVolumeClaim: # 持久卷声明 claimName: jenkins-pvc # 使用的持久卷声明的名称为 jenkins-pvc
动态存储StorageClass
虽然使用PV和PVC能屏蔽一些存储使用上的细节,降低了存储使用的复杂度,但是也会有另一个问题无法解决。当公司Kubernetes集群很多,并且使用它们的技术人员过多时,对于PV的创建是一个很耗时、耗力的工作,并且达到一定规模后,过多的PV将难以维护。所以就需要某种机制用于自动管理PV的生命周期,比如创建、删除、自动扩容等,于是Kubernetes就设计了一个名为StorageClass(缩写为SC,没有命名空间隔离性)的东西,通过它可以动态管理集群中的PV,这样Kubernetes管理员就无须浪费大量的时间在PV的管理中。
具体如何使用,各位读者就参考下文档,这里就不介绍了。