Node Inspectorを用いてDockerでnode.jsをデバッグする方法

本記事では、Node.jsのDockerコンテナをライブラリのNode Inspectorを使ってデバッグする便利な方法について説明します。コンテナを扱う際には少し違った考え方をすることがいかに重要かということを学べる、非常に興味深いケーススタディになりました。

なぜ?

お気に入りのIDEを使ってNode.jsをデバッグすることも可能ですが、データやコードを徹底的に調べようとするなら、ブラウザベースの方法が相変わらず便利です。そこで、Node Inspectorを使えるようにしたかったのです。

どうやって?

この目的を達成する方法は、理論的にはいくつかの選択肢があります。このうち、うまくいく方法をご紹介する前に、まず私が選ばなかった方法、あるいはうまく機能しなかった方法について説明したいと思います。

Node Inspectorとnode-app自体を一体化する

これを行う方法で最も明快なものは、Node.jsの同じコンテナ内でNode Inspectorを実行することです。しかし、次の2つの理由から、私はこの方法を好みません。

  1. node-appとNode Inspectorを並行して実行しなければならないとなると、”vanilla”/standardのnode.jsイメージの実行が困難であること。
  2. 私の意見では、コンテナは本番環境でも開発環境と似たような状態であることが好ましいと思っており、”works on my machine(私のパソコンでは動きます)”という状態を避けたいため。

こうした理由から、本番環境でのコンテナの開始方法が開発環境と似たものになるよう、「node-appコンテナの開発環境での開始方法に変更が必要」という解決策を方法を除外しようと考えたのです。

node-appのコンテナにNode Inspectorをリモート接続する

これが次のアイデアでした。Node Inspectorをnode-app:5858に接続するためにDocker内のDNSを使うというものです。これは問題ないはずです。ただし、残念ながらNode Inspectorは実際、リモートでは動きません。そのため、これはできません。debuggerコンテナとnode-appコンテナは別々のIPアドレスであるため、うまく動かすことができなかったのです。

全てのコンテナをホストネットワーキングする

次に検討したのは以前に使ったことのある方法で、全てのコンテナが”ホスト”のネットワークを使って実行するというものでした。そうすると、Node Inspectorを実行する際、node.jsのプロセスをデバッグするために、簡単にlocalhost:5858に接続することができます。しかし、ホストのネットワークにはいくつかの欠点があるため、デバッグとは関係ない理由ではありますが、これも避けることにしました。

Dockerの「コンテナ」をネットワーキングする

実は、このようなタイプのネットワークの存在自体、まさにこのNode Inspector問題の解決策を探して初めて知りました。この方法を実際に使っている例はGitHubの@seelio氏で、リポジトリはseelio/node-inspector-dockerです。

「コンテナ」のネットワーキングという言い方は少々紛らわしいので、ここではseelioさんのdocker-compose.ymlの例を挙げます。

node-app-inspector-instance:
  container_name: node-inspector-node-app
  # ----------------------------------------------------------
  # build.shスクリプトからイメージを利用する
  image: seelio/node-inspector:latest
  # ----------------------------------------------------------
  # ネットワークをnode-appコンテナと共有します。
  # これは、node-appプロセスがNode Inspectorからのデバッグリクエストを取得する
  # 唯一の方法です。
  # その理由は、node-appはlocalhost/127.0.0.1しかリッスンせず、
  # dockerポートを通じてポートをマッピングしようとするのを良しとしないからです。
  network_mode: "container:node-app"
  # ----------------------------------------------------------
  # node-appコンテナからボリュームを取得します。
  # これにより、node appコンテナからの
  # ソースマップ取得などが可能になります。
  # ----------------------------------------------------------
  # また、node-inspectorファイルを、node-appと
  # node-inspectorの両コンテナで同じ場所に
  # 配置することも重要です。
  volumes_from:
    - node-app

