从零掌握:用 Docker 和 Docker Compose 部署 Node.js 微服务

从零掌握:用 Docker 和 Docker Compose 部署 Node.js 微服务

引言:为何选择容器化部署?

在微服务架构中,Node.js 以其轻量级、高并发和非阻塞 I/O 的特性成为热门选择。然而,传统部署方式(如直接在物理机或虚拟机上运行)面临环境不一致、依赖冲突、扩展困难等问题。容器化技术(如 Docker)通过将应用及其依赖打包为独立单元,解决了这些问题:

  • 环境一致性:开发、测试、生产环境完全一致。
  • 资源隔离:每个微服务独立运行,避免依赖冲突。
  • 快速扩展:通过水平扩展应对流量高峰。
  • 简化运维:结合 Docker Compose 定义多服务依赖关系。

本文将通过一个完整的 Node.js 微服务示例,演示如何使用 Docker 和 Docker Compose 实现从开发到部署的全流程。

一、Docker 基础:构建 Node.js 微服务的容器镜像

1.1 编写 Node.js 微服务代码

假设我们有一个简单的 RESTful API 服务,使用 Express 框架实现:

  1. // app.js
  2. const express = require('express');
  3. const app = express();
  4. const PORT = 3000;
  5. app.get('/', (req, res) => {
  6. res.send('Hello from Node.js Microservice!');
  7. });
  8. app.listen(PORT, () => {
  9. console.log(`Server running on port ${PORT}`);
  10. });

依赖文件 package.json

  1. {
  2. "name": "node-microservice",
  3. "version": "1.0.0",
  4. "main": "app.js",
  5. "scripts": {
  6. "start": "node app.js"
  7. },
  8. "dependencies": {
  9. "express": "^4.18.2"
  10. }
  11. }

1.2 创建 Dockerfile

Dockerfile 是构建镜像的脚本,定义如何将代码打包为容器镜像:

  1. # 使用官方 Node.js 镜像作为基础
  2. FROM node:18-alpine
  3. # 设置工作目录
  4. WORKDIR /usr/src/app
  5. # 复制 package.json 和 package-lock.json(如果有)
  6. COPY package*.json ./
  7. # 安装依赖
  8. RUN npm install
  9. # 复制应用代码
  10. COPY . .
  11. # 暴露端口
  12. EXPOSE 3000
  13. # 定义启动命令
  14. CMD ["npm", "start"]

关键点解释

  • FROM node:18-alpine:使用轻量级的 Alpine Linux 基础镜像,减少镜像体积。
  • WORKDIR:设置容器内的工作目录,避免使用绝对路径。
  • COPY:分步复制文件,利用 Docker 缓存机制加速构建(依赖变化时只需重新运行 npm install)。
  • EXPOSE:声明容器监听的端口(非必须,但便于文档化)。
  • CMD:指定容器启动时运行的命令。

1.3 构建 Docker 镜像

在项目根目录执行以下命令:

  1. docker build -t node-microservice .
  • -t:指定镜像名称和标签(如 node-microservice:latest)。
  • .:表示使用当前目录的 Dockerfile。

构建完成后,可通过 docker images 查看镜像列表。

1.4 运行容器

启动容器并映射端口:

  1. docker run -p 4000:3000 -d node-microservice
  • -p 4000:3000:将宿主机的 4000 端口映射到容器的 3000 端口。
  • -d:后台运行容器。

访问 http://localhost:4000,应看到 Hello from Node.js Microservice!

二、Docker Compose:管理多服务依赖

实际项目中,微服务通常依赖数据库、缓存等其他服务。Docker Compose 通过 YAML 文件定义多容器应用,简化依赖管理。

2.1 添加 Redis 依赖

修改 app.js,添加 Redis 缓存:

  1. const express = require('express');
  2. const redis = require('redis');
  3. const app = express();
  4. const PORT = 3000;
  5. // 创建 Redis 客户端
  6. const client = redis.createClient({
  7. url: 'redis://redis:6379' // 通过服务名连接
  8. });
  9. client.on('error', (err) => console.log('Redis Client Error', err));
  10. await client.connect();
  11. app.get('/', async (req, res) => {
  12. await client.set('key', 'value');
  13. const value = await client.get('key');
  14. res.send(`Hello from Node.js! Redis value: ${value}`);
  15. });
  16. app.listen(PORT, () => {
  17. console.log(`Server running on port ${PORT}`);
  18. });

