03-4. 生產環境部署

Nginx + Gunicorn 完整部署指南與最佳實踐

🎯 本章重點

學習如何將 Django + Gunicorn 應用部署到生產環境,包含完整的配置、監控和維護。

🏗️ 生產環境架構

標準架構圖

Internet
  ↓
┌─────────────────────────────────────────────┐
│  Cloudflare / CDN                            │
│  • DDoS 防護                                 │
│  • SSL/TLS                                   │
│  • 靜態資源緩存                               │
└─────────────────────────────────────────────┘
  ↓
┌─────────────────────────────────────────────┐
│  Nginx (反向代理 + 負載均衡)                  │
│  • 端口:80 (HTTP) / 443 (HTTPS)             │
│  • 功能:                                    │
│    - 反向代理到 Gunicorn                      │
│    - 靜態文件服務                             │
│    - SSL 終止                                │
│    - Gzip 壓縮                               │
│    - 請求限流                                 │
│    - 健康檢查                                 │
└─────────────────────────────────────────────┘
  ↓
┌─────────────────────────────────────────────┐
│  Gunicorn (WSGI/ASGI Server)                │
│  • 端口:8000 (內部)                         │
│  • 配置:                                    │
│    - workers = CPU 核心數                    │
│    - worker_class = "uvicorn.workers.UvicornWorker" │
│  • 管理:Systemd                             │
└─────────────────────────────────────────────┘
  ↓
┌─────────────────────────────────────────────┐
│  Django Application                         │
│  • Python 虛擬環境                           │
│  • 配置文件                                  │
│  • 靜態文件收集                               │
└─────────────────────────────────────────────┘
  ↓
┌──────────────┬──────────────┬──────────────┐
│ PostgreSQL   │ Redis        │ Celery       │
│ (資料庫)      │ (緩存/隊列)   │ (異步任務)    │
└──────────────┴──────────────┴──────────────┘

📦 環境準備

系統要求

# 推薦環境
OS: Ubuntu 22.04 LTS / 24.04 LTS
CPU: 2 核以上
RAM: 4GB 以上
Disk: 20GB 以上(根據數據量調整)

安裝基礎軟體

# 更新系統
sudo apt update
sudo apt upgrade -y

# 安裝 Python 3.11 和相關工具
sudo apt install -y python3.11 python3.11-venv python3.11-dev
sudo apt install -y python3-pip build-essential

# 安裝 Nginx
sudo apt install -y nginx

# 安裝 PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# 安裝 Redis
sudo apt install -y redis-server

# 安裝其他工具
sudo apt install -y git curl wget htop supervisor

🔧 應用部署

1. 創建專用用戶

# 創建專用用戶(安全性)
sudo adduser --system --group --home /opt/myapp myapp

# 設定目錄權限
sudo chown -R myapp:myapp /opt/myapp

2. 部署應用代碼

# 切換到應用用戶
sudo su - myapp

# 克隆代碼
cd /opt/myapp
git clone https://github.com/yourcompany/myapp.git app
cd app

# 創建虛擬環境
python3.11 -m venv venv

# 啟動虛擬環境
source venv/bin/activate

# 安裝依賴
pip install --upgrade pip
pip install -r requirements.txt

# 安裝 Gunicorn 和 Uvicorn
pip install gunicorn uvicorn[standard]

3. Django 配置

# myapp/settings/production.py
import os
from .base import *

# 安全設定
DEBUG = False
SECRET_KEY = os.environ.get('SECRET_KEY')
ALLOWED_HOSTS = [
    'example.com',
    'www.example.com',
    'api.example.com',
]

# 資料庫配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': 'localhost',
        'PORT': '5432',
        'CONN_MAX_AGE': 600,  # 連接池
    }
}

# Redis 緩存
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 50,
            },
        }
    }
}

# 靜態文件
STATIC_ROOT = '/opt/myapp/app/staticfiles'
STATIC_URL = '/static/'

MEDIA_ROOT = '/opt/myapp/app/media'
MEDIA_URL = '/media/'

# 安全設定
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

# 日誌配置
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/myapp/django.log',
            'maxBytes': 100 * 1024 * 1024,  # 100MB
            'backupCount': 10,
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['file'],
        'level': 'WARNING',
    },
}

4. 初始化應用

# 設定環境變數
cat > /opt/myapp/.env << EOF
SECRET_KEY=your-secret-key-here
DB_NAME=myapp_db
DB_USER=myapp_user
DB_PASSWORD=your-db-password
DJANGO_SETTINGS_MODULE=myapp.settings.production
EOF

