朝花夕拾|勿忘初心 朝花夕拾|勿忘初心

使用Dockerfile构建镜像

in Docker read (478) 2616汉字 站长Lucifaer 文章转载请注明来源!

使用Dockerfile构建镜像

首先先写一个Dockerfile:

FROM ubuntu
MAINTAINER Lucifaer "924463052@qq.com"
RUN ["apt-get", "update"]
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
EXPOSE 80

这个Dockerfile由一系列指令和参数组成。每条命令,都必须为大写字母,且后面要跟随一个参数。Dockerfile中的指令会按顺序从上到下执行,所以应该根据需要合理安排指令。

Docker按照如下流程执行Dockerfile中的指令:

  • Docker从基础镜像运行一个容器。
  • 执行一条指令,对容器做出修改。
  • 执行类似docker commit的操作,提交一个新的镜像层。
  • Docker再基于刚提交的镜像运行一个新容器。
  • 执行Dockerfile中的下一条指令,直到所有指令都执行完毕。
  1. 每个Dockerfile的第一条指令必须是FROMFROM指令指定一个已经存在的镜像,后续指令都将基于该镜像进行,这个镜像被称为基础镜像。
  2. 接着指定了MAINTAINER指令,这条指令会告诉Docker该镜像的作者是谁,以及作者的联系方式。
  3. RUN指令会在当前镜像中运行指定的命令。默认情况下,RUN指令会在shell里使用命令包装器/bin/sh -c来执行。如果是在一个不支持shell的平台或者不希望在shell中运行,也可以使用exec格式的RUN指令:

    RUN ["apt-get", "install", "nginx"]
    
    这种方式中,使用一个数组来指定要运行的命令和传递给该命令的每个参数。
  4. 接着设置了EXPOSE指令,这条指令告诉Docker该容器内的应用程序将会使用容器的指定端口。Docker不会自动打开该端口,而是需要用户在使用docker run运行容器时来指定需要打开哪些端口。

可以通过EXPOSE指令来向外部公开多个端口。

基于Dockerfile构建新镜像

执行docker build命令,Dockerfile中的所有指令都被执行并提交,并且在该命令成功结束后返回一个新镜像。

docker build -t="lucifaer/static_web" .

上面使用docker build命令来构建新镜像。通过-t参数为新镜像设置了仓库和名称,例子中的仓库为lucifaer,镜像名为static_web

也可以在构建镜像的过程中为镜像设置一个标签,其使用方法为“镜像:标签”:

docker build -t="lucifaer/static_web:v1"

如果没有制定任何标签,Docker将会自动为镜像设置一个latest标签。

上面命令中的.是告诉Docker到本地目录中去找Dockerfile文件。也可以制定一个Git仓库的源地址来指定Dockerfile的位置:

docker build -t="lucifaer/static_web:v1" git@github.com:lucifaer/docker-static_web

这里的Docker假设这个Git仓库的根目录下存在Dockerfile文件。

如果在构建上下文的根目录下存在以.dockerignore命名的文件的话,那么文件内容会被按行进行分割,每一行都是一条文件过滤匹配模式。这和.gitignore是非常类似的。

如果指令出现失败

这个问题对于Docker来说并不算是一个什么大问题,因为Docker会特别清楚的告诉你什么指令出现了什么错误,之后我们就可以进入交互式shell中进行调试。

由于Docker的每一步创建过程都会将结果提交为镜像,所以Docker的镜像构建会把之前的镜像层看做成缓存。最直接的例子就是我们在构建过程中因为出错而产生中断时,当再次构建时会直接从中断点开始。

当然,有的时候需要确保构建过程不会使用缓存。比如在前面已经缓存了apt-get update,那么Docker将不会再次刷新APT包的缓存。这个时候用户可能需要取得每一个包的最新版本,就需要略过缓存功能,可以使用docker build--no-cache标志:

docker build --no-cache -t="lucifaer/static_web" .

基于构建缓存的Dockerfile模板

说白了,就是利用ENV指令在镜像中设置一个环境变量,这个环境变量来记录镜像模板最后的更新时间,最后使用RUN指令来运行apt-get -qq update来保持软件包是最新版的。