更新 package.json 添加 Redis 依赖:

  1. "dependencies": {
  2. "express": "^4.18.2",
  3. "redis": "^4.6.0"
  4. }

2.2 创建 docker-compose.yml

定义 Node.js 服务和 Redis 服务的依赖关系:

  1. version: '3.8'
  2. services:
  3. node-app:
  4. build: .
  5. ports:
  6. - "4000:3000"
  7. depends_on:
  8. - redis
  9. environment:
  10. - NODE_ENV=production
  11. redis:
  12. image: redis:alpine
  13. ports:
  14. - "6379:6379"

关键点解释

  • version:指定 Compose 文件版本(需与 Docker 引擎兼容)。
  • services:定义多个服务。
    • node-app:使用当前目录的 Dockerfile 构建。
    • redis:直接使用官方 Redis 镜像。
  • depends_on:声明服务启动顺序(但不会等待 Redis 完全就绪,需应用层处理)。
  • environment:设置环境变量(如生产模式)。

2.3 启动多容器应用

在项目根目录执行:

  1. docker-compose up -d
  • -d:后台运行。

访问 http://localhost:4000,应看到 Redis 缓存的值。

三、进阶优化与最佳实践

3.1 多阶段构建:减小镜像体积

优化 Dockerfile,分离构建环境和生产环境:

  1. # 构建阶段
  2. FROM node:18-alpine AS builder
  3. WORKDIR /usr/src/app
  4. COPY package*.json ./
  5. RUN npm install
  6. COPY . .
  7. RUN npm run build # 如果有构建步骤(如 TypeScript)
  8. # 生产阶段
  9. FROM node:18-alpine
  10. WORKDIR /usr/src/app
  11. COPY --from=builder /usr/src/app/node_modules ./node_modules
  12. COPY --from=builder /usr/src/app .
  13. EXPOSE 3000
  14. CMD ["npm", "start"]

3.2 使用 .dockerignore 排除无关文件

创建 .dockerignore 文件,避免将 node_modules、日志文件等无关内容复制到镜像中:

  1. node_modules
  2. .git
  3. .env
  4. logs/

3.3 健康检查与重启策略

docker-compose.yml 中添加健康检查和自动重启:

  1. services:
  2. node-app:
  3. build: .
  4. ports:
  5. - "4000:3000"
  6. healthcheck:
  7. test: ["CMD", "curl", "-f", "http://localhost:3000"]
  8. interval: 30s
  9. timeout: 10s
  10. retries: 3
  11. restart: unless-stopped

3.4 环境变量与配置管理

使用环境变量文件(.env)管理敏感配置:

  1. # .env
  2. REDIS_URL=redis://redis:6379
  3. NODE_ENV=production

docker-compose.yml 中引用:

  1. services:
  2. node-app:
  3. env_file:
  4. - .env

四、常见问题与解决方案

4.1 端口冲突

问题:宿主机端口已被占用。
解决:修改 docker-compose.yml 中的 ports 映射,如 "4001:3000"

4.2 依赖安装失败

问题npm install 报错。
解决

  • 确保 package.jsonpackage-lock.json 版本一致。
  • 检查网络连接(可尝试使用国内镜像源)。

4.3 服务启动顺序问题

问题:Node.js 服务启动时 Redis 未就绪。
解决

  • 在应用代码中添加重试逻辑(如 client.connect() 失败时延迟重试)。
  • 使用 wait-for-it.sh 等工具(需自定义脚本)。

五、总结与扩展

通过本文,您已掌握:

  1. 使用 Docker 构建 Node.js 微服务的容器镜像。
  2. 通过 Docker Compose 管理多服务依赖。
  3. 优化镜像体积、健康检查和配置管理。

下一步建议

  • 学习 Kubernetes 编排容器化应用(适合大规模生产环境)。
  • 探索 CI/CD 流水线(如 GitHub Actions)自动化构建和部署。
  • 研究服务网格(如 Istio)管理微服务通信。

容器化是现代云原生开发的核心技能,掌握 Docker 和 Docker Compose 将显著提升开发效率和部署可靠性。