iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0

小明的現代化農場工廠

今天要學習如何將我們的交易系統容器化,這就像爸爸把傳統農場改造成現代化的工廠一樣。每個功能都被包裝在標準化的「容器」裡,可以在任何地方快速部署,維護更簡單,擴展更容易!

容器化架構設計

微服務容器架構

微服務容器架構

Docker 容器化實作

1. 基礎 Dockerfile 設計

# 交易引擎 Dockerfile
FROM python:3.11-slim

# 設置工作目錄
WORKDIR /app

# 安裝系統依賴
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 複製需求文件
COPY requirements.txt .

# 安裝 Python 依賴
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用程式碼
COPY src/ ./src/
COPY config/ ./config/

# 創建非 root 用戶
RUN useradd -m -s /bin/bash trader && \
    chown -R trader:trader /app

USER trader

# 設置環境變數
ENV PYTHONPATH=/app/src
ENV PYTHONUNBUFFERED=1

# 健康檢查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python src/health_check.py

# 暴露端口
EXPOSE 8080

# 啟動命令
CMD ["python", "src/trading_engine.py"]

2. 多階段構建優化

# 多階段構建 Dockerfile
# 第一階段:構建環境
FROM python:3.11 AS builder

WORKDIR /app

# 安裝構建依賴
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 安裝 Python 依賴
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# 第二階段:運行環境
FROM python:3.11-slim AS runtime

WORKDIR /app

# 安裝運行時依賴
RUN apt-get update && apt-get install -y \
    libpq5 \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# 從構建階段複製 Python 包
COPY --from=builder /root/.local /root/.local

# 複製應用程式碼
COPY src/ ./src/
COPY config/ ./config/

# 創建非 root 用戶
RUN useradd -m -s /bin/bash trader && \
    chown -R trader:trader /app

USER trader

# 更新 PATH
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONPATH=/app/src
ENV PYTHONUNBUFFERED=1

# 健康檢查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python src/health_check.py

EXPOSE 8080

CMD ["python", "src/trading_engine.py"]

3. 服務容器實作

# src/trading_engine.py
import asyncio
import logging
import signal
import sys
from datetime import datetime
from typing import Dict, List, Optional
import aiohttp
from aiohttp import web
import json
import os

