纯干货!构建Dockfile镜像的十三个最佳实践点

简介: 编写.dockerignore文件构建镜像时,Docker需要先准备上下文 ,将所有需要的文件收集到进程中。默认的上下文包含Dockerfile目录中的所有文件,但是,实际上我们并不需要.git目录,.vscode目录、.idea目录等内容。 .dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少Docker镜像的大小。

编写.dockerignore文件

构建镜像时,Docker需要先准备上下文 ,将所有需要的文件收集到进程中。默认的上下文包含Dockerfile目录中的所有文件,但是,实际上我们并不需要.git目录,.vscode目录、.idea目录等内容。 .dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少Docker镜像的大小。

样例:

.git/
.vscode/
.idea/
复制代码


一个容器只运行单个应用

从技术角度讲,你可以在Docker容器中运行多个进程。你可以将数据库,前端,后端,ssh,supervisor都运行在同一个Docker容器中。但是,这会让你非常痛苦:

  • 非常长的构建时间(如,修改前端之后,整个后端也需要重新构建)
  • 非常大的镜像大小
  • 多个应用的日志难以处理(不能直接使用stdout,否则多个应用的日志会混合到一起)
  • 横向扩展时非常浪费资源(不同的应用需要运行的容器数并不相同)
  • 僵尸进程问题(你需要选择合适的init进程)

因此,建议大家为每个应用构建单独的Docker镜像。


选择合适的基础镜像

合适的基础镜像,如scratch、busybox、alpine、distroless等镜像。可以帮助我们减少镜像的大小。同时,越小的镜像表示无用的程序越少,可以大大的减少被攻击的目标,从而提高了安全性。


将多个RUN指令合并为一个

Docker镜像是分层的,下面这些知识点非常重要:

  • Dockerfile中的每个指令都会创建一个新的镜像层。
  • 镜像层将被缓存和复用
  • 当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效
  • 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效
  • 镜像层是不可变的,如果我们再某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件(只是这个文件在Docker容器中不可见了)。

现在,我们将所有的RUN指令合并为一个。同时把apt-get upgrade删除,因为它会使得镜像构建非常不确定(我们只需要依赖基础镜像的更新就好了)。

FROM ubuntu
ADD . /app
RUN apt-get update \
  && apt-get install -y nodejs \
  && cd /app \
  && npm install
CMD npm start
复制代码


记住一点,我们只能将变化频率一样的指令合并在一起。将node.js安装与npm模块安装放在一起的话,则每次修改源代码,都需要重新安装node.js,这显然不合适。

因此,正确的写法是这样的:

FROM ubuntu
RUN apt-get update && apt-get install -y nodejs
ADD . /app
RUN cd /app && npm install
CMD npm start
复制代码


基础镜像和生产镜像的标签不要使用latest

当镜像没有指定标签时,将默认使用latest 标签。因此, FROM ubuntu 指令等同于FROM ubuntu:latest。当时,当镜像更新时,latest标签会指向不同的镜像,这时构建镜像有可能失败。如果你的确需要使用最新版的基础镜像,可以使用latest标签,否则的话,最好指定确定的镜像标签。

样例:

FROM ubuntu:16.04 # 使用16.04作为标签。
RUN apt-get update && apt-get install -y nodejs
ADD . /app
RUN cd /app && npm install
CMD npm start
复制代码


每个RUN指令后删除多余文件

假设我们更新了apt-get源,下载,解压并安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。但是,运行应用时Docker镜像中并不需要这些文件。我们最好将它们删除,因为它会使Docker镜像变大。

样例:

Dockerfile中,我们可以删除/var/lib/apt/lists/目录中的文件(它们是由apt-get update生成的)。

