Docker数据卷

763 阅读16分钟

伸手摘星,即使一无所获,亦不致满手污泥

请关注公众号:星河之码

我们在运行一个docker容器的时候,会产生一系列的数据文件,这些数据文件在我们删除docker容器的时候也会被删除,如果当我们希望把这些数据留下来当做其他的用途的时候,就需要将docker的文件持久化下来,实现数据共享,那么这个数据怎么共享呢。

在docker中提供了两种方实现宿主机与容器之间的数据共享

  • cp命令
  • Docker数据卷

下面就来看看这种种方式是怎么用的

一、cp命令

docker cp 命令用于容器与主机之间的数据拷贝,它是docker最原始的管理数据方式,基本不会用到。但是还是要了解一下的

1.1 docker cp 介绍

  • 主要作用

    在容器和主机之间复制文件/文件夹

  • 语法格式

    #宿主机文件复制到容器内
    docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
    #容器内文件复制到宿主机
    docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
    
    • container :容器名字或者Id,可以是正在运行或已停止的容器
    • SRC_PATH 或 DEST_PATH可以是文件或目录,当 DEST_PATH不存在时,会自动创建
    • 该命令会默认容器路径相对于容器的 /(根)目录
    • 主机路径则是相对于执行 docker cp 命令的当前目录
  • 常用参数说明

    option作用
    -a存档模式(复制所有uid / gid信息)
    -L保持源目标中的链接

    这两个参数一般都用不到

1.2 SRC_PATH 和 DEST_PATH

SRC_PATH是指的被cp的文件, DEST_PATH是目标地址,它们都可以是文件或者目录,所以主要可以分为一下几种情况

  • SRC_PATH 是文件,DEST_PATH 不存在

    创建 DEST_PATH 所需的文件夹,文件正常保存到创建的DEST_PATH 中

  • SRC_PATH 是文件,DEST_PATH 不存在,并以 / 结尾

    报错:目标目录不能直接在/下,并以 / 结尾 目标目录则必须存在

  • SRC_PATH 是文件,DEST_PATH 存在且是一个文件

    DEST_PATH 被源文件的内容覆盖

  • SRC_PATH 是文件,DEST_PATH 存在且是一个文件夹

    SRC_PATH 的文件会被复制到DEST_PATH文件夹中

  • SRC_PATH是文件夹,DEST_PATH 不存在

    创建 DEST_PATH 文件夹,并将源文件夹的内容复制到该文件夹中

  • SRC_PATH是文件夹, DEST_PATH存在并且是一个文件

    错误:无法将文件夹复制到文件

  • SRC_PATH是文件夹,DEST_PATH存在并且是文件夹

    • SRC_PATH 不以 /. 结尾,源文件夹复制到此DEST_PATH文件夹中
    • SRC_PATH 以 /. 结尾,源文件夹的内容被复制到DEST_PATH文件夹中

1.3 docker cp 案例

  • 以nginx镜像为例
docker pull nginx:1.19.3-alpine
  • 宿主机文件 copy to 容器内
#启动nginx容器
docker run -itd --name nginx -p 80:80 nginx:1.19.3-alpine
#在宿主机的/data 创建一个index.html 并且追加一些内容
cd /data
echo "星河之码" > /data/index.html
#将宿主机/data 下的index.html cp 到 nginx 容器的 /usr/share/nginx/html/index.html
docker cp /data/index.html nginx:/usr/share/nginx/html/index.html
  • 容器内文件 copy to 宿主机
#启动nginx容器
docker run -itd --name nginx -p 80:80 nginx:1.19.3-alpine
#将nginx容器下的/etc/nginx/nginx.conf 文件 cp 到宿主机的/data
docker cp nginx:/etc/nginx/nginx.conf /data

文件的用法也是一样的

二、数据卷

2.1 什么是数据卷

数据卷(Data Volumes)是一个可供一个或多个容器使用的特殊目录,它将主机操作系统目录直接映射进容器

