Skip to content

DevOps 实战

Docker 基础

容器 vs 虚拟机

┌───────────────────────────────────────────────────┐
│                容器 vs 虚拟机                    │
├───────────────────┬───────────────────────────────┤
│ 容器               │ 虚拟机                       │
├───────────────────┼───────────────────────────────┤
│ 共享宿主机内核     │ 独立内核                     │
│ 秒级启动           │ 分钟级启动                   │
│ 占用资源少         │ 占用资源多                   │
│ 轻量级             │ 重量级                       │
│ 隔离性相对较弱     │ 隔离性强                     │
└───────────────────┴───────────────────────────────┘

Docker 核心概念

  • 镜像(Image):只读模板,包含运行应用所需的所有文件和依赖
  • 容器(Container):镜像的运行实例,可读写
  • 仓库(Repository):存储镜像的地方,如 Docker Hub
  • Dockerfile:构建镜像的脚本文件
  • docker-compose:定义和运行多容器应用的工具

Dockerfile 指令

指令描述示例
FROM基础镜像FROM node:18-alpine
WORKDIR工作目录WORKDIR /app
COPY复制文件COPY package*.json ./
ADD复制文件(支持 URL 和压缩包)ADD https://example.com/file.tar.gz /app
RUN执行命令RUN npm install
CMD容器启动命令CMD ["npm", "start"]
ENTRYPOINT容器入口点ENTRYPOINT ["node", "app.js"]
EXPOSE暴露端口EXPOSE 3000
ENV设置环境变量ENV NODE_ENV production
ARG构建参数ARG VERSION=1.0.0
VOLUME挂载卷VOLUME ["/app/data"]

多阶段构建

dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/build ./build
RUN npm install --only=production
EXPOSE 3000
CMD ["npm", "start"]

.dockerignore

node_modules
npm-debug.log
yarn-error.log
.DS_Store
.env
.git
.gitignore
build

docker-compose

基本结构

yaml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
    depends_on:
      - db
    volumes:
      - ./app:/app

  db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=app
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

常用命令

  • docker-compose up:启动服务
  • docker-compose down:停止服务
  • docker-compose build:构建镜像
  • docker-compose logs:查看日志
  • docker-compose exec:进入容器
  • docker-compose ps:查看服务状态

Node.js Docker 最佳实践

基础镜像选择

镜像大小特点适用场景
node:18~900MB完整 Node.js 环境开发环境
node:18-alpine~170MB轻量级,基于 Alpine生产环境
node:18-slim~300MB比 Alpine 大,比完整版小折中选择
gcr.io/distroless/nodejs18-debian11~150MB最小化,仅包含运行时安全性要求高

非 Root 用户

dockerfile
FROM node:18-alpine

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S app -u 1001

WORKDIR /app

# 切换到非 root 用户
USER app

COPY package*.json ./
RUN npm install
COPY . .

EXPOSE 3000
CMD ["npm", "start"]

信号处理

dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install

# 使用 dumb-init 处理信号
RUN apk add --no-cache dumb-init

ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "app.js"]

健康检查

dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["npm", "start"]

Serverless

FaaS 概念

FaaS(Function as a Service)是一种无服务器计算模型,允许开发者编写和部署函数,无需管理服务器。

核心特点

  • 按需执行,按使用付费
  • 自动扩缩容
  • 无需管理基础设施
  • 事件驱动

冷启动 vs 热启动

类型描述响应时间适用场景
冷启动首次执行,需要初始化容器100ms-数秒低频率请求
热启动容器已初始化,直接执行<100ms高频率请求

平台对比

平台特点适用场景
AWS Lambda生态丰富,支持多种语言企业级应用
Vercel Functions与 Next.js 集成,部署简单前端应用
Cloudflare Workers边缘计算,响应快全球分布应用
Google Cloud Functions与 GCP 集成企业级应用
Azure Functions与 Azure 集成企业级应用

Serverless Framework

yaml
# serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
javascript
// handler.js
module.exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hello Serverless!',
      input: event,
    }),
  };
};

消息队列

核心概念

  • 生产者(Producer):发送消息的应用
  • 消费者(Consumer):接收消息的应用
  • 队列(Queue):存储消息的缓冲区
  • 主题(Topic):消息的分类
  • ** Broker**:消息队列服务器

常见消息队列

