实践_ 适用于Docker和Kubernetes容器构建的最佳实践

在容器和虚拟化广泛普及的今天,如何构筑安全清洁的容器是一个值得关注的问题。 与对安全领域系统的要求一样,“只安装必要的应用程序”这一最小化原则也是容器构筑的基本法则。 另一方面,最小化的应用减小了镜像的大小并节省了下载时间,同时还减少了容器中的应用程序并使容器更加安全。 本文介绍了总结行业实践的容器最佳实践。 目的是使容器的构建更快、更安全、更灵活。 本文假设读者知道Docker和Kubernetes,但也可以独自构筑Docker容器的规范。

当容器应用程序开始使用容器时,最常见的误解之一是将容器用作虚拟机。 这样做往往使他们不能轻易满足特定的需求,非常痛苦,同时也脱离了容器的最大优点。 很多初学者在小组中问了很多理由:为什么Docker不能aaa? 如何实现Docker bbb? 他所需要的答案只是虚拟的。 现代集装箱已经可以满足这些需求,但这大大削弱了集装箱模型的许多优点。 以经典Apache/MySQL/PHP堆栈为例,您可能希望在一个容器中运行所有组件。 但是,最佳做法是使用两个或三个不同的容器: Apache容器、MySQL容器和运行php-FPM的PHP容器。

容器的设计思想与托管的应用程序具有相同的生命周期,因此每个容器只能包含一个应用程序。 容器启动时应用程序启动,容器停止时应用程序也停止。

如果容器中有多个应用程序,则生命周期可能不同,或者状态可能不同。 例如,正在运行的容器可能会突然崩溃或不响应其中一个核心组件。 由于没有额外的运行状况检查,整个容器管理系统( Docker或Kubernetes )无法确定容器是否健康。 缺省情况下,Kubernetes群集不会重新启动容器。

有些公共镜像可能使用以下操作,但不遵循原则

使用流程管理系统(如supervisor )管理容器中的一个或多个应用程序。 使用bash脚本作为容器中的入口点,生成多个应用程序作为后台作业。

信号处理、PID 1和僵尸过程Linux信号是控制容器中过程的生命周期的主要方法。 要将应用程序的生命周期与容器相关联,以便与以前的最佳做法保持一致,请确保应用程序能够正确处理Linux信号。 最重要的Linux信号之一是SIGTERM,用于终止进程。 应用程序还可以接收SIGKILL信号并且不能成功结束进程,或者接收SIGINT信号并输入Ctrl + C命令。

进程标识符( PID )是Linux内核为每个进程提供的唯一标识符。 PID具有命名空间,容器具有唯一的PID集,这些PID被映射到主机系统的PID。 Linux内核启动时,将使用PID1创建第一个进程。 init系统用于管理其他进程,如systemd和SysV。 同样,在容器中启动的第一个进程也是PID1。 Docker和Kubernetes使用信号与容器中的进程进行通信。 Docker和Kubernetes都只能向容器中具有PID 1的进程发送信号。

在容器环境中,必须考虑两个PIDs和Linux信号问题。

Linux内核如何处理信号?

Linux内核处理PID 1的过程方式与其他过程不同。 默认情况下,PID1、信号量SIGTERM不会自动注册,因此SIGTERM或SIGINT对PID1无效。 缺省情况下,必须使用SIGKILL信号来杀死进程,如果进程无法优雅地关闭,则可能会发生错误、中断数据写入(对于数据存储)或监视不必要的警告。

典型的初始化系统如何处理孤立过程?

一般的初始化系统(如systemd )也用于删除(捕获)孤立的僵尸进程。 僵尸进程(父进程死亡的进程)将添加到具有PID 1的进程中,捕获并关闭。 然而,容器需要在映射到容器PID 1的过程中进行处理。 如果进程处理不正确,则可能会出现内存不足或其他资源不足的风险。

这些问题有一些常见的解决办法

用PID 1执行信号处理程序并登录

该方案用于解决第一个问题。 如果应用程序启用了以受控方式生成子进程(通常在这种情况下),则可以避免第二个问题。 最简单的方法是在docker文件中使用CMD或ENTRYPOINT命令启动进程。 例如,在以下docker文件中,nginx是第一个唯一启动的过程:

来自debian:9

runapt-getupdate&&2

apt-get安装- y nginx

EXPOSE 80

CMD

图中的蓝色表示可再利用的层,红色表示需要重建的层。 重复使用层的原则还导致另一个结果:如果生成步骤取决于存储在本地文件系统中的缓存类型,则必须使用相同的生成步骤生成缓存。 如果未生成此缓存,则可能会使用先前生成的旧缓存执行生成过程。 在包管理器(如apt和yum )中最常见的问题是同时安装RUN命令所需的所有库。 如果更改以下docker文件中的第二个RUN步骤,apt-get update命令不会重新运行,apt缓存将过期。

来自debian:9

RUN apt-get update

