最近一直在使用Docker部署项目, 但是还没有很系统的学习Dockerfile的相关知识, 本篇主要总结的关于Dockerfile的制作, 使用等;
阅读本文你将学会:
- 为什么使用Dockerfile? 使用Dockerfile的好处?
- Dockerfile中的指令: FROM, RUN, COPY, ADD, WORKDIR, CMD, ENTRYPOINT, ENV, EXPOSE.
- Dockerfile中的注意事项
- Dockerfile中的优化
- Dockerfile应用场景举例
- ……
Dockerfile学习
零. 前言
1. 什么是Dockerfile?
虽然我们可以通过docker commit
命令来手动创建镜像,但是通过Dockerfile文件,可以帮助我们自动创建镜像,并且能够自定义创建过程。
本质上,Dockerfile就是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。它简化了从头到尾的构建流程并极大的简化了部署工作。
2. 为什么使用Dockerfile?
使用dockerfile构建镜像有以下好处:
- 像编程一样构建镜像,支持分层构建以及缓存;
- 可以快速而精确地重新创建镜像以便于维护和升级;
- 便于持续集成;
- 可以在任何地方快速构建镜像
—————————-华丽的分割线—————————
一. Dockerfile指令
Dockerfile 指令为 Docker 引擎提供了创建容器映像所需的步骤, 这些指令按顺序逐一执行。
以下是有关一些基本 Dockerfile 指令的详细信息。
1. FROM
FROM 指令用于设置在新映像创建过程期间将使用的容器映像。
格式:
FROM image
示例:
FROM nginx
FROM microsoft/dotnet:2.1-aspnetcore-runtime
2. RUN
RUN 指令指定将要运行并捕获到新容器映像中的命令。 这些命令包括安装软件、创建文件和目录,以及创建环境配置等。
格式:
RUN ["", "", ""]
或RUN cmd1 && cmd2 && ...
示例:
RUN apt-get update
RUN mkdir -p /usr/src/redis
RUN apt-get update && apt-get install -y libgdiplus
RUN ["apt-get","install","-y","nginx"]
注意: 每一个指令都会创建一层,并构成新的镜像。
当运行多个指令时,会产生一些非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。因此,在很多情况下,我们可以合并指令并运行!
例如:RUN apt-get update && apt-get install -y libgdiplus
在命令过多时,一定要注意格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。使用换行符时,可能会遇到一些问题,具体可以参阅后文的转义字符。
3. COPY
COPY 指令将文件和目录复制到容器的文件系统。文件和目录需位于相对于 Dockerfile 的路径中。
格式:
COPY <source> <destination>
或COPY ["", ""]
如果源或目标包含空格,请将路径括在方括号和双引号中
示例:
COPY . .
COPY nginx.conf /etc/nginx/nginx.conf
COPY . /usr/share/nginx/html
COPY hom* /mydir/
4. ADD
ADD 指令与 COPY 指令非常类似,但它包含更多功能。除了将文件从主机复制到容器映像,ADD 指令还可以使用 URL 规范从远程位置复制文件
格式:
ADD<source> <destination>
示例:
# 此示例会将 Python for Windows下载到容器映像的 c:\temp 目录
ADD https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe /temp/python-3.5.1.exe
5. WORKDIR
WORKDIR 指令用于为其他 Dockerfile 指令(如 RUN、CMD)设置一个工作目录,并且还设置用于运行容器映像实例的工作目录。
格式:
WORKDIR <dir>
示例:
WORKDIR /app
6. CMD
CMD指令用于设置部署容器映像的实例时要运行的默认命令。例如,如果该容器将承载 NGINX Web 服务器,则 CMD 可能包括用于启动Web服务器的指令,如 nginx.exe。
如果 Dockerfile 中指定了多个 CMD 指令,只会计算最后一个指令。
格式:
CMD <executable> <param1> <param2> ...
或CMD ["<executable>", "<param1>", "<param2>", ...]
示例:
CMD ["c:\\Apache24\\bin\\httpd.exe", "-w"]
CMD c:\\Apache24\\bin\\httpd.exe -w
7. ENTRYPOINT
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效
格式:
ENTRYPOINT ["<cmd>", "<param1>", "<param2>", ...]
示例:
ENTRYPOINT ["dotnet", "Magicodes.Admin.Web.Host.dll"]
8. ENV
ENV命令用于设置环境变量。这些变量以”key=value”的形式存在,并可以在容器内被脚本或者程序调用。
这个机制给在容器中运行应用带来了极大的便利。
格式:
ENV key=value
示例:
ENV VERSION=1.0 DEBUG=on \
NAME="Magicodes"
9. EXPOSE
EXPOSE用来指定端口,使容器内的应用可以通过端口和外界交互
格式:
EXPOSE <port>
示例:
EXPOSE 80
总结
说了这么多,我们可以用下图来一言以蔽之:
—————————-华丽的分割线—————————
二. Dokcerfile中的注意事项
在许多情况下,Dockerfile 指令需要跨多个行, 这可通过转义字符完成。
默认 Dockerfile 转义字符是反斜杠 \
. 由于反斜杠在 Windows 中也是一个文件路径分隔符,这可能导致出现问题!
以下示例显示使用默认转义字符跨多个行的单个 RUN 指令:
FROM microsoft/windowsservercore
RUN powershell.exe -Command \
$ErrorActionPreference = 'Stop'; \
wget https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile c:\python-3.5.1.exe ; \
Start-Process c:\python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait ; \
Remove-Item c:\python-3.5.1.exe -Force
要修改转义字符,必须在 Dockerfile 最开始的行上放置一个转义分析程序指令
如以下示例所示:
# escape=`
FROM microsoft/windowsservercore
RUN powershell.exe -Command `
$ErrorActionPreference = 'Stop'; `
wget https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile c:\python-3.5.1.exe ; `
Start-Process c:\python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait ; `
Remove-Item c:\python-3.5.1.exe -Force
注意,只有两个值可用作转义字符:\ 和`
—————————-华丽的分割线—————————
三. Dockerfile中的优化
这里只进行简单讲解,后续结合实际案例再进行细说. 但是有几点值得注意的是:
- 不能忽视dockerfile的优化,通常情况下,我们可以忽略那些细小的优化,但是我们需要知道优化的原理,为什么要优化
- 不能为了优化而优化。镜像的构建过程视业务情况情况不同,指令就有多到少的区别,在很多情况下,我们先要以满足业务目标为准,而不是镜像层数。如果需要减少镜像的层数,我们一定要选择合适的基础镜像,或者创建符合我们需要的基础镜像
下面是一些优化的准则:
1. 选择合适的基础镜像
这点相对最为重要。为什么这么说,我们结合现实社会也可以看到,在大部分情况下,一个人一生的成就更多的是看出身。很多情况下,基因和出身决定了你的高度和终点,这点拿到技术层面来说,也是有很大道理的,因此我们需要选择合适的父母——一个合适的镜像。
一个合适的基础镜像是指能满足运行应用所需要的最小的镜像,理论上是能用小的就不要用大的,能用轻量的就不要用重量级的,能用性能好的就不要用性能差的。这里有时候还需要考虑那些能够减少我们构建层数的基础镜像。
2. 优化指令顺序
Docker会缓存Dockerfile中尚未更改的所有步骤,但是,如果更改任何指令,将重做其后的所有步骤!
也就是指令3有变动,那么4、5、6就会重做。
因此,我们需要将最不可能产生更改的指令放在前面,按照这个顺序来编写dockerfile指令。这样,在构建过程中,就可以节省很多时间。比如,我们可以把WORKDIR、ENV等命令放前面,COPY、ADD放后面。
3. 合并指令
前面其实我们提到过这点,甚至还特地讲到了转义字符,其实主要是为此服务。
前面我们说到了,每一个指令都会创建一层,并构成新的镜像。当运行多个指令时,会产生一些非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
因此,在很多情况下,我们可以合并指令并运行,例如:
RUN apt-get update && apt-get install -y libgdiplus
在命令过多时,一定要注意格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。
4. 删除多余文件和清理没用的中间结果
这点很易于理解,通常来讲,体积更小,部署更快!因此在构建过程中,我们需要清理那些最终不需要的代码或文件。比如说,临时文件、源代码、缓存等等。
5. 使用 .dockerignore
.dockerignore
文件用于忽略那些镜像构建时非必须的文件,这些文件可以是开发文档、日志、其他无用的文件
例如:
.dockerignore
.env
.git
.gitignore
.vs
.vscode
docker-compose.yml
docker-compose.*.yml
*/bin
*/obj
—————————-华丽的分割线—————————
四. 一个简单的Dockerfile实例
这个简单的node.js例子仅仅用于示范: ‘编写 -> 编译 -> 运行’ Dockerfile的整个流程
1. 编写应用
首先如下编辑app.js内容:
const http = require('http');
const os = require('os');
console.log("Server starting...");
var handler = function(request, response) {
console.log("Receive request from " + request.connection.remoteAddress);
response.writeHead(200);
response.end("You've hit " + os.hostname() + "\n");
};
var www = http.createServer(handler);
www.listen(8080);
2. 编写Dockerfile
由于要运行node.js, 所以需要node镜像(使用ADD添加), 同时还需要源文件. 所以Dockerfile如下所示:
FROM node:7
ADD app.js /app.js
ENTRYPOINT [ "node", "app.js" ]
首先拉去一个镜像, 然后添加项目文件到根目录下, 然后通过node app.js
运行代码.
3. 构建Dockerfile
使用docker build
命令构建Dockerfile文件, 生成image:
docker build -t test app/
注: 此例中Dockerfile在app目录下!
其中
-t
参数指定了标签名, 推荐使用DockerHubId/imageName:version
的形式, 此时构建的镜像与DockerHub的Id一致可直接推送!
构建完成即可看到镜像:
zk@jasonkay:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest 3bc16cb581f9 About a minute ago 660MB
......
4. 运行镜像
使用docker run
命令即可通过image创建container并自动执行node app.js
启动应用:
zk@jasonkay:~$ docker run test -name node-app -p 8080:8080
Server starting...
然后通过浏览器访问container内部的ip:port, 如下图:
同时终端收到返回消息:
zk@jasonkay:~$ docker run test -name node-app -p 8080:8080
Server starting...
Receive request from ::ffff:172.17.0.1
Receive request from ::ffff:172.17.0.1
以上从应用构建到最后应用部署完整再现了如何使用Dockerfile完成一个可移植应用的开发!
—————————-华丽的分割线—————————
附录
参考文章: