制作Node项目的docker镜像
最近一个项目需要在服务端用到无头浏览器puppeteer,这个工具对于node版本有一定要求,而我们目前的线上node镜像是8.9版本的,无法支持该无头浏览器,因此需要升级node,重新制作镜像。
之前的node镜像的维护人员已经找不到了,因此这次我自己制作,把过程记录下来,供下次升级node作为参考。
阅读官方文档
官方文档永远是最好的文档,因此每个软件安装前,都务必看下官方文档,了解其对于环境和软件的依赖。
比如Node的文档。
拉取基础镜像
因为一些原因,我们只能使用CentOS6.9版本的镜像作为基础镜像。
1 | |
安装工具和依赖库
更换yum源
1 | |
安装依赖库
1 | |
| 名称 | 依赖源 |
|---|---|
| perl | OpenResty |
| pcre-devel | OpenResty |
| zlib-devel | Node |
升级Python
1 | |
注意:Python 软链接指向 Python2.7 版本后,因为yum是不兼容 Python 2.7的,所以yum不能正常工作,我们需要指定 yum 的Python版本:
编辑/usr/bin/yum文件,将头部的#!/usr/bin/python改成#!/usr/bin/python2.6.6
升级GCC
可以参考这个文章。
1 | |
安装Node
从官网下载源码,然后根据下方的安装指南操作即可。
1 | |
注意:Node的make -j4耗时非常久,我在docker内部编译花了几十分钟,请提前预留好时间。
安装OpenResty
从官网下载源码,然后根据下方的安装指南操作即可。
1 | |
进程管理
是通过s6这个进程管理工具来实现的。包括开机启动项、容器启动时需要执行的一些文件操作等。
具体的内容可以查看我的docker-node这个git项目(script下面的脚本应该是用不着的,只需查看S6即可)。
里面有2个目录需要说明下:
etc/services.d:配置开机启动的命令,比如启动nginx。
etc/cont-init.d:配置开机时的文件操作,比如将项目下的nginx配置文件移动到/usr/local/openresty/nginx/conf/vhosts目录下。
配置项规则
我们制定了一个规则(该规则源自全公司的PHP镜像规则):
项目相关的服务器配置信息,统一存放在项目下的.builddep目录下。
镜像里面会自动根据环境变量自动读取对应的项目配置信息,并copy到指定目录的功能。
设置时区
1 | |
清理无用文件
安装完成后,记得把各个安装包都删除掉,以减小镜像体积。
卸载无用的工具软件以及缓存数据
安装好上述软件后,我生成的镜像有1.16G,太大了,因此我需要将无用的软件删除掉,重新生成镜像。清理的内容包括:
yum缓存,可以看到/var/cache/yum这个目录有237M的数据,可以通过yum clean all清理掉。
python,在/usr/local/python27下有100多M,我把这个目录删除了,然后将/usr/bin/python还原为系统默认的2.6.6版本。
这样再次生成的镜像就降低到800M了,不过还是很大,后面还需要进一步精简。
将容器保存为镜像
1 | |
到了这一步,镜像制作就完成了。
接下去就是运行我们的项目程序了。
推送到dockerhub
需要先在dockerhub注册好账号,并提前创建好镜像仓库。另外docker镜像的命名必须符合规范,即docker账号ID/镜像仓库|tag的格式:
1 | |
制作项目镜像
(TODO)编写项目的Dockerfile
为了简化发布操作,我们需要额外再编写一个项目镜像的Dockerfile:
1 | |
生成镜像
为了和线上环境的Dockerfile区分开,我这个本地测试用的文件命名成了Dockerfile.local:
1 | |
启动程序
为了方便测试,我把容器的80端口也转到docker服务器的80端口了:
1 | |
注意:
1、The docker command line is order sensitive. The order of args goes:
docker ${args_to_docker} run ${args_to_run} image_ref ${cmd_in_container}
所以-p设置暴露端口这部分,别放错位置了,否则会报错。2、hosts信息的修改,是不会保存在镜像中的,因此需要每次启动的时候进行指定,即通过上面的–add-host参数指定。
至此,就可以通过docker服务器的80端口,访问docker程序的接口了。
常见报错
编译Node报错:ImportError: No module named zlib
缺少了依赖,重新安装后解决:
1 | |
编译Node报错:node undefined reference to `clock_getres’
参考Node的这个issue:
https://github.com/nodejs/node/issues/30077
通过设置环境变量解决了这个问题:
1 | |
ENTRYPOINT指定的脚本,内部的tail -f /etc/hosts不会挂起
比如我写了这样的ENTRYPOINT:
1 | |
在start.sh文件的末尾执行了tail -f /etc/hosts,预期是让start.sh脚本一直挂起,但是结果发现这个脚本瞬间就退出了,执行的结果状态码也是正常的0,即exit(0)
这个和子进程机制有关系,将其改为在ENTRYPOINT中执行即可,比如:
1 | |
一些技巧
通过set调试shell脚本
可以参考阮一峰的博客:
http://www.ruanyifeng.com/blog/2017/11/bash-set.html
用得比较多的一般是set -e(脚本只要发生错误,就终止执行。)和set -x(在运行结果之前,先输出执行的那一行命令)。