🍭

Cloud Run に Puppeteer を動かすスクリプトをデプロイ

2023/08/20に公開

TL;DR

  • Dockerfile を書かないと、 puppeteer を動かすためのライブラリが動かない
  • apk にはライブラリがないので slim を使おう

packages

$ npm i convert-svg-to-png

Dockerfile

FROM node:18-slim

# Install libnss3 using apk
RUN apt-get update && \
  apt-get install -y libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2

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

# 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 . . 

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

index.js

const express = require("express")
const { convert } = require("convert-svg-to-png")

const app = express()
// specify middleware; otherwise, the req.body will be undefined
app.use(express.raw({ limit: "100mb", type: "image/svg+xml" }))

app.post("/", async (req, res) => {
  console.log(req.body)
  console.log("converting...")
  const png = await convert(req.body, { puppeteer: { args: ["--no-sandbox"] } })
  console.log("converted!")

  res.set("Content-Type", "image/png")
  res.send(png)
})

app.get("/", async (req, res) => {
  res.send("svg-to-png: ready")
})
const port = parseInt(process.env.PORT) || 8080

app.listen(port, async (req, res) => {
  console.log(`svg-to-png: listening on port ${port}`)
})

commands

$ gcloud config set project <PROJECT_ID>
$ gcloud run deploy

pre-amble

VPS 上で Selenium を動かして、SVG → PNG をやってたんだけど、メモリリークしたりするし環境を移行しにくいので外出しすることにした。選択肢として

  • 有料 API
  • Cloudflare Browser Rendering
  • Cloud Run
  • Cloud Functions

があったんだけど、有料 API は高いし、既に一部 GCP 上で動かしてるリソースがあったので Cloud Run or Functions になった。これまでの経験からなんとなく Dockerfile が必要になりそうだったので、とりあえず Cloud Run にした。実は Node.js で Cloud Run 書くのは初めてだったよ。

結論 Puppeteer の記事GitHub issue を見ると、今のところ GCF でも動くっぽいが、過去に動かなかったこともあったようなので、 Cloud Run の方が丸そう。

ちなみに他の環境での「動かないんだけど」系のトラシューも書いてあるので↑の Puppeteer のページ今後も助かりそう。

困ったとこ

もう最初のコードを貼れば動く(はず)なんだが、思い出的に書いておくと

ライブラリがいろいろ足りない

Dockerfile なしでデプロイしたら色々足りないと言われた。Running Puppeteer on WSL のセクションにあるライブラリを入れたら動いた。
Running Puppeteer on Docker のセクションもあるけど、 chrome を落としてくるやり方はひじょーに面倒な経験があったので、スルーして apt-get のやり方でいった。あと、 SO でエラーメッセージを探すと「 libnss3 とかインストールすればいいよ」という回答もあったので、あながち間違ってないと思う。

# Install libnss3 using apk
RUN apt-get update && \
  apt-get install -y libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2

ちなみに alpine の apk だと、上記のファイルがないので slim を使おう。

Dockerfile 自体の書き方はここを見た↓
https://codelabs.developers.google.com/codelabs/cloud-run-hello#3

req.bodyundefined

express 経験がある人には自明なことなんだろうけど、いつも Python で requests を使っている身からするとミドルウェアを入れないと body がとってこれないのは意味不明すぎた。

今回は svg のリクエストが飛んでくるので Raw 用のミドルウェアを入れた。
https://expressjs.com/en/api.html#express.raw

const app = express()
// specify middleware; otherwise, the req.body will be undefined
app.use(express.raw({ limit: "100mb", type: "image/svg+xml" }))

--no-sandbox

Puppeteer を立ち上げるときに、こいつをつけないと Cloud Run では動かない。ローカルでは動くのにデプロイしたら動かない系のやつです。

ちなみに Puppeteer のページにも Running on Heroku のセクションに書いてある。

Ensure that you're using '--no-sandbox' mode when launching Puppeteer. This can be done by passing it as an argument to your .launch() call: puppeteer.launch({ args: ['--no-sandbox'] });.

https://pptr.dev/troubleshooting#running-puppeteer-on-heroku

これを convert-svg-to-png で使うためには、 options の puppeteer に渡す。

const png = await convert(req.body, { puppeteer: { args: ["--no-sandbox"] } })

see: https://github.com/neocotic/convert-svg/tree/main/packages/convert-svg-to-png#options

デプロイのコマンドがわからない

手動で Cloud Build にイメージをプッシュして、そのあと Cloud Run を設定しなきゃいけないのかと思ったらそんなことなかった。普通に gcloud run deploy をすれば両方やってくれてた。

というのは、ここを読んだらわかった。
https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service

アクセス制限をしたい

有象無象にこのエンドポイントを叩かれたくないという場合は、

  1. Cloud Run の SECURITY タブで Require authentication を選択して、
  2. Service Account を新しくつくって、
  3. Cloud Run の Permissions で Service Account に Cloud Run Invoker ロールをつける。
  4. Cloud Run をコールするサービスで、ここのやり方に沿って authenticated request を飛ばす

Discussion