# 創建資料庫
sudo -u postgres psql << EOF
CREATE DATABASE myapp_db;
CREATE USER myapp_user WITH PASSWORD 'your-db-password';
ALTER ROLE myapp_user SET client_encoding TO 'utf8';
ALTER ROLE myapp_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE myapp_user SET timezone TO 'Asia/Taipei';
GRANT ALL PRIVILEGES ON DATABASE myapp_db TO myapp_user;
EOF

# 執行遷移
source venv/bin/activate
export $(cat /opt/myapp/.env | xargs)
python manage.py migrate

# 收集靜態文件
python manage.py collectstatic --noinput

# 創建超級用戶
python manage.py createsuperuser

5. Gunicorn 配置

# /opt/myapp/app/gunicorn.conf.py
import multiprocessing
import os

# 基本配置
bind = "127.0.0.1:8000"  # 只監聽本地(Nginx 反向代理)
workers = multiprocessing.cpu_count()
worker_class = "uvicorn.workers.UvicornWorker"

# 超時配置
timeout = 120
graceful_timeout = 30
keepalive = 20

# 防止記憶體洩漏
max_requests = 2000
max_requests_jitter = 200

# 性能優化
preload_app = True
worker_tmp_dir = "/dev/shm"

# 日誌配置
accesslog = "/var/log/myapp/gunicorn-access.log"
errorlog = "/var/log/myapp/gunicorn-error.log"
loglevel = "warning"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Hooks
def post_fork(server, worker):
    from django import db
    from django.core.cache import cache

    db.connections.close_all()

    try:
        cache.get('health_check')
    except Exception as e:
        server.log.error(f"Redis connection failed: {e}")

    server.log.info(f"Worker {worker.pid} spawned")

def worker_exit(server, worker):
    from django import db
    db.connections.close_all()
    server.log.info(f"Worker {worker.pid} exited")

def on_starting(server):
    server.log.info("Gunicorn server starting")

def on_reload(server):
    server.log.info("Gunicorn server reloading")

6. 創建日誌目錄

# 創建日誌目錄
sudo mkdir -p /var/log/myapp

# 設定權限
sudo chown -R myapp:myapp /var/log/myapp

🔄 Systemd 服務配置

Gunicorn Service

# /etc/systemd/system/myapp-gunicorn.service
[Unit]
Description=Gunicorn daemon for Django myapp
After=network.target

[Service]
Type=notify
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp/app
EnvironmentFile=/opt/myapp/.env

# 虛擬環境路徑
Environment="PATH=/opt/myapp/app/venv/bin"

# Gunicorn 啟動命令
ExecStart=/opt/myapp/app/venv/bin/gunicorn \
    -c /opt/myapp/app/gunicorn.conf.py \
    myapp.asgi:application

# 重載設定
ExecReload=/bin/kill -s HUP $MAINPID

# 優雅停止
KillMode=mixed
TimeoutStopSec=60

# 重啟策略
Restart=always
RestartSec=5

# 安全加固
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Celery Worker Service(可選)

# /etc/systemd/system/myapp-celery.service
[Unit]
Description=Celery worker for Django myapp
After=network.target redis.service

[Service]
Type=forking
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp/app
EnvironmentFile=/opt/myapp/.env

ExecStart=/opt/myapp/app/venv/bin/celery -A myapp worker \
    --pool=prefork \
    --concurrency=4 \
    --loglevel=info \
    --logfile=/var/log/myapp/celery-worker.log \
    --pidfile=/var/run/celery/worker.pid

ExecStop=/bin/kill -s TERM $MAINPID

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

啟動服務

# 重新載入 systemd
sudo systemctl daemon-reload

# 啟動 Gunicorn
sudo systemctl start myapp-gunicorn
sudo systemctl enable myapp-gunicorn

# 啟動 Celery(可選)
sudo systemctl start myapp-celery
sudo systemctl enable myapp-celery

# 查看狀態
sudo systemctl status myapp-gunicorn
sudo systemctl status myapp-celery

# 查看日誌
sudo journalctl -u myapp-gunicorn -f
sudo journalctl -u myapp-celery -f

🌐 Nginx 配置

完整配置範例

# /etc/nginx/sites-available/myapp.conf

# 上游 Gunicorn 服務器
upstream gunicorn_backend {
    # Gunicorn 地址
    server 127.0.0.1:8000 fail_timeout=0;

    # 如果有多個 Gunicorn 實例(負載均衡)
    # server 127.0.0.1:8001 fail_timeout=0;
    # server 127.0.0.1:8002 fail_timeout=0;
}

# HTTP → HTTPS 重定向
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt 證書驗證
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # 重定向到 HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS 主服務器
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL 證書
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # SSL 配置(Mozilla Modern)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # 安全頭
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # 上傳大小限制
    client_max_body_size 100M;
    client_body_buffer_size 1m;

    # 靜態文件
    location /static/ {
        alias /opt/myapp/app/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";

        # Gzip 壓縮
        gzip on;
        gzip_types text/css application/javascript image/svg+xml;
        gzip_vary on;
    }

    # 媒體文件
    location /media/ {
        alias /opt/myapp/app/media/;
        expires 7d;
        add_header Cache-Control "public";
    }

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

    # 反向代理到 Gunicorn
    location / {
        # 代理設定
        proxy_pass http://gunicorn_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket 支援(如果需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 超時設定
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 緩衝設定
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }

    # 請求限流(防止暴力攻擊)
    location /api/login/ {
        limit_req zone=login_limit burst=5 nodelay;
        proxy_pass http://gunicorn_backend;
        # ... 其他 proxy 設定
    }

    # 日誌
    access_log /var/log/nginx/myapp-access.log combined;
    error_log /var/log/nginx/myapp-error.log warn;
}

# 請求限流配置
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

啟用配置

# 創建軟連接
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/

# 測試配置
sudo nginx -t

# 重載 Nginx
sudo systemctl reload nginx

# 查看狀態
sudo systemctl status nginx

🔒 SSL 證書配置

使用 Let’s Encrypt(免費)

# 安裝 Certbot
sudo apt install -y certbot python3-certbot-nginx

# 申請證書
sudo certbot --nginx -d example.com -d www.example.com

# 測試自動更新
sudo certbot renew --dry-run

# 設定自動更新(cron)
sudo crontab -e

# 添加以下行(每天凌晨 2 點檢查)
0 2 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

📊 監控和日誌

1. 日誌輪轉配置

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 myapp myapp
    sharedscripts
    postrotate
        systemctl reload myapp-gunicorn > /dev/null 2>&1 || true
    endscript
}

# 測試
sudo logrotate -f /etc/logrotate.d/myapp

2. 性能監控

# 安裝 Prometheus Node Exporter
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz
tar xvfz node_exporter-*.tar.gz
sudo mv node_exporter-*/node_exporter /usr/local/bin/
sudo useradd -rs /bin/false node_exporter

# 創建 systemd 服務
sudo tee /etc/systemd/system/node_exporter.service << EOF
[Unit]
Description=Node Exporter
After=network.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target
EOF

# 啟動服務
sudo systemctl daemon-reload
sudo systemctl start node_exporter
sudo systemctl enable node_exporter

3. 應用監控腳本

# /opt/myapp/scripts/monitor.sh
#!/bin/bash

# 檢查 Gunicorn 狀態
if ! systemctl is-active --quiet myapp-gunicorn; then
    echo "Gunicorn is down!" | mail -s "Alert: Gunicorn Down" admin@example.com
    systemctl restart myapp-gunicorn
fi

# 檢查 Nginx 狀態
if ! systemctl is-active --quiet nginx; then
    echo "Nginx is down!" | mail -s "Alert: Nginx Down" admin@example.com
    systemctl restart nginx
fi

# 檢查磁碟空間
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
    echo "Disk usage is ${DISK_USAGE}%" | mail -s "Alert: High Disk Usage" admin@example.com
fi

# 檢查記憶體
MEM_USAGE=$(free | awk 'NR==2 {printf "%.0f", $3/$2 * 100}')
if [ $MEM_USAGE -gt 90 ]; then
    echo "Memory usage is ${MEM_USAGE}%" | mail -s "Alert: High Memory Usage" admin@example.com
fi
# 設定 cron(每 5 分鐘檢查一次)
*/5 * * * * /opt/myapp/scripts/monitor.sh

🚀 零停機部署

部署腳本

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

set -e  # 遇到錯誤立即退出

echo "🚀 Starting deployment..."

# 切換到應用目錄
cd /opt/myapp/app

# 拉取最新代碼
echo "📥 Pulling latest code..."
git fetch origin
git reset --hard origin/main

# 啟動虛擬環境
source venv/bin/activate

# 安裝/更新依賴
echo "📦 Installing dependencies..."
pip install -r requirements.txt --quiet

