一、为什么要把Node.js装进Docker?
咱们先来聊聊为什么要做这个组合。想象一下,你开发了一个特别棒的Node.js微服务,在自己电脑上跑得飞起。但当你把它交给运维同事部署到服务器时,突然各种报错:系统环境不一致、Node版本不对、依赖包缺失...这时候Docker就像个万能搬家箱,把整个运行环境打包带走。
Node.js的轻量特性和Docker的容器化简直是天作之合。Node应用本身就不需要太多系统资源,而Docker容器又比虚拟机轻得多。比如我们有个电商系统的商品服务,用Express框架写的,下面是个最简单的例子:
// 技术栈:Node.js + Express
const express = require('express');
const app = express();
app.get('/products', (req, res) => {
res.json([{id: 1, name: 'Docker入门指南'}]);
});
// 注意这里的环境变量读取方式
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`服务已在端口 ${port} 启动`);
});
这个小服务如果直接部署,可能会遇到端口冲突、Node版本要求等问题。但装进Docker后,这些烦恼就都不存在了。
二、从零开始构建你的第一个Docker化Node应用
让我们动手把上面的小服务装进Docker。首先要在项目根目录创建两个关键文件:
Dockerfile- 这是Docker的构建说明书.dockerignore- 告诉Docker哪些文件不用打包
# 技术栈:Docker
# 使用官方Node镜像作为基础
FROM node:18-alpine
# 设置工作目录
WORKDIR /usr/src/app
# 先拷贝package.json(利用Docker缓存层)
COPY package*.json ./
# 安装依赖
RUN npm install
# 拷贝所有源代码
COPY . .
# 暴露端口(和代码中的保持一致)
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
.dockerignore文件内容:
node_modules
npm-debug.log
.DS_Store
.git
构建并运行的命令也很简单:
docker build -t product-service .
docker run -p 3000:3000 -d product-service
现在访问localhost:3000/products就能看到我们的商品数据了。这个例子虽然简单,但已经包含了最核心的要素。
三、进阶技巧:多容器协作与优化
真实的微服务架构往往需要多个容器协同工作。比如我们的商品服务可能需要连接MongoDB数据库。这时候docker-compose就派上用场了。
# 技术栈:Docker Compose
version: '3'
services:
product-service:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=mongo
depends_on:
- mongo
mongo:
image: mongo:5.0
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
对应的Node.js代码需要稍作修改:
// 技术栈:Node.js + Mongoose
const mongoose = require('mongoose');
// 连接Docker Compose中定义的mongo服务
mongoose.connect(`mongodb://${process.env.DB_HOST}:27017/products`, {
useNewUrlParser: true
});
const Product = mongoose.model('Product', new mongoose.Schema({
name: String
}));
// 插入测试数据
async function init() {
await Product.create([{name: 'Docker入门指南'}]);
}
init();
这样我们就实现了一个完整的微服务架构,包含应用服务和数据库服务。启动整个系统只需要一个命令:
docker-compose up -d
四、生产环境必备的实战经验
在实际生产环境中,我们还需要考虑更多因素。下面分享几个关键点:
- 日志处理:容器内的日志需要妥善处理
# 在Dockerfile中添加日志配置
RUN npm install winston
// 使用winston记录日志
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
- 健康检查:确保服务正常运行
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:3000/health || exit 1
- 多阶段构建:减小镜像体积
# 构建阶段
FROM node:18 as builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# 生产阶段
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
- 环境变量管理:敏感信息处理
# 通过.env文件管理环境变量
docker run --env-file .env -p 3000:3000 product-service
五、常见问题与解决方案
在实际使用中,可能会遇到这些问题:
- 容器时区不对:
# 在Dockerfile中设置时区
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
- 热重载失效:
# 开发环境可以使用卷挂载
docker run -v $(pwd):/usr/src/app -p 3000:3000 product-service
- 内存限制:
# 限制容器内存使用
docker run -m "512m" --memory-swap "1g" product-service
- 跨容器网络通信:
# 在docker-compose中使用自定义网络
networks:
app-network:
driver: bridge
六、总结与最佳实践
经过上面的探索,我们可以得出一些最佳实践:
- 始终使用
.dockerignore文件,避免把不必要的文件打包进镜像 - 多阶段构建可以显著减小镜像体积
- 生产环境一定要设置资源限制
- 使用docker-compose管理多服务架构
- 日志要输出到标准输出,方便Docker收集
这种架构特别适合以下场景:
- 需要快速扩展的互联网应用
- 混合多种技术的微服务系统
- 需要频繁部署更新的项目
- 开发环境与生产环境需要高度一致的场景
当然也有需要注意的地方:
- 不要把所有东西都塞进一个容器
- 容器不是虚拟机,要遵循单一职责原则
- 数据持久化要使用卷(volume)
- 镜像安全扫描不能忽视
评论