本文参考《第一本Docker书》
一、Docker镜像原理
Docker镜像是由文件系统叠加而成。最底端是一个引导文件系统,即bootfs,这很像典型的Linux/Unix的引导文件系统。Docker用户几乎永远不会和引导文件系统有什么交互。实际上,当一个容器启动后,它会被移到内存中,而引导文件系统则会被卸载,以留出更多的内存供initrd磁盘镜像使用。
到目前为止,Docker看起来还很像一个典型的Linux虚拟化栈。实际上,Docker镜像的第二层是root文件系统rootfs,它位于引导文件系统之上。rootfs可以是一种或多种操作系统(如Debain或者Ubuntu文件系统)。
在传统的Linux引导过程中,root文件系统会最先以只读的方式加载,当引导结束并完成了完整检查之后,它才会被切换为读写模式。但是在Docker里,root文件系统永远只能是只读状态,并且Docker利用联合加载技术又会在root文件系统层上加载更多的只读文件系统。联合加载指的是一次同时加载多个文件系统,但是在外面看起来只能看到一个文件系统。联合加载会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。
Docker将这样的文件系统称为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像称为父镜像(parent image),可以依次类推,直到镜像栈的最底部,最底部的镜像称为基础镜像(base image)。最后,当从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统。我们想在Docker中运行的程序就是在这个读写层中执行的。
当Docker第一次启动一个容器时,初始读写层是空的。当文件系统发生变化时,这些变化都会应用到这一层上。比如,如果想修改一个文件,这个文件首先会从该读写层下面的只读层复制到读写层。该文件的只读版本依然存在,但是已经被读写层中的该文件副本所隐藏。
二、构建镜像
构建Docker镜像有以下两种方法。
- docker commit命令
- docker build命令和Dockerfile文件
1.docker commit
我们并不推荐使用docker commit命令,而是使用更灵活更强大的Dockerfile来构建Docker镜像。
docker commit 4aad2 username/testimage
更详细介绍参考https://blog.csdn.net/zhanglingge/article/details/47980289中第二章,第4节
2.Dockerfile构建镜像
1.创建一个Dockerfile
mkdir static_web
cd static_web
touch Dockerfile
2.编辑Dockerfile
# Version 0.0.1
FROM ubuntu:20.04
MAINTAINER James Turnbull "james@example.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80
由指令和参数组成。每条指令,如FROM,都必须为大写字母,且后面要跟随一个参数。也支持注释如上文的第一条。Docker大体上按照如下流程执行Dockerfile中指令。
- Docker从基础镜像运行一个容器。
- 执行一条指令,对容器作出修改。
- 执行类似docker commit的操作,提交一个新的镜像层。
- Docker再基于刚提交的镜像运行一个新容器。
- 执行Dockerfile中的下一条指令,直到所有指令都后自行完毕。
命令说明
- FROM 每个Dockerfile的第一条指令必须是FROM。FROM指令指定一个已经存在的镜像,后续指令都将基于此镜像进行,这个镜像被称为基础镜像(base image)。
- MAINTAINER指令,告诉Docker该镜像的做事是谁,以及作者的电子邮件。
- RUN 在当前镜像中运行指定的命令。
- EXPOSE,告诉Docker该容器内的应用程序将会使用容器的指定端口。但这并不意味着可以自动访问任意容器运行中服务的端口(上文的80)。出于安全的原因,Docker并不会自动打开该端口,而是需要用户在使用docker run运行容器时来指定需要打开哪些端口。可以指定多个EXPOSE
3.构建镜像
docker build -t="jamtur01/static_web:1.0" . #如果不指定标签,默认是latest
每一步构建都会将结果提交为镜像,所以某一步出错,则镜像会在上一步构建的镜像停止,而在此构建时,会继续从此步骤开始构建,如果要彻底重头开始构建需要去缓存
docker build --no-cache -t="jamtur01/static_web:1.0" .
也可在文件中指定
ENV REFRESHED_AT 2014-07-01 #如果想刷新一个构建,只需要修改ENV指令中的日期,这样docker会重置缓存。
4.其它常用指令说明
- CMD 指定容器启动时要运行的命令,这点类似于RUN指令,只是RUN指令是指镜像被构建时要运行的命令,而CMD指定容器启动时运行的命令。这和使用docker run命令启动容器非常类似
终端中运行 docker run -it jamtur01/static_web /bin/true Dockerfile中使用 CMD ["/bin/true"] 以上效果相同 传参数 CMD ["/bin/true", "-l"] #此时将-l传递给了 /bin/true 当然也可以不使用数组而指定CMD指令,这时候Docker会在指定的命令前加上 /bin/sh -c 这样可能会导致意料之外的行为,所以Docker推荐以数组语法来设置要执行的命令。
三、容器间连接
三种方式
- 直接访问ip,看/etc/hosts获取ip地址。此方法不推荐,因为容器重启后ip很有可能会发生变化
- 使用Docker Networking 推荐方式
- 链接容器 --link,此方式是1.9版本以前的使用方式
此处着重介绍Docker Networking的使用
列出当前所有网络
user1@ubuntu:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0fea69edc6ab bridge bridge local
57e60d24f56a host host local
590ae358db23 none null local
其中bridge是容器启动后的默认加入的网络,且此网络内容器在没有其它操作的情况下是不可使用容器名互相访问的,如无法ping test1 (不过可以使用IP地址 ping 172.17.0.3)
1.创建一个网络
docker network create test_net
2.启动一个容器并加入到此网络中
docker run -d --net=test_net --name test1 ubuntu bash
3.将已有的容器加入到次网络中
docker network connect test_net test0 #test0是已有的容器,test_net是网络名
4.此时test1和test0即可实现通信,且无论两容器如何重启ip地址如何变化,均不影响。访问方式是容器名称,或者 test1.test_net 均可
如在test0中
ping test1
ping test1.test_net
容器可以加入多个网络中,且没加入一个cat /etc/hosts 会发现,容器多了一个ip地址。可知容器在多个网络中会使用不同的ip
root@5f1814390627:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 5f1814390627 #当前容器在网络bridge中的ip
172.18.0.4 5f1814390627 #当前容器在网络test_net中的ip
散记
1.docker exec与docker attach区别
a.exec是容器重新执行某项命令,attach是附着到容器上。
所以当ctl+d退出时,exec只是结束了当前任务(如当前bash)容器并不一定会停止。但是attach中,会直接将容器停止(即使容器是以-d形式运行)。
2.容器时间(时区)与主机时间(时区)不一致
三个方法
#方法1,共享主机的localtime来保证采用时区一致。
docker run -it --name time -v /etc/timezone:/etc/timezone -v /etc/localtime:/etc/localtime lingge879/django-dev:2.0 bash
#方法2, 复制主机的localtime
docker cp /usr/share/zoneinfo/Asia/Shanghai 容器id:/etc/localtime
docker cp /etc/timezone 容器id:/etc/timezone
#方法3
构建镜像时Dockerfile
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone