iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0

小明的雙農場經營法

今天要學習如何有效管理 Production 和 Development 環境,這就像爸爸同時經營試驗田和正式農田一樣。試驗田可以大膽嘗試新品種,正式農田則要確保穩定收成。今天我們來學習如何在這兩個環境間取得平衡!

Prod vs Dev 環境哲學

環境特性對比

環境特性對比

環境差異管理策略

配置管理矩陣

配置項目 Development Production 差異原因
日誌等級 DEBUG WARNING 開發需詳細資訊,生產避免效能影響
資料庫 SQLite/小型 PostgreSQL 高可用 PostgreSQL 成本vs可靠性考量
快取 本地 Redis Redis Cluster 單點故障 vs 高可用
API 限制 寬鬆 嚴格 測試便利 vs 安全防護
監控 基本 全面 成本控制 vs 營運需求
備份 每日 即時 資料重要性差異

基礎設施差異

# infrastructure/environments/development/main.tf
resource "aws_ecs_service" "trading_bot" {
  name            = "trading-bot-dev"
  cluster         = aws_ecs_cluster.dev.id
  task_definition = aws_ecs_task_definition.trading_bot_dev.arn
  desired_count   = 1
  
  deployment_configuration {
    maximum_percent         = 200
    minimum_healthy_percent = 0  # 允許服務完全停止重啟
  }
  
  # 開發環境使用 Fargate Spot 節省成本
  capacity_provider_strategy {
    capacity_provider = "FARGATE_SPOT"
    weight           = 100
  }
}

resource "aws_ecs_task_definition" "trading_bot_dev" {
  family                   = "trading-bot-dev"
  requires_compatibilities = ["FARGATE"]
  cpu                     = "256"   # 最小配置
  memory                  = "512"   # 開發環境資源最小化
  
  container_definitions = jsonencode([{
    name  = "trading-bot"
    image = "${aws_ecr_repository.trading_bot.repository_url}:dev"
    
    environment = [
      { name = "ENVIRONMENT", value = "development" },
      { name = "LOG_LEVEL", value = "DEBUG" },
      { name = "ENABLE_PROFILING", value = "true" }
    ]
  }])
}
# infrastructure/environments/production/main.tf
resource "aws_ecs_service" "trading_bot" {
  name            = "trading-bot-prod"
  cluster         = aws_ecs_cluster.prod.id
  task_definition = aws_ecs_task_definition.trading_bot_prod.arn
  desired_count   = 3  # 高可用配置
  
  deployment_configuration {
    maximum_percent         = 150
    minimum_healthy_percent = 50  # 確保始終有服務可用
  }
  
  # 生產環境使用標準 Fargate
  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight           = 100
  }
  
  # 生產環境啟用服務發現
  service_registries {
    registry_arn = aws_service_discovery_service.trading_bot.arn
  }
}

resource "aws_ecs_task_definition" "trading_bot_prod" {
  family                   = "trading-bot-prod"
  requires_compatibilities = ["FARGATE"]
  cpu                     = "1024"  # 生產環境充足資源
  memory                  = "2048"
  
  container_definitions = jsonencode([{
    name  = "trading-bot"
    image = "${aws_ecr_repository.trading_bot.repository_url}:latest"
    
    environment = [
      { name = "ENVIRONMENT", value = "production" },
      { name = "LOG_LEVEL", value = "WARNING" },
      { name = "ENABLE_PROFILING", value = "false" }
    ]
    
    # 生產環境健康檢查
    healthCheck = {
      command     = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval    = 30
      timeout     = 5
      retries     = 3
      startPeriod = 60
    }
  }])
}

環境切換工作流程

功能開發到生產部署流程

graph TD
    A[Feature 開發] --> B[本地測試]
    B --> C[Push to Feature Branch]
    C --> D[自動部署到 Dev]
    D --> E[開發者驗證]
    E --> F{功能完成?}
    F --> |否| A
    F --> |是| G[Create PR to Develop]
    G --> H[Code Review]
    H --> I[自動部署到 Staging]
    I --> J[QA 測試]
    J --> K{測試通過?}
    K --> |否| L[修正 Bug]
    L --> A
    K --> |是| M[Create PR to Main]
    M --> N[Senior Review]
    N --> O[自動部署到 Prod]
    O --> P[生產監控]
    
    style D fill:#ccffcc
    style I fill:#ffffcc
    style O fill:#ffcccc