消息队列特点适用场景
RabbitMQ可靠,支持多种协议企业级应用,需要可靠性
Kafka高吞吐量,持久化大数据,日志处理
Redis Stream轻量级,与 Redis 集成简单场景,实时性要求高
NATS高性能,低延迟微服务,IoT

RabbitMQ 示例

javascript
const amqp = require('amqplib');

// 生产者
async function produce() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  
  const queue = 'tasks';
  await channel.assertQueue(queue, { durable: true });
  
  const message = 'Hello RabbitMQ!';
  channel.sendToQueue(queue, Buffer.from(message), { persistent: true });
  console.log(`Sent: ${message}`);
  
  setTimeout(() => {
    connection.close();
  }, 500);
}

// 消费者
async function consume() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  
  const queue = 'tasks';
  await channel.assertQueue(queue, { durable: true });
  
  console.log('Waiting for messages...');
  
  channel.consume(queue, (msg) => {
    const message = msg.content.toString();
    console.log(`Received: ${message}`);
    
    // 模拟处理时间
    setTimeout(() => {
      console.log('Processing done');
      channel.ack(msg);
    }, 1000);
  }, { noAck: false });
}

produce();
consume();

Kafka 示例

javascript
const { Kafka } = require('kafkajs');

const kafka = new Kafka({
  clientId: 'my-app',
  brokers: ['localhost:9092']
});

// 生产者
async function produce() {
  const producer = kafka.producer();
  await producer.connect();
  
  await producer.send({
    topic: 'test-topic',
    messages: [
      { value: 'Hello Kafka!' }
    ]
  });
  
  await producer.disconnect();
}

// 消费者
async function consume() {
  const consumer = kafka.consumer({ groupId: 'test-group' });
  await consumer.connect();
  await consumer.subscribe({ topic: 'test-topic', fromBeginning: true });
  
  await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
      console.log(`Received: ${message.value.toString()}`);
    }
  });
}

produce();
consume();

微服务

单体 vs 微服务

┌───────────────────────────────────────────────────┐
│                单体 vs 微服务                    │
├───────────────────┬───────────────────────────────┤
│ 单体应用           │ 微服务                       │
├───────────────────┼───────────────────────────────┤
│ 单一代码库         │ 多个独立服务                 │
│ 部署简单           │ 部署复杂                     │
│ 开发效率高         │ 开发效率相对较低             │
│ 扩展困难           │ 独立扩展                     │
│ 故障影响范围大     │ 故障隔离                     │
└───────────────────┴───────────────────────────────┘

服务拆分原则

  • 按业务域拆分:如用户服务、订单服务、支付服务
  • 按能力拆分:如认证服务、通知服务、搜索服务
  • 按数据边界拆分:每个服务管理自己的数据
  • 服务粒度适中:避免过细或过粗

服务间通信

通信方式特点适用场景
REST简单,标准服务间调用
gRPC高性能,强类型内部服务通信
消息队列异步,解耦事件驱动
GraphQL灵活,减少冗余前端与后端通信

API 网关

作用

  • 路由请求
  • 认证授权
  • 限流熔断
  • 监控日志
  • 协议转换

常见网关

  • Nginx
  • Kong
  • AWS API Gateway
  • Azure API Management

CI/CD

持续集成(CI)

核心流程

  1. 代码提交
  2. 自动构建
  3. 自动测试
  4. 代码质量检查

持续部署(CD)

核心流程

  1. 构建镜像
  2. 部署到测试环境
  3. 自动化测试
  4. 部署到生产环境

GitHub Actions 示例

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '18'
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test
    - name: Build
      run: npm run build

GitLab CI 示例

yaml
# .gitlab-ci.yml
image: node:18-alpine

stages:
  - test
  - build
  - deploy

test:
  stage: test
  script:
    - npm ci
    - npm test

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/

deploy:
  stage: deploy
  script:
    - echo "Deploying to production"
  environment:
    name: production
  only:
    - main

日志与监控

日志管理

日志级别

  • DEBUG:详细信息,用于调试
  • INFO:一般信息,记录正常操作
  • WARN:警告信息,可能的问题
  • ERROR:错误信息,需要处理
  • FATAL:致命错误,系统崩溃

结构化日志

javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// 使用
logger.info('User logged in', { userId: 123, ip: '192.168.1.1' });
logger.error('Database connection failed', { error: err.message });

监控系统

核心指标

  • CPU 使用率
  • 内存使用率
  • 磁盘使用率
  • 网络流量
  • 应用响应时间
  • 错误率
  • 请求量

