您现在的位置是:网站首页 > 容器化部署文章详情
容器化部署
陈川
【
Node.js
】
40722人已围观
8168字
容器化部署的优势
容器化部署将应用程序及其依赖项打包到一个轻量级、可移植的容器中。与虚拟机相比,容器共享主机操作系统内核,启动更快,资源占用更少。Docker是目前最流行的容器化平台,它提供了标准化的方式来构建、分发和运行容器。
Node.js应用特别适合容器化部署。JavaScript的单线程特性与容器的轻量级设计完美契合。容器化还能解决"在我机器上能跑"的问题,确保开发、测试和生产环境的一致性。
安装Docker环境
在开始之前,需要在开发机器上安装Docker:
# 在Ubuntu上安装
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
# 验证安装
docker --version
docker run hello-world
Windows和Mac用户可以从Docker官网下载Docker Desktop。安装完成后,建议配置Docker使用国内镜像加速:
// /etc/docker/daemon.json
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
编写Dockerfile
Dockerfile是构建容器镜像的蓝图。下面是一个典型的Node.js应用的Dockerfile:
# 使用官方Node.js镜像作为基础
FROM node:18-alpine
# 设置工作目录
WORKDIR /usr/src/app
# 先复制package.json和package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制所有源代码
COPY . .
# 暴露应用端口
EXPOSE 3000
# 定义启动命令
CMD ["npm", "start"]
这个Dockerfile做了以下几件事:
- 使用轻量级的Alpine Linux版本的Node.js 18镜像
- 设置工作目录为/usr/src/app
- 先复制package.json文件并安装依赖(利用Docker的缓存层)
- 然后复制所有源代码
- 暴露3000端口
- 定义容器启动时运行的命令
构建和运行容器
有了Dockerfile后,可以构建镜像并运行容器:
# 构建镜像(注意最后的点号)
docker build -t my-node-app .
# 运行容器
docker run -p 3000:3000 -d my-node-app
常用参数说明:
-t
给镜像打标签-p
端口映射(主机端口:容器端口)-d
后台运行(detached模式)
多阶段构建优化
对于生产环境,可以使用多阶段构建来减小镜像体积:
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY --from=builder /usr/src/app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]
这种构建方式:
- 第一阶段安装所有依赖(包括devDependencies)并构建应用
- 第二阶段只安装生产依赖,并复制构建结果
- 最终镜像不包含构建工具和开发依赖
使用Docker Compose管理多容器
对于需要多个服务的应用(如Node.js + MongoDB),可以使用docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGO_URL=mongodb://mongo:27017/app
depends_on:
- mongo
mongo:
image: mongo:5.0
volumes:
- mongo-data:/data/db
ports:
- "27017:27017"
volumes:
mongo-data:
启动命令:
docker-compose up -d
容器化部署最佳实践
-
使用.dockerignore文件:避免将node_modules等不需要的文件复制到容器中
node_modules npm-debug.log .git .env
-
正确处理信号:确保应用能响应SIGTERM信号,实现优雅关闭
process.on('SIGTERM', () => { server.close(() => { console.log('Server closed') process.exit(0) }) })
-
健康检查:在Dockerfile中添加HEALTHCHECK指令
HEALTHCHECK --interval=30s --timeout=3s \ CMD curl -f http://localhost:3000/health || exit 1
-
日志处理:避免将日志写入容器文件系统,直接输出到stdout/stderr
console.log('Application started on port 3000')
-
安全加固:不以root用户运行容器
RUN chown -R node:node /usr/src/app USER node
Kubernetes部署进阶
对于生产环境,可以使用Kubernetes编排容器。下面是一个基本的deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-app
spec:
replicas: 3
selector:
matchLabels:
app: node-app
template:
metadata:
labels:
app: node-app
spec:
containers:
- name: node-app
image: my-node-app:1.0.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
配合Service暴露应用:
apiVersion: v1
kind: Service
metadata:
name: node-app-service
spec:
selector:
app: node-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
持续集成/持续部署(CI/CD)
结合GitHub Actions实现自动化构建和部署:
name: Node.js CI/CD
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: username/my-node-app:latest
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Install kubectl
uses: azure/setup-kubectl@v1
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
监控和日志收集
在容器环境中,需要专门的工具来收集和分析日志:
-
使用ELK Stack:
# docker-compose.yml中添加 elasticsearch: image: elasticsearch:7.9.2 ports: - "9200:9200" kibana: image: kibana:7.9.2 ports: - "5601:5601"
-
Prometheus监控:
const prometheus = require('prom-client') const collectDefaultMetrics = prometheus.collectDefaultMetrics collectDefaultMetrics({ timeout: 5000 }) app.get('/metrics', async (req, res) => { res.set('Content-Type', prometheus.register.contentType) res.end(await prometheus.register.metrics()) })
-
分布式追踪:
const { NodeTracerProvider } = require('@opentelemetry/node') const { SimpleSpanProcessor } = require('@opentelemetry/tracing') const { JaegerExporter } = require('@opentelemetry/exporter-jaeger') const provider = new NodeTracerProvider() provider.addSpanProcessor( new SimpleSpanProcessor( new JaegerExporter({ serviceName: 'node-app' }) ) ) provider.register()
性能优化技巧
-
使用Nginx作为反向代理:
FROM nginx:alpine COPY --from=builder /usr/src/app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80
nginx.conf示例:
server { listen 80; location / { proxy_pass http://app:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
-
集群模式运行Node.js:
const cluster = require('cluster') const numCPUs = require('os').cpus().length if (cluster.isMaster) { for (let i = 0; i < numCPUs; i++) { cluster.fork() } } else { const app = require('./app') app.listen(3000) }
-
内存限制:
docker run -p 3000:3000 -m 512m --memory-swap 1g my-node-app
常见问题解决
-
容器时间不对:
RUN apk add --no-cache tzdata ENV TZ=Asia/Shanghai
-
文件更改不生效:
# 使用bind mount开发时 docker run -p 3000:3000 -v $(pwd):/usr/src/app my-node-app
-
调试容器:
# 进入运行中的容器 docker exec -it <container-id> /bin/sh # 查看日志 docker logs -f <container-id>
-
端口冲突:
# 查看占用端口的容器 docker ps # 停止冲突容器 docker stop <container-id>
安全注意事项
-
定期更新基础镜像:
FROM node:18-alpine # 而不是node:18
-
扫描镜像漏洞:
docker scan my-node-app
-
最小权限原则:
USER node
-
密钥管理:
# 使用Docker secrets或环境变量文件 docker run --env-file .env my-node-app
实际案例:Express应用容器化
完整的Express应用Docker部署示例:
app.js:
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('Hello Docker!')
})
const port = process.env.PORT || 3000
const server = app.listen(port, () => {
console.log(`App running on port ${port}`)
})
process.on('SIGTERM', () => {
server.close(() => {
console.log('Server closed')
})
})
Dockerfile:
FROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
EXPOSE 3000
USER node
CMD ["node", "app.js"]
构建和运行:
docker build -t express-app .
docker run -p 3000:3000 -d express-app