基于orange-ci环境,制作puppeteer运行镜像,镜像放在CSIG容器平台

最近项目中需要在 orange-ci 中进行利用 puppeteer.js 进行截图保存等一系列操作。那么问题马上就来了:在 orange-ci 中 puppeteer.js 如何运行?

在 orange-ci 中运行 puppeteer.js 也是基于 Linux 环境,在 Linux 环境中安装 Chromium 最长见的 2 个问题就是:

  1. 下载 Chromium 时,下载常常以失败告终。换个镜像源后 Chromium 能下载成功,但启动后
    各种报错,是 Linux上 缺少部分依赖导致的(最新的两个版本我测试不会出现下载 Chromium 失败的情况了,旧项目使用的 5.x 版本经常失败)
  2. 安装完需要的依赖,代码顺利运行。但截图却发现浏览器上的中文字体竟全是框框框框,缺少中文字体库

上面两个问题也可以侧面说明,将项目改用 Docker 部署的话,能避免出现本地开发正常,上线后却出现各种问题的情况。

整体计划

我首先在 KM 平台上搜索了下看看有木有前辈做了相关工作并记录下来了,发现没有找到,基于 123 上制作 docker 镜像倒是有,例如:123上制作docker镜像全教程,以ffmpeg和puppeteer实战为例,我也测试了文章里面的 puppeteer 运行镜像代码去基于 orange-ci 去运行 puppeteer,但是没有成功。所以有了这篇文章,希望可以帮助到后面的童鞋。

笔者的想法是:由于在 orange-ci 流程中可以执行各种命令,所以我可以利用一些命令来运行 js 脚本来进行 puppeteer.js 和一些相关操作。命令的具体操作内容是通过自己发布一个 cli 命令的 npm 包,在包里面实现你需要的一系列包含 puppeteer.js 截图的操作。在 orange-ci 执行命令进行 puppeteer.js 脚本操作的时候,需要依赖一个支持 puppeteer.js 运行的 docker 镜像环境(通过查阅 orange-ci 文档 可以知道,可以通过“构建一个 Docker 缓存”或者“使用 docker 自定义构建环境”来实现)。

所以我们可以先制作一个能够运行 puppeteer.js 的运行镜像,然后给镜像加上 orange-ci 的支持,把镜像 上传 CSIG 容器平台,然后跑 ci 的时候,基于这个镜像环境去跑就行了。

制作 puppeteer 运行镜像

在制作 puppeteer 运行镜像的过程中,真是一波三折,遇到过各种个样的问题。

方案一

最初我想的是制作一个内部不包含 puppeteer.js 本身的镜像,然后利用 cli 中的 puppeteer.js ,cli 中只安装 puppeteer-core,通过调用 puppeteer.launch 时的 executablePath 配置引入自行下载的 Chromium,这样还能加快 npm install 时候的构建速度。

这个方案本身没有问题,虽然在安装好 puppeteer.js 并进行测试的时候遇到过各种坑,但是都解决了,最后卡在最后一步,在 orange-ci 中一直找不到 executablePath 配置的浏览器路径,并且还需要单独处理不同版本下的浏览器问题,所以就暂时搁置了该方案。

方案二

