七、Docker 数据管理
七、Docker 数据管理

七、Docker 数据管理

如果将正在运行中的容器修改成了新的数据,或者修改了现有的一个已经存在的文件内容,那么新产生的数据将会被复制到读写层,进行持久化保存,这个读写层也就是容器的工作目录,此即“写时复制(COW)copy on write”机制。

如下图是将对根的数据写入到了容器的可写层,到时把/data中的数据写入到了一个另外的wolume中用于数据持久化

7.1 容器的数据管理介绍

Docker镜像是分层设计的,镜像层是只读的,通过镜像启动的容器添加了一层可读写的文件系统,用户写入的数据都保存在这一层中。

7.1.1 Docker容器的分层

容器的数据分层

  • LowerDir:image镜像层(镜像本身,只读)
  • UpperDir: 容器的上层(读写),容器变化的数据处在此处
  • MergedDir:容器的文件系统,使用Union FS (联合文件系统)将lowerdir和upperdir合并给容器使用
  • WorkDir:容器在宿主机的工作目录

7.1.2 哪些数据系要持久化

有状态的协议

有状态协议就是通信双方要互相记住对方,并且共享一些信息。而无状态协议的通信每次都是独立的,与上一次的通信没有关系。
“转态”可以理解为“记忆”,有状态对应有记忆,无状态对应无记忆
  • 左侧是无状态的http请求服务,右侧为有状态
  • 下层为不需要存储的服务,上层为需要存储的部分服务

7.1.3 容器数据持久保存方式

如果要将写入到容器的数据永久保存,则需要将容器中的数据保存到宿主机的指定目录

Dokcer的数据类型分为两种:

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

7.2 数据卷(data volume)

7.2.1 数据卷特点和使用

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

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

7.2.1.1 数据卷使用场景

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

7.2.1.2 数据卷的特点

  • 数据卷是目录或者文件,并且可以在多个容器之间共同使用,实现容器之间共享和重用
  • 对数据卷更改数据在所有容器里面会立即更新。
  • 数据卷的数据可以持久保存,即使删除使用该数据卷的容器也不影响。
  • 在容器里面写入数据不会影响到镜像本身,即数据卷的变化不会影响镜像的更新。
  • 依赖于宿主机目录,宿主机出问题,上面的容器会受影响,当宿主机较多时,不方便统一管理。
  • 数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含了数据,会拷贝到新初始化的数据卷中

7.2.1.3 数据卷的使用方法

启动容器时,可以指定使用数据卷实现容器数据的持久化,数据卷有三种:

  • 指定宿主机目录或文件: 指定宿主机的具体路径和容器路径的挂载关系,此方式不会创建数据卷
  • 匿名卷: 不指定数据名称,只指定容器路径,docker自动指定宿主机的路径,此方式不会创建匿名数据卷
  • 命名卷: 指定数据卷的名称和容器路径的挂载关系,此方式不会创建命名数据卷

关于匿名数据卷和命令数据卷

命名卷就是有名字的卷,使用 docker volume create <卷名> 形式创建并命名的卷;而匿名卷就是没名
字的卷,一般是 docker run -v /data 这种不指定卷名的时候所产生,或者 Dockerfile 里面的定义
直接使用的。

有名字的卷,在用过一次后,以后挂载容器的时候还可以使用,因为有名字可以指定。所以一般需要保存的数
据使用命名卷保存。
而匿名卷则是随着容器建立而建立,随着容器消亡而淹没于卷列表中(对于 docker rm 匿名卷不会被自动
删除)。 因此匿名卷只存放无关紧要的临时数据,随着容器消亡,这些数据将失去存在的意义。

Dockerfile中指定VOLUME为匿名数据卷,其目的只是为了将某个路径确定为卷。

按照最佳实践的要求,不应该在容器存储层内进行数据写入操作,所有写入应该使用卷。如果定制镜像的时
候,就可以确定某些目录会发生频繁大量的读写操作,那么为了避免在运行时由于用户疏忽而忘记指定卷,导
致容器发生存储层写入的问题,就可以在 Dockerfile 中使用 VOLUME 来指定某些目录为匿名卷。这样即
使用户忘记了指定卷,也不会产生不良的后果。
这个设置可以在运行时覆盖。通过 docker run 的 -v 参数或者 docker-compose.yml 的 volumes
指定。使用命名卷的好处是可以复用,其它容器可以通过这个命名数据卷的名字来指定挂载,共享其内容(不
过要注意并发访问的竞争问题)。

