Cloud Run に Puppeteer を動かすスクリプトをデプロイ
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 自体の書き方はここを見た↓
req.body
が undefined
express 経験がある人には自明なことなんだろうけど、いつも Python で requests
を使っている身からするとミドルウェアを入れないと body がとってこれないのは意味不明すぎた。
今回は svg
のリクエストが飛んでくるので 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'] });
.
これを 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
をすれば両方やってくれてた。
というのは、ここを読んだらわかった。
アクセス制限をしたい
有象無象にこのエンドポイントを叩かれたくないという場合は、
- Cloud Run の SECURITY タブで Require authentication を選択して、
- Service Account を新しくつくって、
- Cloud Run の Permissions で Service Account に Cloud Run Invoker ロールをつける。
- Cloud Run をコールするサービスで、ここのやり方に沿って authenticated request を飛ばす
Discussion