数据卷存在于宿主机中,独立于容器,和容器的生命周期是分离的,数据卷存在于宿主机的文件系统中,数据卷可以目录也可以是文件,容器可以利用数据卷与宿主机进行数据共享,实现了容器间的数据共享和交换

docker容器数据卷可以看成一个u盘,它可以存在于一个或多个的容器中,由docker挂载到容器,但不属于联合文件系统,Docker不会在容器删除时删除其挂载的数据卷

docker容器数据卷的特点

  • 数据卷可以在容器之间共享或重用数据
  • 数据卷中的更改可以立即生效
  • 数据卷中的更改不会包含在镜像的更新中
  • 数据卷默认会一直存在,即使容器被删除
  • 数据卷的生命周期一直持续到没有容器使用它为止

容器中的管理数据主要有两种方式

  • 数据卷:Data Volumes 容器内数据直接映射到本地主机环境
  • 数据卷容器:Data Volume Containers 使用特定容器维护数据卷

注意事项

  • docker官网推荐尽量进行目录挂载,不要进行文件挂载

    前面虽然介绍可以挂载一个文件,但是我们一般不这么做,都是直接挂载整个目录

  • 挂载数据卷,一般通过run而非create/start创建启动容器

    run 可以启动的同时通过-v参数挂载,而create/start命令创建启动容器后,然后再挂载数据卷,要修改很多配置文件,非常麻烦。

2.2 数据卷类型

一般用数据卷来持久化数据,而数据卷类型有三种

  • 宿主机数据卷(推荐)

    数据卷存在于宿主机的文件系统中,但是容器可以访问。

  • 命名的数据卷

    磁盘上Docker管理的数据卷,它有个自己的名字。

  • 匿名数据卷

    磁盘上Docker管理的数据卷,它没有名字,所以不太容易找到,由Docker来管理这些文件。

数据卷一般都在宿主机文件系统里面(网络文件系统等情况下除外),宿主机数据卷是在宿主机内的指定目录下,而其他两种则在docker管理的目录下,一般是 /var/lib/docker/volumes/目录下

2.3 宿主机数据卷

2.3.1 宿主机数据卷介绍

宿主机数据卷(bind mounts):容器内的数据被存放到宿主机文件系统的任意指定位置,也可以挂载到特定的文件系统中,不由docker管理,除了docker之外的进程也可以任意对他们进行修改

当使用bind mounts时,宿主机的目录或文件被挂载到容器中。容器将按照挂载目录或文件的绝对路径来使用或修改宿主机的数据。宿主机中的目录或文件不需要预先存在,在需要的使用会自动创建。

  • 宿主机有一个目录妥善结构化的文件系统时,bind mounts在性能上是有很大优势

  • bind mounts的容器可以在通过容器内部的进程对主机文件系统进行修改,包括创建,修改和删除重要的系统文件和目录

    也因为docker可以操作宿主机的文件,因此可能会影响到宿主机上Docker以外的进程,或造成安全方面的影响

2.3.2 宿主机数据卷数据覆盖问题

  • 如果挂载一个空的数据卷到容器中的一个非空目录中,那么容器的非空目录下的文件会被复制到数据卷中

  • 如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录会显示数据卷中的数据

    也即是说只要数据卷中的数据非空,即使容器中的目录有数据,这些容器原始数据会被隐藏掉,而使用数据卷的数据

