千江明月头像

Agent 部署配置:环境配置·YAML·环境变量·热重载·多环境管理

部署配置:环境配置·YAML·环境变量·热重载·多环境管理

Agent部署与运维
内容:完整Docker Compose编排、Kubernetes部署、环境配置管理、配置热重载、多环境管理
部署配置体系,含Docker Compose、K8s部署、环境配置管理、配置热重载、多环境管理、Secret管理、健康检查、优雅关闭、CI/CD流水线

字段值
文档版本v1.0
创建日期2026-08-19
适用范围AI Agent生产部署配置
技术栈Docker / Kubernetes / Python / YAML

一、Docker Compose 编排

生产环境Agent服务通常包含:Agent应用、PostgreSQL(状态)、Redis(会话缓存)、Chroma(向量库)四个容器。

services:
  agent:
    build: .
    ports: ["8000:8000"]
    environment:
      - DATABASE_URL=postgresql://agent:agent@db:5432/agent
      - REDIS_URL=redis://redis:6379
      - CHROMA_HOST=chroma
    depends_on: [db, redis, chroma]
    deploy:
      replicas: 3
      resources: {limits: {memory: 2G, cpus: '2'}}
      restart_policy: {condition: on-failure, max_attempts: 3}

  db:
    image: postgres:16
    environment: {POSTGRES_USER: agent, POSTGRES_PASSWORD: agent, POSTGRES_DB: agent}
    volumes: [pgdata:/var/lib/postgresql/data]

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru

  chroma:
    image: chromadb/chroma:latest
    ports: ["8001:8000"]
    volumes: [chromadata:/chroma/chroma]

volumes: {pgdata: , chromadata: }

要点:依赖编排(depends_on)、资源限制(2G/2CPU)、失败重启策略、数据卷持久化(pgdata/chromadata)。

1.1 完整生产级 docker-compose.yml

# docker-compose.prod.yml - 生产环境完整编排
version: "3.9"

services:
  # ============ Agent 应用层 ============
  agent:
    build:
      context: .
      dockerfile: Dockerfile.prod
      args:
        - APP_VERSION=${APP_VERSION:-latest}
    image: ${REGISTRY:-registry.cn}/agent:${APP_VERSION:-latest}
    container_name: agent-app
    ports:
      - "${AGENT_PORT:-8000}:8000"
    environment:
      - ENVIRONMENT=production
      - LOG_LEVEL=INFO
      - DATABASE_URL=postgresql://agent:${DB_PASSWORD}@db:5432/agent
      - REDIS_URL=redis://redis:6379/0
      - CHROMA_HOST=chroma
      - CHROMA_PORT=8000
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - OPENAI_MODEL=gpt-4o
      - MAX_CONCURRENT_REQUESTS=100
      - REQUEST_TIMEOUT=300
      - ENABLE_METRICS=true
      - METRICS_PORT=9090
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
      chroma:
        condition: service_started
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 2G
          cpus: "2"
        reservations:
          memory: 512M
          cpus: "0.5"
      restart_policy:
        condition: on-failure
        max_attempts: 5
        delay: 10s
        window: 60s
      update_config:
        parallelism: 1
        delay: 30s
        order: start-first
        failure_action: rollback
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"
    networks:
      - agent-network

  # ============ 数据库层 ============
  db:
    image: postgres:16
    container_name: agent-db
    environment:
      POSTGRES_USER: agent
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: agent
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d
    ports:
      - "${DB_PORT:-5432}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U agent -d agent"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1"
      placement:
        constraints:
          - node.role != manager
    backup:
      - schedule: "0 2 * * *"
        command: "pg_dump -U agent agent | gzip > /backup/db_$(date +%Y%m%d).sql.gz"
    networks:
      - agent-network

  # ============ 缓存层 ============
  redis:
    image: redis:7-alpine
    container_name: agent-redis
    command: >
      redis-server
      --maxmemory 512mb
      --maxmemory-policy allkeys-lru
      --appendonly yes
      --appendfsync everysec
      --requirepass ${REDIS_PASSWORD}
      --save 900 1
      --save 300 10
      --save 60 10000
    volumes:
      - redisdata:/data
    ports:
      - "${REDIS_PORT:-6379}:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 768M
          cpus: "0.5"
    networks:
      - agent-network

  # ============ 向量数据库 ============
  chroma:
    image: chromadb/chroma:latest
    container_name: agent-chroma
    environment:
      - CHROMA_SERVER_HOST=0.0.0.0
      - CHROMA_SERVER_HTTP_PORT=8000
      - CHROMA_PERSISTENCE_PATH=/chroma/chroma
    volumes:
      - chromadata:/chroma/chroma
    ports:
      - "${CHROMA_PORT:-8001}:8000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/heartbeat"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1"
    networks:
      - agent-network

  # ============ Nginx 反向代理 ============
  nginx:
    image: nginx:alpine
    container_name: agent-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - nginx_logs:/var/log/nginx
    depends_on:
      - agent
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.25"
    networks:
      - agent-network

  # ============ Prometheus 监控 ============
  prometheus:
    image: prom/prometheus:latest
    container_name: agent-prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    ports:
      - "${PROMETHEUS_PORT:-9091}:9090"
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=30d"
      - "--web.enable-lifecycle"
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    networks:
      - agent-network

  # ============ Grafana 仪表盘 ============
  grafana:
    image: grafana/grafana:latest
    container_name: agent-grafana
    environment:
      - GF_SECURITY_ADMIN_USER=${GRAFANA_USER:-admin}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    ports:
      - "${GRAFANA_PORT:-3000}:3000"
    depends_on:
      - prometheus
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.25"
    networks:
      - agent-network

  # ============ Jaeger 链路追踪 ============
  jaeger:
    image: jaegertracing/all-in-one:1
    container_name: agent-jaeger
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - JAEGER_AGENT_HOST=jaeger
    ports:
      - "${JAEGER_UI_PORT:-16686}:16686"
      - "${JAEGER_OTLP_PORT:-4317}:4317"
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    networks:
      - agent-network

volumes:
  pgdata:
    driver: local
  redisdata:
    driver: local
  chromadata:
    driver: local
  nginx_logs:
    driver: local
  prometheus_data:
    driver: local
  grafana_data:
    driver: local

