Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Dockerコンテナ内Node.jsアプリをChromeDevToolsでリモートデバッグする方法

記事のサムネイル

Dockerコンテナ内Node.jsアプリケーションをデバッグする課題

Dockerコンテナ内で実行されているNode.jsアプリケーションのデバッグは、開発環境でよく直面する課題です。コンテナ化された環境では、従来のローカル開発時のデバッグ手法がそのまま使えないケースが多いため、効率的な開発に支障をきたすことがあります。

具体的な課題として以下のようなものがあります:

  1. コンテナ内で発生したエラーの詳細な原因追求が難しい
  2. 変数の中身やコールスタックをリアルタイムで確認できない
  3. console.logによる原始的なデバッグに頼らざるを得ない
  4. ブレークポイントを設置して実行フローを一時停止できない

特に複雑なバックエンド処理やAPI開発では、処理の途中経過を詳細に調査する必要があるケースが多いです。この記事では、ChromeDevToolsを使用してDockerコンテナ内のNode.jsアプリケーションをリモートでデバッグする方法を紹介します。

ChromeDevToolsとNode.jsのリモートデバッグの基本

Node.jsには、ビルトインでリモートデバッグを可能にする機能があります。--inspectまたは--inspect-brkフラグを使うことで、WebSocketプロトコルを通じて外部のデバッガからアプリケーションに接続できるようになります。

主なデバッグフラグ:

  • --inspect: デバッグポートを開き、アプリケーションを実行します
  • --inspect-brk: デバッグポートを開き、最初の行で実行を一時停止します(初回接続時に便利)
  • --inspect=[host:port]: 特定のホストとポートでデバッグポートを開きます

ChromeDevToolsは、この接続を使用してNode.jsアプリケーションとやり取りします。これにより以下のことが可能になります:

  • ブレークポイントの設定と実行の一時停止
  • 変数の値をリアルタイムで確認
  • コールスタックのトレース
  • メモリの使用状況の監視
  • コンソールからコマンドの実行

Dockerコンテナ内でNode.jsアプリケーションをデバッグするためには、このデバッグポート(デフォルトは9229)をコンテナの外部に公開する必要があります。

// デバッグ可能なNode.jsアプリケーションの例
const express = require('express');
const app = express();