常见监控工具

  • Prometheus + Grafana
  • ELK Stack(Elasticsearch + Logstash + Kibana)
  • Datadog
  • New Relic
  • CloudWatch

Prometheus 示例

javascript
const prometheus = require('prom-client');

// 定义指标
const httpRequestsTotal = new prometheus.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status']
});

const httpRequestDuration = new prometheus.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'route'],
  buckets: [0.1, 0.5, 1, 2, 5]
});

// 中间件
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestsTotal.inc({ method: req.method, route: req.path, status: res.statusCode });
    httpRequestDuration.observe({ method: req.method, route: req.path }, duration);
  });
  
  next();
});

// 暴露指标
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', prometheus.register.contentType);
  res.end(await prometheus.register.metrics());
});

实战场景

1. Docker 部署 Node.js 应用

Dockerfile

dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install --only=production

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

docker-compose.yml

yaml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_URL=mongodb://mongo:27017/app
    depends_on:
      - mongo

  mongo:
    image: mongo:4.4
    volumes:
      - mongo-data:/data/db

volumes:
  mongo-data:

2. Serverless 函数

Vercel Functions

javascript
// api/hello.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello Serverless!' });
}

AWS Lambda

javascript
// handler.js
module.exports.handler = async (event) => {
  const { name } = event.queryStringParameters || { name: 'World' };
  
  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      message: `Hello, ${name}!`
    })
  };
};

3. CI/CD 流水线

GitHub Actions

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '18'
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test
    - name: Build
      run: npm run build
    - name: Deploy to Vercel
      run: npx vercel --prod
      env:
        VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

面试高频问题

1. Docker 与虚拟机的区别?

回答思路

  • 隔离级别:Docker 共享宿主机内核,虚拟机有独立内核
  • 启动速度:Docker 秒级启动,虚拟机分钟级启动
  • 资源占用:Docker 轻量级,虚拟机重量级
  • 镜像大小:Docker 镜像小,虚拟机镜像大
  • 隔离性:虚拟机隔离性更强,Docker 隔离性相对较弱

2. 如何优化 Docker 镜像大小?

回答思路

  • 使用 Alpine 基础镜像:体积小
  • 多阶段构建:分离构建和运行环境
  • 最小化层数:合并 RUN 指令
  • 清理缓存:安装后清理包管理器缓存
  • 使用 .dockerignore:排除不必要的文件

3. 什么是微服务?微服务的优缺点?

回答思路

  • 定义:将应用拆分为多个独立的服务,每个服务负责特定功能
  • 优点:独立部署、独立扩展、技术栈灵活、故障隔离
  • 缺点:部署复杂、网络开销、数据一致性挑战、监控难度大

4. 如何实现服务发现?

回答思路

  • 客户端发现:客户端维护服务列表,定期更新
  • 服务端发现:通过负载均衡器或服务注册中心
  • 常见工具:Consul、Etcd、Zookeeper、Kubernetes 服务

5. CI/CD 是什么?有什么好处?

回答思路

  • CI:持续集成,频繁合并代码并自动构建测试
  • CD:持续部署,自动部署到测试或生产环境
  • 好处:减少手动错误、加快开发速度、提高代码质量、快速反馈

6. 什么是冷启动?如何优化?

回答思路

  • 冷启动:Serverless 函数首次执行时的初始化时间
  • 优化方法
    • 减少依赖包大小
    • 使用更轻量级的基础镜像
    • 配置函数保持活跃
    • 预加载资源
    • 使用 Provisioned Concurrency(AWS Lambda)

7. 消息队列有什么作用?

回答思路

  • 解耦:生产者和消费者解耦
  • 削峰:处理突发流量
  • 异步:非阻塞处理
  • 可靠:保证消息传递
  • 顺序:保证消息顺序

8. 如何监控 Node.js 应用?

回答思路

  • 日志监控:结构化日志,ELK Stack
  • 指标监控:Prometheus + Grafana
  • 应用性能监控:New Relic、Datadog
  • 错误监控:Sentry
  • 健康检查:定期检查应用状态

延伸阅读

官方文档

书籍

  • 《Docker 实战》by Sean Kane
  • 《微服务设计》by Sam Newman
  • 《DevOps 实践指南》by Gene Kim
  • 《Serverless 架构》by Peter Sbarski

在线资源

工具

用心学习,用代码说话 💻