5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NowでNuxtとPuppeteerをDockerでデプロイする

Posted at

もうやりたくねー!!です!!

解説専用ではありませんが作っていたコードはGitHubにあります!

khsk/togetter-to-text: TogetterをPuppeteerでスクレイピングする

どんなものを作ったか

フロントをNuxt.jsで構築し、APIでPuppeteerを起動しスクレイピング結果を返して表示する。

動作環境

  • Docker mhart/alpine-node:base-10
  • Nuxt 2.2.0
  • Puppeteer 1.9.0
  • express 4.16.4

NuxtのserverMiddlewareでPuppeteer動かない問題

詳細はこちら
Nuxt.jsからPuppeteerが実行できない TypeError: Cannot read property 'on' of undefined - Qiita

NuxtにAPIサーバーを追加する場合、本来ならNuxt単体でHTTPリクエストを処理できるのですが、Puppeteerが動作しなかったため、expressにNuxtを追加する形になりました。
しんどい!

nowでPuppeteer動かない問題

Google's Puppeteer doesn't work in now.sh · Issue #861 · zeit/now-cli

こちらは知識としては既知だったのですが、nowでNPM形式ではPuppeteerが動きません。
せっかくPuppeteerがchromiumを同梱してくれるようになったのに…

動かすにはDockerfileをデプロイする必要があります。

nowでPuppeteer入りDockerがデプロイできない問題

Puppeteerを動かすDockerfileについては
zeit/now-examples: Examples of Now deployments you can use

now-examples/misc-screenshot at master · zeit/now-examples
が役立ちます。

というか私がDocker及びサーバー構築に無知なのでこれをそのまま動かそうとしました。

が、Puppeteerの他にNuxtなどを含めた結果、ファイルサイズが100MBを超え、サイズ制限に引っかかってデプロイできませんでした。

ローカルのnode_modulesのデプロイに気をつけよう

参照元の構成ではメインのコードが./srcディレクトリに集約されています。

私のコードはルート直下にすべてあることと、

マルチステージビルドへの理解が薄いため、

FROM mhart/alpine-node:10 as build
WORKDIR /usr/src
#COPY package.json package-lock.json /usr/src/
COPY . /usr/src/

.をまるごとコピーしていました。
これではせっかくビルド環境で

RUN PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 yarn install --production

しているのに、ローカルで動かすdevDependenciesやchromiumが含まれてしまってファイルサイズが膨張してしまっていました。
(cpにexceptできれば簡単でしたが)

そのため、ローカルファイルを全部コピーした後、

RUN rm -rf ./node_modules
RUN PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 yarn install --production

とローカルのnode_modulesを削除してから改めてビルドしました。

…が、それでも結果は180MB → 110MBとデプロイには至りませんでした…

(つい最近の更新では.nowignoreが追加されたようです)

Dockerデプロイをv1で動かそう

サイズ制限の問題は
Image size limit of 100 Mb · Issue #1523 · zeit/now-cli
などで話されています。

ここでは
now.json

"features": {
  "cloud": "v1"
},

と、普段v2で動くモードをv1に変更すれば動くよと言われています。

v2v1の違いは正直よくわかりません…

が、確かにDockerのデプロイ自体には成功しました。

デプロイに成功したのにアクセスがタイムアウトする

Docker内でexpressが正常に動いているかがわかりませんでした。
しかしログ的には起動自体は完了していそうです。
しかししかし、動かすJavaScriptを
now-examples/node-express at master · zeit/now-examples
の簡単なexpressにしても同様…
デプロイ先に3000番ポートでアクセスしてもダメでした。

ここで、個人利用のDockerってたしかポートフォワーディングしないとダメだったような記憶があったことを思い出しました。

また、npmのデプロイでもnowは暗黙的にスクリプトで開けたポートをデプロイ先の80番ポートとして扱ってくれていることを思い出しました。

そこで、Dockerfileに

EXPOSE 3000

を追加することで80番ポートでアクセスするとレスポンスが返ってくることが確認できました。

なぜそうなるかと言うと(なぜEXPOSE 3000がなかったかというと)

Automatic port discovery. We no longer rely on the 'EXPOSE' instruction. We automatically forward traffic to the port of the process started by 'CMD'

v2からは自動的に処理してくれるので、v1で動かそうとすると手動設定が必要だったわけです。
ドキュメントをちゃんと読むことも大切ですが、過去にDockerを触ってなかったら非常に危なかった…