2.2.3 宿主机数据卷的语法使用

  • 语法

    docker run -v /宿主机绝对路径目录:/容器内目录 镜像名
    

    例如:启动mysql 的时候

    docker run -itd --name mysql -v /data/mysql:/var/lib/mysql mysql:5.7.31

    当然上述命令只是演示一下-v的写法,实际上启动mysql 还可以加上其他的一些参数,完整命令如下

    docker run -itd --name mysql --restart always --privileged=true -p 3306:3306 -e MYSQL_ROOT_PASSWORD=admin -v /data/mysql:/var/lib/mysql mysql:5.7.31 -- character-set-server=utf8 --collation-server=utf8_general_ci

  • 拉取MySQL镜像

    docker pull mysql:5.7.31
    
  • 运行镜像

    docker run -itd --name mysql --restart always --privileged=true -p 3306:3306 -e MYSQL_ROOT_PASSWORD=admin -v /data/mysql:/var/lib/mysql mysql:5.7.31 --character-set-server=utf8 --collation-server=utf8_general_ci
    

    这里是直接运行mysql镜像,并且将宿主机的/data/mysql目录挂载到容器的/var/lib/mysql

    我们一般推荐先在宿主机下创建好/data/mysql,再进行数据挂载,不然可能有权限问题

2.2.4 容器目录权限

docker run -v 有三种模式,可以控制对容器内文件的读写权限,分别是ro,rw,不指定,其用法如下:

语法

ro:readonly 只读
rw:readwrite 可读可写

docker run -it -v /宿主机绝对路径目录:/容器内目录: 镜像名
docker run -it -v /宿主机绝对路径目录:/容器内目录:ro 镜像名
docker run -it -v /宿主机绝对路径目录:/容器内目录:rw 镜像名

例如:
docker run -d -P --name mysql -v edwinMysql:/etc/mysql: nginx
docker run -d -P --name mysql -v edwinMysql:/etc/mysql:ro nginx
docker run -d -P --name mysql -v edwinMysql:/etc/mysql:rw nginx
  • 不指定(默认)

    • 文件:

      • 宿主机修改文件后容器里面看不到变化
      • 容器里面修改该文件,宿主机也看不到变化
    • 文件夹:不管是宿主机还是容器内修改、新增、删除文件 都会相互同步

      所以我们一般直接挂载整个目录

  • ro(readonly 只读)

    • 文件:容器内不能修改,会提示read-only
    • 文件夹:容器内不能修改、新增、删除文件夹中的文件,会提示read-only
  • rw(readwrite 可读可写)

    • 文件:不管是宿主机还是容器内修改,都会相互同步

      但容器内不允许删除,会提示Device or resource busy

      宿主机删除文件,容器内的不会被同步

    • 文件夹:不管是宿主机还是容器内修改、新增、删除文件,都会相互同步

2.2.5 宿主机目录权限

既然容器目录有权限,那么宿主机目录肯定也有权限,docker推荐我们先创建宿主机目录,然后挂载也是为了提前赋权,因为某些镜像挂载需要宿主机的赋权可以,不然会报错,比如nexus3,下面就以nexus3为例,来看看宿主机目录权限

2.2.5.1 不挂载启动nexus3
  • nexus3的docker官网
    https://hub.docker.com/r/sonatype/nexus3
    
  • 拉取镜像

    docker pull sonatype/nexus3:3.28.1
    
  • 备份镜像

    docker save sonatype/nexus3:3.28.1 -o sonatype.nexus3.3.28.1.tar
    
  • 导入镜像

    docker load -i sonatype.nexus3.3.28.1.tar
    
  • 运行容器

    docker run -d -p 8081:8081 --name nexus3 sonatype/nexus3:3.28.1
    
  • 查看启动日志

    docker logs -f nexus3
    
  • 进入容器查找初始化密码

    #进入容器
    docker exec -it nexus3 /bin/bash
    #/nexus-data目录
    cd /nexus-data/
    #查看密码
    vi admin.password 或者 cat admin.password
    

    当使用cat admin.password 命令的时候,密码会跟目录一起显示,如上图,如果觉得不好看可以使用

    vi admin.password

  • 浏览器端访问

    http://192.168.242.128:8081/
    

    第一次登陆会让我们修改密码,设置一个新密码就可以