比如,Dockerfile 中说 VOLUME /data,那么如果直接 docker run,其 /data 就会被挂载为匿名卷,向 /data 写入的操作不会写入到容器存储层,而是写入到了匿名卷中。但是如果运行时 docker run-v mydata:/data,这就覆盖了 /data 的挂载设置,要求将 /data 挂载到名为 mydata 的命名卷中。所以说 Dockerfile 中的 VOLUME 实际上是一层保险,确保镜像运行可以更好的遵循最佳实践,不向容器存储层内进行写入操作。

数据卷默认可能会保存于 /var/lib/docker/volumes,不过一般不需要、也不应该访问这个位置。

docker run 命令的以下格式可以实现数据卷

-v, --volume=[host-src:]container-dest[:<options>]

<options>
ro 从容器内对此数据卷是只读,不写此项默认为可读可写
rw 从容器内对此数据卷可读可写,此为默认值

方式一:

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

#示例
docker run --rm --name n1 -p 80:80 -v /data/nginx/html/:/usr/share/nginx/html
 nginx

方式二:

#匿名卷,只指定容器内路径,不指定宿主机路径,宿主机自动生成/var/lib/docker/volumes/<卷
ID>/_data目录,并挂载至容器指定路径
-v <容器内路径>

#示例:
docker run --name nginx -v /etc/nginx nginx

方式三:

#命名卷将固定的存放在/var/lib/docker/volumes/<卷名>/_data
-v <卷名>:<容器目录路径>
#<卷名>可以通过以下命令事先创建,如可没有事先创建卷名,docker run时会自动创建卷
docker volume create <卷名>

#示例:
docker volume create vol1 #也可以事先不创建
docker run -d -p 80:80 --name nginx01 -v vol1:/usr/share/nginx/html nginx

docker rm 选项可以删除容器时,同时删除相关联的匿名卷

-v, --volumes Remove the volumes associated with the container

管理数据卷命令

docker volume COMMAND

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

查看数据卷的挂载关系

docker inspect --format="{{.Mounts}}" <容器ID>

7.3 数据卷容器

7.3.1 数据卷容器介绍

在Dockerfile中创建的是匿名数据卷,无法直接实现多个容器之间共享数据

数据卷容器最大的功能是可以让数据在多个docker容器之间共享

如下图所示: 即可以让B容器访问A容器的内容,而容器C也可以访问A容器的内容,即可以实现A,B,C三个容器之间的数据读写共享。

相当于先要创建一个后台运行的容器作为 Server,用于提供数据卷,这个卷可以为其他容器提供数据存储服务,其他使用此卷的容器作为client端 ,但此方法并不常使用

缺点: 因为依赖一个 Server 的容器,所以此 Server 容器出了问题,其它 Client容器都会受影响

7.3.2 使用数据卷容器

启动容器时,指定使用数据卷容器

docker run 命令的以下选项可以实现数据卷容器,格式如下:

--volumes-from <数据卷容器> Mount volumes from the specified container(s)

7.3.3 实战案例:数据卷容器

7.3.3.1 启动一个数据卷容器Server

先启动一个挂载宿主机的数据目录的容器,且可以无需启动

#数据卷容器一般无需映射端口
root@ubuntu-2010:~# cd /apps/
root@ubuntu-2010:/apps# mkdir test{1,2}
root@ubuntu-2010:/apps# ll
total 16
drwxr-xr-x  4 root root 4096 Sep 20 12:24 ./
drwxr-xr-x 22 root root 4096 Sep 20 12:12 ../
drwxr-xr-x  2 root root 4096 Sep 20 12:24 test1/
drwxr-xr-x  2 root root 4096 Sep 20 12:24 test2/
root@ubuntu-2010:/apps# echo "this is test1" > test1/f1.txt
root@ubuntu-2010:/apps# echo "this is test2" > test2/f2.txt
root@ubuntu-2010:~# docker run -d -v /apps/test1:/usr/local/apache2/htdocs/test1:ro -v /apps/test2:/usr/local/apache2/htdocs/test2 --name volume-server httpd
164bb5febfcec5389cd3a5d23d4567c8b4e231cf9a48cbafedcecb92c65cf334
root@ubuntu-2010:~# docker ps 
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS         PORTS     NAMES
164bb5febfce   httpd     "httpd-foreground"   3 seconds ago   Up 3 seconds   80/tcp    volume-server

7.3.3.2 启动多个数据卷容器Client

root@ubuntu-2010:~# docker run -d --name client1 --volumes-from volume-server -p 81:80 httpd
ee6d9e926b923530a9ac85fe1362e4dab818f95cbf3268b920519a60fd1568e0
root@ubuntu-2010:~# docker run -d --name client2 --volumes-from volume-server -p 82:80 httpd
70fcb519245314dfbef8a23ecbc6bc517784194cc0e092d155c563b5cb4181a3
root@ubuntu-2010:~# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS          PORTS                               NAMES
70fcb5192453   httpd     "httpd-foreground"   12 seconds ago   Up 11 seconds   0.0.0.0:82->80/tcp, :::82->80/tcp   client2
ee6d9e926b92   httpd     "httpd-foreground"   19 seconds ago   Up 18 seconds   0.0.0.0:81->80/tcp, :::81->80/tcp   client1
164bb5febfce   httpd     "httpd-foreground"   53 seconds ago   Up 53 seconds   80/tcp                              volume-server

