Docker 通过其简单的容器化模型改变了软件开发,让您可以快速将工作负载打包成可复制的单元。虽然 Docker 很容易掌握,但它的用法有更多的细微差别,而不是总是很明显。当您希望优化 Docker 使用以提高效率和性能时尤其如此。
以下是您应该寻找和避免的七种最常见的 Docker 反模式。尽管您的容器和图像可能会满足您的直接需求,但任何这些实践的存在都表明您正在以一种可能进一步有害的方式偏离容器化原则。
1. 在容器内应用更新可以说,最常见的 Docker 反模式是尝试使用从传统虚拟机继承而来的技术来更新容器。容器文件系统是短暂的,因此当容器停止时所有更改都会丢失。它们的状态应该可以从Dockerfile用于构建图像的状态中重现。
这意味着您不应该apt upgrade在容器内运行。然后,它们将与构建它们的图像不同。容器旨在自由互换;将数据与代码和依赖项分开,让您可以随时替换容器实例。
应该通过定期重建镜像、停止现有容器并根据修改后的镜像启动新容器来应用补丁。社区工具链项目可用于简化此过程并让您了解可用的上游更新。
2. 在一个容器内运行多个服务容器应该是独立的并专注于一项特定的功能。尽管您以前可能在一台物理机器上运行您的 Web 和数据库服务器,但完全解耦的方法会将两个组件分离到单独的容器中。
这种方法可以防止单个容器镜像变得太大。您可以使用内置的 Docker 命令检查每个服务的日志,并相互独立地更新它们。
多个容器为您提供增强的可扩展性,因为您可以轻松增加堆栈各个部分的副本数量。数据库运行缓慢?使用您的容器编排器添加更多 MySQL 容器实例,而无需为已经运行良好的组件分配任何额外资源。
3. 图像构建有副作用Docker 镜像构建应该是幂等操作,总是产生相同的结果。运行docker build不应该对您更广泛的环境产生丝毫影响,因为它的唯一目标是生成容器映像。
尽管如此,许多团队创建了操作外部资源的 Dockerfile。Dockerfile 可以演变成一种包罗万象的 CI 脚本形式,用于发布版本、创建 Git 提交并写入外部 API 或数据库。
这些操作不属于 Dockerfile。创建 Docker 镜像是一个独立的操作,应该是它自己的 CI 管道阶段。发布准备然后作为一个单独的阶段进行,因此您可以始终docker build不意外地发布新标签。
4. 使你的 Dockerfile 过于复杂同样,Dockerfiles 也有可能做的太多。将 Dockerfile 限制为所需的最少指令集可以最大限度地减少图像大小并增强可读性和可维护性。
使用多阶段 Docker 构建时经常会出现问题。此功能可以轻松开发引用多个基础映像的复杂构建序列。许多独立的阶段可能表明您将关注点和耦合过程过于紧密地混合在一起。
在 Dockerfile 中查找用于特定目的的逻辑部分。尝试将它们分解为单独的 Dockerfile,创建可以独立运行的自包含实用程序映像,以完成更广泛管道的一部分。
您可以使用编译源代码所需的依赖项创建一个“构建器”映像。将此图像用作 CI 管道中的一个阶段,然后将其输出作为工件提供给下一阶段。您现在可以将编译后的二进制文件复制到您在生产中使用的最终 Docker 映像中。
5. 硬编码配置包含凭据、机密或硬编码配置密钥的容器映像可能会导致严重的问题和安全风险。将设置烘焙到您的映像中会损害 Docker 的基本吸引力,即将同一事物部署到多个环境中的能力。
使用环境变量和声明的Docker 机密在启动容器时注入配置。这将图像作为可重复使用的资产进行维护,并将敏感数据访问限制为仅运行时。
此规则仍然适用于仅供内部使用的图像。硬编码秘密意味着它们也致力于您的版本控制软件,这可能使它们在服务器遭到破坏时容易被盗。
您应该只为应用程序中的每个更改构建一个容器映像。为单个环境维护多个相似的映像表明您没有从 Docker 的“随处运行”的心态中受益。
最好在您的环境中推广单个映像,从登台到生产。这让您有信心在每个部署中运行完全相同的逻辑环境,因此在暂存中起作用的内容仍将在生产中运行。
拥有一个专门的“生产”图像表明您可能正在遭受上述其他一些反模式的困扰。您可能有一个可以分解的复杂构建序列,或者特定于生产的凭据硬编码到您的图像中。镜像应该按开发生命周期阶段分开,而不是按部署环境分开。通过使用变量注入配置来处理环境之间的差异。
7. 在容器内存储数据容器文件系统的短暂特性意味着您不应该在其中写入数据。应用程序用户创建的持久性数据(例如上传和数据库)应存储在Docker 卷中,否则会在容器重新启动时丢失。
其他类型的有用数据应尽可能避免写入文件系统。将日志流式传输到容器的输出流,在那里可以通过docker logs命令使用它们,而不是将它们转储到容器故障后会丢失的目录中。
在修改现有文件时,容器文件系统写入也会导致显着的性能损失。Docker 使用“写时复制”分层策略意味着存在于较低文件系统层中的文件是从该层读取的,而不是从您的镜像的最后一层读取。如果对文件进行了更改,Docker 必须先将其复制到最上层,然后再应用更改。对于较大的文件,此过程可能需要几秒钟。
注意这些反模式将使您的 Docker 镜像更可重用且更易于维护。它们是仅使用 Docker 容器和采用容器化工作流之间的区别。您可以轻松地编写一个正常运行的 Dockerfile,但一个计划不周的 Dockerfile 会限制您利用所有潜在好处的能力。
容器应该是通过可重现的构建过程创建的短暂的、独立的功能单元。它们映射到您的开发过程中的阶段,而不是部署环境,但本身并不直接促进该过程。图像应该是 CI 管道生成的工件,而不是定义该管道的机制。
采用容器需要转变思维方式。最好从基础开始,了解总体目标,然后查看如何将它们纳入您的流程。从长远来看,在没有适当考虑这些方面的情况下使用容器最终会让人头疼,这与该方法的支持者吹捧的灵活性和可靠性的提高相去甚远。