# 執行遷移
echo "🗄️  Running migrations..."
python manage.py migrate --noinput

# 收集靜態文件
echo "📁 Collecting static files..."
python manage.py collectstatic --noinput

# 零停機重載 Gunicorn
echo "🔄 Reloading Gunicorn (zero-downtime)..."
sudo systemctl reload myapp-gunicorn

# 等待重載完成
sleep 2

# 檢查服務狀態
if systemctl is-active --quiet myapp-gunicorn; then
    echo "✅ Deployment successful!"
else
    echo "❌ Deployment failed! Rolling back..."
    git reset --hard HEAD@{1}
    sudo systemctl restart myapp-gunicorn
    exit 1
fi

# 重啟 Celery(如果有)
if systemctl is-active --quiet myapp-celery; then
    echo "🔄 Restarting Celery workers..."
    sudo systemctl restart myapp-celery
fi

echo "🎉 Deployment completed!"
# 設定執行權限
chmod +x /opt/myapp/scripts/deploy.sh

# 執行部署
sudo -u myapp /opt/myapp/scripts/deploy.sh

Gunicorn 零停機重載原理

# HUP 信號觸發零停機重載
sudo systemctl reload myapp-gunicorn

# 等同於
sudo kill -HUP $(cat /var/run/gunicorn.pid)

# 重載流程:
# 1. Master 進程接收 HUP 信號
# 2. Master 創建新的 worker 進程
# 3. 新 workers 開始接收請求
# 4. 舊 workers 處理完當前請求後退出
# 5. 所有舊 workers 退出,重載完成

# 用戶完全無感知!✅

🛡️ 安全加固

1. 防火牆配置

# 使用 ufw
sudo apt install -y ufw

# 允許 SSH
sudo ufw allow 22/tcp

# 允許 HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# 啟用防火牆
sudo ufw enable

# 查看狀態
sudo ufw status

2. Fail2ban(防止暴力攻擊)

# 安裝 Fail2ban
sudo apt install -y fail2ban

# 配置
sudo tee /etc/fail2ban/jail.local << EOF
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/myapp-error.log
findtime = 600
bantime = 7200
maxretry = 10
EOF

# 重啟 Fail2ban
sudo systemctl restart fail2ban

# 查看狀態
sudo fail2ban-client status

3. 最小權限原則

# Django 應用目錄權限
sudo chown -R myapp:myapp /opt/myapp/app
sudo chmod -R 750 /opt/myapp/app

# 靜態文件目錄(Nginx 需要讀取)
sudo chown -R myapp:www-data /opt/myapp/app/staticfiles
sudo chmod -R 755 /opt/myapp/app/staticfiles

# 日誌目錄
sudo chown -R myapp:myapp /var/log/myapp
sudo chmod -R 750 /var/log/myapp

# 環境變數文件(敏感)
sudo chmod 600 /opt/myapp/.env

🔧 故障排查

常見問題

問題 1:Gunicorn 無法啟動

# 查看詳細錯誤
sudo journalctl -u myapp-gunicorn -n 50 --no-pager

# 常見原因:
# 1. 端口被佔用
sudo lsof -i :8000

# 2. 權限問題
ls -la /opt/myapp/app

# 3. Python 路徑問題
which python
/opt/myapp/app/venv/bin/python --version

# 4. 依賴缺失
/opt/myapp/app/venv/bin/pip list

# 手動測試啟動
sudo -u myapp /opt/myapp/app/venv/bin/gunicorn \
    -c /opt/myapp/app/gunicorn.conf.py \
    myapp.asgi:application

問題 2:502 Bad Gateway

# 原因:Nginx 無法連接到 Gunicorn

# 檢查 Gunicorn 是否運行
sudo systemctl status myapp-gunicorn

# 檢查端口監聽
sudo netstat -tlnp | grep 8000

# 檢查 Nginx 配置
sudo nginx -t

# 查看 Nginx 錯誤日誌
sudo tail -f /var/log/nginx/myapp-error.log

# 測試直接訪問 Gunicorn
curl http://127.0.0.1:8000

問題 3:Workers 頻繁重啟

# 查看日誌
sudo journalctl -u myapp-gunicorn -f

# 常見原因:
# 1. 記憶體不足
free -h
sudo dmesg | grep -i 'out of memory'

# 2. Worker 超時
# 檢查 gunicorn.conf.py 中的 timeout 設定