run apt-get安装- y nginx

而是在一个RUN步骤中组合两个命令

来自debian:9

runapt-getupdate&&2

apt-get安装- y nginx

删除不必要的工具要保护应用程序免受攻击者的攻击,请尝试删除所有不必要的工具以减少应用程序的攻击面。 删除实用程序,例如netcat。 因为你可以用necat自由构建反壳。 如果容器中没有安装netcat,攻击者就不会这么容易使用。

即使未容器化,此最佳实践也适用于任何工作负载。 与传统虚拟机和裸机服务器相比,使用容器要简单得多。

其中一些工具有助于调试。 例如,远离此最佳实践可能需要详细的日志、跟踪、配置文件和应用程序性能管理系统。 事实上,它不能依赖于本地调试工具。 因为在很多情况下,特权很高。

文件系统内容镜像必须保留尽可能少的内容。 如果可以将应用程序编译为单个静态链接的二进制文件,则将该二进制文件添加到临时镜像将创建最终镜像。 镜像只包含一个应用程序,不包含其他应用程序。 通过减少镜像中打包的工具数量,可以减少容器中可能执行的操作。

文件系统安全镜像没有工具是不够的。 必须防止潜在攻击者安装工具。 在这里,可以将两种方法结合起来

首先,不要以超级用户身份在容器中运行。 此方法提供了级别1安全性,允许攻击者使用嵌入镜像(如apt-get或apk )的软件包管理器来防止root拥有的文件发生更改。 要使用此方法,必须禁用或卸载sudo命令。

要以只读模式启动容器,请使用docker run命令的--read-only标志,或者使用Kubernetes的readOnlyRootFilesystem选项。 您可以使用PodSecurityPolicy在Kubernetes中强制执行。

注意:如果应用程序需要将临时数据写入磁盘,则只需使用readOnlyRootFilesystem选项将emptyDir卷添加到临时文件中。 Kubernetes不支持装载到emptyDir卷,因此无法启用noexec标志进行装载。

最小化镜像并生成较小的镜像具有上载时间和下载时间更快等优点。 这对于Kubernetes中pod的冷启动时间尤为重要。 镜像越小,下载节点的速度越快。 但是很难构建一个小镜子。 这是因为最终镜像可能会错误地引入构建依赖关系或未优化的镜像层。

使用最小基础镜像的基础镜像是Dockerfile中的FROM命令引用的镜像。 docker文件中的所有命令都是基于此镜像构建的。 基础镜像越小,生成的镜像越小,下载和加载速度越快。 例如,alpine:3.7镜像比centos:7镜像小几十米。

您也可以使用scratch基本镜像。 这是一个空镜像,可以创建自己的运行时环境。 如果要运行的应用程序是静态链接的二进制文件,则使用临时基本镜像很容易

FROM scratch

COPY mybinary /mybinary

CMD

减少镜像要减少无效的镜像大小,必须严格遵守仅安装所需应用程序的原则。 您可能需要临时安装工具包。 稍后删除。 然而,这种方法也存在问题。 Dockerfile中的每个命令都会创建镜像层,因此创建镜像层后,在以后的步骤中删除镜像实际上无法减小镜像的大小。 (数据仍然存在,但只是隐藏在下面)。 例如

错误docker文件

来自debian:9

runapt-getupdate&&2

apt-get安装- y \

下面的Dockerfile将hello二进制文件合并到第一个容器中,并注入第二个容器。 由于第二个容器从头开始,因此生成的镜像只包含hello二进制文件,而不包含生成过程中所需的源文件和目标文件。 但是,二进制文件必须静态链接才能正常工作。

来自: 1.10 as builder

WORKDIR /tmp/go

COPY hello.go ./

run CGO _ enabled =0go build-a-ldflags '-s '-hello

FROM scratch

CMD

容器注册表扫描服务器和虚拟机,软件漏洞扫描是一种常见的安全手段,通过集中的软件扫描系统列出每台主机上安装的软件包和漏洞源

因为容器原则上是不变的,所以有脆弱性的情况下不建议修复脆弱性。 最佳实践是重建镜像、打包和重新部署修补程序。 与服务器相比,容器的生命周期要短得多,身份的定义要好得多。 因此,同样集中检测容器孔是不好的方法。

为了解决此问题,可以在托管镜像的容器注册表中扫描漏洞。 这可以检测容器镜像的漏洞。 当镜像上载到注册表或更新漏洞库时,将执行扫描或定期开始计划任务的扫描。

检测到漏洞后,可以使用脚本启动自动漏洞修补过程。 希望与gitbab等版本管理的ci/cd流水线合作,持续进行镜像构建,修复漏洞。 一般程序包括:

1 .将镜像存储在容器注册表中并启用漏洞扫描。

2 .配置作业以定期从容器注册表中检索新漏洞,并根据需要触发镜像重建。

3 .在构建新镜像后,通过连续导入系统CD,将镜像导入验证环境。

