Tasuke Hubのロゴ

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

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

Dockerコンテナ内Node.jsアプリのソースマップデバッグが効かない問題の解決法

記事のサムネイル

Dockerコンテナ内でソースマップが機能しない原因

Dockerコンテナ内でNode.jsアプリケーションを実行すると、ソースマップが正しく機能せずデバッグが困難になる状況がよく発生します。この問題はTypeScriptやBabelなどのトランスパイラを使用している開発環境でとくに顕著です。

主な原因として以下の問題が考えられます:

  1. ファイルパスの不一致

    • ホストマシンとコンテナ内でのファイルパスが異なる
    • ソースマップが参照するパスがコンテナ内の実際のパスと一致しない
  2. ボリュームマウント関連の問題

    • 不適切なボリュームマウント設定によりソースファイルがコンテナから見えない
    • パーミッション問題でファイルにアクセスできない
  3. Node.js設定の問題

    • ソースマップサポートが適切に有効化されていない
    • node --inspectフラグの不適切な使用
  4. デバッガー連携の問題

    • VSCodeのデバッグ設定が正しく構成されていない
    • リモートデバッグ接続が適切に設定されていない

これらの問題が複合的に絡み合うことで、コンテナ内のNode.jsアプリケーションでエラーが発生した際に、変換後のJavaScriptコードしか表示されず、元のTypeScriptコードでデバッグができないという状況が発生します。

典型的な例として、以下のようなTypeScriptコードがあるとします:

// src/app.ts
function calculateTotal(items: number[]): number {
  return items.reduce((total, item) => total + item, 0);
}

const result = calculateTotal([1, 2, "3" as any]);
console.log(`Total: ${result}`);

このコードはトランスパイルされて以下のようなJavaScriptになります:

// dist/app.js
"use strict";
function calculateTotal(items) {
    return items.reduce((total, item) => total + item, 0);
}
const result = calculateTotal([1, 2, "3"]);
console.log(`Total: ${result}`);
//# sourceMappingURL=app.js.map

Dockerコンテナ内でこのアプリケーションを実行した際にエラーが発生すると、デバッガーは dist/app.js ファイルの行番号を指し示し、元の src/app.ts ファイルにマッピングされません。この状態では、開発効率が著しく低下します。

次のセクションでは、この問題を解決するためのNode.jsにおけるソースマップの基本設定方法について詳しく説明します。

Node.jsにおけるソースマップの基本設定方法

Node.jsでソースマップを正しく機能させるには、以下の基本設定が重要です。特にDockerコンテナ内で動作させる場合、これらの設定を正確に行う必要があります。

TypeScriptプロジェクトでのソースマップ設定

TypeScriptプロジェクトでソースマップを有効にするには、tsconfig.jsonファイルに以下の設定を追加します:

{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSources": true,
    "sourceRoot": "/",
    "outDir": "./dist",
    // 他の設定...
  }
}

これらの設定の役割は以下のとおりです:

  • sourceMap: ソースマップファイルの生成を有効にします
  • inlineSources: ソースマップにソースコードを含めます
  • sourceRoot: ソースファイルのルートディレクトリを指定します
  • outDir: コンパイル後のファイルの出力先を指定します

Babelを使用している場合の設定

Babelを使用している場合は、.babelrcまたはbabel.config.jsファイルに以下の設定を追加します:

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }]
  ],
  "plugins": [],
  "sourceMaps": true,
  "sourceRoot": "/"
}
webpack設定の例

webpackを使用している場合は、webpack.config.jsファイルでdevtoolオプションを設定します:

module.exports = {
  // 他の設定...
  devtool: 'source-map',
  // または開発環境向けに
  // devtool: 'eval-source-map',
};

Node.jsでのソースマップサポートの有効化

Node.jsでソースマップを使用するには、source-map-supportパッケージをインストールします:

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

そして、アプリケーションのエントリーポイントで以下のコードを追加します:

// アプリケーションの先頭で追加
import 'source-map-support/register';
// または CommonJS の場合
require('source-map-support').install();

Docker環境向けの特別な設定

Docker環境では、パスのマッピング問題に対処するために、追加の設定が必要になることがあります:

