White Box技術部

WEB開発のあれこれ(と何か)

Puppeteerがnodeコンテナで動かなかったから動かした

Puppeteerの実行エラー

puppeteerのJestサンプルをTypeScriptで書いて動かそうとしたら、コンテナ側のライブラリ不足エラーが出たので対処していました。

エラー内容

動かそうとしたサンプルは以下で、Dockerコンテナのベースはnode:14-slimです。

これを実行すると以下のようなerror while loading shared librariesのエラーが表示されます。

エラーをメモしてなかったので、検索履歴から引っ張ってきたものですが、こんな感じのエラーが出ていました。 実際にはエラーは1つずつしか出ないので、都度apt-get installしていました。(見やすく改行や加工をしています)

error while loading shared libraries:
    libatk-bridge-2.0.so.0: cannot open shared object file: No such file or directory
    libgobject-2.0.so.0: cannot open shared object file: No such file or directory
    libnss3.so: cannot open shared object file: No such file or directory
    libasound.so.2: cannot open shared object file: No such file or directory

実行環境

動作時の主なライブラリのバージョンは以下のとおりです。

  • puppeteer: 5.3.1
  • typescript: 4.0.3
  • jest: 26.4.2
  • node: 14.11.0(コンテナ node:14-slim)

不足しているライブラリの追加

以下のようにライブラリを入れるとエラーが消えます。

apt-get install -y libgtk-3.0 libgbm-dev libnss3 libatk-bridge2.0-0 libasound2

コンテナにはbuild-essentialも入れているので、もしかしたらこれも必要かもしれません。

ライブラリ不足解消後の問題

ライブラリ不足を解消してから実行すると、今度は以下のエラーが出たので、エラーメッセージの通り、--no-sandboxを使って実行するように変更して解消しました。

Running as root without --no-sandbox is not supported. See https://crbug.com/638180

まとめ

Puppeteerをnode:14-slimのコンテナで使うためにはlibgtk-3.0 libgbm-dev libnss3 libatk-bridge2.0-0 libasound2のライブラリをOSにインストールし、 TypeScriptの場合は以下のようなコードにする必要があります。

import { beforeAll, afterAll, describe, expect, test } from '@jest/globals';
import puppeteer, { Browser, Page } from 'puppeteer';

let browser: Browser;
let page: Page;

beforeAll(async () => {
  browser = await puppeteer.launch({
    args: ['--no-sandbox']
  });
  page = await browser.newPage();
});

describe('Google Homepage', () => {
  test('has title "Google"', async () => {
    await page.goto('https://google.com', { waitUntil: 'networkidle0' });
    const title = await page.title();
    expect(title).toBe('Google');
  });

  afterAll(async () => {
    await browser.close();
  });
});

追記

そもそもこのexamples配下のコードは非推奨になっているくさく、「theheadless.devの方を見てね」みたいなことがREADMEに書かれてますね。

あとちゃんとは見てないのですが、jestでの実行は以下を使うのが良いのかもしれないです。

久しぶりにやったらTypeScriptのWebpackビルドで詰まった

今日は雑記なのでとりとめもないです。

WebpackでTSをビルドしたらWARNINGとERRORがめっちゃ出る

ちょっと話題に上がったので、なんとなく昔作ったプログラムをリファクタリングしていたのですが、 JSからTSに変えたついでにWebpackも導入したところ、ビルドしたらこんな感じのエラーがずらーっと出て、参ってしまったわけです。

WARNING in ./node_modules/config/lib/config.js 544:15-36
Critical dependency: the request of a dependency is an expression
 @ ./src/app.ts

WARNING in ./node_modules/config/parser.js 39:10-26
Critical dependency: the request of a dependency is an expression
 @ ./node_modules/config/lib/config.js
 @ ./src/app.ts

〜〜省略〜〜

ERROR in ./node_modules/when/lib/env.js
Module not found: Error: Can't resolve 'vertx' in '/reviewet/node_modules/when/lib'
 @ ./node_modules/when/lib/env.js 32:14-35
 @ ./node_modules/when/lib/decorators/timed.js
 @ ./node_modules/when/when.js
 @ ./node_modules/requestretry/index.js
 @ ./node_modules/slack-node/lib/lib/slack.seed.js
 @ ./node_modules/slack-node/lib/index.js
 @ ./src/lib/slack.ts
 @ ./src/domains/Notification.ts
 @ ./src/domains/Android.ts
 @ ./src/app.ts

とはいえ、node_modules配下のファイルをチェックしてしまってエラーになっているような感じなのはすぐに読み取れたので、 「exclude設定がなんかおかしいんだろうな」ぐらいの軽い気持ちではいました。

案の定、ドハマリしてしまったわけですが。

対処方法

運良く、対策が見つかったのでその方法を記載します。

webpack-node-externalsを利用する

以下の記事でwebpack-node-externalsを利用する方法が提案されており、このやり方でエラーを消すことができました。

