一些关于Dockerfile的经验之谈
前言
最近都在给CI流程做改造, 又写了很多Dockerfile, 看看能不能给自己整理出一些零零碎碎的经验来.
容器安全
配置信息
在docker build
的时候, 一般都会需要带上一些认证信息, 最常见的就是要到私有的git仓库里面下载私有的依赖.
笔者都是在dockerfile中使用ARG
命令去传参的. 而在执行docker build
的时候, 则是利用jenkins管理的credentials去完成, 这样就可以让验证信息完全处于密文中, 保障了数据安全(起码不是明文).
而当构建出来的镜像要运行, 配置文件都通过configmap或者是volume去调用, 配置信息也就不需要放在镜像中了.
设置user
在默认情况下, Docker容器都会以root启动, 而容器里面的root跟宿主机里面的root是一样的, UID=0. 这样的隐患是, 当黑客进入到一个使用root去运行的容器时, 就等于获得了宿主机的root权限了. 而为了堵住这个隐患, 我们只需要在Dockerfile里面用USER
命令指定一个非root用户就行.
如果遇到了一些坑爹场景, 必须要用root这个用户名去运行, 也可以使用Username re-map去避开.
设置时区
主要是一些业务上的考虑, 降低歧义或者是其他异常的可能.
警惕COPY * 陷阱
可以直接用一个小实验说明, 笔者简单创建了几个文件和文件夹:
1 | [root@VM_110_57_centos dockerfile-test]# ls |
Dockerfile里面内容如下, 在里面, 使用了COPY *
的操作:
1 | from golang:1.20 |
如果你是个新手, 你也许会以为, 这个COPY *
的操作, 会把当前文件夹里面的所有内容, 包括子文件夹都会复制到镜像里面, 我们验证一下.
1 | # docker build -t rondochen/dockerfile-test:1.0 . |
从上面的输出, 我们就可以看到, COPY *
的操作结果, 是会递归出当前文件夹里面的所有文件, 复制到镜像的WORKDIR
中, 而且目录结构还会改变.
如果这是你本来的需求, 那你可以接着用. 但在笔者的工作场景中, 大多数时候, 当需要复制整个目录到镜像中, 在Dockerfile上是这样操作:COPY . ./
.
指定版本号
在使用FROM指定基础镜像的时候, 不要用latest. 同样的道理, 如果需要在Dockerfile中使用apt
或者yum
时, 也最好指定要安装的软件的版本号.
大原则就是, 防止自动下载了一个让你出错的东西.
优先使用COPY而不是ADD
COPY
和ADD
都可以在构建过程中向容器添加文件, 在大多数情况下, 笔者会建议优先使用COPY
. 因为ADD
除了可以复制文件, 还能解压或者是下载URL等. 为了避免潜在的不确定性, 笔者会尽量不用ADD, 除非我很清楚我就是要通过URL下载一个文件或者有其他COPY命令不能完成的操作, 那才会使用ADD.
充分利用缓存
在docker build
的过程中, 从首行的FROM
开始, docker程序会逐行遍历你的Dockerfile, 每一行的操作都会创建新的层. 但如果遇到一些重复操作, 距离上次的docker build
没有任何变化, 那docker就会使用缓存直接完成这一次的操作.
有鉴于此, 在编排Dockerfile里面的内容的时候, 我们应该尽可能地把一些不会频繁变化的操作, 放到前面. 具体到实际的应用场景, 读者可以参考本站文章设计一个golang项目的容器构建流程: layer的概念. 再延伸到其他的开发框架, 我们都会有类似的场景, 比如在nodejs项目的容器构建过程中, 在Dockerfile中可以先执行npm install
再把其他的项目文件放到容器中, 借此最大限度地复用cache, 节约时间.
但也要警惕缓存给你带来的陷阱, 比如一个单行的RUN apt-get update
.
极简主义
尽量维持Dockerfile的简洁, 同时在项目规划的时候精简容器里面的内容. 下面是一些反例:
一个镜像(容器)运行多个服务
把数据库变更操作放在Dockerfile中执行
把运行日志或者其他业务数据存放在容器内
在容器内保留大量不必要内容, 如编译环境, 或者其他不必要的工具
欢迎补充(笑)
留坑
还有一些在笔者的工作内容中没用到的, 但是面对一些特殊场景下会有用的命令, 这里先留一个坑:
HEALTHCHECK: 因为笔者所在的项目中, 已经有使用k8s自带的健康检查功能, 所以暂不使用
ONBUILD: 对比起使用
ONBUILD
命令去维护基础环境, 作为一个运维, 笔者更倾向于每个项目使用独立的multistage build dockerfile. 可能这个功能更适合用在开发阶段.RUN –mount: 对于一些复杂蹊跷的环境比较适合