// Docker環境用のカスタムソースマップサポート
require('source-map-support').install({
  retrieveSourceMap: function(source) {
    // コンテナ内パスとホストマシンパスの変換
    const containerPath = source;
    const hostPath = containerPath.replace('/app/', '/host/actual/path/');
    
    try {
      // ソースマップファイルを探す
      const sourceMapPath = hostPath + '.map';
      const sourceMap = require('fs').readFileSync(sourceMapPath, 'utf8');
      return {
        url: null,
        map: sourceMap
      };
    } catch (e) {
      return null;
    }
  }
});

上記の例は、コンテナとホスト間のパス変換を行うカスタム実装の例です。実際のプロジェクトでは、パス構造に合わせて調整が必要です。

NODE_OPTIONSによるソースマップ有効化

環境変数を使ってNode.jsにソースマップサポートを有効にする方法もあります:

# Node.js 12以降
NODE_OPTIONS="--enable-source-maps" node dist/app.js

これをDockerfileまたはdocker-compose.ymlに設定することで、コンテナ内での実行時にソースマップが有効になります:

# Dockerfile例
ENV NODE_OPTIONS="--enable-source-maps"

nodemonを使った開発環境での設定

開発中はnodemonを使用することが多いですが、ソースマップサポートを有効にするには以下の設定を行います:

// nodemon.json
{
  "execMap": {
    "ts": "node --enable-source-maps -r ts-node/register"
  },
  "watch": ["src"],
  "ext": "ts,json"
}

これらの基本設定を正しく行った上で、次のセクションで説明するVSCodeとの連携設定を適用することで、Dockerコンテナ内のNode.jsアプリケーションでもソースマップを使用したデバッグが可能になります。

デバッグ設定とVSCodeの連携設定

VSCodeとDockerコンテナ内のNode.jsアプリケーションを連携させ、ソースマップを使用したデバッグを行うには、適切な設定が必要です。ここでは、VSCodeでDockerコンテナ内のアプリケーションをデバッグするための詳細な設定方法を解説します。

launch.jsonの設定

VSCodeでは、.vscode/launch.jsonファイルにデバッグ設定を記述します。Dockerコンテナ内のNode.jsアプリケーションをデバッグするための基本的な設定は以下のとおりです:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Docker: Attach to Node",
      "remoteRoot": "/app",
      "localRoot": "${workspaceFolder}",
      "port": 9229,
      "address": "localhost",
      "skipFiles": ["<node_internals>/**"],
      "sourceMapPathOverrides": {
        "/app/*": "${workspaceFolder}/*"
      }
    }
  ]
}

この設定の主要なポイントは:

  • remoteRoot: コンテナ内のアプリケーションルートディレクトリ(通常は/app/usr/src/appなど)
  • localRoot: ホストマシン上のプロジェクトディレクトリ(${workspaceFolder}変数で指定)
  • sourceMapPathOverrides: ソースマップのパスをホストマシンのパスに変換するためのマッピング

sourceMapPathOverridesの詳細設定

sourceMapPathOverridesはソースマップが参照するパスを、VSCodeが理解できるローカルファイルシステムのパスに変換するための設定です。より複雑なプロジェクト構造の場合は、以下のように詳細なマッピングを設定することもできます:

"sourceMapPathOverrides": {
  "/app/src/*": "${workspaceFolder}/src/*",
  "/app/dist/*": "${workspaceFolder}/dist/*",
  "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
  "webpack:///src/*": "${workspaceFolder}/src/*"
}

複数コンテナ環境での設定

マイクロサービスアーキテクチャなど、複数のDockerコンテナを使用する環境では、サービスごとに異なるデバッグ構成を用意することができます:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Docker: API Service",
      "remoteRoot": "/app",
      "localRoot": "${workspaceFolder}/services/api",
      "port": 9229,
      "address": "localhost",
      "sourceMapPathOverrides": {
        "/app/*": "${workspaceFolder}/services/api/*"
      }
    },
    {
      "type": "node",
      "request": "attach",
      "name": "Docker: Auth Service",
      "remoteRoot": "/app",
      "localRoot": "${workspaceFolder}/services/auth",
      "port": 9230,
      "address": "localhost",
      "sourceMapPathOverrides": {
        "/app/*": "${workspaceFolder}/services/auth/*"
      }
    }
  ]
}

