LambdaでHeadless Chrome(puppeteer)をlayerに分離した上で動かす
LambdaでHeadless Chrome(puppeteer)をlayerに分離した上で動かす:
この前のre:InventでLambda Layerが発表されました。
共通ライブラリを一つにまとめるという使い方もありますが、Lambdaの容量制限である50Mを超える何かを詰めるのに使うのは誰もが考える話だと思います。そうですHeadless Chrome。
とうことでやってました。結果としては簡単だった。
serverlessを使います。いつの間にかlayerも作れるようになってました。
結果としては、Layerがない時代からheadless chromeを動かすような serverless-chrome というプロジェクトがLambda上で動かすバイナリを提供してくれているので、設定ファイルを書いてコマンド打つだけだった。セキュリティが気になる人は頑張ってバイナリをビルドしよう。
欲しいのはバイナリだけなのでLayerではバイナリのあるディレクトリだけを指定。
そしてデプロイの設定のzipには含まれないようにglobalの設定では除外。
そして
Layer側はこれで終わり。めっちゃ簡単やんけ。
無駄にfunction作られちゃうのかなと思ったりしたんですが、functionの設定を記載せずにlayerの設定だけ書けばちゃんとlayerだけ作られるようになってました。かしこい。
あとは使う側でLayerの設定と、Layerは
これだけで動く!
こういうヘッドレスブラウザではフォントが入っていなくてscreenshotが文字化けします。
どうせなので合わせて対応しました。
対応としては、下記のようにフォントファイル+フォントキャッシュ+設定ファイルをすれば化けなくなります。実際に必要なファイル等も下記を参照。
https://qiita.com/komeda-shinji/items/e049edd1389579059c53
上記でも直接lambdaで動かしていますが、lambdaのdockerイメージもあることなのでdockerでやってみました。
フォントファイルを置いて、下記のhandlerを追加して
あとはコマンド実行!!
ここでcacheが生成されなくてめっちゃ悩んでたんですが、GoogleからダウンロードしたNotoSansの
コマンド一発でできるようになって良いですね。
あとはデプロイ。
簡単ですね。dockerの環境分離は素晴らしい。
使う側も少し変更する必要があるけどごく些細な変更ですみます。
三行程度の追加で使えるようになる&デプロイ時のzipの容量が少なくなる!
いいことづくめ!!
tl;dr
- Githubにまとめた https://github.com/celeron1ghz/lambda-layer-puppeteer
- fontにread permissionに注意
概要
この前のre:InventでLambda Layerが発表されました。共通ライブラリを一つにまとめるという使い方もありますが、Lambdaの容量制限である50Mを超える何かを詰めるのに使うのは誰もが考える話だと思います。そうですHeadless Chrome。
とうことでやってました。結果としては簡単だった。
Headless ChromeのLayerを作る
serverlessを使います。いつの間にかlayerも作れるようになってました。結果としては、Layerがない時代からheadless chromeを動かすような serverless-chrome というプロジェクトがLambda上で動かすバイナリを提供してくれているので、設定ファイルを書いてコマンド打つだけだった。セキュリティが気になる人は頑張ってバイナリをビルドしよう。
serverless.yml
service: puppeteer provider: name: aws runtime: nodejs8.10 stage: dev region: ap-northeast-1 package: exclude: - node_modules/@serverless-chrome/lambda/dist/** layers: puppeteer: path: node_modules/@serverless-chrome/lambda/dist name: puppeteer description: chrome-headless binary
そしてデプロイの設定のzipには含まれないようにglobalの設定では除外。
そして
yarn @serverless-chrome/lambda sls deploy
無駄にfunction作られちゃうのかなと思ったりしたんですが、functionの設定を記載せずにlayerの設定だけ書けばちゃんとlayerだけ作られるようになってました。かしこい。
あとは使う側でLayerの設定と、Layerは
/opt/
にマウントされるという点を考慮すればおk。functions: main: handler: handler.main timeout: 60 memoriSize: 3008 layers: - Fn::Join: [ ":", [ "arn:aws:lambda", { Ref: AWS::Region }, { Ref: AWS::AccountId }, "layer:puppeteer:3" ] ]
module.exports.main = async (event, context) => { try { const browser = await puppeteer.launch({ headless: true, executablePath: '/opt/headless-chromium', args: ['--no-sandbox', '--disable-gpu', '--single-process'], }); const page = await browser.newPage(); await page.goto("https://google.co.jp", { waitUntil: ['domcontentloaded', 'networkidle0'] }); const ret = await page.screenshot(); } catch(e) { console.log("Error happen:", e); } return { message: 'OK' }; };
FontファイルのLayerも作る
こういうヘッドレスブラウザではフォントが入っていなくてscreenshotが文字化けします。どうせなので合わせて対応しました。
対応としては、下記のようにフォントファイル+フォントキャッシュ+設定ファイルをすれば化けなくなります。実際に必要なファイル等も下記を参照。
https://qiita.com/komeda-shinji/items/e049edd1389579059c53
上記でも直接lambdaで動かしていますが、lambdaのdockerイメージもあることなのでdockerでやってみました。
フォントファイルを置いて、下記のhandlerを追加して
const exec = require('child_process').execSync; module.exports.fontcache = async (event, context) => { process.env.HOME = process.env.LAMBDA_TASK_ROOT; const fontdir = `${process.env.HOME}/.fonts`; const tempdir = "/tmp/cache/fontconfig/"; console.log(exec(`fc-cache -v ${fontdir}`).toString()); //console.log(exec(`ls ${process.env.HOME}`).toString()); //console.log(exec(`ls -la ${tempdir}`).toString()); console.log(exec(`cp ${tempdir}\* /tmp/result`).toString()); return { message: 'OK' }; };
ここでcacheが生成されなくてめっちゃ悩んでたんですが、GoogleからダウンロードしたNotoSansの
.ttc
のパーミッションが0640
だったので見えてないというだけだったやつでした。皆さんもお気を付けください。mkdir -p .fontconfig chmod 0777 .fontconfig docker run \ -v "$PWD:/var/task" \ -v "$PWD/.fontconfig:/tmp/result:rw" \ -it lambci/lambda:nodejs8.10 \ handler.fontcache chmod 0755 .fontconfig
あとはデプロイ。
sls deploy
使う側も少し変更する必要があるけどごく些細な変更ですみます。
functions: main: handler: handler.main timeout: 60 memoriSize: 3008 layers: - Fn::Join: [ ":", [ "arn:aws:lambda", { Ref: AWS::Region }, { Ref: AWS::AccountId }, "layer:puppeteer:3" ] ] - Fn::Join: [ ":", [ "arn:aws:lambda", { Ref: AWS::Region }, { Ref: AWS::AccountId }, "layer:japanese_font:1" ] ]
module.exports.main = async (event, context) => { process.env.HOME = "/opt/"; //ADD THIS LINE };
いいことづくめ!!
コメント
コメントを投稿