networks:
  agent-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

1.2 开发环境 docker-compose.dev.yml

# docker-compose.dev.yml - 开发环境
version: "3.9"

services:
  agent:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: agent-dev
    ports:
      - "8000:8000"
      - "5678:5678"  # debugpy
    environment:
      - ENVIRONMENT=development
      - LOG_LEVEL=DEBUG
      - DATABASE_URL=postgresql://agent:agent@db:5432/agent
      - REDIS_URL=redis://redis:6379/0
      - CHROMA_HOST=chroma
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - ENABLE_HOT_RELOAD=true
      - PYTHONUNBUFFERED=1
    volumes:
      - ./src:/app/src
      - ./tests:/app/tests
      - ./config:/app/config
    command: >
      python -m debugpy --listen 0.0.0.0:5678
      --wait-for-client -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
    depends_on:
      - db
      - redis
      - chroma
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1"

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: agent
      POSTGRES_PASSWORD: agent
      POSTGRES_DB: agent
    ports:
      - "5432:5432"
    volumes:
      - pgdata_dev:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  chroma:
    image: chromadb/chroma:latest
    ports:
      - "8001:8000"
    volumes:
      - chromadata_dev:/chroma/chroma

volumes:
  pgdata_dev:
  chromadata_dev:

1.3 使用 docker-compose override 合并配置

# docker-compose.override.yml - 自动覆盖(开发时自动加载)
version: "3.9"

services:
  agent:
    environment:
      - LOG_LEVEL=DEBUG
      - ENABLE_SWAGGER=true
    volumes:
      - ./src:/app/src
    command: python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000

  db:
    ports:
      - "5432:5432"  # 开发时暴露数据库端口
# 启动开发环境(自动合并 override)
docker compose up -d

# 启动生产环境(指定文件)
docker compose -f docker-compose.prod.yml up -d

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs -f agent

# 重新构建
docker compose build --no-cache agent

# 扩缩容
docker compose up -d --scale agent=5

二、Kubernetes 部署

2.1 基础 Deployment

apiVersion: apps/v1
kind: Deployment
metadata: {name: agent-service}
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: agent
        image: my-agent:latest
        ports: [{containerPort: 8000}]
        env:
        - name: OPENAI_API_KEY
          valueFrom: {secretKeyRef: {name: agent-secrets, key: openai-api-key}}
        resources:
          requests: {memory: "512Mi", cpu: "500m"}
          limits: {memory: "2Gi", cpu: "2000m"}
        livenessProbe: {httpGet: {path: /health, port: 8000}}
        readinessProbe: {httpGet: {path: /ready, port: 8000}}

K8s要点:Secret管理API Key、资源requests/limits、liveness探针(存活检查)、readiness探针(就绪检查)。

2.2 完整生产级 Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-service
  namespace: agent-prod
  labels:
    app: agent
    version: v1
    tier: backend
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: agent
  template:
    metadata:
      labels:
        app: agent
        version: v1
        tier: backend
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: agent-sa
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
      - name: agent
        image: registry.cn/agent:v1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8000
          name: http
          protocol: TCP
        - containerPort: 9090
          name: metrics
          protocol: TCP
        envFrom:
        - configMapRef:
            name: agent-config
        env:
        - name: OPENAI_API_KEY
          valueFrom:
            secretKeyRef:
              name: agent-secrets
              key: openai-api-key
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: agent-secrets
              key: database-url
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: agent-secrets
              key: redis-url
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 40
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3
          successThreshold: 1
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        startupProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 0
          periodSeconds: 10
          failureThreshold: 30
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15 && curl -X POST http://localhost:8000/shutdown"]
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
          readOnly: true
        - name: tmp-volume
          mountPath: /tmp
        - name: cache-volume
          mountPath: /app/.cache
      volumes:
      - name: config-volume
        configMap:
          name: agent-config-files
      - name: tmp-volume
        emptyDir: {}
      - name: cache-volume
        emptyDir:
          sizeLimit: 1Gi
      terminationGracePeriodSeconds: 60
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: agent
              topologyKey: kubernetes.io/hostname
      tolerations:
      - key: "dedicated"
        operator: "Equal"
        value: "agent"
        effect: "NoSchedule"
      nodeSelector:
        node-role: worker

2.3 Service 定义

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: agent-service
  namespace: agent-prod
  labels:
    app: agent
spec:
  type: ClusterIP
  selector:
    app: agent
  ports:
  - name: http
    port: 80
    targetPort: 8000
    protocol: TCP
  - name: metrics
    port: 9090
    targetPort: 9090
    protocol: TCP
---
# Headless Service(用于 StatefulSet)
apiVersion: v1
kind: Service
metadata:
  name: agent-headless
  namespace: agent-prod
spec:
  clusterIP: None
  selector:
    app: agent
  ports:
  - port: 8000
    name: http

2.4 Ingress 配置

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: agent-ingress
  namespace: agent-prod
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Request-ID $request_id;
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - agent.example.com
    secretName: agent-tls
  rules:
  - host: agent.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: agent-service
            port:
              number: 80
      - path: /ws
        pathType: Prefix
        backend:
          service:
            name: agent-service
            port:
              number: 80

2.5 HPA 自动扩缩容

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: agent-hpa
  namespace: agent-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: agent-service
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "100"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
      - type: Pods
        value: 4
        periodSeconds: 30
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60
      selectPolicy: Min

2.6 ConfigMap 与 Secret

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: agent-config
  namespace: agent-prod
data:
  ENVIRONMENT: "production"
  LOG_LEVEL: "INFO"
  OPENAI_MODEL: "gpt-4o"
  MAX_CONCURRENT_REQUESTS: "100"
  REQUEST_TIMEOUT: "300"
  ENABLE_METRICS: "true"
  METRICS_PORT: "9090"
  CHROMA_HOST: "chroma-service"
  CHROMA_PORT: "8000"
  JAEGER_ENDPOINT: "http://jaeger-collector:14268/api/traces"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  DB_HOST: "postgres-service"
  DB_PORT: "5432"
---
# k8s/config-files.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: agent-config-files
  namespace: agent-prod
