第02章 Docker容器化¶
📚 章节概述¶
本章将深入讲解Docker容器化技术,包括Docker的核心概念、镜像管理、容器操作、网络配置、存储卷管理等。通过本章学习,你将能够熟练使用Docker进行应用容器化,为后续学习Kubernetes打下坚实基础。
🎯 学习目标¶
完成本章后,你将能够:
- 理解Docker的核心概念和架构
- 掌握Docker镜像的创建、管理和优化
- 熟练使用Docker容器进行应用部署
- 理解Docker网络和存储卷机制
- 掌握Docker Compose多容器编排
- 了解Docker安全最佳实践
- 能够编写高质量的Dockerfile
2.1 Docker概述¶
2.1.1 什么是Docker¶
Docker是一个开源的容器化平台,可以将应用程序及其依赖项打包到一个轻量级、可移植的容器中,确保应用在任何环境中都能以相同的方式运行。
Docker的核心价值¶
- 一致性
- 开发、测试、生产环境一致
- 消除"在我机器上能运行"的问题
-
简化环境配置
-
轻量级
- 容器共享宿主机内核
- 启动速度快(秒级)
-
资源占用少
-
可移植性
- 一次构建,到处运行
- 跨平台支持
-
云原生友好
-
隔离性
- 进程级隔离
- 资源限制
- 安全沙箱
2.1.2 Docker vs 虚拟机¶
| 特性 | Docker容器 | 虚拟机 |
|---|---|---|
| 架构 | 共享宿主机内核 | 独立操作系统 |
| 启动时间 | 秒级 | 分钟级 |
| 资源占用 | MB级别 | GB级别 |
| 性能 | 接近原生 | 有损耗 |
| 隔离性 | 进程级 | 系统级 |
| 密度 | 高(单机数百) | 低(单机数十) |
| 适用场景 | 微服务、云原生 | 传统应用、多租户 |
┌─────────────────────────────────────────────────────┐
│ 虚拟机架构 │
├─────────────────────────────────────────────────────┤
│ 应用A │ 应用B │ 应用C │ 应用D │
├───────────┼───────────┼───────────┼─────────────────┤
│ Guest OS │ Guest OS │ Guest OS │ Guest OS │
├───────────┼───────────┼───────────┼─────────────────┤
│ │ Hypervisor (KVM/VMware/Xen) │
├─────────────────────────────────────────────────────┤
│ 宿主机操作系统 │
├─────────────────────────────────────────────────────┤
│ 物理硬件 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Docker架构 │
├─────────────────────────────────────────────────────┤
│ 应用A │ 应用B │ 应用C │ 应用D │
├───────────┼───────────┼───────────┼─────────────────┤
│ │ Docker Engine (容器运行时) │
├─────────────────────────────────────────────────────┤
│ 宿主机操作系统 │
├─────────────────────────────────────────────────────┤
│ 物理硬件 │
└─────────────────────────────────────────────────────┘
2.1.3 Docker架构¶
Docker采用客户端-服务器(C/S)架构,主要组件包括:
核心组件¶
- Docker Client(客户端)
- 用户与Docker交互的接口
- 通过REST API与Docker Daemon通信
-
命令行工具(docker CLI)
-
Docker Daemon(守护进程)
- 监听Docker API请求
- 管理镜像、容器、网络、卷
-
后台运行的服务进程
-
Docker Registry(镜像仓库)
- 存储和分发Docker镜像
- 公有仓库:Docker Hub
-
私有仓库:Harbor、GitLab Registry
-
Docker Objects(Docker对象)
- 镜像(Image)
- 容器(Container)
- 网络(Network)
- 卷(Volume)
- 服务(Service)
工作流程¶
用户命令
│
▼
Docker Client
│
▼ REST API
Docker Daemon
│
├─► 镜像管理(拉取、构建、推送)
├─► 容器管理(创建、启动、停止)
├─► 网络管理(创建、连接)
└─► 卷管理(创建、挂载)
2.2 Docker安装与配置¶
2.2.1 在Ubuntu上安装Docker¶
# 更新包索引
sudo apt-get update
# 安装依赖
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # |管道:将前一命令的输出作为后一命令的输入
# 添加Docker仓库
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # $()命令替换:执行命令并获取输出
# 安装Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 启动Docker服务
sudo systemctl start docker
sudo systemctl enable docker
# 验证安装
sudo docker run hello-world
# 将当前用户添加到docker组(避免每次使用sudo)
sudo usermod -aG docker $USER
2.2.2 在CentOS/RHEL上安装Docker¶
# 卸载旧版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# 安装依赖
sudo yum install -y yum-utils
# 添加Docker仓库
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker Engine
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 启动Docker服务
sudo systemctl start docker
sudo systemctl enable docker
# 验证安装
sudo docker run hello-world
# 将当前用户添加到docker组
sudo usermod -aG docker $USER
2.2.3 在macOS上安装Docker¶
# 使用Homebrew安装
brew install --cask docker
# 或下载Docker Desktop for Mac
# https://www.docker.com/products/docker-desktop
2.2.4 在Windows上安装Docker¶
# 使用Chocolatey安装
choco install docker-desktop
# 或下载Docker Desktop for Windows
# https://www.docker.com/products/docker-desktop
# 启用WSL 2
wsl --install
2.2.5 Docker配置¶
# 查看Docker信息
docker info
# 查看Docker版本
docker version
# 配置Docker守护进程
sudo vim /etc/docker/daemon.json
# 示例配置
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"data-root": "/var/lib/docker",
"insecure-registries": [],
"debug": false,
"experimental": false
}
# 重启Docker服务
sudo systemctl restart docker
2.3 Docker镜像管理¶
2.3.1 镜像基本操作¶
# 搜索镜像
docker search nginx
docker search --filter=stars=100 nginx
# 拉取镜像
docker pull nginx:latest
docker pull nginx:1.27
docker pull nginx@sha256:abc123...
# 查看本地镜像
docker images
docker images --digests
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 删除镜像
docker rmi nginx:latest
docker rmi -f $(docker images -q) # 删除所有镜像
# 查看镜像详情
docker inspect nginx:latest
# 查看镜像历史
docker history nginx:latest
# 导出镜像
docker save -o nginx.tar nginx:latest
# 导入镜像
docker load -i nginx.tar
2.3.2 构建镜像¶
Dockerfile基础¶
# 基础镜像
FROM python:3.12-slim
# 维护者信息
LABEL maintainer="devops@example.com"
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8000
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
# 启动命令
CMD ["python", "app.py"]
构建命令¶
# 基本构建
docker build -t myapp:1.0 .
# 指定Dockerfile路径
docker build -f Dockerfile.prod -t myapp:prod .
# 构建时传递参数
docker build --build-arg APP_VERSION=1.0 -t myapp:latest .
# 多阶段构建
docker build --target builder -t myapp:builder .
# 使用构建缓存
docker build --cache-from myapp:latest -t myapp:new .
# 查看构建历史
docker history myapp:latest
# 查看构建信息
docker inspect myapp:latest
2.3.3 Dockerfile最佳实践¶
1. 选择合适的基础镜像¶
# ✅ 推荐:使用官方镜像
FROM python:3.12-slim
# ❌ 不推荐:使用非官方镜像
FROM my-custom-python:3.12
# ✅ 推荐:使用特定版本
FROM python:3.12.8-slim
# ❌ 不推荐:使用latest标签
FROM python:latest
2. 利用构建缓存¶
# ✅ 推荐:先复制依赖文件,再安装
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# ❌ 不推荐:一次性复制所有文件
COPY . .
RUN pip install -r requirements.txt
3. 减少镜像层数¶
# ✅ 推荐:合并RUN指令
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
git \
vim && \
rm -rf /var/lib/apt/lists/*
# ❌ 不推荐:多个RUN指令
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get install -y vim
4. 多阶段构建¶
# 构建阶段
FROM golang:1.23 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# 运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]
5. 最小化镜像大小¶
# ✅ 推荐:使用alpine
FROM python:3.12-alpine
# ✅ 推荐:清理缓存
RUN pip install --no-cache-dir -r requirements.txt && \
rm -rf /root/.cache
# ✅ 推荐:使用多阶段构建
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
2.3.4 镜像优化实战¶
示例:优化Python应用镜像¶
优化前:
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
优化后:
# 多阶段构建
FROM python:3.12-slim AS builder
# 安装构建依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
g++ \
make && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir --user -r requirements.txt
# 运行阶段
FROM python:3.12-slim
# 安装运行时依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 从构建阶段复制依赖
COPY --from=builder /root/.local /root/.local
# 复制应用代码
COPY . .
# 设置PATH
ENV PATH=/root/.local/bin:$PATH
# 创建非root用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 启动命令
CMD ["python", "app.py"]
优化效果: - 镜像大小:从900MB降至150MB - 安全性:使用非root用户运行 - 性能:利用构建缓存,加快构建速度 - 可维护性:清晰的多阶段构建结构
2.4 Docker容器管理¶
2.4.1 容器基本操作¶
# 运行容器
docker run nginx
docker run -d --name mynginx nginx
docker run -d -p 8080:80 --name mynginx nginx
docker run -d -p 8080:80 -v /data:/data --name mynginx nginx
# 查看运行中的容器
docker ps
docker ps -a # 查看所有容器
docker ps -q # 只显示容器ID
# 查看容器详情
docker inspect mynginx
# 查看容器日志
docker logs mynginx
docker logs -f mynginx # 跟踪日志
docker logs --tail 100 mynginx # 查看最后100行
docker logs --since 2024-01-01 mynginx # 查看指定时间后的日志
# 停止容器
docker stop mynginx
docker stop $(docker ps -q) # 停止所有容器
# 启动容器
docker start mynginx
# 重启容器
docker restart mynginx
# 删除容器
docker rm mynginx
docker rm -f mynginx # 强制删除运行中的容器
docker rm -f $(docker ps -aq) # 删除所有容器
# 进入容器
docker exec -it mynginx /bin/bash
docker exec -it mynginx /bin/sh
# 在容器中执行命令
docker exec mynginx ls -la
docker exec mynginx nginx -t
2.4.2 容器资源限制¶
# 限制CPU使用
docker run -d --cpus="1.5" nginx # 限制使用1.5个CPU
docker run -d --cpuset-cpus="0,1" nginx # 绑定到CPU 0和1
docker run -d --cpu-shares=512 nginx # CPU权重(相对值)
# 限制内存使用
docker run -d --memory="512m" nginx # 限制内存512MB
docker run -d --memory="1g" --memory-swap="2g" nginx # 限制内存和swap
# 限制磁盘IO
docker run -d --device-read-bps /dev/sda:1mb nginx # 限制读速度
docker run -d --device-write-bps /dev/sda:1mb nginx # 限制写速度
# 查看容器资源使用
docker stats
docker stats mynginx
docker stats --no-stream # 不持续更新
2.4.3 容器生命周期管理¶
import docker
import time
class ContainerManager:
"""Docker容器管理类"""
def __init__(self):
self.client = docker.from_env()
def create_container(self, image_name, container_name, **kwargs):
"""创建容器"""
try:
container = self.client.containers.run(
image_name,
name=container_name,
detach=True,
**kwargs
)
print(f"容器创建成功: {container_name}")
return container
except Exception as e:
print(f"容器创建失败: {e}")
return None
def start_container(self, container_name):
"""启动容器"""
try:
container = self.client.containers.get(container_name)
container.start()
print(f"容器启动成功: {container_name}")
return True
except Exception as e:
print(f"容器启动失败: {e}")
return False
def stop_container(self, container_name, timeout=10):
"""停止容器"""
try:
container = self.client.containers.get(container_name)
container.stop(timeout=timeout)
print(f"容器停止成功: {container_name}")
return True
except Exception as e:
print(f"容器停止失败: {e}")
return False
def restart_container(self, container_name, timeout=10):
"""重启容器"""
try:
container = self.client.containers.get(container_name)
container.restart(timeout=timeout)
print(f"容器重启成功: {container_name}")
return True
except Exception as e:
print(f"容器重启失败: {e}")
return False
def remove_container(self, container_name, force=False):
"""删除容器"""
try:
container = self.client.containers.get(container_name)
container.remove(force=force)
print(f"容器删除成功: {container_name}")
return True
except Exception as e:
print(f"容器删除失败: {e}")
return False
def get_container_logs(self, container_name, tail=100):
"""获取容器日志"""
try:
container = self.client.containers.get(container_name)
logs = container.logs(tail=tail)
print(f"容器 {container_name} 日志:")
print(logs.decode('utf-8'))
return logs
except Exception as e:
print(f"获取日志失败: {e}")
return None
def get_container_stats(self, container_name):
"""获取容器统计信息"""
try:
container = self.client.containers.get(container_name)
stats = container.stats(stream=False)
print(f"容器 {container_name} 统计信息:")
print(f" CPU使用率: {self._calculate_cpu_percent(stats)}%")
print(f" 内存使用: {stats['memory_stats']['usage'] / 1024 / 1024:.2f} MB")
print(f" 网络接收: {stats['networks']['eth0']['rx_bytes'] / 1024:.2f} KB")
print(f" 网络发送: {stats['networks']['eth0']['tx_bytes'] / 1024:.2f} KB")
return stats
except Exception as e:
print(f"获取统计信息失败: {e}")
return None
def _calculate_cpu_percent(self, stats):
"""计算CPU使用率"""
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - \
stats['precpu_stats']['cpu_usage']['total_usage']
system_delta = stats['cpu_stats']['system_cpu_usage'] - \
stats['precpu_stats']['system_cpu_usage']
if system_delta > 0 and cpu_delta > 0:
# cgroup v2 环境下 percpu_usage 可能为 None,需做兼容处理
percpu_usage = stats['cpu_stats']['cpu_usage'].get('percpu_usage') or []
num_cpus = len(percpu_usage) if percpu_usage else 1
return (cpu_delta / system_delta) * num_cpus * 100.0
return 0.0
def list_containers(self, all=False):
"""列出容器"""
containers = self.client.containers.list(all=all)
print("容器列表:")
print("-" * 80)
print(f"{'容器ID':<15} {'名称':<20} {'状态':<15} {'镜像'}")
print("-" * 80)
for container in containers:
print(f"{container.id[:12]:<15} {container.name:<20} {container.status:<15} {container.image.tags[0]}") # 切片操作:[start:end:step]提取子序列
return containers
def exec_command(self, container_name, command):
"""在容器中执行命令"""
try:
container = self.client.containers.get(container_name)
result = container.exec_run(command)
print(f"命令执行结果:")
print(result.output.decode('utf-8'))
return result
except Exception as e:
print(f"命令执行失败: {e}")
return None
# 使用示例
if __name__ == '__main__':
manager = ContainerManager()
# 创建容器
container = manager.create_container(
'nginx:latest',
'my-nginx',
ports={'80/tcp': 8080},
environment={'ENV': 'production'}
)
time.sleep(2)
# 列出容器
manager.list_containers()
# 获取日志
manager.get_container_logs('my-nginx')
# 获取统计信息
manager.get_container_stats('my-nginx')
# 停止容器
manager.stop_container('my-nginx')
# 删除容器
manager.remove_container('my-nginx')
2.5 Docker网络¶
2.5.1 网络模式¶
Docker提供多种网络模式:
1. Bridge模式(默认)¶
# 创建bridge网络
docker network create my-bridge
# 使用bridge网络运行容器
docker run -d --name web1 --network my-bridge nginx
docker run -d --name web2 --network my-bridge nginx
# 容器间通过容器名通信
docker exec web1 ping web2
2. Host模式¶
3. None模式¶
4. Container模式¶
# 共享另一个容器的网络
docker run -d --name web1 nginx
docker run -d --name web2 --network container:web1 nginx
5. 自定义网络¶
# 创建自定义bridge网络
docker network create \
--driver bridge \
--subnet 172.20.0.0/16 \
--ip-range 172.20.10.0/24 \
--gateway 172.20.10.1 \
my-custom-network
# 查看网络详情
docker network inspect my-custom-network
# 连接容器到网络
docker network connect my-custom-network web1
docker network connect my-custom-network web2
# 断开容器网络
docker network disconnect my-custom-network web1
# 删除网络
docker network rm my-custom-network
2.5.2 网络实战¶
示例:微服务网络架构¶
import docker
import time
class NetworkManager:
"""Docker网络管理类"""
def __init__(self):
self.client = docker.from_env()
self.networks = {} # 存储已创建的网络对象
def create_network(self, network_name, subnet=None, gateway=None):
"""创建网络"""
try:
ipam_config = None
if subnet and gateway:
ipam_config = docker.types.IPAMConfig(
pool_configs=[
docker.types.IPAMPool(
subnet=subnet,
gateway=gateway
)
]
)
network = self.client.networks.create(
network_name,
driver='bridge',
ipam=ipam_config
)
self.networks[network_name] = network
print(f"网络创建成功: {network_name}")
return network
except Exception as e:
print(f"网络创建失败: {e}")
return None
def deploy_microservices(self):
"""部署微服务架构"""
# 1. 创建网络
frontend_network = self.create_network('frontend-network', '172.20.0.0/16', '172.20.0.1')
backend_network = self.create_network('backend-network', '172.21.0.0/16', '172.21.0.1')
time.sleep(1)
# 2. 部署数据库
# ⚠️ 安全警告:以下代码示例包含硬编码密码,仅用于演示目的。
# 在生产环境中,请务必使用环境变量或密钥管理服务来存储敏感信息。
db = self.client.containers.run(
'mysql:8.4',
name='mysql-db',
environment={
'MYSQL_ROOT_PASSWORD': 'rootpassword',
'MYSQL_DATABASE': 'appdb',
'MYSQL_USER': 'appuser',
'MYSQL_PASSWORD': 'apppassword'
},
network='backend-network',
detach=True
)
print(f"数据库容器启动: {db.name}")
# 3. 部署Redis缓存
redis = self.client.containers.run(
'redis:alpine',
name='redis-cache',
network='backend-network',
detach=True
)
print(f"Redis容器启动: {redis.name}")
# 4. 部署后端API
backend = self.client.containers.run(
'python:3.12-slim',
name='backend-api',
command='python -m http.server 8000',
environment={
'DB_HOST': 'mysql-db',
'REDIS_HOST': 'redis-cache',
'DB_PASSWORD': 'apppassword'
},
ports={'8000/tcp': 8000},
network='backend-network',
detach=True
)
# containers.run() 只支持 network 参数(单个网络),
# 多网络需在启动后通过 connect() 追加
self.networks['frontend-network'].connect(backend)
print(f"后端API容器启动: {backend.name}")
# 5. 部署前端
frontend = self.client.containers.run(
'nginx:alpine',
name='frontend-web',
ports={'80/tcp': 80},
network='frontend-network',
detach=True
)
print(f"前端容器启动: {frontend.name}")
# 6. 等待容器启动
time.sleep(5)
# 7. 测试网络连通性
print("\n测试网络连通性:")
# 后端连接数据库
result = backend.exec_run('ping -c 2 mysql-db')
print(f"后端 -> 数据库: {'成功' if result.exit_code == 0 else '失败'}")
# 后端连接Redis
result = backend.exec_run('ping -c 2 redis-cache')
print(f"后端 -> Redis: {'成功' if result.exit_code == 0 else '失败'}")
# 前端连接后端
result = frontend.exec_run('ping -c 2 backend-api')
print(f"前端 -> 后端: {'成功' if result.exit_code == 0 else '失败'}")
return {
'db': db,
'redis': redis,
'backend': backend,
'frontend': frontend
}
# 使用示例
if __name__ == '__main__':
manager = NetworkManager()
services = manager.deploy_microservices()
print("\n微服务部署完成!")
print("访问地址: http://localhost")
2.6 Docker存储卷¶
2.6.1 卷类型¶
1. Docker Volume(推荐)¶
# 创建卷
docker volume create my-volume
# 查看卷
docker volume ls
docker volume inspect my-volume
# 使用卷
docker run -d --name web -v my-volume:/data nginx
# 删除卷
docker volume rm my-volume
docker volume prune # 删除未使用的卷
2. Bind Mount(绑定挂载)¶
# 挂载宿主机目录
docker run -d --name web -v /host/path:/container/path nginx
# 挂载单个文件
docker run -d --name web -v /host/file.txt:/container/file.txt nginx
# 只读挂载
docker run -d --name web -v /host/path:/container/path:ro nginx
3. Tmpfs(临时文件系统)¶
2.6.2 数据持久化实战¶
示例:数据库数据持久化¶
import docker
import time
class VolumeManager:
"""Docker卷管理类"""
def __init__(self):
self.client = docker.from_env()
def create_volume(self, volume_name, driver='local', **kwargs): # *args接收任意位置参数;**kwargs接收任意关键字参数
"""创建卷"""
try:
volume = self.client.volumes.create(
name=volume_name,
driver=driver,
driver_opts=kwargs
)
print(f"卷创建成功: {volume_name}")
return volume
except Exception as e:
print(f"卷创建失败: {e}")
return None
def deploy_persistent_database(self):
"""部署持久化数据库"""
# 1. 创建数据卷
mysql_data = self.create_volume('mysql-data')
mysql_logs = self.create_volume('mysql-logs')
# 2. 创建配置卷
mysql_config = self.create_volume('mysql-config')
# 3. 部署MySQL
# ⚠️ 安全警告:以下代码示例包含硬编码密码,仅用于演示目的。
# 在生产环境中,请务必使用环境变量或密钥管理服务来存储敏感信息。
mysql = self.client.containers.run(
'mysql:8.4',
name='mysql-server',
environment={
'MYSQL_ROOT_PASSWORD': 'rootpassword',
'MYSQL_DATABASE': 'appdb',
'MYSQL_USER': 'appuser',
'MYSQL_PASSWORD': 'apppassword'
},
volumes={
'mysql-data': {'bind': '/var/lib/mysql', 'mode': 'rw'},
'mysql-logs': {'bind': '/var/log/mysql', 'mode': 'rw'},
'mysql-config': {'bind': '/etc/mysql/conf.d', 'mode': 'ro'}
},
ports={'3306/tcp': 3306},
detach=True
)
print(f"MySQL容器启动: {mysql.name}")
# 4. 等待MySQL启动
time.sleep(10)
# 5. 测试数据库连接
test_container = self.client.containers.run(
'mysql:8.4',
command='mysql -hmysql-server -uappuser -papppassword -e "SHOW DATABASES;"',
network='host',
remove=True
)
print("数据库连接测试:")
print(test_container.logs().decode('utf-8'))
# 6. 创建测试数据
create_table = """
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
insert_data = """
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');
"""
# 执行SQL
self.client.containers.run(
'mysql:8.4',
command=f'mysql -hmysql-server -uappuser -papppassword appdb -e "{create_table}"',
network='host',
remove=True
)
self.client.containers.run(
'mysql:8.4',
command=f'mysql -hmysql-server -uappuser -papppassword appdb -e "{insert_data}"',
network='host',
remove=True
)
# 7. 查询数据
query_result = self.client.containers.run(
'mysql:8.4',
command='mysql -hmysql-server -uappuser -papppassword appdb -e "SELECT * FROM users;"',
network='host',
remove=True
)
print("\n查询结果:")
print(query_result.logs().decode('utf-8'))
# 8. 备份数据
backup_container = self.client.containers.run(
'mysql:8.4',
command='mysqldump -hmysql-server -uroot -prootpassword appdb > /backup/appdb_backup.sql',
volumes={
'/tmp/backup': {'bind': '/backup', 'mode': 'rw'}
},
network='host',
detach=True
)
print("\n数据备份完成")
return mysql
def backup_volume(self, volume_name, backup_path):
"""备份卷"""
try:
# 创建临时容器挂载卷
backup_container = self.client.containers.run(
'alpine',
command=f'tar czf /backup/{volume_name}.tar.gz /data',
volumes={
volume_name: {'bind': '/data', 'mode': 'ro'},
backup_path: {'bind': '/backup', 'mode': 'rw'}
},
remove=True
)
print(f"卷 {volume_name} 备份成功: {backup_path}/{volume_name}.tar.gz")
return True
except Exception as e:
print(f"卷备份失败: {e}")
return False
def restore_volume(self, volume_name, backup_path):
"""恢复卷"""
try:
# 创建临时容器恢复卷
restore_container = self.client.containers.run(
'alpine',
command=f'tar xzf /backup/{volume_name}.tar.gz -C /data',
volumes={
volume_name: {'bind': '/data', 'mode': 'rw'},
backup_path: {'bind': '/backup', 'mode': 'ro'}
},
remove=True
)
print(f"卷 {volume_name} 恢复成功")
return True
except Exception as e:
print(f"卷恢复失败: {e}")
return False
# 使用示例
if __name__ == '__main__':
manager = VolumeManager()
# 部署持久化数据库
mysql = manager.deploy_persistent_database()
# 备份卷
manager.backup_volume('mysql-data', '/tmp/backup')
print("\n数据持久化部署完成!")
2.7 Docker Compose¶
2.7.1 Docker Compose基础¶
Docker Compose用于定义和运行多容器Docker应用。
docker-compose.yml示例¶
# Docker Compose V2 无需 version 字段
services:
# Web服务
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- frontend
restart: unless-stopped
# 应用服务
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=mysql://user:password@db:3306/appdb
- REDIS_URL=redis://redis:6379/0
volumes:
- ./app:/app # ⚠️ 开发环境专用:bind mount 源代码用于热重载。生产环境应在 Dockerfile 中使用 COPY 将代码打包进镜像。
- app-data:/app/data
depends_on:
- db
- redis
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# 数据库服务
# ⚠️ 安全警告:以下配置包含硬编码密码,仅用于演示目的。
# 在生产环境中,请务必使用环境变量或密钥管理服务来存储敏感信息。
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: appdb
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- db-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Redis缓存
redis:
image: redis:alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# 监控服务
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- backend
restart: unless-stopped
# 日志服务
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es-data:/usr/share/elasticsearch/data
networks:
- backend
restart: unless-stopped
networks:
frontend:
driver: bridge
backend:
driver: bridge
volumes:
db-data:
driver: local
redis-data:
driver: local
app-data:
driver: local
prometheus-data:
driver: local
es-data:
driver: local
2.7.2 Docker Compose命令¶
注意: Docker Compose V2 使用
docker compose(空格分隔),旧版docker-compose(连字符)已于 2023 年从 Docker Desktop 中移除。
# 启动服务
docker compose up
docker compose up -d # 后台运行
docker compose up -d --build # 重新构建并启动
# 停止服务
docker compose stop
docker compose down # 停止并删除容器、网络
docker compose down -v # 同时删除卷
# 查看服务状态
docker compose ps
docker compose logs
docker compose logs -f web # 跟踪web服务日志
# 重启服务
docker compose restart
docker compose restart web
# 查看服务资源使用
docker compose top
# 执行命令
docker compose exec web bash
docker compose exec app python manage.py migrate
# 扩缩容
docker compose up -d --scale app=3
# 查看配置
docker compose config
# 拉取镜像
docker compose pull
# 构建镜像
docker compose build
docker compose build --no-cache web
2.7.3 完整实战项目¶
项目结构¶
my-project/
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
├── nginx.conf
├── init.sql
├── prometheus.yml
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── utils.py
├── html/
│ └── index.html
└── data/
└── .gitkeep
docker-compose.yml¶
services: # services定义各个服务容器
# Nginx反向代理
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Flask应用
app:
build:
context: .
dockerfile: Dockerfile
container_name: flask-app
environment:
- FLASK_ENV=production
- DATABASE_URL=mysql://appuser:apppassword@db:3306/appdb
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=${SECRET_KEY:-your-secret-key}
volumes:
- ./app:/app # ⚠️ 开发环境专用:bind mount 源代码用于热重载。生产环境应在 Dockerfile 中使用 COPY 将代码打包进镜像。
- app-data:/app/data
- app-logs:/app/logs
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# MySQL数据库
# ⚠️ 安全警告:以下配置包含硬编码密码,仅用于演示目的。
# 在生产环境中,请务必使用环境变量或密钥管理服务来存储敏感信息。
db:
# ⚠️ 使用 mysql:8.0 以兼容 mysql_native_password 认证插件。
# MySQL 8.4+ 已移除该插件,默认使用 caching_sha2_password。
# 若使用 mysql:8.4+,请删除下方 --default-authentication-plugin 行。
image: mysql:8.0
container_name: mysql-db
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-apppassword}
volumes:
- db-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- db-logs:/var/log/mysql
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"]
interval: 10s
timeout: 5s
retries: 5
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
# Redis缓存
redis:
image: redis:7-alpine
container_name: redis-cache
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redispassword}
volumes:
- redis-data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redispassword}", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Prometheus监控
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
networks:
- backend
restart: unless-stopped
# Grafana可视化
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin}
- GF_INSTALL_PLUGINS=redis-datasource
volumes:
- grafana-data:/var/lib/grafana
- grafana-logs:/var/log/grafana
depends_on:
- prometheus
networks:
- backend
restart: unless-stopped
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
backend:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
volumes:
db-data:
driver: local
db-logs:
driver: local
redis-data:
driver: local
app-data:
driver: local
app-logs:
driver: local
prometheus-data:
driver: local
grafana-data:
driver: local
grafana-logs:
driver: local
Dockerfile¶
# 构建阶段
FROM python:3.12-slim AS builder
# 安装构建依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
g++ \
make && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app # WORKDIR设置工作目录
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir --user -r requirements.txt
# 运行阶段
FROM python:3.12-slim
# 安装运行时依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates \
netcat-openbsd && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 从构建阶段复制依赖
COPY --from=builder /root/.local /root/.local
# 复制应用代码
COPY app/ ./app/
# 设置PATH
ENV PATH=/root/.local/bin:$PATH
# 创建非root用户
RUN useradd -m -u 1000 appuser && \
mkdir -p /app/data /app/logs && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 5000 # EXPOSE声明容器监听的端口
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1 # CMD容器启动时执行的默认命令
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--timeout", "120", "app.main:app"]
app/main.py¶
from flask import Flask, jsonify, request
from flask_cors import CORS
import redis
import pymysql
import logging
from datetime import datetime
import os
app = Flask(__name__)
CORS(app)
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 数据库连接
def get_db_connection():
"""获取数据库连接"""
try: # try/except捕获异常
connection = pymysql.connect(
host=os.getenv('DB_HOST', 'db'),
user=os.getenv('DB_USER', 'appuser'),
password=os.getenv('DB_PASSWORD', 'apppassword'),
database=os.getenv('DB_NAME', 'appdb'),
cursorclass=pymysql.cursors.DictCursor
)
return connection
except Exception as e:
logger.error(f"数据库连接失败: {e}")
return None
# Redis连接
def get_redis_connection():
"""获取Redis连接"""
try:
r = redis.Redis(
host=os.getenv('REDIS_HOST', 'redis'),
port=int(os.getenv('REDIS_PORT', 6379)),
password=os.getenv('REDIS_PASSWORD', 'redispassword'),
decode_responses=True
)
return r
except Exception as e:
logger.error(f"Redis连接失败: {e}")
return None
@app.route('/')
def index():
"""首页"""
return jsonify({
'message': 'Welcome to Flask App',
'timestamp': datetime.now().isoformat(),
'status': 'running'
})
@app.route('/health')
def health():
"""健康检查"""
# 检查数据库连接
db_status = 'ok'
try:
conn = get_db_connection()
if conn:
conn.close()
else:
db_status = 'error'
except:
db_status = 'error'
# 检查Redis连接
redis_status = 'ok'
try:
r = get_redis_connection()
if r:
r.ping()
else:
redis_status = 'error'
except:
redis_status = 'error'
return jsonify({
'status': 'healthy',
'database': db_status,
'redis': redis_status,
'timestamp': datetime.now().isoformat()
})
@app.route('/api/users', methods=['GET', 'POST'])
def users():
"""用户API"""
if request.method == 'GET':
# 尝试从缓存获取
r = get_redis_connection()
cache_key = 'users:list'
if r:
cached = r.get(cache_key)
if cached:
logger.info("从缓存获取用户列表")
return jsonify({'source': 'cache', 'data': cached})
# 从数据库获取
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor() as cursor:
cursor.execute('SELECT * FROM users')
users = cursor.fetchall()
# 缓存结果
if r:
r.setex(cache_key, 300, str(users)) # 缓存5分钟
return jsonify({'source': 'database', 'data': users})
finally:
conn.close()
elif request.method == 'POST':
data = request.json
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor() as cursor:
sql = "INSERT INTO users (name, email) VALUES (%s, %s)"
cursor.execute(sql, (data['name'], data['email']))
conn.commit()
# 清除缓存
r = get_redis_connection()
if r:
r.delete('users:list')
return jsonify({'message': 'User created successfully'}), 201
except Exception as e:
conn.rollback()
return jsonify({'error': str(e)}), 500
finally:
conn.close()
@app.route('/api/stats')
def stats():
"""统计信息"""
r = get_redis_connection()
if not r:
return jsonify({'error': 'Redis connection failed'}), 500
# 增加访问计数
r.incr('stats:visits')
# 获取统计信息
visits = r.get('stats:visits')
return jsonify({
'visits': visits,
'timestamp': datetime.now().isoformat()
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
启动脚本¶
#!/bin/bash
# 启动脚本
echo "启动应用..."
# 创建必要的目录
mkdir -p data logs ssl
# 生成自签名SSL证书(如果不存在)
if [ ! -f ssl/cert.pem ]; then # 条件测试:-f文件存在 -d目录存在 -z空字符串
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout ssl/key.pem \
-out ssl/cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=localhost"
echo "SSL证书生成完成"
fi
# 构建并启动服务
docker compose up -d --build
# 等待服务启动
echo "等待服务启动..."
sleep 10
# 检查服务状态
docker compose ps
echo "应用启动完成!"
echo "访问地址:"
echo " HTTP: http://localhost"
echo " HTTPS: https://localhost"
echo " Grafana: http://localhost:3000"
echo " Prometheus: http://localhost:9090"
2.8 Docker安全最佳实践¶
2.8.1 镜像安全¶
# ✅ 使用官方镜像
FROM python:3.12-slim # FROM指定基础镜像
# ✅ 使用特定版本
FROM python:3.12.8-slim
# ✅ 使用非root用户
RUN useradd -m -u 1000 appuser # RUN在构建时执行命令
USER appuser
# ✅ 最小化镜像
FROM alpine:latest
# ✅ 扫描镜像漏洞(Docker Desktop 4.17+ 使用 docker scout 替代已弃用的 docker scan)
docker scout cves myimage:latest
# ✅ 使用多阶段构建
FROM golang:1.23 AS builder
...
FROM alpine:latest
COPY --from=builder /app /app # COPY将文件复制到镜像中
2.8.2 容器安全¶
# ✅ 使用只读文件系统
docker run --read-only --tmpfs /tmp nginx
# ✅ 限制容器权限
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# ✅ 使用用户命名空间
docker run --userns-remap default nginx
# ✅ 设置资源限制
docker run --memory="512m" --cpus="1.0" nginx
# ✅ 使用seccomp配置
docker run --security-opt seccomp=default.json nginx
# ✅ 使用AppArmor配置
docker run --security-opt apparmor=docker-default nginx
2.8.3 网络安全¶
# ✅ 使用自定义网络
docker network create --driver bridge my-network
# ✅ 限制容器间通信
docker network create --driver bridge --internal internal-network
# ✅ 使用TLS加密(注意:docker daemon 命令已移除,使用 dockerd)
dockerd --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem
2.9 练习题¶
基础题¶
- 选择题
-
Docker和虚拟机的主要区别是什么?
- A. Docker需要完整的操作系统
- B. Docker共享宿主机内核
- C. Docker启动速度慢
- D. Docker资源占用多
-
简答题
- 解释Docker的镜像、容器、仓库的概念。
- 说明Dockerfile中FROM、RUN、CMD、ENTRYPOINT的区别。
进阶题¶
- 实践题
- 编写一个Dockerfile,构建一个Flask应用镜像。
-
使用Docker Compose部署一个包含Web应用、数据库、Redis的完整应用。
-
设计题
- 设计一个多阶段构建的Dockerfile,优化镜像大小。
- 设计一个生产级的Docker Compose配置,包括监控、日志、健康检查。
答案¶
1. 选择题答案¶
- B(Docker共享宿主机内核,这是与虚拟机的主要区别)
2. 简答题答案¶
镜像、容器、仓库的概念: - 镜像:只读的模板,包含运行应用所需的所有内容 - 容器:镜像的运行实例,可以启动、停止、删除 - 仓库:存储和分发镜像的地方,如Docker Hub
FROM、RUN、CMD、ENTRYPOINT的区别: - FROM:指定基础镜像 - RUN:在构建时执行命令 - CMD:容器启动时执行的默认命令 - ENTRYPOINT:容器启动时执行的入口点,CMD会作为参数传递
3. 实践题答案¶
参见2.7.3节的完整实战项目。
4. 设计题答案¶
参见2.3.4节和2.7.3节的示例。
2.10 面试准备¶
大厂面试题¶
字节跳动¶
- 解释Docker的Cgroups和Namespace机制。
- 如何优化Docker镜像大小?
- Docker和Kubernetes的区别是什么?
- 如何保证Docker容器的安全?
腾讯¶
- Docker的网络模式有哪些?
- 如何实现Docker容器间的通信?
- Docker数据持久化的方案有哪些?
- 如何监控Docker容器?
阿里云¶
- Dockerfile的最佳实践是什么?
- 如何排查Docker容器问题?
- Docker Compose的使用场景是什么?
- 如何实现Docker的高可用部署?
📚 参考资料¶
- Docker官方文档:https://docs.docker.com/
- Docker Hub:https://hub.docker.com/
- 《Docker实战》
- 《Docker深入浅出》
- Dockerfile最佳实践:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
🎯 本章小结¶
本章深入讲解了Docker容器化技术,包括:
- Docker的核心概念和架构
- Docker镜像的创建、管理和优化
- Docker容器的操作和资源管理
- Docker网络和存储卷机制
- Docker Compose多容器编排
- Docker安全最佳实践
- 完整的实战项目案例
通过本章学习,你掌握了Docker的核心技术,能够熟练使用Docker进行应用容器化。下一章将深入学习Kubernetes容器编排技术。