作者 | Timothy Mugayi

译者 | 弯月,责编 | 夕颜

封图 | CSDN付费下载自视觉中国

出品 | CSDN(ID:CSDNnews)

在使用Docker的大部分时间里,我们并不关心其内部的工作原理。仅凭启动一个Docker容器并且让应用程序运行良好,并不能说明你已经实现了一个良好的解决方案。有时,由于时间限制,我们只能复制粘贴Docker镜像,却没能真正理解实现细节以及如何构建Docker镜像的细微差别。

在本文中,我们将探讨Docker的最佳实践和反模式。反模式是人们对于反复出现的问题的一般解决方案,这些方案没有效率,甚至会完全抵消Docker技术栈带来的好处。

下面我们来看看我们的哪些做法不可取。

我们需要的标签

标签是必不可少的,我们需要通过标签传达有关Docker镜像的信息。你可以将标签视为Docker镜像ID的别称。Git标签负责标记特定的提交,而Docker标签与之类似,可以给不同时间点上的Docker镜像添加版本。忘记打标签是小事,但会带来一些弊端,具体来讲,如果未指定标记,则默认镜像将被标记为latest。

FROM your_image_name:latest

如果你频繁执行该操作,那么很有可能镜像不是最新的,可能指向的是旧版本。因此,请使用适当的标签并遵守某个版本控制标准,例如语义版本控制。这样,Docker镜像使用者才能确保Docker镜像的兼容性并时刻保持最新,还可以有计划地使用正确的版本。

还有一个情况应该避免。你可以利用最新的默认标签(如FROM python3:latest),从Docker镜像仓库中提取最新的镜像。乍一看,这种做法似乎是个好主意,但却有一些意想不到的副作用:每个最新的请求可能都会派生出与以前的构建完全不同的Docker镜像。弄明白Docker镜像损坏的原因将会变得很困难,因为镜像本应该是不可变的。因此,我强力建议使用特定的标签来标记镜像(例如:python3:1.0.1)。这种方法可以确保你的Dockerfile保持不变。

在同一个容器中运行多个服务

虽然你可以在同一个容器中运行多个服务,但我并不建议你这么做,原因有两个。在使用Docker服务时,我们应该努力维持责任单一性。最佳做法是,组成应用程序的每个服务都应在各自的容器中运行,请务必将每项独立的功能都打包到单独的独立容器镜像中。

将多个服务添加到一个Docker镜像的做法似乎很诱人,但是你不应该将容器镜像视为虚拟机。一个容器包含多个服务,可能会导致你的应用程序很难水平扩展。Docker容器核心概念是,它们都是瞬态的,专为分发而设计,这对于现代Web应用程序来说很理想,因为它的瞬态特性、扩展和并发会非常容易。添加多个服务会增加管理分发的难度。

另外,单个容器上的多个服务还会加大管理安全性的难度。庞大的镜像可能会降低CI/CD的速度,你需要小心。

使用LABEL对镜像进行分类

这并不能说是反模式,但我认为值得一提。我在处理各种Docker镜像时注意到了一件事:有时这些镜像的创建者没有使用LABEL maintainer标签。这个标签可在事件中设置镜像的Author字段,当出现问题或需要澄清时,这个标签可以方便大家了解该与何人内部联系;如果镜像是公开共享的,也可以知道该与哪个外部的人联系。

这绝不是唯一可以使用的标签。你可以根据需要定义各种标签,来对镜像进行分类,定义许可信息,也可以定义标签来帮助自动化。

除了maintainer,还可以使用多行标签:

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="RBTSB Incorporated"
LABEL vendor2=TIPTAPCODE\ Incorporated
LABEL com.example.release-date="202-04-02"
LABEL com.example.version.production="0.0.1"

Docker 1.10之前的单行标签会创建新的docker层,如果你使用的是最新版的Docker,则不必担心创建额外的层。

LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"

我们应该将尽可能多的元数据添加到不可变的Docker镜像,以方便追踪,提高可见性和可维护性。

避免构建依赖特定环境的镜像

在构建Docker镜像时,我们应始终牢记不变性。最好不要使用带有dev、test、staging和production的镜像,因为这会破坏单一来源的原则。另一个问题是,如果在不同环境上验证或调试,则这种做法无法保证镜像的相似。

