volume用于将容器内的数据在本地进行持久化,这一节我们以redis为例用实际操作来看看几种创建volume的方式。


文章目录

  • 操作环境
  • 原始镜像
  • 声明volume
  • 创建volume
  • 指定容器目录不指定宿主机目录
  • 指定容器目录指定宿主机目录
  • 总结


操作环境

  • Centos 7.9.2009
  • Docker version 20.10.2, build 2291f61

原始镜像

直接下载最新的redis镜像

docker pull redis
[root@testmachine fuhanxiao]# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
redis        latest    621ceef7494a   13 days ago   104MB

然后直接启动

[root@testmachine fuhanxiao]# docker run -p 6379:6379 -d redis
1ab8246b84bc23afbd8348deecd74e4260d457e6dd987917bec8b1ddb3f1afdf

需要注意的是因为做了端口映射,必须要满足下面两个

  • 必须要有firewalld或者是iptables服务在跑,默认centos7使用的是firewalld,systemctl status firewalld
  • 必须要打开ip forward,echo 1 > /proc/sys/net/ipv4/ip_forward

查看下容器信息

[root@testmachine fuhanxiao]# docker inspect 1ab
...
...
        "Mounts": [
            {
                "Type": "volume",
                "Name": "d31bb6212a374dd34a93a992274f3a522cd1555c554ccf497277c3c89a668f5b",
                "Source": "/var/lib/docker/volumes/d31bb6212a374dd34a93a992274f3a522cd1555c554ccf497277c3c89a668f5b/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
...
...

可以看到原始镜像配置了一个volume,Destination对应的是容器内的目录/dataSource对应的是宿主机的目录,这里的目录名是一个随机数。

这其实是镜像内暴露了一个volume出来,但是起容器的时候没有声明本地对应的目录,所以docker就自己新建了一个目录作为对应的volume,下面我还会用自己新建的volume对这一理论进行验证。

声明volume

下面我们在原始镜像的基础上开始自己的实验。

创建Dockerfile内容如下

FROM redis
COPY ./redis.conf /usr/local/etc/redis/redis.conf
VOLUME /usr/local/etc/redis
CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ]

主要是这里的第三行,通过VOLUME关键字将容器内部的一个目录声明成一个volume。需要注意的是Dockfile里面只能声明容器内部的目录,因为生成的镜像可能会被运行在各种设备上,所以宿主机的目录一定是在起容器的时候指定和生成的

同时这里还拷贝了一份本地的配置文件到容器内,作为新镜像的启动配置,下面我们就用这个文件来进行测试。

生成新的镜像

[root@testmachine myredis]# docker build -t redis:v2 .
Sending build context to Docker daemon  190.5kB
Step 1/4 : FROM redis
 ---> 621ceef7494a
Step 2/4 : COPY ./redis.conf /usr/local/etc/redis/redis.conf
 ---> fdd3cb20855c
Step 3/4 : VOLUME /usr/local/etc/redis
 ---> Running in a95a0f7fad1c
Removing intermediate container a95a0f7fad1c
 ---> 884d688cb230
Step 4/4 : CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
 ---> Running in 9badfe62c79d
Removing intermediate container 9badfe62c79d
 ---> ca75e474ccf3
Successfully built ca75e474ccf3
Successfully tagged redis:v2
[root@testmachine myredis]# docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
redis        v2        ca75e474ccf3   10 minutes ago   104MB
redis        latest    621ceef7494a   2 weeks ago      104MB

下面我们用这个新的v2镜像来测试。

创建volume

指定容器目录不指定宿主机目录

如上面看到的那样,指定了容器的目录但是不指定宿主机目录的话,docker会自动在宿主机上创建对应的目录。

其中指定容器目录的方式有两种:

通过命令行

[root@testmachine myredis]# docker run -p 6379:6379 -v /usr/local/etc/redis -d redis
af3aeeb1071d3fd2470fa3174803fc73c0872033f5e2e44c25144368f0281aa3

注意这里用的镜像是默认镜像redis,并不是redis:v2,然后通过-v参数声明了容器内的一个目录作为volume。

注意,为了避免错误,volume的目录路径最好是完整路径

下面查看下容器绑定的volume

[root@testmachine myredis]# docker inspect af3
...
...
        "Mounts": [
            {
                "Type": "volume",
                "Name": "b7b0151ded2be4f71dab89ef1ce5289be2785917106d71efe35a0ca2e07aadf8",
                "Source": "/var/lib/docker/volumes/b7b0151ded2be4f71dab89ef1ce5289be2785917106d71efe35a0ca2e07aadf8/_data",
                "Destination": "/usr/local/etc/redis",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "d9b95843b00e4f4861668b79dcd8261b78a233c2c5ee4a588a664e4950730361",
                "Source": "/var/lib/docker/volumes/d9b95843b00e4f4861668b79dcd8261b78a233c2c5ee4a588a664e4950730361/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
...
...

可以看到docker同样创建了一个随机目录,进入这个目录,新建一个文件写点内容

[root@testmachine myredis]# cd /var/lib/docker/volumes/b7b0151ded2be4f71dab89ef1ce5289be2785917106d71efe35a0ca2e07aadf8/_data
[root@testmachine _data]# echo 123 > test.txt

然后去容器内就马上能看到了

[root@testmachine fuhanxiao]# docker exec -it af3 /bin/bash
root@af3aeeb1071d:/data# cd /usr/local/etc/redis/
root@af3aeeb1071d:/usr/local/etc/redis# cat test.txt
123

通过镜像写死

第二种方式就像Dockerfile那样写死一个目录,然后直接起容器就可以了

[root@testmachine fuhanxiao]# docker run -p 6379:6379 -d redis:v2
63a0edc424287c02c6ac57f7a81e390bba6ba7a5e45051dc6f48839cde3f5ce6

查看绑定和上面类似,除了路径名不同,就不重复验证了。

指定容器目录指定宿主机目录

docker随机创建目录显然不太利于数据和文件的管理,所以最好还是认为指定宿主机的目录。值得一提的是,指定宿主机目录一定要有对应的容器目录,不管这个容器目录在Dockerfile有没有声明过都可以

[root@testmachine fuhanxiao]# docker run -p 6379:6379 -v /home/fuhanxiao/myredis/v2:/usr/local/etc/redis -d redis:v2
612734ee9c1a0291d70f408867ef7333f8d0840cd3f461297b3dd9a872f08389

这里我在v2目录下也弄了一个配置文件,容器起的时候会将宿主机的内容复制到容器内,但是注意容器起了以后两边不管那边有修改都能双向同步

注意,如果宿主机的配置文件有错误会导致容器起不来,如果宿主机的目录是空的容器起来之后volume的目录也会是空的

总结

总结下相关的知识点:

  • 指定容器内的路径可以在镜像内写死或者是起容器时候指定
  • 指定宿主机的路径只能在起容器时指定,而且必须和容器内路径一起指定
  • 用绝对路径
  • 起容器的时候会将宿主机的内容拷贝同步到容器内(docker建立的随机目录不算),之后是双向同步
  • 内容的更新都是实时热更新

我是T型人小付,一位坚持终身学习的互联网从业者。