第二章 构建容器镜像
本章介绍 5 中不同的方式, 使用 Docker
原生工具来定义与构建镜像. 本章会介绍一个不推荐但很有效的方法.
本章主要内容
Dockerfile
简介- 构建
Docker
镜像
2.1 技术要求(略)
2.2 Dockerfile
简介
Dockerfile
是一个简单的文本文件, 其中包含一组用户定义的指令.
当 docker image build
命令调用 Dockerfile
文件时, 会生成容器镜像.
Dockerfile
文件看起来如下:
FROM alpine:latest
LABEL maintainer=”Russ McKendrick <russ@mckendrick.io>”
LABEL description=”This example Dockerfile installs NGINX.”
RUN apk add --update nginx && \
rm -rf /var/cache/apk/* && \
mkdir -p /tmp/nginx/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf
ADD files/html.tar.gz /usr/share/nginx/
EXPOSE 80/tcp
ENTRYPOINT [“nginx”]
CMD [“-g”, “daemon off;”]
逻辑上 Dockerfile
文件做了什么是很好理解的.
首先介绍一下 Alpine
.
Alpine Linue
是一个小的, 独立的, 非商业的 Linux 发行版, 它致力于安全, 高效, 以及易用的特征.
虽然小, 但是很强大, 基本的功能也都有, 所以本书基于此发行版展开. 下面看看与其他 Linux 发行版的对比.
2.2.1 深入回顾 Dockerfile
根据前面引入的 Dorckerfile
文件中的指令, 将按照出现的顺序来加以介绍:
FROM
LABEL
RUN
COPY
和ADD
EXPOSE
ENTRYPOINT
和CMD
- 其他指令
2.2.2 FROM
FROM
表示基于什么 LInux 发行版作为基础. 这里使用最新的 Alpine, 即 FROM Alpine:latest
.
2.2.3 LABEL
用于添加附加消息来描述镜像, 该消息可以是任意的数据. 但是也不建议不加限制的使用.
实际上就相当于注释. 用于描述镜像的使用, 介绍等相关信息.
作者建议参考 http://label-schema.org/ 来确定要添加什么 LABEL
.
可以使用命令 docker image inspect <IMAGE_ID>
来查看 LABEL
.
书中引入了
-f {{.Config.Labels}}
来进行过滤, 但似乎现在语法调整了经过验证, 在 Win 上
-f
后跟随的 模板语法需要使用引号括起来这个语法是 GO 中的语法, 必要时还是要学学 GO
2.2.4 RUN
该指令用于在镜像内安装软件或执行脚本, 命令, 或其他任务. 下面实际上执行了三条命令:
RUN apk add --update nginx && \ # 安装
rm -rf /var/cache/apk/* && \ # 删除临时文件, 以确保镜像最小
mkdir -p /tmp/nginx/ # 确保 nginx 可以正确启动
然后书中给出了对代码的解释, 包括
&&
,\
等字符的含义, 以及脚本的含义.
2.2.5 COPY
和 ADD
乍看一下, 这两个指令很类似. 首先介绍 COPY
指令.
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf
COPY
指令将本地 (主机) 的文件拷贝到镜像中. 这里会对镜像中的文件进行覆盖.
另一条指令即 ADD
:
ADD files/html.tar.gz /usr/share/nginx/
该命令会将本地文件添加到镜像中, 并自动解压. 除此之外, ADD
还允许将远程文件添加到镜像中.
这里作者描述到, 远程压缩文件会被视为一个文件, 不会被解压缩. 也就是时候在使用前需要手动解压. 然后将压缩包删除.
jk: 这里有待测试.
但不清楚
COPY
和ADD
的不同, 是不是就是:
- 是否会对压缩包解压?
- 是否可以添加远程资源?
2.2.6 EXPOSE
用于定义容器在运行时需要导出的端口与协议. 该指令不会映射主机端口, 只是说明允许访问的, 容器中应用的端口以及使用的协议. 例如需要在容器中运行的是开放 80 端口的 HTTP 服务器. 可以使用
EXPOSE 80/tcp
2.2.7 ENTRYPOINT
和 CMD
这两个命令都表示在执行镜像的时候执行什么命令.
简单来说, ENTRYPOINT
和 CMD
会合并起来运行. 同时 CMD
命令允许被覆盖.
例如:
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
如果运行容器等价于 nginx -g daemon 0ff;
如果运行时带有参数, 例如:
docker container run --name nginx-version dockerfile-example -v
则会等价于 nginx -v
, 即 CMD
被覆盖.
jk: 细节还是整理官方文档比较好. 官方问道描述了这里的两种参数格式, 区别还是很大的. 不过本书够用.
这里有一个执行的区别, 一个是 exec 模式, 另一个是 shell 模式:
我的理解:
- shell 提供了执行环境, 可以将运行的结果等内容保存在一个环境中, 可以利用如管道等一些 shell 的技巧对数据进行处理.
- 而 exec 模式仅仅是运行应用程序本身, 程序执行结束后, 就结束了, 没有所谓的上下文缓存数据, 也不会有管道等一些 shell 技巧.
2.2.8 其他指令
其他指令没有包含在前面的 Dockerfile
案例中.
USER
: 该指令表示命令在执行的时候, 使用什么用户名. 在Dockerfile
文件中,USER
指令可以用于RUN
指令,CMD
指令,ENTRYPOINT
指令. 当然, 在USER
指令中定义的用户必须存在, 否则镜像构建会失败. 同时, 适用USER
指令也会带来权限问题, 不仅仅是在容器中, 在卷的挂载中也存在权限问题.简单来说用于设置在容器中执行命令的用户, 这与权限等内容相关. 当然必须保证用户存在, 若不存在会报错.
WORKDIR
: 该指令为USER
指令可以使用的同一指令集合 (RUN
,CMD
,ENTRYPOINT
) 设置工作目录. 它会允许你使用CMD
和ADD
指令. (这里的允许也许应该理解为有助于, 方便于的意思吧).用于设置当前工作目录. 逻辑上类似于执行
cd
命令.ONBUILD
:ONBUILD
指令可以存储一组命令, 该命令会在该镜像作为其他容器的基础镜像时使用. 例如, 你需要为开发者提供一个镜像, 而它们需要测试不同基代码. 那么你可以使用ONBUILD
来引导实际上需要执行的代码. 然后, 开发者只需要简单的添加代码到指定的目录, 然后运行 Docker 的构建命令即可, 它会将代码添加到运行的镜像中.ONBUILD
指令可以与ADD
或RUN
指令联合使用. 如下面代码:ONBUILD RUN apk update && apk upgrade && rm -rf /var/cache/apk/*
那么该镜像作为其他容器镜像的基础竟像时, 每次构建都会执行更新操作.
ENV
:ENV
指令可以在镜像构建或执行的时候设置环境变量. 这些环境变量可以在执行镜像时被重写.
2.2.9 Dockerfiles
最佳实践
现在, 我们已经介绍了 Dockerfile 的指令, 下面我们来看看怎么编写 Dockerfile 来进行最佳实践. 遵循下面的几项, 确保你的镜像够简洁, 一致, 并易于别人阅读:
- 请习惯使用
.gockerignore
文件. 我们会在本章的 构建 Docker 镜像 一节来介绍.dockerignore
文件. 如果你熟悉.gitignore
文件, 你会发现它们很像. 在构建过程中, 它会忽略你在文件中所指定的项. - 请记住每一个文件夹中只包含一个
Dockerfile
文件, 这样有助于你组织你的容器. - 针对 Dockerfile 使用版本控制系统, 例如 Git. 就像其他文本文档一样. 版本控制系统允许你对文件进行回溯.
- 在每一个镜像中尽可能少的安装包. 最大的目标是尽可能让你的镜像小而稳定. 不必的包就不要安装.
- 确保每一个容器仅执行一个应用. 每次需要执行一个新应用时, 就启用一个新的容器.
- 尽可能保持简单. 复杂的 Dockerfile 会增加潜在的问题, 并使得过度膨胀.
- 通过示例来学习. 在 Docker Hub 上, Docker 自己就有一个非常详细的风格指南来发布官方的镜像. 在本章结尾处, 有一个进一步阅读, 其中包含一个链接来介绍它.
2.3 构建 Docker 镜像
本节中, 我们开始介绍 docker image build
命令. 从此开始为将来需要使用的镜像做准备. 我们会介绍不同的方案来完成这个目标. 这好比是前面为虚拟机所配置的模板. 这有助于完成硬件工作而节省你的时间. 此时你只用将需要的软件添加到新的镜像中.
简单议译了, 没丢多少内容.
在使用 docker build
命令的时候有很多种选择. 我们总是使用哪些简单好用的命令. 首先我们使用 --help
来看看我们可以做什么:
$ docker image build --help
这里会列出很多可以用在构建命令中的选项. 这里可能会有很多东西需要消化, 但是我们通常只会使用 --tag
或简写 -t
来为我们的镜像命名.
你还可以使用其他选项来限制构建过程中使用多少 CPU 与内存. 一般, 不会用尽所有的 CPU 资源和内存. 特别是在生产服务器上. 这样长时间的构建, 会造成其他问题. 还有一些选项会影响容器在构建与运行时对网络的配置.
议译了部分.
一般情况下不会使用到 --file
或 -f
选项. 养成特定的文件组织方式与命名习惯, 有助于减少该选项的使用.
我个人到觉得使用不同的文件来针对不同的环境还是很常用的.
在构建过程中传入附加的 ENV 参数是很有价值的. 该参数用于构建过程, 容器不会继承该参数 (不是写在 Dockerfile 中的 ENV, 而是构建过程中作为参数传入的). 这个技巧可以在构建过程中为构建与测试环境配置代理等.
而前面我们提到的 .dockerignore
文件用于排除构建镜像时, 我们不需要使用的文件或文件夹. 默认情况下, Dockerfile 文件所在的内容都会被添加到镜像中. 可以将 Dockerfile 放在特定文件夹中, .dockerignore
也是一样. 它应该放在 Dockerfile 相同的目录中.
将所有需要的放在一个目录下, 这有助于维护与管理, 也可以让 dockerigmore
尽可能小.
下面开始.
2.3.1 使用 Dockerfile
构建容器镜像的首要方法就是使用 Dockerfile. 实际上, 我们会使用前面章节中的 Dockerfile, 然后使用命令 docker image build
命令来获得我们自己的 Nginx 镜像.
下面我们再次看看下面的 Dockerfile:
FROM alpine:latest
LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
LABEL description="This example Dockerfile installs NGINX."
RUN apk add --update nginx && \
rm -rf /var/cache/apk/* && \
mkdir -p /tmp/nginx/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf ADD files/html.tar.gz /usr/share/nginx/
EXPOSE 80/tcp
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
请不要忘记, 你还需要使用 default.conf
, html.tar.gz
, 以及 nginx.conf
文件, 在 files
文件夹下. 你可以在附带的 GitHub 仓库中找到这些文件.
因此有两个方式来构建我们的镜像. 第一种方式使用 --file
来选择构建. 我们还需要利用 --tag
来为镜像提供唯一的名字:
$ docker image build --file <Dockerfile 文件的路径> --tag <仓库>:<标记> .
然后书中解释了路径, 仓库, 标记等含义, 一般
<仓库>
是登录 Docker Hub 的用户名, 在第三章 发布镜像中会讨论到, 这里暂时使用local
来表示.
如果我们已有一个名为 Dockerfile
的文件, 可以忽略 --file
选项, 这是构建的第二种方法, 如下:
$ docker image build --tag local:dockerfile-example .
最重要的一点是, 不要忘记最后的点. 这是在告诉 docker image build
命令在当前目录执行. build 时会看到如下类似的界面:
一旦完成编译 (build), 你应该可以执行下面命令来查看是否有镜像可用, 以及镜像的大小:
docker image ls
如你所示下面的终端, 我的镜像大小是 7.15M:
你可以使用下面的命令, 运行一个新构建的镜像:
docker container run -d --name dockerfile-example -p 8080:80 local:dockerfile-example
该命令会运行一个名为 docker-example
的容器. 你可以使用下面的命令来检查是否有运行的容器:
docker container ls
打开浏览器, 并定位到 http://localhost:8080
, 应该会向你展示一个极度简单的 Web 页面:
下一步, 我们会快速执行一些命令, 这些命令在本章 Dockerfile
简介一节中都介绍过, 首先是下面的命令:
docker container run --name nginx-version local:dockerfile-example -v
你可以看到我的 nginx 版本是 1.16.1
下一个命令我们会查看在构建时嵌入的运行时会展示的标签.
要显示这些信息, 执行下面命令:
docker image inspect -f {{.Config.Labels}} local:dockerfile-example
下面便是我们进入时会显示的信息:
然后我们继续, 我们可以使用下面的命令停止正在运行的容器, 然后移除容器.
docker container stop dockerfile-example
docker container rm dockerfile-example nginx-version
我们会在第4章 管理容器中详细介绍 docker container 的命令.
2.3.2 使用已存在的容器
构建镜像最简单的方式是从 Docker Hub 的官方提供的镜像开始. Docker 也会保存这些托管在在 GitHub 仓库中的官方构建所使用的 Dockerfile
文件. 因此使用已存在的镜像, 你至少有两种方式可选择. 使用 Dockerfile
, 你可以明确的看到在构建时添加了什么, 并且可以添加你所需要的东西. 你甚至可以使用版本控制来管理和维护他, 如果你需要修改或分享它的时候.
还有另一种方式可以做到这一点. 然而, 我强烈不建议也不应该考虑使用这个方式.
说明
我仅在原型设计阶段会使用该方法, 然后使用交互式命令, 以确保每一个步骤可以正确执行. 然后将其写入
Dockerfile
中. 你应该使用Dockerfile
.
首先, 我们需要下载 base
镜像, 以前面的案例, 我会使用 Alpine Linux
:
docker image pull alpine:latest
下一步, 我在后台运行容器, 使其可以进行交互:
docker container run -it --name alpine-test alpine /bin/sh
一旦容器运行起来, 你可以使用 apk
命令来安装所需要的包, 也可以使用你所喜好的包管理命令.
例如, 下面的命令会安装 NGINX
apk update
apk upgrade
apk add --update nginx
rm -rf /var/cache/apk/*
mkdir -p /tmp/nginx/
exit
在安装了你必须使用的包以后, 你需要保存容器. 上述代码中最后的 exit
会停止运行的容器, 因为这个由我们附加的 shell 进程是前台进程恰好在前台保持着容器的运行.
你可以看到下面的终端输出
注意: 至此应该停止了, 除了下面将要演示的案例, 不建议使用这个方式来创建, 发布镜像.
然后, 我将停止运行的容器保存为镜像, 你需要执行形如下面的命令:
docker container commit <容器名> <仓库>:<标记>
例如, 运行下面的命令将我之前运行, 配置的容器保存为副本:
docker container commit alpine-test local:broken-container
可以看到作者的命名是有问题的容器, 因为这个用法常在容器出现问题时, 将容器保存, 甚至是 tar 格式, 来分享给其他开发者, 来协助处理问题所在. (这句很复杂, 议译了).
将容器保存为文件, 执行:
docker image save -o <文件名>.tar <REPOSITORY>:<TAG>
例如:
docker image save -o broken-container.tar local:broken-container
这会给我代来 7.9M, 名为 broken-container.tar
的文件. 我们可以将其解压, 它的结构如下:
镜像由一系列 JSON 文件, 文件夹, 以及其他 tar 文件构成. 所有的镜像结构都是如此, 因此你可以想象, 为什么这个方法不好.
最大的原因是信任 (我们已经提及到了). 你的最终用户无法清晰的看到他所运行的镜像中有什么. 不确定你会在什么源下载什么软件, 以及是否会检查校验构建过程, 以及配置了什么东西等.
而是用 Dockerfile
, 你可以清晰的看到构建镜像时执行了什么, 但在这个方法中, 你什么也看不到.
另一个原因是, 它无法构建一个好用的默认配置. 例如无法使用 ENTRYPOINT
, CMD
, EXPOSE
等, 也就是用户在运行容器时需要配置所有内容.
早期, 发布镜像常常这么处理. 然后, Docker 才有了构建功能, 逐渐摒弃了这个方法.
2.3.3 Using Scratch as a base
简单的翻译为, 使用最精简的作为 base 镜像
至此, 我们依旧使用的是 Docker Hub 中准备好的镜像作为我们的基础镜像. 然而, 这依旧是最好的方法来避免问题, 让你从 0 开始构建自己的镜像.
现在, 但凡听到术语 scratch
, 它的字面表达就是从 0 开始. 从 0 开始, 可以让镜像尽可能小, 但是也表示对初学者过于困难.
Docker Hub 为我们提供了一个名为 scratch
的 TAR
文件,可以作为从 零 开始的基础. 可以将其用在 FORM
指令中, 在其基础上添加其他内容来构建整个 Docker
.
按照
DockerHub
中的介绍, 该镜像是最小的起点, 该镜像不能pull
, 也不能运行, 但是可以用在Dockerfile
的FROM
中.但是似乎没啥用处. 下面是运行截图.
生成的镜像也是 0 字节, 也不明白有什么作用? 不过用来作为卷, 容器, 等存储用还是可以的.
书中作者说, 基于该镜像可以构建整个
Docker
, 有点项了解该怎么构建.太棒了, 作者给出了这个实现过程. nice!
然后, 使用 Alpine Linux
作为起点. 使用它是因为它不仅仅提供 ISO, 镜像, 以及各种虚拟机的发型版, 还提供了 TAR
格式的压缩包, 其中包含整个操作系统. 可以在 GitHub
, 或官网上下载: https://www.alpinelinux.org/downloads/.
作者的操作是:
- 从
Alpine
官网下载发行版 (作者使用的是x86_x64
MINI ROOT FILESYSTEM). - 然后编写
Dockerfile
, 基于scratch
, 并将下载的apline
压缩包拷贝进去, 然后执行/bin/sh
这里需要注意, 我们使用
scratch
, 其中没有任何东西, 也不会有可执行程序用来执行RUN
等命令.
作者给出的 Dockerfile
文件 (适当修改了 Alpine
的版本):
FROM scratch
ADD alpine-minirootfs-3.20.1-x86_64.tar.gz /
CMD ["/bin/sh"]
注意这里不能使用
COPY
,ADD
会自动解压.
然后构建镜像
docker image build --tag local:fromscratch .
生成了镜像
然后可以运行容器
docker container run -it --name alpine-test local:fromscratch /bin/sh
然后会进入 Alpine
命令行中, 执行 cat /etc/*release
可以输出系统信息
这个过程很简洁, 这得感谢 Alpine 的简介, 如果使用其他发行版会复杂很多.
如何使用其他发行版不在本书范围, 如果有需要可以阅读本章最后的进一步阅读一节.
进一步阅读的内容包括:
- 你可以在这里找到官方
Docker
容器镜像的指南: https://github.com/docker-library/official-images/ - 一些帮助你从已有安装创建容器的工具 (很多打不开). 这里似乎类似于案例中的
alpine
, 是文件系统的软件. - 完整的 Go HTTP Hello World 应用程序: https://github.com/geetarista/go-http-hello-world
2.3.4 使用 ENVs
本节介绍强大的 ENV
, 如果你是开发人员, 会比较熟悉它.
它可以在 Dockerfile
中编写默认值, 同时在运行容器时, 不用重新构建, 也可以进行修改配置.
默认值定义在
Dockerfile
中. 运行时可以利用环境变量进行配置.
ENV
语法有两种
ENV <KEY> <VALUE>
ENV <KEY>=<VALUE>
两种语法的区别在于:
- 不使用等号的, 一行只允许定义一组环境变量. 但是阅读更为清晰.
- 使用等号的, 一行可以定义多组环境变量, 使用空格分隔.
可以使用命令 docker image inspect <IMG_ID>
来查看环境变量.
下面看一个实例: 仅仅使用已安装的 NGINX
, 使用 Dockerfile
来构建一个简单的镜像.
使用 Alpine Linux
, 我们的操作如下:
- 设置环境
ENV
来定义我们需要安装的PHP
版本. - 安装
Apache2
, 以及我们指定版本的PHP
. - 设置镜像, 确保
Apache2
可以正常运行. - 删除
index.html
文件, 添加index.php
文件. 来显示phpinfo
命令执行的结果. - 在容器中导出
80
端口. - 设置
Apache
为默认进程.
注意: PHP5 不是长期支持版. 因此, 我们使用较老的 Alpine Linux 3.8. 它是最后一个支持 PHP5 的版本.
FROM alpine:3.8
LABEL maintainer="R <R@xxx>"
LABEL description="xxx..."
ENV PHPVERSION 7
RUN apk add --update apache2 php${PHPVERSION}-apache2 php${PHPVERSION} && \
rm -rf /var/cache/apk/* && \
mkdir /run/apache2/ && \
rm -rf /var/www/localhost/htdocs/index.html && \
echo "<?php phpinfo(); ?>" > /var/www/localhost/htdocs/index.php && \
chmod 755 /var/www/localhost/htdocs/index.php
EXPOSE 80/tcp
ENTRYPOINT ["httpd"]
CMD ["-D", "FOREGROUND"]
然后使用下面命令来构建使用 PHP7 的惊醒
docker build --tag local/apache-php:7 .
然后作者介绍了构建的过程, 并对构建的日志进行了简要说明.
然后使用下面的命令运行容器
docker container run -d -p 8080:80 --name apache-php7 local/apache-php:7
注意, 没有 PHP6 的版本, 细节可以参考: https://wiki.php.net/rfc/php6
然后修改 Dockerfile
中的 PHPVERSION
的值为 5, 然后重新构建
docker image build --tag local/apache-php:5 .
然后运行容器
docker container run -d -p 9090:80 --name apache-php5 local/apache-php:5
最后执行 docker image ls
来查看镜像的尺寸
可见 PHP7 比起 PHP5 小得多. 然后作者逐步描述了这个构建的过程. 实际上就是解释了差值语法. 这属于 Linux shell 的部分.
下面是一个更为高级的案例: 使用 Dockerfile
, 来安装, 并使用 HashiCorp 来配置 Consul. 在该容器中, 使用 ENV
来定义版本, 以及文件的 SHA256.
FROM alpine:latest
LABEL maintainer=""
LABEL description=""
ENV CONSUL_VERSION=1.7.1
ENV CONSUL_SHA256=09f3583c6cd7b1f748c0c012ce9b3d96de95a6c0d2334327b74f7d72b1fa5054
RUN apk add --update ca-certificates wget && \
wget -O consul.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip && \
echo "$CONSUL_SHA256 *consul.zip" | sha256sum -c - && \
unzip consul.zip && \
mv consul /bin/ && \
rm -rf consul.zip && \
rm -rf /tmp/* /var/cache/apk/*
EXPOSE 8300 8301 8301/udp 8302 8302/udp 8400 8500 8600 8600/udp
VOLUME ["/data"]
ENTRYPOINT ["/bin/consul"]
CMD ["agent", "-data-dir", "/data", "-server", "-bootstrap-expect", "1", "-client=0.0.0.0" ]
可以看到, 使用 NEV 可以简单的通过修改, 就可以构建不同的内容, 同时基于 GITHUB 这类软件, 可以很好的管理.
其中有些不明的指令, 例如
VOLUME
, 不用担心, ch4 管理容器 中会介绍.
2.3.5 使用多阶段构建 (multi-stage build)
本节是使用 Dockerfile
的最后一个部分, 将会使用一个新的构建方式.
在前面的实例中, 我们通过包管理器直接将已编译好的软件添加到容器中.
如果我们需要自己编译, 那么就需要完整的编译环境. 它会非常占存储空间.
要自己编译需要一些步骤:
- 下载构建环境容器的镜像, 并开始构建容器
- 拷贝源代码到构建容器中
- 在构建容器中编译源代码
- 将构建完成的应用程序从构建容器中拷贝出来
- 删除构建容器
- 重新使用
Dockerfile
创建镜像, 并将构建好的应用拷贝到镜像中.
这个步骤只是一个流程描述, 在 Docker 17.05
之后, 提供了一个多阶段构建的功能.
Dockerfile
包含两个构建部分:
- 第一个部分, 名为
builder
, 使用来自DockerHub
的官方GO
容器镜像. 这里从GitHub
下载源, 编译成静态二进制程序.
FROM golang:latest as builder
WORKDIR /go-http-hello-world/
RUN go get -d -v golang.org/x/net/html
ADD https://raw.githubusercontent.com/geetarista/go-http-hello-world/master/hello_world/hello_world.go ./hello_world.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM scratch
COPY /go-http-hello-world/app .
CMD ["./app"]
这里作者使用
ADD
+URL
目的是下载一个未压缩的文件.
编译得到的二进制文件包含一个 HTTP 服务, 从操作系统角度来看不需要其他内容, 因此可以使用 scratch
开始构建镜像. 也就是说镜像中只包含可执行的二进制程序. 不包含构建环境.
连系统都不包含.
构建镜像使用:
docker image build --tag local:go-hello-world .
经过测试, 这段 go 代码执行会有问题. 但具体什么问题不清楚. 可能是网络问题.
RUN go get -d -v golang.org/x/net/html
会报错.
最后作者演示了构建镜像, 运行容器, 测试 HTTP 访问, 然后停止容器, 删除容器等几个步骤.