在容器中偵錯 Node.js
當將 Docker 檔案新增至 Node.js 專案時,會新增工作和啟動設定,以啟用在 Docker 容器中偵錯該應用程式。然而,由於 Node.js 周圍的龐大生態系統,這些工作無法適用於每個應用程式框架或程式庫,這表示某些應用程式將需要額外的設定。
設定 Docker 容器進入點
Docker 擴充功能會透過 package.json
的屬性推斷 Docker 容器的進入點,也就是在 Docker 容器中以偵錯模式啟動應用程式的命令列。擴充功能首先在 scripts
物件中尋找 start
指令碼;如果找到且以 node
或 nodejs
命令開頭,則會使用它來建構以偵錯模式啟動應用程式的命令列。如果找不到,或者如果不是可辨識的 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 物件的 localRoot
和 remoteRoot
屬性之一或兩者都設定為工作區和 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"]