方案一先暂停告一段落,先进行方案二。当时通过查找资料,找到了一份 Dockerfile 代码(,对 Dockerfile 不熟悉的童鞋可以看我的另一篇小结:docker 常用命令解析和使用小坑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 基于`alpine`版本的`node`10
FROM node:10.15.2-alpine

# 安装 Chromium (72)。从 alpine/v3.9 版本库中下载
RUN apk update && apk upgrade && \
echo @v3.9 http://dl-cdn.alpinelinux.org/alpine/v3.9/community >> /etc/apk/repositories && \
echo @v3.9 http://dl-cdn.alpinelinux.org/alpine/v3.9/main >> /etc/apk/repositories && \
apk add --no-cache \
freetype@v3.9 \
chromium@v3.9 \
harfbuzz@v3.9 \
nss@v3.9

# 复制测试代码到镜像
COPY ./puppeteer.js .
COPY ./puppeteer.js ./tmp

# 安装 Puppeteer 时不让它自动下载 Chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
# 选择 Chromium 72 对应的 Puppeteer 版本
RUN yarn add puppeteer@1.11.0

# 添加用户
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
&& mkdir -p /home/pptruser/Downloads \
&& chown -R pptruser:pptruser /home/pptruser

# 添加 cjk 字体以支持中文
# COPY NotoSansCJK-Regular.ttc /usr/share/fonts/TTF

USER pptruser

构建命令:docker build -t node-puppeteer:puppeteer1_11-node10-alpine .

运行命令:docker run -it --rm node-puppeteer:puppeteer1_11-node10-alpine sh

我进行了一些简单修改,加入了测试代码,发现是可以直接成功运行 puppeteer.js 并且截图成功的,上面的 COPY ./puppeteer.js . 是拷贝我自己的测试代码,你可以自己写或者用原来作者提供的,具体请见:构建 puppeteer docker 镜像

我把镜像上传至 TKEx-CSIG 容器平台,并且进行测试也可以打印出我要的信息了,到现在为止一切顺利,接下来要加上支持 orange-ci 的运行环境了,这个镜像依赖的环境是 node:10.15.2-alpine,基于 alpine 版本的 node 10。那么问题来了:直接把依赖镜像修改为支持 orange-ci 的镜像,找不到合适的基础镜像咋办?没有合适的基础镜像,那就只能自己搭一个或者在当前的 Dockerfile 文件中进行安装 orange-ci 的运行环境。

但是我在当前 Dockerfile 中进行修改和安装一些需要的软件时,却一直失败(可能是我哪里写错了~~),例如我在上述代码中通过 apk安装 vi 或者 vim,因为当前容器中没有 vi 或 vim,不好在命令行编辑文件(只能只用 echo 命令),以及测试的时候修改也不方便(后续遇到这种情况我是采用 docker cp 命令进行复制操作的)。附:Alpine镜像使用

并且我发现基于当前 Dockerfile 的镜像中 puppeteer.js 的版本很低(1.11.0),chrome 的版本也比较低,然后当前项目里面使用的是最新版本,想升级但是暂时没找到合适的下载路径。所以方案二我暂时也搁置了,当然如果适合的童鞋可以直接使用。

方案三

方案三我是想着在当前使用的 orange-ci 基础镜像上自己写 dockerDockerfile 来创建环境(开始就想了几种方案,所以才会暂时先搁置前面遇到问题的方案)。但我在寻找前面方案问题和报错的解决办法的过程中,发现 puppeteer.js 官方有一个 Troubleshooting.md 文件中记录了一些可能会坑,以及 Running Puppeteer in Docker。所以我直接使用官方的 Dockerfile 文件创建了一个,经过测试是可以成功在镜像中运行 puppeteer.js(再次说明认真查看官方资料很重要!!!)。

类似于方案二,我需要加上 orange-ci 的运行环境,我直接把官方的镜像的基础镜像源替换为了我当前 orange-ci 所依赖的基础景象源,我直接把 FROM node:12-slim 替换为 csighub.tencentyun.com/orange-ci/default-env,所有内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
FROM csighub.tencentyun.com/orange-ci/default-env 

MAINTAINER chrootliu

# 拷贝测试文件,后在容器中用 node 运行即可,省的上传文件或编辑文件测试
COPY ./puppeteer.js .

# 安装最新的 Chrome Dev 包和字体以支持主要的Charsets(中文,日语,阿拉伯语,希伯来语,泰国和其他一些语言)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

# If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
# browser.launch({executablePath: 'google-chrome-stable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm i puppeteer \
# Add user so we don't need --no-sandbox.
# same layer as npm install to keep re-chowned files from using up several hundred MBs more space
&& groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
&& mkdir -p /home/pptruser/Downloads \
&& chown -R pptruser:pptruser /home/pptruser \
&& chown -R pptruser:pptruser /node_modules

# Run everything after as non-privileged user.
USER pptruser

CMD ["google-chrome-stable"]

经过本地测试和 devCloud 上的测试,该方案可以完美的支持我需要的运行环境,并且和方案二一样自带了 puppeteer.js,可以直接就进行测试,不需要额外安装,主要部分终于大功告成…

这里需要注意两个问题:

  1. 若你使用的项目里面还单独安装了 puppeteer.js 的话,那项目里面的代码会直接调用 package.json 里面的 puppeteer.js,这里要确定自己安装的 puppeteer.js 正常安装了正确的 Chromium,否则还是会报错不能成功打开浏览器。
  2. 如果在会报错 Error: Failed to launch the browser process! spawn chrome.exe ENOENT...,可能是你没有加入在去沙箱环境的 args 配置:
    1
    2
    3
    const browser = await puppeteer.launch({
    args: ["--no-sandbox", "--disable-setuid-sandbox"]
    });

将镜像上传至 TKEx-CSIG 容器平台

这个步骤有可以直接看官方文档,文档已经写的非常详细啦,上传完毕后可以直接修改 orange-ci 的 FROM 镜像源为你自己的镜像源,也可以像我一样再在DevCloud 上测试测试。

这里附我测试 puppeteer.js 的测试代码(项目内容相关逻辑已经删除):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const puppeteer = require('puppeteer');

(async () => {
const beginTime = +new Date();

const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const browserTime = +new Date();
const page = await browser.newPage();
const newTabTime = +new Date();
// await page.goto('https://www.baidu.com/');
// const openLinkTime = +new Date();
const pageContent = `这里补充html页面内容`
await page.setContent(pageContent, {
waitUntil: ['load']
})

const loadTIme = +new Date();
const domcontendLoadTime = +new Date();
await page.screenshot({ path: './test.png' });
const screenshotTime = +new Date();
await browser.close();

const endTime = +new Date();

console.log('开始时间:'+ beginTime + ',浏览器打开时间:' + (browserTime - beginTime) + ',new tab 时间:' + (newTabTime - browserTime) + ',内容load时间:' + (loadTIme - newTabTime)+ ',domcontentloaded时间:' + (domcontendLoadTime - loadTIme) + ',截图时间:'+ (screenshotTime - domcontendLoadTime) + ',结束时间:' + endTime + ',总耗时:' + (endTime - beginTime))
})();

以上就是本次基于 orange-ci 环境,制作 puppeteer 运行镜像,所经历的坎坷。其实中间还遇到过很多各种报错,以及涉及到一些不同环境下安装依赖库、安装 Chromium,中文字体支持等遇到的问题,这里就不一一展示出来了~~