🚀 在 VS Code 中

在容器中偵錯 Node.js

當將 Docker 檔案新增至 Node.js 專案時,會新增工作和啟動設定,以啟用在 Docker 容器中偵錯該應用程式。然而,由於 Node.js 周圍的龐大生態系統,這些工作無法適用於每個應用程式框架或程式庫,這表示某些應用程式將需要額外的設定。

設定 Docker 容器進入點

Docker 擴充功能會透過 package.json 的屬性推斷 Docker 容器的進入點,也就是在 Docker 容器中以偵錯模式啟動應用程式的命令列。擴充功能首先在 scripts 物件中尋找 start 指令碼;如果找到且以 nodenodejs 命令開頭,則會使用它來建構以偵錯模式啟動應用程式的命令列。如果找不到,或者如果不是可辨識的 node 命令,則會使用 package.json 中的 main 屬性。如果兩者都找不到或無法辨識,則您需要明確設定用於啟動 Docker 容器的 docker-run 工作的 dockerRun.command 屬性。

某些 Node.js 應用程式框架包含用於管理應用程式的 CLI,並用於在 start 指令碼中啟動應用程式,這會模糊化底層的 node 命令。在這些情況下,Docker 擴充功能無法推斷啟動命令,您必須明確設定啟動命令。

範例:設定 Nest.js 應用程式的進入點

{
  "tasks": [
    {
      "type": "docker-run",
      "label": "docker-run: debug",
      "dependsOn": ["docker-build"],
      "dockerRun": {
        "command": "nest start --debug 0.0.0.0:9229"
      },
      "node": {
        "enableDebugging": true
      }
    }
  ]
}

範例:設定 Meteor 應用程式的進入點

{
  "tasks": [
    {
      "type": "docker-run",
      "label": "docker-run: debug",
      "dependsOn": ["docker-build"],
      "dockerRun": {
        "command": "node --inspect=0.0.0.0:9229 main.js"
      },
      "node": {
        "enableDebugging": true
      }
    }
  ]
}

自動啟動瀏覽器至應用程式的入口頁面

Docker 擴充功能可以在應用程式於偵錯工具中啟動後,自動啟動瀏覽器至應用程式的進入點。此功能預設為啟用,並透過 launch.json 中偵錯設定的 dockerServerReadyAction 物件進行設定。

此功能取決於應用程式的幾個方面:

  • 應用程式必須將記錄輸出到偵錯主控台。
  • 應用程式必須記錄「伺服器已就緒」訊息。
  • 應用程式必須提供可瀏覽的頁面。

雖然預設設定可能適用於基於 Express.js 的應用程式,但其他 Node.js 框架可能需要明確設定其中一個或多個方面。

確保應用程式記錄寫入偵錯主控台

此功能取決於應用程式將其記錄寫入附加偵錯工具的偵錯主控台。然而,並非所有記錄框架都會寫入偵錯主控台,即使已設定為使用基於主控台的記錄器(因為某些「主控台」記錄器實際上會繞過主控台並直接寫入 stdout)。

解決方案因記錄框架而異,但通常需要建立/新增一個*實際*寫入主控台的記錄器。

範例:設定 Express 應用程式寫入偵錯主控台

預設情況下,Express.js 使用 debug 記錄模組,它可以繞過主控台。這可以透過將記錄函數明確繫結到主控台的 debug() 方法來解決。

var app = require('../app');
var debug = require('debug')('my-express-app:server');
var http = require('http');

// Force logging to the debug console.
debug.log = console.debug.bind(console);

另請注意,debug 記錄器僅在透過 DEBUG 環境變數啟用時才會寫入記錄,該變數可以在 docker-run 工作中設定。(預設情況下,當 Docker 檔案新增至應用程式時,此環境變數會設定為 `*`。)

{
  "tasks": [
    {
      "type": "docker-run",
      "label": "docker-run: debug",
      "dependsOn": ["docker-build"],
      "dockerRun": {
        "env": {
          "DEBUG": "*"
        }
      },
      "node": {
        "enableDebugging": true
      }
    }
  ]
}

