Docker镜像讲解
镜像是什么
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
如何得到镜像:
- 从远程仓库下载
- 拷贝别人的
- 自己制作一个镜像DockerFile
Docker镜像加载原理
UnianFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
bootfs(boot file system)主要包含pootloader
和kernel
,bootloader
主要是引导加载kernel,Linux刚启动时会加载bootfs.文件系统,在Docker镜像的最底层是oootfs。这一层与我们典型的Linux/Unixⅸ系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs4转交给内核,此时系统也会卸载bootfs。
rootfs(root file system),在bootfs之上。包含的就是典型Linux系统中的/dev,/proc,bin,/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Jbuntu,Centos等等。
Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统JnionFS。
当用docker run启动这个容器时,实际上在镜像的顶部添加了一个新的可写层。这个可写层也叫容器层。
总的来说:因为Docker镜像是分层的,因此在加载一个镜像的时候,会按照从底层到高层的顺序依次加载该镜像所需要的镜像层。在加载的过程中,如果当前镜像层已经存在,则会跳过当前镜像层。
分层理解
分层镜像
所有的Dock镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于Ubuntu Linux16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
该镜像当前已经包含3个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件。
这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。
Docker通过存储擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。Linux上可用的存储引擎有AUFS、Overlay:2、Device Mapper、Btrfs以及ZFS。顾名思义,每种存储引擎都基于Linux中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。
特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!
这一层就是我们通常说的容器层,容器之下的都叫镜像层!
容器数据卷
什么是容器数据卷
docker的理念回顾
将应用和环境打包成一个镜像!
如果数据都在容器中,那么我们将容器删除,数据就会丢失!
所以我们希望数据可以持久化。
容器之间可以有一个数据共享的支术!Docker容器中产生的数据,同步到本地!
这就是卷技术!目录的挂载,将我们容器内的目录,挂载到Linux上面!
使用数据卷
方式一:直接使用命令来挂载
docker run -it -v 主机目录:容器目录
#测试
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker run -it -v /home/test:/home centos /bin/bash
#启功后,通过docker inspect 容器id
测试文件同步
#1、在主机上建立一个文件(或是在容器对应目录中建立一个文件)
#2、可以发现在容器(或是主机中)对应目录中有该文件
即使容器停止,只要容器还在,主机修改对应目录文件后,启动容器后就会同步过去
具名挂载和匿名挂载
#匿名挂载
-v 容器内路径!
docker run -d -P --name nginx01 -v /ect/nginx nginx
#查看所有的volume的情况
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker volume ls
DRIVER VOLUME NAME
local 3ec15df8c594e6d69eae596ae597c3a30b3f1640c67ff292fb6b38986af800d8
#可以发现,这种就是匿名挂载,我们在-v只写了容器内的路径,没有写容器外的路径!
#具名挂载
[root@iZbp1cobq68xtff4clzo6uZ home]# docker run -d -p 3344:80 --name nginx02 -v juming-nginx:/etc/nginx nginx
f9d25a9a5c17391024b34cbc258764b9f333a90a7daef6807b981de86e48d537
[root@iZbp1cobq68xtff4clzo6uZ home]# docker volume ls
DRIVER VOLUME NAME
local juming-nginx
#通过 -v 卷名:容器内路径
#查看一下这个卷
#命令
docker volume inspect juming-nginx
所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/juming-nginx/_data
我们通过具名挂载可以方便的找到我们的一个卷,大多数情况在使用的具名挂载
#如何确定是具名挂载还是匿名挂载,还是指定路径挂载!
-v 容器内路径 #匿名挂载
-v 卷名:容器内路径 #具名挂载
-v /宿主机路径:容器内路径 #指定路径挂载
DockerFile
DockerFile介绍
dockerfile是用来构建docker镜像的文件!命令参数脚本!
构建步骤:
1、编写一个dockerfile文件
2、docker build构建成为一个镜像
3、docker run运行镜像
4、docker push发布镜像(DockerHub、阿里云镜像仓库!)
查看官方是这么做的?
DockerFile构建过程
基础知识
1、每个保留关键字(指令)都是必须是大写字母
2、执行从上到下顺序执行
3、# 表示注释
4、每一个指令都会创建提交一个新的镜像层,并提交!
dockerfile是面向开发的,我们以后要发布项目,做镜像,就需要编写dockerfile文件,这个文件十分简单!
DockerFile:构建文件,定义了一切的步骤,源代码
DockerImages:通过DockerFile构建生成的镜像,最终发布和运行的产品!
Docker容器:容器就是镜像运行起来提供的服务!
DockerFile的指令
FROM #基础镜镜像,,一切从这里开始构建
MAINTAINER #镜像是谁写的,姓名+邮箱
RUN #镜像构建的时候需要运行的命令
ADD #步骡:tomcat镜像,这个tomcat压缩包中添加内容
WORKDIR #镜像的工作目录
VOLUME #挂载的目录
EXPOSE #保留端口配置
CMD #指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT #指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD #当构建一个被继承DockerFi1e这个时候就会运行ONBUILD的指令。触发指令。
COPY #类似ADD,将我们文件拷贝到镜像中
ENV #构建的时候设置环境变量!
CMD和ENTRPOINT的区别
CMD #指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT #指定这个容器启动的时候要运行的命令,可以追加命令
测试CMD
#编写dockerfi1e文件
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#vim dockerfile-cmd-test
FROM centos
CMD ["1s","-a"]
#构件镜像
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#docker build -f dockerfile-cmd-test -t cmdtest
#run运行,发现我们的ls非a命令生效
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#docker run dd8e4401d72f
dockerenv
bin
dev
etc
home
lib
1ib64
#想追加一个命令 -l ls -al
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#docker run dd8e4401d72f -l
docker:Error response from daemon:OCI runtime create failed:container_linux.go:349:starting container process caused "exec:\"-1\":executable file not found in SPATH":unknown.
# cmd的清理下 -l 替换了CMD["ls","-a"]命令,-l 不是命令所以报错!
测试ENTRPOINT
#编写dockerfi1e文件
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#vim dockerfile-cmd-test
FROM centos
NTRPOINT ["1s","-a"]
#使用NTRPOINT后允许在运行时追加指令,如:
[root@iZbp1cobq68xtff4clzo6uZ dockerfile]#docker run dd8e4401d72f -l
Docker网络
理解docker0网络
首先清空docker的所有环境(镜像、容器)
ip 测试
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker exec -it 35b2f6e5a361 ip addr
#使用以上命令可能会报错,原因是:下载的tomcat容器没有该类型的命令,所有需要先下载指令---使用下面的命令
apt update && apt install -y iproute2
#查看容器的内部网络地址 ip addr
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker exec -it 35b2f6e5a361 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
66: eth0@if67: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@iZbp1cobq68xtff4clzo6uZ ~]# curl localhost:4915
curl: (7) Failed connect to localhost:4915; Connection refused
#docker容器外ping容器内部
[root@iZbp1cobq68xtff4clzo6uZ ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.052 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.052 ms
#发现可以ping通容器内部
原理
1、每启动一个docker容器,docker就会给docker容器分配一个ip,只要安装了docker,就会有一个网卡docker0桥接模式,使用的技术是evth-pair技术!
再次查看主机的ip,发现多了一个67:venth00b31bd@if66
2、再启动一个容器测试
# 我发现这个容器的网卡,都是一对一对的
# evth-pair 就是一对的虚拟设备接口,他们都是成对出现的,一段连着协议,一端彼此相连
# 正因为有这个特性,evth-pair充当一个桥梁,连接各种虚拟网络设备
# OpenStac ,Docker容器的连接,OVS的连接,都是使用 evth-pair技术
3、测试tomcat01和tomcat02
[root@iZbp1cobq68xtff4clzo6uZ ~]# ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.053 ms
#结论:容器和容器之间是可以互相通信的!!!
绘制一个网络模型图:
结论:tomcat01和tomcat02是公用的一个路由器—docker0
所有的容器不指定网络的情况下,都是docker0路由的,docker会给我们的容器分配一个默认的可用IP
小结
Docker使用的是Linux的桥接,宿主机中是一个Docker容器的网桥—docker0.
Docker中的所有的网络接口都是虚拟的,虚拟的转发效率高。
只要容器删除,对应的网桥一对就没有了。
容器互联–link
docker容器中如果无法使用ping命令!!!
#先更新apt
apt-get update
#安装ping有关的命令
apt install iputils-ping
思考一个场景,我们编写了一个微服务,database url=ip:,项目不启动,数据库ip换掉了,我们希望可以处理这个问题,可以名字来进行访问容器?
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker exec -it tomcat01 ping tomcat02
ping: tomcat02: Name or service not known
#再运行一个容器并通过--linke连接另一个容易
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat:8
#再一次通过tomca03 ping tomcat02
[root@iZbp1cobq68xtff4clzo6uZ ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.123 ms
#反向ping,使用tomcat02 ping tomcat02
[root@iZbp1cobq68xtff4clzo6uZ home]# docker exec tomcat02 ping tomcat03
ping: tomcat03: Name or service not known #发现并不能ping通
探究:inspect
查看tomcat03的host配置文件
–link就是在hosts的配置文件中增加了一个与之相连的容器的IP地址
自定义网络
查看所有的docker网络
[root@iZbp1cobq68xtff4clzo6uZ home]# docker network ls
NETWORK ID NAME DRIVER SCOPE
8d3e676f2dc9 bridge bridge local
17ff3f6e961e host host local
149c99e5d3ac none null local
网络模式
bridge:桥接docker(默认,自己桥接也使用bridge模式)
none:不配置网络
host:和宿主机共享网络
container:容器网路连通!(用的少!局限很大)
测试
#我们直接启动的命令,--net bridge 而这个就是我们的docker0
docker run -d -P --name tomcat01 tomcat
docker run -d -P --name tomcat01 --net bridge tomcat
# docker特点,默认域名不能访问,--link可以打通连接!
#自定一个bridge网络
# --driver bridge 模式
# --subnet 网段 定义一个网段
# --gateway ip 定义网关
[root@iZbp1cobq68xtff4clzo6uZ home]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
dcee3b34689e108d0a2528382fd208524cddad0569cd8cd7aee139b1e2d19e6f
[root@iZbp1cobq68xtff4clzo6uZ home]# docker network ls
NETWORK ID NAME DRIVER SCOPE
8d3e676f2dc9 bridge bridge local
17ff3f6e961e host host local
dcee3b34689e mynet bridge local
149c99e5d3ac none null local
#通过自己定义的网络,运行两个容器tomcat01-net-02、tomcat01-net-01
#再查看自定义网络的消息,结果如下:
"ConfigOnly": false,
"Containers": {
"8419874cc781dbd857eaa79d7ab739ddfe3be240827907e0100e4ac93ef28b8a": {
"Name": "tomcat01-net-02",
"EndpointID": "db00c84a4eda1a5ba0ac60d62bb17b70b417e29b0804fd4b9c8c6bc96bddb815",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"b38602b97407d5d09d18fa9df9858d08841a211002e003cb0fe2b3c19b1e8ec0": {
"Name": "tomcat01-net-01",
"EndpointID": "2e4aa45d3217a5894fc2a14bc6ce829a5cccee5a8ece16f28a8a5e50388d13e0",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
#再次测试ping连接
[root@iZbp1cobq68xtff4clzo6uZ home]# docker exec -it tomcat01-net-01 ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.097 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.069 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.073 ms
64 bytes from 192.168.0.3: icmp_seq=4 ttl=64 time=0.078 ms
^C
--- 192.168.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.069/0.079/0.097/0.010 ms
#不使用--link也可以使用”ping 名字“去连接测试了
[root@iZbp1cobq68xtff4clzo6uZ home]# docker exec -it tomcat01-net-01 ping tomcat01-net-02
PING tomcat01-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.081 ms
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.080 ms
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.081 ms
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=4 ttl=64 time=0.103 ms
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=5 ttl=64 time=0.070 ms
64 bytes from tomcat01-net-02.mynet (192.168.0.3): icmp_seq=6 ttl=64 time=0.078 ms
自定义的网络docker都已经维护了加入该网段容器的对应关系,推荐使用这个方法!!!
网络连通
连接一个容器到一个网络
#命令:
Usage: docker network connect [OPTIONS] NETWORK CONTAINER
#测试命令
[root@iZbp1cobq68xtff4clzo6uZ home]# docker network connect mynet tomcat01
#使用命令查看mynet的网络详情
[root@iZbp1cobq68xtff4clzo6uZ home]# docker network inspect mynet
#连通之后,就是将tomcat01放到了自定义网络mynet网络下
#这个一个容器就有两个IP地址
# 测试 tomcat01 ping tomcat01-net-01
[root@iZbp1cobq68xtff4clzo6uZ home]# docker exec -it tomcat01 ping tomcat01-net-01
PING tomcat01-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat01-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.061 ms
#由于tomcat02并没有加入自定义网络,所以不能ping通该网络中的容器
[root@iZbp1cobq68xtff4clzo6uZ home]# docker exec -it tomcat02 ping tomcat01-net-01
ping: tomcat01-net-01: Name or service not known