029-容器化部署
·
029-容器化部署
难度:🔴 | 预计时间:120分钟 | 前置:028-微服务架构
容器化部署是现代应用开发和部署的标准实践。本章将详细介绍如何使用Docker对FastAPI应用进行容器化,包括单容器部署、多容器编排、生产环境优化等内容。
29.1 Docker基础
29.1.1 Docker环境配置
# docker_utils.py
import docker
import logging
from typing import Dict, List, Optional
from pathlib import Path
class DockerManager:
"""Docker管理器"""
def __init__(self):
self.client = docker.from_env()
self.logger = logging.getLogger(__name__)
def build_image(self,
dockerfile_path: str,
tag: str,
build_args: Optional[Dict[str, str]] = None) -> bool:
"""构建Docker镜像"""
try:
self.logger.info(f"开始构建镜像: {tag}")
# 构建镜像
image, logs = self.client.images.build(
path=dockerfile_path,
tag=tag,
buildargs=build_args or {},
rm=True,
forcerm=True
)
# 输出构建日志
for log in logs:
if 'stream' in log:
self.logger.info(log['stream'].strip())
self.logger.info(f"镜像构建成功: {tag}")
return True
except Exception as e:
self.logger.error(f"镜像构建失败: {e}")
return False
def run_container(self,
image: str,
name: str,
ports: Optional[Dict[str, int]] = None,
environment: Optional[Dict[str, str]] = None,
volumes: Optional[Dict[str, Dict[str, str]]] = None,
detach: bool = True) -> Optional[str]:
"""运行容器"""
try:
self.logger.info(f"启动容器: {name}")
container = self.client.containers.run(
image=image,
name=name,
ports=ports or {},
environment=environment or {},
volumes=volumes or {},
detach=detach,
remove=False
)
self.logger.info(f"容器启动成功: {container.id}")
return container.id
except Exception as e:
self.logger.error(f"容器启动失败: {e}")
return None
def stop_container(self, container_name: str) -> bool:
"""停止容器"""
try:
container = self.client.containers.get(container_name)
container.stop()
self.logger.info(f"容器已停止: {container_name}")
return True
except Exception as e:
self.logger.error(f"停止容器失败: {e}")
return False
def remove_container(self, container_name: str) -> bool:
"""删除容器"""
try:
container = self.client.containers.get(container_name)
container.remove(force=True)
self.logger.info(f"容器已删除: {container_name}")
return True
except Exception as e:
self.logger.error(f"删除容器失败: {e}")
return False
def get_container_logs(self, container_name: str) -> str:
"""获取容器日志"""
try:
container = self.client.containers.get(container_name)
logs = container.logs(tail=100).decode('utf-8')
return logs
except Exception as e:
self.logger.error(f"获取日志失败: {e}")
return ""
def list_containers(self, all_containers: bool = False) -> List[Dict]:
"""列出容器"""
try:
containers = self.client.containers.list(all=all_containers)
return [
{
'id': container.id[:12],
'name': container.name,
'image': container.image.tags[0] if container.image.tags else 'unknown',
'status': container.status,
'ports': container.ports
}
for container in containers
]
except Exception as e:
self.logger.error(f"列出容器失败: {e}")
return []
def list_images(self) -> List[Dict]:
"""列出镜像"""
try:
images = self.client.images.list()
return [
{
'id': image.id[:12],
'tags': image.tags,
'size': image.attrs['Size'],
'created': image.attrs['Created']
}
for image in images
]
except Exception as e:
self.logger.error(f"列出镜像失败: {e}")
return []
29.1.2 Dockerfile模板生成器
# dockerfile_generator.py
from typing import Dict, List, Optional
from pathlib import Path
from jinja2 import Template
class DockerfileGenerator:
"""Dockerfile生成器"""
def __init__(self):
self.base_template = """
# {{ description }}
FROM {{ base_image }}
# 设置工作目录
WORKDIR {{ workdir }}
# 设置环境变量
{% for key, value in env_vars.items() %}
ENV {{ key }}={{ value }}
{% endfor %}
# 安装系统依赖
{% if system_packages %}
RUN apt-get update && apt-get install -y \
{% for package in system_packages %}
{{ package }}{% if not loop.last %} \{% endif %}
{% endfor %}
&& rm -rf /var/lib/apt/lists/*
{% endif %}
# 复制依赖文件
{% for src, dest in copy_files.items() %}
COPY {{ src }} {{ dest }}
{% endfor %}
# 安装Python依赖
{% if requirements_file %}
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r {{ requirements_file }}
{% endif %}
# 复制应用代码
COPY {{ app_source }} {{ app_dest }}
# 创建非root用户
{% if create_user %}
RUN useradd --create-home --shell /bin/bash {{ username }} && \
chown -R {{ username }}:{{ username }} {{ workdir }}
USER {{ username }}
{% endif %}
# 健康检查
{% if health_check %}
HEALTHCHECK --interval={{ health_check.interval }} \
--timeout={{ health_check.timeout }} \
--start-period={{ health_check.start_period }} \
--retries={{ health_check.retries }} \
CMD {{ health_check.command }}
{% endif %}
# 暴露端口
{% for port in expose_ports %}
EXPOSE {{ port }}
{% endfor %}
# 启动命令
{% if entrypoint %}
ENTRYPOINT {{ entrypoint }}
{% endif %}
{% if cmd %}
CMD {{ cmd }}
{% endif %}
""".strip()
def generate_fastapi_dockerfile(self,
config: Dict,
output_path: Optional[str] = None) -> str:
"""生成FastAPI应用的Dockerfile"""
# 默认配置
default_config = {
'description': 'FastAPI Application',
'base_image': 'python:3.11-slim',
'workdir': '/app',
'env_vars': {
'PYTHONUNBUFFERED': '1',
'PYTHONDONTWRITEBYTECODE': '1'
},
'system_packages': ['gcc', 'curl'],
'copy_files': {
'requirements.txt': '.'
},
'requirements_file': 'requirements.txt',
'app_source': '.',
'app_dest': '.',
'create_user': True,
'username': 'app',
'health_check': {
'interval': '30s',
'timeout': '30s',
'start_period': '5s',
'retries': '3',
'command': 'curl -f http://localhost:8000/health || exit 1'
},
'expose_ports': [8000],
'cmd': '["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]'
}
# 合并配置
merged_config = {**default_config, **config}
# 生成Dockerfile内容
template = Template(self.base_template)
dockerfile_content = template.render(**merged_config)
# 保存到文件
if output_path:
Path(output_path).write_text(dockerfile_content)
return dockerfile_content
def generate_multi_stage_dockerfile(self,
stages: List[Dict],
output_path: Optional[str] = None) -> str:
"""生成多阶段构建的Dockerfile"""
dockerfile_lines = []
for i, stage in enumerate(stages):
stage_name = stage.get('name', f'stage{i}')
base_image = stage['base_image']
# 阶段开始
if i == 0:
dockerfile_lines.append(f"FROM {base_image} AS {stage_name}")
else:
dockerfile_lines.append(f"\nFROM {base_image} AS {stage_name}")
# 工作目录
if 'workdir' in stage:
dockerfile_lines.append(f"WORKDIR {stage['workdir']}")
# 环境变量
if 'env_vars' in stage:
for key, value in stage['env_vars'].items():
dockerfile_lines.append(f"ENV {key}={value}")
# 系统包安装
if 'system_packages' in stage:
packages = ' \\
'.join(stage['system_packages'])
dockerfile_lines.append(
f"RUN apt-get update && apt-get install -y \\
{packages} \\
&& rm -rf /var/lib/apt/lists/*"
)
# 复制文件
if 'copy_files' in stage:
for src, dest in stage['copy_files'].items():
dockerfile_lines.append(f"COPY {src} {dest}")
# 从其他阶段复制
if 'copy_from' in stage:
for copy_item in stage['copy_from']:
from_stage = copy_item['from']
src = copy_item['src']
dest = copy_item['dest']
dockerfile_lines.append(f"COPY --from={from_stage} {src} {dest}")
# 运行命令
if 'run_commands' in stage:
for cmd in stage['run_commands']:
dockerfile_lines.append(f"RUN {cmd}")
# 用户设置
if stage.get('create_user'):
username = stage.get('username', 'app')
workdir = stage.get('workdir', '/app')
dockerfile_lines.append(
f"RUN useradd --create-home --shell /bin/bash {username} && \\"
f" chown -R {username}:{username} {workdir}"
)
dockerfile_lines.append(f"USER {username}")
# 健康检查(仅最后阶段)
if i == len(stages) - 1 and 'health_check' in stage:
hc = stage['health_check']
dockerfile_lines.append(
f"HEALTHCHECK --interval={hc['interval']} \\"
f" --timeout={hc['timeout']} \\"
f" --start-period={hc['start_period']} \\"
f" --retries={hc['retries']} \\"
f" CMD {hc['command']}"
)
# 暴露端口(仅最后阶段)
if i == len(stages) - 1 and 'expose_ports' in stage:
for port in stage['expose_ports']:
dockerfile_lines.append(f"EXPOSE {port}")
# 启动命令(仅最后阶段)
if i == len(stages) - 1:
if 'entrypoint' in stage:
dockerfile_lines.append(f"ENTRYPOINT {stage['entrypoint']}")
if 'cmd' in stage:
dockerfile_lines.append(f"CMD {stage['cmd']}")
dockerfile_content = '\n'.join(dockerfile_lines)
# 保存到文件
if output_path:
Path(output_path).write_text(dockerfile_content)
return dockerfile_content
29.2 单容器部署
29.2.1 基础Dockerfile
# Dockerfile
FROM python:3.11-slim
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非root用户
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
29.2.2 优化的生产环境Dockerfile
# Dockerfile.prod
FROM python:3.11-slim as builder
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 安装构建依赖
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖到临时目录
RUN pip install --user --no-cache-dir --upgrade pip && \
pip install --user --no-cache-dir -r requirements.txt
# 生产阶段
FROM python:3.11-slim as production
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PATH=/home/app/.local/bin:$PATH
# 安装运行时依赖
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# 创建应用用户
RUN useradd --create-home --shell /bin/bash app
# 设置工作目录
WORKDIR /app
# 从构建阶段复制Python包
COPY --from=builder /root/.local /home/app/.local
# 复制应用代码
COPY --chown=app:app . .
# 切换到应用用户
USER app
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
29.2.3 容器配置管理
# container_config.py
from typing import Dict, List, Optional
from pydantic import BaseModel, Field
import os
import json
class HealthCheckConfig(BaseModel):
"""健康检查配置"""
interval: str = "30s"
timeout: str = "30s"
start_period: str = "5s"
retries: int = 3
command: str = "curl -f http://localhost:8000/health || exit 1"
class ResourceConfig(BaseModel):
"""资源配置"""
memory_limit: Optional[str] = None
memory_reservation: Optional[str] = None
cpu_limit: Optional[float] = None
cpu_reservation: Optional[float] = None
class LoggingConfig(BaseModel):
"""日志配置"""
driver: str = "json-file"
options: Dict[str, str] = Field(default_factory=lambda: {
"max-size": "10m",
"max-file": "3"
})
class ContainerConfig(BaseModel):
"""容器配置"""
name: str
image: str
ports: Dict[str, int] = Field(default_factory=dict)
environment: Dict[str, str] = Field(default_factory=dict)
volumes: Dict[str, str] = Field(default_factory=dict)
networks: List[str] = Field(default_factory=list)
restart_policy: str = "unless-stopped"
health_check: Optional[HealthCheckConfig] = None
resources: Optional[ResourceConfig] = None
logging: Optional[LoggingConfig] = None
labels: Dict[str, str] = Field(default_factory=dict)
depends_on: List[str] = Field(default_factory=list)
@classmethod
def from_env(cls, prefix: str = "CONTAINER_") -> "ContainerConfig":
"""从环境变量创建配置"""
config_data = {}
for key, value in os.environ.items():
if key.startswith(prefix):
config_key = key[len(prefix):].lower()
# 处理嵌套配置
if "_" in config_key:
parts = config_key.split("_")
current = config_data
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
current[parts[-1]] = value
else:
config_data[config_key] = value
return cls(**config_data)
def to_docker_run_args(self) -> Dict:
"""转换为docker run参数"""
args = {
'name': self.name,
'image': self.image,
'ports': self.ports,
'environment': self.environment,
'volumes': self.volumes,
'restart_policy': {'Name': self.restart_policy},
'labels': self.labels,
'detach': True
}
# 添加网络配置
if self.networks:
args['network'] = self.networks[0] # Docker API限制
# 添加资源限制
if self.resources:
host_config = {}
if self.resources.memory_limit:
host_config['mem_limit'] = self.resources.memory_limit
if self.resources.memory_reservation:
host_config['mem_reservation'] = self.resources.memory_reservation
if self.resources.cpu_limit:
host_config['cpu_quota'] = int(self.resources.cpu_limit * 100000)
if self.resources.cpu_reservation:
host_config['cpu_shares'] = int(self.resources.cpu_reservation * 1024)
if host_config:
args['host_config'] = host_config
# 添加日志配置
if self.logging:
args['log_config'] = {
'type': self.logging.driver,
'config': self.logging.options
}
return args
class ContainerManager:
"""容器管理器"""
def __init__(self, docker_client):
self.client = docker_client
def deploy_container(self, config: ContainerConfig) -> bool:
"""部署容器"""
try:
# 停止并删除现有容器
self.stop_and_remove_container(config.name)
# 获取docker run参数
run_args = config.to_docker_run_args()
# 创建并启动容器
container = self.client.containers.run(**run_args)
print(f"容器 {config.name} 部署成功: {container.id[:12]}")
return True
except Exception as e:
print(f"容器部署失败: {e}")
return False
def stop_and_remove_container(self, name: str) -> bool:
"""停止并删除容器"""
try:
container = self.client.containers.get(name)
container.stop()
container.remove()
print(f"已停止并删除容器: {name}")
return True
except Exception:
# 容器不存在或已停止
return True
def get_container_status(self, name: str) -> Optional[Dict]:
"""获取容器状态"""
try:
container = self.client.containers.get(name)
return {
'id': container.id[:12],
'name': container.name,
'status': container.status,
'image': container.image.tags[0] if container.image.tags else 'unknown',
'ports': container.ports,
'created': container.attrs['Created'],
'started': container.attrs['State']['StartedAt']
}
except Exception:
return None
def update_container(self, config: ContainerConfig) -> bool:
"""更新容器(重新部署)"""
return self.deploy_container(config)
29.3 多阶段构建
29.3.1 开发和生产分离
# Dockerfile.multi-stage
# 开发依赖阶段
FROM python:3.11-slim as dev-deps
WORKDIR /app
# 安装开发工具
RUN apt-get update && apt-get install -y \
gcc \
g++ \
git \
vim \
&& rm -rf /var/lib/apt/lists/*
# 复制开发依赖
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt
# 生产依赖阶段
FROM python:3.11-slim as prod-deps
WORKDIR /app
# 安装构建依赖
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 复制生产依赖
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 测试阶段
FROM dev-deps as test
# 复制源代码
COPY . .
# 运行测试
RUN python -m pytest tests/ -v
RUN python -m flake8 .
RUN python -m mypy .
# 构建阶段
FROM prod-deps as build
# 复制源代码
COPY . .
# 编译Python文件
RUN python -m compileall .
# 生产阶段
FROM python:3.11-slim as production
# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PATH=/home/app/.local/bin:$PATH
# 安装运行时依赖
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# 创建应用用户
RUN useradd --create-home --shell /bin/bash app
# 设置工作目录
WORKDIR /app
# 从构建阶段复制依赖和代码
COPY --from=build /root/.local /home/app/.local
COPY --from=build --chown=app:app /app .
# 切换到应用用户
USER app
# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
29.3.2 构建脚本
# build_script.py
import subprocess
import sys
import argparse
from pathlib import Path
from typing import List, Optional
class DockerBuilder:
"""Docker构建器"""
def __init__(self, dockerfile_path: str = "."):
self.dockerfile_path = Path(dockerfile_path)
self.build_args = {}
self.tags = []
def add_build_arg(self, key: str, value: str) -> "DockerBuilder":
"""添加构建参数"""
self.build_args[key] = value
return self
def add_tag(self, tag: str) -> "DockerBuilder":
"""添加标签"""
self.tags.append(tag)
return self
def build(self,
dockerfile: str = "Dockerfile",
target: Optional[str] = None,
no_cache: bool = False,
pull: bool = False) -> bool:
"""构建镜像"""
cmd = ["docker", "build"]
# 添加构建参数
for key, value in self.build_args.items():
cmd.extend(["--build-arg", f"{key}={value}"])
# 添加标签
for tag in self.tags:
cmd.extend(["-t", tag])
# 添加选项
if target:
cmd.extend(["--target", target])
if no_cache:
cmd.append("--no-cache")
if pull:
cmd.append("--pull")
# 添加Dockerfile和上下文
cmd.extend(["-f", dockerfile, str(self.dockerfile_path)])
print(f"执行构建命令: {' '.join(cmd)}")
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print("构建成功!")
print(result.stdout)
return True
except subprocess.CalledProcessError as e:
print(f"构建失败: {e}")
print(e.stderr)
return False
def push(self, tag: str) -> bool:
"""推送镜像"""
cmd = ["docker", "push", tag]
print(f"推送镜像: {tag}")
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print("推送成功!")
return True
except subprocess.CalledProcessError as e:
print(f"推送失败: {e}")
print(e.stderr)
return False
def build_development():
"""构建开发环境镜像"""
builder = DockerBuilder()
success = (builder
.add_tag("myapp:dev")
.add_tag("myapp:latest-dev")
.add_build_arg("BUILD_ENV", "development")
.build(dockerfile="Dockerfile.multi-stage", target="dev-deps"))
if success:
print("开发环境镜像构建完成")
else:
sys.exit(1)
def build_test():
"""构建测试镜像"""
builder = DockerBuilder()
success = (builder
.add_tag("myapp:test")
.add_build_arg("BUILD_ENV", "test")
.build(dockerfile="Dockerfile.multi-stage", target="test"))
if success:
print("测试镜像构建完成")
else:
sys.exit(1)
def build_production():
"""构建生产环境镜像"""
builder = DockerBuilder()
# 获取版本号
try:
result = subprocess.run(["git", "describe", "--tags", "--always"],
capture_output=True, text=True, check=True)
version = result.stdout.strip()
except:
version = "latest"
success = (builder
.add_tag(f"myapp:{version}")
.add_tag("myapp:latest")
.add_build_arg("BUILD_ENV", "production")
.add_build_arg("VERSION", version)
.build(dockerfile="Dockerfile.multi-stage", target="production", pull=True))
if success:
print(f"生产环境镜像构建完成: myapp:{version}")
# 可选:推送到仓库
if input("是否推送到仓库? (y/N): ").lower() == 'y':
builder.push(f"myapp:{version}")
builder.push("myapp:latest")
else:
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Docker构建脚本")
parser.add_argument("target", choices=["dev", "test", "prod"],
help="构建目标")
args = parser.parse_args()
if args.target == "dev":
build_development()
elif args.target == "test":
build_test()
elif args.target == "prod":
build_production()
if __name__ == "__main__":
main()
29.4 Docker Compose编排
29.4.1 基础编排配置
# docker-compose.yml
version: '3.8'
services:
# 数据库服务
postgres:
image: postgres:15
container_name: blog_postgres
environment:
POSTGRES_DB: ${POSTGRES_DB:-blog_db}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
ports:
- "${POSTGRES_PORT:-5432}:5432"
networks:
- blog_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
container_name: blog_redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-}
volumes:
- redis_data:/data
ports:
- "${REDIS_PORT:-6379}:6379"
networks:
- blog_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# FastAPI应用
app:
build:
context: .
dockerfile: Dockerfile
target: production
args:
BUILD_ENV: production
container_name: blog_app
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@postgres:5432/${POSTGRES_DB:-blog_db}
- REDIS_URL=redis://:${REDIS_PASSWORD:-}@redis:6379
- SECRET_KEY=${SECRET_KEY:-your-secret-key}
- DEBUG=${DEBUG:-false}
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-localhost,127.0.0.1}
ports:
- "${APP_PORT:-8000}:8000"
volumes:
- ./logs:/app/logs
- ./uploads:/app/uploads
networks:
- blog_network
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
# Nginx反向代理
nginx:
image: nginx:alpine
container_name: blog_nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./static:/var/www/static:ro
- ./uploads:/var/www/uploads:ro
ports:
- "${NGINX_PORT:-80}:80"
- "${NGINX_SSL_PORT:-443}:443"
networks:
- blog_network
depends_on:
- app
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
volumes:
postgres_data:
driver: local
redis_data:
driver: local
networks:
blog_network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
29.4.2 环境配置文件
# .env
# 数据库配置
POSTGRES_DB=blog_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_secure_password
POSTGRES_PORT=5432
# Redis配置
REDIS_PASSWORD=your_redis_password
REDIS_PORT=6379
# 应用配置
SECRET_KEY=your-very-secure-secret-key
DEBUG=false
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com
APP_PORT=8000
# Nginx配置
NGINX_PORT=80
NGINX_SSL_PORT=443
# 日志级别
LOG_LEVEL=INFO
# 邮件配置
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-email-password
# .env.dev
# 开发环境配置
POSTGRES_DB=blog_dev_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_PORT=5432
REDIS_PASSWORD=
REDIS_PORT=6379
SECRET_KEY=dev-secret-key
DEBUG=true
ALLOWED_HOSTS=*
APP_PORT=8000
NGINX_PORT=80
NGINX_SSL_PORT=443
LOG_LEVEL=DEBUG
29.4.3 开发环境编排
# docker-compose.dev.yml
version: '3.8'
services:
postgres:
ports:
- "5432:5432"
environment:
POSTGRES_DB: blog_dev_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
redis:
ports:
- "6379:6379"
command: redis-server --appendonly yes
app:
build:
target: dev-deps
args:
BUILD_ENV: development
environment:
- DATABASE_URL=postgresql://postgres:password@postgres:5432/blog_dev_db
- REDIS_URL=redis://redis:6379
- SECRET_KEY=dev-secret-key
- DEBUG=true
- RELOAD=true
volumes:
- .:/app
- /app/__pycache__
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
ports:
- "8000:8000"
# 开发工具
adminer:
image: adminer
container_name: blog_adminer
ports:
- "8080:8080"
networks:
- blog_network
depends_on:
- postgres
# Redis管理界面
redis-commander:
image: rediscommander/redis-commander:latest
container_name: blog_redis_commander
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- "8081:8081"
networks:
- blog_network
depends_on:
- redis
29.4.4 生产环境编排
# docker-compose.prod.yml
version: '3.8'
services:
postgres:
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.5'
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
redis:
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.25'
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
app:
build:
target: production
args:
BUILD_ENV: production
deploy:
replicas: 2
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
- REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
- SECRET_KEY=${SECRET_KEY}
- DEBUG=false
- WORKERS=4
volumes:
- ./logs:/app/logs
- ./uploads:/app/uploads
command: gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
nginx:
volumes:
- ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
deploy:
resources:
limits:
memory: 128M
cpus: '0.25'
reservations:
memory: 64M
cpus: '0.1'
# 监控服务
prometheus:
image: prom/prometheus:latest
container_name: blog_prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "9090:9090"
networks:
- blog_network
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle'
grafana:
image: grafana/grafana:latest
container_name: blog_grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:-admin}
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana:/etc/grafana/provisioning
ports:
- "3000:3000"
networks:
- blog_network
depends_on:
- prometheus
volumes:
prometheus_data:
grafana_data:
29.5 容器编排管理
29.5.1 编排管理脚本
# compose_manager.py
import subprocess
import sys
import os
import yaml
from typing import List, Optional, Dict
from pathlib import Path
class ComposeManager:
"""Docker Compose管理器"""
def __init__(self, project_name: str = None, env_file: str = None):
self.project_name = project_name
self.env_file = env_file
self.base_cmd = ["docker-compose"]
if project_name:
self.base_cmd.extend(["-p", project_name])
if env_file:
self.base_cmd.extend(["--env-file", env_file])
def _run_command(self, cmd: List[str], check: bool = True) -> subprocess.CompletedProcess:
"""执行命令"""
full_cmd = self.base_cmd + cmd
print(f"执行命令: {' '.join(full_cmd)}")
return subprocess.run(full_cmd, check=check, capture_output=True, text=True)
def up(self,
services: Optional[List[str]] = None,
detach: bool = True,
build: bool = False,
force_recreate: bool = False) -> bool:
"""启动服务"""
cmd = ["up"]
if detach:
cmd.append("-d")
if build:
cmd.append("--build")
if force_recreate:
cmd.append("--force-recreate")
if services:
cmd.extend(services)
try:
result = self._run_command(cmd)
print("服务启动成功")
return True
except subprocess.CalledProcessError as e:
print(f"服务启动失败: {e}")
print(e.stderr)
return False
def down(self,
remove_volumes: bool = False,
remove_images: str = None) -> bool:
"""停止服务"""
cmd = ["down"]
if remove_volumes:
cmd.append("-v")
if remove_images:
cmd.extend(["--rmi", remove_images])
try:
result = self._run_command(cmd)
print("服务停止成功")
return True
except subprocess.CalledProcessError as e:
print(f"服务停止失败: {e}")
print(e.stderr)
return False
def restart(self, services: Optional[List[str]] = None) -> bool:
"""重启服务"""
cmd = ["restart"]
if services:
cmd.extend(services)
try:
result = self._run_command(cmd)
print("服务重启成功")
return True
except subprocess.CalledProcessError as e:
print(f"服务重启失败: {e}")
print(e.stderr)
return False
def build(self,
services: Optional[List[str]] = None,
no_cache: bool = False,
pull: bool = False) -> bool:
"""构建服务"""
cmd = ["build"]
if no_cache:
cmd.append("--no-cache")
if pull:
cmd.append("--pull")
if services:
cmd.extend(services)
try:
result = self._run_command(cmd)
print("服务构建成功")
return True
except subprocess.CalledProcessError as e:
print(f"服务构建失败: {e}")
print(e.stderr)
return False
def logs(self,
services: Optional[List[str]] = None,
follow: bool = False,
tail: Optional[int] = None) -> bool:
"""查看日志"""
cmd = ["logs"]
if follow:
cmd.append("-f")
if tail:
cmd.extend(["--tail", str(tail)])
if services:
cmd.extend(services)
try:
result = self._run_command(cmd, check=False)
print(result.stdout)
return True
except Exception as e:
print(f"查看日志失败: {e}")
return False
def ps(self) -> bool:
"""查看服务状态"""
try:
result = self._run_command(["ps"])
print(result.stdout)
return True
except subprocess.CalledProcessError as e:
print(f"查看状态失败: {e}")
return False
def exec(self, service: str, command: List[str], interactive: bool = True) -> bool:
"""在服务中执行命令"""
cmd = ["exec"]
if interactive:
cmd.append("-it")
cmd.append(service)
cmd.extend(command)
try:
# 对于交互式命令,不捕获输出
if interactive:
subprocess.run(self.base_cmd + cmd, check=True)
else:
result = self._run_command(cmd)
print(result.stdout)
return True
except subprocess.CalledProcessError as e:
print(f"执行命令失败: {e}")
return False
def scale(self, service_replicas: Dict[str, int]) -> bool:
"""扩缩容服务"""
cmd = ["up", "-d", "--scale"]
for service, replicas in service_replicas.items():
cmd.append(f"{service}={replicas}")
try:
result = self._run_command(cmd)
print("服务扩缩容成功")
return True
except subprocess.CalledProcessError as e:
print(f"服务扩缩容失败: {e}")
print(e.stderr)
return False
def config(self) -> Optional[Dict]:
"""验证并查看配置"""
try:
result = self._run_command(["config"])
config = yaml.safe_load(result.stdout)
return config
except subprocess.CalledProcessError as e:
print(f"配置验证失败: {e}")
print(e.stderr)
return None
except yaml.YAMLError as e:
print(f"配置解析失败: {e}")
return None
class DeploymentManager:
"""部署管理器"""
def __init__(self):
self.environments = {
'dev': {
'compose_files': ['docker-compose.yml', 'docker-compose.dev.yml'],
'env_file': '.env.dev'
},
'staging': {
'compose_files': ['docker-compose.yml', 'docker-compose.staging.yml'],
'env_file': '.env.staging'
},
'prod': {
'compose_files': ['docker-compose.yml', 'docker-compose.prod.yml'],
'env_file': '.env'
}
}
def deploy(self, environment: str, services: Optional[List[str]] = None) -> bool:
"""部署到指定环境"""
if environment not in self.environments:
print(f"未知环境: {environment}")
return False
env_config = self.environments[environment]
# 设置compose文件
compose_files = env_config['compose_files']
env_file = env_config['env_file']
# 检查文件是否存在
for file in compose_files + [env_file]:
if not Path(file).exists():
print(f"文件不存在: {file}")
return False
# 创建管理器
manager = ComposeManager(
project_name=f"blog_{environment}",
env_file=env_file
)
# 添加compose文件参数
for file in compose_files:
manager.base_cmd.extend(["-f", file])
print(f"开始部署到 {environment} 环境...")
# 构建镜像
if not manager.build(services, no_cache=environment == 'prod'):
return False
# 启动服务
if not manager.up(services, build=False, force_recreate=environment == 'prod'):
return False
print(f"部署到 {environment} 环境成功")
return True
def rollback(self, environment: str, services: Optional[List[str]] = None) -> bool:
"""回滚部署"""
if environment not in self.environments:
print(f"未知环境: {environment}")
return False
env_config = self.environments[environment]
manager = ComposeManager(
project_name=f"blog_{environment}",
env_file=env_config['env_file']
)
for file in env_config['compose_files']:
manager.base_cmd.extend(["-f", file])
print(f"开始回滚 {environment} 环境...")
# 停止当前服务
if not manager.down():
return False
# 重新启动服务(使用之前的镜像)
if not manager.up(services):
return False
print(f"回滚 {environment} 环境成功")
return True
29.5.2 部署脚本
#!/bin/bash
# deploy.sh
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
log_info "检查依赖..."
if ! command -v docker &> /dev/null; then
log_error "Docker未安装"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
log_error "Docker Compose未安装"
exit 1
fi
log_info "依赖检查通过"
}
# 检查环境文件
check_env_file() {
local env_file=$1
if [[ ! -f "$env_file" ]]; then
log_error "环境文件不存在: $env_file"
exit 1
fi
log_info "环境文件检查通过: $env_file"
}
# 备份数据
backup_data() {
local environment=$1
local backup_dir="backups/$(date +%Y%m%d_%H%M%S)"
log_info "创建数据备份..."
mkdir -p "$backup_dir"
# 备份数据库
docker-compose -p "blog_${environment}" exec -T postgres pg_dump -U postgres blog_db > "$backup_dir/database.sql"
# 备份上传文件
if [[ -d "uploads" ]]; then
cp -r uploads "$backup_dir/"
fi
log_info "数据备份完成: $backup_dir"
}
# 部署到开发环境
deploy_dev() {
log_info "部署到开发环境..."
check_env_file ".env.dev"
docker-compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.dev -p blog_dev up -d --build
log_info "开发环境部署完成"
log_info "应用地址: http://localhost:8000"
log_info "数据库管理: http://localhost:8080"
log_info "Redis管理: http://localhost:8081"
}
# 部署到生产环境
deploy_prod() {
log_info "部署到生产环境..."
check_env_file ".env"
# 创建备份
backup_data "prod"
# 构建生产镜像
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache
# 启动服务
docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env -p blog_prod up -d
# 等待服务启动
log_info "等待服务启动..."
sleep 30
# 健康检查
if curl -f http://localhost/health > /dev/null 2>&1; then
log_info "生产环境部署成功"
log_info "应用地址: http://localhost"
else
log_error "健康检查失败,请检查服务状态"
exit 1
fi
}
# 停止服务
stop_services() {
local environment=$1
log_info "停止 $environment 环境服务..."
case $environment in
"dev")
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -p blog_dev down
;;
"prod")
docker-compose -f docker-compose.yml -f docker-compose.prod.yml -p blog_prod down
;;
*)
log_error "未知环境: $environment"
exit 1
;;
esac
log_info "$environment 环境服务已停止"
}
# 查看日志
view_logs() {
local environment=$1
local service=$2
case $environment in
"dev")
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -p blog_dev logs -f $service
;;
"prod")
docker-compose -f docker-compose.yml -f docker-compose.prod.yml -p blog_prod logs -f $service
;;
*)
log_error "未知环境: $environment"
exit 1
;;
esac
}
# 显示帮助
show_help() {
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " dev 部署到开发环境"
echo " prod 部署到生产环境"
echo " stop <env> 停止指定环境的服务"
echo " logs <env> [service] 查看指定环境的日志"
echo " backup <env> 备份指定环境的数据"
echo " help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 dev # 部署开发环境"
echo " $0 prod # 部署生产环境"
echo " $0 stop dev # 停止开发环境"
echo " $0 logs prod app # 查看生产环境应用日志"
}
# 主函数
main() {
check_dependencies
case "${1:-help}" in
"dev")
deploy_dev
;;
"prod")
deploy_prod
;;
"stop")
if [[ -z "$2" ]]; then
log_error "请指定环境"
show_help
exit 1
fi
stop_services "$2"
;;
"logs")
if [[ -z "$2" ]]; then
log_error "请指定环境"
show_help
exit 1
fi
view_logs "$2" "$3"
;;
"backup")
if [[ -z "$2" ]]; then
log_error "请指定环境"
show_help
exit 1
fi
backup_data "$2"
;;
"help")
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
main "$@"
29.6 生产环境优化
29.6.1 性能优化配置
# production_config.py
from typing import Dict, Any
import os
class ProductionConfig:
"""生产环境配置"""
# 应用配置
APP_CONFIG = {
'workers': int(os.getenv('WORKERS', '4')),
'worker_class': 'uvicorn.workers.UvicornWorker',
'worker_connections': int(os.getenv('WORKER_CONNECTIONS', '1000')),
'max_requests': int(os.getenv('MAX_REQUESTS', '1000')),
'max_requests_jitter': int(os.getenv('MAX_REQUESTS_JITTER', '100')),
'timeout': int(os.getenv('TIMEOUT', '30')),
'keepalive': int(os.getenv('KEEPALIVE', '2')),
'bind': f"0.0.0.0:{os.getenv('PORT', '8000')}",
'preload_app': True,
'access_log': True,
'error_log': '-',
'log_level': os.getenv('LOG_LEVEL', 'info').lower()
}
# 数据库连接池配置
DATABASE_CONFIG = {
'pool_size': int(os.getenv('DB_POOL_SIZE', '20')),
'max_overflow': int(os.getenv('DB_MAX_OVERFLOW', '30')),
'pool_timeout': int(os.getenv('DB_POOL_TIMEOUT', '30')),
'pool_recycle': int(os.getenv('DB_POOL_RECYCLE', '3600')),
'pool_pre_ping': True
}
# Redis配置
REDIS_CONFIG = {
'max_connections': int(os.getenv('REDIS_MAX_CONNECTIONS', '50')),
'retry_on_timeout': True,
'socket_timeout': int(os.getenv('REDIS_SOCKET_TIMEOUT', '5')),
'socket_connect_timeout': int(os.getenv('REDIS_CONNECT_TIMEOUT', '5'))
}
# 缓存配置
CACHE_CONFIG = {
'default_timeout': int(os.getenv('CACHE_DEFAULT_TIMEOUT', '300')),
'key_prefix': os.getenv('CACHE_KEY_PREFIX', 'blog:'),
'version': int(os.getenv('CACHE_VERSION', '1'))
}
@classmethod
def get_gunicorn_config(cls) -> Dict[str, Any]:
"""获取Gunicorn配置"""
return cls.APP_CONFIG
@classmethod
def get_database_url(cls) -> str:
"""获取数据库URL"""
base_url = os.getenv('DATABASE_URL')
if not base_url:
raise ValueError("DATABASE_URL环境变量未设置")
# 添加连接池参数
pool_params = [
f"pool_size={cls.DATABASE_CONFIG['pool_size']}",
f"max_overflow={cls.DATABASE_CONFIG['max_overflow']}",
f"pool_timeout={cls.DATABASE_CONFIG['pool_timeout']}",
f"pool_recycle={cls.DATABASE_CONFIG['pool_recycle']}"
]
separator = '&' if '?' in base_url else '?'
return f"{base_url}{separator}{'&'.join(pool_params)}"
29.6.2 Gunicorn配置文件
# gunicorn.conf.py
import multiprocessing
import os
# 服务器套接字
bind = f"0.0.0.0:{os.getenv('PORT', '8000')}"
backlog = 2048
# 工作进程
workers = int(os.getenv('WORKERS', multiprocessing.cpu_count() * 2 + 1))
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 100
# 超时设置
timeout = 30
keepalive = 2
graceful_timeout = 30
# 预加载应用
preload_app = True
# 日志配置
accesslog = "-"
errorlog = "-"
loglevel = os.getenv('LOG_LEVEL', 'info').lower()
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# 进程命名
proc_name = 'fastapi-blog'
# 安全设置
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190
# 性能调优
worker_tmp_dir = '/dev/shm'
# 钩子函数
def on_starting(server):
"""服务器启动时调用"""
server.log.info("FastAPI Blog服务器启动中...")
def on_reload(server):
"""配置重载时调用"""
server.log.info("配置已重载")
def when_ready(server):
"""服务器准备就绪时调用"""
server.log.info("FastAPI Blog服务器已就绪")
def worker_int(worker):
"""工作进程收到SIGINT信号时调用"""
worker.log.info(f"工作进程 {worker.pid} 收到中断信号")
def pre_fork(server, worker):
"""工作进程fork前调用"""
server.log.info(f"工作进程 {worker.pid} 即将启动")
def post_fork(server, worker):
"""工作进程fork后调用"""
server.log.info(f"工作进程 {worker.pid} 已启动")
def worker_abort(worker):
"""工作进程异常终止时调用"""
worker.log.error(f"工作进程 {worker.pid} 异常终止")
29.6.3 Nginx配置
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
# 基础设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 上游服务器
upstream fastapi_backend {
least_conn;
server app:8000 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# 限流配置
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# 主服务器配置
server {
listen 80;
server_name localhost;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 静态文件
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 上传文件
location /uploads/ {
alias /var/www/uploads/;
expires 1M;
add_header Cache-Control "public";
}
# 健康检查
location /health {
proxy_pass http://fastapi_backend;
access_log off;
}
# API限流
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# 登录限流
location /api/auth/login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 默认代理
location / {
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 缓存设置
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
}
29.6.4 监控配置
# monitoring/docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
networks:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- monitoring
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
networks:
- monitoring
volumes:
prometheus_data:
grafana_data:
networks:
monitoring:
driver: bridge
# prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "rules/*.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'fastapi-blog'
static_configs:
- targets: ['app:8000']
metrics_path: '/metrics'
scrape_interval: 5s
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'postgres'
static_configs:
- targets: ['postgres:5432']
- job_name: 'redis'
static_configs:
- targets: ['redis:6379']
29.7 实践练习
练习1:多环境部署
- 创建测试环境的Docker配置
- 实现自动化的环境切换脚本
- 配置不同环境的资源限制
练习2:容器优化
- 优化Docker镜像大小
- 实现多阶段构建优化
- 配置容器健康检查
练习3:监控集成
- 集成Prometheus监控
- 配置Grafana仪表板
- 设置告警规则
练习4:安全加固
- 实现容器安全扫描
- 配置网络安全策略
- 实现密钥管理
29.8 本章小结
本章详细介绍了FastAPI应用的容器化部署:
- Docker基础:学习了Docker环境配置和Dockerfile编写
- 单容器部署:掌握了基础和优化的容器配置
- 多阶段构建:实现了开发和生产环境的分离
- Docker Compose:学习了多服务编排和环境管理
- 容器编排:实现了完整的部署和管理脚本
- 生产优化:配置了性能优化和监控系统
通过本章学习,你应该能够:
- 编写高效的Dockerfile
- 使用Docker Compose管理多服务应用
- 实现多环境的容器化部署
- 配置生产环境的性能优化
- 集成监控和日志系统
下一章我们将学习云平台部署,包括AWS、Azure和Google Cloud的部署策略。
更多推荐



所有评论(0)