headless chromeを使ってサーバーレスでいい感じにクローリングするプログラムを作ろうとしたら沼に浸かりました。
うまいこと動くテンプレートをgithubで公開しています。
https://github.com/inatomitk/lambda-puppeteer-template
環境
- puppeteer ※headless chromeのNode.jsのAPI
- AWS Lambda
- Node.js
沼りポイント
headless chromeのバイナリがLambdaにアップロードできない
Lambdaには一つのfunctionあたり50MBまでのファイルしかアップロードできないという制限があります。
普通の処理ならその制限を気にする必要もない(まず、そんな大きいファイルにならない)です。が、headless chromeはサイズが大きい(50MB弱くらい)なので、その他諸々のパッケージを含めるとアップロードできない可能性が高いです。
なので、Lambdaのlayerを使用しました。layerを使用することで関連するパッケージを関数とは別でアップロード・使用することが可能です。
serverlessというLambdaにアップロードするための便利なツールがlayerにも対応しています。
ちなみに、serverlessでアップロードする際に、素直にserverless deploy
をすると毎回layerもアップロードされるのであまりよろしくないです。
$ serverless deploy function -f [関数名]
と実行すると、該当の関数だけアップロードされます。
puppeteerが動かない
様々な記事を参考にさせていただいたのですが、バージョンの相性のせいなのか、うまくheadless chromeが起動せずうまく動きませんでした。
https://github.com/RafalWilinski/serverless-puppeteer-layers
試行錯誤を重ね、遂に、↑を参考に構築したところ、無事クローラーが動きました。
だが、しかし、、、
ゾンビプロセスが溜まりクラッシュする
数回の実行ではまったく問題なかったのですが、短い時間に連続で何度も実行するとクラッシュしてしまう。。
メモリを観察すると、実行毎にメモリが圧迫されていたことがわかりました。そして限度を超えるとクラッシュする。
そこでプロセスを出力してみると、ゾンビプロセス(メモリ0のプロセス)が実行の度に溜まっていることがわかりました。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
486 989 0.0 0.0 0 0 ? Z 07:56 0:00 [headless_shell] <defunct>
486 937 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 888 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 840 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 793 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 748 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 694 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 645 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 62 0.0 0.0 0 0 ? Z 07:45 0:00 [headless_shell] <defunct>
486 596 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 548 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 500 0.0 0.0 0 0 ? Z 07:48 0:00 [headless_shell] <defunct>
486 451 0.0 0.0 0 0 ? Z 07:48 0:00 [headless_shell] <defunct>
※「あれ?Lambdaってサーバレスだから毎回プロセスを破棄するんじゃないの?」と思ってましたが、そんなことはないようです。
https://qiita.com/koshigoe/items/afa3368352020660a220
ここからスーパー試行錯誤しました。
一定時間待ったり、クローラ実行用のchild processを作ってchild process毎killしたり、あらゆる手でプロセスの終了を試みました。
が、解決しなかった。
※該当のissueも存在します。執筆時点でまだopenになっている。
https://github.com/GoogleChrome/puppeteer/issues/1825
バージョンのせいなのか、人によってkillするだけで上手くいったと報告してる人もいるのが難しいところでした。
私の場合は、puppeteer.launch
時点で孤立したゾンビプロセスが生成され、消せなかった。。
ライブラリ依存の問題であろうことはわかってたので、Lambdaを辞めるという選択肢も含めて諦めかけていました。
もういっそこの際レポジトリにコントリビュートしてやろうかなと思っていたところ、意外な形で解決しました。
headless chromeの新バージョンが出た
https://github.com/alixaxel/chrome-aws-lambda
最新のheadless chromeに追従してくれるこのライブラリをベースにlaunch部分を書き直したら動きました。
今回のトラブルを活かして↑のレポジトリをlayerにしてserverlessで使えるようにテンプレート化したレポジトリを公開しています。
https://github.com/inatomitk/lambda-puppeteer-template
結論
バージョンアップは偉大