VSCodeのRemote Containers拡張機能の活用

VSCodeのRemote Containers拡張機能を使うと、コンテナ内の開発環境とより密接に統合できます:

  1. VSCodeにRemote Containers拡張機能をインストール
  2. .devcontainer/devcontainer.jsonファイルを作成:
{
  "name": "Node.js App",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/app",
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash",
    "typescript.tsdk": "node_modules/typescript/lib"
  },
  "extensions": [
    "dbaeumer.vscode-eslint",
    "ms-vscode.vscode-typescript-tslint-plugin"
  ],
  "forwardPorts": [3000, 9229],
  "remoteUser": "node"
}
  1. コマンドパレットから「Remote-Containers: Reopen in Container」を選択

これにより、VSCodeはDockerコンテナの内部で直接実行され、パスのマッピング問題を回避できます。

デバッグに関する重要なTips

  1. ブレークポイントが機能しない場合の確認事項:

    • ソースファイルがコンテナ内にマウントされているか
    • ソースマップファイル(.map)が生成されているか
    • sourceMapPathOverridesの設定が正しいか
  2. ホットリロードとデバッグの共存: nodemonやts-node-devでホットリロードを使用する場合は、以下のように起動オプションを調整します:

    nodemon --inspect=0.0.0.0:9229 --exec "node -r ts-node/register" src/index.ts
  3. デバッグセッション開始のタイミング: アプリケーションの起動タイミングとデバッガーの接続タイミングに注意してください。以下のような方法が有効です:

    • --inspect-brkフラグを使用して、デバッガーが接続するまで実行を一時停止
    • スクリプトの先頭に小さな遅延を入れる:
    setTimeout(() => {
      console.log('デバッガー接続のための遅延');
    }, 2000);
  4. エラーメッセージの確認: デバッグセッションで何か問題が発生した場合は、VSCodeのデバッグコンソールとターミナル出力を確認して、エラーメッセージを調査してください。

これらの設定を正しく行うことで、Dockerコンテナ内のNode.jsアプリケーションでもVSCodeを使用して効率的にデバッグできるようになります。次のセクションでは、より効率的なデバッグのためのDockerfileとdocker-compose.ymlの最適化方法について説明します。

Dockerfileとdocker-compose.ymlの最適化方法

Dockerコンテナ内でのNode.jsアプリケーションのデバッグ体験を向上させるには、Dockerfileとdocker-compose.ymlファイルの適切な設定が不可欠です。ここでは、ソースマップデバッグを効率的に行うための最適化方法を説明します。

デバッグ用Dockerfile

デバッグに最適化されたDockerfileの例を紹介します:

FROM node:18-alpine

# デバッグ用の作業ディレクトリを設定
WORKDIR /app

# 依存関係のファイルをコピー
COPY package.json package-lock.json ./

# 開発用依存関係を含めてすべてインストール
RUN npm ci

# TypeScriptやnodemonなどのグローバルツールをインストール(オプション)
RUN npm install -g typescript ts-node nodemon

# ソースコードはボリューム・マウントで提供するため、
# この時点ではコピーしない(開発用)

# デバッグポートを公開
EXPOSE 3000 9229

# デバッグモードでアプリを起動
CMD ["node", "--inspect=0.0.0.0:9229", "-r", "ts-node/register", "src/index.ts"]

このDockerfileのポイント:

  • --inspect=0.0.0.0:9229 フラグを使って、コンテナ外からのデバッガー接続を許可
  • -r ts-node/register を使って、TypeScriptファイルを直接実行可能に
  • ソースコードをイメージ内にコピーせず、ボリューム・マウントを前提とした設計

開発用docker-compose.yml

デバッグ用に最適化されたdocker-compose.ymlファイルの例:

version: '3.8'