data:
  agent.yaml: |
    model:
      name: gpt-4o
      temperature: 0.7
      max_tokens: 4096
    tools:
      max_concurrent: 5
      timeout: 60
    memory:
      type: persistent
      max_entries: 100
    logging:
      level: INFO
      format: json
  prompts.yaml: |
    system: |
      You are a helpful AI assistant.
    greeting: "Hello! How can I help you?"
---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: agent-secrets
  namespace: agent-prod
type: Opaque
stringData:
  openai-api-key: "sk-xxxxxxxxxxxxxxxxxxxx"
  database-url: "postgresql://agent:password@postgres-service:5432/agent"
  redis-url: "redis://:password@redis-service:6379/0"
  jwt-secret: "your-jwt-secret-key"

2.7 PodDisruptionBudget

# k8s/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: agent-pdb
  namespace: agent-prod
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: agent

2.8 StatefulSet(有状态服务)

# k8s/statefulset.yaml - 用于有状态的Agent服务
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: agent-stateful
  namespace: agent-prod
spec:
  serviceName: agent-headless
  replicas: 3
  selector:
    matchLabels:
      app: agent-stateful
  template:
    metadata:
      labels:
        app: agent-stateful
    spec:
      containers:
      - name: agent
        image: registry.cn/agent:v1.0.0
        ports:
        - containerPort: 8000
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_INDEX
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['apps.kubernetes.io/pod-index']
        volumeMounts:
        - name: agent-data
          mountPath: /app/data
  volumeClaimTemplates:
  - metadata:
      name: agent-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

三、环境配置管理

3.1 环境变量层级

配置层级(优先级从高到低)

1. 命令行参数
--port 8000

2. 环境变量
AGENT_PORT=8000

3. .env文件
生产环境专用

4. config/agent.yaml
环境特定配置

5. config/default.yaml
默认配置

3.2 Python 配置管理器

import os
import yaml
from pathlib import Path
from typing import Any, Optional
from dataclasses import dataclass, field
from enum import Enum


class Environment(str, Enum):
    """环境枚举"""
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"
    TEST = "test"


@dataclass
class DatabaseConfig:
    """数据库配置"""
    url: str = "postgresql://localhost:5432/agent"
    pool_size: int = 10
    max_overflow: int = 20
    pool_timeout: int = 30
    pool_recycle: int = 3600
    echo: bool = False


@dataclass
class RedisConfig:
    """Redis配置"""
    url: str = "redis://localhost:6379/0"
    max_connections: int = 20
    socket_timeout: int = 5
    socket_connect_timeout: int = 5
    retry_on_timeout: bool = True


@dataclass
class LLMConfig:
    """LLM配置"""
    provider: str = "openai"
    model: str = "gpt-4o"
    api_key: str = ""
    temperature: float = 0.7
    max_tokens: int = 4096
    timeout: int = 120
    max_retries: int = 3
    streaming: bool = True


@dataclass
class AgentConfig:
    """Agent配置"""
    max_concurrent_requests: int = 100
    request_timeout: int = 300
    max_steps: int = 10
    max_context_tokens: int = 100000
    enable_streaming: bool = True
    enable_tools: bool = True
    enable_memory: bool = True
    enable_tracing: bool = True


@dataclass
class ObservabilityConfig:
    """可观测性配置"""
    enable_metrics: bool = True
    metrics_port: int = 9090
    enable_tracing: bool = True
    jaeger_endpoint: str = "http://localhost:14268/api/traces"
    enable_logging: bool = True
    log_level: str = "INFO"
    log_format: str = "json"


@dataclass
class SecurityConfig:
    """安全配置"""
    secret_key: str = "change-me-in-production"
    jwt_secret: str = "change-me-in-production"
    jwt_algorithm: str = "HS256"
    jwt_expire_minutes: int = 60
    cors_origins: list = field(default_factory=lambda: ["*"])
    rate_limit_per_minute: int = 60


@dataclass
class AppConfig:
    """应用总配置"""
    environment: Environment = Environment.DEVELOPMENT
    app_name: str = "agent-service"
    app_version: str = "1.0.0"
    host: str = "0.0.0.0"
    port: int = 8000
    debug: bool = False
    
    database: DatabaseConfig = field(default_factory=DatabaseConfig)
    redis: RedisConfig = field(default_factory=RedisConfig)
    llm: LLMConfig = field(default_factory=LLMConfig)
    agent: AgentConfig = field(default_factory=AgentConfig)
    observability: ObservabilityConfig = field(default_factory=ObservabilityConfig)
    security: SecurityConfig = field(default_factory=SecurityConfig)
    
    # 自定义配置
    custom: dict = field(default_factory=dict)