設定應用程式何時「就緒」

當應用程式將 Listening on port <number> 格式的訊息寫入偵錯主控台時,擴充功能會判斷應用程式已「就緒」可接收 HTTP 連線,Express.js 預設就是這樣做。如果應用程式記錄不同的訊息,則您應該將偵錯啟動設定的 dockerServerReadyAction 物件的 pattern 屬性設定為符合該訊息的 JavaScript 正則表達式。正則表達式應包含一個捕獲群組,對應於應用程式正在監聽的埠。

例如,假設應用程式記錄以下訊息

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
  debug('Application has started on ' + bind);
}

偵錯啟動設定(在 launch.json 中)中對應的 pattern

{
  "configurations": [
    {
      "name": "Docker Node.js Launch",
      "type": "docker",
      "request": "launch",
      "preLaunchTask": "docker-run: debug",
      "platform": "node",
      "dockerServerReadyAction": {
        "pattern": "Application has started on port (\\d+)"
      }
    }
  ]
}

請注意埠號的 (\\d+) 捕獲群組,以及使用 \ 作為 JSON 逸出字元來表示 \d 字元類別中的反斜線。

設定應用程式入口頁面

預設情況下,Docker 擴充功能將開啟瀏覽器的「主要」頁面(無論應用程式如何判斷)。如果瀏覽器應開啟至特定頁面,則應將偵錯啟動設定的 dockerServerReadyAction 物件的 uriFormat 屬性設定為 Node.js 格式字串,其中一個字串權杖指示應替換埠的位置。

偵錯啟動設定(在 launch.json 中)中開啟 about.html 頁面而不是主要頁面的對應 uriFormat 將會是

{
  "configurations": [
    {
      "name": "Docker Node.js Launch",
      "type": "docker",
      "request": "launch",
      "preLaunchTask": "docker-run: debug",
      "platform": "node",
      "dockerServerReadyAction": {
        "uriFormat": "http://localhost:%s/about.html"
      }
    }
  ]
}

將 Docker 容器原始碼檔案對應到本機工作區

預設情況下,Docker 擴充功能會假設執行中 Docker 容器中的應用程式原始碼檔案位於 /usr/src/app 資料夾中,然後偵錯工具會將這些檔案對應回已開啟工作區的根目錄,以便將中斷點從容器轉譯回 Visual Studio Code。

如果應用程式原始碼檔案位於不同的位置(例如,不同的 Node.js 框架有不同的慣例),無論是在 Docker 容器內還是已開啟的工作區內,則應將偵錯啟動設定的 node 物件的 localRootremoteRoot 屬性之一或兩者都設定為工作區和 Docker 容器內的根原始碼位置。

例如,如果應用程式改為位於 /usr/my-custom-location,則對應的 remoteRoot 屬性將會是

{
  "configurations": [
    {
      "name": "Docker Node.js Launch",
      "type": "docker",
      "request": "launch",
      "preLaunchTask": "docker-run: debug",
      "platform": "node",
      "node": {
        "remoteRoot": "/usr/my-custom-location"
      }
    }
  ]
}

疑難排解

Docker 映像因缺少 node_modules 而無法建置或啟動

Dockerfile 的排列方式通常旨在最佳化映像建置時間、映像大小或兩者兼具。然而,並非每個 Node.js 應用程式框架都支援所有典型的 Node.js Dockerfile 最佳化。特別是,對於某些框架,node_modules 資料夾必須是應用程式根資料夾的直接子資料夾,而 Docker 擴充功能會建立 Dockerfile,其中 node_modules 資料夾存在於父層或祖先層級(這通常是 Node.js 允許的)。

解決方案是從 Dockerfile 中移除該最佳化

FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
# Remove the `&& mv node_modules ../` from the RUN command:
# RUN npm install --production --silent && mv node_modules ../
RUN npm install --production --silent
COPY . .
EXPOSE 3000
RUN chown -R node /usr/src/app
USER node
CMD ["npm", "start"]