見出し画像

Firebase Cloud Functions で PDF を画像化しようとして Cloud Run にたどり着いた 後編

概要

Firebase Cloud Functions でなんとか PDF を画像に変換できないか
色々試したことと、最終的に Cloud Run を利用した話です。

前回の続きです。
Firebase Cloud Functions だけでは PDF を画像にすることができなかったので、今回は Cloud Run を使ってどのように解決したのかを紹介します。

Cloud Run とは

Cloud Run は Docker で作成したイメージやアプリケーションをコンテナとしてクラウドで実行できるものです。

Docker を使うことができるので、ランタイム環境も自由にいじることができます。

※ Cloud Run を使用するためには課金設定をONにする必要があります。

Firebase Cloud Functions でネックになっていたこと

Firebase Cloud Functions ではランタイム環境をいじることができないため、pdf2pic のような内部でライブラリに依存しているようなパッケージを使うことができませんでした。

しかし、Cloud Run ではランタイム環境をいじることができるので、
依存ライブラリをインストールした環境を用意した上で処理を実行することが可能です。

今回は pdf2pic ではなく PDF を画像に変換できる pdftoppm というコマンドを使える環境にして、http通信をトリガーに PDF を画像を変換するという構成にしました。

実際にやったこと

Cloud Run で動作するアプリを作成 

まず公式のチュートリアルを参考に事前準備などを済ませて、
Hello World サンプルアプリを改良して POST された PDF のバイナリデータを受け取って、pdftoppm で画像に変換し、レスポンスとして返すようなエンドポイントを追加しました。

app.post('/ENDPOINT', (request, response, next) => {
   (async () => {
       if (request.method !== 'POST') {
           response.status(405).send('Request method is not POST');
           return;
       }

       const base64String = String(request.body.encoded);
       if (typeof base64String === 'undefined') {
           response.status(400).send('Request body is empty');
           return
       }
   
       console.log(`Request body is ok.`);

       const decodedBinary = Buffer.from(base64String, 'base64');
       const localPDFPath = 'calendar.pdf';
       fs.writeFileSync(localPDFPath, decodedBinary);
   
       console.log(`Decode and write file success.`);
   
       // NOTE: PDF を読み込んで pdftoppm コマンドで PNG に変換
       const localPNGPath = 'calendar.png'
       await new Promise((resolve, reject) => {
           exec(`pdftoppm -png -singlefile ${localPDFPath} calendar`, (error) => {
               if (error) {
                   reject(error);
               } else {
                   resolve();
               }
           });
       });

       console.log('Convert PDF to PNG success');

       const encodedPNGBinary = fs.readFileSync(localPNGPath, { encoding: 'base64' });

       fs.unlinkSync(localPDFPath);
       fs.unlinkSync(localPNGPath);
       
       response.status(200).send(encodedPNGBinary);
   })().catch(next);
});

■ Dockerfile の作成

以下のような Dockerfile を作成しました。

# https://hub.docker.com/_/node
FROM node:10-alpine

# Create and change to the app directory.
WORKDIR /usr/src/app

# Install required packages
RUN apk --no-cache add poppler-utils

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
RUN npm install --only=production

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD [ "npm", "start" ]

ほとんどチュートリアルのものと同じですが、
違うのは poppler-utils というパッケージをインストールしている部分です。

poppler-utils には先述した PDF を画像に変換することができる pdftoppm が入っているのでインストールしています。

■ イメージのアップロードとデプロイ

あとはチュートリアル通りにイメージを Container Registry にアップロードして、Cloud Run にデプロイするだけです。

# Container Registry にアップロードするコマンド
gcloud builds submit --tag gcr.io/<PROJECT-ID>/<IMAGE-NAME>
# アップロードしたイメージを Cloud Run にデプロイするコマンド
gcloud run deploy --image gcr.io/<PROJECT-ID>/<IMAGE-NAME> --platform managed

デプロイが終了するとアクセスできる URL が表示されるので、
あとはその URL に Firebase Cloud Functions からアクセスするだけです。

注意したほうがいいと思ったこと

■ Container Registry にアップしたイメージ、Cloud Run にデプロイしたイメージは Storage に保存される

Cloud Run と同時に Firebase Cloud Storage も一緒に使っていましたが、
ふと使用状況の保存バイト数のタブを見てみると以下のようになっていました。

スクリーンショット 2020-07-02 12.36.11

使用量の多さにびっくりしましたが、よく見ると上から Cloud Run にデプロイしたコンテナのサイズ、実際に Cloud Storage にアップした画像などのサイズ、Cloud Build で使用した(?)サイズになっているみたいでした。

なのでイメージのサイズが大きいものを複数デプロイしたりすると Cloud Storage の料金が大変なことになるかもしれないので、その辺りは注意したほうが良さそうです。

公式のドキュメントにもありますが Container Registry にアップされたイメージは Cloud Storage にアップされるみたいです。

最後に

Cloud Run にたどり着くまでに色々試行錯誤しましたが、どれも普段使わない技術ばかりでいい経験になりました。

今後も好き嫌いせずにチャレンジしていきたいと思っています。

この記事が気に入ったらサポートをしてみませんか?