環境同步工作流程

# .github/workflows/env-sync.yml
name: Environment Synchronization

on:
  workflow_dispatch:
    inputs:
      source_env:
        description: 'Source environment'
        required: true
        type: choice
        options:
          - production
          - staging
      target_env:
        description: 'Target environment'
        required: true
        type: choice
        options:
          - development
          - staging
      sync_type:
        description: 'Synchronization type'
        required: true
        type: choice
        options:
          - config-only
          - data-subset
          - schema-only

jobs:
  sync-environments:
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.target_env }}
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
    
    - name: Validate sync operation
      run: |
        # 防止意外覆蓋生產環境
        if [[ "${{ github.event.inputs.target_env }}" == "production" ]]; then
          echo "❌ 不允許同步到生產環境"
          exit 1
        fi
        
        # 檢查同步類型和環境組合的合理性
        if [[ "${{ github.event.inputs.source_env }}" == "production" ]] && 
           [[ "${{ github.event.inputs.sync_type }}" == "data-subset" ]]; then
          echo "⚠️ 從生產環境同步資料,請確認已獲得授權"
        fi
    
    - name: Configure AWS credentials for source
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1
        role-to-assume: ${{ secrets.SOURCE_ENV_ROLE_ARN }}
        role-session-name: EnvSync-Source
    
    - name: Export source environment data
      env:
        SOURCE_ENV: ${{ github.event.inputs.source_env }}
        SYNC_TYPE: ${{ github.event.inputs.sync_type }}
      run: |
        case $SYNC_TYPE in
          "config-only")
            # 匯出配置檔案
            aws s3 cp s3://trading-config-$SOURCE_ENV/ ./temp-config/ --recursive
            ;;
          "data-subset")
            # 匯出測試資料子集
            python scripts/export_test_data.py \
              --source-env $SOURCE_ENV \
              --output ./temp-data/
            ;;
          "schema-only")
            # 匯出資料庫結構
            pg_dump --schema-only $SOURCE_DATABASE_URL > ./temp-schema.sql
            ;;
        esac
    
    - name: Configure AWS credentials for target
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1
        role-to-assume: ${{ secrets.TARGET_ENV_ROLE_ARN }}
        role-session-name: EnvSync-Target
    
    - name: Import to target environment
      env:
        TARGET_ENV: ${{ github.event.inputs.target_env }}
        SYNC_TYPE: ${{ github.event.inputs.sync_type }}
      run: |
        case $SYNC_TYPE in
          "config-only")
            # 匯入配置(先備份)
            aws s3 cp s3://trading-config-$TARGET_ENV/ ./backup-config/ --recursive
            aws s3 cp ./temp-config/ s3://trading-config-$TARGET_ENV/ --recursive
            ;;
          "data-subset")
            # 匯入測試資料
            python scripts/import_test_data.py \
              --target-env $TARGET_ENV \
              --input ./temp-data/
            ;;
          "schema-only")
            # 匯入資料庫結構
            psql $TARGET_DATABASE_URL < ./temp-schema.sql
            ;;
        esac
    
    - name: Verify synchronization
      run: |
        # 驗證同步結果
        python scripts/verify_env_sync.py \
          --target-env ${{ github.event.inputs.target_env }} \
          --sync-type ${{ github.event.inputs.sync_type }}
    
    - name: Restart target environment services
      run: |
        # 重啟目標環境服務以載入新配置
        aws ecs update-service \
          --cluster trading-${{ github.event.inputs.target_env }} \
          --service trading-bot-service \
          --force-new-deployment

配置管理最佳實踐

環境特定配置檔案

# src/config/settings.py
from pydantic import BaseSettings
from typing import Optional
import os

class BaseConfig(BaseSettings):
    """基礎配置類別"""
    
    # 應用程式基本設定
    app_name: str = "TradingBot"
    version: str = "1.0.0"
    environment: str = "development"
    
    # API 設定
    api_host: str = "0.0.0.0"
    api_port: int = 8080
    api_prefix: str = "/api/v1"
    
    # 日誌設定
    log_level: str = "INFO"
    log_format: str = "json"
    
    # 安全設定
    secret_key: str
    access_token_expire_minutes: int = 30
    
    # 交易設定
    bybit_api_key: str
    bybit_secret_key: str
    bybit_testnet: bool = True
    
    # 資料庫設定
    database_url: str
    redis_url: str
    
    class Config:
        case_sensitive = False
        env_file = ".env"

