logo
0
0
WeChat Login
docs: 增加 dockerfile 指令解释

自定义镜像之 Dockerfile 详解

Docker 生态之所以如此繁荣,是因为有许许多多的组织或者开发者贡献了大量的功能不同的镜像,这些镜像被用在各种场景中,比如软件分发,CI/CD,云原生应用部署,可观测性等等。

我们将从最简单的镜像创建方式开始,只需将一个容器实例 commit 作为镜像即可。然后,我们将探索一种更强大、更实用的镜像创建方法:Dockerfile。

从容器创建镜像

首先使用交互式运行一个 alpine 容器。

docker run -it alpine

然后我们在容器中执行一些命令,比如安装一个软件,然后退出容器。

apk update apk add figlet figlet "hello docker" exit

这样,我们就在 alpine 容器中安装了 figlet 工具,当然,之后我们会安装一些更加有用的软件, 比如 git,nginx 等等。然后我们需要将这个新的容器环境跟其他人分享,我们可以通过 commit 命令将容器保存为一个镜像。

docker ps -a #查看容器 docker commit <container_id>

这样,我们就创建了一个装有 figlet 的镜像,我们可以通过 docker image 命令查看。

docker image ls

从上一个命令中,获取新创建镜像的 ID,将其重新 tag 为 alpine-figlet。

docker tag <image_id> alpine-figlet

然后我们就可以使用这个新的镜像了。

docker run alpine-figlet figlet "hello docker"

最后我们也可以使用 docker push 命令将镜像推送到镜像仓库中,其他人便可以使用 docker pull 来使用这个镜像了。

Dockerfile 详解

上述从容器创建镜像的方式虽然简单易懂,但是如果涉及版本迭代的时候, 比如下次我需要再额外安装一个 git 命令,就需要重新 commit 一个容器,然后重新 tag 一个镜像, 这样比较麻烦,而且容易出错。因此,我们需要一种更加灵活的镜像创建方式,这就是 Dockerfile。 我们来使用 Dockerfile 来完成上述的同样的事情,最后使用 docker build 命令来构建镜像。

docker build -t alpine-figlet-from-dockerfile .

同样可以使用这个镜像

docker run alpine-figlet-from-dockerfile figlet "hello docker"

这样当我们需要安装 git 的时候,只需要修改 Dockerfile 中的命令后重新构建镜像即可。

docker build -t alpine-figlet-from-dockerfile . docker run alpine-figlet-from-dockerfile git

Dockerfile 指令

核心构建指令

指令说明示例
FROM指定基础镜像,必须是第一条指令FROM ubuntu:20.04
WORKDIR设置工作目录,后续指令在此目录执行WORKDIR /app
COPY复制文件/目录到镜像中COPY . /app
ADD复制文件到镜像中,支持URL和自动解压ADD app.tar.gz /app
RUN在构建时执行命令RUN apt-get update
CMD容器启动时的默认命令CMD ["python", "app.py"]
ENTRYPOINT容器启动时的入口点,不会被覆盖ENTRYPOINT ["./start.sh"]

网络和存储指令

指令说明示例
EXPOSE声明容器监听的端口EXPOSE 8080
VOLUME创建挂载点VOLUME ["/data"]

环境配置指令

指令说明示例
ENV设置环境变量ENV NODE_ENV=production
ARG定义构建时参数ARG VERSION=1.0

元数据和高级指令

指令说明示例
LABEL添加镜像元数据LABEL version="1.0"
ONBUILD当镜像作为基础镜像时触发的指令ONBUILD COPY . /app

健康检查和Shell指令

指令说明示例
HEALTHCHECK定义容器健康检查HEALTHCHECK CMD curl -f http://localhost/
SHELL指定默认shellSHELL ["/bin/bash", "-c"]

小结

Dockerfile 到底是什么呢?

Dockerfile是一种静态文件,用来声明镜像的内容。

它为什么如此重要呢?

Dockerfile给容器化实践提供了一种规范,让创建镜像的操作简单化,标准化。 简单化让开发者可以快速上手,标准化让镜像可以重复使用,可移植,可复用。这些好处从侧面上推动了Docker的普及。

使用 Dockerfile 构建一个 jupyter notebook 镜像

接下来让我们使用 Docker 来构建一个真实可用的镜像,比如 jupyter notebook 镜像。Dockerfile

docker build -t jupyter-sample jupyter_sample/

该镜像使用 RUN 指令来安装 jupyter notebook,使用 WORKDIR 指令设置工作目录, 使用 COPY 指令将代码复制到镜像中,使用 EXPOSE 指令来暴露端口, 最后使用 CMD 指令来启动 jupyter notebook 服务。

使用上述镜像来启动 jupyter notebook 服务。

docker run -d -p 8888:8888 jupyter-sample

我们使用了 -p 参数来将容器内的 8888 端口映射到宿主机的 8888 端口,在 cnb 上我们可以通过添加一个端口映射来实现外网访问。

port_forward

点击这个浏览器图标,就可以访问 jupyter notebook 服务了。

使用多阶段构建来打包一个 golang 应用

在实际开发中,我们经常需要构建 golang 应用。 如果使用传统的单阶段构建,最终的镜像会包含整个 Go 开发环境,导致镜像体积非常大。 通过多阶段构建,我们可以创建一个非常小的生产镜像。

创建一个 main.go 文件, 一个普通构建的 Dockerfile 以及一个多阶段构建的 Dockerfile

构建镜像:

docker build -t golang-demo-single -f golang_sample/Dockerfile.single golang_sample/ docker build -t golang-demo-multe -f golang_sample/Dockerfile.multi golang_sample/

运行容器:

docker run -d -p 8080:8080 golang-demo-single docker run -d -p 8081:8081 golang-demo-multe

容器运行成功后可以通过如下命令行来访问,可以看到两个容器都是在运行我们写的 golang 服务。

curl http://localhost:8080 curl http://localhost:8081

让我们来对比一下单阶段构建和多阶段构建的区别:

# 查看镜像大小 docker images | grep golang-demo

你会发现最终的镜像只有几十 MB,而如果使用单阶段构建(直接使用 golang 镜像),镜像大小会超过 1GB。这就是多阶段构建的优势:

  • 最终镜像只包含运行时必需的文件
  • 不包含源代码和构建工具,提高了安全性
  • 大大减小了镜像体积,节省存储空间和网络带宽

这种构建方式特别适合 Go 应用,因为 Go 可以编译成单一的静态二进制文件。在实际开发中,我们可以使用这种方式来构建和部署高效的容器化 Go 应用。

空镜像 scratch

似乎每个镜像都会基于一个基础镜像,那么最开始的镜像是什么呢?

scratch 是 Docker 提供的一个特殊的空镜像,它是一个完全空白的镜像,不包含任何文件系统、shell、包管理器或其他任何内容。它的大小为 0 字节,是一切镜像的起点。

使用 scratch 构建 Ubuntu 镜像

创建一个 Dockerfile 来构建自定义 Ubuntu 镜像:

重要说明:scratch 镜像是完全空白的,没有任何可执行文件,包括 tarrm 等命令。 因此在 scratch 阶段不能执行任何 RUN 指令。只能使用 COPY、ADD、ENV、WORKDIR、CMD 等不需要执行命令的指令。

构建和测试

# 进入 scratch_sample 目录 cd scratch_sample/ # 构建镜像(直接从远端下载) docker build -t custom-ubuntu-scratch . # 运行容器 docker run -it custom-ubuntu-scratch # 在容器中测试 cat /etc/os-release ls -la /