数据管理

参考文档

容器数据的目录分层

  • **Lower Dir:**镜像层,即镜像本身,只读层
  • **Upper Dir:**容器的上层,容器中变化的文件存放于此处,读写层
  • **Merged Dir:**容器的文件系统,使用的是联合文件系统(Union FS) 将 lowerdir 和 upperdir 合并完成后给容器使用,最终呈现给用户的统一视图
  • **WorkDir:**容器在宿主机的工作目录,在使用过程中其内容用户不可见,挂载后内容会被清空

查看容器的数据分层

  • docker inspect name (GraphDriver 字段)

范例一

未有数据修改前

# docker history busybox:stable 
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
334e4a014c81   10 days ago   /bin/sh -c #(nop)  CMD ["sh"]                   0B        
<missing>      10 days ago   /bin/sh -c #(nop) ADD file:ac7a70cec126c0804…   4.86MB    


# docker run -d --name busybox -d busybox:stable tail -f /etc/hosts
# docker inspect busybox
...
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1-init/diff:/var/lib/docker/overlay2/08c35ade32977f0f78381997288ef5b0842432a283cdb2e2a3acfddd3d98874c/diff",
                "MergedDir": "/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/merged",
                "UpperDir": "/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/diff",
                "WorkDir": "/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/work"
            },
            "Name": "overlay2"
        },

...


# LowerDir,因为镜像分了两层,所以产生了两个LowerDir。
# ls /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1-init/diff
dev  etc  proc  sys
# ls /var/lib/docker/overlay2/08c35ade32977f0f78381997288ef5b0842432a283cdb2e2a3acfddd3d98874c/diff
bin  dev  etc  home  lib  lib64  root  tmp  usr  var
# cat /var/lib/docker/overlay2/08c35ade32977f0f78381997288ef5b0842432a283cdb2e2a3acfddd3d98874c/diff/etc/localtime 
TZif2UTCTZif2UTC
UTC0


# UpperDir,读写层没有数据修改,因此没有数据。
# ls /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/diff


# MergedDir,将所有LowerDir和UpperDir合并到一起
# ls /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/merged
bin  dev  etc  home  lib  lib64  proc  root  sys  tmp  usr  var


# WorkDir,没有数据,即使有数据,在容器中也是不可见的
# tree /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/work
/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/work
└── work

有数据修改后

# docker exec -it busybox sh
/ # dd if=/dev/zero of=/root/test.img bs=1M count=10
/ # ls -l /root/test.img -h
-rw-r--r--    1 root     root       10.0M Dec 17 15:32 /root/test.img
/ # echo 'UTC+8' >> /etc/localtime



# LowerDir,因为是只读层,所以无变化
# ls /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1-init/diff
dev  etc  proc  sys
# ls /var/lib/docker/overlay2/08c35ade32977f0f78381997288ef5b0842432a283cdb2e2a3acfddd3d98874c/diff
bin  dev  etc  home  lib  lib64  root  tmp  usr  var
# cat /var/lib/docker/overlay2/08c35ade32977f0f78381997288ef5b0842432a283cdb2e2a3acfddd3d98874c/diff/etc/localtime 
TZif2UTCTZif2UTC
UTC0


# UpperDir,读写层发生了数据修改
# tree /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/diff
/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/diff
├── etc
│   └── localtime # 从只读层复制过来并进行了数据添加(修改)
└── root
    └── test.img # 直接写入到了读写层
# cat /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/diff/etc/localtime
TZif2UTCTZif2UTC
UTC0
UTC+8



# MergedDir,因为有了Union FS的存在,所以看起来还是一个整体
# ls /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/merged
bin  dev  etc  home  lib  lib64  proc  root  sys  tmp  usr  var
# cat /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/merged/etc/localtime 
TZif2UTCTZif2UTC
UTC0
UTC+8


# WorkDir,没有数据,即使有数据,在容器中也是不可见的
# tree /var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/work
/var/lib/docker/overlay2/6f416501797d99899f520c77774448afe0c7ed20de23e968c927eb665b566be1/work
└── work

范例二

