尝试以扩展 VS Code 中的代理模式!

在容器中调试 Node.js

将 Docker 文件添加到 Node.js 项目时,会添加任务和启动配置以在容器内调试该应用程序。然而,由于 Node.js 庞大的生态系统,这些任务无法适应所有的应用程序框架或库,这意味着某些应用程序将需要额外的配置。

配置容器入口点

容器工具扩展通过 package.json 的属性推断容器的入口点——即在容器内以调试模式启动应用程序的命令行。该扩展首先在 scripts 对象中查找 start 脚本;如果找到,并且它以 nodenodejs 命令开头,则使用它来构建以调试模式启动应用程序的命令行。如果未找到,或者不是一个可识别的 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 对象进行配置。

此功能取决于应用程序的几个方面

  • 应用程序必须将日志输出到调试控制台。
  • 应用程序必须记录“server ready”消息。
  • 应用程序必须提供可浏览的页面。

虽然默认设置可能适用于基于 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 连接。如果应用程序记录了不同的消息,则应将调试启动配置(在 launch.json 中)的 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+) 捕获组,以及 \ 作为 JSON 转义字符用于 \d 字符类中的反斜杠。

配置应用程序入口页面

默认情况下,容器工具扩展将打开浏览器的“主”页面(具体如何确定取决于应用程序)。如果浏览器应打开到特定页面,则应将调试启动配置(在 launch.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 对象的 localRootremoteRoot 属性中的一个或两个,应分别设置为工作区和容器内的根源位置。

例如,如果应用程序位于 /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"]