docker存储

一、docker volume类型

1.1 docker分层结构

容器是由最上面可读可写的容器层,以及若干个只读镜像层组成,创建容器时,容器中的数据都来自镜像层。这样的分层结构最大的特点是写时复制:

  1. 容器中新生成的数据会直接存放在容器层,也可以称之为可写层。

  2. 修改容器中现有的数据会先从镜像层将数据复制到容器层,修改后的数据就会直接保存在容器层,而镜像层数据不会有任何变化。

  3. 如果多个镜像层中有相同的命名文件,在容器层只会看到最上面镜像层的内容。

1.2 联合挂载技术

Docker镜像采用这种分层构建设计,能使镜像结构和容器的创建,共享和分发变得非常高效,每个镜像层可以称之为layer,这些layer被存放在了/var/lib/docker/volume/<storage-driver>/目录下,这里的storage-driver可以有很多种,比如AUFS、OverlayFS、VFS、Brtfs等。可以通过docker info命令查看存储驱动,通常Ubuntu类的系统默认采用的是AUFS;CentOS7.1+系列采用的是OverlayFS。

OverlayFS是一种堆叠文件系统,它依赖并建立在其他的文件系统之上(例如ext4和xfs等待),并不直接参与磁盘空间结构的划分,仅仅将原来底层文件系统中不同的目录进行合并,然后向用户呈现,这也就是联合挂载技术。

1.3 volume类型

默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着,当该容器不再存在时,数据将不会持久保存,并且如果另一个进程需要它,则可能很难从容器中取出数据。为了能够保存持久化数据,Docker提出了volume的概念。

简单来说,volume就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于Docker Host文件系统上。Docker为容器提供了两个选项来将文件存储在主机中,以便即使容器停止后文件也可以持久存储。

Volume类型:

  • Bind mounts 宿主机---->容器

  • Docker managed volume 容器---->宿主机

1.3.1 bind mounts

Bind mounts只需要在创建容器时,使用-v参数指明docker主机目录或文件和容器目录或文件映射的对应关系,将本地的数据映射到容器内,可以使用多个-v映射多个目录或文件,还需要主义的是目录只能映射目录,文件只能映射文件,不然会报错。示例如下

 [root@ldh ~]# mkdir /html -v
 mkdir: created directory ‘/html’
 ​
 [root@ldh ~]# echo "Hello World" > /html/index.html
 ​
 [root@ldh ~]# cat /html/index.html 
 Hello World
 [root@ldh ~]# docker run -itd --name nginx -p 80:80 -v /html:/usr/share/nginx/html nginx:latest 
 d77a21cf75515e7340c11b43cd6d8803512948b584807e59c8fc60b6d556e483
 ​
 # 验证
 [root@ldh ~]# curl http://192.168.100.5
 Hello World

当docker主机上的数据发生了变化,容器中的数据也会随着发生变化,示例如下

 [root@ldh ~]# echo "Hello Hello" > /html/index.html 
 ​
 [root@ldh ~]# curl http://192.168.100.5
 Hello Hello

就算把容器删除,也不会影响到docker主机上的数据,示例如下

 [root@ldh ~]# docker ps -a
 CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                               NAMES
 d77a21cf7551   nginx:latest   "/docker-entrypoint.…"   3 minutes ago   Up 3 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   nginx
 ​
 [root@ldh ~]# docker rm nginx -f
 nginx
 ​
 [root@ldh ~]# docker ps -a
 CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
 ​
 [root@ldh ~]# ls /html/
 index.html
 ​
 [root@ldh ~]# cat /html/index.html 
 Hello Hello
 ​

此外,Bind mounts还可以指定数据的读写权限,默认权限为可读可写。在映射volume时,指定容器中的文件权限为只读,那么在容器那日该文件只能被读取,无法修改,这样也提高了容器的安全性。示例如下

 [root@ldh ~]# docker run -itd --name nginx -p 80:80 -v /html:/usr/share/nginx/html:ro nginx:latest 
 f443b371fa208e9be39c3cca301d43f0a37d05d65f324aea3aca28679e446b32
 ​
 [root@ldh ~]# docker exec -it nginx /bin/bash
 ​
 root@f443b371fa20:/# cd /usr/share/nginx/html/
 ​
 root@f443b371fa20:/usr/share/nginx/html# ls
 index.html
 ​
 root@f443b371fa20:/usr/share/nginx/html# echo 123 > index.html 
 bash: index.html: Read-only file system
1.3.2 docker managed volume

第二种volume类型是由Docker管理的volume,与Bind mounts相比,Docker管理的volume在使用时不需要指定docker主机路径,Docker管理的volume具有以下几个优点:

  1. 与Bind mounts相比,Docker管理的volume更易于备份或迁移。

  2. 用户可以使用Docker CLI命令或Docker API管理volume。

  3. volume在Linux和Windows容器上均可工作。

  4. 可以在多个容器之间更安全地共享volume,提供volume加密功能。

  5. 可以通过容器序言填充新volume的内容。