class TradingEngineService:
    """容器化交易引擎服務"""
    
    def __init__(self):
        self.app = web.Application()
        self.setup_routes()
        self.setup_middleware()
        
        # 服務狀態
        self.is_healthy = False
        self.start_time = datetime.utcnow()
        self.processed_signals = 0
        self.active_positions = {}
        
        # 配置
        self.config = self.load_config()
        
        # 設置日誌
        self.setup_logging()
        
        # 註冊信號處理器
        self.setup_signal_handlers()
        
    def load_config(self) -> Dict:
        """載入配置"""
        config = {
            'host': os.getenv('HOST', '0.0.0.0'),
            'port': int(os.getenv('PORT', 8080)),
            'debug': os.getenv('DEBUG', 'false').lower() == 'true',
            'redis_url': os.getenv('REDIS_URL', 'redis://localhost:6379'),
            'database_url': os.getenv('DATABASE_URL', 'postgresql://localhost/trading'),
            'binance_api_key': os.getenv('BINANCE_API_KEY'),
            'binance_secret_key': os.getenv('BINANCE_SECRET_KEY'),
            's3_config_bucket': os.getenv('S3_CONFIG_BUCKET'),
            'telegram_bot_token': os.getenv('TELEGRAM_BOT_TOKEN'),
            'telegram_chat_id': os.getenv('TELEGRAM_CHAT_ID')
        }
        return config
    
    def setup_logging(self):
        """設置日誌"""
        log_level = logging.DEBUG if self.config['debug'] else logging.INFO
        
        logging.basicConfig(
            level=log_level,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.StreamHandler(sys.stdout),
                logging.FileHandler('/app/logs/trading_engine.log') if os.path.exists('/app/logs') else logging.NullHandler()
            ]
        )
        
        self.logger = logging.getLogger(__name__)
    
    def setup_routes(self):
        """設置路由"""
        self.app.router.add_get('/health', self.health_check)
        self.app.router.add_get('/status', self.status_check)
        self.app.router.add_post('/signal', self.process_signal)
        self.app.router.add_get('/positions', self.get_positions)
        self.app.router.add_post('/positions/{symbol}/close', self.close_position)
        self.app.router.add_get('/metrics', self.get_metrics)
    
    def setup_middleware(self):
        """設置中間件"""
        
        @web.middleware
        async def logging_middleware(request, handler):
            start_time = datetime.utcnow()
            try:
                response = await handler(request)
                duration = (datetime.utcnow() - start_time).total_seconds()
                self.logger.info(f"{request.method} {request.path} - {response.status} - {duration:.3f}s")
                return response
            except Exception as e:
                duration = (datetime.utcnow() - start_time).total_seconds()
                self.logger.error(f"{request.method} {request.path} - ERROR: {e} - {duration:.3f}s")
                raise
        
        @web.middleware
        async def cors_middleware(request, handler):
            response = await handler(request)
            response.headers['Access-Control-Allow-Origin'] = '*'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
            return response
        
        self.app.middlewares.append(logging_middleware)
        self.app.middlewares.append(cors_middleware)
    
    def setup_signal_handlers(self):
        """設置信號處理器"""
        
        def signal_handler(signum, frame):
            self.logger.info(f"收到信號 {signum},開始優雅關閉...")
            asyncio.create_task(self.graceful_shutdown())
        
        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGINT, signal_handler)
    
    async def health_check(self, request):
        """健康檢查端點"""
        
        # 檢查各種依賴服務
        checks = {
            'service': True,
            'database': await self.check_database(),
            'redis': await self.check_redis(),
            's3': await self.check_s3(),
            'binance_api': await self.check_binance_api()
        }
        
        all_healthy = all(checks.values())
        status_code = 200 if all_healthy else 503
        
        response_data = {
            'status': 'healthy' if all_healthy else 'unhealthy',
            'timestamp': datetime.utcnow().isoformat(),
            'uptime_seconds': (datetime.utcnow() - self.start_time).total_seconds(),
            'checks': checks
        }
        
        return web.json_response(response_data, status=status_code)
    
    async def status_check(self, request):
        """狀態檢查端點"""
        
        status_data = {
            'service_name': 'trading-engine',
            'version': '1.0.0',
            'start_time': self.start_time.isoformat(),
            'uptime_seconds': (datetime.utcnow() - self.start_time).total_seconds(),
            'processed_signals': self.processed_signals,
            'active_positions_count': len(self.active_positions),
            'memory_usage': self.get_memory_usage(),
            'config': {
                'debug': self.config['debug'],
                'port': self.config['port']
            }
        }
        
        return web.json_response(status_data)
    
    async def process_signal(self, request):
        """處理交易信號"""
        
        try:
            data = await request.json()
            
            # 驗證信號格式
            required_fields = ['symbol', 'action', 'price', 'timestamp']
            if not all(field in data for field in required_fields):
                return web.json_response(
                    {'error': 'Missing required fields'}, 
                    status=400
                )
            
            # 處理信號
            result = await self.execute_trading_signal(data)
            
            self.processed_signals += 1
            
            return web.json_response({
                'status': 'success',
                'signal_id': result.get('signal_id'),
                'processed_at': datetime.utcnow().isoformat()
            })
            
        except json.JSONDecodeError:
            return web.json_response({'error': 'Invalid JSON'}, status=400)
        except Exception as e:
            self.logger.error(f"處理信號時發生錯誤: {e}")
            return web.json_response(
                {'error': 'Internal server error'}, 
                status=500
            )
    
    async def get_positions(self, request):
        """獲取持倉資訊"""
        
        positions = []
        for symbol, position_data in self.active_positions.items():
            positions.append({
                'symbol': symbol,
                'quantity': position_data['quantity'],
                'entry_price': position_data['entry_price'],
                'current_price': await self.get_current_price(symbol),
                'unrealized_pnl': position_data.get('unrealized_pnl', 0),
                'entry_time': position_data['entry_time']
            })
        
        return web.json_response({
            'positions': positions,
            'total_positions': len(positions),
            'timestamp': datetime.utcnow().isoformat()
        })
    
    async def close_position(self, request):
        """關閉特定持倉"""
        
        symbol = request.match_info['symbol']
        
        if symbol not in self.active_positions:
            return web.json_response(
                {'error': f'No active position for {symbol}'}, 
                status=404
            )
        
        try:
            # 執行平倉
            result = await self.execute_close_position(symbol)
            
            return web.json_response({
                'status': 'success',
                'closed_position': result,
                'timestamp': datetime.utcnow().isoformat()
            })
            
        except Exception as e:
            self.logger.error(f"平倉時發生錯誤: {e}")
            return web.json_response(
                {'error': 'Failed to close position'}, 
                status=500
            )
    
    async def get_metrics(self, request):
        """獲取服務指標(Prometheus 格式)"""
        
        metrics = [
            f"# HELP trading_engine_uptime_seconds Service uptime in seconds",
            f"# TYPE trading_engine_uptime_seconds gauge",
            f"trading_engine_uptime_seconds {(datetime.utcnow() - self.start_time).total_seconds()}",
            f"",
            f"# HELP trading_engine_processed_signals_total Total number of processed signals",
            f"# TYPE trading_engine_processed_signals_total counter",
            f"trading_engine_processed_signals_total {self.processed_signals}",
            f"",
            f"# HELP trading_engine_active_positions Current number of active positions",
            f"# TYPE trading_engine_active_positions gauge",
            f"trading_engine_active_positions {len(self.active_positions)}",
            f"",
            f"# HELP trading_engine_memory_usage_bytes Memory usage in bytes",
            f"# TYPE trading_engine_memory_usage_bytes gauge",
            f"trading_engine_memory_usage_bytes {self.get_memory_usage()}"
        ]
        
        return web.Response(
            text="\n".join(metrics),
            content_type="text/plain"
        )
    
    async def execute_trading_signal(self, signal_data: Dict) -> Dict:
        """執行交易信號"""
        
        # 這裡實作實際的交易邏輯
        self.logger.info(f"執行交易信號: {signal_data}")
        
        # 模擬信號處理
        signal_id = f"signal_{int(datetime.utcnow().timestamp())}"
        
        return {
            'signal_id': signal_id,
            'status': 'executed',
            'timestamp': datetime.utcnow().isoformat()
        }
    
    async def execute_close_position(self, symbol: str) -> Dict:
        """執行平倉"""
        
        position = self.active_positions.get(symbol)
        if not position:
            raise ValueError(f"No position found for {symbol}")
        
        # 實作平倉邏輯
        self.logger.info(f"平倉 {symbol}")
        
        # 從活躍持倉中移除
        del self.active_positions[symbol]
        
        return {
            'symbol': symbol,
            'closed_quantity': position['quantity'],
            'pnl': position.get('unrealized_pnl', 0)
        }
    
    async def check_database(self) -> bool:
        """檢查數據庫連接"""
        try:
            # 實作數據庫健康檢查
            return True
        except:
            return False
    
    async def check_redis(self) -> bool:
        """檢查 Redis 連接"""
        try:
            # 實作 Redis 健康檢查
            return True
        except:
            return False
    
    async def check_s3(self) -> bool:
        """檢查 S3 連接"""
        try:
            # 實作 S3 健康檢查
            return True
        except:
            return False
    
    async def check_binance_api(self) -> bool:
        """檢查 Binance API 連接"""
        try:
            # 實作 Binance API 健康檢查
            return True
        except:
            return False
    
    async def get_current_price(self, symbol: str) -> float:
        """獲取當前價格"""
        # 實作價格獲取邏輯
        return 50000.0  # 模擬價格
    
    def get_memory_usage(self) -> int:
        """獲取記憶體使用量"""
        import psutil
        process = psutil.Process()
        return process.memory_info().rss
    
    async def graceful_shutdown(self):
        """優雅關閉"""
        
        self.logger.info("開始優雅關閉流程...")
        
        # 1. 停止接受新請求
        self.is_healthy = False
        
        # 2. 完成正在處理的請求
        await asyncio.sleep(2)
        
        # 3. 關閉所有持倉(可選)
        if self.active_positions:
            self.logger.info("關閉所有活躍持倉...")
            for symbol in list(self.active_positions.keys()):
                try:
                    await self.execute_close_position(symbol)
                except Exception as e:
                    self.logger.error(f"關閉持倉 {symbol} 時發生錯誤: {e}")
        
        # 4. 關閉數據庫連接
        # await self.close_database_connections()
        
        self.logger.info("優雅關閉完成")
        sys.exit(0)
    
    async def start_server(self):
        """啟動服務器"""
        
        runner = web.AppRunner(self.app)
        await runner.setup()
        
        site = web.TCPSite(
            runner, 
            self.config['host'], 
            self.config['port']
        )
        
        await site.start()
        
        self.is_healthy = True
        self.logger.info(f"交易引擎服務已啟動,監聽 {self.config['host']}:{self.config['port']}")
        
        # 保持服務運行
        try:
            while True:
                await asyncio.sleep(1)
        except asyncio.CancelledError:
            await runner.cleanup()

