- 一个基础的 Dockerfile
- 自定义镜像
- 参数传递
- 日志输出
- 精简镜像
- Stage-1
FROM golang:1.16-alpine AS build
RUN apk add —no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project - Stage-2
FROM scratch
COPY —from=build /bin/project /bin/project
ENTRYPOINT [“/bin/project”]
CMD [“—help”] - 更多内容
一个基础的 Dockerfile
Dockerfile 是用于构建 Docker 镜像的一个文本文件,在 Dockerfile 中用户可以定义容器的基础镜像是什么,如何将外部二进制放入镜像中,容器里的程序如何启动等等。
Dockerfile 的语法比较简单易读,拿一个最简单的 Dockerfile 举例:
FROM ubuntu:latest
RUN apt-get update && apt-get install nginx -y
CMD [“sh”, “-c”, “nginx -g ‘daemon off;’”]
以上三行的解释如下:
此镜像会使用 Ubuntu 最新版本作为基础镜像构建;
运行 apt-get update && apt-get install nginx -y 命令来安装 nginx 软件包;
通过 sh 启用 nginx,并使得 nginx 在前台运行(在 Docker 下,PID 为 1 的进程必须在前台运行,否则容器会退出)。
参数解释如下:
FROM :设置基础镜像名称,此选项为必选的;
RUN:在构建镜像时运行一些命令,这些命令可以是软件更新,软件安装,从其他路径获取镜像需要的文件等;
CMD:设置容器默认的启动命令。
通过 Dockerfile 构建镜像
如果要通过 Dockerfile 构建镜像文件,找一台安装好 Docker 的机器,新建一个目录,将 Dockerfile 放在该目录下:
运行下列命令即可进行镜像构建:
docker build . -t nginx:test
当进行构建完成后,最后一行会提示 Successfully tagged nginx:test,此时通过 docker images 命令可以查看到新建的 nginx:test 镜像。
在本地运行镜像
通过下列命令运行容器:
docker run -d -p 8899:80 nginx:test
通过浏览器访问 Linux host 的 8899 端口,可以正常访问到,页面为 Nginx 的默认页面:
如果换个启动命令会如何?
上个章节我们在 run 容器时未配置任何的启动参数(即 image 名称后未包含任何命令),那如果添加一个启动命令会怎样?
通过下列命令运行容器:
docker run -it -p 8899:80 nginx:test sh
再次通过浏览器访问 Linux Host 的 8899 端口,发现页面无法打开。
这是预期的现象,一开始我们在 Dockerfile 中设置的 CMD 只是一个默认的启动命令,docker run 可以覆盖掉,上述实验中的 sh 即是覆盖 CMD 的参数,进入到容器后通过 ps 查看进程,会发现没有 Nginx 相关的进程,证明 CMD 确实未被执行:
ENTRYPOINT
在 Docker 的官方手册中,有两种办法设置容器的默认启动命令,CMD 和 ENTRYPOINT,一般在 Dockerfile 中必须设置 CMD 或者 ENTRYPOINT,两者也可以同时使用。
简单来说,ENTRYPOINT 可以用于设置容器的默认启动命令,并且不能轻易被覆写。
比如将一开始的示例 Dockerfile 中的 CMD 换为 ENTRYPOINT 后,再次通过下列命令启动容器,会发现网页能正常访问:
如果进入到容器中通过 ps 查看进程,会发现我们输入的 sh 被作为参数传递到了 ENTRYPOINT 设置的命令之后。
ENTRYPOINT 与 CMD 混合使用
当把 ENTRYPOINT 和 CMD 混合使用时,会出现和上个实验类似的现象,即 CMD 设置的值会作为参数传递给 ENTRYPOINT。
示例 Dockerfile:
FROM ubuntu:latest
RUN apt-get update && apt-get install nginx -y
ENTRYPOINT [“sh”, “-c”]
CMD [“nginx -g ‘daemon off;’”]
再次构建镜像并运行镜像,网页可以正常访问。进入到容器后,查看启动命令,和最早单行 CMD 的执行结果是一致的:
在 Docker 的官方文档中,建议通过 ENTRYPOINT 作为容器的主启动命令,CMD 可以辅助地作为启动参数。在实际应用中,可以视需求只使用 CMD 或者 ENTRYPOINT,或者两者同时使用。
自定义镜像
在第一章中我们已经做好了一个镜像,也可以正常运行,我们可以在此基础上,稍微调整下默认的访问界面:
比如,将默认页面调整为下列内容:
This is a Test Page
要实现此功能,可以使用下列 Dockerfile:
FROM ubuntu:latest
RUN apt-get update && apt-get install nginx -y && echo “
This is a Test Page
“>/var/www/html/index.nginx-debian.htmlCMD [“sh”, “-c”, “nginx -g ‘daemon off;’”]
简单来说就是在 RUN 中通过命令将 HTML 内容写入到了默认的 Nginx 页面中。
构建镜像并运行容器后,测试效果如下:
如果是复杂的页面替换呢?
通过 RUN 可以替换简单的页面,如果是复杂的页面,则可以预先写好 HTML 文件,然后拷贝到容器中使用。
在实验中我们在 Dockerfile 的根目录下创建了 src 目录,然后创建了名为 index.html 的文件:
使用下列 Dockerfile 构建镜像:
FROM ubuntu:latest
RUN apt-get update && apt-get install nginx -y
COPY src/index.html /var/www/html/
CMD [“sh”, “-c”, “nginx -g ‘daemon off;’”]
运行容器后访问效果如下:
成功替换了默认的访问页面。
在新的 Dockerfile 中我们使用了另一个很重要的命令:COPY,如其名称一样,意思是将指定的文件拷贝到容器镜像的指定目录中。
参数传递
前面的示例中,我们只是做了一个简单的静态页面,现实中容器几乎不会是静态不变的,这时候就涉及到如何将外部参数传递给容器,且容器要能够读取并使用这个参数。
这时候就用到了第一篇文章中提到的环境变量。
环境变量的传递在第二篇文章中简单提过,通过 -e 参数来指定。本文着重讲讲在容器中如何调用。
其中最简单的方法就是:shell 脚本。
还是以上述的 nginx 网页为例,我们希望在每次容器启动时,都能够从环境变量中获取主机名等信息,然后将这些信息展示在 web 页面中,为了实现这个需求,需要做以下修改:
- 预先编写好 index.html 模板,在模板中设置“关键词”,这些关键词可以通过 sed 等工具查找替换成其他内容;
- 编写 shell 脚本,这个脚本在每次容器启动时会运行,脚本会通过 sed 等工具将 index.html 中的关键词替换为环境变量中的值;shell 脚本的末尾设置容器启动参数;
- 修改 Dockerfile,将必要的文件通过 COPY 复制到容器中;修改 CMD,将默认启动命令修改为 shell 脚本。
具体的步骤如下:
1、修改一下上个章节中的 index.html 文件,新增几行,每一行都有一些“默认关键词”,这些关键词未来会通过 shell 脚本替换掉。
2、用于替换关键词的脚本如下,工作机制是通过 sed 来进行查找替换,比如将 demoapp 替换成环境变量 HOSTNAME:
3、相应的 Dockerfile 内容如下:
FROM ubuntu:latest
WORKDIR /var/www/html/
RUN apt-get update && apt-get install nginx -y
COPY src /var/www/html/
COPY start.sh /usr/bin/start.sh
CMD [“/usr/bin/start.sh”]
完整的目录结构如下:
构建并通过下列命令运行容器:
docker run -e hostinfo=$HOSTNAME -p 8899:80 nginx:test
访问到的页面内容如下,已经成功地显示 hostname,Private IP,宿主机信息等内容:
默认工作路径
在上面的示例 Dockerfile 中,我们新引入了一个参数 WORKDIR,此参数用于定义 RUN、COPY、CMD、ENTRYPOINT 等的默认路径。
因此,上述 Dockerfile 可以进行微调,将 COPY 中的绝对路径 “/var/www/html” 替换为相对路径 “.”,表示将文件复制到 WORKDIR 设置的 /var/www/html 下:
FROM ubuntu:latest
WORKDIR /var/www/html/
RUN apt-get update && apt-get install nginx -y
COPY src .
COPY start.sh /usr/bin/start.sh
CMD [“/usr/bin/start.sh”]
You have mail in /var/spool/mail/root
在编写 start.sh 启动脚本时,我们额外添加了 pwd 命令,用于在执行脚本时显示当前的目录,运行容器时显示当前路径为 /var/www/html,和 WORKDIR 一致:
日志输出
在容器环境下,一般需要将程序的运行日志输出到控制台方便排错,像 Nginx 等传统应用在运行时并不会将日志输出到控制台,此时也可以通过 shell 脚本来简单实现。
修改后的 start.sh 如下(此时 nginx 就需要放在后台执行,这样才能保证脚本运行到结尾 tail 处):
构建并运行容器后,再次通过网页访问,即可在容器的控制台看到访问日志:
至此,一个基础的镜像制作就到此为止,虽然简单,但其实很多传统应用到容器的改造都会利用到上面介绍的手段,比如 MySQL,官方的镜像就是使用 docker-entrypoint.sh 脚本进行初始化,根据环境变量设置数据库密码等信息。
精简镜像
在上面的例子中,我们使用 Ubuntu 作为基础镜像,使用 nginx 作为 web 引擎,其中 Ubuntu 镜像自身有 72M,做好的 nginx 镜像有 162M,单个镜像看好像并不是很大,但当环境中容器越来越多时,精简镜像就成了一件很有意义的事。
精简镜像一般可以带来下列好处:
更小的空间占用:无论是对于容器镜像仓库还是运行容器的机器来说,都会占用更少的磁盘空间;
更快的镜像拉取时间:一般在生产环境中运行容器时,都是先从镜像仓库下载镜像,再在本地启动容器,镜像越小拉取镜像更快;
更快的启动时间:容器较小时,也可能有较快的启动速度;
更加安全:一般通过精简镜像,可以移除没必要的二进制文件,从而也可以避免这些二进制潜在的安全风险。
使用更精简的基础镜像
在开源社区中,有一些区别于主流 Ubuntu、CentOS 存在的极简基础镜像,比如 Alpine、Busybox 等,其大小是 Ubuntu 等的数十分之一:
使用这些镜像通常不会有什么问题,但因为底层不一样,可能会遇到一些坑,比如 Alpine 默认会在域名解析时加上 search domain,但其他镜像不会,这就有可能导致域名解析问题。
更进一步的话,也可以直接使用 docker 内置的 scratch 作为基础镜像,scratch 相当于一个空镜像。
减少命令行数
在 Dockerfile 中,RUN、COPY 和 ADD 三种语句每一行都会对应地创建一个新的层,同种操作将多行命令合并成单行,一定程度上可以减少镜像大小。比如在编写 RUN 时,多个操作可以合并成一行,像文始的示例一样:
RUN apt-get update && apt-get install nginx -y
另外,减少 RUN、COPY 和 ADD 的使用也可以减少层,比如通过 “RUN wget http://
删除临时文件
在进行一些安装、拷贝操作时,可能会残留一些临时文件,删除这些临时文件可以进一步缩小镜像大小。比如在使用 apt 更新库并安装软件后,可以加上下列清理命令:
RUN apt-get update && apt-get install nginx -y && rm -rf /var/lib/apt/lists/*
多阶段构建
Docker 支持多阶段构建,比如第一阶段使用 golang 镜像进行程序编译,第二阶段通过 COPY 将第一阶段的成品包添加到容器,再设置启动命令等等。
Stage-1
FROM golang:1.16-alpine AS build
RUN apk add —no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project
Stage-2
FROM scratch
COPY —from=build /bin/project /bin/project
ENTRYPOINT [“/bin/project”]
CMD [“—help”]
更多内容
以上就是本文全部内容,只是作为一个引子,Docker 的官方文档很多都有介绍到,下面是一些文档链接:
Dockerfile 参考:
https://docs.docker.com/engine/reference/builder/
编写 Dockerfile 的最佳实践:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
创建一个基础镜像: