文章目录
- 一、镜像
- 1、镜像
- 2、镜像原理之联合文件系统
- 3、镜像原理之分层
- 4、commit镜像
- 二、数据卷
- 1、数据卷
- 2、-v使用数据卷
- 3、实战:MySQL 同步数据
- 4、docker volume相关指令
- 5、匿名和具名挂载
- 6、数据卷之Dockerfile
- 7、数据卷容器
一、镜像
1、镜像
镜像是一种轻量级、可执行的独立软件包
,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。
所有应用,直接打包docker镜像,就可以直接跑起来!而镜像的获取,可以:
- 从远程仓库下载
- 拷贝或docker save -o 导出
- 通过DockerFile自己制作
2、镜像原理之联合文件系统
UnionFs (联合文件系统):
Union文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加
,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。Union文件系统是 Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(基础镜像没有父镜像),可以制作各种具体的应用镜像。
Docker镜像加载原理:
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
- boots(boot file system):主要包含 bootloader和 Kernel, bootloader主要是引导加 kernel,Linux刚启动时会加bootfs文件系统,在 Docker镜像的最底层是 boots。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs转交给内核,此时系统也会卸载bootfs
- rootfs(root file system):在 bootfs之上。包含的就是典型 Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。 rootfs就是各种不同的操作系统发行版,比如 Ubuntu, Centos等等
平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M?
对于个精简的OS,rootfs可以很小,只需要包合最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的Linux发行版, boots基本是一致的, rootfs会有差別,因此不同的发行版可以公用bootfs
3、镜像原理之分层
随便下载一个镜像,可以看到是在一层一层的下载:
Docker镜像采用这种分层结构,好处是可以
资源共享
。比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像
,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。
docker inspect redis
# 在镜像元数据信息中看到分层的相关数据
[
....
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:c2adabaecedbda0af72b153c6499a0555f3a769d52370469d8f6bd6328af9b13",
"sha256:744315296a49be711c312dfa1b3a80516116f78c437367ff0bc678da1123e990",
"sha256:379ef5d5cb402a5538413d7285b21aa58a560882d15f1f553f7868dc4b66afa8",
"sha256:d00fd460effb7b066760f97447c071492d471c5176d05b8af1751806a1f905f8",
"sha256:4d0c196331523cfed7bf5bafd616ecb3855256838d850b6f3d5fba911f6c4123",
"sha256:98b4a6242af2536383425ba2d6de033a510e049d9ca07ff501b95052da76e894"
]
},
"Metadata": {
....
]
所有的 Docker镜像都起始于一个基础镜像层,当进行修改或加新的内容时,就会在当前镜像层之上,创建新的镜像层。
- 基于 Ubuntu Linux16.04创建一个新的镜像,这就是新镜像的第一层
- 如果在该镜像中添加 Python包,就会在基础镜像层之上创建第二个镜像层
- 如果继续添加一个安全补丁,就会创健第三个镜像层该像当前已经包含3个镜像层
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。下图中,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版。
这种更新情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。
下图展示了与系统显示相同的三层镜像。所有镜像层堆并合井,对外提供统一的视图。
Docker 镜像都是
只读
的,当容器启动时,一个新的可写层加载到镜像的顶部!这一层就是我们通常说的
容器层
,容器之下的都叫镜像层!
4、commit镜像
指令:
docker commit 提交容器成为一个新的副本
# 命令和git原理类似
docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[版本TAG]
示例:修改tomcat容器,并提交成一个镜像
# 1、启动一个默认的tomcat
[root@9527 ~] docker run -d -p 8080:8080 tomcat
de57d0ace5716d27d0e3a7341503d07ed4695ffc266aef78e0a855b270c4064e
# 2、发现这个默认的tomcat 是没有webapps应用,官方的镜像默认webapps下面是没有文件的!
[root@9527 ~] docker exec -it de57d0ace571 /bin/bash
root@de57d0ace571:/usr/local/tomcat#
# 3、从webapps.dist拷贝文件进去webapp
root@de57d0ace571:/usr/local/tomcat: cp -r webapps.dist/* webapps
root@de57d0ace571:/usr/local/tomcat: cd webapps
root@de57d0ace571:/usr/local/tomcat/webapps: ls
ROOT docs examples host-manager manager
# 4、将操作过的容器通过commit调教为一个镜像!我们以后就使用我们修改过的镜像即可,而不需要每次都重新拷贝webapps.dist下的文件到webapps了,这就是我们自己的一个修改的镜像
[root@9527 ~] docker commit -a="9527code" -m="add webapps app" de57d0ace571 tomcat02:1.0
sha256:d5f28a0bb0d0b6522fdcb56f100d11298377b2b7c51b9a9e621379b01cf1487e
[root@9527 ~] docker images|grep -i tomcat
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat02 1.0 d5f28a0bb0d0 14 seconds ago 652MB
tomcat latest 1b6b1fe7261e 5 days ago 647MB
# 看到镜像大小比官方的大一点,因为我加了几个文件
如果你想要
保存当前容器的状态
,就可以通过commit来提交,获得一个镜像
,就好比我们我们使用虚拟机的快照。
二、数据卷
1、数据卷
如果数据存储在容器中,那删除容器,数据就会丢失,因此需要对数据进行持久化。基于此,Docker有数据卷技术,实现了:
- Docker容器中产生的数据同步到本地
- 容器之间可以数据共享
如下图中,将容器中的/usr/mysql目录映射到宿主机的/home/mysql目录:(其实就是挂载,把容器内的目录,挂载到宿主机上面)
Volume是外部默认的联合文件系统或者是存在于宿主文件系统中正常的文件或文件夹。
总结一句话:容器的持久化和同步操作!容器间也可以共享数据!
2、-v使用数据卷
docker run -it -v 主机目录:容器目录 ...
[root@9527 home] docker run -it -v /home/ceshi:/home centos /bin/bash
使用docker inspect 查看容器元数据信息中的挂载信息:
此时,在容器内创建文件,宿主机目录中同步数据成功。在宿主机创建文件,容器中对应目录也同步。
停止容器 ⇒ 主机上修改文件 ⇒ 启动容器
⇒ 容器内的数据依旧是同步的:
从此,对于上篇中的nginx、tomcat…,只需要修改对应的本地目录,容器内的数据也会自动同步更新。
3、实战:MySQL 同步数据
# 获取镜像
[root@9527 home] docker pull mysql:5.7
# 运行容器, 需要做数据挂载!
# 安装启动mysql,需要配置密码(注意)
# 官方测试指令 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
# 启动我们的
-d # 后台运行
-p # 端口映射
-v # 卷挂载。可指定多个
-e # 环境配置
--name # 容器的名字
[root@9527 home] docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
9552bf4eb2b69a2ccd344b5ba5965da4d97b19f2e1a78626ac1f2f8d276fc2ba
# 启动成功之后,我们在本地使用navicat链接测试一下
# navicat链接到服务器的3310 ---> 3310和容器的3306映射,这个时候我们就可以连接上mysql了
# 在本地测试创建一个数据库,查看本地目录中,数据持久化同步成功。
此时彻底删除容器:
docker rm -f mysql01
本地目录中的数据不受影响,这就实现了容器数据持久化的功能!
4、docker volume相关指令
[root@9527 ~] 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.
- docker volume ls 列出所有的数据卷
# 只需要展示数据卷的名称,使用-q
[root@9527 ~] docker volume ls
DRIVER VOLUME NAME
local 561b81a03506f31d45ada3f9fb7bd8d7c9b5e0f826c877221a17e45d4c80e096
- docker volume create 创建一个数据卷
[root@9527 ~] docker volume create centos-volume
- docker volume inspect 查看卷的信息
[root@9527 ~] docker volume inspect centos-volume
- docker volume prune 移除未使用的数据卷
[root@9527 ~] docker volume prune
- docker volume rm 移除一个或多个数据卷,不能移除被容器使用的数据卷
# -f, --force Force the removal of one or more volumes
[root@9527 ~]# docker volume rm 8aee2f0aa880dc1892a01c211101f6360158ae64867b90e5ad3685d7a4ca9496
Error response from daemon: remove 8aee2f0aa880dc1892a01c211101f6360158ae64867b90e5ad3685d7a4ca9496:
volume is in use - [857601f85a5adf1e1305f2f6a7c317431af6841d750b7dd947a03b0725e0ae49]
# 需要先移除该容器(只停止是不行的)
5、匿名和具名挂载
匿名挂载:-v 后面只写了容器内的路径
# 匿名挂载
-v 容器内路径
docker run -d -P --name nginx01 -v /etc/nginx nginx # -P 随机指定端口
# 查看所有volume的情况
[root@9527 ~] docker volume ls
DRIVER VOLUME NAME
local 561b81a03506f31d45ada3f9fb7bd8d7c9b5e0f826c877221a17e45d4c80e096
local 36083fb6ca083005094cbd49572a0bffeec6daadfbc5ce772909bb00be760882
# 这里发现,这种情况就是匿名挂载,我们在-v 后面只写了容器内的路径,没有写容器外的路径!
具名挂载:-v后面给卷起个名字,注意没有/,更不是宿主机的本地目录路径。即-v 卷名:容器内的路径
# 具名挂载
[root@9527 ~] docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
26da1ec7d4994c76e80134d24d82403a254a4e1d84ec65d5f286000105c3da17
[root@9527 ~] docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
26da1ec7d499 nginx "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 0.0.0.0:32769->80/tcp nginx02
486de1da03cb nginx "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:32768->80/tcp nginx01
[root@9527 ~] docker volume ls
DRIVER VOLUME NAME
local 561b81a03506f31d45ada3f9fb7bd8d7c9b5e0f826c877221a17e45d4c80e096
local juming-nginx
# 通过-v 卷名:容器内的路径
# 查看一下这个卷
[root@9527 ~] docker volume inspect juming-nginx
[
{
"CreatedAt": "2020-08-12T18:15:21+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
"Name": "juming-nginx",
"Options": null,
"Scope": "local"
}
]
所有docker容器内的卷,没有指定目录的情况下都是在 /var/lib/docker/volumes/xxxxx/_data
具名挂载可以方便的找到我们的一个卷,大多数情况下使用的是具名挂载。区别:
# 如何确定是具名挂载还是匿名挂载,还是指定路径挂载!
-----------------
-v 容器内路径 // 匿名挂载
-v 卷名:容器内路径 //具名挂载
-v /主机路径:容器内路径 // 指定路径挂载
关于权限ro、rw:
# 通过 -v 容器内容路径 ro rw 改变读写权限
ro readonly # 只读
rw readwrite # 可读可写
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
# ro 只要看到ro就说明这个路径只能通过宿主机来操作,容器内容无法操作
6、数据卷之Dockerfile
Dockerfile就是用来构建docker镜像的构建文件(命令脚本),通过这个脚本可以生成镜像,镜像是一层一层的,脚本一个个的命令,每个命令都是一层。
# 创建一个dockerfile文件, 名字可以随机,但建议Dockerfile
# 文件的内容 指定(大写) 参数
FROM centos
VOLUME ["volume01", "volume02"] # 代表容器内的两个目录,即匿名挂载
CMD echo "----end----"
CMD /bin/bash
# 这里的每一个命令都是镜像的一层!
开始构建:
拿构建的镜像启动容器:
这个卷和外部宿主机有一个同步的目录:
inspect查看容器元信息中数据卷的映射关系:
这种方式我们未来使用的十分多,假设构建镜像时候没有挂载卷,要手动镜像挂载 -v 卷名:容器内路径!
7、数据卷容器
docker02挂载了docker01,docker01就叫数据卷容器。
# 使用上面自己构建的镜像启动两个容器docker01、docker02
docker run -it --name docker01 code-9527/centos:1.0
# docker02挂载docker01
docker run -it --name docker02 --volumes-from docker01 code-9527/centos:1.0
同样的,在docker02中创建文件,docker01中也能同步成功。即--volumes-from实现了容器间数据的共享。
# 也可以再挂一个docker03
docker run -it --name docker03 --volumes-from docker01 code-9527/centos:1.0
此时,docker02和docker03都挂载于docker01,删除docker01,02和03的数据还是能正常访问,这里是拷贝的关系!
基于以上的测试,就可以实现多个mysql容器数据的共享:
[root@9527 home] docker run -d -p 3344:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
[root@9527 home] docker run -d -p 3345:3306
-v /etc/mysql/conf.d -v /var/lib/mysql
-e MYSQL_ROOT_PASSWORD=123456 --name mysql02
--volumes-from mysql01 mysql:5.7
总结:
容器之间配置信息的传递, 数据卷容器的生命周期一直持续到没有容器使用为止(复制拷贝的关系)
且一旦你持久化到了本地,这个时候,本地的数据是不会删除的!