概要
エンジニアもどき2年目に入りました。色々あって以下を行うLambda関数を作成することになりました。
色々学びがあったので備忘録がわり兼アウトプットのために書くことにしました。
- 特定のページのキャプチャ取得
- 取得したキャプチャをS3に保存
- S3に保存したURLをDyanmoに記録
part1では1. 特定のページのキャプチャ取得
の実装とデプロイまで行います。
part1での実装内容はこちら
前提
以下の内容が出てきます。インストールなどの準備は完了している前提です。
- nodejs
- ServerlessFramework
- yarn
- webpack
- iamのユーザ作成済み
実装作業
1. 準備
フォルダとテンプレートの作成をします。
mkdir puppeteer-capture
cd puppeteer-capture
serverless create --template aws-nodejs
また、webpackを使用する(modeule構文などesnextで実装する)ため次のパッケージも追加します。
yarn add -D webpack webpack-cli
yarn add -D @babel/core @babel/preset-env babel-loader
yarn add -D serverless-webpack
webpackの設定を書きます。
{
"compilerOptions": {
"module": "esnext",
"baseUrl": ".",
},
"exclude": ["node_modules"]
}
const path = require('path');
const slsw = require('serverless-webpack');
module.exports = {
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
entry: slsw.lib.entries,
devtool: slsw.lib.webpack.isLocal
? 'cheap-module-eval-source-map'
: 'source-map',
output: {
libraryTarget: 'commonjs',
filename: '[name].js',
path: path.join(__dirname, '.webpack'),
},
target: 'node',
module: {
rules: [
{
test: /\.js$/,
enforce: 'pre',
exclude: /node_modules/,
include: __dirname,
use: [
{
loader: 'babel-loader',
},
],
},
],
},
};
以上で事前準備は完了です。
2. キャプチャを取得する関数の実装
キャプチャを取得してローカルに保存する関数を作成します。
2-1. ライブラリの追加
今回はブラウザを操作するpuppeteer-core
とAWS上でchromium(ブラウザ)を動かすchrome-aws-lambda
を使用します。また、ローカルでの動作確認用にpuppeteerも追加しています。
# ライブラリの追加
yarn add puppeteer-core chrome-aws-lambda
yarn add -D puppeteer
事前準備で追加したwebpackの設定を除き、package.json
に以下の3つのライブラリが追加されていれば完了です。
{
"devDependencies": {
// ・・・ 省略 ・・・
"puppeteer": "^3.0.2",
// ・・・ 省略 ・・・
},
"dependencies": {
"chrome-aws-lambda": "^2.1.1",
"puppeteer-core": "^3.0.2"
}
}
2-2. 実装
handler.js
にキャプチャの取得/ローカルへの保存を行う関数を実装します。
import { args, defaultViewport, executablePath, headless, puppeteer } from 'chrome-aws-lambda';
import { writeFileSync } from 'fs';
const getCapture = async (url) => {
// ブラウザの起動
let browser = null;
try {
browser = await puppeteer.launch({
args,
defaultViewport,
executablePath: await executablePath,
headless,
});
// ページに移動
const page = await browser.newPage();
await page.goto(url);
// キャプチャの取得(フルページ、jpegを指定)
return await page.screenshot({ fullPage: true, type: 'jpeg' });
} catch (error) {
console.log(error);
return null;
} finally {
if (browser !== null) {
await browser.close();
}
}
};
export const captureFunction = async event => {
const url = event.url || 'https://google.com/';
// キャプチャ取得
const jpgBuf = await getCapture(url);
if (!jpgBuf) {
return { statusCode: 500, body: 'キャプチャの取得に失敗しました.' };
}
// ファイルに書き出し
writeFileSync('/tmp/hoge.jpg', jpgBuf);
return { statusCode: 200, body: 'キャプチャの取得に成功しました。' };
};
2-3. 動作の確認
まずはserverless.yml
を以下のように編集します。
regionやprofileの項目は各自の環境に合わせて記述してください。
service: puppeteer-capture
provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-1
profile: private
stage: dev
# 使用するプラグイン
plugins:
- serverless-webpack
# 関数の設定
functions:
captureFunction:
handler: handler.captureFunction
以下を実行して動作の確認を行います。
# ローカルでの動作確認
sls invoke local --function hello -c ./serverless.yml
一瞬ブラウザが立ち上がり/tmp
にhoge.jpg
という名前でキャプチャ画像が取得できていればOKです。
任意のページのキャプチャを取得する場合は、以下のように実行します。
# 任意のページのキャプチャを取得
sls invoke local --function captureFunction --data '{"url":"https://qiita.com/"}' -c ./serverless.yml
デプロイ
作成したLambda関数をAWS上にデプロイします。
1. 準備
chrome-aws-lambda
込みでlambdaにデプロイすると容量が結構大きくなってしまうためパッケージはLambda Layerに入れておき、そこから使うことにします。
まずは、Layersに配置するものを格納しておくディレクトリを作成し、パッケージを追加します。
ドキュメントによると[任意の名前]/nodejs/node_modules
にパッケージを追加する必要があるようです。
# ディレクトリの作成
mkdir layers/nodejs
cd layers/nodejs
# パッケージの追加
yarn init -y
yarn add chrome-aws-lambda puppeteer-core
あわせて、webpack.config.js
に外部依存の設定を追加します。
// ・・・ 省略 ・・・
target: 'node',
// 追加
externals: ['chrome-aws-lambda'],
// ・・・ 省略 ・・・
};
2. Layersの設定
Layersに格納するパッケージを格納しているパスと関数からの参照を行うための設定をserverless.yml
に記述します。
コンソールからもLayersの設定はできますが、今回は設定に関しては全てコンソールを使わずに行ないます。
Layer設定のname
項目によりlambdaだけでなくlayerもstageでの切り分けができます。
service: puppeteer-capture
provider:
# ・・・ 省略 ・・・
# 追加1 : Layer名などの環境変数
environment:
STAGE: ${self:provider.stage}
PREFIX: ${self:service}-${self:provider.stage}
CAPTURE_LAYER: ${self:provider.environment.PREFIX}-capture-layer
# ・・・ 省略 ・・・
# 関数の設定
functions:
captureFunction:
handler: handler.captureFunction
# 追加2 : Layerの参照
layers:
- { Ref: CaptureLayerLambdaLayer }
# 追加3 : Layerの設定
layers:
CaptureLayer:
path: layers
name: ${self:provider.environment.CAPTURE_LAYER}
3. デプロイと動作確認
以下のコマンドでデプロイすると画像のような結果が得られます。
# デプロイ
serverless deploy
後はデプロイした関数の動作を確認するだけです。
# デプロイした関数の実行
sls invoke --function captureFunction -c ./serverless.yml
sls invoke --function captureFunction --data '{"url":"https://qiita.com/"}' -c ./serverless.yml
以下のようにレスポンスが返ってきていれば完了です。
lambdaは実行後、使用されたすべてのリソース(に格納されているすべてのファイルを含む)が破棄されてしまうので、/tmp
ではなくS3などに保存する必要があります。そこで次回は2. 取得したキャプチャをS3に保存
するように修正します。
おわりに
特に大変だったのは次の3点でした。
- ちゃんとwebpackを使う
- パス周りの設定方法(相対パス排除)webpack, Jest, Flowのimportの相対パスを綺麗にする
- ServerlessFrameworkだけで完結
- コンソールでLayerのzip追加しなくても何とかできないか
- Layerをstageで切り分ける
AWS Serverless Application Modelも気になるのでいずれはそちらも使ってみたいなぁ。