0
0

AWS Lambda(Node.js 18.x)でwkhtmltopdfを使用するまでの流れ

Last updated at Posted at 2024-08-25

wkhtmltopdfを使用することになった背景

Puppeteerでは大容量ファイルに対応できなかった

私が開発しているLaravelのWebサービスでは書類をPDF生成するという機能があります。
今まではAWS Lambda上でPuppeteerを使用してPDF生成を行っていましたが、最近になってHTMLファイルの時点で65MBという巨大なファイルをPDF生成するケースが発生し始めてしまい、実行途中でブラウザのタイムアウトや切断が発生するようになってしまったため、緊急で他の方法に入れ替えなければならなくなりました。

他の選択肢の検討

最初に検討したのはLambdaを使用することを辞めて、barryvdh/laravel-dompdfをWorker上で動かす運用でしたが、大きいHTMLファイルの処理に非常に時間がかかるため断念しました。
wkhtmltopdfはパフォーマンスに優れているのと、Lambda関数上でPuppeteerを使用している部分を入れ替えるだけで済むので、最適な選択肢だと判断しました
※wkhtmltopdfは既に開発が終了しているので、近い将来にまた別の選択肢を考えなければなりません。詳しくは後述

実行時間の比較

10MB 65MB 実行環境 備考
Puppeteer 30秒 Puppeteerのエラーで異常終了 AWS Lambda メモリを2,048MBにしてもNG
wkhtmltopdf 10秒 60秒 AWS Lambda メモリが1,024MBだと異常終了するが、1,536MBなら安定した
dompdf 15分 30分経過して終わらないので中断 Ubuntu (WSL2) php artisan tinkerでテスト

wkhtmltopdfは開発終了している

GitHubのリポジトリはアーカイブされており、今後更新されることはない状態。
wkhtmltopdfのダウンロードページにはAmazon Linux 2版が提供されていますが、Amazon Linux 2023版は提供されていません。
Lambda ランタイムではNode.js 18.xまでは実行環境がAmazon Linux 2ですが、Node.js 20.xからAmazon Linux 2023に変更されるため、Node.jsランタイムのバージョンアップを行うタイミングでwkhtmltopdf以外の選択肢を考える必要があります。
GitHub上のやり取りを見たところ、Alma Linux 9版を使えばAmazon Linux 2023でも動くという話もあるみたいですが、私は未検証です。

AWS Lambdaでwkhtmltopdfを使用するまでの手順

Lambdaレイヤーを作成する

wkhtmltopdfをダウンロードする

  • こちらにアクセスして、Amazon Linux 2 (lambda zip)をダウンロード
    image.png

レイヤーを作成する

  • ダウンロードしたzipファイルを解凍する
    image.png

  • 解凍フォルダ内のfontsフォルダに日本語フォントを設置する(今回はNoto Sans CJK-Regularのみ使用)
    image.png

  • 解凍フォルダをもう一度zip圧縮する
    image.png

  • AWS Lambdaのコンソール画面でサイドバーの[レイヤー]→右上部[レイヤーの作成]を押下
    image.png

  • アップロードボタンからzipファイルをアップロードしてレイヤーを作成する
    ※10MB超えてもS3使わずに問題なく利用できている
    ※エラーになった場合はS3を使用してzipファイルをアップロードする

image.png

Lambda関数の作成

関数の作成

  • AWS Lambdaのコンソール画面で右上部[関数の作成]を押下

image.png

  • ランタイムをNode.js 18.xに設定して[関数の作成]を押下
    ※Lambda関数用のロールを作成している場合はここで設定
    image.png

関数にレイヤーを追加

  • コードタブ最下部から[レイヤーの追加]を押下
    image.png

  • カスタムレイヤーを選択して、先ほど作成したレイヤーを追加
    image.png

  • これでwkhtmltopdfの実行ファイルが"/opt/bin/"に設置されたので、Lambda関数でこれを呼び出せばPDF生成が実行できる

コードの設定

  • 実行コード例(※そのままコピペしても動きません)
const { exec } = require('child_process');
const fs = require("fs");
// その他必要なものをインポートする

exports.handler = async (event, context) => {
    // 何らかの方法でPDF生成前のHTMLファイルをtmpディレクトリに設置しておく
    
    const htmlFilePath = "PDF生成前のHTMLファイルのパス(tmpディレクトリ)";
    const pdfFilePath = "生成したPDFファイル出力先のパス(tmpディレクトリ)";

    return new Promise((resolve, reject) => {
        exec(`/opt/bin/wkhtmltopdf ${htmlFilePath} ${pdfFilePath}`, {
            env: {
              ...process.env,
              LD_LIBRARY_PATH: '/opt/lib',
              FONTCONFIG_PATH: '/opt/fonts'
            }
        }, (error, stdout, stderr) => {
            if (error) {
              return reject(error);
            }

            const output = fs.readFileSync(pdfFilePath);
            // S3にアップする等、生成したPDFを使ってやりたいことをする

            resolve({ data: "呼び出し元に返したい内容"});
        });
    });
}

メモリを1,536MBに増やす

  • 設定タブ→一般設定からメモリを1,536MBに設定
    ※メモリの設定によってLambda利用料金が倍増するので、ユースケースに合わせて設定する
    image.png

日本語の文字化けが起きたら確認すること

  • HTMLファイルに以下のタグがない場合は追加する
    ※忘れると日本語が謎の文字で表示されました
<meta charset="utf-8">
  • レイヤーのfontsフォルダに日本語フォントファイルが入っていなければ追加する
    ※忘れると日本語が「□」で表示されました
    ※スタイルの中でフォントを指定しなくてもフォントファイルをfontsフォルダに設置するだけで日本語で表示されました
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0