7.3.3.3 验证访问

root@ubuntu-2010:~# curl localhost:81/test1/f1.txt
this is test1
root@ubuntu-2010:~# curl localhost:81/test2/f2.txt
this is test2
root@ubuntu-2010:~# curl localhost:82/test1/f1.txt
this is test1
root@ubuntu-2010:~# curl localhost:82/test2/f2.txt
this is test2

7.3.3.4 进入容器测试读写

读写权限仅依赖于源数据卷Server容器

#进入 Server 容器修改数据
root@ubuntu-2010:~# docker exec -ti volume-server bash
root@164bb5febfce:/usr/local/apache2# cat htdocs/test1/f1.txt htdocs/test2/f2.txt 
this is test1
this is test2
root@164bb5febfce:/usr/local/apache2# echo hello >> htdocs/test1/f1.txt 
bash: htdocs/test1/f1.txt: Read-only file system
root@164bb5febfce:/usr/local/apache2# echo hello >> htdocs/test2/f2.txt
root@ubuntu-2010:~# curl localhost:81/test2/f2.txt
this is test2
hello
root@ubuntu-2010:~# curl localhost:82/test2/f2.txt
this is test2
hello

#进入 Client 容器修改数据
root@ubuntu-2010:~# docker exec -ti client1 bash
root@ee6d9e926b92:/usr/local/apache2# echo "test1 v2" > htdocs/test1/f1.txt 
bash: htdocs/test1/f1.txt: Read-only file system
root@ee6d9e926b92:/usr/local/apache2# echo "test2 v2" > htdocs/test2/f2.txt
root@ubuntu-2010:~# curl localhost:81/test2/f2.txt
test2 v2
root@ubuntu-2010:~# curl localhost:82/test2/f2.txt
test2 v2

7.3.3.5 在宿主机直接修改

root@ubuntu-2010:~# echo "test1 v3" > /apps/test1/f1.txt 
root@ubuntu-2010:~# echo "test2 v3" > /apps/test2/f2.txt 
root@ubuntu-2010:~# curl localhost:81/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:81/test2/f2.txt
test2 v3
root@ubuntu-2010:~# curl localhost:82/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:82/test2/f2.txt
test2 v3

7.3.3.6 关闭卷容器Server测试能否启动新容器

关闭卷容器,仍然可以创建新的client容器以及访问旧的client容器

root@ubuntu-2010:~# docker stop volume-server 
volume-server
root@ubuntu-2010:~# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS                     PORTS                               NAMES
70fcb5192453   httpd     "httpd-foreground"   10 minutes ago   Up 10 minutes              0.0.0.0:82->80/tcp, :::82->80/tcp   client2
ee6d9e926b92   httpd     "httpd-foreground"   10 minutes ago   Up 10 minutes              0.0.0.0:81->80/tcp, :::81->80/tcp   client1
164bb5febfce   httpd     "httpd-foreground"   10 minutes ago   Exited (0) 6 seconds ago                                       volume-server
root@ubuntu-2010:~# docker run -d --name client3 --volumes-from volume-server -p 83:80 httpd
455d9cc97d8dd040bc278a84c8758eeb189079468665713c6473f1f13f92ba40
root@ubuntu-2010:~# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS                      PORTS                               NAMES
455d9cc97d8d   httpd     "httpd-foreground"   5 seconds ago    Up 4 seconds                0.0.0.0:83->80/tcp, :::83->80/tcp   client3
70fcb5192453   httpd     "httpd-foreground"   10 minutes ago   Up 10 minutes               0.0.0.0:82->80/tcp, :::82->80/tcp   client2
ee6d9e926b92   httpd     "httpd-foreground"   10 minutes ago   Up 10 minutes               0.0.0.0:81->80/tcp, :::81->80/tcp   client1
164bb5febfce   httpd     "httpd-foreground"   11 minutes ago   Exited (0) 32 seconds ago                                       volume-server
root@ubuntu-2010:~# curl localhost:81/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:82/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:83/test1/f1.txt
test1 v3

7.3.3.7 删除源卷容器Server,访问client和创建新的client容器

删除数据卷容器后,旧的client 容器仍能访问,但无法再创建新的client容器