2.2.5.2 挂载目录启动nexus3
  • 退出容器

    exit
    
  • 删除所有运行容器的命令

    docker rm $(docker stop $(docker ps -aq))
    

    这是删除刚才启动的容器,如果没有执行上面的不挂载启动,不要执行,这只是为了试验,所以启动了多次,需要把之前的删除,删除也有很多种方式,可以自由选择,这个命令会把所有容器都停止然后删除

  • 数据卷挂载启动

    docker run -d -p 8081:8081 --name nexus3 -v /data/nexus3/:/nexus-data/ sonatype/nexus3:3.28.1
    

    拉取的动作上面已经做了,这里就不再重复了

    将/nexus-data/挂载到宿主机的/data/nexus3下,这样即使容器停了,下载jar 不会被删除

    nexus3下载的jar就是在/nexus-data下

  • 查看容器启动日志

    docker logs -f nexus3
    

    报错信息如下:

    而报错的原因以及解决方案,在官网也给出了说明文档

  • 删除容器

    docker rm -f nexus3
    

    报错信息告诉我们没有权限,就先删除感刚刚启动失败的容器,然后创建目录赋权后在重新运行

  • 先在宿主机创建挂载目录

    mkdir /data/nexus3
    
  • 为挂载目录授权

    chown -R 200 /data/nexus3
    

    开发环境中推荐为挂载目录授最高权限777;生产环境需要查看官网文档,结合实际生产环境进行授权,这里是200的权限

  • 再次运行容器

    docker run -d -p 8081:8081 --name nexus3 -v /data/nexus3/:/nexus-data/ sonatype/nexus3:3.28.1
    
  • 查看容器启动日志

    docker logs -f nexus3
    #成功启动nexus3
    

2.5 命名的数据卷

命名的数据卷顾名思义就是给数据卷起一个名字,以nginx为例,看看命名的数据卷是怎么挂载的

  • 拉取基础镜像

    docker pull nginx:1.19.3-alpine
    
  • 挂载数据卷启动容器

    docker run -itd --name nginx -p 80:80 -v edwin-nginx:/etc/nginx nginx:1.19.3-alpine
    

    以上命令,没有宿主机目录位置,只有一个名字和一个容器的目录,edwin-nginx 就是数据卷的名字

  • 查看docker数据卷

    docker volume ls
    

  • 查看edwin-nginx宿主机目录

    docker volume inspect edwin-nginx
    

  • 进入docker数据卷默认目录

    cd /var/lib/docker/volumes/edwin-nginx
    

    前文提到命名的数据卷和匿名数据卷的目录在docker管理下,一般是 /var/lib/docker/volumes/目录下

  • 查看文件 ls

    会发现edwin-nginx有一个 data目录 ,所有的文件docker默认保存在_data目录中

    cd _data
    

  • 删除容器

    此时当我们删除容器后,会发现宿主机中的数据还存在

    docker rm $(docker stop $(docker ps -aq))
    

2.6 匿名数据卷

命名的数据卷顾名思义就是没有给数据卷起一个名字,还是以nginx为例,看看匿名的数据卷是怎么挂载的

  • 拉取基础镜像

    docker pull nginx:1.19.3-alpine
    
  • 挂载数据卷启动容器

    docker run -itd --name nginx -p 80:80 -v /etc/nginx nginx:1.19.3-alpine
    

    以上命令,没有宿主机目录位置,也没有起名字,只有一个容器的目录

  • 查看docker数据卷

    docker volume ls
    
  • 查看宿主机目录

    docker volume inspect 59400fa952cf41e9c11c599812290391edcb4b4b0a310dbba95141842964b2e1
    

    59400fa952cf41e9c11c599812290391edcb4b4b0a310dbba95141842964b2e1 是容器Id,

  • 进入docker数据卷默认目录

    cd /var/lib/docker/volumes/59400fa952cf41e9c11c599812290391edcb4b4b0a310dbba95141842964b2e1
    

    前文提到命名的数据卷和匿名数据卷的目录在docker管理下,一般是 /var/lib/docker/volumes/目录下

  • 查看文件 ls

    会发现dbd07daa4e40148b11.... 下有一个 data目录 ,所有的文件docker默认保存在_data目录中

    cd _data
    
  • 查看id宿主机目录

    docker volume inspect 59400fa952cf41e9c11c599812290391edcb4b4b0a310dbba95141842964b2e1
    

  • 删除容器

    此时当我们删除容器后,同样会发现宿主机中的数据还存在

    docker rm $(docker stop $(docker ps -aq))
    

