こんにちは、証券フロンティア事業部の西永です。
普段はPHPでAPI開発をしたりGoでバックエンドをやっています。
前回同記事名でLambda上からChromeを起動してスクリーンショットを撮る方法を紹介しました。
その際はGo1.xマネージドランタイムで動かす方法を紹介していましたが、時が経ちGo1.x環境が廃止されてしまいました…
Lambda の Go 1.x マネージドランタイムは非奨励になりました。Go 1.x ランタイムを使用する関数がある場合は、関数を provided.al2023 または provided.al2 に移行する必要があります。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-golang.html
今後はprovided.al2またはprovided.al2023環境でGoを動かす必要があります。
Go1.xのコンテナを用意してLambdaをコンテナで動かす方法もありますが、今回はprovided.al2023環境で動かす方法を紹介します。
本題
コード
まずはコードの方から。
今回ライブラリはchromedp(chromedp/chromedp)を使用しています。
こちらを使用するとchromedriverが不要になります。
他言語の場合はchromedriverをダウンロードして使用すれば同様のことが出来るかと思います。
package main import ( "context" "encoding/base64" "log" "os" "os/exec" "github.com/aws/aws-lambda-go/lambda" "github.com/chromedp/chromedp" "github.com/codeclysm/extract" ) func handler(_ context.Context) (string, error) { // レイヤーは「/opt/」の中にある // Lambda上では「/tmp/」でしか自由にディレクトリの操作が行えない // レイヤーを結合させてtar.gzを展開 if _, err := exec.Command("sh", "-c", "cat /opt/chromium.tar.gz.* > /tmp/chromium.tar.gz").Output(); err != nil { return "", err } file, err := os.Open("/tmp/chromium.tar.gz") if err != nil { return "", err } defer file.Close() if err := extract.Gz(context.Background(), file, "/tmp/", nil); err != nil { return "", err } os.Setenv("FONTCONFIG_PATH", "/tmp/fonts") os.Setenv("LD_LIBRARY_PATH", "/tmp/lib") // Chromium起動 opts := []chromedp.ExecAllocatorOption{ chromedp.Headless, chromedp.NoSandbox, chromedp.Flag("disable-setuid-sandbox", true), chromedp.Flag("disable-dev-shm-usage", true), chromedp.Flag("single-process", true), chromedp.Flag("no-zygote", true), // ウィンドウサイズを指定したい場合 // chromedp.WindowSize(width, height), chromedp.ExecPath("/tmp/chromium"), } ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() ctx, cancel = chromedp.NewContext(ctx, chromedp.WithErrorf(log.Printf)) defer cancel() var imageBuf []byte err = chromedp.Run(ctx, chromedp.Tasks{ // ローカルのHTMLを開く場合は「file::///○○.html」等で指定 chromedp.Navigate("https://www.tecotec.co.jp/"), chromedp.FullScreenshot(&imageBuf, 90), }) // base64で出力(S3に保存する場合はここから書き換えてください) return "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageBuf), err } func main() { lambda.Start(handler) }
コードを見れば分かるかと思いますが、今回はLambdaレイヤーで用意しているchromium.tar.gz.XXが肝になります。
では次にLambdaレイヤーを用意します。
レイヤー
1.brotliのインストール
# apt-get install brotli
等を行い、
# brotli --version brotli 1.0.7
が出来るようになれば完了です。
こちらはこの後実行するShellでbrファイルを展開するために使用します。
2.フォントのダウンロード
今回は前回と同じくNoto Sans Japaneseを使用します。
ダウンロード&展開してNotoSansJP-Regular.ttfを用意します。
3.Shellの用意
下記ShellをNotoSansJP-Regular.ttfと同じ階層に用意します。
Shell名は何でもよいです。今回はchromium.shとします。
#!/bin/bash
# 自身のshellの位置に移動
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"
# ディレクトリの作成&移動
mkdir -p tmp
cd tmp
# ダウンロードするファイルのURL
DOWNLOAD_URL="https://github.com/Sparticuz/chromium/releases/download/v131.0.1/chromium-v131.0.1-pack.tar"
# ダウンロード先のファイル名
OUTPUT_FILE="chromium-v131.0.1-pack.tar"
# ダウンロードコマンドを実行
curl -o $OUTPUT_FILE -L $DOWNLOAD_URL
# ダウンロードしたファイルを展開
tar -xvf $OUTPUT_FILE
# ダウンロードしたファイルを削除
rm $OUTPUT_FILE
# brotliファイルを展開
# 展開時点では5つファイルが生成されるが
# - al2.tar.brはAmazonLinux2023環境では不要(AmazonLinux2環境で必要)
# - fonts.tar.brは日本語に対応してないのと同階層にフォントを予め用意しているので不要
brotli -d al2023.tar.br
brotli -d chromium.br
brotli -d swiftshader.tar.br
# 展開したファイルを削除
rm *.br
# 権限追加
chmod +x chromium
# tarファイルを展開
tar -xvf al2023.tar
tar -xvf swiftshader.tar
# tarディレクトリを削除
rm *.tar
# fontsディレクトリの作成
mkdir -p fonts
# ディレクトリ移動
cd fonts
# 再度fontsディレクトリの作成
mkdir -p fonts
XML_CONTENT='<?xml version="1.0" ?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/var/task/.fonts</dir>
<dir>/var/task/fonts</dir>
<dir>/opt/fonts</dir>
<dir>/tmp/fonts</dir>
<cachedir>/tmp/fonts-cache/</cachedir>
<config></config>
</fontconfig>'
# fonts.confの作成
echo "$XML_CONTENT" > fonts.conf
# 予め用意しておいたフォントをコピー
cp -r ../../NotoSansJP-Regular.ttf fonts
# ディレクトリ位置を戻す
cd ..
# tar.gzファイルを作成
tar -zcvf ../chromium.tar.gz *
# ディレクトリ位置を戻す
cd ..
# tmpディレクトリの削除
rm -rf tmp
# 分割
split -b 45m -d chromium.tar.gz chromium.tar.gz.
# chromium.tar.gzの削除
rm -rf chromium.tar.gz
# zipファイルの作成と元ファイルの削除
for file in chromium.tar.gz.*; do
zip -r $file.zip $file
rm $file
done
今回はSparticuz/chromiumを使用しています。
こちらはLambda上でNodeJSからChroimiumを動かすライブラリですが、必要なものだけ引き抜いてGoに応用しています。*1
これでディレクトリ内にフォント(NotoSansJP-Regular.ttf)とchromium.sh2つが用意された状態になるかと思います。

4.Shellの実行
# bash chromium.sh
でchromium.shを実行します。

これらがレイヤーになります。2つに分かれているのは1つにまとめると50MBを超えるためです。 *2
2つに分けることでS3に上げる手間を省いてます。 *3

これでレイヤーの作成は完了です。
AWSへ反映
では実際にLambdaに反映していきます。
まず「Lambda > レイヤー > レイヤーの作成」より、先程作成したchromium.tar.gz.00.zipとchromium.tar.gz.01.zipをそれぞれアップロードします。
オプションは画像の通り。

そしてLambdaを作成します。

次にLambdaのアップロードを行います。
コンパイル&Zipの作成方法については公式のものがありますので説明は割愛します。

今度はレイヤーの設定を行います。
「Lambda > レイヤー > レイヤーを追加」より、上で追加したレイヤーをそれぞれ両方とも設定します。

最後に、「設定 > 一般設定」よりメモリのサイズを1024MB以上に変更&タイムアウト時間も伸ばしてください。

では実際にLambdaを実行してみます。
「テスト > テストイベント」よりサンプルそのままで実行できます。

無事出力に成功しました。
出力の最初と最後のダブルクォーテーションを省いた部分をブラウザのURLに貼り付ければスクショ結果が確認できます。

おわりに
provided.al2023環境(Amazon Linux 2023)でLambda上からブラウザを動かす方法を紹介しましたがいかがでしたでしょうか。
今回はGoで行いましたが他言語にも応用できるかと思いますので、機会があればお試しください。
Amazon Web Servicesおよびかかる資料で使用されるその他の AWS 商標 は、米国および/またはその他の諸国における、Amazon.com, Inc. またはその関連会社の商標です。
テコテックの採用活動について
テコテックでは新卒採用、中途採用共に積極的に募集をしています。 採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。 ご興味を持っていただけましたら是非ご覧ください。 tecotec.co.jp