root@ubuntu-2010:~# docker rm -fv volume-server 
volume-server
root@ubuntu-2010:~# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED              STATUS              PORTS                               NAMES
455d9cc97d8d   httpd     "httpd-foreground"   About a minute ago   Up About a minute   0.0.0.0:83->80/tcp, :::83->80/tcp   client3
70fcb5192453   httpd     "httpd-foreground"   12 minutes ago       Up 12 minutes       0.0.0.0:82->80/tcp, :::82->80/tcp   client2
ee6d9e926b92   httpd     "httpd-foreground"   12 minutes ago       Up 12 minutes       0.0.0.0:81->80/tcp, :::81->80/tcp   client1
root@ubuntu-2010:~# docker run -d --name client4 --volumes-from volume-server -p 84:80 httpd
docker: Error response from daemon: No such container: volume-server.
See 'docker run --help'.
root@ubuntu-2010:~# curl localhost:81/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:82/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:83/test1/f1.txt
test1 v3

7.3.3.8 重新创建容器卷Server

重新创建容器卷后,还可继续创建新client 容器

root@ubuntu-2010:~# docker run -d -v /apps/test1:/usr/local/apache2/htdocs/test1:ro -v /apps/test2:/usr/local/apache2/htdocs/test2 --name volume-server httpd
75222cc7bd89cbf30f2b05036fd62810e8ce22050ed53f0e6f3b22ea8f9a7141
root@ubuntu-2010:~# docker run -d --name client4 --volumes-from volume-server -p 84:80 httpd
0bb84768fdef93444d00e6fbd5a38caff6640d420430cbd02c7e99cb48656033
root@ubuntu-2010:~# curl localhost:81/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:82/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:83/test1/f1.txt
test1 v3
root@ubuntu-2010:~# curl localhost:84/test1/f1.txt
test1 v3

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

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

#备份方式
docker run -it --rm --volumes-from [container name] -v $(pwd):/backup ubuntu
root@ca5bb2c1f877:/#tar cvf /backup/backup.tar [container data volume]

#说明
[container name] #为需要备份的匿名卷容器
[container data volume] #表示容器中的需要备份的数据卷
#还原方式

docker run -it --rm --volumes-from [container name] -V $(pwd):/backup ubuntu
root@ca5bb2c1f877:/#tar xvf /backup/backup.tar -C [container data volume]

范例:利用数据卷容器备份MySQL数据库

#mysql容器默认使用了匿名卷
root@ubuntu-2010:~# docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.29 
f716235428805986ee84e4621147187847011883cf3cd854917ef93d579f50b4


root@ubuntu-2010:~# docker volume ls
DRIVER    VOLUME NAME
local     00d406fcffdf17ec6f26038d31c1be9ec6d1d0773b67ca71b24264cbb57ea6d2

#备份数据库
root@ubuntu-2010:~# docker run -ti --rm --volumes-from mysql -v $(pwd):/backup alpine tar cvf /backup/mysql.tar /var/lib/mysql
root@ubuntu-2010:~# rm -rf /var/lib/docker/volumes/
00d406fcffdf17ec6f26038d31c1be9ec6d1d0773b67ca71b24264cbb57ea6d2/ metadata.db
backingFsBlockDev 

#删除数据库文件                                                
root@ubuntu-2010:~# rm -rf /var/lib/docker/volumes/00d406fcffdf17ec6f26038d31c1be9ec6d1d0773b67ca71b24264cbb57ea6d2/_data/*


#还原数据库
root@ubuntu-2010:~# docker run -ti --rm --volumes-from mysql -v $(pwd):/backup alpine tar xvf /backup/mysql.tar -C /

#验证登陆
root@ubuntu-2010:~# apt -y install mysql-client
root@ubuntu-2010:~# mysql -uroot -p123456 -h10.0.0.210
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.29 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

7.3.5 数据卷容器总结

  • 将提供卷的容器Server 删除,已经运行的容器Client依然可以使用挂载的卷,因为容器是通过挂载访问数据的,但是无法创建新的卷容器客户端,但是再把卷容器Server创建后即可正常创建卷容器Client,此方式可以用于线上共享数据目录等环境,因为即使数据卷容器被删除了,其他已经运行的容器依然可以挂载使用
  • 由此可知, 数据卷容器的功能只是将数据挂载信息传递给了其它使用数据卷容器的容器,而数据卷容器本身并不提供数据存储功能
  • 数据卷容器可以作为共享的方式为其他容器提供文件共享,类似于NFS共享,可以在生产中启动一个实例挂载本地的目录,然后其他的容器分别挂载此容器的目录,即可保证各容器之间的数据一致性
  • 数据卷容器的 Server 和 Client 可以不使用同一个镜像生成
  • 最终实现了多个客户端容器共享相同的持久化宿主机的存储方案

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注