ちなみにloaderの設定にexcludeを追加したり、babel-loaderを消してみたり、resolveを書いてみたり、tsconfig.jsonにexcludeを書いたりしましたが、全て効果はありませんでした。

webpack.jsサンプル

自分のwebpack.js(実際にはwebpack-mergeを使っているので、webpack.common.jsという名前ですが)に反映したコードは以下になります。

import path from 'path';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import nodeExternals from 'webpack-node-externals';

const src = path.resolve(__dirname, 'src');
const dist = path.resolve(__dirname, 'dist');

export default {
  target: 'node',
  externals: [nodeExternals()],

  entry: {
    app: src + '/app.ts'
  },

  plugins: [new CleanWebpackPlugin()],

  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.tsx?$/,
        loader: 'ts-loader'
      }
    ]
  },

  resolve: {
    extensions: ['.js', '.ts', '.json']
  },

  output: {
    filename: '[name].js',
    publicPath: dist,
    path: dist
  }
};

tsconfig.jsonはこんな感じです。

{
  "compilerOptions": {
    "target": "es2017",
    "module": "esnext",
    "lib": [
      "esnext",
      "dom"
    ],
    "allowJs": true,
    "sourceMap": true,
    "isolatedModules": true,

    "strict": true,

    "moduleResolution": "node",
    "typeRoots": ["src/@types"],
    "esModuleInterop": true
  }
}

ちなみに・・・

これがそのコード修正の全量なのですが、大体やりたかったリファクタリングが終わったので実行してみたところ、iOS側のアプリデータもまともに解析できなくなっていました。。。

調べてみると、RSSで配信されている内容が変わっていたからなのですが、そもそもアプリ自体の情報が配信されなくなっていたため、軽い修正で直るわけでもないので、せっかくやったけどもういいかな・・・っていう気分になっていたりはします。

やっぱり今アプリ開発していないのがモチベーションを下げているんですかね。自分で使っていれば意地でもなんとかしていたと思うので。

【AWS】APN1-DataTransfer-Out-Bytesの内訳をチェックする

発端と記事内容

AWSのコストをCost Explorerでチェックしていたら「APN1-DataTransfer-Out-Bytes」が前月と比較して2倍になっていたのですが、どこが食っているのかを調べようとしたら、Cost ExplorerからはELBで食っているところまでしかわからなかったので、もうちょっと頑張って調べた(けど思ったほど詳しくは見れなかった)という話です。

ちなみにCost Explorerでの調べ方

Cost Explorerではフィルターを使って、対象のサービスを見つけることまではできます。

  • フィルターは 使用タイプ > APN1-DataTransfer-Out-Bytes を選択して、
  • グループ化の条件で サービス を指定すれば、

以下のように出てきます。

f:id:seri_wb:20200925083536p:plain

ELBまでわかれば原因のサービスがわかることも多いかもしれませんが、 このシステムでは対象になっているELB配下に、ECSのサービスが結構紐付いているので、ECSサービスまでは知りたいとなったわけです。

「APN1-DataTransfer-Out-Bytes」でネット検索すると、以下のページがおもむろにヒットします。 項目の説明としてはあっていますが、別にS3だけの話ではないので注意が必要です。 AWS の Amazon S3 請求および使用状況レポートを理解する - Amazon Simple Storage Service

 

Billingを使う

いつもCost Explorerを見ているので忘れがちですが、Billingサービスを直接開くと料金明細が見れるので、そこから追ってみます。

まずは請求書を見てみた

対象月の請求書を見てみると、Data Transferの項目があるので開いてみると、末尾にBandwidthの料金が載っています(画像では金額を消していますが、実際には項目の右側にあります)。

f:id:seri_wb:20200925091047p:plain

上から2項目は0$だったのですが(当たり前)、下2項目、特に$0.114 per GB - first 10 TB / month data transfer out beyond the global free tierでガッツリ課金されていました。

とはいえ、これではわかったのは転送量の総量(と金額)くらいなので、別の方法を使います。

正直、AZ間通信分である$0.01 per GB - regional data transfer - in/out/between EC2 AZs or using elastic IPs or ELBでも結構な通信量が発生していることには驚きました。 通常のデータ転送費と比べると費用は1/10でも、そこそこな金額にはなっていたので侮りがたい・・・

Cost & Usage ReportsからAWS 使用状況レポートを見る

やや調べていると、サービス別で転送量を見る方法があるようなので、その方法である、AWS 使用状況レポートを使ってみました。

f:id:seri_wb:20200925091816p:plain

EC2 ELBっていうから・・・

使用状況レポートでは、AWSのサービスをプルダウンから選択して状況をチェックするようになっているので、 Cost Explorerでの結果をもとにAmazon Elastic Compute Cloudを選択して、使用タイプをDataTransfer-Out-Bytesにし、レポートをダウンロードして中身を見てみたのですが、 請求書の画面で確認した転送量には全く及ばない程度の量しか利用されていませんでした。