# 3. 代碼錯誤
# 查看應用日誌
tail -f /var/log/myapp/django.log

問題 4:靜態文件 404

# 檢查靜態文件路徑
ls -la /opt/myapp/app/staticfiles

# 重新收集靜態文件
sudo -u myapp bash -c "cd /opt/myapp/app && source venv/bin/activate && python manage.py collectstatic --noinput"

# 檢查 Nginx 配置
sudo nginx -t
grep -A 5 "location /static/" /etc/nginx/sites-available/myapp.conf

# 檢查權限
ls -ld /opt/myapp/app/staticfiles

📈 性能優化

1. Nginx 緩存

# 在 http 區塊中添加
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;

# 在 location / 中添加
proxy_cache my_cache;
proxy_cache_valid 200 10m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;

2. 資料庫連接池

# 使用 django-db-connection-pool
pip install django-db-connection-pool

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'dj_db_conn_pool.backends.postgresql',
        'NAME': 'myapp_db',
        'POOL_OPTIONS': {
            'POOL_SIZE': 10,
            'MAX_OVERFLOW': 5,
        }
    }
}

3. 啟用 HTTP/2

# 在 server 區塊中
listen 443 ssl http2;
listen [::]:443 ssl http2;

💡 面試常見問題

Q1:生產環境部署的完整流程是什麼?

完整答案:

部署流程分為準備、部署、驗證三個階段:

1. 環境準備

  • 安裝系統依賴(Python、Nginx、PostgreSQL、Redis)
  • 創建專用用戶和目錄
  • 配置虛擬環境

2. 應用部署

# 拉取代碼
git clone repo

# 安裝依賴
pip install -r requirements.txt

# 配置環境變數
# 執行遷移
python manage.py migrate

# 收集靜態文件
python manage.py collectstatic

3. 服務配置

  • 配置 Gunicorn(gunicorn.conf.py)
  • 配置 Systemd 服務(自動啟動、重啟)
  • 配置 Nginx(反向代理、靜態文件)
  • 配置 SSL 證書

4. 安全加固

  • 防火牆(UFW)
  • Fail2ban(防暴力攻擊)
  • 最小權限原則
  • SSL/TLS 配置

5. 監控和日誌

  • 日誌輪轉(Logrotate)
  • 監控腳本(健康檢查)
  • 性能監控(Prometheus)

6. 驗證

# 檢查服務狀態
systemctl status myapp-gunicorn

# 測試訪問
curl https://example.com

# 查看日誌
journalctl -u myapp-gunicorn -f

Q2:如何實現零停機部署?

完整答案:

零停機部署的核心:利用 Gunicorn 的 graceful reload

原理:

# 發送 HUP 信號
sudo systemctl reload myapp-gunicorn

# 等同於
sudo kill -HUP $(cat /var/run/gunicorn.pid)

# 重載流程:
1. Master 進程接收 HUP 信號
2. 創建新的 worker 進程
3. 新 workers 載入最新代碼
4. 新 workers 開始接收請求
5. 舊 workers 處理完當前請求後退出
6. 所有舊 workers 退出,完成重載

# 用戶完全無感知!

完整部署腳本:

#!/bin/bash
# 1. 拉取最新代碼
git pull origin main

# 2. 安裝依賴
pip install -r requirements.txt

# 3. 執行遷移
python manage.py migrate

# 4. 收集靜態文件
python manage.py collectstatic --noinput

# 5. 零停機重載
sudo systemctl reload myapp-gunicorn

# 6. 驗證部署
if systemctl is-active --quiet myapp-gunicorn; then
    echo "Deployment successful"
else
    echo "Deployment failed, rolling back"
    git reset --hard HEAD@{1}
    sudo systemctl restart myapp-gunicorn
fi

注意事項:

  • ✅ 適用於代碼更新
  • ✅ 適用於配置更新
  • ⚠️ 不適用於 Gunicorn 版本升級(需要 restart)
  • ⚠️ 資料庫遷移要向後兼容(舊代碼能跑新 schema)

Q3:Nginx 和 Gunicorn 各自的職責是什麼?

完整答案:

Nginx 的職責(前端服務器):

  1. 反向代理

    • 接收用戶請求,轉發到 Gunicorn
    • 負載均衡(如果有多個 Gunicorn 實例)
  2. 靜態文件服務

    • 直接提供 /static/、/media/ 文件
    • Nginx 處理靜態文件比 Python 快 10-100 倍
  3. SSL/TLS 終止

    • 處理 HTTPS 加密/解密
    • 減輕 Gunicorn 負擔
  4. 安全和限流

    • 請求限流(Rate Limiting)
    • 防止 DDoS
    • IP 白名單/黑名單
  5. 緩存

    • 緩存動態內容
    • 減少 Gunicorn 負載
  6. 壓縮

    • Gzip 壓縮響應
    • 節省帶寬