# docker history nginx:1.23 
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
3964ce7b8458   3 days ago    /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B        
<missing>      3 days ago    /bin/sh -c #(nop)  STOPSIGNAL SIGQUIT           0B        
<missing>      3 days ago    /bin/sh -c #(nop)  EXPOSE 80                    0B        
<missing>      3 days ago    /bin/sh -c #(nop)  ENTRYPOINT ["/docker-entr…   0B        
<missing>      3 days ago    /bin/sh -c #(nop) COPY file:e57eef017a414ca7…   4.62kB    
<missing>      3 days ago    /bin/sh -c #(nop) COPY file:abbcbf84dc17ee44…   1.27kB    
<missing>      3 days ago    /bin/sh -c #(nop) COPY file:5c18272734349488…   2.12kB    
<missing>      3 days ago    /bin/sh -c #(nop) COPY file:7b307b62e82255f0…   1.62kB    
<missing>      3 days ago    /bin/sh -c set -x     && addgroup --system -…   61.3MB    
<missing>      3 days ago    /bin/sh -c #(nop)  ENV PKG_RELEASE=1~bullseye   0B        
<missing>      3 days ago    /bin/sh -c #(nop)  ENV NJS_VERSION=0.7.9        0B        
<missing>      3 days ago    /bin/sh -c #(nop)  ENV NGINX_VERSION=1.23.3     0B        
<missing>      11 days ago   /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B        
<missing>      11 days ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      11 days ago   /bin/sh -c #(nop) ADD file:1f1efd56601ebc26a…   80.5MB  


# docker run -d --name nginx nginx:1.23
# docker inspect nginx
...
        "GraphDriver": {
            "Data": {
                # 因为镜像有很多分层,所以会产生很多的LowerDir(每个目录以":"为分隔符显示)
                "LowerDir": "/var/lib/docker/overlay2/94d22d89b8fa020dbb17290f5c5684000202ac1b58f634a0f0f70877ec16ad9c-init/diff:/var/lib/docker/overlay2/98d2ab81673615b7cf8aeb797dd146d518eb0bfde0d09df27c1b20e9f33cf8c0/diff:/var/lib/docker/overlay2/44a0f9233d0165e521c43c32cc08d747a2becfc0efbfcfe6ecd44ee7b4aac09c/diff:/var/lib/docker/overlay2/431b57061d42f526085599cee1e2e0d3a469d67679410ea0c2a3d7f1960f3b36/diff:/var/lib/docker/overlay2/a5370a8bc35cb3eea7a9f8f61f7288a55b57b01d4dcb37c3d855dd2a81a606ed/diff:/var/lib/docker/overlay2/58740eec574cd2e7ab620df0cccfa7a1c94cf0b2d5de8182547787712c8551d0/diff:/var/lib/docker/overlay2/5a6d1455634f092cede6488935884d64276fe8f7aeff2e73d8c8d8e18005a1ef/diff",
                "MergedDir": "/var/lib/docker/overlay2/94d22d89b8fa020dbb17290f5c5684000202ac1b58f634a0f0f70877ec16ad9c/merged",
                "UpperDir": "/var/lib/docker/overlay2/94d22d89b8fa020dbb17290f5c5684000202ac1b58f634a0f0f70877ec16ad9c/diff",
                "WorkDir": "/var/lib/docker/overlay2/94d22d89b8fa020dbb17290f5c5684000202ac1b58f634a0f0f70877ec16ad9c/work"
            },
            "Name": "overlay2"
        },
...

容器数据存放过程

  • docker 镜像是由多个只读层叠加而成,启动容器时,docker 会在只读层的基础上添加一个读写层(就是添加一个可读可写的文件系统),进而生成容器,用户写入的数据都保存在读写层中
  • 如果运行中的容器修改了一个现有已存在的文件:
    • 那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏
    • 本质上就是利用 写时复制机制(COW copy on write)
  • 如果运行中的容器生成了新的数据:
    • 那么新产生的数据将会被复制到读写层,进行持久化保存,这个读写层就是容器的工作目录
    • 本质上就是利用 写时复制机制(COW copy on write)

容器数据持久保存方式

  • 数据卷(Data Volume)
    • 直接将宿主机目录挂载至容器的指定目录,推荐使用此种方式
    • 常用
  • 数据卷容器(Data Volume Container)
    • 间接使用宿主机空间,将宿主机的目录挂载至一个专门的数据卷容器,然后让其他容器通过数据卷容器读写宿主机的数据
    • 不常用

数据卷

  • 相当于k8s中的hostPath?

数据卷的特点

  • 数据卷实际上就是宿主机上的目录或者是文件,可以被直接mount到容器当中使用

  • 数据卷是目录或者文件,并且可以在多个容器之间共同使用,实现容器之间共享和重用

  • 对数据卷更改数据在所有容器里会立即更新

  • 数据卷的数据可以持久保存,即使删除容器 数据卷中的数据也不会消失

  • 在容器中写入数据不会影响到镜像本身,即数据卷的变化不会影响镜像的更新

  • 依赖于宿主机目录,宿主机出现问题,上面的容器会受到影响,当宿主机较多时,不方便统一管理

  • 匿名和命名数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含了数据,会拷贝到新初始化的数据卷中

数据卷的使用场景

实际生产环境中,需要针对不同类型的服务、不同类型的数据存储要求做相应的规划,最终保证服务的可扩展性、稳定性以及数据的安全性

  • 数据库
  • 日志输出
  • 静态web页面
  • 应用配置文件
  • 多容器间目录或文件共享

数据卷的缺点

  • **使容器的迁移性较差:**如多个nginx使用数据卷共享配置文件,如果涉及到迁移 将会导致配置文件不能整体的迁移到其他主机

数据卷的类型

  • **目录数据卷:**将宿主机目录挂载容器目录

  • **命名卷:**指定数据卷的名称和容器路径的挂载关系

  • **匿名卷:**不指定数据卷名称,只指定容器内目录路径充当挂载点,docker会自动指定宿主机的路径进行挂载,默认方式

数据卷注意事项

  • 必须在构建镜像的Dockerfile中指定了VOLUME 才能使用数据卷,否则指定容器会出现无法启动等问题

数据卷的实现

语法

docker run -v [localhost-src:]container-dest[:option]

#:option
:ro #只读挂载,即容器内数据只读不能修改
:rw #读写挂载,即容器内数据可读可写,此为默认值

目录数据卷的实现

#将宿主机目录挂载容器目录,两个目录都可以自动创建
-v <宿主机绝对路径的目录或文件>:<容器内绝对路径的目录或文件>[:ro]

#范例:
docker run -d -p 80:80 --name nginx -v /data/nginx/html:/apps/nginx/html nginx

命名卷的实现

  • 命名卷的默认存放路径:/var/lib/docker/volumes/<卷名>/_data
-v <卷名>:<容器内目录路径>

#也可以通过以下命令事先创建命名卷,如事先未创建 docker run时也会自动创建
docker volume create <卷名>

#范例:
docker run -d -p 80:80 --name nginx -v vol1:/apps/nginx/html nginx

匿名卷的实现

  • 匿名卷的默认存放路径:/var/lib/docker/volumes/<卷ID>/_data
-v <容器内路径>

#范例:
docker run -d -p 80:80 --name nginx -v /apps/nginx/html nginx

数据卷的管理

docker rm -v container #加上-v,删除容器时,同时删除数据卷

#数据卷管理命令
Usage:  docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes

Run 'docker volume COMMAND --help' for more information on a command.

#范例:
docker inspect --format="{{.Mounts}}" <容器ID> #查看数据卷的挂载关系
docker volume rm `docker volume ls -q` #删除所有数据卷

数据卷实战案例

注意事项

  • 做映射的宿主机目录必须为空,否则将出现无法启动容器等问题

读写挂载目录数据卷

nginx使用目录数据卷

#临时启动容器观察网页主目录的存放位置
[root@docker ~]# docker run -it --rm  nginx:latest bash
root@2367c5428c7a:/# cat /etc/nginx/conf.d/default.conf 
...
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
...

#在宿主机准备所需目录和文件(不准备也可以,docker会自动创建)
[root@docker ~]# mkdir -p /data/docker/nginx/html
[root@docker ~]# echo 'test page on local host v1' > /data/docker/nginx/html/index.html

#启动容器并指定宿主机目录和容器目录的映射关系
[root@docker ~]# docker run -d -p 80:80 -v /data/docker/nginx/html/:/usr/share/nginx/html nginx:latest 

#测试访问
[root@docker ~]# curl 10.0.0.8
test page on local host v1

#在启动一个容器并指定同样的映射关系
[root@docker ~]# docker run -d -p 81:80 -v /data/docker/nginx/html/:/usr/share/nginx/html nginx:latest

#测试访问
[root@docker ~]# curl 10.0.0.8
test page on local host v1
[root@docker ~]# curl 10.0.0.8:81
test page on local host v1

#模拟版本升级(随意选择一个容器修改)
[root@docker ~]# docker exec -it 09d5d0c84a24 sh
# echo 'test page on local host v2' > /usr/share/nginx/html/index.htm

#测试访问(因为默认是读写挂载,所以在任何容器中都可以修改)
[root@docker ~]# curl 10.0.0.8
test page on local host v2
[root@docker ~]# curl 10.0.0.8:81
test page on local host v2

#宿主机文件也同时被修改
[root@docker ~]# cat /data/docker/nginx/html/index.html 
test page on local host v2

mysql使用目录数据卷

注意:

  • MySQL挂载数据卷时不能开启只读选项,否则数据库将无法写入数据
  • MySQL不进行数据卷挂载的话 容器删除后数据将丢失
#观察mysql的数据存放路径
[root@docker ~]# docker run -it --rm mysql:5.7.30 sh
# cat /etc/mysql/mysql.conf.d/mysqld.cnf
...
[mysqld]
pid-file	= /var/run/mysqld/mysqld.pid
socket		= /var/run/mysqld/mysqld.sock
datadir		= /var/lib/mysql #数据存放路径
#log-error	= /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address	= 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

