现已发布!阅读有关 11 月的新功能和修复的信息。

在容器中调试 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+) 捕获组,以及使用 \ 作为 \d 字符类中反斜杠的 JSON 转义字符。

配置应用程序入口页面

默认情况下,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": "https://127.0.0.1:%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"
      }
    }
  ]
}

故障排除

由于缺少 node_modules,Docker 镜像构建或启动失败

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"]