Docker03-镜像管理

什么是Docker镜像?

Docker镜像是由文件系统堆叠而成。

最低层是引导文件系统,即bootfs,类似linux/unix的引导文件系统。docker用户几乎永远不会和bootfs交互。

从下往上第二层是root文件系统,即rootfs,rootfs可以是一种或多种操作系统(如debian或者ubuntu)。传统的linux引导过程中,rootfs会先以只读的方式加载,当引导结束后,会切换为读写模式。而在docker中,rootfs永远都是只读模式。

docker利用联和加载技术(union mount)在rootfs上加载更多的只读文件系统,将多个文件系统按层叠加在一起,在外面只能看到一个文件系统,这样最终的文件系统会包含所有的底层文件和目录。

docker将这样各层的只读文件系统成为镜像,一个镜像可以位于另外一个镜像的上面,下面的镜像称为上面镜像的父镜像,类似栈的结构,最底部的镜像称为基础镜像。

当容器启动后,会在最顶层加载一个读写层,docker中运行的程序就是在这个读写层中运行的。

从网上找一张图来说明(图片来自网络):

docker plex 文件权限 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。

从registry中拉取和搜索镜像

从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 "laozhou@blog.csdn.net"
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 "laozhou@blog.csdn.net"
 ---> 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

可以看出构建的流程:

  1. 如果基础镜像不在本地,则下载基础镜像
  2. 基于基础镜像,运行一个新的容器,在容器中执行一条指令,完成后提交为一个新的镜像层,删除容器
  3. 基于第二步的新的镜像,运行一个新的容器,在容器中执行下一条指令,完成后提交为一个新的镜像层,删除容器
  4. 重复第3步,直到所有指令执行完成。

可以使用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:这两个命令没有真实跑,你在跑的时候,要确保容器名称唯一。

Dockerfile指令介绍

  • 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/srcRUN ["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,这个话题具体可在具体用到了在深入。