示例:通过-v告诉Docker需要一个volume,并将其mount到/usr/share/nginx/html目录,那么docker会在/var/lib/docker/volumes/目录下创建一个volume

 [root@ldh ~]# docker run -itd --name nginx -p 80:80 -v /usr/share/nginx/html nginx:latest 
 3f85b8b07489140500a19a51776d5d3538f643ffac1d6559a5ed7735174eea18
 [root@ldh ~]# docker container inspect nginx
 ......
    "Mounts": [
             {
                 "Type": "volume",
                 "Name": "c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35",
                 "Source": "/var/lib/docker/volumes/c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35/_data",
                 "Destination": "/usr/share/nginx/html",
                 "Driver": "local",
                 "Mode": "",
                 "RW": true,
                 "Propagation": ""
             }
         ],
 ......
 [root@ldh ~]# cd /var/lib/docker/volumes/c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35/_data
 ​
 [root@ldh _data]# ls
 50x.html  index.html

可以看到docker主机上的/var/lib/docker/volumes/c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35/_data目录以及挂在到容器中/usr/share/nginx/html目录下,但是查看docker主机的volume中的内容,是容器中的数据。也就是说使用Docker管理的volume,在做映射时,会把容器中的数据映射到docker主机中。

修改volume中的数据,再访问,发现volume中修改的数据会同步到容器中。

 [root@ldh _data]# echo "Hello World" > index.html 
 ​
 [root@ldh _data]# curl http://192.168.100.5
 Hello World

我们还可以使用docker volume命令查看docker管理的有哪些volume,并且还可以创建volume,示例如下

 # 查看docker管理的volume
 [root@ldh ~]# docker volume ls
 DRIVER    VOLUME NAME
 local     c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35
 # 创建一个volume,名为test,在创建新的容器时,可以直接指定使用该volume
 ​
 # 创建test卷
 [root@ldh ~]# docker volume create test
 test
 ​
 [root@ldh ~]# docker volume ls
 DRIVER    VOLUME NAME
 local     c717c635bb52251d3921eddd42a5d17bacb8b823bbb39d7c5df0e772c1987e35
 local     test
 ​
 # 创建新容器使用test卷
 [root@ldh ~]# docker run -itd --name testnginx -p 8081:80 -v test:/usr/share/nginx/html nginx:latest 
 0d66ec2c2ede5ff5943f856b12129458d5e181b692038dc4a39284671f44eb65
 ​
 [root@ldh ~]# docker container inspect testnginx
 ......
 "Mounts": [
             {
                 "Type": "volume",
                 "Name": "test",
                 "Source": "/var/lib/docker/volumes/test/_data",
                 "Destination": "/usr/share/nginx/html",
                 "Driver": "local",
                 "Mode": "z",
                 "RW": true,
                 "Propagation": ""
             }
         ],
 ......
1.3.3 两种volume的不同点

在使用docker volume命令创建volume时,volume默认都是存放在本地的,实际上本地的volume就是一个目录。除此之外Docker还支持在外部存储系统上创建volume,比如:NFS、Ceph等。

两种volume的相同点都是使用Docker主机文件系统中的某个路径。

两种volume的不同点如下表所示:

不同点Bind mountsDocker managed volume
volume位置可以任意指定/var/lib/docker/volumes/...
对已有mount point影响隐藏并替换为volume原有数据复制到volume
是否支持单个文件支持不支持
权限控制可设置为只读,默认为读写无控制,均为读写权限
移植性移植性弱,与主机路径绑定移植性弱,无需指定主机目录

二、容器之间数据共享

实现容器间数据共享的方法如下:

  1. 第一种方法是将共享数据放在volume中,然后将其mount到多个容器。

  2. 第二种方法是使用volume container,volume container是用来为其他容器提供volume的容器。这样可以提高容器的移植性。

  3. 第三种方法是将数据打包到镜像中,然后通过--volumes-from共享

2.1 方法一

第一种方法是将共享数据放在volume中,然后将其mount到多个容器。示例如下,创建三个nginx容器组成的web集群,使用相同的html文件。在使用-v指定volume时,可以是docker主机的路径或者是docker创建的volume。

 [root@ldh ~]# cat /html/index.html 
 Hello Hello
 [root@ldh ~]# docker run -itd --name web1 -p 8081:80 -v /html:/usr/share/nginx/html nginx:latest 
 09f4bdacdf40b7c2d5da11ee0ef89f9bae152a7fdcfe6f49e43383ce31fa11bc
 ​
 [root@ldh ~]# docker run -itd --name web2 -p 8082:80 -v /html:/usr/share/nginx/html nginx:latest 
 b2356ab39801ee25ce9458b7b1160211933c31b52905252c45e522a311691067
 ​
 [root@ldh ~]# docker run -itd --name web3 -p 8083:80 -v /html:/usr/share/nginx/html nginx:latest 
 407653d82ec1d9ddd0818b84dcf608cd6dbb3e38ee8d9529c86e7f935157c2d0
 ​
 [root@ldh ~]# curl http://192.168.100.5:8081
 Hello Hello
 ​
 [root@ldh ~]# curl http://192.168.100.5:8082
 Hello Hello
 ​
 [root@ldh ~]# curl http://192.168.100.5:8083
 Hello Hello

2.2 方法二

第二种方法是使用volume container,volume container是用来为其他容器提供volume的容器。这样可以提高容器的可移植性。

创建volume container,容器名为ldh_data,使用-v指定了volume,提供html。需要注意的是,这里使用的是docker create创建的容器,也就是说容器只是创建了,并未启动,因为volume container只是提供数据,自身并不需要运行。

 [root@ldh ~]# cat /html/index.html 
 Hello Hello
 [root@ldh ~]# docker create --name ldh_data -v /html:/usr/share/nginx/html busybox:latest
 33da6312c5bf2143c0dc441f1ca6235a703b8edf784d8e207cbd7e9a12de8ea1
 ​
 [root@ldh ~]# docker run -itd --name nginx1 -p 8081:80 --volumes-from ldh_data nginx:latest 
 4b8dd9db236ad4e2ac0439ecaa08dc6fa0351f46078b8f571aa2f6d709e8fd08
 ​
 [root@ldh ~]# docker run -itd --name nginx2 -p 8082:80 --volumes-from ldh_data nginx:latest 
 9942d50a85875b189a1d9933206147b97656a20d6a2c368733770aa5c52c39f4
 ​
 [root@ldh ~]# docker run -itd --name nginx3 -p 8083:80 --volumes-from ldh_data nginx:latest 
 c63e422676c0cd86519919d423bcd06de9cecfc2cbf339efe89d724128ee0c2c
 [root@ldh ~]# curl http://192.168.100.5:8081
 Hello Hello
 ​
 [root@ldh ~]# curl http://192.168.100.5:8082
 Hello Hello
 ​
 [root@ldh ~]# curl http://192.168.100.5:8083
 Hello Hello
 ​

与第一种实现数据共享的方法相比,不必再为每个容器指定volume,所有volume都在volume container中定义好了,容器只需与volume container关联,实现了容器与Docker主机的解耦。

2.3 方法三

volume container的数据归根到底还是在docker主机里,对容器的可移植性有一定的限制。这里可以使用data-packed volume container。其原理是将数据打包到镜像中,然后通过--volumes-from共享。

示例如下,使用Dockerfile构建镜像,将docker主机中的数据进行打包。ADD指令是将html目录中的数据添加到容器目录/usr/share/nginx/html中。VOLUME指令的作用与-v效果相同,用来创建volume,并且会把/usr/share/nginx/html目录中的数据复制到volume中。

 [root@ldh ~]# mkdir html -v
 mkdir: created directory ‘html’
 [root@ldh ~]# echo "Hello World" > html/index.html
 [root@ldh ~]# vim Dockerfile
 FROM busybox:latest
 ADD html /usr/share/nginx/html
 VOLUME /usr/share/nginx/html
 CMD ["/bin.bash"]
 [root@ldh ~]# docker build -t ldhtest .
 [+] Building 0.1s (7/7) FINISHED    docker:default
  => [internal] load build definition from Do  0.0s
  => => transferring dockerfile: 135B          0.0s
  => [internal] load metadata for docker.io/l  0.0s
  => [internal] load .dockerignore             0.0s
  => => transferring context: 2B               0.0s
  => [internal] load build context             0.0s
  => => transferring context: 82B              0.0s
  => [1/2] FROM docker.io/library/busybox:lat  0.0s
  => [2/2] ADD html /usr/share/nginx/html      0.0s
  => exporting to image                        0.0s
  => => exporting layers                       0.0s
  => => writing image sha256:50935064363accc5  0.0s
  => => naming to docker.io/library/ldhtest    0.0s

使用新构建的镜像创建data-packer volume container,创建新的nginx容器,使用参数--volumes-from指定使用data-packer volume提供的volume。

 [root@ldh ~]# docker create --name ldh_data ldhtest:latest 
 9b0a15166b137f3e878272b1b2bbc5ad2d9e2ae89ca9a9a564cc78bb83f87787
 ​
 [root@ldh ~]# docker run -itd --name nginx -p 80:80 --volumes-from ldh_data nginx:latest 
 7389b4c757269518661d67b516367f519ec3e18116df4ca0aaa8c7c3f2144d64
 [root@ldh ~]# curl http://192.168.100.5
 Hello World

容器能够正确读取volume中的数据。data-packed volume container是自包含的,不依赖docker主机提供数据,具有很强的移植性,非常适合使用静态数据的场景,比如应用的配置信息、web server的静态文件等。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