#指定目录数据卷启动容器
[root@docker ~]# docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=12345 -v /data/docker/mysql:/var/lib/mysql mysql:5.7.30 

#观察宿主机是否生成文件
[root@docker ~]# ll /data/docker/mysql/
total 188484
...
-rw-r----- 1 systemd-coredump input 50331648 Jan 31 02:28 ib_logfile0
-rw-r----- 1 systemd-coredump input 50331648 Jan 31 02:28 ib_logfile1
-rw-r----- 1 systemd-coredump input 12582912 Jan 31 02:28 ibtmp1
drwxr-x--- 2 systemd-coredump input     4096 Jan 31 02:28 mysql
...

#在容器中创建数据库
[root@docker ~]# docker exec -it 61006 sh
# mysql -uroot -p12345
...
mysql> create database testdb;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| testdb             |
+--------------------+
5 rows in set (0.00 sec)

#在宿主机查看是否生成
[root@docker ~]# ll -tr /data/docker/mysql/
...
drwxr-x--- 2 systemd-coredump input       20 Jan 31 02:33 testdb

只读挂载目录数据卷

  • 默认数据卷为可读可写,加上ro选项,可以实现容器只读挂载
  • 对于不希望容器修改的数据 如:配置文件、脚本等 可以使用此方式挂载
  • 只读挂载后将无法在容器内直接修改挂载点的数据,但容器中非挂载点的数据仍可修改
[root@docker ~]# docker run -d -p 80:80 -v /data/docker/nginx/html/:/usr/share/nginx/html:ro nginx:latest
1c838d1381754a5c3cc7f62cedc7c802e2a9052ed3691f5e2479721aea7e832e

[root@docker ~]# docker exec -it 1c838d bash

root@1c838d138175:/# echo 'test page on local host v3' > /usr/share/nginx/html/index.html
bash: /usr/share/nginx/html/index.html: Read-only file system

文件数据卷

tomcat使用文件数据卷

  • 文件挂载用于很少更改文件内容的场景,如:nginx、tomcat的配置文件等
#同时实现三个数据卷的挂载,只读方式挂载文件数据卷,读写方式挂载目录数据卷

[root@docker ~]# docker run -d -v /data/bin/catalina.sh:/apps/tomcat/bin/catalina.sh:ro -v /data/testapp:/data/tomcat/webapps/testapp -v /data/lgos:/apps/tomcat/logs -p 8080:8080 tomcat-web:app1

匿名数据卷

  • 删除容器不会删除匿名数据卷,如要删除 需执行 docker volume rm 匿名卷ID

nginx实现匿名数据卷

#利用匿名数据卷创建容器
[root@docker ~]# docker run -d -p 80:80 --name nginx01 -v /usr/share/nginx/html nginx:latest 

#访问主页
[root@docker ~]# curl 10.0.0.8
...
<body>
<h1>Welcome to nginx!</h1>
...

#查看自动生成的匿名卷
[root@docker ~]# docker volume ls
DRIVER    VOLUME NAME
local     5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa

#查看自动生成的匿名卷详细信息
[root@docker ~]# docker volume inspect 5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa 
[
    {
        "CreatedAt": "2022-01-31T03:40:01+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa/_data",
        "Name": "5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa",
        "Options": null,
        "Scope": "local"
    }
]
[root@docker ~]# docker inspect --format="{{.Mounts}}" nginx01
[{volume 5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa /var/lib/docker/volumes/5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa/_data /usr/share/nginx/html local  true }]

#查看匿名卷的文件
[root@docker ~]# ls /var/lib/docker/volumes/5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa/_data
50x.html  index.html

#修改宿主机匿名卷的内容
[root@docker ~]# echo 'test page v1' > /var/lib/docker/volumes/5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa/_data/index.html 

#测试访问
[root@docker ~]# curl 10.0.0.8
test page v1

#删除容器
[root@docker ~]# docker rm -f nginx01 
nginx01

#删除容器后匿名卷不会随之删除
[root@docker ~]# docker volume ls
DRIVER    VOLUME NAME
local     5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa

#手动删除匿名卷
[root@docker ~]# docker volume rm 5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa 
5bef99058cc79351f9145be0851044d6f1a832bf7cd2263f68cb03191b3662aa

命名数据卷

nginx实现命名数据卷

#实现命名数据卷方式一
#命名数据卷直接创建容器
[root@docker ~]# docker run -d -p 80:80 --name nginx01 -v vol1:/usr/share/nginx/html nginx:latest