(でもEXPOSEしても-pオプションを使わないとうまくいかないのでは?とすごく悩んではいました。今でもちゃんとした動きは把握していないデス)

レスポンス真っ暗問題

ちゃんとポートフォワーディングできるようになりましたが、レスポンスがなくてまっさら。
ただ、スクリプトを
now-examples/node-express at master · zeit/now-examples
に変えるとちゃんと表示されたので、expressの問題だろうと見当はつきました。

結論から言うと、expressでNuxtを動かすためにコピペしたコード、

【Node.js】express + Nuxt.jsでシンプルなテンプレートを作った - Qiita

const express = require("express")
const { Nuxt, Builder } = require("nuxt")

const app = express()

let config = require("./nuxt.config.js")
config.dev = !(process.env.NODE_ENV === "production")
const nuxt = new Nuxt(config)

if (config.dev) {
    const builder = new Builder(nuxt)
    builder.build()
}

が問題でした。

こちらもコピペしたDockerfileでは

FROM mhart/alpine-node:base-10
WORKDIR /usr/src
ENV NODE_ENV="production"

としているので、production環境になりNuxtのためのbuilderが働いていませんでした。

expressでNuxtを動かす背景を理解していないために起きた問題ですね。

// if (config.dev) {
    const builder = new Builder(nuxt)
    builder.build()
// }

とすることで解決。

Puppeteer動かない問題

ここからはPuppeteerとの戦いになります…

launchできない

これはローカルのコードでは手元のchromiumを動かそうとしますが、Dockerではちゃんと用意したものを使わないといけないからです。
ここで

config.dev = !(process.env.NODE_ENV === "production")

NODE_ENV === "production"が役立ち、デプロイ用の記述を使い分けられることに気づいたのが良かったです。

now-examples/setup.js at master · zeit/now-examples

をちゃんと確認して、

        if (process.env.NODE_ENV === 'production') {
            this.browser = await puppeteer.launch({
                executablePath: '/usr/bin/chromium-browser',
                args: ['--no-sandbox', '--headless', '--disable-gpu', '-—disable-dev-tools'],
                dumpio: true,
                devtools: false,
            })
        } else {
            this.browser = await puppeteer.launch({ headless: true, })
        }

productionではalpineのchromiumのパスをちゃんと指定しましょう。

.click()に失敗する

if (await this.page.$(selector)) {
    await this.page.click(selector)
}

ちゃんと要素の存在を確認しているのに、デプロイ先では

Node is either not visible or not an (HTMLElement)
と出てきてクリックに失敗します。

Node is either not visible or not an HTMLElement for page.click() in Puppeteer 1.6 · Issue #2977 · GoogleChrome/puppeteer

不本意ですが、倣ってevaluateでブラウザ内でクリックすることになりました。

本番はクリック結果を待つためにPromise.allを使います
        const moreTweetId = '#more_tweet_btn'
        if (await this.page.$(moreTweetId)) {
            if (process.env.NODE_ENV === 'production') {
                // now環境で.click()にError: Node is either not visible or not an HTMLElementがでるのでeval clickで回避する
                await Promise.all([this.page.waitFor((moreTweetId) => !document.querySelector(moreTweetId), moreTweetId), this.page.evaluate((moreTweetId) => { document.querySelector(moreTweetId).click() }, moreTweetId),])
            } else {
                await Promise.all([this.page.waitFor((moreTweetId) => !document.querySelector(moreTweetId), moreTweetId), this.page.click(moreTweetId),])
            }
        }

デプロイ制限

nowはデプロイ無制限をうたっていますが、

logs prematurely rate limited · Issue #1169 · zeit/now-cli

何回も(それでも結構な回数)デプロイした結果、制限に引っかかってしまいました。
動かない焦りで雑なデプロイを繰り返していたので、逆に良かったかも…

その他 不定期失敗

yarn install

デプロイ時のyarn installで

error https://registry.yarnpkg.com/@nuxtjs/youch/-/youch-4.2.3.tgz: Extracting tar content of undefined failed, the file appears to be corrupt: "Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?"

が出るときがありました。時間を空けて解消。

CDN

Puppeteer動作時にpage.goto
net::ERR_NAME_RESOLUTION_FAILED
になることがありました。
デプロイし直すと直ったり直らなかったりします…

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?