通过上面的演示,发现命名数据卷和匿名数据卷本质上区别不大,就是启动的时候不指定名字,在docker数据卷默认目录分别创建了一个以名字和id的挂载目录

2.7 清理数据卷

在上面的演示中最后一步我们删除容器后,发现数据卷仍然存在,这个时候如果确认这些数据卷是没有用的,就需要去清理它,不然会占用资源

  • 查看docker数据卷

    docker volume ls
    

    可以看到,虽然容器删除了,但是数据卷仍然存在,暂用空间。可以通过prune命令清理

  • 清理数据卷

    docker volume prune
    
  • 查看docker数据卷

    docker volume ls
    

    数据卷被清理,资源被释放

三、数据卷容器

实现容器之间的数据同步,其他容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器

  • 如果需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。
  • 数据卷容器也是一个容器,它的目的是专门用来提供数据卷给其他容器挂载。

数据卷容器一般也被称为父容器

基本结构如下:

通过这个图可以看出来,数据卷容器就是介于容器和宿主机之间的一个容器,数据卷容器挂载到宿主机,其他容器跟数据卷容器进行挂载,从而到底多个容器数据共享的目的

  • 语法

    docker run --volumes-from:
    

    通过--volumes-from:进行挂载

下面来实现上图中两个Nginx的挂载

  • 基础镜像

    docker pull centos:7.8.2003
    docker pull nginx:1.19.3-alpine
    

    这里是使用centos:7.8.2003作为一个数据卷容器

  • 在宿主机中创建挂载目录

    mkdir /data/mountFile
    
  • 为挂载目录授权

    chown -R 777 /data/mountFile/
    
  • 启动父镜像并挂载到宿主机

    docker run -d --name data-volume -v /data/mountFile:/usr/nginx centos:7.8.2003
    

    可以发现,这个父容器虽然启动了,但是它并没有执行,这在官网也有解释,可以不用管

    创建好的数据卷容器是处于停止运行的状态,因为使用 —volumes-from 参数所挂载数据卷的容器 自己并不需要保持在运行状态

  • 启动子容器nginx1

    创建nginx1,让它继承(关键字: --volumes-from)data-volume 容器

    docker run -itd --name nginx1 -p 80:80 --volumes-from data-volume nginx:1.19.3-alpine
    

  • 进入容器

    docker exec -it nginx1 /bin/sh
    

    docker exec -it nginx1 /bin/bash会报错

  • 在nginx1中创建一个index.html文件

    echo "Edwin nginx" > /usr/nginx/index.html
    
  • 退出容器

    exit
    
  • 查看宿主机中是否存在index.html

    cd /data/mountFile
    

    发现在容器nginx1中创建的文件,在宿主机的挂载目录下也有同步了过来

  • 启动子容器nginx2

    创建nginx2,让它继承(关键字: --volumes-from)data-volume 容器

    docker run -itd --name nginx2 -p 81:80 --volumes-from data-volume nginx:1.19.3-alpine
    
  • 进入容器nginx2

    docker exec -it nginx2 /bin/sh
    
  • 在nginx2中创建一个index2.html文件

    echo "Edwin nginx2" > /usr/nginx/index2.html
    
  • 退出容器nginx2

    exit
    
  • 查看宿主机中是否存在index2.html

    cd /data/mountFile
    ls
    

    进入容器中,发现文件同步(实现文件共享)