#实现命名数据卷方式二
#先创建命名数据卷
[root@docker ~]# docker volume create vol1
vol1
#通过创建的命名数据卷创建容器
[root@docker ~]# docker run -d -p 80:80 --name nginx01 -v vol1:/usr/share/nginx/html nginx:latest

#查看创建的命名数据卷
[root@docker ~]# docker volume inspect vol1 
[
    {
        "CreatedAt": "2022-01-31T04:03:53+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/vol1/_data",
        "Name": "vol1",
        "Options": {},
        "Scope": "local"
    }
]
[root@docker ~]# docker inspect --format="{{.Mounts}}" nginx01
[{volume vol1 /var/lib/docker/volumes/vol1/_data /usr/share/nginx/html local z true }]

#查看命名数据卷文件
[root@docker ~]# ls /var/lib/docker/volumes/vol1/_data/
50x.html  index.html
[root@docker ~]# cat /var/lib/docker/volumes/vol1/_data/index.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

#修改命名数据卷文件
[root@docker ~]# echo 'test page v1' > /var/lib/docker/volumes/vol1/_data/index.html

#测试访问
[root@docker ~]# curl 10.0.0.8
test page v1

#利用现在的命名数据卷创建新容器,可以实现和原有容器共用一个数据卷
[root@docker ~]# docker run -d -p 81:80 --name nginx02 -v vol1:/usr/share/nginx/html nginx:latest
41268a0883a4dc22f5f251b80a4e3b9ed511a5d3fdec5456d34dbcd06ae60b19
[root@docker ~]# curl 10.0.0.8:81
test page v1

#删除命名数据卷
#删除指定的命名数据卷
[root@docker ~]# docker volume rm vol1 
#清理全部不再使用的卷(-f表示强制 非交互操作)
[root@docker ~]# docker volume prune -f

数据卷容器

数据卷容器前言

  • 数据卷容器是将宿主机的目录挂载至一个专门的数据卷容器,然后让其他容器通过数据卷容器读写宿主机的数据,此方式不常用
  • 在Dockerfile中创建的是匿名数据卷,无法实现多个容器间的数据共享
  • 数据卷容器最大的作用是让数据在多个docker容器内共享
  • 相当于先要创建一个后台运行的容器作为 server,用于提供数据卷,这个卷可以为其他容器提供数据存储服务,其他使用此卷的容器作为client端
  • 关闭数据卷容器后,仍然可以基于关闭的数据卷容器创建新容器及访问旧的client容器(因为虽然数据卷容器关闭,但是挂载关系在宿主机仍然存在)只要数据卷容器在就可以
  • 删除数据卷容器后,旧的client容器仍能访问,但无法再创建新的client容器
  • 重新创建容器卷容器后,还可以继续创建client容器
  • 数据卷容器的 Server 和 Client 可以不使用同一个镜像生成

数据卷容器的使用

  • 启动容器时指定数据卷容器
docker run --volumes-from <数据卷容器>

数据卷容器实战案例

准备数据

[root@docker ~]# mkdir -p /data/docker/nginx/html/

[root@docker ~]# echo 'test page on volume-server v1' > /data/docker/nginx/html/index.html

[root@docker ~]# cat /data/docker/nginx/html/index.html
test page on volume-server v1

创建一个数据卷容器 Server

  • 并挂载宿主机的数据目录,容器无需启动也可以,数据卷容器一般无需端口映射
[root@docker ~]# docker run -d --name volume-server -v /data/docker/nginx/html/:/usr/share/nginx/html nginx:latest 

启动多个数据卷容器 Client

[root@docker ~]# docker run --name client1 -d -p 80:80 --volumes-from volume-server nginx:latest 
e37c3ee2cfecfb82713927df02c09aa32306b8cc5207d230a602f51781b5a05b
[root@docker ~]# docker run --name client2 -d -p 81:80 --volumes-from volume-server nginx:latest 
c279c85ffa6a3d1b1e1fda6da1e2d4043aa71caabe91e8fed6c57561690a76a7

