目录
镜像的定制
镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。
镜像的定制实际上就是定制每一层所添加的配置、文件。
如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么曾经遇到的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
有了 Dockerfile,当我们需要定制自己额外的需求时,只需在 Dockerfile 上添加或者修改指令,重新生成 image 即可,省去了敲命令的麻烦。
Dockerfile文件格式
## Dockerfile文件格式 # This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: docker_user # Command format: Instruction [arguments / command] .. # 1、第一行必须指定 基础镜像信息 FROM ubuntu # 2、维护者信息 MAINTAINER docker_user docker_user@email.com # 3、镜像操作指令 RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list RUN apt-get update && apt-get install -y nginx RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf # 4、容器启动执行指令 CMD /usr/sbin/nginx
Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令、容器启动执行指令。
- 一开始必须要指明所基于的镜像名称,
- 接下来一般会说明维护者信息;
- 后面则是镜像操作指令,例如 RUN 指令。每执行一条RUN 指令,镜像添加新的一层,并提交;
- 最后是 CMD 指令,来指明运行容器时的操作命令。
构建镜像
使用 docker build
命令进行镜像构建。其格式为:
docker build [选项] <上下文路径/URL/->
docker build 命令会根据 Dockerfile 文件及上下文构建新 Docker 镜像。
镜像构建上下文(Context)
如果注意,会看到
docker build
命令最后有一个.
。
.
表示当前目录,而Dockerfile
就在当前目录,因此不少初学者以为这个路径是在指定Dockerfile
所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定 上下文路径。那么什么是上下文呢?
首先我们要理解 docker build
的工作原理。
Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker
命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker
功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN
指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY
指令、ADD
指令等。而docker build
命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build
命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
构建上下文是指 Dockerfile 所在的本地路径或一个URL(Git仓库地址)。构建上下文环境会被递归处理,所以构建所指定的路径还包括了子目录,而URL还包括了其中指定的子模块。
如果在 Dockerfile
中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build
命令所在的目录下的 package.json
,也不是复制 Dockerfile
所在目录下的 package.json
,而是复制 上下文(context) 目录下的 package.json
。
因此,COPY
这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY ../package.json /app
或者 COPY /opt/xxxx /app
无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
Dockerfile解析过程
Dockerfile解析过程
Dockerfile 指令详解
Dockerfile 指令
1、FROM指令
①使用说明:
使用格式:FROM <image>或FROM <image>:tag
作用描述:dockerfile的第一条非注释指令,为后面提供基础镜像。
②使用建议:
(1)建议尽可能使用官方提供的image版本作为baseimage。
(2)建议使用Debian image,因为这些image易于控制同时尺寸都很小,大多数在100MB以下,非常适合进行分发。
2、RUN指令
①使用说明:
使用格式:RUN <command> 或 RUN [“executable”,”param1”,”param2”]
作用描述:run指令会在前一条命令创建出的镜像基础上启动一个容器,并在容器中运行命令,命令结束后会提交一个新镜像。上述两种用法,前者将在shell终端中运行命令,即 /bin/sh -c ;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN ["/bin/bash", "-c", "echo hello"]
②使用建议:
(1)为了保持Dockerfile可读性,易于理解,方便维护。建议将多条RUN 命令使用"/"连接起来。
(2)apt-get应该是大多数Dockerfile都会定义的RUN 命令。当使用apt-get,有如下建议可参考:不用将RUN apt-get update单独作为一条命令。如果关联包发生变化后,在执行apt-get install 命令时,docker 查找cache时有可能会有问题。
(3)回避使用 RUN apt-get upgrade 或者 disk-upgrade 命令。因为很多外部的软件包在未经认证情况执行upgrade会失败。如果有一些软件包过期了,那么你应该联系软件包的维护者来确定是否需要升级。比如你确定一个第三方的软件包 foo 可以进行升级。那么执行apt-get install -y foo就可以自动完成升级。
(4)如果可能的话,将你准备安装的软件包安装字母顺序排列。这样可以回避重复安装软件包的情况,同时也有助于进行软件更新。通过添加"\"进行分割,将增强代码的可读性。
3、ADD和COPY指令
①使用说明:
使用格式:两个命令的使用方式类似 ADD <src> <dest>、COPY <src> <dest>
作用描述:
ADD:该命令将复制指定的 <src> 到容器中的 <dest> 。 其中 <src> 可以是Dockerfile所在目录的一个相对路径;也可以是一个URL;还可以是一个tar文件(自动解压为目录)
COPY:复制本地主机的<src>(为Dockerfile所在目录的相对路径)到容器中的<dest>。
②使用建议:
(1)当使用本地目录为源目录时,推荐使用 COPY
4、CMD与ENTRYPOINT指令
①使用说明:
使用格式:
其中CMD有以下三种使用方式
CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
ENTRYPOINT 使用格式:
ENTRYPOINT ["executable", "param1", "param2"] (JSON数组格式)
ENTRYPOINT command param1 param2 (shell格式 ,shell中执行)
作用描述:
CMD:指定启动容器时执行的命令,每个Dockerfile只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。可被 docker run 提供的参数覆盖。
ENTRYPOINT :配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。每个Dockerfile中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效。
②使用建议:
(1)CMD命令用来执行image中的所有应用。CMD一般采用CMD [“executable”, “param1”, “param2”…]的格式来运行。所以,如果你的image是用来提供服务的,例如Apache,Rails。你就应该执行类似这样的命令CMD ["apache2","-DFOREGROUND"]。
(2)在其他的case中,CMD用来执行特定的shell,比如:bash,python,perl等等。比如: CMD ["perl", "-de0"] , CMD ["python"] , or CMD [“php”, “-a”]。
(3)当你执行docker run -it python时就可以进入特定的shell中。
(4)CMD经常是配合 ENTRYPOINT 来使用的 (两者需要使用JSON数组格式)。除非确定你的用户非常了解ENTRYPOINT 的特性。否则还是建议你事先设定好ENTRYPOINT
5、EXPOSE指令
①使用说明:
使用格式:EXPOSE <port> [<port>...]
作用描述:指定容器暴露的端口
②使用建议:
(1)EXPOSE命令定义了container用来监听连接者的端口。因此,你应该为你的image定义一个比较通用的端口。比如一个用来提供Apache web服务的image,你应该expose 80.而提供MongoDB的image,应该提供27017端口。
(2)对于一些外部访问,你的用户可以使用docker run -p的形式来进行端口绑定。
6、ENV指令
①使用说明:
使用格式:ENV <key> <value>或ENV <key>=<value>
作用描述:为创建出来的容器创建环境变量
②使用建议:
(1)为了保证application可以顺利执行,你可以通过ENV来更新PATH环境变量。比如:通过ENV PATH /usr/local/nginx/bin:$PATH 可以确保CMD ["nginx"]顺利执行。
(2)ENV也可以用来提供特定的环境变量,比如你可以自定义postgres所需要的PGDATA变量。
(3)最后ENV可以用来定义一些版本信息。
7、WORKDIR指令
①使用说明:
使用格式:WORKDIR /path/to/workdir 。
作用描述:为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
②使用建议:
(1)为了保持执行过程清晰,你应该经常使用绝对路径来设定WORKDIR。
(2)你应该使用WORKDIR来替代 RUN cd .. && do-something
8、VOLUME指令
①使用说明:
使用格式:VOLUME ["/data"] 。
作用描述:创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
②使用建议:
(1)VOLUME应该被用来导出数据库存储区域,配置文件存储区域或者container内部app创建的目录或者文件。
参考:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/