4 .手动检查是否正常。

5 .如果找不到问题,请手动将灰阶放置在生产环境中。

正确的标记镜像Docker镜像通常由两部分标识:名称和标签。 例如,对于centos:8.0.1镜像,centos是名称,8.0.1是标记。 如果Docker命令未指定最新标记,则默认情况下使用最新标记。 名称和标签对在特定时间点必须是唯一的。 但是,如果需要,可以将标签重新分配给其他镜像。 要构建镜像,需要正确的标记,并遵循统一的一致标记策略。

容器镜像是一种打包和发布软件的方法。 标记镜像允许您下载特定版本的软件。 因此,容器镜像上的标记系统与软件发布策略相关联。

使用语义版本标记软件版本的一种常见方法是使用语义版本号( thesemanticversioningspecification )版本号来标记特定版本的源代码( git tag ) 语义化版本号规范是semver.org提出来处理版本号的规则方法,用于改进不同软件版本号格式的混淆。 在本规范中,软件版本号由三个部分组成: X.Y.Z

x是主要版本,如果发生向后不兼容的修改或霸权更新,则x会增加。

y是次版本,如果添加了向后兼容的更改或兼容的新功能,则添加1。

z是修补程序版本,仅应用兼容性修补程序,兼容性修复会增加。

次版本号或修补程序版本号的每个增量都必须是向后兼容的更改。

如果是系统,请根据以下策略标记镜像

最新标签总是指最新的(可能是稳定的)镜像。 创建新镜像时,标签将移动。

X.Y.Z标签指的是软件的特定版本。 请不要移动到其他反射镜。

X.Y标记是软件X.Y辅助分支的最新修补程序版本。 发布新的修补程序版本时,它将被移动。

x标记是x主分支的最新次版本的最新修补程序版本。 当发布新的修补程序版本或新的次要版本时,它们会移动。

使用此策略,用户可以灵活地选择要使用的软件版本。 您可以选择特定版本的X.Y.Z以确保镜像保持不变,也可以选择不太具体的标签以自动检索更新。

如果在Git中连续分发要向其提供哈希标记的系统并且经常分发软件,则语义版本控制规范中的版本号可能不可用。 在这种情况下,处理版本号的常见方法是使用Git commit SHA-1散列(或其短版本)作为版本号。 根据设计原理,Git的提交哈希不变,被引用到软件的特定版本中。

git提交散列可以用作软件的版本号,也可以用作在软件的特定版本中构建的Docker镜像的标记。 这使Docker镜像具有可跟踪性。 在这种情况下,image标签是不变的,因此您可以立即了解特定容器正在运行的软件版本。 在连续配送管线中,用于部署的版本号会自动更新。

考虑使用公共镜像的Docker的一个显着优点是可用于各种软件的许多公共可用镜像。 这些镜子很快就可以开始了。 但是,在设计联机环境的容器策略时,可能存在共同提供的镜像无法满足要求的限制。 以下是可能无法使用通用镜像的限制示例

正确控制反射镜内部的内容。

不想依赖于外部存储库。

我想严格控制生产环境的脆弱性。

每个镜像都需要相同的基本操作系统。

对这些限制的对策都是一样的,而且有很高的代价。 那是必须构筑自己的镜像。 您可以自己配置有限数量的镜像,但如果数量增加。 为了有机会大规模管理这样的系统,可以考虑以下方法

即使镜像很少,也能以可靠的方式自动生成镜像。

可以解决镜像漏洞,并在容器注册表中扫描漏洞。 企业中不同团队创建的镜像将执行内部标准方法。 有几种工具可以帮助您在构建和部署的镜像中实施策略

container-diff用于分析镜像的内容并比较两个镜像之间的镜像。

container-structure-test允许您测试镜像内容是否符合定义的规则集。

Grafeas :工件元数据API,用于存储有关镜像的元数据,并在以后检查镜像是否符合策略。

kuberetes提供了一个批准控制器,允许您在将工作负载部署到kuberetes之前检查许多先决条件。

Kubernetes还提供了Pod安全策略,允许您强制在群集中使用安全选项。

另外,还可以采用将Debian和Alpine等公共镜像用作基准镜像,根据该镜像构筑其他镜像的混合系统。 或者,您可能希望将通用镜像用于某些非关键镜像,并根据其他情况构建自己的镜像。

对于可以在Docker镜像中包含第三方库和软件包的软件,请确保授予适当的许可证。 当Docker镜像发布到公共注册表时,第三方许可证可能会对重新分发进行限制。

总结本文介绍了容器构建过程中应遵循的基本原则。 根据这些原则,可以确保所构建容器的安全、精炼、收缩、控制。 当然,这些条款也只是建议性质。 在满足需求的基础上尽可能遵守。 其中的一些方法只是作为参考,也可以遵守基本原则使用适合自己的解决方法。

大家都在看

相关专题