services:
  node-app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      # ソースコードを直接マウント(ホットリロード対応)
      - ./:/app
      # node_modulesは除外(パフォーマンス改善)
      - /app/node_modules
    ports:
      # アプリケーションポート
      - "3000:3000"
      # デバッグポート
      - "9229:9229"
    environment:
      - NODE_ENV=development
      # ソースマップサポートを有効化
      - NODE_OPTIONS=--enable-source-maps
    # コンテナ内のファイル変更検知を有効化(ホットリロード向け)
    environment:
      - CHOKIDAR_USEPOLLING=true
    # デバッガー接続を待機するフラグを追加可能(必要に応じて)
    command: node --inspect-brk=0.0.0.0:9229 -r ts-node/register src/index.ts

このdocker-compose.ymlのポイント:

  • ホストのソースコードを直接コンテナにマウント(リアルタイム編集対応)
  • node_modulesディレクトリは除外(パフォーマンス向上)
  • デバッグポート(9229)を公開
  • 環境変数による設定(NODE_OPTIONS, CHOKIDAR_USEPOLLINGなど)

ボリュームマウントの最適化

ボリュームマウントに関する一般的な問題と解決方法:

  1. node_modulesのマウント問題

    volumes:
      - ./:/app
      # node_modulesを除外して、コンテナ内のものを使用
      - /app/node_modules

    これにより、ホストとコンテナのnode_modulesの衝突を防ぎます。

  2. パーミッション問題

    # Dockerfile内でユーザー設定
    RUN addgroup -g 1000 node_user && \
        adduser -u 1000 -G node_user -s /bin/sh -D node_user
    
    # 適切なパーミッション設定
    RUN chown -R node_user:node_user /app
    
    # 非rootユーザーに切り替え
    USER node_user
  3. SELinuxの問題(主にRedHat系のシステム):

    # ホストでの実行
    docker run --volume=$(pwd):/app:z ...

    :zオプションでSELinuxコンテキストを適切に設定できます。

マルチステージビルド用Dockerfile

本番環境用のマルチステージビルドDockerfileの例:

# ビルドステージ
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# TypeScriptをコンパイル(ソースマップを生成)
RUN npm run build

# 実行ステージ
FROM node:18-alpine

WORKDIR /app

# 本番用依存関係のみインストール
COPY package*.json ./
RUN npm ci --omit=dev

# ビルドステージからビルド済みファイルとソースマップをコピー
COPY --from=builder /app/dist /app/dist
# ソースファイルもコピー(ソースマップが参照するため)
COPY --from=builder /app/src /app/src

# ソースマップサポートを有効化
ENV NODE_OPTIONS=--enable-source-maps

EXPOSE 3000

CMD ["node", "dist/index.js"]

このマルチステージビルドのポイント:

  • ビルドステージでTypeScriptコンパイルとソースマップ生成
  • 実行ステージでは必要なファイルのみを含む軽量イメージ
  • ソースマップのために元のソースファイルもコピー

環境別設定の分離

開発環境と本番環境で設定を分離する方法:

# docker-compose.yml(共通設定)
version: '3.8'

services:
  node-app:
    build:
      context: .
    ports:
      - "3000:3000"

# docker-compose.dev.yml(開発環境用)
version: '3.8'

services:
  node-app:
    build:
      dockerfile: Dockerfile.dev
    volumes:
      - ./:/app
      - /app/node_modules
    ports:
      - "9229:9229"
    environment:
      - NODE_ENV=development
      - NODE_OPTIONS=--enable-source-maps
    command: nodemon --inspect=0.0.0.0:9229 src/index.ts

# docker-compose.prod.yml(本番環境用)
version: '3.8'

services:
  node-app:
    build:
      dockerfile: Dockerfile.prod
    environment:
      - NODE_ENV=production

使用例:

# 開発環境
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

# 本番環境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

デバッグ環境の自動検出

package.jsonスクリプトを使用して環境を自動的に検出・起動する例:

{
  "scripts": {
    "start": "node dist/index.js",
    "start:dev": "nodemon --inspect=0.0.0.0:9229 src/index.ts",
    "docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up",
    "docker:debug": "docker-compose -f docker-compose.yml -f docker-compose.debug.yml up",
    "docker:prod": "docker-compose -f docker-compose.yml -f docker-compose.prod.yml up"
  }
}

このように、Dockerfileとdocker-compose.ymlを適切に設定することで、Dockerコンテナ内でのNode.jsアプリケーションのデバッグ環境を大幅に改善できます。次のセクションでは、ソースマップデバッグに関する一般的な問題のトラブルシューティング方法について説明します。

