很多新手一上来就是 FROM centos 然后 RUN 一堆 yum install 的,这样还停留在虚拟机的角度。可以FROM alpine 或者干脆拿官方的改,alpine初期的时候问题蛮多的,很多人建议使用alpine做基础镜像最好是测试好再上线,现在 alpine 的快速发展,这种现象很少了。
``
id 的话不便于长期发展,而 latest 标签无法回滚
现在 dockerhub 上有很多的镜像了,很多人还是喜欢造轮子,造出来的镜像层又多,无用的文件又停留在层理,主进程还不是业务进程,还不支持传入环境变量来让用户选择场景和传入配置信息启动。
如果你的是一个java应用,那么你应该使用 java 作为基础应用,如果你是 tomcat 应用,你应该使用tomcat 作为基础应用,而不是按照虚拟机的思维,把 Java 装好,然后装应用;tomcat 也一样,装 java,装 tomcat,装应用。。。
之前我举例的
ADD https://xxxxx.tar.gz
RUN 安装编译安装需要的依赖 \
&& 编译安装 \
&& 然后remove无关的仅保留最小的运行环境 \
&& 删掉上面下载的源码包
虽说最终起来的容器看不到源码包。实际上文件还停留在镜像的层里,所以尽量合并和减少层防止层保持住文件,也就是像官方那样一层RUN解决
RUN curl或者wget下载源码包\
&& 安装编译安装需要的依赖 \
&& 编译安装 \
&& 然后remove无关的仅保留最小的运行环境 \
&& 删掉下载的源码包
最后一些零散的建议和常见错误
- 编写 entrypoint 脚本让启动更人性化
- 同时如果是初期上 docker 到生产,考虑到排错啥的,可以在官方 dockerfile 里添加一些常见的排错命令
- 如果是网络出现故障,可以基于大的 rootfs 安装一些网络排查的相关工具然后
docker run --rm -ti --net container=目标容器 工具镜像名 bash
利用--net container=xxx
和目标容器在同一个 network namespaces 里排查,同理 k8s 拉起的容器我们也可以在对应节点上这样去排查网络 - 尽量使用 ENV 和 ARG 让人不改或者少改 Dockerfile 即可做构建对应版本的镜像
- 容器时间不对的话可以构建镜像的时候安装包 tzdate,然后声明变量TZ即可声明容器运行的时区,或者构建的时候复制宿主机的
/etc/localtime
或者运行的时候挂载宿主机的/etc/localtime
,另外要注意时间和时区是两回事,例如 prometheus 的 web 页面上是 js 代码强制使用 UTC 时区,时间是对的,只能修改源码里的 js 部分,而不是时间不对。 - 如果是编译型语言,妥善利用多阶段构建(后面容器无法运行排错的时候会讲解多阶构建)
- 代码里应该要注意优雅退出。收到信号的时候释放东西啥的。
最后最有争议的话题
其实现在的 java 和 php,还有 go 啥的依赖的运行环境基本不会变,变更发布新版本也就只有代码,war,jar 和 go 编译的二进制,为此可以两种做法:
- (php或者前端)代码,war 包或者二进制全部打包到镜像里
- 不变的层做个镜像,启动利用 entrypoint 脚本接受传入的 git 分支或者 war 包啥的内网下载直链下载到容器里或者启动直接挂载 nfs 里的 war 包或者代码啥的启动
很多人都是传统的第一种思维,看到第二种的时候直接张口说这样不行。如果后续接触到了k8s会发现k8s有个initContainers
,谷歌也说了可以利用initContainers
去初始化或者克隆git代码。
其实两种均可,例如第一种,在没有 gc 原生 docker 下,每一次发布都会老版本镜像存在,虽说层共享,但是最后的代码层的容量还是占据了宿主机容量的,例如 10 个版本,每个版本的 war 包为 400M,10个版本就是 4000M。
第二种每次启动都需要下载,需要网速,如果是内网可以尝试,代码或者war包啥的都是在容器层,容器删除就会消失,每次只有一个版本存在,只占据一个版本war包容量不会吃宿主机多大容量。实在接受不了可以运维给研发做个这种通用镜像给他们在开发阶段的时候使用,功能稳定了后再固化到镜像里去
最后是推荐一个漠然大佬的示例,漠然大佬的 github 上很多镜像下载量很多,可以去他 github 看,这里我放下他的 java 的应用示例 https://github.com/mritd/dockerfile