Docker镜像是由文件系统堆叠而成。
最低层是引导文件系统,即bootfs,类似linux/unix的引导文件系统。docker用户几乎永远不会和bootfs交互。
从下往上第二层是root文件系统,即rootfs,rootfs可以是一种或多种操作系统(如debian或者ubuntu)。传统的linux引导过程中,rootfs会先以只读的方式加载,当引导结束后,会切换为读写模式。而在docker中,rootfs永远都是只读模式。
docker利用联和加载技术(union mount)在rootfs上加载更多的只读文件系统,将多个文件系统按层叠加在一起,在外面只能看到一个文件系统,这样最终的文件系统会包含所有的底层文件和目录。
docker将这样各层的只读文件系统成为镜像,一个镜像可以位于另外一个镜像的上面,下面的镜像称为上面镜像的父镜像,类似栈的结构,最底部的镜像称为基础镜像。
当容器启动后,会在最顶层加载一个读写层,docker中运行的程序就是在这个读写层中运行的。
容器启动时,初始的读写层是空的。当文件系统发生变化时,这些变化会应用到读写层上,比如要修改一个文件,这个文件会先从下层的只读层中复制到读写层来,然后在读写层修改。只读层中这个文件的原版本还是存在,只不过被当前读写层屏蔽了。这种机制叫写时复制,这种机制可让镜像层永远都不发生变化。
➜ ~ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 00fd29ccc6f1 22 months ago 111MB
ubuntu latest 00fd29ccc6f1 22 months ago 111MB
仓库:一组镜像,每个镜像通过tag区分,相同的镜像可以有多个tag。
从docker hub拉取镜像,指定仓库名称和tag。
docker pull ubuntu:12.04
当在命令中不指定仓库的tag,默认会使用latest。
使用docker search可以搜索docker hub上公共的镜像
docker search puppet
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
puppet/puppetserver A Docker Image for running Puppet Server. ... 81
alekzonder/puppeteer GoogleChrome/puppeteer image and screensho... 59 [OK]
如果搜索到了,会返回仓库名,描述,用户评价,是否官方,是否自动构建等信息。自动构建后面单独说明。
如何构建自己的镜像?有两种方式,使用docker commit命令,以及使用Dockerfile文件和docker build命令。
docker commit命令不推荐使用,忽略过。
我们重点使用Dockerfile的方式来构建镜像。Dockerfile是一个文件,里面使用特定的指定来说明如何构建一个镜像,然后通过docker build命令读取Dockerfile文件完成镜像构建。
存放Dockerfile文件的目录,在docker中称为构建上下文,docker在构建过程中会将该目录中的文件和子目录上传到docker守护进程,这样守护进程在执行一些如ADD命令时,就可以访问上下文中的数据了。
➜ docker mkdir static_web
➜ docker cd static_web
➜ static_web touch Dockerfile
➜ static_web vim Dockerfile
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER laozhou "[email protected]"
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
➜ static_web docker build -t="zhouyinyan/static_web" .
构建的输出:
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
a7344f52cb74: Pull complete
515c9bb51536: Pull complete
e1eabe0537eb: Pull complete
4701f1215c13: Pull complete
Digest: sha256:2f7c79927b346e436cc14c92bd4e5bd778c3bd7037f35bc639ac1589a7acfa90
Status: Downloaded newer image for ubuntu:14.04
---> 2c5e00d77a67
Step 2/5 : MAINTAINER laozhou "[email protected]"
---> Running in a8f232cb9910
---> 800e1757a6fd
Removing intermediate container a8f232cb9910
Step 3/5 : RUN apt-get update && apt-get install -y nginx
---> Running in 784d3dbed63e
.......
---> ccebd5bf5d3e
Removing intermediate container 784d3dbed63e
Step 4/5 : RUN echo 'hi, i am in your container' > /usr/share/nginx/html/index.html
---> Running in cd251b3632bd
---> 9478a44a8cc4
Removing intermediate container cd251b3632bd
Step 5/5 : EXPOSE 80
---> Running in 7d1f835b521a
---> fe314188b0dd
Removing intermediate container 7d1f835b521a
Successfully built fe314188b0dd
Successfully tagged zhouyinyan/static_web:latest
可以看出构建的流程:
可以使用docker images查看新构建的镜像。如果想深入看镜像是如何构建出来的,使用docker history命令
➜ static_web docker history zhouyinyan/static_web
使用新的镜像,启动容器
➜ static_web docker run -d --name static_web -p 80 zhouyinyan/static_web nginx -g "daemon off;"
de80814742bf2c8a4ad4fc376dbb1d24b50bcb47c34dad3cfec1fd70b1193a42
-p : 控制容器暴露的端口。
可以使用docker port 或者docker ps 命令,查看容器端口和宿主机端口映射
➜ static_web docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
de80814742bf zhouyinyan/static_web "nginx -g 'daemon ..." 2 minutes ago Up 2 minutes 0.0.0.0:32768->80/tcp static_web
可以看到,容器的80端口映射到宿主机的32768端口上。docker会使用宿主机的32768-61000中的一个比较大的端口来映射容器端口。
通过curl访问下新的static_web容器看看:
➜ static_web curl localhost:32768
hi, i am in your container
当然我们在运行时,可以通过 -p 80:80 的方式来指定宿主机的端口。
docker run -d --name static_web -p 80:80 zhouyinyan/static_web nginx -g "daemon off;"
docker还提供一种简单的方式,即-P参数,它可以暴露在Dockerfile通过EXPOSE 指令公开的所有端口。
docker run -d --name static_web -P zhouyinyan/static_web nginx -g "daemon off;"
ps:这两个命令没有真实跑,你在跑的时候,要确保容器名称唯一。
CMD指令:指定容器启动时要运行的命令。类似RUN命令,只不过RUN命令是构建过程要运行的命令,而CMD是容器被启动时要执行的命令。
比如在docker run命令中指定要运行命令
docker run -ti zhouyinyan/static_web /bin/bash
可等效的在Dockerfile中
CMD ["/bin/bash"]
需要注意的是,如果在docker run命令中指定了要运行的命令,则会覆盖CMD指令中的命令。同时在Dockerfile只能有一个CMD指令。
ENTRYPOINT指令:类似CMD指令,也是指定容器启动时要运行的命令,但与CMD不同的是,它不会被docker run命令中的指定的参数覆盖,而是会把docker run命令中的参数传递给ENTRYPOINT指令指定的命令。
比如ENTRYPOINT指令如下:
ENTRYPOINT ["/usr/sbin/nginx"]
在doker run中指定参数
docker run -ti zhouyinyan/static_web -g "daemon off;"
此时,容器实际执行的命令时 /usr/sbin/nginx -g “daemon off;”。
可以使用ENTRYPOINT和CMD来完成,当docker run不指定参数时,使用默认参数,指定参数时,使用指定的参数这样的效果。
比如:
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
当docker run中不指定参数时:
docker run -ti zhouyinyan/static_web
实际运行的是:/usr/sbin/nginx -h
而当docker run中指定参数时:
docker run -ti zhouyinyan/static_web -g "daemon off;"
实际运行的是: /usr/sbin/nginx -g "daemon off;
ps: 可以使用 --entrypoint 覆盖 ENTRYPOINT指令。
WORKDIR指令:在容器内部设置一个工作目录,CMD指令、ENTRYPOINT指令 指定的程序会在这个目录下执行。
比如:
WORKDIR /opt/webapp/db
CMD ["pwd"]
ps:可以使用-w参数覆盖WORKDIR指令的目录。
ENV指令:在镜像构建过程中,指定环境变量。
EVN TARGET_DIR /opt/app
WORKDIR TARGET_DIR
ps: 可以使用-e参数来指定容器中的环境变量
USER:指定以什么样的用户去运行容器。
USER nginx
ps: 可以使用-u参数 覆盖。
VOLUME: 用来向基于镜像创建的容器添加卷,卷在后面单独说明。
VOLUME ["/opt/project"]
所有基于此镜像的容器都会创建一个/opt/project的挂载点。
ADD: 用来将构建上下文中的目录和文件复制到镜像中去,注意不能对构建上下文之外的文件进行ADD操作,除了URL。
ADD software.lic /opt/application/software.lic
ADD http://some.org/file.zip /data/file.zip
COPY: 类似ADD,不同的是COPY只会关注构建上下文中复制本地文件。
COPY conf.d/ /etc/apache2/
LABEL: 为Docker镜像添加元数据,元数据已键值对的形式。
LABEL version="1.0"
LABEL location="new york" type="data center" role="web server"
ONBUILD: 该指令中指定其他的Dockerfile指令,直接构建该镜像时不会允许指令。而当该镜像作为其他镜像的的基础镜像时(FROM指定的镜像),其他镜像在构建过程中会触发指定的指令,指令会插入到FROM指令之后。
比如:
ONBUILD ADD . /app/src
ONBUILD RUN ["cd","/app/src"]
那么在已该镜像作为基础镜像的新镜像在构建时,会执行ADD . /app/src
和RUN ["cd","/app/src"]
指令。有点像模板的作用。
其他如STOPSIGNAL,ARG等指令,如果使用到时,再参考即可。
构建好镜像后,可以使用docker push命令将之推送到docker hub上。
➜ static_web docker push zhouyinyan/static_web
ps: 你需要注册docker hub的账号,在push之前,通过docker login先登录。
在docker hub上可以定义自动构建(automated builds),我们只需要将github上包含Docerfile文件的仓库连接到docker hub上即可,这样,当向github的仓库推送代码时,会自动触发构建,生成镜像。具体操作请cloud.docker.com上自行完成,非常简单。
最后,如果镜像不想在使用了,使用docker rmi命令删除
docker rmi zhouyinyan/static_web
ps:如果镜像上有容器,则可先删除容器,在删除镜像,或者使用-f参数强制删除。
docker开源了docker registry,因此我们非常容易的可以运行自己私有的docker registry,这个话题具体可在具体用到了在深入。