在容器中除錯 Node.js
當將 Docker 檔案新增到 Node.js 專案時,會新增任務和啟動配置,以實現在容器中除錯該應用程式。然而,由於 Node.js 生態系統龐大,這些任務無法適應所有應用程式框架或庫,這意味著某些應用程式將需要額外的配置。
配置容器入口點
容器工具擴充套件透過 package.json
的屬性推斷容器的入口點——即在容器中以除錯模式啟動應用程式的命令列。擴充套件首先在 scripts
物件中查詢 start
指令碼;如果找到,並且它以 node
或 nodejs
命令開頭,則使用它來構建以除錯模式啟動應用程式的命令列。如果未找到,或者不是可識別的 node
命令,則使用 package.json
中的 main
屬性。如果兩者都未找到或未識別,則需要顯式設定用於啟動容器的 docker-run
任務的 dockerRun.command
屬性。
一些 Node.js 應用程式框架包含用於管理應用程式的 CLI,並用於在 start
指令碼中啟動應用程式,這會模糊底層的 node
命令。在這種情況下,容器工具擴充套件無法推斷啟動命令,您必須顯式配置啟動命令。
示例:為 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
}
}
]
}
自動將瀏覽器啟動到應用程式的入口頁面
容器工具擴充套件可以在應用程式在偵錯程式中啟動後自動將瀏覽器啟動到應用程式的入口點。此功能預設啟用,並透過 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>
的訊息時(Express.js 預設如此),擴充套件會確定應用程式已“就緒”以接收 HTTP 連線。如果應用程式記錄了不同的訊息,則應將除錯啟動配置中 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": "Containers: Node.js Launch",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"platform": "node",
"dockerServerReadyAction": {
"pattern": "Application has started on port (\\d+)"
}
}
]
}
請注意埠號的
(\\d+)
捕獲組,以及在\d
字元類中將\
用作 JSON 跳脫字元。
配置應用程式入口頁面
預設情況下,容器工具擴充套件將開啟瀏覽器的“主”頁(無論應用程式如何確定)。如果瀏覽器應開啟到特定頁面,則應將除錯啟動配置中 dockerServerReadyAction 物件的 uriFormat
屬性設定為 Node.js 格式字串,其中包含一個字串令牌,指示應替換埠的位置。
除錯啟動配置(在 launch.json
中)中對應的 uriFormat
,用於開啟 about.html
頁面而不是主頁面,將是
{
"configurations": [
{
"name": "Containers: Node.js Launch",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"platform": "node",
"dockerServerReadyAction": {
"uriFormat": "https://:%s/about.html"
}
}
]
}
將容器原始檔對映到本地工作區
預設情況下,容器工具擴充套件假定正在執行的容器中的應用程式原始檔位於 /usr/src/app
資料夾中,然後偵錯程式將這些檔案映射回開啟的工作區的根目錄,以便將斷點從容器轉換回 Visual Studio Code。
如果應用程式原始檔位於不同的位置(例如,不同的 Node.js 框架有不同的約定),無論是在容器內還是在開啟的工作區內,則應將除錯啟動配置中 node 物件的 localRoot
和 remoteRoot
屬性之一或兩者都設定為工作區和容器內的根源位置。
例如,如果應用程式位於 /usr/my-custom-location
,則相應的 remoteRoot
屬性將是
{
"configurations": [
{
"name": "Containers: Node.js Launch",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"platform": "node",
"node": {
"remoteRoot": "/usr/my-custom-location"
}
}
]
}
故障排除
由於缺少 node_modules,容器映象無法構建或啟動
Dockerfile 通常以最佳化映象構建時間、映象大小或兩者的方式進行組織。然而,並非所有 Node.js 應用程式框架都支援所有典型的 Node.js Dockerfile 最佳化。特別是,對於某些框架,node_modules
資料夾必須是應用程式根資料夾的直接子資料夾,而容器工具擴充套件構建的 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"]