ご覧のとおり、ここで重要なトリックはnetwork_mode: "container:node-app"です。これは、基本的に「Node InspectorコンテナはNode.jsコンテナのネットワークに寄生できる」ということを意味し、結果として「Node.jsアプリがバインドされれば、Node Inspectorがlocalhost:5858に接続できる」ということを意味します。ここで注意すべき重要な点は、その際Node Inspectorのポート(例えば、デフォルトなら8080)をNode.jsコンテナのコンフィギュレーションで設定する必要があるということです。Node Inspectorコンテナのコンフィギュレーション上ではなくです。例えば、次のとおりです。

ports:
  # ----------------------------------------------------------
  # appポートの開放
  - 3000:3000
  # ----------------------------------------------------------
  # node-inspectorがリッスンするポートの開放
  # これを以降でなくここで行うのは、node-inspector
  # コンテナが--net=containerモードを使うからです
  - 8080:8080

この方法を採ったところ、2つの問題がありました。1つ目は、Docker Composeのバグのようなのです。というのは、このタイプのコンフィギュレーションを持つコンテナをコールドスタートしようとした際に、以下のエラーが出たからです。

ERROR: Service 'debugger' uses the network stack of container 'node-app' which does not exist.


エラー:「デバッガ」サービスは、存在しないコンテナ「node-app」のネットワークスタックを使用しています。

デバッガ用に別々のdocker-compose.ymlファイルを使うことにより、これを打開することもできますが、それでも1つ端末の問題が残っています。開発におけるワークフローの一部として、Node.jsのnode-appコンテナは再起動されます。残念ながら、どういうわけかこれが同時にNode Inspectorからの寄生ネットワークを「壊して」しまいました。つまり、node-appが再起動するたびに、Node Inspectorを再起動しなければならなくなってしまったのです。これはできないことはないのですが、私としては、あまりやりたいことではありませんでした。

解決策:ブリッジとホストネットワークのハイブリッド

最終的な解決策は、後になってみると、非常に明快なものでした。本番環境のコンテナ(Node.js、postgresなど)は全て、ブリッジネットワーキングを使っていますが、だからと言ってNode Inspector用のホストネットワーキングを使えないというわけではありません。

デバッガのためのDocker Composeの設定は、以下のとおり、プロジェクト固有の設定がほとんどなので、とにかくシンプルです。

debugger:
  container_name: debugger
  network_mode: host
  extends:
    service: base
  volumes:
    - /app/containers/debugger:/app/container

求められるのは、node-appコンテナにマッピングするポート5858を追加するというちょっとしたトリックです。これは、Node Inspectorからはホストのポート5858として見えるようにするためです。

node-app:
  ports:
    - "5858:5858" # debuggerコンテナがアクセスできるよう、ポートのマッピングが必要

注意すべき重要なことは以下のとおりです。

  • これは、開発のときにだけ使われるdocker-compose.override.ymlファイルに含まれています。
  • EC2のセキュリティグループは、ポート5858が実際にホストの外からは見えないように設定します。
  • EC2のセキュリティグループの設定は、外へのNode Inspector独自のインターフェースポートを許可します。そのため、アクセスを制限するためにHTTPセキュリティを使います。

まとめ

  • たとえ他のコンテナ向けには使わないという場合でも、Node Inspector向けにはホストのネットワーキングを使うようにします。
  • Node.jsのデバッグポート(例えば、5858)をホストにマッピングするようにしますが、それはあなたの開発環境でだけにします。
  • なるべく、ホスト外からこのポートへのアクセスを許可しないようにします。
  • Node Inspector独自のポート(例えば、8080)への外部からのアクセス(自分用)を許可するようにしても(例えば、8080)、基本的にはセキュアにしておくのが理想です。

本記事では、主にプロジェクト固有のところに関しては、まだ詳細まで説明しきれていません。もしご不明な点がございましたら、ご質問くださればより詳細にお答えすることができます。