Node.js Docker化部署全流程
1. 前置知识准备
在开始 Node.js 的 Docker 化部署之前,我们需要了解一些基本概念和工具。
1.1 Docker 基础
Docker 是一个开源的应用容器引擎,基于 Go 语言开发并遵从 Apache2.0 协议开源。它可以让开发者将应用程序及其依赖打包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。Docker 容器与虚拟机的区别在于,容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而虚拟机则是在硬件层面实现。
Docker 有几个核心概念:
- 镜像(Image):Docker 镜像就是一个只读的模板,例如一个镜像可以包含一个完整的 Ubuntu 操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序。镜像是创建 Docker 容器的基础。
- 容器(Container):容器是镜像运行时的实体。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。可以把容器看作是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
- 仓库(Repository):仓库是集中存放镜像文件的地方。类似于代码仓库,Docker 仓库用来保存镜像。Docker Hub 是 Docker 官方提供的公共仓库,用户可以在上面查找和分享镜像。
1.2 Node.js 基础
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。它让 JavaScript 可以在服务器端运行,打破了 JavaScript 只能在浏览器中运行的限制。Node.js 使用事件驱动、非阻塞 I/O 模型,这使得它非常轻量和高效,特别适合构建网络应用,如 Web 服务器、实时应用(如聊天应用、在线游戏)等。
Node.js 应用通常由一个或多个 JavaScript 文件组成,通过 npm
(Node Package Manager)来管理项目的依赖。npm
是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 800,000 个包(package)。我们在开发 Node.js 应用时,会在项目根目录下创建一个 package.json
文件,这个文件记录了项目的元数据和依赖关系。
2. 创建 Node.js 示例应用
在进行 Docker 化部署之前,我们先创建一个简单的 Node.js 示例应用。
2.1 创建项目目录
首先,在本地创建一个新的目录用于存放我们的 Node.js 项目,例如:
mkdir my-node-app
cd my-node-app
2.2 初始化项目
使用 npm
初始化项目,这会在项目目录下生成一个 package.json
文件,该文件用于记录项目的依赖和其他元数据。执行以下命令:
npm init -y
-y
参数表示使用默认配置初始化项目,这样我们就无需逐个回答 npm init
的交互式问题。
2.3 编写 Node.js 代码
创建一个名为 app.js
的文件,编写以下简单的 Node.js 代码,创建一个基本的 HTTP 服务器:
const http = require('http');
const hostname = '0.0.0.0';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
这段代码使用 Node.js 内置的 http
模块创建了一个 HTTP 服务器,监听在 0.0.0.0:3000
地址上,当有请求到达时,返回 “Hello, World!”。
2.4 测试应用
在项目目录下执行以下命令启动应用:
node app.js
然后在浏览器中访问 http://localhost:3000
,应该可以看到 “Hello, World!” 的输出。
3. 创建 Docker 镜像
现在我们已经有了一个 Node.js 应用,接下来我们要为它创建一个 Docker 镜像。
3.1 创建 Dockerfile
在项目根目录下创建一个名为 Dockerfile
的文件(注意没有文件扩展名)。Dockerfile
是一个文本文件,包含了一条条的指令,用于构建 Docker 镜像。以下是一个基本的 Dockerfile
示例:
# 使用官方的 Node.js 基础镜像
FROM node:14
# 设置工作目录
WORKDIR /app
# 将 package.json 和 package - lock.json 复制到工作目录
COPY package*.json./
# 安装项目依赖
RUN npm install
# 将项目的所有文件复制到工作目录
COPY. /app
# 暴露应用运行的端口
EXPOSE 3000
# 定义容器启动时执行的命令
CMD [ "node", "app.js" ]
下面对 Dockerfile
中的指令进行详细解释:
- FROM node:14:指定基础镜像,这里使用官方的 Node.js 14 镜像。这个镜像已经安装好了 Node.js 和 npm,我们可以在此基础上构建我们的应用镜像。
- WORKDIR /app:设置容器内的工作目录为
/app
。后续的指令(如COPY
、RUN
等)如果没有指定绝对路径,都会在这个工作目录下执行。 - *COPY package.json./**:将本地项目中的
package.json
和package - lock.json
文件复制到容器的工作目录中。这样做是为了先安装依赖,因为如果项目文件有更新,但依赖没有改变,就可以复用之前安装依赖时的缓存,加快镜像构建速度。 - RUN npm install:在容器内执行
npm install
命令,安装项目所需的依赖。这些依赖会被安装到容器的工作目录下的node_modules
文件夹中。 - COPY. /app:将本地项目的所有文件复制到容器的工作目录
/app
中。这一步会覆盖之前复制的package.json
和package - lock.json
文件,但由于依赖已经安装,不会影响安装过程。 - EXPOSE 3000:声明容器内应用会监听 3000 端口。这只是一个声明,在运行容器时还需要通过
-p
参数将容器端口映射到主机端口。 - CMD [ "node", "app.js" ]:指定容器启动时要执行的命令,这里是运行我们的
app.js
文件。
3.2 构建 Docker 镜像
在项目根目录下,执行以下命令构建 Docker 镜像:
docker build -t my - node - app:v1.0.0.
这里 -t
参数用于指定镜像的标签(tag),格式为 镜像名:版本号
。最后的 .
表示当前目录,即 Dockerfile
所在的目录。Docker 会根据 Dockerfile
中的指令逐步构建镜像,构建过程中会输出详细的日志信息。如果构建成功,你可以通过以下命令查看本地的镜像列表:
docker images
你应该能看到名为 my - node - app:v1.0.0
的镜像。
4. 运行 Docker 容器
现在我们已经构建好了 Docker 镜像,接下来可以运行容器了。
4.1 简单运行容器
执行以下命令运行容器:
docker run -d -p 3000:3000 my - node - app:v1.0.0
这里 -d
参数表示以守护进程(detached)模式运行容器,容器会在后台运行。-p 3000:3000
表示将主机的 3000 端口映射到容器的 3000 端口,这样我们就可以通过主机的 http://localhost:3000
访问容器内运行的应用。my - node - app:v1.0.0
是要运行的镜像名称和版本号。
运行容器后,你可以再次在浏览器中访问 http://localhost:3000
,应该能看到 “Hello, World!” 的输出,说明应用在容器内正常运行。
4.2 查看容器日志
如果应用在容器内运行出现问题,我们可以查看容器的日志来排查错误。执行以下命令查看容器日志:
docker logs <容器 ID 或容器名称>
例如,如果容器名称为 my - node - app - container
,则执行:
docker logs my - node - app - container
日志中会包含应用启动时的输出信息以及运行过程中的错误信息(如果有)。
4.3 进入容器
有时候我们需要进入正在运行的容器内部,查看文件系统、执行命令等。可以使用以下命令进入容器:
docker exec -it <容器 ID 或容器名称> sh
这里 -it
参数表示以交互模式(interactive)和终端模式(tty)进入容器,sh
是容器内的 shell 程序。进入容器后,你可以在容器的文件系统中导航,查看和修改文件,执行命令等。例如,你可以进入工作目录 /app
,查看项目文件:
cd /app
ls
完成操作后,输入 exit
命令退出容器。
5. 多阶段构建优化镜像
随着项目的依赖增多,生成的镜像可能会变得很大。为了减小镜像体积,我们可以使用 Docker 的多阶段构建。
5.1 修改 Dockerfile 进行多阶段构建
# 第一阶段:构建阶段
FROM node:14 as build - stage
WORKDIR /app
COPY package*.json./
RUN npm install
COPY. /app
RUN npm run build
# 第二阶段:运行阶段
FROM node:14 - slim
WORKDIR /app
COPY --from = build - stage /app/dist./
EXPOSE 3000
CMD [ "node", "dist/app.js" ]
在这个 Dockerfile
中,我们定义了两个阶段:
- 构建阶段:使用
node:14
镜像作为基础,安装依赖并执行构建命令(假设项目中有npm run build
命令用于生成生产环境代码)。这个阶段会生成一个包含所有依赖和构建结果的镜像。 - 运行阶段:使用
node:14 - slim
镜像作为基础,node:14 - slim
是一个精简版的 Node.js 镜像,体积更小。通过COPY --from = build - stage /app/dist./
命令将构建阶段生成的dist
目录(假设构建结果在dist
目录)复制到运行阶段的镜像中。这样运行阶段的镜像只包含运行应用所需的代码,不包含开发依赖,大大减小了镜像体积。
5.2 构建多阶段镜像
执行与之前相同的构建命令:
docker build -t my - node - app:v1.0.1.
构建完成后,通过 docker images
查看镜像大小,会发现 my - node - app:v1.0.1
的体积比之前单阶段构建的镜像要小很多。
6. 部署到 Docker 仓库
将 Docker 镜像部署到 Docker 仓库可以方便在不同环境中共享和使用镜像。这里我们以 Docker Hub 为例。
6.1 注册 Docker Hub 账号
如果还没有 Docker Hub 账号,需要先在 Docker Hub 官网 注册一个账号。
6.2 登录 Docker Hub
在本地终端执行以下命令登录 Docker Hub:
docker login
按照提示输入 Docker Hub 的用户名和密码即可登录。
6.3 标记镜像
在推送镜像到 Docker Hub 之前,需要给镜像标记正确的名称,格式为 用户名/镜像名:版本号
。例如,如果你的 Docker Hub 用户名是 yourusername
,执行以下命令标记镜像:
docker tag my - node - app:v1.0.1 yourusername/my - node - app:v1.0.1
6.4 推送镜像
执行以下命令将标记后的镜像推送到 Docker Hub:
docker push yourusername/my - node - app:v1.0.1
推送过程中会显示上传进度,完成后,你可以在 Docker Hub 上看到上传的镜像。
7. 在生产环境部署
在生产环境中,我们通常会使用容器编排工具来管理多个容器,如 Docker Compose 或 Kubernetes。这里以 Docker Compose 为例进行简单介绍。
7.1 安装 Docker Compose
Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。在不同操作系统上安装 Docker Compose 的方法略有不同:
- Linux:可以通过官方脚本安装,执行以下命令:
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker - compose - $(uname - s)-$(uname - m)" -o /usr/local/bin/docker - compose
sudo chmod +x /usr/local/bin/docker - compose
- MacOS:可以使用 Homebrew 安装,执行以下命令:
brew install docker - compose
- Windows:可以从 Docker 官网下载安装包进行安装,安装包地址:https://docs.docker.com/compose/install/windows/
7.2 创建 docker - compose.yml 文件
在项目根目录下创建一个名为 docker - compose.yml
的文件,内容如下:
version: '3'
services:
my - node - app:
image: yourusername/my - node - app:v1.0.1
ports:
- 3000:3000
这里 version
指定了 Docker Compose 文件的版本,services
下定义了一个名为 my - node - app
的服务,使用我们之前推送到 Docker Hub 的镜像 yourusername/my - node - app:v1.0.1
,并将主机的 3000 端口映射到容器的 3000 端口。
7.3 启动应用
在项目根目录下执行以下命令启动应用:
docker - compose up - d
-d
参数表示以守护进程模式运行容器。执行该命令后,Docker Compose 会根据 docker - compose.yml
文件的配置拉取镜像并启动容器。你可以通过以下命令查看容器状态:
docker - compose ps
通过浏览器访问 http://localhost:3000
,应该能看到应用正常运行。
8. 故障排查与常见问题解决
在 Node.js Docker 化部署过程中,可能会遇到一些问题,下面是一些常见问题及解决方法。
8.1 镜像构建失败
- 问题表现:执行
docker build
命令时,构建过程中出现错误,导致镜像构建失败。 - 可能原因:
- 网络问题:在安装依赖(
RUN npm install
)时,可能由于网络不稳定或无法访问 npm 仓库导致失败。可以尝试切换网络或配置 npm 代理。例如,如果你在公司网络环境下,可以设置 npm 代理:
- 网络问题:在安装依赖(
npm config set proxy http://proxy.example.com:8080
npm config set https - proxy http://proxy.example.com:8080
- **依赖冲突**:项目的依赖可能存在版本冲突。可以尝试在本地先解决依赖冲突,例如通过 `npm install` 或 `yarn install` 命令在本地安装依赖,确保没有错误,然后再构建镜像。
- **Dockerfile 语法错误**:仔细检查 `Dockerfile` 中的指令是否正确,例如指令的拼写、参数的使用等。
8.2 容器启动失败
- 问题表现:执行
docker run
或docker - compose up
命令后,容器无法正常启动。 - 可能原因:
- 端口冲突:主机上指定映射的端口已经被其他进程占用。可以通过
lsof -i :端口号
命令查看哪个进程占用了端口,然后停止该进程或更换容器映射的端口。 - 镜像问题:可能镜像构建不完整或镜像本身存在问题。可以尝试重新构建镜像,并检查镜像构建日志。也可以通过
docker run --rm -it 镜像名 sh
命令进入镜像内部,检查应用的文件和依赖是否正常。 - 应用代码问题:应用代码本身可能存在错误,导致无法启动。可以通过
docker logs
命令查看容器日志,分析错误原因。例如,如果应用是因为缺少某个环境变量而无法启动,可以在docker run
命令中通过-e
参数设置环境变量,或在docker - compose.yml
文件中通过environment
字段设置环境变量。
- 端口冲突:主机上指定映射的端口已经被其他进程占用。可以通过
8.3 应用无法访问
- 问题表现:容器启动正常,但通过浏览器或其他客户端无法访问应用。
- 可能原因:
- 网络配置问题:检查容器和主机之间的网络配置是否正确。例如,在使用 Docker Compose 时,确保
docker - compose.yml
文件中的网络配置正确。如果容器需要访问外部网络,确保主机的防火墙没有阻止容器的网络请求。 - 应用监听地址问题:检查 Node.js 应用的监听地址是否正确。在前面的示例中,我们设置为
0.0.0.0
,表示监听所有网络接口。如果应用只监听了localhost
,则只能在容器内部访问,无法从主机外部访问。可以修改应用代码中的监听地址为0.0.0.0
。
- 网络配置问题:检查容器和主机之间的网络配置是否正确。例如,在使用 Docker Compose 时,确保
通过以上步骤和问题解决方法,你应该能够顺利地将 Node.js 应用进行 Docker 化部署,并在生产环境中稳定运行。在实际应用中,还需要根据具体的业务需求和环境进行更多的优化和配置。