トラブルシューティングと実践的なデバッグ手法

Dockerコンテナ内でNode.jsアプリケーションをデバッグする際に遭遇する一般的な問題とその解決方法について解説します。実践的なデバッグ手法を身につけることで、トラブルシューティングを効率的に行えるようになります。

ソースマップが正しく読み込まれない問題

症状: ブレークポイントが効かない、またはコンパイル後のJavaScriptコードでのみデバッグできる

解決策:

  1. ソースマップファイルの存在を確認:

    # コンテナ内で実行
    docker exec -it <container_id> find /app -name "*.map" | grep app.js.map
  2. ソースマップファイルの内容を確認:

    # コンテナ内で実行
    docker exec -it <container_id> cat /app/dist/app.js.map

    ソースマップファイルには以下のような内容が含まれているはずです:

    {
      "version": 3,
      "file": "app.js",
      "sourceRoot": "",
      "sources": ["../src/app.ts"],
      "names": [],
      "mappings": "..."
    }

    sources の値がコンテナ内の正しい相対パスを指しているか確認してください。

  3. NODE_OPTIONSの設定を確認:

    # コンテナ内で実行
    docker exec -it <container_id> printenv | grep NODE_OPTIONS

    --enable-source-maps フラグが含まれていることを確認します。

パスのマッピング問題

症状: ブレークポイントが認識されるが、デバッガが別のファイルにジャンプする

解決策:

  1. VSCodeのsourceMapPathOverrides設定を調整:

    "sourceMapPathOverrides": {
      "../src/*": "${workspaceFolder}/src/*",
      "/absolute/path/in/container/*": "${workspaceFolder}/*"
    }
  2. コンテナ内のソースパスを確認:

    // デバッグ中に実行
    console.log(__dirname);
    console.log(__filename);

    VSCodeとコンテナ内のパスの相違を確認し、マッピングを調整します。

  3. ソースマップのsourceRootを修正:

    // tsconfig.json
    {
      "compilerOptions": {
        "sourceRoot": "/app/src"
      }
    }

デバッガー接続の問題

症状: VSCodeのデバッガーがコンテナに接続できない

解決策:

  1. ポートの公開状況を確認:

    # ホストマシンで実行
    docker ps | grep 9229

    出力例: 0.0.0.0:9229->9229/tcp のように、ポートが正しくマッピングされているか確認します。

  2. インスペクタが正しく起動しているか確認:

    # コンテナログを確認
    docker logs <container_id> | grep "Debugger listening"

    インスペクタが起動していれば、Debugger listening on ws://127.0.0.1:9229/... のようなメッセージが表示されます。

  3. --inspect-brkフラグを使用:

    node --inspect-brk=0.0.0.0:9229 -r ts-node/register src/index.ts

    これにより、デバッガー接続まで実行が一時停止します。

ホットリロードとデバッグの連携問題

症状: ファイルを変更するとデバッガ接続が切れる、または変更が反映されない

解決策:

  1. nodemonの設定を最適化:

    // nodemon.json
    {
      "watch": ["src"],
      "ext": "ts,json",
      "exec": "node --inspect=0.0.0.0:9229 -r ts-node/register src/index.ts",
      "delay": "1000",
      "signal": "SIGTERM"
    }

    delayオプションにより、ファイル変更後の再起動前に短い遅延が発生します。これにより、デバッガが適切に再接続できる可能性が高まります。

  2. auto-attach設定を使用:

    VSCodeの設定で自動再接続を有効にします:

    // .vscode/settings.json
    {
      "debug.javascript.autoAttachFilter": "smart",
      "debug.javascript.terminalOptions": {
        "skipFiles": ["<node_internals>/**"]
      }
    }

TypeScript / Babel特有の問題

症状: TypeScriptやBabelでトランスパイルされたコードでデバッグ時に変数の値が正しく表示されない

