3.1 环境一致性保证
3.1.1 "在我机器上能运行"的困境
传统开发中常见的问题:
开发环境 (MacOS) 测试环境 (Ubuntu 20.04) 生产环境 (CentOS 7)
├── Python 3.10 ├── Python 3.8 ├── Python 3.6
├── MySQL 8.0 ├── MySQL 5.7 ├── MariaDB 10.3
├── Redis 7.0 ├── Redis 6.2 ├── Redis 5.0
└── Node.js 18 └── Node.js 16 └── Node.js 14
结果:同一份代码在不同环境表现不一致!
3.1.2 Docker如何保证一致性
Docker通过打包整个运行环境解决这个问题:
dockerfile
FROM python:3.9-slim
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 设置环境变量
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["flask", "run", "--host=0.0.0.0"]
关键要素:
- 确定性的基础镜像 :
python:3.9-slim保证基础环境一致 - 声明式依赖管理 :
requirements.txt锁定依赖版本 - 环境变量配置:统一的配置方式
- 启动命令标准化:相同的启动流程
3.1.3 一次构建,到处运行
bash
# 开发环境构建
docker build -t myapp:1.0 .
# 开发环境运行
docker run -d -p 5000:5000 myapp:1.0
# 推送到仓库
docker push registry.company.com/myapp:1.0
# 测试环境部署(完全相同的镜像)
ssh test-server
docker pull registry.company.com/myapp:1.0
docker run -d -p 5000:5000 registry.company.com/myapp:1.0
# 生产环境部署(完全相同的镜像)
ssh prod-server
docker pull registry.company.com/myapp:1.0
docker run -d -p 5000:5000 registry.company.com/myapp:1.0
保证 :所有环境运行的是完全相同的二进制文件和配置。
3.1.4 配置与代码分离
虽然镜像保持一致,但不同环境的配置可以不同:
bash
# 开发环境:使用开发数据库
docker run -d \
-e DB_HOST=localhost \
-e DB_PORT=5432 \
-e DB_NAME=dev_db \
-e DEBUG=true \
myapp:1.0
# 生产环境:使用生产数据库
docker run -d \
-e DB_HOST=prod-db.company.com \
-e DB_PORT=5432 \
-e DB_NAME=prod_db \
-e DEBUG=false \
myapp:1.0
或使用配置文件:
bash
# 使用不同的配置文件
docker run -d -v /config/dev.env:/app/.env myapp:1.0 # 开发
docker run -d -v /config/prod.env:/app/.env myapp:1.0 # 生产
依赖版本锁定
Python示例:
txt
# requirements.txt - 锁定具体版本
Flask==2.3.0
SQLAlchemy==2.0.15
psycopg2-binary==2.9.6
redis==4.5.5
Node.js示例:
json
{
"dependencies": {
"express": "4.18.2",
"mongoose": "7.0.3",
"redis": "4.6.5"
}
}
Docker镜像包含这些确定版本的依赖,避免"依赖地狱"。
3.2 快速部署与扩展
3.2.1 快速部署
3.2.1.1 传统部署流程
1. 准备服务器 (15分钟)
2. 安装操作系统 (30分钟)
3. 配置网络和安全 (20分钟)
4. 安装运行时 (Python/Node.js) (15分钟)
5. 安装系统依赖 (10分钟)
6. 部署应用代码 (10分钟)
7. 配置服务自启动 (10分钟)
8. 测试验证 (20分钟)
总计:约2-3小时
3.2.1.2 Docker部署流程
bash
# 一条命令,30秒完成
docker run -d -p 80:80 myapp:latest
# 或使用Docker Compose
docker-compose up -d
时间对比:
- 传统方式:2-3小时
- Docker方式:30秒
- 提速200倍+
3.2.2 水平扩展
当流量增加时,快速扩展实例:
bash
# 运行3个相同的应用实例
docker run -d --name app1 -p 8001:80 myapp:latest
docker run -d --name app2 -p 8002:80 myapp:latest
docker run -d --name app3 -p 8003:80 myapp:latest
# 配合负载均衡器(如Nginx)分发流量
使用Docker Compose扩展:
bash
# 启动1个实例
docker-compose up -d
# 扩展到5个实例
docker-compose up -d --scale web=5
yaml
# docker-compose.yml
version: '3'
services:
web:
image: myapp:latest
# 不指定具体端口,让Docker自动分配
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
3.2.3 蓝绿部署
无停机更新应用:
bash
# 当前运行 v1.0(绿色环境)
docker run -d --name app-green -p 80:80 myapp:1.0
# 启动 v2.0(蓝色环境)
docker run -d --name app-blue -p 8080:80 myapp:2.0
# 测试 v2.0
curl http://localhost:8080
# 切换流量到 v2.0(通过负载均衡器)
# 停止 v1.0
docker stop app-green
docker rm app-green
# 如果v2.0有问题,快速回滚
docker stop app-blue
docker start app-green
3.2.4 金丝雀发布
逐步切换流量到新版本:
bash
# 运行4个v1.0实例
docker run -d --name app1 myapp:1.0
docker run -d --name app2 myapp:1.0
docker run -d --name app3 myapp:1.0
docker run -d --name app4 myapp:1.0
# 将1个实例更新到v2.0(25%流量)
docker stop app4
docker run -d --name app4 myapp:2.0
# 观察指标,如果正常,继续替换其他实例
docker stop app3
docker run -d --name app3 myapp:2.0
# 逐步完成全部替换
3.2.5 自动化部署
结合CI/CD实现自动部署:
yaml
# GitLab CI 示例
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t myapp:$CI_COMMIT_SHA .
- docker push myapp:$CI_COMMIT_SHA
test:
stage: test
script:
- docker run myapp:$CI_COMMIT_SHA pytest
deploy_production:
stage: deploy
script:
- docker pull myapp:$CI_COMMIT_SHA
- docker stop myapp-prod || true
- docker rm myapp-prod || true
- docker run -d --name myapp-prod -p 80:80 myapp:$CI_COMMIT_SHA
only:
- main
3.3 资源隔离与限制
为什么需要资源限制
没有资源限制的风险:
场景:3个容器共享主机
- 容器A:正常应用,需要1GB内存
- 容器B:正常应用,需要1GB内存
- 容器C:出现内存泄漏,无限增长
结果:容器C占满所有内存,导致容器A、B和宿主机崩溃!
CPU资源限制
限制CPU份额(相对值)
bash
# 设置CPU份额为512(默认1024)
docker run -d --cpu-shares=512 nginx
# 两个容器的CPU使用比例
docker run -d --name app1 --cpu-shares=1024 myapp # 得到66.7%
docker run -d --name app2 --cpu-shares=512 myapp # 得到33.3%
注意 :--cpu-shares 是相对权重,只在CPU竞争时生效。
限制CPU核心数(绝对值)
bash
# 限制使用1.5个CPU核心
docker run -d --cpus="1.5" nginx
# 限制使用特定的CPU核心(0和1)
docker run -d --cpuset-cpus="0,1" nginx
# 限制CPU使用率上限为50%
docker run -d --cpu-quota=50000 --cpu-period=100000 nginx
实践建议:
bash
# 开发环境:不限制
docker run -d myapp
# 测试环境:限制CPU防止干扰
docker run -d --cpus="2" myapp
# 生产环境:根据性能测试结果精确限制
docker run -d --cpus="4" --memory="4g" myapp
内存资源限制
限制内存大小
bash
# 限制最大内存为512MB
docker run -d --memory="512m" nginx
# 限制内存和交换空间总和为1GB
docker run -d --memory="512m" --memory-swap="1g" nginx
# 禁用交换空间
docker run -d --memory="512m" --memory-swap="512m" nginx
OOM行为控制
bash
# 当内存不足时,不要杀死容器(慎用)
docker run -d --memory="512m" --oom-kill-disable nginx
# 设置OOM分数调整值(-1000到1000,值越低越不容易被杀死)
docker run -d --memory="512m" --oom-score-adj=-500 nginx
内存预留
bash
# 预留256MB内存(软限制)
docker run -d --memory="1g" --memory-reservation="256m" nginx
当内存不足时,Docker会尝试将容器内存使用降到预留值以下。
磁盘I/O限制
bash
# 限制块设备读取速度为1MB/s
docker run -d --device-read-bps /dev/sda:1mb nginx
# 限制块设备写入速度为1MB/s
docker run -d --device-write-bps /dev/sda:1mb nginx
# 限制读取IOPS为100
docker run -d --device-read-iops /dev/sda:100 nginx
# 限制写入IOPS为100
docker run -d --device-write-iops /dev/sda:100 nginx
# 设置I/O权重(相对值,10-1000)
docker run -d --blkio-weight 500 nginx
资源限制实战示例
yaml
# docker-compose.yml - 完整的资源限制配置
version: '3.8'
services:
web:
image: myapp:latest
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
restart: unless-stopped
database:
image: postgres:13
deploy:
resources:
limits:
cpus: '4'
memory: 4G
reservations:
cpus: '2'
memory: 2G
volumes:
- db-data:/var/lib/postgresql/data
cache:
image: redis:6
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
volumes:
db-data:
查看资源使用情况
bash
# 实时查看所有容器的资源使用
docker stats
# 查看特定容器的资源使用
docker stats myapp
# 输出示例:
# CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
# myapp 2.5% 256MiB / 512MiB 50% 1.2kB / 648B 0B / 0B
3.4 版本控制与回滚机制
镜像标签策略
语义化版本
bash
# 使用语义化版本号
docker build -t myapp:1.0.0 .
docker build -t myapp:1.1.0 .
docker build -t myapp:2.0.0 .
# 同时打多个标签
docker tag myapp:1.1.0 myapp:1.1
docker tag myapp:1.1.0 myapp:1
docker tag myapp:1.1.0 myapp:latest
Git提交哈希
bash
# 使用Git提交哈希作为标签
GIT_HASH=$(git rev-parse --short HEAD)
docker build -t myapp:$GIT_HASH .
docker tag myapp:$GIT_HASH myapp:latest
# 可追溯到具体代码版本
docker run -d myapp:a3f7c8e
时间戳标签
bash
# 使用时间戳
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
docker build -t myapp:$TIMESTAMP .
# 示例:myapp:20260210-143022
版本回滚
简单回滚
bash
# 当前运行v2.0,出现问题
docker stop myapp
docker rm myapp
# 回滚到v1.0
docker run -d --name myapp -p 80:80 myapp:1.0
保留多个版本
bash
# 同时保留多个版本的镜像
docker images
# REPOSITORY TAG IMAGE ID SIZE
# myapp 3.0 abc123 200MB
# myapp 2.0 def456 195MB
# myapp 1.0 ghi789 180MB
# 快速切换版本
docker stop myapp-v3
docker run -d --name myapp-v2 -p 80:80 myapp:2.0
使用数据卷保持数据
bash
# 升级时保持数据不丢失
docker run -d \
--name myapp-v1 \
-v app-data:/app/data \
-p 80:80 \
myapp:1.0
# 升级到v2.0,数据卷复用
docker stop myapp-v1
docker run -d \
--name myapp-v2 \
-v app-data:/app/data \
-p 80:80 \
myapp:2.0
# 如需回滚,数据依然完整
docker stop myapp-v2
docker run -d \
--name myapp-v1-rollback \
-v app-data:/app/data \
-p 80:80 \
myapp:1.0
镜像历史追踪
bash
# 查看镜像构建历史
docker history myapp:2.0
# 查看镜像详细信息(包括构建时间、标签等)
docker image inspect myapp:2.0
# 导出镜像历史到JSON
docker image inspect myapp:2.0 --format='{{json .}}' | jq
回滚策略最佳实践
yaml
# docker-compose.yml - 蓝绿部署配置
version: '3.8'
services:
app-blue:
image: myapp:${BLUE_VERSION:-1.0}
ports:
- "8080:80"
environment:
- COLOR=blue
app-green:
image: myapp:${GREEN_VERSION:-2.0}
ports:
- "8081:80"
environment:
- COLOR=green
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app-blue
- app-green
切换版本:
bash
# 当前使用blue(v1.0)
export BLUE_VERSION=1.0
export GREEN_VERSION=2.0
docker-compose up -d
# 切换到green(v2.0),通过修改nginx配置
# 如需回滚,再次切换nginx配置到blue
3.5 微服务架构的支持
单体应用 vs 微服务
单体应用:
┌─────────────────────────────┐
│ Monolithic App │
│ ┌─────────────────────┐ │
│ │ User Module │ │
│ │ Order Module │ │
│ │ Payment Module │ │
│ │ Product Module │ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ Database │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
微服务架构:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Order │ │ Payment │ │ Product │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐
│DB │ │DB │ │DB │ │DB │
└───┘ └───┘ └───┘ └───┘
Docker与微服务的契合
1. 服务隔离
每个微服务运行在独立容器中:
bash
# 用户服务
docker run -d --name user-service -p 8001:80 user-service:1.0
# 订单服务
docker run -d --name order-service -p 8002:80 order-service:1.0
# 支付服务
docker run -d --name payment-service -p 8003:80 payment-service:1.0
# 商品服务
docker run -d --name product-service -p 8004:80 product-service:1.0
2. 独立部署
bash
# 只更新订单服务,不影响其他服务
docker stop order-service
docker rm order-service
docker run -d --name order-service -p 8002:80 order-service:2.0
3. 独立扩展
bash
# 订单服务访问量大,扩展为3个实例
docker run -d --name order-service-1 -p 8002:80 order-service:1.0
docker run -d --name order-service-2 -p 8003:80 order-service:1.0
docker run -d --name order-service-3 -p 8004:80 order-service:1.0
# 其他服务保持1个实例
Docker Compose管理微服务
yaml
# docker-compose.yml
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "8001:80"
environment:
- DB_HOST=user-db
depends_on:
- user-db
networks:
- microservices
order-service:
build: ./order-service
ports:
- "8002:80"
environment:
- DB_HOST=order-db
- USER_SERVICE_URL=http://user-service
depends_on:
- order-db
- user-service
networks:
- microservices
payment-service:
build: ./payment-service
ports:
- "8003:80"
environment:
- DB_HOST=payment-db
- ORDER_SERVICE_URL=http://order-service
depends_on:
- payment-db
- order-service
networks:
- microservices
product-service:
build: ./product-service
ports:
- "8004:80"
environment:
- DB_HOST=product-db
depends_on:
- product-db
networks:
- microservices
# 数据库服务
user-db:
image: postgres:13
environment:
- POSTGRES_DB=user_db
networks:
- microservices
order-db:
image: postgres:13
environment:
- POSTGRES_DB=order_db
networks:
- microservices
payment-db:
image: postgres:13
environment:
- POSTGRES_DB=payment_db
networks:
- microservices
product-db:
image: postgres:13
environment:
- POSTGRES_DB=product_db
networks:
- microservices
# API网关
api-gateway:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- user-service
- order-service
- payment-service
- product-service
networks:
- microservices
networks:
microservices:
driver: bridge
启动所有服务:
bash
docker-compose up -d
服务发现与通信
容器间通过服务名通信:
python
# order-service中调用user-service
import requests
# 使用服务名作为主机名
user_data = requests.get('http://user-service/api/users/123')
Docker自动解析服务名到容器IP。
微服务监控
yaml
# 添加监控服务
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- microservices
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- microservices
3.6 小结
通过本章学习,我们掌握了Docker的五大核心功能特性:
✅ 环境一致性
- 通过镜像封装完整运行环境
- 一次构建,到处运行
- 配置与代码分离
✅ 快速部署与扩展
- 秒级启动容器
- 快速水平扩展
- 支持蓝绿部署和金丝雀发布
- 自动化CI/CD集成
✅ 资源隔离与限制
- CPU、内存、磁盘I/O限制
- 防止资源耗尽
- 精确的资源配额管理
✅ 版本控制与回滚
- 镜像标签管理
- 快速版本切换
- 数据持久化保证安全回滚
✅ 微服务架构支持
- 服务隔离
- 独立部署和扩展
- 服务发现和通信
- 完整的微服务生态
实践建议
- 开发环境:使用Docker保证团队环境一致
- 测试环境:使用资源限制防止相互干扰
- 生产环境:结合编排工具(Kubernetes)管理大规模部署
- 监控告警:始终监控容器资源使用情况
3.7 下一步
在第4章中,我们将学习Docker的安装与配置:
- Linux系统安装Docker
- Docker Desktop(Windows/macOS)
- Docker配置文件详解
- 镜像加速器配置
- 权限与用户组设置
掌握正确的安装和配置是后续实践的基础。
本章思考题:
- 在你的项目中,如何利用Docker的环境一致性特性避免"在我机器上能运行"的问题?
- 什么场景下需要限制容器的资源使用?如何设置合理的限制值?
- 微服务架构中,每个服务应该如何设计镜像和配置管理?
- 如何设计一个安全的版本回滚策略?
相关资源:
- Docker资源限制文档:https://docs.docker.com/config/containers/resource_constraints/
- 12-Factor App:https://12factor.net/
- 微服务设计模式:https://microservices.io/patterns/