为什么要使用非Root容器?

在默认情况下,Docker容器以root身份运行。以root用户身份运行的Docker容器可以完全控制主机系统。然而,出于安全考虑,我并不推荐这种做法。使用非root运行的Docker容器镜像可以多一层保护,在生产环境中通常建议使用非root容器。但是,由于这些容器由非root用户运行,因此无法执行需要特殊权限的任务。如果需要利用USER指令指定非root用户(如以下示例所示),则需要进行一些上下文切换。

FROM python:3.6-slim-buster
LABEL maintainer="Timothy Mugayi <timothy.mugayi@gmail.com>"RUN apt-get update && apt-get install -y --no-install-recommends \
wget && rm -rf /var/lib/apt/lists/*# Dumb init
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64
RUN chmod +x /usr/local/bin/dumb-initRUN pip install --upgrade pipWORKDIR /usr/src/appCOPY requirements.txt .RUN pip install -r requirements.txtCOPY helloworld.py .USER 1001ENTRYPOINT ["/usr/local/bin/dumb-init", "python3", "-u", "./helloworld.py"]

如果你使用的基础镜像不是由root生成的,但需要切换回root,则可以执行以下操作:

 FROM <namespace>/<image>:<tag_version>USER root

不要在单个容器内运行太多进程

容器的优点以及容器相对于虚拟机的优势在于,通过多个相互交互的容器组成一个完整的应用程序。我们无需在单个容器中运行完整的应用程序。相反,我们应尽可能将应用程序分解为多个服务,并将服务分布到多个容器上。这样可以最大程度地提高灵活性和可靠性。

不要在容器内安装操作系统

使用一段时间Docker后,你就有可能遇到这种情况。虽然你可以在容器内安装和运行完整的Linux操作系统。但是你应该这样做吗?

这可能不是一个好主意。Docker镜像是使用层的概念构建的,因此添加的东西越多,镜像就会膨胀得越大。一个完整的操作系统并不是Docker的理想使用情况。在理想情况下,你的容器内部只应该加载必要的组件。

不要在容器内运行不必要的服务

为了充分利用容器的优势,你应该尽可能保持容器精简。这样可以最大程度地提高性能,并最大程度地降低安全风险。因此,请避免运行并非绝对必要的服务。例如,若非必要请不要在容器内运行SSH服务,你可以选用其他方式(例如Docker exec)登录到容器。

不要在容器内加载不必要的程序

你必须知道这种反模式。在使用Docker时,很多人倾向于在镜像中加载sonar之类的工具,来确保代码覆盖率等。

使用从Docker CE 17.05+开始支持的多阶段构建(Multistage builds)模式,你可以在Dockerfile中使用多个FROM stage。临时构建的阶段容器将被丢弃,因此最终的运行时容器镜像都很精简。举个例子,当你需要从源文件编译一些二进制文件,然后在第二个阶段中将二进制文件复制到最终镜像中。

优点:

  • 构建速度更快,精简的镜像可以让CI/CD过程更快,镜像通过网络进行传输时花费的时间也更少。

  • 需要的存储空间更少。

  • 冷启动(拉取镜像)更快。

  • 潜在的受攻击面更少。

缺点:

  • 容器内的工具较少,但这是保证容器的精简所需付出的一点小小的代价。

在容器内加入供观察的工具

即便没有监视解决方案,你也可以照常运行容器,但需要牢记,从本质上来说,你很难知道容器内部发生了什么,特别是随着容器数量的增加。

Docker本身自带了许多指标,能够公开每个运行容器的CPU、内存、网络和I/O使用情况,可通过Docker远程API的/stats端点访问。App dynamics 和 Newrelic 是两个现成的程序,可以与Docker镜像一起打包,帮助你从应用程序级别了解应用程序和容器的运行状况。

基础镜像的爱恨交织

“你知道是谁构建了这个景象,里面都添加了什么吗?”

这里说的都是可追溯性。牢记安全性是所有软件开发人员都应努力的方向。你需要了解如何跟踪Docker镜像的源,并了解里面有什么。

你需要时刻牢记以下几点:

  • 镜像是怎样创建的。

  • 验证镜像在创建后未经更改。

  • 验证镜像的内容。

  • 扫描镜像是否有安全漏洞。

我们可以通过一些工具对容器进行静态分析。这些工具涉及方方面面,已超出了本文的范围,但是我建议你花一些时间来学习。

Clair是一个有趣的工具,可为你的Docker应用程序提供自动容器漏洞和安全扫描。扫描基于常见的漏洞和公开(CVE)的数据库。如果你本地运行Docker,则可以下载Postgres,然后将Clair连接到Postgres上。以下是启动和运行Clair所需的最低配置:

$ mkdir $PWD/clair_config
$ curl -L https://raw.githubusercontent.com/coreos/clair/master/config.yaml.sample -o $
PWD/clair_config/config.yaml
$ docker run -d -e POSTGRES_PASSWORD="" -p 5432:5432 postgres:9.6
$ docker run --net=host -d -p 6060-6061:6060-6061 -v
PWD/clair_config:/config quay.io/coreos/clair:latest -config=/config/config.yaml

如果你想更改Postgres的端口,请确保同时修改config.yaml文件。如果你的系统上已经运行了另一个Postgres,请注意不要将docker端口更改为5432以外的端口。

clair:
database:
# Database driver
type: pgsql
options:
# PostgreSQL Connection string
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
source: host=localhost port=5432 user=postgres password=123456 sslmode=disable statement_timeout=60000

在镜像启动并执行后,你可以运行Docker ps来确认容器已启动且正常运行:

你需要注意Clair没有Web UI或CLI。你只能通过REST API或第三方CLI工具来使用。

Bayan Collector是一款轻巧的静态分析应用程序,可以从镜像仓库启动容器以进行静态分析,可以运行任意脚本并收集有用的信息(例如已安装的软件包),强制执行策略,以及对镜像进行校验。

Docker Bench for Security是由Docker团队创建的工具,它会在Docker的宿主上运行一个安全最佳做法清单,并标记发现的任何问题。

不要在容器镜像中存储敏感数据

请一定避免这个错误。如果你的镜像对外公开,或者开发人员无意中将镜像推送到公共的Docker镜像仓库中,那么就会导致隐私和敏感信息的泄露。切记永远不要在Dockerfile中对敏感信息使用COPY或`。

为了避免这种情况,请将敏感数据存储在安全文件系统上,并让容器进行连接。一般情况下,这个文件系统应该在容器所在的宿主上,或者可通过AWS Elastic Block Storage(EBS)等块存储或S3等对象存储服务来使用。

此外,你应避免在Docker镜像中存储安全凭证。作为开发人员,有时我们会采用偷懒的做法,在代码中硬编码密码和私钥。你需要习惯使用-e参数在运行时为Docker容器指定环境变量。

你也可以通过env-file,从文件中读取环境变量。通过CMD或`从第三方来源获取凭据的自定义脚本也可用于获取Docker容器所需的相关凭据。

不要在容器内存储数据或日志

容器化改变了日志的性质。容器是瞬态、无状态应用程序的理想选择。本质上,存储在运行容器中的所有数据都应该是短暂的,你可能已经注意到,当容器关闭时,数据将丢失。因此,将数据存储在Docker容器之外的做法更值得推崇。有一些工具可以帮助你提取Docker日志,并将其放在更永久的数据存储中。

在处理Docker日志时需要牢记一点:Docker至少拥有三个级别的日志记录,即Docker容器、Docker服务和宿主操作系统,你选择的日志记录方法应该能够提取所有级别的日志。

不要写入容器的文件系统

每次将内容写入容器的文件系统都会激活“写时复制”策略。这会使用存储驱动程序(deviermapper、overlayfs或其他驱动)创建新的存储层。在实际应用中,这会给存储驱动带来巨大的压力,特别是使用Devicemapper或BTRFS的情况下。

确保容器只向卷中写入数据。对于小型的临时文件可以写入tmpfs,因为tmpfs是仅存在于内存或交换分区中的临时文件系统。

不要运行PID 1

这是许多人都不知道的常见问题。

Docker中的进程运行时没有init进程负责清理子进程,所以容器可能会出现僵尸进程,导致意料之外的错误。

使用tini或dumb-init

PID 1在Unix中很特殊,因此在init系统中忽略它通常会导致进程和信号处理错误。这会导致类似于容器无法优雅地停止,或本应摧毁的容器出现泄露等问题。

僵尸进程指的是运行已经停止,但依然在进程表中占据位置的进程,因为它们的父进程没有调用wait系统调用进行回收。理论上,每个结束的进程都会非常短暂地呈现僵尸状态,但有些进程的僵尸状态会持续很久。

如果某个进程会生成新进程,但信号处理的实现不好,无法捕获子进程的信号并将其终止,那么可以使用Tini或dumb-init。例如,bash脚本就无法正确处理或释放信号。

下面是运行dumb init的示例,其中prepare.sh可以是shell脚本,也可以是用于执行应用程序的命令:

RUN wget -O
/usr/local/bin/dumb-init
https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64RUN chmod +x /usr/local/bin/dumb-initENTRYPOINT ["/usr/local/bin/dumb-init", "/usr/bin/prepare.sh"]

或者如果你选择tini,可以参考下面使用Python anaconda conda的示例:

RUN conda install --yes -c
conda-forge tiniENTRYPOINT ["tini", "-g", "--", "/usr/bin/prepare.sh"]

最后,这里是一个更一般的示例,不依赖于任何编程语言:

FROM node:13.12.0-slimMAINTAINER Timothy Mugayi <timothy.mugayi@gmail.com>ENV TINI_VERSION='v0.13.0'
# Add tini init, see https://github.com/krallin/tini
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tiniRUN chmod +x /tini
# Set tini as entrypoint
ENTRYPOINT ["/tini", "--"]

在CI/CD中使用Dockerfile格式检查

跟编程语言一样,Docker也有格式检查工具。

采用格式检查工具有许多好处,因为它会强制你使用最佳实践。在CI/CD过程中加入格式检查,可以帮助团队避免常见的错误,并在构建产品Docker镜像时建立最佳实践。可以从hadolint开始,这是一个用Haskell编写的Dockerfile格式检查工具,能够解析Dockerfile成AST,并在AST上执行规则检查。

我们来运行一个示例。要对Docker进行格式检查,可以执行以下命令:

$ docker run --rm -i
hadolint/hadolint < Dockerfile

格式检查完成后会显示以下结果:

上图中的错误码DL3008“Pin”来自hadolint的错误码描述状态。固定版本号可以强制构建时安装特定版本的包,不论缓存中是什么。这个技巧可以减少依赖包意料之外的改变导致的构建错误。

最后的一点想法

在本文中,我们谈了很多内容,覆盖了从安全到Docker镜像流水线的方方面面。

有许多技巧可以让Docker镜像更好、更安全。希望这篇文章可以给你带来一些启发,帮助你理解应该做什么、不应该做什么,并为你提供一些在构建内部或外部Docker容器镜像时可以应用的方案。

原文链接:

https://medium.com/better-programming/docker-best-practices-and-anti-patterns-e7cbccba4f19

本文为CSDN翻译文章,转载请注明出处。

推荐阅读

  • 大促下的智能运维挑战:阿里如何抗住“双11猫晚”?

  • 20万个法人、百万条银行账户信息,正在暗网兜售

  • 当莎士比亚遇见Google Flax:教你用字符级语言模型和归递神经网络写“莎士比亚”式句子

  • Hyperledger Fabric 和企业级以太坊,谁才是企业首选?

  • 面试时遇到「看门狗」脖子上挂着「时间轮」,我就问你怕不怕?

  • 同期两篇 Nature:运行温度高于 1K 的量子计算平台问世!

  • GitHub 标星 10,000+,Apache 顶级项目 ShardingSphere 的开源之路

    真香,朕在看了!
    

从安全到镜像流水线,Docker 最佳实践与反模式一览相关推荐

  1. 打docker镜像_从安全到镜像流水线,Docker 最佳实践与反模式一览

    作者 | Timothy Mugayi 译者 | 弯月,责编 | 夕颜 封图 | CSDN付费下载自视觉中国 出品 | CSDN(ID:CSDNnews) 在使用Docker的大部分时间里,我们并不关 ...

  2. Docker 镜像优化与最佳实践

    云栖TechDay41期,阿里云高级研发工程师御坂带来Docker镜像优化与最佳实践.从Docker镜像存储的原理开始,针对镜像的存储.网络传输,介绍如何在构建中对这些关键点进行优化.并介绍Docke ...

  3. Docker 最佳实践

    Docker 最佳实践 [编者的话]本文是Docker使用过程中的一些最佳实践.虽然很多都是老话重谈,但是很多人在使用过程中还是没有遵守,比如每个进程只使用一个容器这个最佳实践,有很多人都来问,如果不 ...

  4. 面向数据科学家的 Docker 最佳实践

    作者 | Thushan Ganegedara 译者 | 弯月,责编 |  唐小引 头图 | CSDN 下载自东方 IC 出品 | CSDN(ID:CSDNnews) 作为一名数据科学家,每天我都要与 ...

  5. Docker最佳实践:构建最小镜像

    镜像大小其实是衡量我们容器打包技术的重要指标,我们应该在不影响应用正常运行的情况下,尽量让我们的容器镜像变得更小,这样,不管是从安全还是维护效率角度来讲,都是最佳实践. 本文我们从两种情况阐述我们的问 ...

  6. 第 3 章 镜像 - 018 - 镜像命名的最佳实践

    为镜像命名 创建镜像时 docker build 命令时已经为镜像取了个名字,例如:  docker build -t ubuntu-with-vi 这里的 ubuntu-with-vi 就是镜像的名 ...

  7. Dockerfile制作jdk镜像和微服务镜像部署的最佳实践【Dockerfile实战】

    因为公司之前搭建测试服务器是我搭建的,其中包含使用docker来部署微服务项目,于是将这篇Dockerfile的最佳实践记录于此,为避免大家被坑,希望此文能帮你解除疑惑~ ps:因为是公司服务器不是个 ...

  8. Ranger开源流水线docker化实践案例

    1.背景 开发部门决定在Apache Ranger开源社区贡献代码,目标是个人国内排名Top1,世界排名Top2,并且在已经成为Ranger项目的Committer情况下,争取成为Ranger项目的P ...

  9. Docker最佳实践-部署LNMP环境

    标签(linux): docker 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 环境准备 [root@docker ~]# cat /etc/redhat-r ...

最新文章

  1. 绕开bug的feed_dict,用自己的数据集训练DCGAN
  2. html禁止文本选择,[译]用CSS来禁止文本选择
  3. boost::type_traits模块用法的一些示例
  4. @Data注解不生效的原因
  5. bzoj1096 [ZJOI2007]仓库建设
  6. spring boot集成oss
  7. 嘉年华回顾丨杜小勇教授带你解密One Size Does not Fit All?
  8. Spring Boot基础学习笔记23:用户自定义授权管理
  9. 柳传志退休后拿近1亿薪酬?联想回应了:严重失实
  10. 无痕 PS、读得懂文字,OpenAI 的二代 DALL·E 惊艳亮相
  11. 使用SVN clang: error: linker command failed with exit code 1 (use -v to see invocation)
  12. 3389服务器信息是什么意思,服务器3389端口监控问题
  13. 如何在 Mac 上设置 iCloud 功能?
  14. 最经济方案 谈P2P电影服务器(转)
  15. 怎么写脚本实现自动输入密码
  16. Uva375 内接圆和等腰三角形
  17. 代码要写注释吗?写你就输了
  18. 【IDEA 教程系列第 14 篇】idea 快速跳转到错误位置
  19. 基于R语言对哺乳动物睡眠时间sleep数据集的分析
  20. HDOJ(HDU) 1862 EXCEL排序(类对象的快排)

热门文章

  1. 部编版是什么版本_部编版是人教版吗
  2. 华硕2020年显卡_TrendForce集邦咨询:2020年液晶显示器年出货成长率达5.4%,华硕成长率居冠、三星排名上升...
  3. ue4中在物体上加ui_UE4 物体位置同步相关源码分析浅谈
  4. 【文末赠书】漫画:什么是 “跳表” ?
  5. 新版:全世界最前沿的125科学问题
  6. 清华“最强本科生”揭晓!网友:我大概是来凑数的……
  7. 人一生中最该看清的5个真相
  8. ALBERT、XLNet,NLP技术发展太快,如何才能跟得上节奏?
  9. 美国“四院院士”特伦斯谈人工智能“瓶颈”:远未达极限,数学家已经有了实现AI可解释性的理论工具...
  10. 你需要学好知识图谱----用AI技术连接世界