class ConfigManager:
    """配置管理器"""
    
    def __init__(self, config_dir: str = "config"):
        self.config_dir = Path(config_dir)
        self._config: Optional[AppConfig] = None
    
    def load(self, environment: Optional[str] = None) -> AppConfig:
        """加载配置"""
        # 1. 确定环境
        env = environment or os.getenv("ENVIRONMENT", "development")
        
        # 2. 加载默认配置
        config = self._load_yaml("default.yaml")
        
        # 3. 加载环境特定配置(覆盖默认)
        env_config = self._load_yaml(f"{env}.yaml")
        config = self._deep_merge(config, env_config)
        
        # 4. 加载.env文件
        config = self._load_env_file(config, env)
        
        # 5. 加载环境变量(覆盖所有)
        config = self._load_env_vars(config)
        
        # 6. 转换为dataclass
        self._config = self._dict_to_config(config)
        
        return self._config
    
    def _load_yaml(self, filename: str) -> dict:
        """加载YAML文件"""
        filepath = self.config_dir / filename
        if not filepath.exists():
            return {}
        
        with open(filepath, "r", encoding="utf-8") as f:
            return yaml.safe_load(f) or {}
    
    def _load_env_file(self, config: dict, env: str) -> dict:
        """加载.env文件"""
        env_file = self.config_dir / f".env.{env}"
        if not env_file.exists():
            env_file = self.config_dir / ".env"
        
        if not env_file.exists():
            return config
        
        with open(env_file, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                if "=" in line:
                    key, value = line.split("=", 1)
                    key = key.strip()
                    value = value.strip().strip('"').strip("'")
                    config[key] = value
        
        return config
    
    def _load_env_vars(self, config: dict) -> dict:
        """加载环境变量"""
        for key, value in os.environ.items():
            if key.startswith("AGENT_"):
                config_key = key[6:].lower()  # 去掉AGENT_前缀
                config[config_key] = value
        
        return config
    
    def _deep_merge(self, base: dict, override: dict) -> dict:
        """深度合并字典"""
        result = base.copy()
        for key, value in override.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = self._deep_merge(result[key], value)
            else:
                result[key] = value
        return result
    
    def _dict_to_config(self, config: dict) -> AppConfig:
        """字典转dataclass"""
        app_config = AppConfig()
        
        # 基础配置
        app_config.environment = Environment(config.get("environment", "development"))
        app_config.app_name = config.get("app_name", app_config.app_name)
        app_config.app_version = config.get("app_version", app_config.app_version)
        app_config.host = config.get("host", app_config.host)
        app_config.port = int(config.get("port", app_config.port))
        app_config.debug = config.get("debug", app_config.debug)
        
        # 数据库配置
        db_config = config.get("database", {})
        app_config.database = DatabaseConfig(
            url=db_config.get("url", os.getenv("DATABASE_URL", app_config.database.url)),
            pool_size=int(db_config.get("pool_size", app_config.database.pool_size)),
            max_overflow=int(db_config.get("max_overflow", app_config.database.max_overflow)),
            pool_timeout=int(db_config.get("pool_timeout", app_config.database.pool_timeout)),
            pool_recycle=int(db_config.get("pool_recycle", app_config.database.pool_recycle)),
            echo=db_config.get("echo", app_config.database.echo),
        )
        
        # Redis配置
        redis_config = config.get("redis", {})
        app_config.redis = RedisConfig(
            url=redis_config.get("url", os.getenv("REDIS_URL", app_config.redis.url)),
            max_connections=int(redis_config.get("max_connections", app_config.redis.max_connections)),
        )
        
        # LLM配置
        llm_config = config.get("llm", {})
        app_config.llm = LLMConfig(
            provider=llm_config.get("provider", app_config.llm.provider),
            model=llm_config.get("model", os.getenv("OPENAI_MODEL", app_config.llm.model)),
            api_key=llm_config.get("api_key", os.getenv("OPENAI_API_KEY", app_config.llm.api_key)),
            temperature=float(llm_config.get("temperature", app_config.llm.temperature)),
            max_tokens=int(llm_config.get("max_tokens", app_config.llm.max_tokens)),
            timeout=int(llm_config.get("timeout", app_config.llm.timeout)),
            max_retries=int(llm_config.get("max_retries", app_config.llm.max_retries)),
            streaming=llm_config.get("streaming", app_config.llm.streaming),
        )
        
        # Agent配置
        agent_config = config.get("agent", {})
        app_config.agent = AgentConfig(
            max_concurrent_requests=int(agent_config.get("max_concurrent_requests", app_config.agent.max_concurrent_requests)),
            request_timeout=int(agent_config.get("request_timeout", app_config.agent.request_timeout)),
            max_steps=int(agent_config.get("max_steps", app_config.agent.max_steps)),
            max_context_tokens=int(agent_config.get("max_context_tokens", app_config.agent.max_context_tokens)),
            enable_streaming=agent_config.get("enable_streaming", app_config.agent.enable_streaming),
            enable_tools=agent_config.get("enable_tools", app_config.agent.enable_tools),
            enable_memory=agent_config.get("enable_memory", app_config.agent.enable_memory),
            enable_tracing=agent_config.get("enable_tracing", app_config.agent.enable_tracing),
        )
        
        # 可观测性配置
        obs_config = config.get("observability", {})
        app_config.observability = ObservabilityConfig(
            enable_metrics=obs_config.get("enable_metrics", app_config.observability.enable_metrics),
            metrics_port=int(obs_config.get("metrics_port", app_config.observability.metrics_port)),
            enable_tracing=obs_config.get("enable_tracing", app_config.observability.enable_tracing),
            jaeger_endpoint=obs_config.get("jaeger_endpoint", os.getenv("JAEGER_ENDPOINT", app_config.observability.jaeger_endpoint)),
            enable_logging=obs_config.get("enable_logging", app_config.observability.enable_logging),
            log_level=obs_config.get("log_level", os.getenv("LOG_LEVEL", app_config.observability.log_level)),
            log_format=obs_config.get("log_format", app_config.observability.log_format),
        )
        
        # 安全配置
        sec_config = config.get("security", {})
        app_config.security = SecurityConfig(
            secret_key=sec_config.get("secret_key", os.getenv("SECRET_KEY", app_config.security.secret_key)),
            jwt_secret=sec_config.get("jwt_secret", os.getenv("JWT_SECRET", app_config.security.jwt_secret)),
            jwt_algorithm=sec_config.get("jwt_algorithm", app_config.security.jwt_algorithm),
            jwt_expire_minutes=int(sec_config.get("jwt_expire_minutes", app_config.security.jwt_expire_minutes)),
            cors_origins=sec_config.get("cors_origins", app_config.security.cors_origins),
            rate_limit_per_minute=int(sec_config.get("rate_limit_per_minute", app_config.security.rate_limit_per_minute)),
        )
        
        # 自定义配置
        app_config.custom = config.get("custom", {})
        
        return app_config
    
    def get(self) -> AppConfig:
        """获取当前配置"""
        if self._config is None:
            return self.load()
        return self._config
    
    def reload(self) -> AppConfig:
        """重新加载配置"""
        return self.load()

3.3 YAML 配置文件

# config/default.yaml - 默认配置
environment: development
app_name: agent-service
app_version: 1.0.0
host: 0.0.0.0
port: 8000
debug: false

database:
  url: postgresql://localhost:5432/agent
  pool_size: 10
  max_overflow: 20
  pool_timeout: 30
  pool_recycle: 3600
  echo: false

redis:
  url: redis://localhost:6379/0
  max_connections: 20

llm:
  provider: openai
  model: gpt-4o
  temperature: 0.7
  max_tokens: 4096
  timeout: 120
  max_retries: 3
  streaming: true

agent:
  max_concurrent_requests: 100
  request_timeout: 300
  max_steps: 10
  max_context_tokens: 100000
  enable_streaming: true
  enable_tools: true
  enable_memory: true
  enable_tracing: true

observability:
  enable_metrics: true
  metrics_port: 9090
  enable_tracing: true
  jaeger_endpoint: http://localhost:14268/api/traces
  enable_logging: true
  log_level: INFO
  log_format: json

security:
  jwt_algorithm: HS256
  jwt_expire_minutes: 60
  cors_origins:
    - "*"
  rate_limit_per_minute: 60

custom:
  features:
    enable_caching: true
    enable_cost_monitor: true
    enable_model_router: true
# config/development.yaml - 开发环境覆盖
environment: development
debug: true

database:
  echo: true
  pool_size: 5

llm:
  model: gpt-4o-mini  # 开发用小模型
  temperature: 0.9

observability:
  log_level: DEBUG
  log_format: text

security:
  cors_origins:
    - "http://localhost:3000"
    - "http://localhost:8080"
# config/production.yaml - 生产环境覆盖
environment: production
debug: false

database:
  pool_size: 20
  max_overflow: 50
  pool_recycle: 1800

llm:
  model: gpt-4o
  temperature: 0.7
  timeout: 60
  max_retries: 5

agent:
  max_concurrent_requests: 200
  max_steps: 8  # 生产环境限制步数

observability:
  log_level: INFO
  log_format: json

security:
  cors_origins:
    - "https://agent.example.com"
  rate_limit_per_minute: 100
# config/staging.yaml - 预发布环境
environment: staging
debug: false

database:
  pool_size: 15

llm:
  model: gpt-4o
  temperature: 0.7

observability:
  log_level: INFO

四、配置热重载

4.1 文件监听热重载

import asyncio
from watchfiles import awatch
from typing import Callable, Optional


class HotReloader:
    """配置热重载器"""
    
    def __init__(self, config_manager: ConfigManager, 
                 config_dir: str = "config"):
        self.config_manager = config_manager
        self.config_dir = config_dir
        self._watchers: list[asyncio.Task] = []
        self._callbacks: list[Callable] = []
        self._running = False
    
    def on_reload(self, callback: Callable):
        """注册重载回调"""
        self._callbacks.append(callback)
    
    async def start(self):
        """启动文件监听"""
        self._running = True
        task = asyncio.create_task(self._watch_files())
        self._watchers.append(task)
        print(f"[HotReloader] 监听目录: {self.config_dir}")
    
    async def stop(self):
        """停止监听"""
        self._running = False
        for task in self._watchers:
            task.cancel()
        self._watchers.clear()
    
    async def _watch_files(self):
        """监听配置文件变化"""
        async for changes in awatch(self.config_dir):
            if not self._running:
                break
            
            for change_type, path in changes:
                if path.endswith(('.yaml', '.yml', '.env')):
                    print(f"[HotReloader] 检测到配置变化: {change_type.name} {path}")
                    await self._reload()
                    break
    
    async def _reload(self):
        """重新加载配置"""
        try:
            old_config = self.config_manager.get()
            new_config = self.config_manager.reload()
            
            # 对比配置差异
            diff = self._diff_configs(old_config, new_config)
            if diff:
                print(f"[HotReloader] 配置变更: {diff}")
            
            # 通知回调
            for callback in self._callbacks:
                try:
                    if asyncio.iscoroutinefunction(callback):
                        await callback(new_config, diff)
                    else:
                        callback(new_config, diff)
                except Exception as e:
                    print(f"[HotReloader] 回调执行失败: {e}")
        
        except Exception as e:
            print(f"[HotReloader] 重载失败: {e}")
    
    def _diff_configs(self, old, new) -> dict:
        """对比配置差异"""
        diff = {}
        old_dict = old.__dict__ if hasattr(old, '__dict__') else old
        new_dict = new.__dict__ if hasattr(new, '__dict__') else new
        
        for key in set(list(old_dict.keys()) + list(new_dict.keys())):
            old_val = old_dict.get(key)
            new_val = new_dict.get(key)
            if old_val != new_val:
                diff[key] = {"old": str(old_val), "new": str(new_val)}
        
        return diff

4.2 热重载使用示例

# main.py
import asyncio
from config_manager import ConfigManager, HotReloader


async def main():
    # 加载配置
    config_manager = ConfigManager(config_dir="config")
    config = config_manager.load(environment="production")
    
    # 创建热重载器
    reloader = HotReloader(config_manager, config_dir="config")
    
    # 注册回调
    def on_config_change(new_config, diff):
        print(f"配置已更新: {diff}")
        # 可以在这里更新全局配置引用
        # 更新数据库连接池
        # 更新LLM客户端
        # 更新日志级别
    
    reloader.on_reload(on_config_change)
    
    # 启动热重载
    await reloader.start()
    
    # 启动应用
    app = AgentApp(config)
    await app.start()


if __name__ == "__main__":
    asyncio.run(main())

4.3 配置热重载流程

应用回调函数热重载器文件监听配置文件应用回调函数热重载器文件监听配置文件文件变更事件触发重载重新加载YAML读取.env和环境变量对比配置差异通知配置变更更新数据库连接池更新LLM客户端更新日志级别无需重启应用

五、多环境管理

5.1 多环境配置目录结构

config/
├── default.yaml          # 默认配置(所有环境共享)
├── development.yaml      # 开发环境覆盖
├── staging.yaml          # 预发布环境覆盖
├── production.yaml       # 生产环境覆盖
├── test.yaml             # 测试环境覆盖
├── .env.development      # 开发环境变量
├── .env.staging          # 预发布环境变量
├── .env.production       # 生产环境变量
├── .env.test             # 测试环境变量
├── secrets/              # 敏感配置(不入Git)
│   ├── development/
│   │   └── api_keys.yaml
│   ├── staging/
│   │   └── api_keys.yaml
│   └── production/
│       └── api_keys.yaml
└── prompts/              # Prompt模板
    ├── system.yaml
    ├── tools.yaml
    └── agents.yaml

5.2 多环境管理器

class EnvironmentManager:
    """多环境管理器"""
    
    ENVIRONMENTS = ["development", "staging", "production", "test"]
    
    def __init__(self, config_dir: str = "config"):
        self.config_dir = Path(config_dir)
        self.current_env: Optional[str] = None
        self.config_manager = ConfigManager(config_dir)
    
    def detect_environment(self) -> str:
        """自动检测当前环境"""
        # 1. 环境变量
        env = os.getenv("ENVIRONMENT")
        if env and env in self.ENVIRONMENTS:
            return env
        
        # 2. 根据主机名判断
        hostname = os.uname().nodename if hasattr(os, 'uname') else os.getenv("COMPUTERNAME", "")
        if "prod" in hostname.lower():
            return "production"
        elif "staging" in hostname.lower():
            return "staging"
        elif "test" in hostname.lower():
            return "test"
        
        # 3. 默认开发环境
        return "development"
    
    def load_config(self, env: Optional[str] = None) -> AppConfig:
        """加载指定环境的配置"""
        env = env or self.detect_environment()
        self.current_env = env
        
        print(f"[EnvironmentManager] 当前环境: {env}")
        config = self.config_manager.load(environment=env)
        
        # 验证配置
        self._validate_config(config, env)
        
        return config
    
    def _validate_config(self, config: AppConfig, env: str):
        """验证配置"""
        errors = []
        
        # 生产环境必须配置API Key
        if env == "production":
            if not config.llm.api_key or config.llm.api_key == "":
                errors.append("生产环境必须配置 OPENAI_API_KEY")
            if config.security.secret_key == "change-me-in-production":
                errors.append("生产环境必须修改默认 SECRET_KEY")
            if config.debug:
                errors.append("生产环境不应开启 debug 模式")
            if "*" in config.security.cors_origins:
                errors.append("生产环境不应使用通配符 CORS")
        
        if errors:
            raise ValueError(f"配置验证失败:\n" + "\n".join(f"  - {e}" for e in errors))
    
    def get_env_specific_overrides(self, env: str) -> dict:
        """获取环境特定覆盖"""
        overrides = {
            "development": {
                "debug": True,
                "llm.model": "gpt-4o-mini",
                "observability.log_level": "DEBUG",
                "observability.log_format": "text",
            },
            "staging": {
                "debug": False,
                "llm.model": "gpt-4o",
                "observability.log_level": "INFO",
            },
            "production": {
                "debug": False,
                "llm.model": "gpt-4o",
                "observability.log_level": "INFO",
                "observability.log_format": "json",
                "agent.max_steps": 8,
            },
            "test": {
                "debug": True,
                "llm.model": "gpt-4o-mini",
                "database.url": "sqlite:///test.db",
            },
        }
        return overrides.get(env, {})

5.3 .env 文件管理

# .env.development
ENVIRONMENT=development
LOG_LEVEL=DEBUG
OPENAI_API_KEY=sk-dev-xxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-4o-mini
DATABASE_URL=postgresql://agent:agent@localhost:5432/agent_dev
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=dev-secret-key-not-for-production
# .env.production
ENVIRONMENT=production
LOG_LEVEL=INFO
OPENAI_API_KEY=sk-prod-xxxxxxxxxxxxxxxx
OPENAI_MODEL=gpt-4o
DATABASE_URL=postgresql://agent:${DB_PASSWORD}@db:5432/agent
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
SECRET_KEY=${SECRET_KEY}
JAEGER_ENDPOINT=http://jaeger:14268/api/traces

5.4 .gitignore 配置

# .gitignore

# 敏感配置文件
.env
.env.*
!.env.example
config/secrets/

# 但保留示例
!.env.development.example

六、Secret管理

6.1 Kubernetes Secret管理

# k8s/sealed-secret.yaml - 使用 Sealed Secrets 加密
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: agent-secrets
  namespace: agent-prod
spec:
  encryptedData:
    openai-api-key: AgBh...加密内容...
    database-url: AgBh...加密内容...
    redis-url: AgBh...加密内容...
  template:
    metadata:
      name: agent-secrets
    type: Opaque

6.2 HashiCorp Vault 集成

import hvac


class VaultSecretManager:
    """Vault密钥管理"""
    
    def __init__(self, vault_url: str, vault_token: str):
        self.client = hvac.Client(url=vault_url, token=vault_token)
    
    def get_secret(self, path: str, key: str) -> str:
        """获取密钥"""
        read_response = self.client.secrets.kv.read_secret_version(
            path=path,
            mount_point="secret"
        )
        return read_response["data"]["data"][key]
    
    def get_all_secrets(self, path: str) -> dict:
        """获取路径下所有密钥"""
        read_response = self.client.secrets.kv.read_secret_version(
            path=path,
            mount_point="secret"
        )
        return read_response["data"]["data"]
    
    def put_secret(self, path: str, **secrets):
        """写入密钥"""
        self.client.secrets.kv.v2.create_or_update_secret(
            path=path,
            secret=secrets,
            mount_point="secret"
        )


class SecretProvider:
    """密钥提供者(支持多种后端)"""
    
    @staticmethod
    def create(provider: str = "env", **kwargs):
        """创建密钥提供者"""
        if provider == "env":
            return EnvSecretProvider()
        elif provider == "vault":
            return VaultSecretManager(
                vault_url=kwargs["url"],
                vault_token=kwargs["token"]
            )
        elif provider == "k8s":
            return K8sSecretProvider(
                namespace=kwargs.get("namespace", "default")
            )
        else:
            raise ValueError(f"不支持的密钥提供者: {provider}")


class EnvSecretProvider:
    """环境变量密钥提供者"""
    
    def get_secret(self, key: str) -> str:
        return os.getenv(key, "")
    
    def get_all_secrets(self, prefix: str = "") -> dict:
        return {
            k: v for k, v in os.environ.items()
            if k.startswith(prefix)
        }

七、健康检查与就绪探针

7.1 健康检查端点

from fastapi import FastAPI
from fastapi.responses import JSONResponse
import asyncio
import time


class HealthChecker:
    """健康检查器"""
    
    def __init__(self, config: AppConfig):
        self.config = config
        self.start_time = time.time()
        self._db_healthy = True
        self._redis_healthy = True
        self._llm_healthy = True
        self._last_check = 0
    
    async def check_liveness(self) -> dict:
        """存活检查(进程是否活着)"""
        return {
            "status": "alive",
            "uptime_seconds": time.time() - self.start_time,
            "timestamp": datetime.now().isoformat(),
        }
    
    async def check_readiness(self) -> dict:
        """就绪检查(是否能处理请求)"""
        checks = {}
        all_healthy = True
        
        # 检查数据库
        try:
            db_ok = await self._check_database()
            checks["database"] = "ok" if db_ok else "error"
            if not db_ok:
                all_healthy = False
        except Exception as e:
            checks["database"] = f"error: {e}"
            all_healthy = False
        
        # 检查Redis
        try:
            redis_ok = await self._check_redis()
            checks["redis"] = "ok" if redis_ok else "error"
            if not redis_ok:
                all_healthy = False
        except Exception as e:
            checks["redis"] = f"error: {e}"
            all_healthy = False
        
        # 检查LLM API(轻量检查)
        try:
            llm_ok = await self._check_llm()
            checks["llm"] = "ok" if llm_ok else "degraded"
        except Exception as e:
            checks["llm"] = f"error: {e}"
        
        return {
            "status": "ready" if all_healthy else "not_ready",
            "checks": checks,
            "timestamp": datetime.now().isoformat(),
        }
    
    async def check_startup(self) -> dict:
        """启动检查"""
        return await self.check_readiness()
    
    async def _check_database(self) -> bool:
        """检查数据库连接"""
        try:
            # 实际实现中执行 SELECT 1
            return True
        except:
            return False
    
    async def _check_redis(self) -> bool:
        """检查Redis连接"""
        try:
            # 实际实现中执行 PING
            return True
        except:
            return False
    
    async def _check_llm(self) -> bool:
        """检查LLM API"""
        try:
            # 轻量检查,不实际调用LLM
            return True
        except:
            return False


def setup_health_endpoints(app: FastAPI, health_checker: HealthChecker):
    """设置健康检查端点"""
    
    @app.get("/health")
    async def health():
        """存活探针"""
        result = await health_checker.check_liveness()
        return JSONResponse(content=result)
    
    @app.get("/ready")
    async def ready():
        """就绪探针"""
        result = await health_checker.check_readiness()
        status_code = 200 if result["status"] == "ready" else 503
        return JSONResponse(content=result, status_code=status_code)
    
    @app.get("/startup")
    async def startup():
        """启动探针"""
        result = await health_checker.check_startup()
        status_code = 200 if result["status"] == "ready" else 503
        return JSONResponse(content=result, status_code=status_code)
    
    @app.get("/metrics")
    async def metrics():
        """Prometheus指标"""
        return JSONResponse(content={
            "uptime": time.time() - health_checker.start_time,
            "requests_total": getattr(app.state, "requests_total", 0),
            "errors_total": getattr(app.state, "errors_total", 0),
        })

7.2 探针配置说明

探针作用

K8s探针类型

启动探针
startupProbe

存活探针
livenessProbe

就绪探针
readinessProbe

启动探针: 等待应用启动完成
失败则重启Pod

存活探针: 检查进程是否活着
失败则重启Pod

就绪探针: 检查是否能处理请求
失败则从Service摘除


八、优雅关闭

8.1 优雅关闭实现

import signal
import asyncio
from typing import Callable


class GracefulShutdown:
    """优雅关闭管理器"""
    
    def __init__(self):
        self._shutdown_handlers: list[Callable] = []
        self._shutting_down = False
        self._shutdown_event = asyncio.Event()
    
    def register_handler(self, handler: Callable):
        """注册关闭处理器"""
        self._shutdown_handlers.append(handler)
    
    def setup_signal_handlers(self):
        """设置信号处理器"""
        loop = asyncio.get_event_loop()
        
        for sig in (signal.SIGTERM, signal.SIGINT):
            loop.add_signal_handler(sig, self._handle_signal)
        
        print("[GracefulShutdown] 信号处理器已注册")
    
    def _handle_signal(self):
        """处理信号"""
        if self._shutting_down:
            print("[GracefulShutdown] 已在关闭中,请等待...")
            return
        
        self._shutting_down = True
        print("[GracefulShutdown] 收到关闭信号,开始优雅关闭...")
        self._shutdown_event.set()
    
    async def wait_for_shutdown(self):
        """等待关闭信号"""
        await self._shutdown_event.wait()
    
    async def shutdown(self, timeout: int = 30):
        """执行优雅关闭"""
        print(f"[GracefulShutdown] 开始执行关闭处理器(超时{timeout}秒)")
        
        # 1. 停止接收新请求
        print("[GracefulShutdown] 停止接收新请求")
        
        # 2. 等待进行中的请求完成
        print("[GracefulShutdown] 等待进行中的请求完成...")
        await asyncio.sleep(5)  # 给请求5秒完成时间
        
        # 3. 执行关闭处理器
        for handler in self._shutdown_handlers:
            try:
                if asyncio.iscoroutinefunction(handler):
                    await asyncio.wait_for(handler(), timeout=10)
                else:
                    handler()
                print(f"[GracefulShutdown] 处理器 {handler.__name__} 完成")
            except asyncio.TimeoutError:
                print(f"[GracefulShutdown] 处理器 {handler.__name__} 超时")
            except Exception as e:
                print(f"[GracefulShutdown] 处理器 {handler.__name__} 失败: {e}")
        
        # 4. 关闭数据库连接
        print("[GracefulShutdown] 关闭数据库连接")
        
        # 5. 关闭Redis连接
        print("[GracefulShutdown] 关闭Redis连接")
        
        # 6. 清理资源
        print("[GracefulShutdown] 清理资源")
        
        print("[GracefulShutdown] 优雅关闭完成")


# 使用示例
async def main():
    shutdown = GracefulShutdown()
    shutdown.setup_signal_handlers()
    
    # 注册关闭处理器
    shutdown.register_handler(close_database)
    shutdown.register_handler(close_redis)
    shutdown.register_handler(flush_logs)
    
    # 启动应用
    app = AgentApp()
    app_task = asyncio.create_task(app.start())
    
    # 等待关闭信号
    await shutdown.wait_for_shutdown()
    
    # 执行关闭
    await shutdown.shutdown(timeout=30)
    
    # 取消应用任务
    app_task.cancel()
    try:
        await app_task
    except asyncio.CancelledError:
        pass
    
    print("[Main] 应用已关闭")


async def close_database():
    """关闭数据库"""
    print("  → 关闭数据库连接池")
    await asyncio.sleep(0.5)

async def close_redis():
    """关闭Redis"""
    print("  → 关闭Redis连接")
    await asyncio.sleep(0.3)

def flush_logs():
    """刷新日志"""
    print("  → 刷新日志缓冲区")

8.2 优雅关闭流程

Redis数据库应用PodK8sRedis数据库应用PodK8s等待15秒让Service摘除PodterminationGracePeriodSeconds=60发送 SIGTERMpreStop: sleep 15停止接收新请求等待进行中的请求完成关闭连接池连接已关闭关闭连接连接已关闭刷新日志清理资源进程退出

九、CI/CD 部署流水线

9.1 GitHub Actions 配置

# .github/workflows/deploy.yml
name: Deploy Agent Service

on:
  push:
    branches: [main, develop]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-asyncio pytest-cov
    
    - name: Run tests
      run: |
        pytest --cov=src --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
  
  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name != 'pull_request'
    permissions:
      contents: read
      packages: write
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set environment
      id: env
      run: |
        if [[ ${{ github.ref }} == refs/tags/v* ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
          echo "image_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
        elif [[ ${{ github.ref }} == refs/heads/main ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
          echo "image_tag=staging-${{ github.sha }}" >> $GITHUB_OUTPUT
        else
          echo "environment=development" >> $GITHUB_OUTPUT
          echo "image_tag=dev-${{ github.sha }}" >> $GITHUB_OUTPUT
        fi
    
    - name: Build Docker image
      run: |
        docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.env.outputs.image_tag }} .
    
    - name: Log in to Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Push image
      run: |
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.env.outputs.image_tag }}
    
    outputs:
      environment: ${{ steps.env.outputs.environment }}
      image_tag: ${{ steps.env.outputs.image_tag }}
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name != 'pull_request'
    environment: ${{ needs.build.outputs.environment }}
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set K8s context
      uses: azure/k8s-set-context@v4
      with:
        kubeconfig: ${{ secrets.KUBE_CONFIG }}
    
    - name: Deploy to K8s
      run: |
        env=${{ needs.build.outputs.environment }}
        tag=${{ needs.build.outputs.image_tag }}
        
        # 更新镜像标签
        kubectl set image deployment/agent-service \
          agent=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${tag} \
          -n agent-${env}
        
        # 等待滚动更新完成
        kubectl rollout status deployment/agent-service \
          -n agent-${env} --timeout=300s
        
        # 验证部署
        kubectl get pods -n agent-${env} -l app=agent
    
    - name: Run smoke tests
      run: |
        sleep 30  # 等待Pod就绪
        SERVICE_URL=$(kubectl get svc agent-service -n agent-${{ needs.build.outputs.environment }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
        
        # 健康检查
        curl -f http://${SERVICE_URL}/health
        
        # 就绪检查
        curl -f http://${SERVICE_URL}/ready

9.2 部署流程图

通过

失败

tag v*

main

develop

通过

失败

Git Push

运行测试

分支判断

停止

构建生产镜像

构建预发布镜像

构建开发镜像

部署到K8s生产

部署到K8s预发布

部署到K8s开发

冒烟测试

部署成功

自动回滚


十、配置验证与安全

10.1 配置验证

class ConfigValidator:
    """配置验证器"""
    
    @staticmethod
    def validate(config: AppConfig) -> list[str]:
        """验证配置,返回错误列表"""
        errors = []
        
        # 基础验证
        if config.port < 1 or config.port > 65535:
            errors.append("端口必须在1-65535范围内")
        
        # 数据库验证
        if not config.database.url:
            errors.append("数据库URL不能为空")
        if config.database.pool_size < 1:
            errors.append("数据库连接池大小必须大于0")
        
        # LLM验证
        if not config.llm.api_key:
            errors.append("LLM API Key不能为空")
        if config.llm.temperature < 0 or config.llm.temperature > 2:
            errors.append("temperature必须在0-2范围内")
        if config.llm.max_tokens < 1 or config.llm.max_tokens > 128000:
            errors.append("max_tokens必须在1-128000范围内")
        
        # Agent验证
        if config.agent.max_steps < 1:
            errors.append("max_steps必须大于0")
        if config.agent.max_concurrent_requests < 1:
            errors.append("max_concurrent_requests必须大于0")
        
        # 安全验证
        if config.environment == Environment.PRODUCTION:
            if config.debug:
                errors.append("[生产环境] 不应开启debug模式")
            if config.security.secret_key == "change-me-in-production":
                errors.append("[生产环境] 必须修改默认SECRET_KEY")
            if config.security.jwt_secret == "change-me-in-production":
                errors.append("[生产环境] 必须修改默认JWT_SECRET")
            if "*" in config.security.cors_origins:
                errors.append("[生产环境] 不应使用通配符CORS")
        
        return errors

10.2 配置安全检查清单

检查项开发环境生产环境说明
API Key可用测试Key必须用真实Key不能硬编码
SECRET_KEY默认值必须修改至少32字符
CORS允许通配符必须指定域名防止CSRF
Debug开启关闭避免信息泄露
日志级别DEBUGINFO避免敏感信息
HTTPS可选必须防止中间人攻击
Rate Limit宽松严格防止滥用

十一、总结

11.1 部署配置最佳实践

  1. 配置分离:代码与配置完全分离,不同环境不同配置文件
  2. 密钥安全:API Key等敏感信息不硬编码,用环境变量或Secret管理
  3. 分层覆盖:默认配置 → 环境配置 → 环境变量,逐层覆盖
  4. 热重载:支持配置变更不重启应用
  5. 健康检查:完善的liveness/readiness探针
  6. 优雅关闭:收到信号后先完成进行中的请求再退出
  7. CI/CD:自动化构建、测试、部署流程
  8. 配置验证:启动时验证配置完整性和安全性

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/lza_csdn2019/article/details/169158841

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--