解決策:

  1. inlineSourcesオプションを有効にする:

    // tsconfig.json
    {
      "compilerOptions": {
        "sourceMap": true,
        "inlineSources": true
      }
    }
  2. sourcesContentを含める:

    // webpack.config.js
    module.exports = {
      devtool: 'source-map',
      module: {
        rules: [
          {
            test: /\.ts$/,
            use: {
              loader: 'ts-loader',
              options: {
                compilerOptions: {
                  sourceMap: true,
                  inlineSources: true
                }
              }
            }
          }
        ]
      }
    };

実践的なデバッグテクニック

  1. 条件付きブレークポイント:

    VSCodeでブレークポイントを設定した後、右クリックして「条件の編集」を選択し、特定の条件でのみ停止するようにします:

    user.id === '12345' // 特定のユーザーIDの場合のみ停止
  2. ロギングブレークポイント:

    実行を停止せずにログを出力するブレークポイントを設定できます:

    // 右クリック → 「ログメッセージを編集」を選択
    "User ID: {user.id}, Name: {user.name}" // 変数の値を出力
  3. 変数ウォッチの活用:

    デバッグビューの「ウォッチ」パネルで変数の値を継続的に監視します:

    // 例えば以下のような式を監視
    user.orders.length
    user.orders.filter(o => o.total > 100)
  4. 堆積デバッグ(Postmortem Debugging):

    クラッシュ時のメモリダンプを分析することができます:

    # クラッシュ時にダンプファイルを生成
    node --inspect-brk --heapsnapshot-signal=SIGUSR2 app.js

    別のターミナルからSIGUSR2シグナルを送信してヒープスナップショットを作成:

    kill -USR2 <pid>
  5. デバッグ用のdockerコマンド:

    # コンテナ内でシェルを起動してデバッグ
    docker exec -it <container_id> sh
    
    # コンテナ内の特定のプロセスを確認
    docker exec -it <container_id> ps aux | grep node
    
    # コンテナのログをリアルタイムで監視
    docker logs -f <container_id>

これらのトラブルシューティング手法と実践的なデバッグテクニックを活用することで、Dockerコンテナ内のNode.jsアプリケーションでも効率的なデバッグが可能になります。次のセクションでは、CI/CD環境でのデバッグについて説明します。

CI/CD環境での効率的なデバッグ方法

継続的インテグレーション・継続的デリバリー(CI/CD)環境でのデバッグは、開発環境とは異なる課題があります。このセクションでは、CI/CD環境でのNode.jsアプリケーションのデバッグを効率的に行うための方法について説明します。

CI/CD環境でのソースマップ設定

CI/CD環境でのソースマップ設定には、以下のポイントを考慮する必要があります:

  1. 本番ビルドでのソースマップの扱い

    // webpack.config.js
    module.exports = {
      mode: process.env.NODE_ENV,
      devtool: process.env.NODE_ENV === 'production' 
        ? 'hidden-source-map'  // 外部からアクセスできないがエラーレポートには使用可能
        : 'source-map'         // 開発環境では完全なソースマップ
    };

    hidden-source-mapは、ソースマップファイルを生成しますが、JavaScriptファイルからの参照を含めないため、外部からアクセスできなくなります。

  2. 環境変数を使った条件分岐

    // app.ts
    if (process.env.NODE_ENV !== 'production') {
      // 開発環境のみでソースマップサポートを有効にする
      require('source-map-support').install();
    }

CI環境での自動テスト実行

CI環境でのテスト実行時にもソースマップを活用することで、失敗したテストのデバッグが容易になります:

# .github/workflows/test.yml (GitHub Actions例)
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - name: Run tests with source map support
        run: |
          NODE_OPTIONS="--enable-source-maps" npm test
      - name: Upload source maps as artifacts if tests fail
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: sourcemaps
          path: |
            dist/**/*.js.map
            src/

このワークフローでは、テストが失敗した場合にソースマップとソースファイルをアーティファクトとしてアップロードし、後からデバッグに使用できるようにします。

エラーモニタリングツールの活用

