PuppeteerでHeadless Chromeを動かすコードを Lambda上で動かすStarter Kitを作った。
Chromeの準備
Puppeteerのインストール時に落としてくるChromeをLambda上で動かそうとしても Lambdaにないshared libraryに依存しているため失敗する。
error while loading shared libraries: libpangocairo-1.0.so.0: cannot open shared object file: No such file or directory
Lambda上でHeadless Chromeを動かす例がないか調べたらserverless-chromeというのがあって、 Headless用の設定でChromeをビルドしていた。 ほかにはchromelessというのもあるが これはserverless-chromeに 依存している。 最小構成でPuppeteerを使いたかったので、今回はこれらを使わず一から作ることにした。
serverless-chromeにもビルドしたものが置いてあるが、少しバージョンが古いようだったので最新版でビルドした。 基本的には書いてある 通りやればうまくいく。他のプロセスとのshared memoryとして/dev/shmを使っているのを、/tmpに置き換える ようにしないと、実行時の page.goto() で Failed Provisional Load: ***, error_code: -12 になる。
ビルドしたheadless_shellには問題になった依存は含まれていないようだ。
$ ldd headless_shell
linux-vdso.so.1 => (0x00007ffcb6fed000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5f17dbe000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f5f17bba000)
librt.so.1 => /lib64/librt.so.1 (0x00007f5f179b1000)
libnss3.so => /usr/lib64/libnss3.so (0x00007f5f17692000)
libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007f5f17466000)
libsmime3.so => /usr/lib64/libsmime3.so (0x00007f5f1723e000)
libnspr4.so => /lib64/libnspr4.so (0x00007f5f17001000)
libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f5f16dd8000)
libfontconfig.so.1 => not found
libfreetype.so.6 => /usr/lib64/libfreetype.so.6 (0x00007f5f16b3b000)
libm.so.6 => /lib64/libm.so.6 (0x00007f5f16839000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f5f16533000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f5f1631d000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5f15f5b000)
/lib64/ld-linux-x86-64.so.2 (0x000055ba0af5e000)
libplc4.so => /lib64/libplc4.so (0x00007f5f15d55000)
libplds4.so => /lib64/libplds4.so (0x00007f5f15b51000)
Puppetterで落としてくる普通のChromeはLambdaの制限の50MBを超えていたが、 ビルドしたものはぎりぎり超えていないのでパッケージに含められるようになった。 PuppeteerのChromeは環境変数 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD を設定することで含めないようにできる。
他のパッケージのサイズによっては50MBを超えてしまうこともあるので、 パッケージに含めずS3からダウンロードできるようにもした。
いずれの場合も最終的な置き先はLambdaで唯一書き込める/tmpになる。 この領域は512MBまで使えるので展開してもまだ余裕がある。
Error: EROFS: read-only file system, open 'node_modules/puppeteer/.local-chromium'
ChromeのLaunch時のOption
いろいろ試した結果、最低限必要だったのはこのあたり。
exports.launchOptionForLambda = [
// error when launch(); No usable sandbox! Update your kernel
'--no-sandbox',
// error when launch(); Failed to load libosmesa.so
'--disable-gpu',
// freeze when newPage()
'--single-process'
];
エラーは分かりづらいものが多く、ときにはエラーすら出ずに止まってしまうこともある。 デバッグの際はdumpioを有効にする。
const browser = await puppeteer.launch({
...
dumpio: !!util.DEBUG,
});
Babel
現在のLambdaのNodeのバージョンはv6.10.3。 node.greenによるとES2015は99%対応していて、ES2016もべき乗演算子(2 ** 3 = 8)以外は対応しているが、ES2017のasync/awaitは7.6からなので、8系に対応するまではbabelにかける必要がある。 ちなみにPuppeteerは6.4以降で動く。
$ yarn add --dev babel-cli babel-preset-env
babel-preset-env .babelrcはこんな感じ。
$ cat .babelrc
{
"presets": [
["env", {
"targets": {
"node": "6.10"
}
}]
]
}