Docker镜像详解
1、镜像是什么
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
所有应用打包成docker镜像,就可以直接跑起来
2、UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
3、Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是UnionFS。
bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。
这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M??
对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。
4、镜像分层
以我们的pull命令为例,在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载 。
以tomcat举例,平时使用中的tomcat只有一百兆左右,但是docker镜像文件有四百多兆,因为它会进行多层封装,导致如此。
docker为什么要采用镜像分层?
最大的好处就是共享资源。例如:有多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,同时内存中也只需加载一份base镜像,就可以为所有容器服务了。而镜像的每一层都可以被共享。
Docker分层的特点:
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称为“容器层”,“容器层”之下的都叫“镜像层”。
5、Commit镜像
命令:
docker commit -m="提交描述信息" -a="作者" 容器ID 镜像名[:TAG]
测试:
docker运行默认tomcat容器中,webapps下面默认没有任何项目文件,所以访问会404。而自带的项目文件都在webapps.dist文件下面。
我们需要做的时将webapps.dist下面的项目文件拷贝到webapps下面,然后将容器打包成镜像发布出去。
#查看正在运行的tomcat的容器ID
[root@Eva ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c3dd6ff5f8a4 tomcat "catalina.sh run" 8 weeks ago Up 3 seconds 0.0.0.0:8080->8080/tcp tomcat8080
#进入容器内部
[root@Eva ~]# docker exec -it c3dd6ff5f8a4 /bin/bash
#看到有webapps和webapps.dist两个文件夹
root@c3dd6ff5f8a4:/usr/local/tomcat# ls
BUILDING.txt LICENSE README.md RUNNING.txt conf lib native-jni-lib webapps work
CONTRIBUTING.md NOTICE RELEASE-NOTES bin include logs temp webapps.dist
#进入webapps目录下
root@c3dd6ff5f8a4:/usr/local/tomcat# cd webapps
#可以看到没有默认的项目文件
root@c3dd6ff5f8a4:/usr/local/tomcat/webapps# ls
#退回到上层目录
root@c3dd6ff5f8a4:/usr/local/tomcat/webapps# cd ..
#将webapps.dist中的项目目录拷贝到webapps下面
root@c3dd6ff5f8a4:/usr/local/tomcat# cp -r webapps.dist/* webapps
#再次进入webapps可以看到默认的一些项目文件
root@c3dd6ff5f8a4:/usr/local/tomcat# cd webapps
root@c3dd6ff5f8a4:/usr/local/tomcat/webapps# ls
ROOT docs examples host-manager manager
#退出到主机后使用commit将容器提交为一个新的镜像
[root@Eva ~]# docker commit -a="Miracle42" -m="add webapps default app" f3b47f4d17b8 tomcat42:1.0
sha256:092676e6974e6ade292edee30d6c0debc3d5aaf4cd3d5fb29c0c957356d20242
#查看新生成的镜像
[root@Eva ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat42 1.0 092676e6974e 17 seconds ago 550MB
Docker容器数据卷
1、什么是容器数据卷
从docker理念来讲:
- docker将运用与运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对数据的要求希望是持久化的。
- 容器之间希望可能共享数据。
Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据作为镜像的一部分保存下来,那么当容器删除后,数据自然也没有了。
所以为了能够保存数据,自然出现了docker容器数据卷。
简而言之:Docker容器数据卷就是做数据持久化,保存容器数据,同时也可以做到容器间的数据共享
2、容器数据卷的作用
卷就是目录或者文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持久化存储或共享数据的特性。
卷的设计目的就是数据的持久化,完全独立于容器的生命周期,因此Docker不会再容器删除时删除其挂载的数据卷。
其特点:
- 数据卷可在容器之间共享或重用数据。
- 卷中的更改可以直接生效。
- 数据卷中的更改不会包含在镜像的更新中。
- 数据卷的生命周期一直持续到没有容器使用它为止。
3、容器数据卷的使用
1)直接使用命令(-v)进行挂载
输入以下指令,将实现卷挂载,如果文件夹不存在,将重新创建。
docker run -v /宿主机绝对路径目录:/容器内目录 镜像名
#命令
docker run -v /主机目录:/容器目录 镜像ID
#运行centos镜像,并进行挂载
[root@Eva ~]# docker run -it -v /root/home:/home centos /bin/bash
#进行容器内部对挂载的目录进行操作
[root@0e6ece00287a /]# cd /home
[root@0e6ece00287a home]# ls
[root@0e6ece00287a home]# mkdir testv
[root@0e6ece00287a home]# exit
exit
#可以看到操作同步到了主机目录,
[root@Eva ~]# ls /root/home
testv
#例子2:挂载mysql配置文件和数据文件到本地
docker run -d -p 3306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql
验证挂载是否成功,输入docker inspect查看详细信息,若有如下信息则表示挂载成功:
挂载成功后,容器与宿主操作文件都会相互同步,即使容器exit退出后,重连即可恢复连接。
2)具名和匿名挂载
# 三种挂载: 匿名挂载、具名挂载、指定路径挂载
-v 容器内路径 #匿名挂载
-v 卷名:容器内路径 #具名挂载
-v /宿主机路径:容器内路径 #指定路径挂载 docker volume ls 是查看不到的
# 匿名挂载
-v 容器内路径!
docker run -d -P --name nginx01 -v /etc/nginx nginx
# 查看所有的volume的情况
➜ ~ docker volume ls
DRIVER VOLUME NAME
local 33ae588fae6d34f511a769948f0d3d123c9d45c442ac7728cb85599c2657e50d
# 这里发现,这种就是匿名挂载,我们在 -v只写了容器内的路径,没有写容器外的路劲!
# 具名挂载
➜ ~ docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
➜ ~ docker volume ls
DRIVER VOLUME NAME
local juming-nginx
# 通过 -v 卷名:容器内路径
# 查看一下这个卷
所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/xxxx/_data
下
如果指定了目录,docker volume ls 是查看不到的
带权限的挂载
# 通过 -v 容器内路径: ro rw 改变读写权限
ro #readonly 只读
rw #readwrite 可读可写
docker run -d -P --name nginx05 -v juming:/etc/nginx:ro nginx
docker run -d -P --name nginx05 -v juming:/etc/nginx:rw nginx
ro 只要看到ro就说明这个路径只能通过宿主机来操作,容器内部是无法操作!
3)使用Dockerfile进行挂载
Dockerfile就是用来构建Docker镜像的构建文件,是一个命令脚本,通过这个脚本可以生成一个镜像。
使用vim创建一个名为dockerfile01的Dockerfile文件
#编写一个Dockerfile
FROM centos #源于centos
VOLUME ["volume01","volume02"] #指定两个匿名挂载
cmd echo "-------end---------"
cmd /bin/bash
使用build对Dockerfile构建镜像
#编写Dockerfile文件
[root@Eva docker-test-volume]# vim dockerfile01
[root@Eva docker-test-volume]# cat dockerfile01
FROM centos
VOLUME ["volume01","volume02"]
cmd echo "-------end---------"
cmd /bin/bash
#使用build根据dockerfile01生成镜像
-f #指定dockerfile的文件的位置
-t #指定生成镜像的名称不能以/或者大写字母开头
[root@Eva docker-test-volume]# docker build -f dockerfile01 -t miracle42/centos:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 470671670cac
Step 2/4 : VOLUME ["volume01","volume02"]
---> Running in b2173ff97d4e
Removing intermediate container b2173ff97d4e
---> db365b613255
Step 3/4 : cmd echo "-------end---------"
---> Running in 8975a769bd28
Removing intermediate container 8975a769bd28
---> 28ebe5f2f0fa
Step 4/4 : cmd /bin/bash
---> Running in 8d394a585647
Removing intermediate container 8d394a585647
---> 5800ff4ec9b5
Successfully built 5800ff4ec9b5
Successfully tagged miracle42/centos:1.0
#查看新生成的镜像
[root@Eva docker-test-volume]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
miracle42/centos 1.0 5800ff4ec9b5 28 seconds ago 237MB
下面的命令可以查看当前容器主机挂载地址
[root@Eva home]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f966869e014 5800ff4ec9b5 "/bin/bash" 5 minutes ago Up 5 minutes jolly_shannon
[root@Eva home]# docker inspect 7f966869e014
进入容器内部创建文件,然后在主机的挂在地址查看文件是否同步过来:
#在容器内部的数据卷中创建文件
[root@7f966869e014 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var volume01 volume02
[root@7f966869e014 /]# cd volume01
[root@7f966869e014 volume01]# touch container.txt
#在主机中进入上面找到的挂载地址,可以看到里边已经有了刚才在容器中创建的文件
[root@Eva home]# cd /var/lib/docker/volumes/a06277c1c1faf41909e286f3db4bbe1ed7d73b43fbc28459e922360c8c35e5bd/_data
[root@Eva _data]# ls
container.txt
4)容器间挂载同步
首先启动我们之间创建的miracle42/centos镜像生成名为centos01的容器,可以看到容器内部有volume01,volume02两个挂载目录。
[root@Eva _data]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
miracle42/centos 1.0 5800ff4ec9b5 25 minutes ago 237MB
[root@Eva _data]# docker run -it --name centos01 5800ff4ec9b5 bash
[root@6a943a955478 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var volume01 volume02
接着再次启动一个miracle42/centos镜像命名为centos02,并且使用--volumes-from
命令实现与centos01进行挂载同步。可以看到centos02内部同样有volume01,volume02两个挂载目录
[root@Eva _data]# docker run -it --name centos02 --volumes-from centos01 5800ff4ec9b5
[root@ac27409b73fd /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var volume01 volume02
在centos02容器中的volume01中创建一个文件
[root@ac27409b73fd /]# cd volume01
[root@ac27409b73fd volume01]# ls
[root@ac27409b73fd volume01]# mkdir centos02.txt
[root@ac27409b73fd volume01]# exit
exit
进入centos01容器中的volume01仍然可以看到此文件
[root@Eva _data]# docker exec -it 6a943a955478 bash
[root@6a943a955478 /]# cd volume01
[root@6a943a955478 volume01]# ls
centos02.txt
删除centos01,centos02依旧可以访问这个文件
容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用它为止。