# src/health_check.py
import asyncio
import aiohttp
import sys
import os

async def health_check():
    """健康檢查腳本"""
    
    port = os.getenv('PORT', 8080)
    
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(f'http://localhost:{port}/health', timeout=5) as response:
                if response.status == 200:
                    print("健康檢查通過")
                    sys.exit(0)
                else:
                    print(f"健康檢查失敗,狀態碼: {response.status}")
                    sys.exit(1)
    except Exception as e:
        print(f"健康檢查錯誤: {e}")
        sys.exit(1)

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

# 主程式入口
if __name__ == "__main__":
    service = TradingEngineService()
    asyncio.run(service.start_server())

Docker Compose 編排

1. 完整的 Docker Compose 設定

# docker-compose.yml
version: '3.8'

services:
  # 交易引擎服務
  trading-engine:
    build:
      context: .
      dockerfile: Dockerfile.trading-engine
    container_name: trading-engine
    ports:
      - "8080:8080"
    environment:
      - DEBUG=false
      - REDIS_URL=redis://redis:6379
      - DATABASE_URL=postgresql://postgres:password@postgres:5432/trading
      - S3_CONFIG_BUCKET=${S3_CONFIG_BUCKET}
      - BINANCE_API_KEY=${BINANCE_API_KEY}
      - BINANCE_SECRET_KEY=${BINANCE_SECRET_KEY}
      - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
      - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
    depends_on:
      - postgres
      - redis
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    networks:
      - trading-network
    healthcheck:
      test: ["CMD", "python", "src/health_check.py"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # 市場數據服務
  market-data:
    build:
      context: .
      dockerfile: Dockerfile.market-data
    container_name: market-data
    ports:
      - "8081:8080"
    environment:
      - DEBUG=false
      - REDIS_URL=redis://redis:6379
      - BINANCE_API_KEY=${BINANCE_API_KEY}
      - BINANCE_SECRET_KEY=${BINANCE_SECRET_KEY}
    depends_on:
      - redis
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    networks:
      - trading-network

  # 風險管理服務
  risk-management:
    build:
      context: .
      dockerfile: Dockerfile.risk-management
    container_name: risk-management
    ports:
      - "8082:8080"
    environment:
      - DEBUG=false
      - DATABASE_URL=postgresql://postgres:password@postgres:5432/trading
      - S3_CONFIG_BUCKET=${S3_CONFIG_BUCKET}
    depends_on:
      - postgres
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    networks:
      - trading-network

  # 通知服務
  notification:
    build:
      context: .
      dockerfile: Dockerfile.notification
    container_name: notification
    ports:
      - "8083:8080"
    environment:
      - DEBUG=false
      - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
      - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
      - SMTP_SERVER=${SMTP_SERVER}
      - SMTP_PORT=${SMTP_PORT}
      - SENDER_EMAIL=${SENDER_EMAIL}
    restart: unless-stopped
    networks:
      - trading-network

  # PostgreSQL 數據庫
  postgres:
    image: postgres:15
    container_name: postgres-db
    environment:
      - POSTGRES_DB=trading
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
    restart: unless-stopped
    networks:
      - trading-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis 緩存
  redis:
    image: redis:7-alpine
    container_name: redis-cache
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped
    networks:
      - trading-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Nginx 負載平衡器
  nginx:
    image: nginx:alpine
    container_name: nginx-lb
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - trading-engine
      - market-data
      - risk-management
      - notification
    restart: unless-stopped
    networks:
      - trading-network

  # Prometheus 監控
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./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'
    restart: unless-stopped
    networks:
      - trading-network

  # Grafana 儀表板
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    depends_on:
      - prometheus
    restart: unless-stopped
    networks:
      - trading-network

volumes:
  postgres_data:
  redis_data:
  prometheus_data:
  grafana_data:

networks:
  trading-network:
    driver: bridge

2. Nginx 配置

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream trading_engine {
        server trading-engine:8080;
    }
    
    upstream market_data {
        server market-data:8080;
    }
    
    upstream risk_management {
        server risk-management:8080;
    }
    
    upstream notification {
        server notification:8080;
    }

    # 日誌格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;

    # API Gateway 配置
    server {
        listen 80;
        server_name localhost;

        # 健康檢查
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }

        # 交易引擎 API
        location /api/trading/ {
            proxy_pass http://trading_engine/;
            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 5s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        # 市場數據 API
        location /api/market/ {
            proxy_pass http://market_data/;
            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;
            
            # WebSocket 支持
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        # 風險管理 API
        location /api/risk/ {
            proxy_pass http://risk_management/;
            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;
        }

        # 通知服務 API
        location /api/notification/ {
            proxy_pass http://notification/;
            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 /static/ {
            alias /var/www/static/;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }

    # HTTPS 配置 (可選)
    server {
        listen 443 ssl http2;
        server_name localhost;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;

        # 其他配置與 HTTP 相同...
    }
}

3. 容器編排腳本

#!/bin/bash
# scripts/deploy.sh

set -e

echo "🚀 開始部署交易系統容器..."

# 檢查環境變數
required_vars=("BINANCE_API_KEY" "BINANCE_SECRET_KEY" "S3_CONFIG_BUCKET")
for var in "${required_vars[@]}"; do
    if [ -z "${!var}" ]; then
        echo "❌ 缺少環境變數: $var"
        exit 1
    fi
done

# 創建必要目錄
mkdir -p logs
mkdir -p ssl

# 構建映像
echo "🔨 構建 Docker 映像..."
docker-compose build --no-cache

# 啟動服務
echo "🚀 啟動服務..."
docker-compose up -d

# 等待服務啟動
echo "⏳ 等待服務啟動..."
sleep 30

# 檢查服務狀態
echo "🔍 檢查服務狀態..."
docker-compose ps

# 健康檢查
echo "🏥 執行健康檢查..."
services=("trading-engine" "market-data" "risk-management" "notification")

for service in "${services[@]}"; do
    echo "檢查 $service..."
    
    if docker-compose exec -T $service python src/health_check.py; then
        echo "✅ $service 健康檢查通過"
    else
        echo "❌ $service 健康檢查失敗"
        docker-compose logs $service
        exit 1
    fi
done

echo "🎉 部署完成!"
echo "📊 監控面板: http://localhost:3000 (admin/admin)"
echo "📈 Prometheus: http://localhost:9090"
echo "🔗 API Gateway: http://localhost:80"
#!/bin/bash
# scripts/stop.sh

echo "🛑 停止交易系統..."

# 優雅停止
docker-compose down --timeout 30

echo "✅ 交易系統已停止"
#!/bin/bash
# scripts/logs.sh

if [ -z "$1" ]; then
    echo "顯示所有服務日誌:"
    docker-compose logs -f
else
    echo "顯示 $1 服務日誌:"
    docker-compose logs -f $1
fi

4. Prometheus 監控配置

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'trading-engine'
    static_configs:
      - targets: ['trading-engine:8080']
    metrics_path: '/metrics'
    scrape_interval: 10s

  - job_name: 'market-data'
    static_configs:
      - targets: ['market-data:8080']
    metrics_path: '/metrics'
    scrape_interval: 10s

  - job_name: 'risk-management'
    static_configs:
      - targets: ['risk-management:8080']
    metrics_path: '/metrics'
    scrape_interval: 10s

  - job_name: 'notification'
    static_configs:
      - targets: ['notification:8080']
    metrics_path: '/metrics'
    scrape_interval: 10s

  - job_name: 'nginx'
    static_configs:
      - targets: ['nginx:80']
    metrics_path: '/metrics'
    scrape_interval: 30s

  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres:5432']
    scrape_interval: 30s

  - job_name: 'redis'
    static_configs:
      - targets: ['redis:6379']
    scrape_interval: 30s

容器管理最佳實踐

1. 安全性配置

# security_config.py
class ContainerSecurityConfig:
    """容器安全配置"""
    
    @staticmethod
    def get_security_best_practices():
        return {
            'dockerfile': [
                '使用非 root 用戶運行應用',
                '最小化映像層數',
                '不在映像中包含敏感資訊',
                '使用 .dockerignore 排除不必要文件',
                '定期更新基礎映像',
                '使用官方映像',
                '掃描映像漏洞'
            ],
            
            'runtime': [
                '限制容器資源使用',
                '設置只讀檔案系統',
                '禁用特權模式',
                '使用安全上下文',
                '網路隔離',
                '日誌監控',
                '定期備份'
            ],
            
            'secrets_management': [
                '使用環境變數傳遞敏感資訊',
                '避免在映像中硬編碼秘密',
                '使用 Docker Secrets 或外部秘密管理',
                '加密傳輸通道',
                '定期輪換憑證',
                '最小權限原則'
            ]
        }
    
    @staticmethod
    def generate_secure_compose_config():
        """生成安全的 Compose 配置"""
        
        security_config = {
            'security_opt': [
                'no-new-privileges:true'
            ],
            'cap_drop': ['ALL'],
            'cap_add': ['CHOWN', 'SETGID', 'SETUID'],
            'read_only': True,
            'tmpfs': ['/tmp', '/var/run'],
            'user': '1000:1000'
        }
        
        return security_config

2. 資源管理

# docker-compose.override.yml (生產環境)
version: '3.8'

services:
  trading-engine:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '1.0'
          memory: 1G
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID

  market-data:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

  postgres:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    command: postgres -c shared_preload_libraries=pg_stat_statements
    environment:
      - POSTGRES_INITDB_ARGS=--data-checksums

  redis:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

小結

今天我們完成了交易系統的容器化,就像把農場的每個功能都包裝成可隨時部署的標準化模組。重要概念包括:

容器化優勢:

  • 環境一致性
  • 快速部署和擴展
  • 資源隔離和管理
  • 微服務架構支持

Docker 最佳實踐:

  • 多階段構建優化
  • 非 root 用戶運行
  • 健康檢查機制
  • 優雅關閉處理

服務編排:

  • Docker Compose 管理
  • 服務依賴配置
  • 負載平衡和反向代理
  • 監控和日誌集中化

安全性考量:

  • 容器安全配置
  • 秘密管理
  • 網路隔離
  • 資源限制

記住爸爸說的:「好的工廠要有標準化的生產線,每個環節都能獨立運作,出問題時能快速定位和修復」。容器化讓我們的系統更加穩健和可維護!

經過 29 天的學習,我們已經建立了一個完整的雲端量化交易系統,從 AWS 基礎設施到容器化部署,涵蓋了現代金融科技的各個重要環節。


系列完結,感謝您的學習之旅!


上一篇
Day 28: Trading Config Setting on s3
系列文
小資族的量化交易 10129
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言