译者前言

从本文可看出,作者 Steve 非常尊重长久以来屹立不倒的良好软件工程实践,这使得本文可以作为 Dockerfile 规范的参考。

本文原作者:Steve Mushero

原文链接:https://steve-mushero.medium.com/dockerfile-good-practices-5d677b9538a4

Docker 已经无处不在,写 Dockerfile 的方式也五花八门,但很难找到一个可以作为范例的,大都较为简单。少数写的好的,既没有解释清楚好的原因,也没有给出必要的规范或指引。

因此,本文旨在为复杂的 Dockerfile 编写提供有价值的规范与指南。下面我将基于我的 DockerFile Annotated Example 而展开 (https://steve-mushero.medium.com/dockerfile-annotated-example-64a31ef3d144)。

1. General Concepts

首先,介绍一下一般性规范,即长久以来软件项目都应遵守的良好实践,可直接应用于 Dockerfile。

这些规范在 Dockerfile 的场景下,作用发挥得更加明显。这是因为 Dockerfile 是一种高度动态的文件,从项目伊始到进入维护阶段很久,修改 Dockerfile 都非常正常。因此需要认真努力,来维持它持续的高质量。

2. Syntax Block

The syntax block,如果要用,则必须放在第一行。虽然这让漂亮的标题和注释块屈居第二,但是我们别无选择。

很少有语言需要 syntax block,但使用 Docker 的各种实验选项时,可能需要开启它。下面是例子:

# syntax = docker/dockerfile:experimental

3. Title & Comment Block

文件的正文,应开始于以下部分:

  • 一个真正的标题 (title);

  • 目的 (purpose);

  • 所有者 (owner) ;

  • 和其他文件头部注释应有的标准内容。

Dockerfile 通常放在顶层目录,它需要独立运行,因此在大型项目中,这里的标题和信息比一般的源文件重要得多。

还包括给后来人看的各种假设、issues、复杂性说明。例如所需的 Docker 版本,此文件如何与 Composer、Kubernetes 等系统交互。

你还可以指出此 Dockerfile 产生的容器,是如何与它所属的更大系统交互的,以及任何与开发、测试、生产环境相关的要点,都应该有说明。

4. TODO

任何时候都应该有一个统一的 TODO 段落,虽然可以在文件中这写一点,那写一点,但通常也有一些更大或较为 meta 的 TODO。

5. Build-Time Arguments

不建议使用,但如果你一定要用,可以加一个注释掉的段落并附带说明,便于之后及时了解它的用途。

6. FROM

  • FROM 语句是重中之重;

  • FROM “段落”是重中之重;

  • 带注释的、带历史记录的、带现存问题记录的 FROM “段落”是重中之重!

如果选择此镜像或版本有任何特殊原因,必须记录下来。

下面的例子中,我们针对版本选择的特殊原因做了记录,这样就避免了后续不知情的开发者随意更改。

# Based on official PHP container
#   Note below on assumptions from that base
# Use 7.3 for now as no mod_php yet via php7-apache2 on AlpineFROM php:7.3.16-alpine

7. Global Arguments

建议少用,如果你预感以后会用到,就事先给它准备一个注释掉的段落,并附带说明,这样就限定了别人加 Global Arguments 的位置及用途(在本例中,它必须在 FROM 之后)。

# Global Args from Docker/BuildKit must be added here after FROMARG TARGETPLATFORM

8. ONBUILD

不要用,但如果你觉得以后可能会用到,就事先给它准备一个注释掉的段落,并附带说明,原因同 Global Arguments。

# ONBUILD used to run things in downstream builds
#   Such as single layer copies
#  Not used for now# ONBUILD

9. Labels

Labels 很重要,且千奇百怪,它的应用场景也较为广泛,包括 build、deployment、lifecycle management……等多种流程中。

本案例中,我们对其加以限定,只允许使用 OCI Labels,使其更通用和易于管理。

# OCI Annotations from https://github.com/opencontainers/image-spec/blob/master/annotations.md
LABEL org.opencontainers.image.maintainer="Steve.Mushero@ELKman.io"
\      org.opencontainers.image.authors="Steve.Mushero@ELKman.io"
\      org.opencontainers.image.title="ELKman"                      #      org.opencontainers.image.revision="" FROM git
#      org.opencontainers.image.created="2020-05-01T01:01:01.01Z"

10. Base Container Info

总要有基础镜像,它通常已经预装了一些程序,例如 Apache、PHP、Java 或其他容器。

你应当清楚地知道,你对基础镜像做了哪些假设、预期哪些文件处于哪些路径、使用了哪些预设的环境变量。

把这些假设调查清楚,并在注释中记录(即使之后它们可能会发生变化)是好的实践。这样我们在修改此文件时,才能辨别自己修改的位置和内容是否正确。基础镜像越复杂,你越应该这么做,因为后续构建镜像者很容易卡在这些问题上。

你也可以不使用基础镜像的预设,自己用到的部分,全都用自己提供的版本,以确保不被意外修改。但这有可能破坏原镜像的完整性,因此不建议这么做。

# Offical PHP Apache container defaults & assumptions
#  From base Dockerfile:
#   User:  www-data
#   WORKDIR:  /var/www/html (Note we change this)
#   php.ini:  In /usr/local/etc/php (Note we update this via sed)
#   Apache Conf:  In /etc/apache2 (Note we update this via sed)
#   Packages:  LOTS of dev like gcc, g++, make, etc. probably remove

11. ENV Variables

你可以在这里设置各种用户、路径等。

一定要将它们设置为 ENV,而不是在其他地方 hard-code,后者会使得 Dockerfile 变得难以修改。

虽然只要改变一个 ENV 值就会使缓存失效,但仍然建议在能用的地方尽可能使用 ENV,否则很容易出现难以排查的错误,尤其是随着时间的推移,人和事发生变化时。

ENV MAINWORKDIR /var/www
ENV MAINUSER root
ENV MAINGROUP root
ENV APACHEUSER www-data
ENV APACHEGROUP www-data

12. Install & Repo Setup

Yum apt 只用来安装你需要的东西,如果必要,可以构建 yum 或 apt 缓存。这个段落还可能包括安装程序、其选项等的注释,尤其是希望避免缓存、最小化空间时。

# apk supports --virtual to group & later remove packages
#   RUN apk add --no-cache --virtual .build-deps gcc
#   RUN apk del --no-cache .build-deps

13. ENV Install Tools

将基本的 OS 工具放在一个像这样的 ENV 变量中,会更容易管理,且未来修改时更加整洁。为支持 troubleshooting(问题排查)和部署,这个列表最开始会较长,后续随着某些工具的去掉会逐渐缩短(当然,有必要的话该加就加)。

# Lists of tools - will shrink over time to reduce size
#  Alpha order, please
#   Telnet not available on alpine
ENV INSTALL_TOOLS   \bash              \busybox-extras    \curl              \less

14. Install Basic Tools

使用变量安装基本工具,这样就再也不必修改实际安装的那一行,就可以更容易地确保有正确的安装程序选项等,而不必重复和编辑这些行:

# Update Repo Info & Install Basic PackagesRUN apk update --no-cache && \apk add --no-cache --clean-protected ${INSTALL_TOOLS}

15. Install Specialized Packages

对某些特殊包,或不通过发行版的包管理器安装的(例如 CentOS 上不通过 yum 安装),要将其放到单独段落,它们通常有特别的安装顺序、流程、选项。

单独的段落,使得它们显而易见,更容易管理。

# Install Specialized Packages
#   We need SQLite for Telescope & other usesENV EXTRA_PACKAGES sqlite3
RUN apk update --no-cache && \apk add --no-cache ${EXTRA_PACKAGES}

16. Remove Useless Stuff

加一个段落,用于删除用不到的软件或程序,这会让容器体积小,且从安全角度看,这会减小攻击面。例如,许多基础镜像都包含 gcc,而在运行时你永远都用不到 gcc,所以请把它去掉。

这假设你将进行多阶段构建或使用 Squash 选项,这两个选项都会进行最终复制和扁平化,以便只包括所有层中的活动文件。

# Stuff to remove for smaller size
#   Packages:  Some images have dev stuf like gcc, g++, make, etc.ENV REMOVE_PACKAGES gcc
RUN apk del $(REMOVE_PACKAGES)

17. Section Markers

段落标记是好实践,这使得文件结构清晰,内容便于查找,并防止未来的修改被随机添加到错误的地方。这是保持 Dockerfile 卫生的重要举措。

##### End of OS Items #####

18. Service Items Section

容器内的服务可以非常多样化,从 Apache 或 Nginx 到大型代码库,再到像 MySQL 或 Elasticsearch 这样的大型数据系统。它们都有自己的需求和复杂性,大多数都相当简单,但部署过程一般都很复杂。通常情况下,最好使用它们的专用容器,但有时需要将它们包含在你的容器中,比如将 Apache 包含在 PHP Laravel 应用程序容器中。

在这种情况下,还有很多细节要处理。这些部分通常是前文讲过的部分的迷你版,包括包列表、安装文件和经常就地复制或编辑的配置文件。

这是针对 Apache 的,从一个 ENV 变量开始,包含我们需要安装的包的列表,然后安装它们。

##### Apache Items ###### Install Apache & PHP Modules
#   php7-apache2 installs much of PHP & Apache,ENV PHP_PACKAGES php7 php7-apache2 php7-json php7-phar php7-iconv \php7-openssl php7-curl php7-mbstring php7-fileinfo \php7-tokenizer php7-dom php7-session php7-pdo php7-pdo_sqlite \php7-xml php7-simplexml php7-xmlwriter php7-zipRUN apk update --no-cache && \apk add --no-cache --clean-protected ${PHP_PACKAGES}

19. Specialized Configurations

设置配置文件的方法有很多种,你应该将它们分开并清楚地记录下来。
在本案例中,我们希望保留几乎所有的默认值,所以与其复制文件,不如对配置文件做少量原地修改。

基本上,我们先设置 ENV,然后运行 sed 来实现修改。

请注意,在第一部分中,我们最开始用复制制品文件的方式,但后来转移到使用基本镜像中包含的文件的方式。

# Using default Alpine Apache configs and modifying from there
# Then we override, which lets us use unmodified official filesENV APACHECONFFILE       /etc/apache2/httpd.conf
ENV APACHECONFDDIR       /etc/apache2/conf.d
ENV APACHEVHOSTCONFFILE  ${APACHECONFDDIR}/default.conf
ENV APACHESECURITYFILE   ${APACHECONFDDIR}/security.conf# Copy over PHP file from PHP-Apache
#   Skipping as seems the Alpine version has one: php7-module.conf
# COPY /deploy/apache/docker-php.conf ${APACHECONFDDIR}/docker-php.confRUN echo && \# Remove stuff we don't want nor need for security, etc.rm /etc/apache2/conf.d/userdir.conf && \rm /etc/apache2/conf.d/info.conf && \## Apache main config overrides#sed -ri -e 's/^#ServerName.*$/ServerName elkman/g' ${APACHECONFFILE}           && \sed -ri -e 's/^ServerTokens.*$/ServerTokens Prod/g' ${APACHECONFFILE}           && \sed -ri -e 's/^ServerSignature.*$/ServerSignature Off/g' ${APACHECONFFILE}

20. Other Services

接下来是其他服务和配置,在本例中是 PHP。

基础镜像中已经安装了 PHP,所以我们只需要处理配置,通过复制和原地修改,再加上清理基础镜像并删除用不到的部分,以确保它都是清晰的。

##### PHP Items ###### PHP Configs - Complicated as there're two PHP on Alpine 7.3
# Some PHP containers use date-specific extension dir in php.ini
# On Alpine, careful of which php is used for CLI
#   vs. mod_php to verify their paths - Very confusing# Disble default php so can't get confused on configs, modules, etc.
#   Then the one we want works fine in path
RUN mv /usr/local/bin/php /usr/local/bin/php.bad
# For Alphine 7.3 we use /usr/bin/php and /usr/etc/php
ENV PHP_INI_DIR /etc/php7
ENV PHPEXTDIR "/usr/lib/php7/modules/"
# Use the default prod configuration from php:7.4.4-apache (php.ini-development also exists)
COPY deploy/php/php.ini-production $PHP_INI_DIR/php.ini
# Copy overrides
COPY deploy/php/php-override-prod.ini $PHP_INI_DIR/conf.d/
COPY deploy/php/php-sourceguardian.ini $PHP_INI_DIR/conf.d/
# Install composer & prestissimo for parallel downloads if needed
RUN curl -sS https://getcomposer.org/installer | \php -- --install-dir=/usr/local/bin --filename=composer && \composer global require hirak/prestissimo --no-plugins --no-scripts

21. Add Your Code

现在你已经安装了服务,此时应该添加自己的代码,这次从构建环境中拷贝。

你也可以从 git 仓库 pull,以包(rpm 包?)的形式安装等。但是我们的构建环境已经 pull 了所有的代码、制品、构建脚本、Dockerfile 等,所以直接拷贝是最简单的。

COPY 命令非常具体,且已经过大量测试。

还请注意,对于 .dockerignore 上的注释、权限等,团队需要保持充分且一致的理解。

.dockerignore 通常是很多很多个小时工作的结果,所以需要任何时候都需要大家清晰地理解它。

#### Add Code ##### Need to change WORKDIR as Apache default is /var/www/html
WORKDIR ${MAINWORKDIR}# Copy files from VM
#   Copy App Directories - Not setting owners here, it's done later
#     Note will ignore the .dockerignore things, so tune that, too
#   Currently we depend on git to create/ignore all the dirs we need, especially in storage
#       We do this because later we want to git clone into container as part of build
COPY app app
COPY config config
COPY resources resources
COPY routes routes
COPY bootstrap bootstrap
COPY database database
COPY storage storage
COPY public public
COPY tests tests
# Copy Specific Files
COPY artisan ./
COPY composer.json ./
COPY composer.lock ./
COPY package.json ./
COPY package-lock.json ./
COPY webpack.mix.js ./

22. Building & Compiling Things

写了代码后,通常需要 build 之后才能使用 —— 对于 JavaScript 来说很常见,不过在我们的例子中,运行 PHP Composer 也是容器构建的一个步骤。

跟往常一样,这里的文档、目的、特殊问题(issues)要做到 crystal clear,因为这通常是几天或几周的工作和测试的结果。

在本例中,我们在容器构建过程中运行 PHP Composer 来获取并设置所需的库。

这很麻烦,而且我们还直接从外部 COPY 一份作为缓存,来提高性能,这是经过大量试验并踩了很多坑的结果。

# Run Composer install
#   ENV COMPOSER_CACHE_DIR - Can set if needed, now using default
#   Cannot use RUN mount here as we need a cache dir, and mount only supports files (as far as I can tell)
#   Copy in composer cache, use and removeCOPY /composer-cache/files /root/.composer/cache/files
# Note: Have to run 'composer dump-autoload' for some reason here; seems install not fully doing it
RUN composer install --no-dev --classmap-authoritative --no-ansi \--no-scripts  --no-interaction --no-suggest && \composer dump-autoload && \rm -rf /root/.composer/cache

然后,跑 npm 以获得 Vue.js 和所有必要的 Javascript 代码。

# NPM Stuff & Webpack (part of dev script)
# RUN npm install --no-optional
# Moving to ci instead of install (ci uses lock file)
RUN npm ci --no-optional
RUN npm run prod

在这个例子中,我们在寻求最优解的同时,先绕过问题。管理 JavaScript 的东西非常有挑战性,所以我们保留 build 好的目录,从而避免重新执行容易崩溃的 build 过程。

# Move public artifacts to doc root - do this after npm run
# Get .htaccess, too
# We missing anything in the standard html?
# Not moving as better to point Doc Root to our public
# RUN mv public/* html/ && mv public/.htaccess html/

23. Data & Things

一旦所有的服务、代码都准备好了,我们就开始准备数据。在本案例中,就是创建空的 sqllite 的 .db 文件(但表的创建和初始化不在此处,而是在更后面的步骤里)。

# Move DB file from source tree to writable storage area
#   For now, touch empty file - we initialize this DB later
#   Later we can copy a default DB if we wish
#     RUN mv database/db.sqlite storage/database/RUN touch storage/database/db.sqlite

24. Environment Setup

一旦所有的服务、代码、数据都准备好了,我们就可以设置 .env 文件,这些文件在运行时会用到,在接下的一些 build 步骤中也会用到。

# .env File - Need to copy for productionCOPY .env.production .env# Copy dusk env for now for testingCOPY .env.dusk.testing .env.dusk.testing

25. Setup System

现在是时候对系统本身进行设置了。

对于 Laravel (PHP 框架)来说,这意味着运行一堆 Laravel 命令来设置 PHP 配置、秘钥、初始化数据库。

这部分会经常变更,所以好的注释是很重要的。

# Setup configs & code; may later do as other user, fixed UID, etc.
# Generate a new key each time (though we also need on install)RUN php artisan key:generate# Optimize & cache; do before we migrate or run other artisan jobsRUN php artisan optimize# Seed tables, Telescope, etc. data into DB
#   Run after keygen, before other artisan cmdsRUN php artisan migrate# Update DB version to app code version; this for container's initial DB onlyRUN php artisan elkman:update

26. Remove Logs

移除上述所有步骤产生的日志,这样镜像更干净,且体积更小。

谨记,一定要清除在构建过程中创建的任何日志(因为其中可能包含你不希望用户看到的敏感信息)。

# Remove log file so we start clean (and with right log file owner)RUN rm -rf storage/logs/*

27. File Permissions

因为前面的各种 COPY 及命令执行,使得在 Docker 中设置文件权限这件事,很容易变成一团糟。对于 Laravel 这样复杂的运行时环境,尤其如此。

所以一定要提前决定好用什么方法做,并且写清楚注释或文档。我之所以这么说,是因为我们做了无数测试,对此深有体会。

下面的例子中,在同一个地方,一次性把权限设置都做完。这样的好处是:容易查找、容易修改、容易加特例。

# Permissions carefully managed here
#   Set all directory permissions
#   Set global owner & read perms, then set for writable, exec, etc.ENV READPERM 440
ENV WRITEPERM 660
RUN chown -R ${MAINUSER}:${APACHEGROUP} ./                   && \chmod -R ${READPERM}  ./                                 && \chmod -R ${WRITEPERM} storage                            && \# Set all dirs to be executable so we can get into them#  Do after any chmods abovefind ./ -type d -print0 | xargs -0 chmod ug+x

28. Final Purging

加一个最终的清理段落,降低镜像大小。

### Data Purge
# Need to purge & cleanup
# rm composer & caches
# rm npx & caches
# rm any man pages, etc.
# vendor cleanupRUN rm -rf /tmp/*# End of apk installs, we can clean
#   As apk cache clean seems uselessRUN rm -rf /var/cache/apk/*

至此,一个相当好的 Dockerfile 就诞生了。

必 看

 加入我们 

岗位名称:云操作系统研发工程师

工作职责:

1. 使用容器化技术解决大数据产品在私有化部署及 SaaS 场景下面临的多种技术挑战;

2. 开发基于 Kubernetes 的自动化部署及基础应用平台,提升产品的运维效率和稳定性。

岗位要求:

1. 计算机或相关专业毕业,本科及以上学历;

2. 对操作系统、网络等底层基础知识有深入的理解;

3. 至少熟练掌握 C/C++/Java/Python/Go 中的一种编程语言,有良好的编码习惯;

4. 熟悉 Kubernetes、Docker 原理及应用;

5. 熟练掌握 Linux Shell/Python/Go 语言开发者优先;

6. 熟悉 Hadoop 生态,有分布式系统开发经验者优先;

7. 做事积极主动,责任心强,有快速学习能力。

这里有完全扁平的管理,这里有一群专注做事的伙伴,这里有开放的沟通文化,这里有轻松舒适的办公环境,这里有我们,这里欢迎你!

扫码二维码投递简历

✎✎✎

更多内容

  • 解读四大应用场景,神策分析云之 LTV 分析模型抢先体验

  • 金融新基建系列报告:银行业六大中期趋势展望

  • 神策数据王灼洲:如何进行有效的数据治理,提升数据价值?

▼ 点击“阅读原文”,查看更多岗位

Dockerfile 布局的良好实践相关推荐

  1. 阿里云弹性计算产品总监王志坤:在分布式云领域的产品布局和最佳实践

    近日,2022 年全球分布式云大会北京站顺利举办,阿里云弹性计算产品总监王志坤在主论坛上发表了主题为<强劲可靠.无处不在的云,助力企业云上业务创新>的演讲,为大家详细阐述了阿里云在分布式云 ...

  2. Dockerfile构建镜像最佳实践

    参考文章:Dockerfile构建镜像最佳实践 在前文Dockefile及命令详解中我们已经学习了如何通过Dockerfile构建镜像以及命令的详细说明,但是在生产环境或项目使用时如何构建出一个尽可能 ...

  3. 淘宝弹性布局方案lib-flexible实践

    2个月前,写过一篇文章<从网易与淘宝的font-size思考前端设计稿与工作流>总结过一些移动web中有关手机适配的一些思路,当时也是因为工作的关系分析了下网易跟淘宝的移动页面,最后才有那 ...

  4. 前端设计-css网格布局的最佳实践

    越来越常见的问题-现在人们正在使用css网格布局来生产-似乎是"最好的做法是什么?"这个问题的简短答案是使用规范中定义的布局方法.您选择使用的规范的特定部分,以及如何将网格与其他布 ...

  5. 2.React Native Flex布局介绍以及实践

    好久没有写博客,一直在用自己的印象笔记记录一些问题.2017年了,想重新的把博客写起来.也希望通过这个平台交一些朋友. 没有具体的介绍基本的语法,主要是说明了与标准的CSS Flex的一些区别以及一个 ...

  6. 四:(之六_镜像发布)Dockerfile语法梳理和实践

    *6.镜像发布 1>注册Docker Hub账号并登陆. build的镜像名称格式必须是: dockerhub账户名/标识: 使用docker login在项目目录下登录: 浏览器: 2> ...

  7. 汇集ATJ等互联网大厂和国内外大型银行的最新数智化布局与落地实践 | DAMS上海站...

    近年来,因数据衍生.关联.发展起来的技术层出不穷,我们不断探索数据从资源转化为资产的方法,又面临在数据共享和互通中引发的安全隐患:我们迫切希望进行企业核心数据库的开源化.国产化替换,又碍于" ...

  8. Dockerfile的使用和实践(10)

    一.实验概述 将一个Python程序打包成一个docker  image,然后运行成一个Container容器 二.实验步骤 1.app.py的内容 from flask import Flask f ...

  9. [libgdx游戏开发教程]使用Libgdx进行游戏开发(7)-屏幕布局的最佳实践

    管理多个屏幕 我们的菜单屏有2个按钮,一个play一个option.option里就是一些开关的设置,比如音乐音效等.这些设置将会保存到Preferences中. 多屏幕切换是游戏的基本机制,Libg ...

最新文章

  1. thinkphp mysql desc table_Thinkphp 连接数据库、查询、添加
  2. mongodb 索引建立问题
  3. 越狱解决iphone4s外放无声音
  4. powershell自动化操作AD域、Exchange邮箱系列(7)—get-aduser/get-user获取信息 取值方法及区别
  5. Leetcode 1218.最长定差子序列
  6. gd库多点画图 php_用 PHP 实现身份证号码识别
  7. windows最好用的mp3格式转换软件推荐
  8. 俄文输入法_【俄语怎么学】手把手教你使用俄语输入法
  9. mini6410 LED驱动程序及LED测试程序的设计
  10. Windows PE文件各个节(Section)分析
  11. 计算机组成原理学习笔记一
  12. 李宏毅(机器学习)机器学习概述+线性回归案例分析
  13. android 布局覆盖 超出一部分_谈谈移动端屏幕适配的几种方法
  14. 27岁从业软件测试5年的我被无情的辞退了
  15. MD5 加密安全吗?
  16. linux命令part,技术|十个鲜为人知的 Linux 命令-Part 3
  17. intel酷睿游戏计算机,英特尔11代酷睿游戏芯片跑分曝光
  18. 智能网联汽车专业术语
  19. 动态规划 - 整数拆分
  20. 计算机系高考激励的句子,60条高考激励句子

热门文章

  1. python爬虫表格中清除空格_爬虫清洗:python strip()函数 去空格\n\r\t函数的用法
  2. java实型常量用十六进制表示_Java 基本语法
  3. java m4a文件拼接_面试官:为啥不提倡字符串拼接?看阿里java开发手册怎么说
  4. python3 isinstance用法_对python中assert、isinstance的用法详解
  5. AutoML简要概述
  6. python决策树分类 导入数据集_python+sklearn实现决策树(分类树)
  7. 斜杠的意思是或还是和_开启你的斜杠人生
  8. 数字图像处理技术详解程序_安装地暖施工程序有哪些 安装地暖技术要求是什么【详解】...
  9. python接口返回json处理_python 接口返回的json字符串实例
  10. linux ls 输出对齐,理解 Linux 中 `ls` 的输出