要做出高品质的docker镜像,一个靠谱的dockerfile必不可少!

前言

上文简单地梳理了下docker的知识体系,并通过dockerfile了解了docker镜像是如何从0到1的,而本文会在上文的基础上来说明下docker镜像是如何从1到2的。毕竟做出镜像是第一步,而做出高质量的镜像才是我们最终的目的。

本文从高质量docker镜像的目标,以及为了达到这些目标,dockerfile都需要做哪些优化两个方面作为切入点,来见证一个高质量的docker镜像是如何诞生的。

正文

高质量docker镜像需要完成的目标

首先,衡量docker镜像质量的参数主要性能,大小以及安全性三个角度来

  • 更快的构建速度

  • 更小的Docker镜像大小

  • 更少的Docker镜像层

  • 充分利用镜像缓存

  • 增加Dockerfile可读性

  • 让Docker容器使用起来更简单

为了达到这些目标dockerfile需要做哪些优化

能扔的文件统统扔掉

docker镜像大小是影响docker镜像质量最直观的一个因素。docker镜像越小,占用的资源以及传输速率显然是更好的。

针对这种情况,那么不必要的文件被干掉显然是合理的选择。为此docker提供了.dockerignore文件来管理生成镜像时需要忽略的文件。

这不是什么新鲜的事物,事实上诸如git等软件同样含有.gitignore,用来控制不需要被git管理的文件。

这样可以有效加快镜像构建时间,同时减少docker镜像的大小。

功能和角色单一对于docker来说不是件坏事

虽然技术上实现docker单镜像多功能没有任何的困难,但是实际上几乎没有人这么做。原因如下:

  • 非常长的构建时间(不管需不需要,所有的组件都必须重新构建)

  • 非常大的镜像大小(功能和角色越多,依赖的组件以及资源同样也会越多)

  • 日志难以管理(多个功能的日志不能直接使用stdout,否则日志混合到一起无法查看)

  • 横向扩展时资源浪费严重(不管需不需要,所有的组件都必须横向扩展)

所以,单个docker镜像功能和角色尽量单一,而上层镜像的编排则可以留给专业人士docker compose来处理。

合理的RUN指令合并

  • 首先先科普个知识点,docker镜像是分层的。而dockerfile中的每个RUN指令都会创建一个新的镜像层。

  • 镜像层将被缓存和复用。当dockerfile的RUN指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效。

  • 某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效。上文曾经说过docker镜像类似于洋葱。它们都有很多层。而且为了修改内层,则需要将外面的层都破坏掉,这么理解就会形象的多。而将RUN指令合并的话,有助于减少docker的层级,提升缓存的利用率。

  • 但是并不是所有的RUN指令都需要合并在一起,我们只能将变化频率一样的指令合并在一起。因为如果频率不一样,会造成很多无用的变更和构建,得不偿失。

基础镜像的合理使用

任何一个docker镜像都会依赖一个基础镜像,即dockerfile的第一个FROM命令,这是一个docker镜像的基础。这个基础的选择很大程度上会决定一个docker镜像的起点,所以选择一个好的基础镜像还是十分重要的。下面两点可以参考:

  • 官方的docker镜像很久以来都是使用Ubuntu作为最基础镜像的,这源于Ubuntu在docker镜像上述三个特性上的优异表现。但是这种传统在Alpine越来越普及以及Alpine Linux的创始人Natanael Copa加入Docker后可能会发生改变。有一种说法是他正在将Docker的官方镜像包从Ubuntu切换到Alpine。不管留言是否能成真,拥有更小体积的Alpine版本的镜像可能是你在可以选择的情况下更好的那一个选择。

  • 关于基础镜像的版本,如果不写版本号,那么默认会选择latest的版本的基础镜像。如果考虑到安全性以及有必要这么做的话,那无可厚非;但是如果依赖一个稳定版本的基础镜像即可或者网络环境足够安全的话,使用固定版本的镜像也不失为一个好的选择,毕竟不需要在基础镜像版本更新后重新下载以及构建docker镜像,这在很多场景下是不需要的。

每个RUN指令后删除多余文件

假设我们更新了apt-get源,下载、解压并安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。

但是,运行应用时Docker镜像中并不需要这些文件。我们最好将它们删除,毫无疑问它们会使Docker镜像变大。

设置WORKDIR和CMD

WORKDIR指令可以设置默认目录,也就是运行RUN/CMD/ENTRYPOINT指令的地方。设置WORKDIR后可以节省很多切换目录的操作。

CMD指令可以设置容器创建时执行的默认命令。如果命令较多但是不复杂的话,你应该将命令写在一个数组中,数组中每个元素为命令的每个单词;此外,如果命令较多且逻辑复杂的话,可以将所有命令和逻辑封装在一个脚本中,在CMD中配置执行该脚本即可。

COPY与ADD如何选择

COPY指令非常简单,仅用于将文件拷贝到镜像中;ADD相对来讲复杂一些,可以用于下载远程文件以及解压压缩包等复杂操作。

如非必要,建议在能使用COPY的场景优先使用COPY,毕竟下载文件以及解压缩等操作还是会消耗性能且浪费时间。

合理调整各种命令的顺序

我们应该把变化最少的部分放在dockerfile的前面,这样可以充分利用镜像缓存。

比如拷贝静态文件以及安装基础组件的命令应该在dockerfile的最前面,源代码等改动频率较大的文件以及相关操作放在后面,这样静态文件的拷贝以及基础组件的安装就不会受到源代码修改的影响。

设置默认的环境变量,映射端口和数据卷

运行Docker容器时很可能需要一些环境变量,在dockerfile设置默认的环境变量是一种很好的方式。这种方式能动态传递和设置某些属性,能显著的提高镜像的适用性和通用性。

另外,我们还应该在ockerfile中设置映射端口和数据卷,即使用VOLUME以及EXPOSE关键字。

添加HEALTHCHECK

运行容器时,可以指定--restart always选项。这样的话,容器崩溃时,docker守护进程(docker daemon)会重启容器。对于需要长时间运行的容器,这个选项非常有用。

但是,如果容器的确在运行,但是服务已经不可用(陷入死循环,配置错误)怎么办?使用HEALTHCHECK指令可以让docker周期性的检查容器的健康状况。我们只需要指定一个命令,如果一切正常的话返回0,否则返回1。docker守护进程会根据HEALTHCHECK指令的返回结果来决定如何进行failover操作。

文末贴一个从网上拿过来的一个写的比较好的dockerfile供大家参考:

FROM node:7-alpine 

ENV PROJECT_DIR=/app 
WORKDIR $PROJECT_DIR

COPY package.json $PROJECT_DIR 
RUN npm install 
COPY . $PROJECT_DIR

ENV MEDIA_DIR=/media \ 
  NODE_ENV=production \
  APP_PORT=3000

VOLUME $MEDIA_DIR 
EXPOSE $APP_PORT 
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1

CMD ["start"] 

文章到这里就结束了,如果想一起入门学习K8S的小伙伴,欢迎点赞转发加关注,下次学习不迷路!

挂个公众号二维码,公众号的文章是最新的,CSDN的会有些滞后,想追更的朋友欢迎大家关注公众号,谢谢大家支持。 

公众号地址:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值