主题
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
builddocker-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: getjavascript
// 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)
核心流程:
- 代码提交
- 自动构建
- 自动测试
- 代码质量检查
持续部署(CD)
核心流程:
- 构建镜像
- 部署到测试环境
- 自动化测试
- 部署到生产环境
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 buildGitLab 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