FROM ubuntu:16.04
RUN apt-get update \
  && apt-get install -y nodejs \
  # added lines
  && rm -rf /var/lib/apt/lists/*
ADD . /app
RUN cd /app && npm install
CMD npm star
复制代码


设置WORKDIR和CMD

WORKDIR指令可以设置默认目录,也就是运行RUN / CMD / ENTRYPOINT指令的地方。

CMD指令可以设置容器创建是执行的默认命令。另外,你应该讲命令写在一个数组中,数组中每个元素为命令的每个单词。

样例:

FROM node:7-alpine
WORKDIR /app
ADD . /app
RUN npm install
CMD ["npm", "start"]
复制代码


使用ENTRYPOINT时,用exec启动命令(可选)

在使用entrypoint的脚本中,我们要用exec命令运行应用。不使用exec的话,我们则不能顺利地关闭容器,因为SIGTERM信号会被bash脚本进程吞没。exec命令启动的进程可以取代脚本进程,因此所有的信号都会正常工作。

相比ADD,优先使用COPY

COPY指令非常简单,仅用于将文件拷贝到镜像中。ADD相对来讲复杂一些,可以用于下载远程文件以及解压压缩包。

样例:

FROM node:7-alpine
WORKDIR /app
COPY . /app
RUN npm install
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
复制代码


设置默认的环境变量,映射端口和数据卷

运行Docker容器时很可能需要一些环境变量。在Dockerfile设置默认的环境变量是一种很好的方式。另外,我们应该在Dockerfile中设置映射端口和数据卷。

样例:

FROM node:7-alpine
ENV PROJECT_DIR=/app
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR
RUN npm install
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \
  NODE_ENV=production \
  APP_PORT=3000
VOLUME $MEDIA_DIR
EXPOSE $APP_PORT
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
复制代码


ENV指令指定的环境变量在容器中可以使用。如果你只是需要指定构建镜像时的变量,你可以使用ARG指令。

使用LABEL设置镜像元数据

使用LABEL指令,可以为镜像设置元数据,例如镜像创建者或者镜像说明。旧版的Dockerfile语法使用MAINTAINER指令指定镜像创建者,但是它已经被弃用了。有时,一些外部程序需要用到镜像的元数据,例如nvidia-docker需要用到com.nvidia.volumes.needed。

样例:

FROM node:7-alpine
LABEL maintainer "jakub.skalecki@example.com"
复制代码


一个镜像可以有多个label。要指定多个labels,Docker推荐尽可能地把多个labels合并到一个LABEL指令中去。每一个LABEL指令会生成一个新的镜像层,如果你使用多个label,将导致构建出一个低效的镜像。


添加HEALTHCHECK

运行容器时,可以指定--restart always选项。这样的话,容器崩溃时,Docker守护进程(docker daemon)会重启容器。对于需要长时间运行的容器,这个选项非常有用。但是,如果容器的确在运行,但是不可(陷入死循环,配置错误)用怎么办?

使用HEALTHCHECK指令可以让Docker周期性的检查容器的健康状况。我们只需要指定一个命令,如果一切正常的话返回0,否则返回1。

样例:

FROM node:7-alpine
LABEL maintainer "jakub.skalecki@example.com"
ENV PROJECT_DIR=/app
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR
RUN npm install
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \
  NODE_ENV=production \
  APP_PORT=3000
VOLUME $MEDIA_DIR
EXPOSE $APP_PORT
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
复制代码


当请求失败时,curl --fail命令返回非0状态。


合理调整COPY和RUN的顺序

我们应该把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存。

样例:

源代码会经常变化,则每次构建镜像时都需要重新安装NPM模块,这显然不是我们希望看到的。因此我们可以先拷贝package.json,然后安装NPM模块,最后才拷贝其余的源代码。这样的话,即使源代码变化,也不需要重新安装NPM模块。

FROM node:7-alpine
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]


相关文章
|
2月前
|
Dubbo 前端开发 Java
让你在组建企业级项目时手到擒来——浅谈各类常用工具和框架概述
让你在组建企业级项目时手到擒来——浅谈各类常用工具和框架概述
|
6天前
|
Kubernetes 安全 测试技术
多环境镜像晋级/复用最佳实践
本文介绍了在应用研发场景中,如何通过阿里云服务实现镜像构建部署的高效和安全。主要关注两个实践方法来确保“所发即所测”。
|
2月前
|
存储 Kubernetes 数据安全/隐私保护
总结归纳Kubernetes | 一站式速查知识,助您轻松驾驭容器编排技术(配置与密码安全)
总结归纳Kubernetes | 一站式速查知识,助您轻松驾驭容器编排技术(配置与密码安全)
61 0
|
2月前
|
Kubernetes API 调度
总结归纳Kubernetes | 一站式速查知识,助您轻松驾驭容器编排技术(水平扩展控制)
总结归纳Kubernetes | 一站式速查知识,助您轻松驾驭容器编排技术(水平扩展控制)
71 0
|
安全 Cloud Native 算法
《云原生架构容器&微服务优秀案例集》——01 互联网——唱鸭 轻松玩转 DevSecOps,用 ACR EE 构建安全高效交付流程
《云原生架构容器&微服务优秀案例集》——01 互联网——唱鸭 轻松玩转 DevSecOps,用 ACR EE 构建安全高效交付流程
351 0
|
存储 监控 安全
「技术管理」有效技术生命周期管理的6个步骤
「技术管理」有效技术生命周期管理的6个步骤
|
安全 Cloud Native 算法
《2023云原生实战案例集》——04 互联网——唱鸭轻松 玩转DevSecOps,用ACR EE构建安全高效交付流程
《2023云原生实战案例集》——04 互联网——唱鸭轻松 玩转DevSecOps,用ACR EE构建安全高效交付流程
|
存储 运维 Kubernetes
开发者测评:相比 Harbor,我选择 ACR 的三点原因
本次活动是为了帮助更多开发者了解容器镜像服务 ACR 的功能、使用方式和产品体验,从而在不同应用场景下,更好地进行容器镜像管理方案的选型。不少开发者在活动中发表了他们在这个过程中的思考,以及选择阿里云容器镜像服务 ACR 的原因。
开发者测评:相比 Harbor,我选择 ACR 的三点原因
|
存储 运维 Kubernetes
docker 原理及在运维工作的地位和作用 | 学习笔记
快速学习docker 原理及在运维工作的地位和作用
164 0
docker 原理及在运维工作的地位和作用 | 学习笔记
|
缓存 JavaScript 前端开发
如何使用本地 Docker 更好地开发?我们总结了这八条经验
如果你像我们一样需要运行许多不同的应用程序,那么将开发环境容器化可以极大地提高工作效率。这里有一些可以优化本地 Docker 环境的技巧。
274 0
如何使用本地 Docker 更好地开发?我们总结了这八条经验