class DevelopmentConfig(BaseConfig):
    """開發環境配置"""
    
    environment: str = "development"
    log_level: str = "DEBUG"
    bybit_testnet: bool = True
    
    # 開發環境特定設定
    enable_profiling: bool = True
    enable_debug_toolbar: bool = True
    reload_on_change: bool = True
    
    # 寬鬆的安全設定
    access_token_expire_minutes: int = 1440  # 24 小時
    
    class Config:
        env_file = ".env.development"

class ProductionConfig(BaseConfig):
    """生產環境配置"""
    
    environment: str = "production"
    log_level: str = "WARNING"
    bybit_testnet: bool = False
    
    # 生產環境特定設定
    enable_profiling: bool = False
    enable_debug_toolbar: bool = False
    reload_on_change: bool = False
    
    # 嚴格的安全設定
    access_token_expire_minutes: int = 15  # 15 分鐘
    
    # 效能優化設定
    database_pool_size: int = 20
    database_max_overflow: int = 30
    redis_max_connections: int = 50
    
    class Config:
        env_file = ".env.production"

def get_config() -> BaseConfig:
    """根據環境變數返回對應配置"""
    
    env = os.getenv("ENVIRONMENT", "development").lower()
    
    if env == "production":
        return ProductionConfig()
    elif env == "staging":
        # 可以創建 StagingConfig 或使用 ProductionConfig
        return ProductionConfig()
    else:
        return DevelopmentConfig()

# 全域配置實例
settings = get_config()

環境變數管理

# .env.development
ENVIRONMENT=development
LOG_LEVEL=DEBUG

# 開發環境 API 設定
BYBIT_API_KEY=dev_api_key
BYBIT_SECRET_KEY=dev_secret_key
BYBIT_TESTNET=true

# 開發環境資料庫
DATABASE_URL=postgresql://dev_user:dev_pass@localhost:5432/trading_dev
REDIS_URL=redis://localhost:6379/0

# 開發環境 AWS 設定
AWS_S3_BUCKET=trading-dev-bucket
AWS_REGION=us-east-1

# 開發環境特定功能
ENABLE_PROFILING=true
ENABLE_DEBUG_TOOLBAR=true
FAKE_API_RESPONSES=true
# .env.production
ENVIRONMENT=production
LOG_LEVEL=WARNING

# 生產環境 API 設定 (從 AWS Secrets Manager 載入)
BYBIT_API_KEY=${aws:secretsmanager:trading-secrets:SecretString:BYBIT_API_KEY}
BYBIT_SECRET_KEY=${aws:secretsmanager:trading-secrets:SecretString:BYBIT_SECRET_KEY}
BYBIT_TESTNET=false

# 生產環境資料庫
DATABASE_URL=${aws:secretsmanager:trading-db:SecretString:DATABASE_URL}
REDIS_URL=${aws:secretsmanager:trading-redis:SecretString:REDIS_URL}

# 生產環境 AWS 設定
AWS_S3_BUCKET=trading-prod-bucket
AWS_REGION=us-east-1

# 生產環境監控
DATADOG_API_KEY=${aws:secretsmanager:monitoring:SecretString:DATADOG_API_KEY}
SENTRY_DSN=${aws:secretsmanager:monitoring:SecretString:SENTRY_DSN}

資料管理策略

測試資料管理

# scripts/manage_test_data.py
import asyncio
import json
from typing import Dict, Any, List
from src.database import get_session
from src.models import User, TradingStrategy, Order

