存储卷
就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。这就意味着,当我们在容器中的这个目录下写入数据时,容器会将其内容直接写入到宿主机上与此容器建立了绑定关系的目录。在宿主机上的这个与容器形成绑定关系的目录被称作存储卷。卷的本质是文件或者目录,它可以绕过默认的联合文件系统,直接以文件或目录的形式存在于宿主机上。
宿主机的 /data/web
目录与容器中的 /container/data/web
目录绑定关系,然后容器中的进程向这个目录中写数据时,是直接写在宿主机的目录上的,绕过容器文件系统与宿主机的文件系统建立关联关系,使得可以在宿主机和容器内共享数据库内容,让容器直接访问宿主机中的内容,也可以宿主机向容器写入内容, 容器和宿主机的数据读写是同步的。
生活案例
存储卷就相当于租了个地下室,建立对应的映射就相当于有了这个地下室的钥匙,能够使用房子以外的空间。
如果有一天龙卷风来了,房子被吹毁了,但是地下室依然安然无恙,就相当于容器销毁了我们的这个地下室依然没有影响。
显然,容器更擅长无状态应用。因为未持久化数据的容器根目录的生命周期与容器的生命周期一样,容器文件系统的本质是在镜像层上面创建的读写层,运行中的容器对任何文件的修改都存在于该读写层,当容器被删除时,容器中的读写层也会随之消失。
虽然容器希望所有的业务都尽量保持无状态,这样容器就可以开箱即用,并且可以任意调度,但实际业务总是有各种需要数据持久化的场景,比如 MySQL、 Kafka 等有状态的业务。因此为了解决有状态业务的需求, Docker 提出了 卷(Volume)
的概念。
目前 Docker 提供了三种方式将数据从宿主机挂载到容器中
volume
docker 管理卷,默认映射到宿主机的 /var/lib/docker/volumes
目录下, 只需要在容器内指定容器的挂载点是什么,而被绑定宿主机下的那个目录,是由容器引擎 daemon 自行创建一个空的目录,或者使用一个已经存在的目录,与存储卷建立存储关系,这种方式极大解脱用户在使用卷时的耦合关系,缺陷是用户无法指定那些使用目录,临时存储比较适合。bind mount
绑定数据卷,映射到宿主机指定路径下,在宿主机上的路径要人工的指定一个特定的路径, 在容器中也需要指定一个特定的路径, 两个已知的路径建立关联关系。tmpfs mount
临时数据卷,映射到于宿主机内存中,一旦容器停止运行, tmpfs mounts 会被移除,数据就会丢失,用于高性能的临时数据存储。存储卷可以通过命令方式创建,也可以在创建容器的时候通过 -v
and --mount
指定。
命令清单如下:
docker volume create
创建存储卷
docker volume create [OPTIONS] [VOLUME]
-d, --driver
: 指定驱动,默认是 local--label
: 指定元数据# 创建匿名卷
docker volume create
# 遍历卷
docker volume ls
# 查看卷的详细信息
docker volume inspect 1a9eb041a8b3b0143b6a58e0470366dfaf6c67ef05715a6d907d00b24e059193
docker volume create myvoltest1
docker volume create --label MYTEST=1 myvoltest2
docker volume inspect
查看卷详细信息
docker volume inspect [OPTIONS] VOLUME [VOLUME...]
-f
:指定相应个格式,如jsondocker volume inspect myvotest3 myvotest4
docker volume ls
列出卷
docker volume ls [OPTIONS]
--format
: 指定相应个格式,如 json,table--filter,-f
: 过滤-q
: 仅显示名称#列出所有存储卷
docker volume ls
#指定存储卷格式
docker volume ls --format json
#过滤存储卷
docker volume ls -f label=MYTEST
#仅显示存储卷名称
docker volume ls -q
docker volume rm
删除卷,需要容器不使用
docker volume rm [OPTIONS] VOLUME [VOLUME...]
-f,--force
:强制删除# 删除存储卷
docker volume rm myvotest7 myvotest8
#写文件到宿主机目录下
echo "123" > /data/var/lib/docker/volumes/myvotest7/_data/1.txt
docker volume prune
删除不使用的本地卷(匿名卷)
docker volume prune [OPTIONS]
--filter
:过滤-f, --force
:不提示是否删除docker volume prune
-v 和-mount 都可以完成管理卷的创建。
-v参数
:完成目录映射
docker run -v name:directory[:options] .........
docker run -d --name myvolnginx1 -v volnginx1:/usr/share/nginx/html/ nginx:1.24.0
docker exec -it myvolnginx1 bash
cd /usr/share/nginx/html/
rm index.html
ls /data/var/lib/docker/volumes/volnginx1/_data
如果指定ro选项,那么容器中的文件是不能删除的。
--mount 参数
完成目录映射
--mount '=,='
type
: 类型表示 bind, volume, or tmpfssource
, src :对于命名卷,这是卷的名称。对于匿名卷,省略此字段destination, dst,target
:文件或目录挂载在容器中的路径ro,readonly
: 只读方式挂载创建命名卷
docker run -d --name mynginxvol3 --mount 'src=nginxvol3,dst=/usr/share/nginx/html' nginx:1.24.0
创建匿名卷
docker run -d --name mynginxvol4 --mount 'dst=/usr/share/nginx/html' nginx:1.24.0
通过 Dockerfile 的 VOLUME 可以创建 docker 管理卷。这个我们后续在 Dockerfile中详细讲解。
我们也可以通过 dockerfile
的 VOLUME
指令在镜像中创建 Data Volume,这样只要通过该镜像创建的容器都会存在挂载点,但值得注意的是通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,而是由 docker 随机生成的。
docker volume create test1
docker run -d --name mynginx1 -p 8045:80 -v test1:/usr/share/nginx/html nginx:1.24.0
docker inspect mynginx1
docker run -d --name mynginx2 -v test2:/usr/share/nginx/html/:ro -p 8046:80 nginx:1.24.0
docker exec -it mynginx2 bash
cd /usr/share/nginx/html
echo "hello from -v" > index.html
docker run -d --name mynginx3 -p 8047:80 --mount src=test3,dst=/usr/share/nginx/html/ nginx:1.24.0
docker run -d --name mynginx3 -p 8047:80 --mount src=test3,dst=/usr/share/nginx/html/ nginx:1.24.0
#删除存储卷
docker volume rm test3
docker run -d --name mynginx51 -p 8052:80 -v test4:/usr/share/nginx/html/ nginx:1.24.0
docker run -d --name mynginx52 -p 8053:80 -v test4:/usr/share/nginx/html/ nginx:1.24.0
docker run -d --name mynginx53 -p 8054:80 -v test4:/usr/share/nginx/html/ nginx:1.24.0
cd /data/var/lib/docker/volumes/test4/_data
同时,浏览器中也发生了改变。
docker run -v name:directory[:options] .........
docker run -d --name mynginx6 -v /data/cjl/testbind:/usr/share/nginx/html/ nginx:1.24.0
docker inspect mynginx6
在宿主机上进行修改看容器中是否会发生改变
--mount '=,='
type
: 类型表示 bind, volume, or tmpfssource , src
: 宿主机目录,这个和管理卷是不一样的。destination, dst,target
:文件或目录挂载在容器中的路径ro,readonly
: 只读方式挂载docker run -d --name mynginx7 --mount type=bind,src=/data/cjl/testmymountbind,dst=/usr/share/nginx/html/ nginx:1.24.0
在容器中创建文件,看是否会同步到宿主机中。
docker exec -it mynginx7 bash
echo "Hello bind by mount!" > index.html
宿主机中目录不存在时,绑定卷会失败。
docker run -d --name mynginx8 --mount type=bind,src=/data/cjl/testbindmount1/,dst=/usr/share/nginx/html nginx:1.24.0
宿主机中目录存在时,绑定卷成功。
docker run -d --name mynginx8 --mount type=bind,src=/data/cjl/testbindmount2/,dst=/usr/share/nginx/html nginx:1.24.0
如果宿主机中目录不存在,-v方式可以创建
docker run -d --name mynginx9 -v /data/cjl/testbindmount3:/usr/share/nginx/html/ nginx:1.24.0
如果宿主机和容器中有同样名称的文件,会以宿主机中为主
echo "test -v volume" > index.html
docker run -d --name mynginx10 -v /data/cjl/testbindmount4:/usr/share/nginx/html/ nginx:1.24.0
启动两个nginx容器,并绑定到同一个宿主机目录下
docker run -d -p 8061:80 --name mynginx11 -v /data/cjl/testbindmount5/:/usr/share/nginx/html nginx:1.24.0
docker run -d -p 8062:80 --name mynginx12 -v /data/cjl/testbindmount5/:/usr/share/nginx/html nginx:1.24.0
临时卷数据位于内存中,在容器和宿主机之外。
tmpfs 局限性
--tmpfs /app
docker run -d --name mynginx15 --tmpfs /test1 nginx:1.24.0
--mount '=,='
type
: 类型表示 bind, volume, or tmpfsdestination, dst,target
:挂载在容器中的路径tmpfs-size
: tmpfs 挂载的大小(以字节为单位)。默认无限制。tmpfs-mode
: tmpfs 的八进制文件模式。例如, 700 或 0770。默认为 1777或全局可写。docker run -d --name mynginx16 --mount type=tmpfs,dst=/test2 nginx:1.24.0
docker run -d --name mynginx17 -p 8078:80 --tmpfs /usr/share/nginx/html/ nginx:1.24.0
重启容器后,nginx首页文件将不存在
docker run -d --name mynginx18 -p 8092:80 --mount type=tmpfs,dst=/usr/share/nginx/html/,tmpfs-size=1m nginx:1.24.0
当我们创建临时卷时指定挂载的大小时,如果有超过该大小的文件拷贝进入容器中不被允许。
该案例主要要求我们掌握临时卷的创建方式,了解临时卷的特殊存储方式
运行一个容器,并在容器中创建一个文件,回到宿主机查找该文件,可以找到并且和容器中的一模一样。
docker run -d --name mynginx19 nginx:1.24.0
创建容器并绑定临时卷,在容器中对应的目录下创建标记文件,发现在宿主机中找不到该文件
docker run -d --name mynginx20 --mount type=tmpfs,dst=/test/ nginx:1.24.0
实战目的
掌握挂载卷的使用,将 mysql 的业务数据存储到外部
实战步骤
使用 MySQL 5.7 的镜像创建容器并创建一个普通数据卷 mysql-data 用来保存容器中产生的数据。需要在容器中连接 MySQL 服务, 并创建数据库 test, 并在在该数据库中创建一个简单的表并插入一些数据进来。
docker run --name mysql2 -v /data/cjl/mysql2test:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
#在容器中登录mysql
mysql -h localhost -uroot -p
docker run --name mysql2new -v /data/cjl/mysql2test:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
因此,存储卷只要我们不是人工删除,它在容器删除后也不会自动销毁。