Gunicorn 的職責(應用服務器):

  1. 運行 Django 應用

    • 執行 Python 代碼
    • 處理業務邏輯
  2. 管理 Workers

    • 創建和管理多個 worker 進程
    • 自動重啟崩潰的 workers
  3. 處理動態請求

    • 執行視圖函數
    • 查詢資料庫
    • 生成 HTML/JSON
  4. 維護連接池

    • 資料庫連接池
    • Redis 連接池

為什麼需要兩者?

❌ 錯誤架構:直接暴露 Gunicorn
Internet → Gunicorn (8000) → Django

問題:
- 沒有 SSL
- 靜態文件性能差
- 無法限流
- 安全性低

✅ 正確架構:Nginx + Gunicorn
Internet → Nginx (443) → Gunicorn (127.0.0.1:8000) → Django

優點:
- SSL 終止
- 靜態文件高效
- 請求限流
- 負載均衡
- 緩存加速

類比:

  • Nginx = 前台接待(處理常見問題、引導客人)
  • Gunicorn = 業務專員(處理複雜業務邏輯)

Q4:如何監控和排查生產環境問題?

完整答案:

監控層次:

1. 系統層監控

# CPU、記憶體、磁碟
htop
free -h
df -h

# 網絡連接
netstat -tlnp

# 進程狀態
ps aux | grep gunicorn

2. 服務層監控

# Systemd 服務狀態
systemctl status myapp-gunicorn
systemctl status nginx

# 日誌
journalctl -u myapp-gunicorn -f
tail -f /var/log/nginx/myapp-error.log

3. 應用層監控

# Django 日誌
tail -f /var/log/myapp/django.log

# Gunicorn 日誌
tail -f /var/log/myapp/gunicorn-access.log
tail -f /var/log/myapp/gunicorn-error.log

4. 常見問題排查

問題:502 Bad Gateway

# 原因:Nginx 無法連接到 Gunicorn

# 檢查 Gunicorn 是否運行
systemctl status myapp-gunicorn

# 檢查端口監聽
netstat -tlnp | grep 8000

# 測試直接訪問
curl http://127.0.0.1:8000

問題:Workers 頻繁重啟

# 查看日誌找原因
journalctl -u myapp-gunicorn -f

# 常見原因:
1. 記憶體不足 → free -h
2. Worker 超時 → 調整 timeout
3. 代碼錯誤 → 查看應用日誌

問題:響應慢

# 查看 access log
tail -f /var/log/myapp/gunicorn-access.log
# 看 %(D)s(響應時間)

# 分析慢查詢
# 啟用 Django Debug Toolbar(測試環境)
# 或使用 django-silk

# 檢查資料庫
# PostgreSQL 慢查詢日誌

5. 自動化監控

# 監控腳本(Cron 每 5 分鐘)
*/5 * * * * /opt/myapp/scripts/monitor.sh

# 檢查:
- 服務是否運行
- 磁碟空間
- 記憶體使用
- CPU 使用
- 響應時間

# 告警:
- 郵件通知
- Slack 通知
- 自動重啟(如果可能)

✅ 重點回顧

部署架構

Internet → Nginx (443) → Gunicorn (127.0.0.1:8000) → Django
                 ↓
           靜態文件 (/static/, /media/)

關鍵配置

# Gunicorn
workers = CPU 核心數
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 120
preload_app = True

# Nginx
upstream gunicorn_backend {
    server 127.0.0.1:8000;
}

# Systemd
Type=notify
Restart=always

零停機部署

# 核心命令
sudo systemctl reload myapp-gunicorn

# 原理:
# 1. 創建新 workers
# 2. 新 workers 接收請求
# 3. 舊 workers 優雅退出

安全加固

1. 防火牆(UFW)
2. Fail2ban(防暴力攻擊)
3. SSL/TLS(Let's Encrypt)
4. 最小權限原則

📚 接下來

恭喜完成 03. Gunicorn 實戰篇

下一章:04-1. Worker 超時問題

  • 常見問題排查
  • 記憶體洩漏
  • 連接數不足
  • CPU 使用率過高

最後更新:2025-10-31

0%