Dockerfile:

FROM ubuntu
MAINTAINER Lucifaer "924463052@qq.com"
ENV REFRESHED_AT 2017-01-18
RUN apt-get -qq update

从新镜像启动容器

docker run -d -p 80 --name static_web lucifaer/static_web nginx -g "daemon off;"
  • -d选项,告诉Docker以分离的方式在后台运行。
  • 同时也指定了需要再容器中运行的命令:nginx -g "daemon off;"。这将以前台运行的方式启动Nginx,来作为我们的Web服务器。
  • -p标志用来控制Docker咋运行时应该公开哪些网络端口给外部。运行一个容器时,Docker可以通过两种方法来在宿主机上分配端口。

    • Docker可以在宿主机上随机选择一个位于32768~61000的一个比较大的端口号来映射到容器中的80端口上。
    • 可以在Docker宿主机中指定一个具体的端口号来映射到容器中的80端口上。

    查看一下Docker端口映射情况:

$ docker ps -l
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                   NAMES
7124a88e797f        lucifaer/static_web   "nginx -g 'daemon off"   9 seconds ago       Up 9 seconds        0.0.0.0:32768->80/tcp   static_web

可以看到容器中的80端口被映射到了主机的32768端口上。也可以通过docker port来查看容器端口映射情况。

$ docker port 7124a88e797f
80/tcp -> 0.0.0.0:32768

通过-p选项映射到特定端口

$ docker run -d -p 8080:80 --name static_web lucifaer/static_web nginx -g "daemon off;"

上面的命令会将容器中的80端口绑定到本地的8080端口上,也可以使用下面的命令将端口绑定限制在特定的网络接口(IP地址)上:

$ docker run -d -p 127.0.0.1:8080:80 --name static_web lucifaer/static_web nginx -g "daemon off;"

可以通过在端口绑定时使用/udp后缀来指定UDP端口。

Docker中还有一个更简单的方法,即-P参数,该参数可以用来对外公开在Dockerfile中通过EXPOSE指令公开的所有端口:

$ docker run -d -P --name static_web lucifaer/static_web nginx -g "deamon off;"

会将容器内的80端口对本地宿主机公开,并且绑定到宿主机的一个随机端口上。该命令会将用来构建镜像的Dockerfile文件中EXPOSE指令指定的其他端口也一并公开。

Dockerfile命令

CMD

CMD命令用于指定一个容器启动时要运行的命令。和RUN命令不同的是,RUN命令是在指定镜像被构建时要运行的命令,比如apt update,而CMD命令是指定容器被运行时要执行的命令,比如sudo service apache2 start

可以认为下面两条指令是做的同一件事情:

$ docker run -i -t lucifaer/static_web /bin/true
CMD ["/bin/bash", "-l"]

-l代表传给/bin/bash的参数。

docker run命令可以覆盖CMD命令。
在Dockerfile中只能指定一条CMD命令。如果执行了多条CMD命令,也只有最后一条CMD将被使用。

ENTRYPOINT

ENTRYPOINT命令与CMD指令非常相似,区别在于ENTRYPOINT命令不易被覆盖,也就是不容易被docker run所覆盖,并且docker run中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令。

为ENTRYPOINT指定指令参数:

ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

还有这么一种情况(重构一下镜像):

ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
docker run -t -i lucifaer/static_web -g "daemon off;"

我们指定了一个参数-g "daemon off;,这个参数会传递给用ENTRYPOINT指定的命令。通过上面命令的组合,任何在命令行中指定的参数都会被传递给Nginx守护进行。如果在启动容器时不指定任何参数,则在CMD指令中指定的-h参数会被传递给Nginx守护进程。

如果需要,用户可以在运行时通过--entrypoint标志覆盖ENTRYPOINT指令。

WORKDIR

WORKDIR指令是用来在从镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT/CMD指定的程序会在这个目录下执行。

也就是说,可以使用该命令为Dockerfile中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录。简单理解,就是cd命令。

可以通过-w标志在运行时覆盖工作目录。

$ docker run -t -i -w /var/log ubuntu pwd

ENV

ENV指令用来在镜像构建过程中设置环境变量。

ENV RVM_PATH /home/rvm/

