White Box技術部

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

久しぶりにやったら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時間半くらいかかりました。

CircleCIでDockerfileのあるプロダクトをテストする

既存のアプリケーションをコンテナ化したら、CircleCIで回していたテストがコケていたので、その修正の記録です。

CircleCIの設定を書く

CircleCIの設定をいじるのは久しぶりだったので、以下のようなことも調べつつやっていたのですが、結局ハマってだいぶ時間をとられてしまいました。

  • dockerキー配下のimageは、先頭のコンテナイメージが、stepsのコマンド実行に使われる
  • コンテナから別コンテナを呼ぶ際の名前を変えたければ、nameキーを使う
  • ポートフォワードの指定はconfig.ymlからできない
  • dockerhubのdockerはalpineだったので、ソフトウェアのインストールはapk add

テスト用イメージの作成(ハマったとこ)

何にハマっていたのかと言うと、大きくは2つで

  1. コンテナから他のコンテナに繋げない
  2. コンテナの状態を育てていけない

というところでした。

1. コンテナから他のコンテナに繋げない

こういうことをしたかった、という実行環境のイメージは以下のような感じでした。

f:id:seri_wb:20200126004427j:plain:w600

各種stepを実行するためのコンテナイメージ(docker)がCircleCI上で動作していて、 そのコンテナの中で、アプリケーション(python)用のコンテナがビルドされ、起動後にテストが実行される・・・みたいな。

つまり、テストアプリケーションのコンテナを、Docker in Dockerの状態にしたかったのです。

そして、アプリケーションのコンテナから参照するテスト用のコンテナ(MySQL、Azurite)は、実行用コンテナと同列で起動させれば、ローカルのDocker Composeと同じ状態にできるなって思ったのですが、 結論から言うとこれはできないそうです。

f:id:seri_wb:20200315195206j:plain:w600

そうとは知らず、色々試してはみたので、結果だけは残しておこうかなって・・・

privilegedオプションでできること・できないこと

まずは、stepsの実行イメージをdockerベースのコンテナにし、そのコンテナでビルドしたイメージをprivilegedオプションを付けて起動させて、やりたいことができるか確認しました。

  build:
    docker:
      - image: docker:git
      - image: arafato/azurite:latest
        name: azurite
...
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build image
          command: docker build -t hoge/hoge-app .
      - run:
          name: Run image
          command: docker run -td --privileged --name hoge-app hoge/hoge-app bash

この状態で、それぞれのコンテナからできたことと、できなかったことは以下になりました。

dockerイメージのコンテナを実行コンテナ、その上でビルドしたイメージをアプリケーションとして書いています。

できた

  • 実行コンテナからAzuriteのイメージ名でcurl接続

できなかった

  • アプリケーションからAzuriteへのcurl接続(ホスト名解決できない)
  • 実行コンテナからlocalhostでAzuriteにcurl接続(ホスト名をazuriteにするとできる)

ちなみにAzuriteのホスト名解決を行うためには、dockerキーのvalue値でnameの指定も必要です(image名と同じだとしても)。

2. コンテナの状態を育てていけない

次に、アプリケーションのコンテナでテストを実行できるようにするには、 Dockerfileで実行される処理以外にも処理を実行する必要があったので、コンテナの状態を育てていく必要がありました。

そこで、docker run時にコマンドとしてpipenv sync --devを渡していたわけですが、これがなぜか実行されなかったため、 run時はDockerfile上のCMDを動作させないためにbashを渡し、別のステップでdocker execで実施するようにするしかありませんでした。

また、docker execでpipenv sync --devを実行したステップの後、pipenv run lintなどをexecに渡して実行すると

「mypyなんぞない」

と言われたので、、、execにコマンドを渡す際はbash -cを使って渡すようにしました。

      - run:
          name: Install dependencies
          command: docker exec hoge-app bash -c 'pipenv sync --dev'
      - run:
          name: Lint code
          command: docker exec hoge-app bash -c 'pipenv run lint 2>&1' | tee lint.log
      - run:
          name: Test hoge-app
          command: docker exec hoge-app bash -c 'pipenv run test 2>&1' | tee test.log

      - store_artifacts:
          path: lint.log
      - store_artifacts:
          path: test.log

とりあえずこうすると、Docker in DockerでもLintとTestの実行はできたのですが、名前解決がうまく行かないことなどから肝心のテストがコケるので、サポート問い合わせした結果、やりたいような構成はできないという回答をもらったわけです。

以下にも直接通信ができないと書いてあるので、そのとおりだと言う話でもあるのですが。。。

最終的なconfig.yml

というわけで、最終的には実行コンテナをDockerfileのベースイメージにし、Dockerfileに書いている処理をstepsに記載するという、普通のやり方で対応しました。

以下は最終的なconfig.ymlを適当に抜粋したものになります。

version: 2

jobs:
  build:
    docker:
      - image: circleci/python:3.6.6
        name: hoge-app
        environment:
          DB_HOST: test-db
      - image: circleci/mysql:5.7-ram
        name: test-db
        environment:
          MYSQL_USER: root
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          MYSQL_TCP_PORT: 3306
      - image: redis
        name: redis
      - image: arafato/azurite:latest
        name: azurite
    working_directory: ~/hoge-app
    environment:
      LANG: ja_JP.UTF-8
      LC_ALL: ja_JP.UTF-8
      LANGUAGE: ja_JP:ja
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Set locales
          command: sudo localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
      - run:
          name: Update submodule
          command: git submodule update --recursive --init
      - run:
          name: Install dependencies
          command: |
            sudo apt-get update && \
            sudo apt-get install -y curl build-essential \
            mecab mecab-ipadic libmecab-dev
      - run:
          name: Install dependencies
          command: pipenv sync --dev
      - run:
          name: Setup azurite filedata
          command: curl -v -X PUT -H 'x-ms-version:2017-11-09' http://azurite:10000/azureacount/filedata?restype=container

      - run:
          name: Lint code
          command: pipenv run lint 2>&1 | tee lint.log
      - run:
          name: Test hoge-app
          command: pipenv run test 2>&1 | tee test.log

      - store_artifacts:
          path: lint.log
      - store_artifacts:
          path: test.log