您现在的位置是:网站首页 > 容器化部署文章详情

容器化部署

容器化部署的优势

容器化部署将应用程序及其依赖项打包到一个轻量级、可移植的容器中。与虚拟机相比,容器共享主机操作系统内核,启动更快,资源占用更少。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做了以下几件事:

  1. 使用轻量级的Alpine Linux版本的Node.js 18镜像
  2. 设置工作目录为/usr/src/app
  3. 先复制package.json文件并安装依赖(利用Docker的缓存层)
  4. 然后复制所有源代码
  5. 暴露3000端口
  6. 定义容器启动时运行的命令

构建和运行容器

有了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"]

这种构建方式:

  1. 第一阶段安装所有依赖(包括devDependencies)并构建应用
  2. 第二阶段只安装生产依赖,并复制构建结果
  3. 最终镜像不包含构建工具和开发依赖

使用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

容器化部署最佳实践

  1. 使用.dockerignore文件:避免将node_modules等不需要的文件复制到容器中

    node_modules
    npm-debug.log
    .git
    .env
    
  2. 正确处理信号:确保应用能响应SIGTERM信号,实现优雅关闭

    process.on('SIGTERM', () => {
      server.close(() => {
        console.log('Server closed')
        process.exit(0)
      })
    })
    
  3. 健康检查:在Dockerfile中添加HEALTHCHECK指令

    HEALTHCHECK --interval=30s --timeout=3s \
      CMD curl -f http://localhost:3000/health || exit 1
    
  4. 日志处理:避免将日志写入容器文件系统,直接输出到stdout/stderr

    console.log('Application started on port 3000')
    
  5. 安全加固:不以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

监控和日志收集

在容器环境中,需要专门的工具来收集和分析日志:

  1. 使用ELK Stack

    # docker-compose.yml中添加
    elasticsearch:
      image: elasticsearch:7.9.2
      ports:
        - "9200:9200"
    kibana:
      image: kibana:7.9.2
      ports:
        - "5601:5601"
    
  2. 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())
    })
    
  3. 分布式追踪

    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()
    

性能优化技巧

  1. 使用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;
      }
    }
    
  2. 集群模式运行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)
    }
    
  3. 内存限制

    docker run -p 3000:3000 -m 512m --memory-swap 1g my-node-app
    

常见问题解决

  1. 容器时间不对

    RUN apk add --no-cache tzdata
    ENV TZ=Asia/Shanghai
    
  2. 文件更改不生效

    # 使用bind mount开发时
    docker run -p 3000:3000 -v $(pwd):/usr/src/app my-node-app
    
  3. 调试容器

    # 进入运行中的容器
    docker exec -it <container-id> /bin/sh
    
    # 查看日志
    docker logs -f <container-id>
    
  4. 端口冲突

    # 查看占用端口的容器
    docker ps
    # 停止冲突容器
    docker stop <container-id>
    

安全注意事项

  1. 定期更新基础镜像

    FROM node:18-alpine  # 而不是node:18
    
  2. 扫描镜像漏洞

    docker scan my-node-app
    
  3. 最小权限原则

    USER node
    
  4. 密钥管理

    # 使用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

上一篇: 进程监控

下一篇: 性能分析工具

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步