// ブレークポイントを設定できる場所
app.get('/', (req, res) => {
  const message = 'Hello World'; // この変数の値を確認できます
  console.log('Request received');
  res.send(message);
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

このコードをデバッグするには、node --inspect=0.0.0.0:9229 index.jsのように実行します。0.0.0.0を指定することで、あらゆるIPアドレスからの接続を許可します。これはDockerコンテナ内で実行する場合に重要な設定です。

Dockerfileとdocker-compose.ymlの設定例

Dockerコンテナ内でNode.jsアプリケーションをデバッグするためには、Dockerfileとdocker-compose.ymlファイルを適切に設定する必要があります。以下に実用的な例を示します。

Dockerfileの例:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

# デバッグモードで実行するためのコマンド
# 本番環境では変更することを忘れずに
CMD ["node", "--inspect=0.0.0.0:9229", "index.js"]

# または、起動時に自動的に一時停止する場合
# CMD ["node", "--inspect-brk=0.0.0.0:9229", "index.js"]

重要なポイントは、--inspect=0.0.0.0:9229フラグを使用していることです。0.0.0.0は、あらゆるインターフェースからの接続を許可するために必要です。

docker-compose.ymlの例:

version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"  # アプリケーションポート
      - "9229:9229"  # デバッグポート
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    # 以下はnpmスクリプトでデバッグを開始する場合の例
    # command: npm run debug

この設定では、2つの重要なポートマッピングがあります:

  1. 3000:3000 - アプリケーションのHTTPポート
  2. 9229:9229 - Node.jsデバッグポート

package.jsonのスクリプト例:

{
  "scripts": {
    "start": "node index.js",
    "debug": "node --inspect=0.0.0.0:9229 index.js",
    "debug-brk": "node --inspect-brk=0.0.0.0:9229 index.js"
  }
}

これにより、npm run debugコマンドでデバッグ可能なモードでアプリケーションを起動できます。または、最初の行で実行を一時停止する場合はnpm run debug-brkを使用します。

デバッグポートの公開と接続方法

デバッグポートを公開した後、ChromeDevToolsを使ってコンテナ内のNode.jsアプリケーションに接続する方法を説明します。

1. コンテナの起動

まず、設定したDockerコンテナを起動します:

# docker-compose.ymlがある場合
docker-compose up

# または個別のDockerコンテナを起動する場合
docker run -p 3000:3000 -p 9229:9229 your-node-app

コンテナが起動すると、Node.jsアプリケーションがデバッグモードで実行され、以下のようなメッセージが表示されます:

Debugger listening on ws://0.0.0.0:9229/xxxxxxxxxxxx
For help, see: https://nodejs.org/en/docs/inspector

2. Chrome DevToolsでの接続

ChromeブラウザでNode.jsアプリケーションにデバッグ接続する方法はいくつかあります:

方法1: Chrome拡張機能を使用

Node.js V8 Inspector Managerなどの拡張機能を使用すると、デバッグ可能なNode.jsプロセスにワンクリックで接続できます。

方法2: DevTools専用URL

Chromeのアドレスバーに以下のURLを入力:

chrome://inspect

「Discover network targets」をクリックし、「Configure...」ボタンからリモートターゲットを追加します:

localhost:9229

「Done」をクリックして、「Remote Target」セクションに表示されるNode.jsアプリケーションの「inspect」リンクをクリックします。

Chrome DevTools接続画面例

方法3: コマンドラインから直接開く

# macOSの場合
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check https://localhost:9229/json

3. 接続トラブルシューティング

接続できない場合は、以下を確認してください:

  1. コンテナが正常に起動しているか
  2. デバッグポート(9229)が正しくマッピングされているか
  3. ファイアウォールでポートがブロックされていないか
  4. Node.jsアプリケーションが--inspect=0.0.0.0:9229パラメータで起動されているか

以下のコマンドでポートがリッスンされているか確認できます:

# Dockerコンテナ内のプロセスを確認
docker exec -it <container_name> ps aux | grep node

# ポートが開いているか確認
docker exec -it <container_name> netstat -tulpn | grep 9229

ポートが開いているにもかかわらず接続できない場合は、ネットワーク設定を確認するか、コンテナを再起動してみてください。

ブレークポイントの設定と変数調査のテクニック

ChromeDevToolsに接続できたら、効果的にデバッグを行うための方法を見ていきましょう。

ブレークポイントの設定方法

  1. ソースパネルでコードを特定: ChromeDevToolsの「Sources」タブで、デバッグしたいファイルを選択します。

  2. ブレークポイントを設定: 調査したい行の行番号をクリックしてブレークポイントを設定します。青いマーカーが表示されます。

  3. 条件付きブレークポイント: 行番号を右クリックして「Add conditional breakpoint」を選択すると、特定の条件が満たされた場合にのみ実行が停止します。

    // 例: userIdが特定の値の場合のみ停止
    userId === '12345'

変数とスコープの調査

実行が一時停止したら、以下の方法で変数を調査できます:

  1. Scopesパネル: 現在のスコープで利用可能な変数とその値がすべて表示されます。

  2. Watchパネル: 特定の変数やエクスプレッションの値を継続的に観察できます。「+」ボタンをクリックして監視したい式を追加します。

    // 例: 配列の長さを監視
    users.length
    
    // オブジェクトの特定のプロパティを監視
    request.headers.authorization
  3. コンソールでの評価: 一時停止中にコンソールパネルで変数や式を評価できます。

    // 例: 現在のスコープにあるusersオブジェクトを調査
    > users
    > users.filter(u => u.isActive)

実行フローの制御

デバッグ中にコードの実行フローを制御するためのボタンがあります:

  • Resume (F8): 次のブレークポイントまで実行を再開します
  • Step Over (F10): 現在の行を実行し、次の行で停止します(関数呼び出しにはステップインしません)
  • Step Into (F11): 関数呼び出しにステップインします
  • Step Out (Shift+F11): 現在の関数から抜けます
  • Deactivate breakpoints: 一時的にすべてのブレークポイントを無効化します

デバッグコントロール例

高度なテクニック

  1. Blackboxing: Node.jsのコアモジュールや依存ライブラリをブラックボックス化して、自分のコードのみにフォーカスします。

    設定方法: DevToolsの設定(F1) > Blackboxing > パターンを追加(例:/node_modules/

  2. ログポイント: コードを修正せずに「console.log」を挿入したのと同じ効果が得られます。行番号を右クリックして「Add logpoint」を選択します。

    // 例: リクエストの内容をログ出力
    Request received: {req.url}
  3. 非同期コードのデバッグ: 「Async」チェックボックスをオンにすると、Promiseや非同期関数の実行フローを追跡できます。

  4. DOM変更のブレークポイント: フロントエンドのデバッグで特定のDOM要素が変更されたときに一時停止する機能です。

これらのテクニックを駆使することで、複雑なコードの挙動を効率的に調査できます。特にDockerコンテナ内で実行されているコードは直接アクセスしにくいため、リモートデバッグの能力を最大限に活用することが重要です。

実践的なトラブルシューティングと解決例

最後に、Dockerコンテナ内のNode.jsアプリケーションデバッグで遭遇する可能性のある具体的な問題と、その解決方法を紹介します。

ケース1: デバッグポートに接続できない

問題: chrome://inspectでターゲットが表示されない。

解決策:

  1. コンテナの実行コマンドを確認:

    docker ps -a
  2. コンテナ内のNode.jsプロセスを確認:

    docker exec -it <container_name> ps aux | grep node

    正しいパラメータ(--inspect=0.0.0.0:9229)で実行されているか確認します。

  3. ポートマッピングを確認:

    docker port <container_name>

    9229ポートが正しくマッピングされているか確認します。

  4. ネットワーク設定の修正:

    # docker-compose.ymlの修正例
    services:
      app:
        ports:
          - "127.0.0.1:9229:9229"  # 特定のインターフェースにバインド

ケース2: TypeScriptやトランスパイルされたコードのデバッグ

問題: ブレークポイントが機能しない、またはソースマップが正しく読み込まれない。

解決策:

  1. ソースマップを有効にする:

    // tsconfig.jsonの設定
    {
      "compilerOptions": {
        "sourceMap": true,
        "inlineSources": true
      }
    }
  2. Node.jsでソースマップを有効にする:

    node --inspect=0.0.0.0:9229 --require source-map-support/register dist/index.js
  3. プロジェクトにソースマップサポートを追加:

    npm install --save-dev source-map-support

ケース3: nodemonとデバッグの連携

問題: ホットリロード中にデバッグ接続が切断される。

解決策:

  1. package.jsonのスクリプトを修正:

    {
      "scripts": {
        "debug": "nodemon --inspect=0.0.0.0:9229 index.js"
      }
    }
  2. nodemonの設定ファイルを作成:

    // nodemon.json
    {
      "restartable": "rs",
      "ignore": [".git", "node_modules/**/node_modules"],
      "verbose": true,
      "execMap": {
        "js": "node --inspect=0.0.0.0:9229"
      },
      "events": {
        "restart": "osascript -e 'display notification \"App restarted due to changes\" with title \"nodemon\"'"
      },
      "watch": ["src/"],
      "ext": "js,json"
    }

ケース4: メモリリークの診断

問題: コンテナがメモリを消費し続け、最終的にクラッシュする。

解決策:

  1. Memory Profileを取得:

    ChromeDevToolsの「Memory」タブで「Take heap snapshot」を使用。

  2. コードでのヒープダンプの生成:

    // index.jsに追加
    const heapdump = require('heapdump');
    
    // メモリ使用量が増加したタイミングでダンプを生成
    process.on('SIGUSR2', () => {
      heapdump.writeSnapshot('/app/heapdump-' + Date.now() + '.heapsnapshot');
    });
  3. コマンドでヒープダンプを生成:

    docker exec -it <container_name> kill -USR2 1
  4. ヒープダンプファイルをローカルにコピー:

    docker cp <container_name>:/app/heapdump-xxxx.heapsnapshot ./
  5. ChromeDevToolsの「Memory」タブで「Load」を使って分析。

ケース5: APIリクエストのデバッグ

問題: 特定のAPIエンドポイントで問題が発生し、リクエスト内容の調査が必要。

解決策:

  1. リクエストハンドラーにブレークポイントを設定:

    app.post('/api/users', (req, res) => {
      // ここにブレークポイントを設定
      const userData = req.body;
      // ...処理...
    });
  2. Network条件付きブレークポイントの活用:

    DevToolsの「Sources」タブで、「XHR/fetch breakpoints」にURLパターンを追加します。

  3. リクエストモックツールの活用:

    Postmanなどでコンテナに直接リクエストを送信し、デバッグブレークポイントをトリガーします。

これらの実践的なトラブルシューティング例を参考に、Dockerコンテナ内のNode.jsアプリケーションを効率的にデバッグしてください。適切なデバッグ環境を整えることで、開発効率が大幅に向上し、より堅牢なアプリケーションの開発が可能になります。

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