[root@docker ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS         PORTS                               NAMES
c279c85ffa6a   nginx:latest   "/docker-entrypoint.…"   3 seconds ago    Up 1 second    0.0.0.0:81->80/tcp, :::81->80/tcp   client2
e37c3ee2cfec   nginx:latest   "/docker-entrypoint.…"   10 seconds ago   Up 8 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   client1
011bef7c757b   nginx:latest   "/docker-entrypoint.…"   7 minutes ago    Up 7 minutes   80/tcp                              volume-server

测试访问

[root@docker ~]# curl 10.0.0.8
test page on volume-server v1
[root@docker ~]# curl 10.0.0.8:81
test page on volume-server v1

进入容器测试读写

  • 读写权限依赖于源数据卷Server容器
#进入 Server 容器修改数据
[root@docker ~]# docker exec -it volume-server bash
root@011bef7c757b:/# echo 'test page v1' > /usr/share/nginx/html/index.html 
root@011bef7c757b:/# curl 10.0.0.8
test page v1

#进入 Client 容器修改数据
[root@docker ~]# docker exec -it client1 bash
root@e37c3ee2cfec:/# echo 'test page v2' > /usr/share/nginx/html/index.html
root@e37c3ee2cfec:/# curl 10.0.0.8
test page v2

宿主机测试读写

[root@docker ~]# echo 'test page v3' > /data/docker/nginx/html/index.html
[root@docker ~]# curl 10.0.0.8
test page v3

关闭容器卷server测试

  • **结论:**关闭数据卷容器后,仍然可以基于关闭的数据卷容器创建新容器及访问旧的client容器(因为虽然数据卷容器关闭,但是挂载关系在宿主机仍然存在)
[root@docker ~]# docker run --name client3 -d -p 82:80 --volumes-from volume-server nginx:latest 
891a6aba9a64227e639c7c295bef7450b1c7f9c0827614629ba37f66a33ba2c0
[root@docker ~]# curl 10.0.0.8:82
test page v3
[root@docker ~]# curl 10.0.0.8:81
test page v3
[root@docker ~]# curl 10.0.0.8
test page v3

[root@docker ~]# echo 'test page v6' > /data/docker/nginx/html/index.html
[root@docker ~]# curl 10.0.0.8
test page v6
[root@docker ~]# curl 10.0.0.8:81
test page v6
[root@docker ~]# curl 10.0.0.8:82
test page v6

删除容器卷server测试

  • **结论:**删除数据卷容器后,旧的client容器仍能访问,但无法再创建新的client容器
[root@docker ~]# docker rm -fv volume-server 
volume-server

[root@docker ~]# curl 10.0.0.8
test page v6
[root@docker ~]# curl 10.0.0.8:81
test page v6
[root@docker ~]# curl 10.0.0.8:82
test page v6

[root@docker ~]# docker run --name client4 -d -p 83:80 --volumes-from volume-server nginx:latest 
docker: Error response from daemon: No such container: volume-server.
See 'docker run --help'.

重新创建容器卷server测试

  • 重新创建容器卷容器后,还可以继续创建client容器
[root@docker ~]# docker run --name client4 -d -p 83:80 --volumes-from volume-server nginx:latest 
docker: Error response from daemon: No such container: volume-server.
See 'docker run --help'.

[root@docker ~]# docker run -d --name volume-server -v /data/docker/nginx/html/:/usr/share/nginx/html nginx:latest 
f98c630cd07873cb71eea3bb095d790e3d22370f65f199a8624d40c63597c06b

[root@docker ~]# docker run --name client4 -d -p 83:80 --volumes-from volume-server nginx:latest 
c1726679bcce5369b1351b45304bf2f7d16775e410791071efce0e1b671a79ca
[root@docker ~]# curl 10.0.0.8:83
test page v6

利用数据卷容器备份指定容器的数据卷

  • 由于匿名数据卷在宿主机中的存储位置不确定,所以为了方便的备份匿名数据卷,可以利用数据卷容器实现数据卷的备份

备份还原概述

#说明
[container name]          #表示需要备份|还原的容器
[container data volume]   #表示容器内的需要备份|还原的数据卷对应的目录
$(pwd) #为宿主机映射的目录,充当备份目录

#在执行备份命令容器上 执行备份的方式
docker run -it --rm --volumes-from [container name] -v $(pwd):/backup ubuntu
#tar cvf /backup/backup.tar [container data volume]

#还原方式
docker run -it --rm --volumes-from [container name] -v $(pwd):/backup ubuntu
#tar xvf /backup/backup.tar -C [container data volume]

范例:

...

清理数据卷

  • Docker 在长时间使用的情况下,经常需要删除旧的容器并创建新的容器,长此以往,Docker 的数据卷 volumes 会产生了非常多的僵尸文件,而这些将是文件大都是未绑定容器的目录

查询

# 查看 Docker 的磁盘使用情况
# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          18        1         902.2MB   902.2MB (99%)
Containers      1         1         0B        0B
Local Volumes   3         0         418.6MB   418.6MB (100%)
Build Cache     0         0         0B        0B


# Local Volumes 本质上是查询的此目录的使用量
# du -sh /var/lib/docker/volumes
400M	/var/lib/docker/volumes


# 查询可回收的数据卷
# docker volume ls -qf dangling=true
2126f494f73e95becd33ae0a420e3d7149002f6a71040a6628946e00bdfd8115
bb18b9a989b5f01b9751b8e7279d5533fd794f63be94042d5a8512b269e9ae98
dc93a67b68099d2ee43f40c8bf624a47753f69caf857854ced90ecd24209394f


# 本质上也是查询的此目录下的内容
# ls /var/lib/docker/volumes
2126f494f73e95becd33ae0a420e3d7149002f6a71040a6628946e00bdfd8115
backingFsBlockDev
bb18b9a989b5f01b9751b8e7279d5533fd794f63be94042d5a8512b269e9ae98
dc93a67b68099d2ee43f40c8bf624a47753f69caf857854ced90ecd24209394f
metadata.db

清理

# 删除所有dangling数据卷(即无用的Volume,僵尸文件)
# docker volume rm $(docker volume ls -qf dangling=true)
2126f494f73e95becd33ae0a420e3d7149002f6a71040a6628946e00bdfd8115
bb18b9a989b5f01b9751b8e7279d5533fd794f63be94042d5a8512b269e9ae98
dc93a67b68099d2ee43f40c8bf624a47753f69caf857854ced90ecd24209394f
# ls /var/lib/docker/volumes
backingFsBlockDev  metadata.db


---

# 删除所有dangling镜像(即无tag的镜像)
# docker rmi $(docker images | grep "^<none>" | awk "{print $3}")

# 删除所有关闭的容器
# docker ps -a | grep Exit | cut -d ' ' -f 1 | xargs docker rm

删除关闭的容器、无用的数据卷和网络,以及dangling镜像(即无tag的镜像)

  • 注意,所有关闭的容器都会被删除,请核查是否存在关闭运行但是需要保留的容器
# 删除关闭的容器、无用的数据卷和网络
# docker system prune

# 删除更彻底,可以将没有容器使用Docker镜像都删掉
# docker system prune -a

Docker 相关文件

/var/lib/docker/overlay2/

  • 此目录中存放镜像、容器等相关文件
  • 此目录还存储驱动相关的文件,如果将其下的内容全部删除将无法拉取镜像
# 没有镜像和容器时次目录占用的空间很小
# docker images 
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
root@gitlab-server:~# du -sh /var/lib/docker/overlay2/
8.0K	/var/lib/docker/overlay2/

存在镜像时占用的空间

# docker images 
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        1.23      3964ce7b8458   12 hours ago   142MB

# du -sh /var/lib/docker/overlay2/
150M	/var/lib/docker/overlay2/


# 由于镜像本身存在多层,因此每层会对应一个目录
# ll /var/lib/docker/overlay2/
total 36
drwx--x---  9 root root 4096 Dec 14 21:49 ./
drwx--x--- 13 root root 4096 Dec 14 17:57 ../
drwx--x---  4 root root 4096 Dec 14 21:49 431b57061d42f526085599cee1e2e0d3a469d67679410ea0c2a3d7f1960f3b36/
drwx--x---  4 root root 4096 Dec 14 21:49 44a0f9233d0165e521c43c32cc08d747a2becfc0efbfcfe6ecd44ee7b4aac09c/
drwx--x---  4 root root 4096 Dec 14 21:49 58740eec574cd2e7ab620df0cccfa7a1c94cf0b2d5de8182547787712c8551d0/
drwx--x---  3 root root 4096 Dec 14 21:49 5a6d1455634f092cede6488935884d64276fe8f7aeff2e73d8c8d8e18005a1ef/
drwx--x---  4 root root 4096 Dec 14 21:49 98d2ab81673615b7cf8aeb797dd146d518eb0bfde0d09df27c1b20e9f33cf8c0/
drwx--x---  4 root root 4096 Dec 14 21:49 a5370a8bc35cb3eea7a9f8f61f7288a55b57b01d4dcb37c3d855dd2a81a606ed/
drwx------  2 root root 4096 Dec 14 21:49 l/
# ll /var/lib/docker/overlay2/ | wc -l
10

存在镜像并创建容器时的空间

# docker images 
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        1.23      3964ce7b8458   13 hours ago   142MB

# du -sh /var/lib/docker/overlay2/
150M	/var/lib/docker/overlay2/

# ll /var/lib/docker/overlay2/ | wc -l
10

---

# 创建容器后就相当于把现有的镜像相关文件复制了一份,所以会加上源镜像的大小
# docker run -d --name nginx01 nginx:1.23 
92181b473faa70946f73f29393a56845ba0dc2582eacc03ab46248e0683fd0d7
# ll /var/lib/docker/overlay2/ | wc -l
12
# du -sh /var/lib/docker/overlay2/
298M	/var/lib/docker/overlay2/


# 新生成的文件
# ll /var/lib/docker/overlay2/ -t
total 44
drwx--x---  5 root root 4096 Dec 14 21:53 1022605727bbe7994588c886ae5c9d00d42324ea8570f705025c1c3e43703497/
drwx--x--- 11 root root 4096 Dec 14 21:53 ./
drwx--x---  4 root root 4096 Dec 14 21:53 1022605727bbe7994588c886ae5c9d00d42324ea8570f705025c1c3e43703497-init/
...



# 再次创建新的容器后
# docker run -d --name nginx02 nginx:1.23 
33639bb998a4eb4ba40993b3ab4eef58a2c12fe3dc908cffed2b7d3303743282
# du -sh /var/lib/docker/overlay2/
445M	/var/lib/docker/overlay2/
# ll /var/lib/docker/overlay2/ | wc -l
14
# ll /var/lib/docker/overlay2/ -t
total 52
drwx--x---  5 root root 4096 Dec 14 21:57 8feabaa39bf21ae33dd47e73d8a0f6a5370f927d717c62d80f1652097de7e028/
drwx--x---  4 root root 4096 Dec 14 21:57 8feabaa39bf21ae33dd47e73d8a0f6a5370f927d717c62d80f1652097de7e028-init/
...

/var/lib/docker/containers/

  • 存放容器的配置信息

容器数据持久化 -v

使用 -v jenkins_home:/var/jenkins_home 的方式运行容器时,jenkins_home 是一个 Docker 卷(volume),用于将主机文件系统上的数据挂载到容器中的某个目录(在这里是 /var/jenkins_home)。这种卷有以下几种不同的行为:

1. Docker 自动创建的卷

如果你使用了 -v jenkins_home:/var/jenkins_home,但没有在主机上显式定义 jenkins_home 目录,Docker 会自动为你创建一个名为 jenkins_home 的卷。这个卷是由 Docker 自行管理的,存储在 Docker 容器引擎的默认路径中,通常是在以下路径:

  • Linux/MacOS: /var/lib/docker/volumes/
  • Windows: 在 Docker for Windows 的虚拟机中

在这个路径中,你会看到一个以卷名 jenkins_home 命名的目录。这个目录中的内容会被挂载到容器内的 /var/jenkins_home,以便 Jenkins 在容器中运行时可以持久化数据。

2. 命名卷的好处

使用命名卷(比如 jenkins_home)的主要好处是,数据会被持久化,即使容器被删除,卷中的数据不会丢失。你可以重新启动或者创建新的容器,并挂载同样的卷,数据依然会保留。

3. 主机路径 vs 卷

如果你想要明确将主机上的某个目录与容器中的目录绑定,应该使用绝对路径,比如:

-v /path/on/host:/var/jenkins_home

这样,/path/on/host 就会对应容器内的 /var/jenkins_home 目录。

总结来说,-v jenkins_home:/var/jenkins_home 这种方式意味着 Docker 会在其默认的卷存储路径中创建或使用名为 jenkins_home 的卷,且这个卷中的数据会映射到容器内的 /var/jenkins_home

docker run -v 命令用于运行 Docker 容器。它允许你指定各种选项和参数来自定义容器的行为。其中一个重要的选项是 -v 标志,用于挂载容器的卷。

docker run -v 实现的是持久卷(Persistent Volume)而不是临时卷(Temporary Volume)。

通过使用 -v--volume 选项,你可以将主机上的一个目录或文件挂载到容器中,从而实现数据在容器和主机之间的持久性存储。这意味着即使容器停止运行或被删除,挂载的卷仍然存在于主机上,数据不会丢失。

持久卷对于许多应用场景非常有用,例如数据库持久化存储、日志文件存储和配置文件的共享等。它允许容器与主机之间进行数据交换,并且在容器的生命周期中保持数据的一致性和持久性。

-v 选项说明

-v <主机路径>:<容器路径>[:<选项>]
  • <主机路径>:这指定了你想要从主机机器上挂载卷的路径。它可以是绝对路径或相对路径。
  • <容器路径>:这指定了你想要将卷挂载到容器内的路径。它也可以是绝对路径或相对路径。容器将能够访问该位置上的文件和目录。
  • <选项>(可选):这个字段允许你指定卷挂载的附加选项。一些常见的选项包括只读 (ro),允许容器读取卷但不修改它,以及指定特定的卷驱动程序。

范例

# 将主机上的目录挂载到容器:
docker run -v /主机路径:/容器路径 ...


# 将主机的当前工作目录挂载到容器:
docker run -v $(pwd):/容器路径 ...


# 只读挂载卷:
docker run -v /主机路径:/容器路径:ro ...


# 使用命名卷(用逗号分隔的键值对提供选项):
docker run -v 卷名称:/容器路径:选项1=值1,选项2=值2 ...