调试运行在Docker容器中的Node.js应用程序
调试一个应用程序涉及到检测和消除代码中现有的错误,也就是所谓的bug,这些错误可能会导致它意外地工作甚至崩溃。调试允许开发人员发现并解决应用程序中的错误或缺陷,以防止不正确和不需要的操作。
在本教程中,我们将建立一个带有错误的Node.js应用程序,在Docker容器中运行,并使用Node.js的Visual Studio Code调试工具对其进行调试。本教程可供开发人员在构建其应用程序时参考。
前提条件
要跟上本教程,你需要具备以下条件。
- 对[Docker容器]和[Node.js]有初步了解。
- 安装了[Docker]。
- 安装了[Node.js]。
- 安装了[Microsoft Visual Studio Code]。
- 一个网络浏览器。我将使用[谷歌浏览器]。
第1步:创建根目录
我们将创建一个简单的待办事项列表应用程序,允许用户添加和删除任务。在对应用程序进行编码时,我们还将引入一个小错误,并使用Visual Studio Code来处理和修复这个问题。
打开一个新的终端窗口,浏览到一个根目录,并运行下面的命令。
$ mkdir TodoApp
$ cd TodoApp
第2步:初始化Node.js项目
通过执行下面的命令来初始化该项目。
$ npm init -y
上述命令将在TodoApp 文件夹中生成一个名为package.json的文件。package.json 文件包含与Node.js项目有关的信息。它还向npm提供了关于项目依赖的信息。
该文件将看起来像这样。
创建待办事项应用程序
接下来,我们将使用Express.js构建我们的待办事项应用程序:一个快速、流行和轻量级的Node.js网络框架。
Express使网络应用程序的开发更加容易。
第1步:安装项目的依赖项
我们将通过运行下面的命令来安装express 和应用程序所需的其他一些先决条件。
$ npm install express body-parser cookie-session ejs --save
第2步:为app.js文件编码
然后我们将在TodoApp 目录中用下面的代码创建一个名为app.js 的文件。
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const session = require('cookie-session')
const urlencodedParser = bodyParser.urlencoded({ extended: false })
const port = 3000
app.use(session({ secret: process.env.SECRET }))
.use(function (req, res, next) {
next()
})
.get ('/todo', function (req, res) {
res.render('todo.ejs', { todolist: req.session.todolist })
})
.post ('/todo/add/', urlencodedParser, function (req, res) {
if (req.body.newtodo != '') {
req.session.todolist.push(req.body.newtodo)
}
res.redirect('/todo')
})
.get ('/todo/delete/:id', function (req, res) {
if (req.params.id != '') {
req.session.todolist.splice(req.params.id, 1)
}
res.redirect('/todo')
})
.use (function (req, res, next) {
res.redirect('/todo')
})
.listen(port, () => console.log(`MyTodo app is listening on port ${port}!`))
第3步:创建todo.ejs文件
接下来我们将创建一个文件./views/todo.ejs ,并在其中粘贴以下代码。
<!DOCTYPE html>
<html>
<head>
<title>My todolist</title>
<style>
a {text-decoration: none; color: black;}
</style>
</head>
<body>
<h1>My todolist</h1>
<ul>
<% todolist.forEach(function(todo, index) { %>
<li><a href="/todo/delete/<%= index %>">✘</a> <%= todo %></li>
<% }); %>
</ul>
<form action="/todo/add/" method="post">
<p>
<label for="newtodo">What should I do?</label>
<input type="text" name="newtodo" id="newtodo" autofocus />
<input type="submit" />
</p>
</form>
</body>
</html>
第4步:启动网络服务器
通过执行下面的命令来启动网络服务器。
$ export SECRET=bestkeptsecret; node app.js
控制台窗口将显示一个成功信息,即服务器正在运行并监听指定端口。
创建一个Docker镜像
现在我们已经成功创建了一个Node.js应用程序。
接下来,我们将为它创建一个Docker镜像。
一个Docker容器是由一个Docker镜像构建的,它包含了用Docker部署和运行应用程序所需的必要信息。
要运行一个Docker容器,我们可以创建自己的Docker镜像,或者下载一个已经建立的Docker镜像。在我们的案例中,我们将创建我们自己的Docker镜像。一个Docker镜像是由多层组成的,是一个只读文件系统。
Docker的工作方式是,它为Docker文件中包含的每条指令创建一个层。每一个新层都驻扎在之前的层之上。
对于可能经常变化的应用程序的代码,建议将其放在文件的最后。
第1步:创建一个Dockerfile
在项目的根目录下创建一个名为Dockerfile的文件,其中包含以下几行代码。
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "npm", "run" , "start-debug" ]
让我们仔细看看我们刚刚创建的文件。
- FROM。设置基本镜像。以后要添加的所有东西都将基于这个镜像。在本教程中,我们将使用Node.js的14版。
- WORKDIR:这里设置了
COPY、RUN和CMD命令的工作目录。 - RUN:它在Docker容器中执行
npm install命令。 - COPY:它将文件复制到构建环境中的Docker镜像中。
- EXPOSE:它确保在Docker容器中运行的进程在3000端口上监听。它有助于将端口从主机转发到容器上。
- CMD:它在我们的Docker容器启动后,在容器内执行
node app.js命令。
第2步:创建一个.dockerignore文件
由于我们不希望向构建上下文发送大文件,并加快系统的速度,我们使用.dockerignore 文件。
它是一个类似于.gitignore 的文本文件,包含了不应该被添加到构建中的目录和文件的名称。文件.dockerignore ,将包含以下内容。
node_modules
npm-debug.log
第3步:构建一个Docker镜像
接下来,我们将执行docker build 命令来构建我们的Docker镜像。
$ docker build -t to-do-app .
-t 标志指定了镜像的名称。上下文的路径指向我们要从Docker文件中引用的一组文件。
正如前面所讨论的,docker build 命令为Docker文件中的每个命令添加了一个新层。
一旦命令执行成功,Docker就会删除中间的容器。
第4步:运行Docker容器
有了我们的Docker镜像,我们现在可以通过执行docker run 命令来运行它,并把下面的参数传给它。
-p是主机上的端口。在我们的例子中,我们将使用3001端口,该端口将被转发到容器的3000端口,并以 。:-e将创建一个环境变量。在我们的例子中,我们使用 ,并把它的值分配给 。SECRETbestkeptsecret-d确保容器将在后台执行。- 本例中图像的名称是:
to-do-app。
整个命令将看起来像下面这样。
$ docker run -p 3001:3000 -e SECRET=bestkeptsecret -d to-do-app
第5步:验证容器是否正在运行
我们可以用以下方法验证我们的Docker容器是否在运行。
$ docker ps
输出将类似于下图所示。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a8a8e6a13ae a94edd9b09fd "docker-entrypoint.s…" 2 weeks ago Up 49 seconds 0.0.0.0:3001->3000/tcp, :::3001->3000/tcp nostalgic_diffie
第6步:检查Docker日志
要检查日志,请执行docker logs 命令,然后输入容器的id 。
$ docker logs 9a8a8e6a13ae
输出将显示如下。
$ docker logs 9a8a8e6a13ae
MyTodo app is listening on port 3000!
第7步:在网络浏览器上测试应用程序
现在,我们的应用程序已经启动并运行。我们可以浏览到链接http://localhost:3001,我们尝试添加一个新的todo。正如我们在下面看到的,应用程序在todo.ejs 文件的行上显示一个错误:todolist.forEach(function(todo, index) 。
在下面的步骤中,我们将看看如何使用Visual Studio Code来调试上面的错误。
第8步:停止Docker容器
首先,我们要通过执行下面的命令来停止容器的运行。
$ docker kill 9a8a8e6a13ae
使用Visual Studio Code进行调试
Visual Studio Code为在Docker容器中运行的Node.js应用程序提供了调试工具。
下面是我们可以使用这些工具的步骤。
第1步:更新Docker文件
编辑Dockerfile ,替换下面这一行。
CMD ["node", "app.js"]
用。
CMD ["npm","run", "start-debug"]
我们更新后的Dockerfile ,现在看起来如下图所示。
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "npm", "run" , "start-debug" ]
第2步:编辑package.json文件
编辑package.json 文件,添加下面这行代码。
"start-debug": "node --inspect=0.0.0.0 app.js"
上面这行代码初始化了Node.js进程以监听运行在9229端口的调试客户端。
更新后的package.json 文件现在看起来会是这样。
{
"name": "TodoApp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start-debug": "node --inspect=0.0.0.0 app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cookie-session": "^1.4.0",
"ejs": "^3.1.6",
"express": "^4.17.1"
}
}
第3步:构建Docker镜像
注意,每次Dockerfile 被更新,我们都必须再次构建Docker镜像。
$ docker build -t to-do-app .
另外,请注意,Docker现在将运行我们之前更新的npm run start-debug 命令。
第4步:运行Docker容器
为了使用Visual Studio Code进行调试,我们必须转发9229端口。
这可以通过运行下面的命令来完成。
$ docker run -p 3001:3000 -p 9229:9229 -e SECRET=bestkeptsecret22222 -d to-do-app
上述命令将产生以下输出。
$ docker run -p 3001:3000 -p 9229:9229 -e SECRET=bestkeptsecret22222 -d to-do-app
f2484b712a78084b2df7e07a8b72a8d2736fd81ed75e678459a703b4812385bb
第5步:检查Docker日志
我们可以通过运行docker logs 命令并指定容器的id 来检查日志,如下图所示。
$ docker logs f2484b712a78084b2df7e07a8b72a8d2736fd81ed75e678459a703b4812385bb
> TodoApp@1.0.0 start-debug /usr/src/app
> node --inspect=0.0.0.0 app.js
Debugger listening on ws://0.0.0.0:9229/6a1580dd-ef36-4db3-b7b6-40b3f37f41d1
For help, see: https://nodejs.org/en/docs/inspector
MyTodo app is listening on port 3000!
注意,调试器现在正在监听端口9229。接下来,我们将配置Visual Studio Code来调试我们的应用程序。
使用Visual Studio Code调试应用程序时的步骤
启动Visual Studio Code,并打开项目目录,如下图。
第1步:配置Visual Studio Code
Visual Studio Code中的调试配置存储在一个名为launch.json 的文件中。要访问它,按Ctrl+Shift+P ,然后搜索Debug: Open launch.json ,并打开它。
编辑launch.json 文件,使其看起来像下面的代码片断。
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker: Attach to Node",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/usr/src/app",
"protocol": "inspector",
"skipFiles": [
"${workspaceFolder}/node_modules/**/*.js",
"<node_internals>/**/*.js"
]
}
]
}
由于我们不想翻阅node_modules 目录和内置Node.js核心模块中的代码,我们已经包含了skipFiles 属性。
第2步:放置一个断点来调试代码
现在我们已经设置好了一切,我们可以开始调试应用程序。如果你还记得前面的内容,我们在view/todo.ejs 文件中出现了一个错误,其中下面这行代码超过了todolist 数组。
todolist.forEach(function(todo, index)
在app.js 文件中,todo.ejs 文件在这一行得到了渲染res.render('todo.ejs', { todolist: req.session.todolist }) 。我们将在该行设置一个断点,并检查todolist 变量的值,如下图所示。
第3步:插入一个表达式来检查变量的值
按Shift+Ctrl+D ,切换到调试视图。
然后点击Debug and Run 按钮,如下图所示。
为了检查req.session.todolist 变量的值,我们将插入一个表达式来观察。其方法是在窗口的侧边栏上选择watch下的+ 标志,然后输入变量的名称(req.session.todolist),如下图所示。
第4步:在网络浏览器上测试该应用程序
接下来,切换到网络浏览器,刷新页面http://localhost:3001/todo,如下图所示。
Waiting for localhost 信息将出现在页面的左下方,这意味着我们创建的断点已经暂停了执行,我们可以检查req.session.todolist 变量的值。
现在移回Visual Studio Code,获得如图所示的细节。
所以req.session.todolist 变量是undefined 。是否可以修复这个bug?
第5步:修复该错误
ejb 模板经过todolist 数组,应该被放置在当前会话中。然而,我们没有初始化这个数组,所以它是未定义的。
为了修复这个错误,我们将在.use 函数中加入下面几行代码。
if (typeof (req.session.todolist) == 'undefined') {
req.session.todolist = []
}
这段代码应该添加在next 函数的正上方。
我们的.use 函数将看起来如下。
app.use(session({ secret: process.env.SECRET }))
.use(function (req, res, next) {
if (typeof (req.session.todolist) == 'undefined') {
req.session.todolist = []
}
next()
})
第6步:停止Docker容器
接下来,我们将通过执行下面的docker ps 命令来检索我们正在运行的容器的id 。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f2484b712a78 to-do-app "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:9229->9229/tcp, :::9229->9229/tcp, 0.0.0.0:3001->3000/tcp, :::3001->3000/tcp jovial_swartz
通过运行docker kill ,然后再运行其id ,停止容器,如下图。
$ docker kill f2484b712a78
f2484b712a78
第7步:重新构建Docker镜像
为了应用我们之前所做的改变,我们必须重新运行docker build ,如下图所示。
$ docker build -t to-do-app .
第8步:重新运行Docker容器
接下来,使用下面的命令运行该容器。
$ docker run -p 3001:3000 -p 9229:9229 -e SECRET=bestkeptsecret22222 -d to-do-app
第9步:重新加载网络浏览器以确认是否修复了一个错误
在浏览器上重新加载https://localhost:3001/todo页面,并检查结果,如下图所示。
总结
我们已经成功地编写了一个待办事项列表Node.js应用程序,在Docker容器中执行,并利用我们的Visual Studio Code调试工具来识别和修复这个问题。
本教程对那些想在Docker容器中构建Node.js应用程序并利用Visual Studio代码调试工具来调试代码的开发者有帮助。