这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面制定了环境变量前缀一样。

RUN gem install unicorn

该指令会以以下的方式运行:

RVM_PATH=/home/rvm/ gem install unicorn

可以在ENV指令中指定单个环境变量,也可以指定多个环境变量。

ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch i386"

同时也可以在其他指令中使用这些环境变量:

ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR

如果需要,可以通过在环境变量前加上一个反斜线来进行转义

同样,在docker run中,可以用-e标志来传递环境变量。这些变量将只会在运行时有效。而ENV则会被持久的保存到镜像中。

USER

USER指令用来指定该镜像会以什么样的用户去运行,默认为root

docker run中可以通过-u来覆盖该命令。

VOLUME

VOLUME指令用来向基于镜像创建的容器添加卷。在后面用到时候再详细解释。

ADD

ADD指令用来将构建环境下的文件和目录复制到镜像中。比如,在安装一个应用程序时,ADD指令需要源文件位置和目的文件位置两个参数。

ADD software.lic /opt/application/software.lic

这里,ADD指令会将构建目录下的software.lic文件复制到镜像中的/opt/application/software.lic。指向源文件的位置参数可以是一个URL,或者构建上下文或者环境中文件名或目录,但是不能对除此以外的文件进行操作。

ADD命令在处理本地归档文件(zip,tar)时,如果将一个归档文件指定为源文件,Docker会自动的将归档文件解开:

ADD latest.zip /var/www/wordpress/

如果目的为止的目录下已经存在了和归档文件同名的文章或者目录,那么目的位置中的文件或者目录不会被覆盖。
如果通过ADD指令向镜像添加一个文件或者目录,那么这将使Dockerfile中的后续指令都不能继续使用之前的构建缓存。

COPY

COPY指令和ADD指令相似,但是COPY指令不会做文件提取和解压操作。

COPY指令的目的位置必须是容器内部的一个绝对路径。

ARG

ARG指令用来定义可以在docker build命令运行时传递给构建运行时的变量,在构建时使用--build-arg标志即可。用户只能在构建时指定在Dockerfile文件中定义过的参数。

ONBUILD

ONBUILD指令能为镜像添加触发器。当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行。

举个构建Apache2镜像的例子:

Dockerfile:

FROM ubuntu
MAINTAINER Lucifaer "924463052@qq.com"
RUN apt-get update && apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache2"]
CMD ["-D", "FOREGROUND"]

另外创建一个webapp的镜像,来调用上面的apache镜像:

FROM lucifaer/apache2
MAINTAINER lucifaer "924463052@qq.com"
ENV APPLICATION_NAME webapp
ENV ENVIRONMENT development
docker build -t="lucifaer/webapp" .
Sending build context to Docker daemon 2.048 kB
Step 1/4 : FROM lucifaer/apache2
# Executing 1 build trigger...
Step 1/1 : ADD . /var/www/
 ---> 4e80ffc62453
Removing intermediate container 9978f14cfec8
Step 2/4 : MAINTAINER lucifaer "924463052@qq.com"
 ---> Running in 7747ebbb7129
 ---> b2d57c8b1aca
Removing intermediate container 7747ebbb7129
Step 3/4 : ENV APPLICATION_NAME webapp
 ---> Running in e6ccd8647f9f
 ---> 68f1057a573f
Removing intermediate container e6ccd8647f9f
Step 4/4 : ENV ENVIRONMENT development
 ---> Running in 6c08badd94ea
 ---> 4f32c8de4fdb
Removing intermediate container 6c08badd94ea
Successfully built 4f32c8de4fdb

可以看到在FROM之后,Docker插入了一条ADD命令,这条ADD命令就是在ONBUILD触发器中所指定的。

ONBUILD触发器会按照在福镜像中指定的顺序执行,并且只能被继承一次,在孙子镜像中并不会执行。

后面会单独拿出来一篇文章,用一个具体的例子来说一下使用Docker构建Web应用程序。

docker
最后由Lucifaer修改于2017-06-01 00:33

此处评论已关闭

博客已萌萌哒运行
© 2018 由 Typecho 强力驱动.Theme by Yodu
PREVIOUS NEXT
雷姆
拉姆