使用タイプのプルダウン項目にないものも結果には出る

じゃあ次に、とすぐECSを選択して出力しようとしたのですが、使用タイプのプルダウンに、EC2の場合とは違って、DataTransfer-Out-Bytesは表示されません。

なので「これは違うのかー」と別のサービスを調べたりしたのですが、結論としてはECSであっていましたし、中にAPN1-DataTransfer-Out-BytesAPN1-DataTransfer-ELB-Out-Bytes の結果が含まれていました。

ちなみにUsage Valueの単位はバイトのようです。CSVの各項目の見方については以下のサイトを参照。 Amazon S3 用の AWS 使用状況レポート - Amazon Simple Storage Service

ここからECSで動いているサービスに対するアクセスが原因なのは確定させられましたが、結局どのサービスがどの程度使っているのかはわかりませんでした。

f:id:seri_wb:20200925093855p:plain

結局CloudWatchを使う

わからなかったとはいえ、ECSであることは判明したので、あとはいつものようにCloudWatchを開き、 Container Insights > Performance monitoring から、ECS Servicesで対象のクラスタを選択し、ネットワーク TX/RXをみれば、 どのサービスがどのくらい転送しているかは判明するので、当たりをつけることはできました。

ちなみに、Billingの方は何も設定しなくても利用できますが、Container InsightsはECSクラスタ作成時に設定しないと利用できないので、 ECSでサービス運用する場合は設定をおすすめします。

結論

まとめると、APN1-DataTransfer-Out-Bytesの内訳をチェックするには、それぞれ以下の作業で確認していく必要があります。

# 作業 確認内容 今回の場合
1 Cost Explorer
使用タイプのフィルタ
対象のAWSサービス EC2 ELB
2 Billingの請求書チェック Data Transferの内訳 Bandwidthでの転送量
3 AWS 使用状況レポートで
サービス毎の使用タイプチェック
Usage Valueの合計値 ECSでの転送量
4 Container Insightsで
ネットワーク TX/RXの状況確認
転送量の多いサービス (サービス毎で差があった)

遠回りしたところもあるようで、結局は全部見ないと確定できなかったというのが難しいところですね。

一度あたりが付けば毎回ここまでは見なくても良さそうですが、たまにはこんなことも必要なのかなっていう感じです!

ブログを書かなくなった現状を打破するために雑記カテゴリを作った

ブログの記事が下書きで止まる問題

ここ2年ほど、ブログを書いては下書き保存で終わるということが続いており、結果として全然ブログを書いてない感じになっています。 その下書きも、ちゃんと記事の体になしていれば、「忘れないようにブログに書いておく」という目的は達成できるので良いのですが、あいにくそうはなっていないため、読み返してみても

「これなんで書いたんだっけ?」

「あれどこに書いたっけ?そもそも書いてたっけ?」

と、あまり芳しくありません。

このままだといい加減まずいかなと思ったので、ちょっと対応することにしました。

雑にでも形にしたらどうだろうか?

そもそも記事を公開できていないのは、

  • 題材に技術的なもの・調査が必要なものを選びがち
  • 1つの記事をちゃんとまとめて、形なりにも読み物にしようとする
  • そうすると1記事書くのにいつも数時間かかる
  • そんな時間を捻出できない

というところにあるので、これらの問題を解消するために、普段メモするようなことを雑に内容をまとめて公開することにしました。

具体的には、以下のような気持ちでやっていきたいなと思っています。

  • 業務や調査での学びを、とりとめもなく書く
  • 平日時間が取れれば、業務終了後に書く
  • 1つ30分1時間位で書く(30分は無理そうなので
  • 時間が来たらそこで公開する

雑記カテゴリの作成

こういった記事については、通常の記事と区別するために【雑記】のカテゴリを付けることにします。

雑記に記載した内容をまとめられれば、対応する雑記の方は消すかもしれません。そんなカテゴリにする予定です。

これで書くようになると良いんですが・・・

しかしホント書いてないな。。

数年前から自分でも書く頻度は落ちてきているなと思ってはいたのですが、ここ2年は本当にひどいですね。 こうなってくるとスタートアップに転職したのが多分に影響しているのかなという感じがします。

今の会社だと自分のやることが目まぐるしく変わっていて、2ヶ月同じ業務やっていたら長い!みたいなところがあり、 意識高い感じで言うと学びが多い期間ではあるのですが、多すぎて振り返っていれなかったのかもしれません。

私の意識は割と低いので、再利用できないと思ってしまったのかもしれないですね(将来、楽するためにブログを書いているので)。

でも最近は業務が一巡したというか、再利用することも出てきたので、やっぱりまとめないとなと危機感が募ってきて、こんな記事を書いた次第です。

ちなみにこの記事を書くには1時間半くらいかかりました。