Docker镜像和操作系统安全更新

12

考虑到我正在使用Node.js 10.8.0 node:10.8.0-jessie Docker镜像 作为我的Dockerfile应用程序的基础镜像。该应用程序在生产环境中稳定运行,已经有几个月没有更新了。

Node.js 10.8.0 镜像是基于buildpack-deps:jessie镜像创建的,它本身又基于buildpack-deps:jessie-scm镜像创建的。而这又是基于buildpack-deps:jessie-curl镜像创建的,其基础镜像debian:jessie

Debian Jessie的系统/安全更新会定期发布。在传统的托管环境中,我可以使用sudo apt-get update && sudo apt-get upgrade来更新我的主机,这样就没问题了。

但是我如何确保我的在容器中运行的Node.js应用程序获取最新的Debian Jessie更新和补丁,同时保持Node.js node:10.8.0-jessie?
在我的CI中对我的应用程序Dockerfile运行sudo apt-get update && sudo apt-get upgrade,并定期创建一个新的映像来部署容器并不是正确的方法。
因为所有这一切都始于debian:jessie镜像,我希望该镜像会定期更新以及所有相关的镜像也同步更新。
然后我将再次拉取Node.js 10.8.0映像(--no-cache),重新构建我的应用程序映像,并部署它。
我的问题是:这个假设正确吗?是否有任何官方的Docker文档介绍这个对我来说似乎至关重要的工作流程?如何获得关于debian:jessie和node:10.8.0-jessie镜像补丁发行的通知?
2个回答

6
首选的工作流程是拉取更新的基础镜像,或重新构建本地构建的基础镜像。然后重新构建子镜像。如果有可能,您应该只运行安装命令而非升级命令。要将应用程序固定到特定版本,可以在安装命令中添加该版本的依赖项。
这种方法优于在现有镜像中升级软件包的原因有几个:
- 升级镜像仍会保留旧版软件包的镜像层,从而使镜像不必要地增加大小。 - 升级需要为每个新版本创建一个新的Dockerfile,并将父镜像设置为前一个版本,导致维护上的挑战。 - 通过升级过程复制镜像需要先安装许多旧版本并再次构建层。 - 每次升级都会添加图层,减少了联合文件系统的性能。一些存储驱动程序还有限制,限制了您可以创建的图层数,经过足够多次的升级周期之后,这些限制最终将失败。 - 升级引入了状态漂移的风险。如果升级链中的一个镜像包含在Dockerfile链中丢失的更改,或者以前的安装状态如何影响新的安装运行,那么您将得到一个不可重现的环境,可能因未知原因而中断。
唯一可能执行升级的情况是上游基础镜像没有得到维护。最好是找到不同的基础镜像或在本地构建它。但当两种方法都不可行时,我可能会构建一个本地基础镜像,作为未维护的外部基础镜像的子镜像,第一步是升级软件包。在Dockerfile中,此操作如下:
FROM scratch as remote-unmaintained
ADD unmaintained.tgz /

FROM remote-unmaintained as local-base
RUN upgrade-cmd

FROM local-base as app
COPY app /
CMD /app

谢谢。有没有可靠的方法检查上游基础镜像是否定期更新? - Alexander Zeitler
你可以查看过去的表现,但那只是过去,因此不可靠来进行未来预测。例如,这里是nginx镜像的标签历史记录:https://hub.docker.com/r/library/nginx/tags/。不幸的是,如果上游没有使用不同的标签运行构建,则标签历史记录的帮助性会降低。 - BMitch
但是,当仅进行操作系统更新时,这是否意味着 nginx:1.15.1 会被更新为 nginx:1.15.2?我期望裸机上的 nginx 1.15.2 与 docker 化的 nginx:1.15.2 相匹配,而不管 nginx:1.1.5.2 更新了多少次操作系统。 - Alexander Zeitler
您想更新nginx的新版本以获取应用程序补丁。这样做,如果构建包括它们,您将获得底层操作系统的补丁。没有一个标准答案,这完全取决于存储库的构建和维护方式。 - BMitch

-4
当我想要使用Docker镜像时,我总是尝试找到这个镜像的alpine版本。这背后有原因:

Alpine Linux是一个基于musl libc和busybox的安全、轻量级Linux发行版。

https://alpinelinux.org/

如果您还不了解Alpine Linux,但您对此感兴趣,我建议您浏览他们的网站。
好处在于这个发行版由很好的社区管理。这意味着该发行版经常更新以整合新的安全修复程序。
重要的是要理解,这非常有趣(就Docker而言),因为正如您在问题中所说,构建的镜像在时间上是不可变的。
通过在Docker中使用基于Alpine的镜像,您可以确保具有来自当前已知漏洞/安全威胁的最新修补程序。要了解他们多久更新一次他们的Docker镜像,您可以查看此页面:https://hub.docker.com/r/library/alpine/tags/ 基于此镜像的每个镜像将同时更新,并且有许多镜像:

我不认为我的解决方案是最好的,但它可以在一定程度上帮助:

在我的服务器上,我创建了一个定时任务,每天拉取我正在使用的Alpine镜像,例如node:8-alpine

之后,每当我基于这个镜像构建我的应用程序时,我就确信我有了最新的更新。
如果你想深入了解,甚至可以在拉取node:8-alpine镜像后构建你的应用程序。
关于如何防止新的更新/升级,我不知道,但是如果您想使用Alpine镜像,您可以订阅他们的RSS:https://alpinelinux.org/atom.xml 使用Alpine镜像的许多其他原因,但由于它将超出主题,所以我只给你提供这篇文章:https://nickjanetakis.com/blog/the-3-biggest-wins-when-using-alpine-as-a-base-docker-image

编辑 1:

既然您有一个 CI 环境并且使用它来构建镜像,您可以定期创建自己的 Dockerfile,在其中执行 apt-get update && apt-get install 命令,然后将此镜像用作应用程序镜像的基础镜像。但这种方式会向最终镜像添加层,并增加其大小。


3
我觉得这并没有真正回答主帖的问题。假设基础的alpine镜像已经更新了,你怎么知道中间镜像node:8-alpine也进行了更新,并且需要将其应用到你自己的镜像中? - David Maze
我也觉得Alpine镜像有点被高估了:我看到很多Dockerfiles都是从安装完整的C工具链、多个语言运行时和开发者工具开始的,即使你在比较大的Linux发行版上节省了100MB,也会被掩盖在噪音中。 - David Maze
我的帖子主要回答了问题中的“维护和稳定部分”。但是,针对您提出的疑问,您是正确的:由于图像是不可变的,如果要更新基本图像,则需要重新构建它。开发人员在他们的Dockerfile中添加库,因为Alpine图像只有运行命令所需的内容。但是,如果您需要像Node.js这样的框架,则需要安装其依赖项,这是正常的。该图像仍将比基于Debian或Ubuntu图像的官方Node.js图像更小。 - Paul Rey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接