今天要學習如何有效管理 Production 和 Development 環境,這就像爸爸同時經營試驗田和正式農田一樣。試驗田可以大膽嘗試新品種,正式農田則要確保穩定收成。今天我們來學習如何在這兩個環境間取得平衡!
配置項目 | 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 環境,就像農場的試驗田和正式農田需要不同的管理策略一樣。重要的概念包括:
至此,我們完成了 CI/CD 相關的學習!明天我們將開始進入量化交易的理論部分,探討量化交易與傳統交易的差異。
下一篇:Day 16 - 淺談關於量化 vs 傳統交易的部分