Docker镜像数据读写原理
- Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层
- 如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏,此即“写时复制(COW copyon write)"机制
- COW机制节约空间,但会导致性低下,虽然关闭重启容器,数据不受影响,但会随着容器的删除,其对应的可写层也会随之而删除,即数据也会丢失.如果容器需要持久保存数据,并不影响性能可以用数据卷技术实现
Docker容器的分层
Docker镜像是分层设计的,镜像层是只读的,通过镜像启动的容器添加了一层可读写的文件系统,用户写入的数据都保存在这一层中。
容器的数据分层目录
- LowerDir: image 镜像层,即镜像本身,只读
- UpperDir: 容器的上层,可读写 ,容器变化的数据存放在此处
- MergedDir: 容器的文件系统,使用Union FS(联合文件系统)将lowerdir 和 upperdir 合并完成后给容器使用,最终呈现给用户的统一视图
- WorkDir: 容器在宿主机的工作目录,挂载后内容会被清空,且在使用过程中其内容用户不可见
案例:
"GraphDriver": {
"Data": {
"LowerDir": "/data/docker/overlay2/086a9610ca8e8299ccfa6656cc8939f10d981c670eb3d8e0dace3578566e1950-init/diff:/data/docker/overlay2/3719ac9519537c60c9232d10a4f8cf8886795dd10dc77e1419366cf6620a6f4a/diff:/data/docker/overlay2/af7dbf5445243ce62ea3332d6e8de09ac9343507eb09c6bf33af1536d0ea4bb6/diff:/data/docker/overlay2/6c24180effae6e89f5340c4cb97d4d16cf16dc078ea07fd0c83d974a93d29390/diff:/data/docker/overlay2/37d92a852bed1006ae43fbb7370d279f5eac095ded055f1c3da31e114357ad87/diff:/data/docker/overlay2/6aaf443aeea3dfaac29f6d4a9ccad2b90a44cb1f69590ba4028168be32c65f27/diff:/data/docker/overlay2/c43183788449098ba9809d7a8a58666db201daef8eb41ef81e6ce9cbb6818df7/diff:/data/docker/overlay2/31316f8b67f97e6c4a9543dfc4328c0e3c29d52f7dfcdd1af66d66d0c658dcc7/diff:/data/docker/overlay2/4f1e571f109868c90e38a6f0538c5abf9daff5c47ffedb19894d56ce1edd9b37/diff",
"MergedDir": "/data/docker/overlay2/086a9610ca8e8299ccfa6656cc8939f10d981c670eb3d8e0dace3578566e1950/merged",
"UpperDir": "/data/docker/overlay2/086a9610ca8e8299ccfa6656cc8939f10d981c670eb3d8e0dace3578566e1950/diff",
"WorkDir": "/data/docker/overlay2/086a9610ca8e8299ccfa6656cc8939f10d981c670eb3d8e0dace3578566e1950/work"
},
"Name": "overlay2"
},
容器数据持久保存的两种方式
如果要将写入到容器的数据永久保存,则需要将容器中的数据保存到宿主机的指定目录
Docker的数据类型分为两种:
- 数据卷(Data Volume): 直接将宿主机目录挂载至容器的指定的目录 ,推荐使用此种方式
- 数据卷容器(Data Volume Container): 间接使用宿主机空间,数据卷容器是将宿主机的目录挂载至一个专门的数据卷容器,然后让其他容器通过数据卷容器读写宿主机的数据 ,此方式不常用
1. 数据卷的使用
数据卷使用场景
- 数据库
- 日志输出
- 静态web页面
- 应用配置文件
- 多容器间目录或文件共享
数据卷的特点
- 数据卷是目录或者文件,并且可以在多个容器之间共同使用,实现容器之间共享和重用
- 对数据卷更改数据在所有容器里面会立即更新。
- 数据卷的数据可以持久保存,即使删除使用使用该容器卷的容器也不影响。
- 在容器里面的写入数据不会影响到镜像本身,即数据卷的变化不会影响镜像的更新
- 依赖于宿主机目录,宿主机出问题,上面容器会受影响,当宿主机较多时,不方便统一管理
- 匿名和命名数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含了数据,会拷贝到新初始化的数据卷中
数据卷分类
- 指定宿主机目录或文件: 指定宿主机的具体路径和容器路径的挂载关系,此方式不会创建数据卷
- 匿名卷: 不指定数据名称,只指定容器内目录路径充当挂载点,docker自动指定宿主机的路径进行挂载,此方式会创建匿名数据卷,Dockerfile中VOLUME指定的卷即为此种
- 命名卷: 指定数据卷的名称和容器路径的挂载关系,此方式会创建命名数据卷
数据卷使用语法
-v, --volume=[host-src:]container-dest[:<options>]
<options>
ro 从容器内对此数据卷是只读,不写此项默认为可读可写
rw 从容器内对此数据卷可读可写,此为默认值
方式1:
#指定宿主机目录或文件格式:
-v <宿主机绝对路径的目录或文件>:<容器目录或文件>[:ro] #将宿主机目录挂载容器目录,两个目录都可自动创建
案例:
[root@ubuntu2204 ~]#mkdir -pv /opt/nginx/html/
mkdir: 已创建目录 '/opt/nginx'
mkdir: 已创建目录 '/opt/nginx/html/'
[root@ubuntu2204 ~]#echo 'Hello Moore,Nice to meet you' > /opt/nginx/html/index.html
[root@ubuntu2204 ~]#docker run -d -p 8080:80 -v /opt/nginx/html/:/data/nginx/html/ nginx-alpine:1.16.1
bb8b25caf71b1b641a5d25c45f074a7c4c5e611374214e97bcf6c8eeb2f218d3
[root@ubuntu2204 ~]#curl 127.0.0.1:8080
Hello Moore,Nice to meet you
------------------------------------------------------------------------------------------------
方式2
#匿名卷,只指定容器内路径,没有指定宿主机路径信息,宿主机自动生成/var/lib/docker/volumes/<卷ID>/_data目录,并挂载至容器指定路径
-v <容器内路径>
#案例:
[root@ubuntu2204 1.16.1-alpine]#cat Dockerfile
FROM alpine-base:v1.2
LABEL auther="mooreyxia" \
version="1.0"
ADD nginx-1.16.1.tar.gz /usr/local/src
RUN cd /usr/local/src/nginx-1.16.1 \
&& ./configure --prefix=/apps/nginx \
&& make && make install \
&& ln -s /apps/nginx/sbin/nginx /usr/bin/
RUN addgroup -g 2019 -S nginx \
&& adduser -s /sbin/nologin -S -D -u 2019 -G nginx nginx
COPY nginx.conf /apps/nginx/conf/nginx.conf
ADD index.html /data/nginx/html/index.html
RUN chown -R nginx.nginx /data/nginx/ /apps/nginx/
EXPOSE 80 443
CMD ["nginx"]
VOLUME /data/nginx/html/ --> Dockerfile中直接设置也可
#或者在run命令中直接指定,生成的数据卷名称是随机的
[root@ubuntu2204 1.16.1-alpine]#docker run -d -p 83:80 -v /data/nginx/html/ --name anon-server nginx-alpine:1.16.1
d10e8dcb46a335b9cc3d8ae4322c51f501a45da96d660e0d84bb0c79352027f1
[root@ubuntu2204 1.16.1-alpine]#docker volume ls
DRIVER VOLUME NAME
local 169341e0045107bbdea856220db8221082c950408918d7c58f03fffc09e73997
------------------------------------------------------------------------------------------------
方式3
#命名卷将固定的存放在/var/lib/docker/volumes/<卷名>/_data
-v <卷名>:<容器目录路径>
#可以通过命令事先创建,如可没有事先创建卷名,docker run时也会自动创建卷docker volume create <卷名>
案例:
[root@ubuntu2204 1.16.1-alpine]#docker run -d -p 84:80 -v website:/data/nginx/html/ nginx-alpine:1.16.1
f895a3ce97ac7fd6cd1935dc92311f6f921deb161f8bb396ea7a92f7ef7db6ab
[root@ubuntu2204 1.16.1-alpine]#docker volume ls
DRIVER VOLUME NAME
local website
管理数据卷命令
#数据卷的删除或者是不用的数据卷都可以删除,用rm -f 的方式容易有残留文件
[root@ubuntu2204 1.16.1-alpine]#docker volume --help
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.
案例:删除没有被使用的数据卷
[root@ubuntu2204 1.16.1-alpine]#docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f895a3ce97ac nginx-alpine:1.16.1 "nginx" 6 minutes ago Up 6 minutes 443/tcp, 0.0.0.0:84->80/tcp, :::84->80/tcp intelligent_matsumoto
d10e8dcb46a3 nginx-alpine:1.16.1 "nginx" 16 minutes ago Up 16 minutes 443/tcp, 0.0.0.0:83->80/tcp, :::83->80/tcp anon-server
2374dfc04840 nginx-alpine:1.16.1-1 "nginx" 2 hours ago Up 2 hours 443/tcp, 0.0.0.0:90->80/tcp, :::90->80/tcp data-server1
f345b2624792 nginx-alpine:1.16.1 "nginx" 2 hours ago Up 2 hours 443/tcp, 0.0.0.0:81->80/tcp, :::81->80/tcp server1
5c7d31a7764a nginx-alpine:1.16.1 "nginx" 2 hours ago Up 2 hours 443/tcp, 0.0.0.0:82->80/tcp, :::82->80/tcp server2
176f11e9ee8c nginx-alpine:1.16.1 "nginx" 3 hours ago Up 3 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp data-server
[root@ubuntu2204 1.16.1-alpine]#docker rm -f intelligent_matsumoto
intelligent_matsumoto
[root@ubuntu2204 1.16.1-alpine]#docker rm -f anon-server
anon-server
[root@ubuntu2204 1.16.1-alpine]#docker volume ls
DRIVER VOLUME NAME
local 169341e0045107bbdea856220db8221082c950408918d7c58f03fffc09e73997
local website
[root@ubuntu2204 1.16.1-alpine]#docker volume rm 169341e00451 website
案例:引用同一个数据卷目录,开启多个容器,实现多个容器共享数据
[root@ubuntu2204 ~]#docker run -d -v /data/testdir:/data/nginx/html/ -p
8001:80 nginx-alpine:1.16.1
56a5460f584bd2de56040c4a1dff86ad8a9723cfd6bf21ed8a538b9629b0874c
[root@ubuntu2204 ~]#docker run -d -v /data/testdir:/data/nginx/html/ -p
8002:80 nginx-alpine:1.16.1
e7b5bff6ce56fa51ed6411175c9c9f1fb9bf8e7b1b9471080380b01692f89e58
[root@ubuntu2204 ~]#docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
e7b5bff6ce56 nginx-alpine:1.16.1 "nginx" 6 seconds ago
Up 5 seconds 443/tcp, 0.0.0.0:8002->80/tcp hungry_robinson
56a5460f584b nginx-alpine:1.16.1 "nginx" 33 seconds ago
Up 31 seconds 443/tcp, 0.0.0.0:8001->80/tcp stupefied_dubinsky
[root@ubuntu2204 ~]#curl 127.0.0.1:8001
Test page on host
[root@ubuntu2204 ~]#curl 127.0.0.1:8002
Test page on host
数据卷容器
数据卷容器最大的功能是可以让数据在多个docker容器之间共享
实现方式:
- 先要创建一个后台运行的容器作为 Server,用于提供数据卷,这个卷可以为其他容器提供数据存储服务,其他使用此卷的容器作为client端 ,但此方法并不常使用
- 缺点: 因为依赖一个 Server 的容器,所以此 Server 容器出了问题,其它 Client容器都会受影响
案例:
#--volumes-from <数据卷容器> Mount volumes from the specified container(s)
#创建一个后台运行的容器作为 Server,用于提供数据卷
[root@ubuntu2204 ~]#docker run -d -p 80:80 -v /opt/nginx/html/:/data/nginx/html/ -v /opt/dir1:/dir1 --name data-server nginx-alpine:1.16.1
176f11e9ee8c1e8873bc05ed5820f2a80a3d330e250c22030d3bb2ff371233be
[root@ubuntu2204 ~]#docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
176f11e9ee8c nginx-alpine:1.16.1 "nginx" 13 seconds ago Up 12 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp data-server
[root@ubuntu2204 ~]#docker volume ls
DRIVER VOLUME NAME
[root@ubuntu2204 ~]#curl 127.0.0.1
Hello Moore,Nice to meet you
[root@ubuntu2204 ~]#touch /opt/dir1/abc.txt
[root@ubuntu2204 ~]#docker exec data-server ls /dir1
abc.txt
#其他使用此卷的容器作为client端
[root@ubuntu2204 ~]#docker run -d -p 82:80 --name server2 --volumes-from data-server nginx-alpine:1.16.1
5c7d31a7764a0a3b561fd86107a1676f9d60db54fdb72ebfac67bed3c769ef9f
[root@ubuntu2204 ~]#docker run -d -p 81:80 --name server1 --volumes-from data-server nginx-alpine:1.16.1
f345b26247924e27d89e2bdce21a78ae4faf162f01a49be8116460d8ab66f514
[root@ubuntu2204 ~]#docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f345b2624792 nginx-alpine:1.16.1 "nginx" 4 seconds ago Up 3 seconds 443/tcp, 0.0.0.0:81->80/tcp, :::81->80/tcp server1
5c7d31a7764a nginx-alpine:1.16.1 "nginx" 30 seconds ago Up 29 seconds 443/tcp, 0.0.0.0:82->80/tcp, :::82->80/tcp server2
176f11e9ee8c nginx-alpine:1.16.1 "nginx" 8 minutes ago Up 8 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp data-server
[root@ubuntu2204 ~]#curl 127.0.0.1:80
Hello Moore,Nice to meet you
[root@ubuntu2204 ~]#curl 127.0.0.1:81
Hello Moore,Nice to meet you
[root@ubuntu2204 ~]#curl 127.0.0.1:82
Hello Moore,Nice to meet you
[root@ubuntu2204 ~]#docker exec server2 ls /dir1
abc.txt
[root@ubuntu2204 ~]#docker exec server1 ls /dir1
abc.txt
注意:
- 删除数据卷容器后,旧的client 容器仍能访问,但无法再创建新的client容器
- 删除数据卷容器后,旧的client 容器仍能访问,但无法再创建基于数据卷容器的新的client容器,但可以创建基于已创建的Client容器的Client容器
利用数据卷容器备份指定容器的数据卷实现
为了方便的备份匿名数据卷,可以利用数据卷容器实现数据卷的备份
#使用语法
#在执行备份命令容器上执行备份方式
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]
案例:
[root@ubuntu2204 ~]#vim /data/docker/volumes/169341e0045107bbdea856220db8221082c950408918d7c58f03fffc09e73997/_data/index.html
[root@ubuntu2204 ~]#curl 127.0.0.1:90
Test Page based nginx-alpine v2.0
[root@ubuntu2204 ~]#docker run -it --rm --volumes-from data-server1 -v $(pwd):/backup nginx-alpine:1.16.1-1 tar cvf /backup/backup.tar /data/nginx/html
tar: removing leading '/' from member names
data/nginx/html/
data/nginx/html/index.html
[root@ubuntu2204 ~]#ls
backup.tar install_docker.sh nginx-1.16.1.tar.gz nginx_install.sh repositories web02_status.txt
[root@ubuntu2204 ~]#tar tvf backup.tar
drwxr-xr-x nginx/nginx 0 2023-01-10 16:49 data/nginx/html/
-rw-r--r-- nginx/nginx 34 2023-01-10 16:49 data/nginx/html/index.html
[root@ubuntu2204 ~]#docker volume ls
DRIVER VOLUME NAME
local 169341e0045107bbdea856220db8221082c950408918d7c58f03fffc09e73997
#故意破环容器内数据
[root@ubuntu2204 ~]#rm -f /data/docker/volumes/169341e0045107bbdea856220db8221082c950408918d7c58f03fffc09e73997/_data/index.html
[root@ubuntu2204 ~]#curl 127.0.0.1:90
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.16.1</center>
</body>
</html>
#还原数据
[root@ubuntu2204 ~]#docker run -it --rm --volumes-from data-server1 -v $(pwd):/backup nginx-alpine:1.16.1-1 sh
/ # ls /backup/
backup.tar install_docker.sh nginx-1.16.1.tar.gz nginx_install.sh repositories web02_status.txt
/ # tar xf /backup/backup.tar
/ # ls
apps backup bin data dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # ls /data/nginx/html/
index.html
/ # exit
[root@ubuntu2204 ~]#curl 127.0.0.1:90
Test Page based nginx-alpine v2.0
数据卷容器总结 - 实现多个客户端容器共享相同的持久化宿主机的存储方案
- 将提供卷的容器Server 删除,已经运行的容器Client依然可以使用挂载的卷,因为容器是通过挂载访问数据的,但是无法创建新的卷容器客户端,但是再把卷容器Server创建后即可正常创建卷容器Client,此方式可以用于线上共享数据目录等环境,因为即使数据卷容器被删除了,其他已经运行的容器依然可以挂载使用。由此可知, 数据卷容器的功能只是将数据挂载信息传递给了其它使用数据卷容器的容器,而数据卷容器本身并不提供数据存储功能
- 数据卷容器可以作为共享的方式为其他容器提供文件共享,类似于NFS共享,可以在生产中启动一个实例挂载本地的目录,然后其他的容器分别挂载此容器的目录,即可保证各容器之间的数据一致性
- 当创建Client容器时,会复制Server容器的数据卷信息,后续Server容器状态和存在与否,都不会影响Client容器使用的数据卷
- 当Server容器删除后,不能再基于Server容器创建新的Client容器,但可以基于已存在的Client容器来创建新的Client容器
我是moore,大家一起加油!