本番環境でのエラーをより効果的にデバッグするために、エラーモニタリングツールとソースマップを連携させる方法:

  1. Sentryとの連携

    // sentry.js
    const Sentry = require('@sentry/node');
    
    Sentry.init({
      dsn: process.env.SENTRY_DSN,
      environment: process.env.NODE_ENV,
      release: process.env.GIT_COMMIT_SHA,
      integrations: [
        // エラー発生時に自動的にトランザクションを終了
        new Sentry.Integrations.OnUncaughtException(),
        new Sentry.Integrations.OnUnhandledRejection(),
      ],
    });

    ソースマップをSentryにアップロードするスクリプト:

    #!/bin/bash
    # upload-sourcemaps.sh
    
    sentry-cli releases new $GIT_COMMIT_SHA
    sentry-cli releases files $GIT_COMMIT_SHA upload-sourcemaps ./dist --url-prefix '~/dist'
    sentry-cli releases finalize $GIT_COMMIT_SHA
  2. CI/CDパイプラインでのソースマップのアップロード

    # GitLab CI/CD設定例
    deploy:
      stage: deploy
      script:
        - npm ci
        - npm run build
        - ./upload-sourcemaps.sh
        - npm start
      only:
        - main

デバッグビルド用のCI/CDジョブ

デバッグ用のビルドを自動的に生成する専用のCI/CDジョブを設定することも有効です:

# CircleCI設定例
version: 2.1
jobs:
  build-debug:
    docker:
      - image: node:18
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: npm ci
      - run:
          name: Build with full source maps
          command: |
            NODE_ENV=development npm run build
      - persist_to_workspace:
          root: .
          paths:
            - dist
            - src
            - node_modules
  
  deploy-debug:
    docker:
      - image: node:18
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Deploy debug version
          command: |
            # デバッグビルドを特別な環境(ステージングなど)にデプロイ
            NODE_OPTIONS="--enable-source-maps" npm run deploy:debug

Dockerfileの条件分岐

CI/CD環境で使用するDockerfileに条件分岐を導入し、環境に応じた設定を適用します:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 環境変数ARGを使用して条件分岐
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

# 開発/テスト環境の場合はソースマップサポートを有効にする
RUN if [ "$NODE_ENV" != "production" ]; then \
      echo "Enabling source maps for debugging" && \
      npm install --no-save source-map-support && \
      echo "require('source-map-support').install();" > src/source-map-install.js; \
    fi

# デバッグ用の起動コマンド
CMD if [ "$NODE_ENV" != "production" ]; then \
      node --enable-source-maps -r ./src/source-map-install.js dist/index.js; \
    else \
      node dist/index.js; \
    fi

このDockerfileを使用してイメージをビルドする際に、必要に応じてNODE_ENV引数を指定します:

# デバッグ用ビルド
docker build --build-arg NODE_ENV=development -t my-app:debug .

# 本番用ビルド
docker build -t my-app:production .

CI/CDでのリモートデバッグの設定

CI/CDパイプラインでのリモートデバッグを可能にする設定例:

# GitHub Actions例
jobs:
  debug-session:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Start debug container
        run: |
          docker run -d --name debug-container -p 9229:9229 -e NODE_OPTIONS="--inspect=0.0.0.0:9229" my-app:debug
      - name: Setup tmate session (for debugging)
        uses: mxschmitt/action-tmate@v3
        with:
          limit-access-to-actor: true

このアクションは、失敗したワークフローでtmateセッションを開始し、リモートからSSHでアクセスしてデバッグを行うことができます。

デバッグログの強化

CI/CD環境でのデバッグを容易にするためのログ設定:

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    process.env.NODE_ENV === 'production'
      ? winston.format.json()
      : winston.format.prettyPrint()
  ),
  defaultMeta: { service: 'user-service' },
  transports: [
    new winston.transports.Console(),
    // CI/CDでのデバッグ用に特別なログファイルを追加
    ...(process.env.CI
      ? [new winston.transports.File({ filename: '/var/log/app-debug.log', level: 'debug' })]
      : [])
  ]
});

// エラー発生時にスタックトレースとソースの位置情報を詳細にログ
logger.exceptions.handle(
  new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.prettyPrint()
    )
  })
);

module.exports = logger;

これらの方法を組み合わせることで、CI/CD環境でもDockerコンテナ内のNode.jsアプリケーションを効率的にデバッグすることができます。本番環境での問題が発生した場合でも、ソースマップを活用して迅速に根本原因を特定し、解決することができるようになります。

TH

Tasuke Hub管理人

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

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

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

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

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

おすすめ記事

おすすめコンテンツ