Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

LambdaでHeadless Chrome(puppeteer)をlayerに分離した上で動かす

More than 1 year has passed since last update.

tl;dr

概要

この前の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

欲しいのはバイナリだけなのでLayerではバイナリのあるディレクトリだけを指定。
そしてデプロイの設定のzipには含まれないようにglobalの設定では除外。

そして

yarn @serverless-chrome/lambda
sls deploy

Layer側はこれで終わり。めっちゃ簡単やんけ。

無駄に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

簡単ですね。dockerの環境分離は素晴らしい。

使う側も少し変更する必要があるけどごく些細な変更ですみます。

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
};

三行程度の追加で使えるようになる&デプロイ時のzipの容量が少なくなる!

いいことづくめ!!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away