上文「Docker 初探」介绍了 Docker 的基本概念与使用方法,本文接着介绍使用 Docker 的几个最佳实践。
当启动一个容器时,小的容器镜像可以更快的通过网络进行拉取,并且可以更快的加载到内存中。
下面,列出一些为镜像瘦身的经验:
选择适当的基础镜像
要使用应用程序所需的最小最适当的基础镜像。
例如:如果应用程序需要 JDK,那么可以考虑使用官方的openjdk
基础镜像;而不是使用ubuntu
基础镜像,然后在此基础上去安装openjdk
。
使用多阶段构建
例如:对于 Maven 管理的 Java 应用程序,可在Dockerfile
中编写两个阶段。第一个阶段使用maven
镜像,将 Java 源码编译为jar
包或war
包;第二个阶段使用tomcat
镜像,将第一个阶段生成的jar
包或war
包拷贝到对应位置。这样的话,最终的镜像只有tomcat
这个阶段的部分,不会包括编译阶段的环境或拉取的依赖包,只有运行必须的包和环境。
针对该场景,具体Dockerfile
的编写请参考上文「镜像构建最佳实践:利用多阶段构建减小镜像体积 - Docker 初探」。
尝试减少镜像的层数
尝试合并Dockerfile
中单独的RUN
命令数量来减少镜像的层数。
如下的两个Dockerfile
代码片段,第一个在镜像中创建了两层,而第二个仅创建了一层。
RUN apt-get -y update
RUN apt-get install -y python
RUN apt-get -y update && apt-get install -y python
尝试制作自己的公共基础镜像
我们自己的项目中,可能有多个镜像有大量相同的部分。可以尝试抽取共同的部分来制作我们自己的公共基础镜像,然后基于该基础镜像来编写各个镜像自定义的部分。这样的话,Docker 只需要加载一次公共层,然后它们就会被缓存下来,而由公共镜像派生的镜像也会加载的更快。
避免将数据存储在容器的可写层中
避免使用存储驱动程序将应用程序数据存储在容器的可写层中,这样会增加容器的大小,并且从 I/O 的角度看,效率也低于卷(Volume)或绑定挂载(Bind Mounts)。
避免在生产环境使用绑定挂载(Bind Mounts)
绑定挂载(Bind Mounts)非常适合在开发期间使用,其可以将源代码目录或二进制文件挂载到容器,给我们带来了诸多方便。但对于生产环境,不要使用绑定挂载(Bind Mounts),而代之以卷(Volume)。
建议将敏感数据与非敏感数据分开存储
对于生产环境,建议将敏感数据与非敏感数据分开存储。如:若使用的是 Kubernetes 平台,可使用Secret
来存储敏感数据,使用ConfigMap
来存储诸如配置文件等非敏感数据;若使用的是 Swarm 平台,使用Secret
来存储敏感数据,使用Config
来存储非敏感数据。
修改应用程序代码并在源码控制系统创建拉取请求(Pull Request)后,建议使用 DevOps CI/CD 流水线自动去构建镜像、测试镜像,并对镜像打标签。
更进一步,为了保障镜像的稳定性与安全性,部署到生产环境之前,需要开发、测试和安全团队对镜像进行签名。
挂载方式的差异化使用
本地开发环境使用绑定挂载(Bind Mounts)可能更方便(诸如访问源码文件夹);而生产环境应使用卷(Volume)来代替绑定挂载。
Docker 的差异化安装
本地开发环境安装 Docker 桌面会更方便一些;而生产环境应安装 Docker 引擎并结合用户空间映射,以便将 Docker 进程与主机进程更好的隔离。
时间漂移的差异化处理
本地开发环境不用担心时间漂移的问题;而生产环境应在 Docker 主机和每个容器进程中安装 NTP(Network Time Protocol,网络时间协议)客户端,并将它们的状态同步至同一个 NTP 服务器。使用容器编排平台(Kubernetes 或 Swarm)的话,还需要确保 Node 的时钟与容器的时钟同步到相同的时间源。
综上,本文总结了使用 Docker 的几个最佳实践,包括:镜像瘦身最佳实践、应用程序数据持久化最佳实践、镜像稳定性与安全性保障最佳实践,以及开发环境与生产环境差异化处理最佳实践。希望我们在日常工作中使用 Docker 时,能遵循这些最佳实践。
参考资料
[1] Docker development best practices | Docker Documentation - docs.docker.com