class TestDataManager:
    """測試資料管理器"""
    
    def __init__(self, environment: str):
        self.environment = environment
        self.session = get_session()
    
    async def create_test_users(self) -> List[Dict]:
        """創建測試使用者"""
        test_users = [
            {
                "username": "test_trader_1",
                "email": "trader1@test.com",
                "is_active": True,
                "trading_balance": 10000.0
            },
            {
                "username": "test_trader_2", 
                "email": "trader2@test.com",
                "is_active": True,
                "trading_balance": 5000.0
            }
        ]
        
        created_users = []
        for user_data in test_users:
            user = User(**user_data)
            self.session.add(user)
            created_users.append(user_data)
        
        await self.session.commit()
        return created_users
    
    async def create_test_strategies(self) -> List[Dict]:
        """創建測試策略"""
        test_strategies = [
            {
                "name": "Arbitrage Strategy Test",
                "description": "Test arbitrage strategy",
                "config": {
                    "symbol": "BTCUSDT",
                    "min_profit_rate": 0.001,
                    "max_position_size": 1000
                }
            }
        ]
        
        created_strategies = []
        for strategy_data in test_strategies:
            strategy = TradingStrategy(**strategy_data)
            self.session.add(strategy)
            created_strategies.append(strategy_data)
        
        await self.session.commit()
        return created_strategies
    
    async def anonymize_production_data(self, sample_size: int = 1000):
        """匿名化生產資料用於開發環境"""
        
        # 只在非生產環境執行
        if self.environment == "production":
            raise ValueError("不能在生產環境執行資料匿名化")
        
        # 獲取生產環境資料樣本
        orders = await self.session.execute(
            "SELECT * FROM orders ORDER BY created_at DESC LIMIT :limit",
            {"limit": sample_size}
        )
        
        anonymized_orders = []
        for order in orders:
            anonymized_order = {
                **order,
                "user_id": f"test_user_{hash(order['user_id']) % 1000}",
                "api_key": "test_api_key",
                "created_at": order["created_at"]
            }
            anonymized_orders.append(anonymized_order)
        
        return anonymized_orders

async def setup_test_environment():
    """設定測試環境"""
    
    manager = TestDataManager("development")
    
    print("🔧 設定測試環境...")
    
    # 創建測試資料
    users = await manager.create_test_users()
    strategies = await manager.create_test_strategies()
    
    print(f"✅ 創建了 {len(users)} 個測試使用者")
    print(f"✅ 創建了 {len(strategies)} 個測試策略")
    
    print("🎉 測試環境設定完成!")

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

監控和告警差異

環境特定監控配置

# monitoring/alerts/development.yml
groups:
  - name: development-alerts
    rules:
      - alert: ServiceDown
        expr: up{environment="development"} == 0
        for: 5m
        labels:
          severity: info
          environment: development
        annotations:
          summary: "開發環境服務停止"
          description: "開發環境的服務已停止超過 5 分鐘"

      - alert: HighMemoryUsage
        expr: memory_usage_percent{environment="development"} > 90
        for: 10m
        labels:
          severity: warning
          environment: development
        annotations:
          summary: "開發環境記憶體使用率過高"
# monitoring/alerts/production.yml
groups:
  - name: production-alerts
    rules:
      - alert: ServiceDown
        expr: up{environment="production"} == 0
        for: 1m
        labels:
          severity: critical
          environment: production
        annotations:
          summary: "生產環境服務停止"
          description: "生產環境的服務已停止超過 1 分鐘"

      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5..",environment="production"}[5m]) > 0.01
        for: 2m
        labels:
          severity: critical
          environment: production
        annotations:
          summary: "生產環境錯誤率過高"
          description: "生產環境 5xx 錯誤率超過 1%"

      - alert: TradingLoss
        expr: trading_pnl{environment="production"} < -10000
        for: 1m
        labels:
          severity: critical
          environment: production
        annotations:
          summary: "交易虧損超過閾值"
          description: "交易系統累計虧損超過 $10,000"

小結

今天我們學習了如何有效管理 Production 和 Development 環境,就像農場的試驗田和正式農田需要不同的管理策略一樣。重要的概念包括:

  1. 環境差異化:根據環境特性調整配置和資源
  2. 安全的同步流程:在環境間安全地同步配置和資料
  3. 配置管理:使用環境特定的配置檔案和變數
  4. 監控差異:不同環境的監控和告警策略
  5. 測試資料管理:安全地處理和匿名化測試資料

至此,我們完成了 CI/CD 相關的學習!明天我們將開始進入量化交易的理論部分,探討量化交易與傳統交易的差異。


下一篇:Day 16 - 淺談關於量化 vs 傳統交易的部分


上一篇
Day 14: Github Setup Environment
系列文
小資族的量化交易 10115
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言