Python WSGI/ASGI #
Menjalankan aplikasi web berbasis bahasa pemrograman Python di belakang server web Caddy adalah salah satu standar terbaik untuk deployment produksi modern. Ekosistem pengembangan web Python dibagi menjadi dua standar antarmuka server utama: WSGI (Web Server Gateway Interface) untuk framework tradisional berbasis sinkronus (synchronous) seperti Django dan Flask, serta ASGI (Asynchronous Server Gateway Interface) untuk framework modern berbasis asinkronus (asynchronous/real-time) seperti FastAPI dan Sanic. Karena Caddy ditulis dalam bahasa Go dan tidak dapat mengeksekusi kode Python secara native, kita memerlukan perantara berupa Application Server Python (Gunicorn untuk WSGI, dan Uvicorn untuk ASGI). Caddy bertindak di gerbang terdepan sebagai reverse proxy yang menangani protokol HTTPS otomatis, kompresi data, dan penyajian berkas statis berkinerja tinggi, sedangkan Gunicorn/Uvicorn bertugas mengelola proses worker Python di latar belakang. Kita akan membahas arsitektur integrasi WSGI dan ASGI, mempraktikkan setup Flask dengan Gunicorn serta FastAPI dengan Uvicorn, menyusun konfigurasi Django produksi, membuat manajemen service menggunakan systemd, hingga mengotomatiskan deployment tanpa downtime.
Arsitektur Deployment Python #
Memahami alur data request sangat penting saat merancang infrastruktur server Python kita.
Caddy berdiri sebagai benteng terdepan yang langsung menerima trafik internet (port 80 dan 443). Ketika request halaman dinamis masuk, Caddy mendekripsi koneksi SSL/TLS, lalu meneruskan request tersebut secara lokal melalui berkas socket UNIX ke Application Server Python. Gunicorn atau Uvicorn bertindak sebagai manajer proses yang mendistribusikan request tersebut ke beberapa anak proses (worker processes) yang menjalankan kode framework Django, Flask, atau FastAPI kita.
flowchart TD
Internet["Internet (Port 443)"] -->|"HTTPS"| Caddy["Server Caddy"]
Caddy -->|"Berkas Statis / Media"| Disk["Read langsung dari Disk"]
Caddy -->|"Berkas Dinamis / API"| Socket["Kirim ke UNIX Socket (via unix//run/gunicorn.sock)"]
subgraph Backend["Backend Python"]
Socket --> AppServer["Gunicorn / Uvicorn Pool (Application Server)"]
AppServer --> AppPython["Django / FastAPI App (Kode Python)"]
end
Disk --> Response["Respons HTML"]
AppPython --> Response
style Caddy stroke:#0288d1,stroke-width:2px
style Backend stroke:#37474f,stroke-width:1px,stroke-dasharray:5,5
Pemisahan ini memastikan bahwa biner Caddy yang sangat cepat menangani seluruh operasi jaringan I/O yang berat, sementara runtime Python hanya difokuskan pada pemrosesan logika aplikasi.
Flask + Gunicorn (WSGI) #
Flask adalah micro-framework sinkronus (WSGI) yang sangat populer karena kesederhanaannya. Di lingkungan produksi, kita menjalankan Flask di bawah pengawasan Gunicorn.
1. Persiapan Virtual Environment lokal #
Kita wajib mengisolasi dependensi pustaka Python proyek kita menggunakan virtual environment (venv) demi menghindari konflik versi pustaka global sistem operasi:
# Membuat direktori virtual environment di folder proyek
python3 -m venv /var/www/myapp/venv
source /var/www/myapp/venv/bin/activate
# Menginstal Flask dan Gunicorn
pip install Flask gunicorn
2. Kode Aplikasi Flask Sederhana (app.py) #
# /var/www/myapp/app.py
from flask import Flask, jsonify
app = Flask(__name__)
# Endpoint health check untuk verifikasi status server oleh Caddy
@app.get('/health')
def health():
return jsonify(status='UP', services='healthy')
@app.route('/')
def hello():
return "<h1>Hello from Flask behind Caddy!</h1>"
if __name__ == '__main__':
app.run()
3. Menjalankan Gunicorn via UNIX Socket #
Jalankan Gunicorn dengan mendefinisikan jumlah worker dan jalur UNIX socket yang aman:
cd /var/www/myapp
# Jalankan Gunicorn dengan 4 worker process terikat pada socket berkas
/var/www/myapp/venv/bin/gunicorn \
--workers 4 \
--bind unix:/run/myapp/gunicorn.sock \
--access-logfile /var/log/myapp/access.log \
--error-logfile /var/log/myapp/error.log \
--timeout 120 \
app:app
FastAPI + Uvicorn (ASGI) #
FastAPI adalah framework modern asinkronus (ASGI) yang dirancang untuk membangun API dengan performa tinggi. Karena FastAPI bersifat asinkronus, kita tidak bisa menggunakan Gunicorn biasa. Kita menggunakan Uvicorn sebagai worker di bawah manajemen Gunicorn.
1. Instalasi Dependensi ASGI #
pip install fastapi "uvicorn[standard]" gunicorn
2. Kode Aplikasi FastAPI (main.py) #
# /var/www/fastapi-app/main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserItem(BaseModel):
username: str
email: str
@app.get("/health")
async def health_check():
return {"status": "healthy"}
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "role": "member"}
@app.post("/api/users")
async def create_user(user: UserItem):
return {"message": "User sukses dibuat", "data": user}
3. Menjalankan ASGI dengan Uvicorn Workers di Gunicorn #
Kita menggabungkan keunggulan manajemen proses Gunicorn dengan performa event-loop asinkronus Uvicorn dengan mendefinisikan kelas worker kustom:
cd /var/www/fastapi-app
# Menggunakan UvicornWorker untuk penanganan asinkronus
/var/www/fastapi-app/venv/bin/gunicorn \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind unix:/run/fastapi-app/uvicorn.sock \
--access-logfile /var/log/fastapi/access.log \
--timeout 120 \
main:app
Django Production Setup #
Django adalah framework full-stack WSGI yang membutuhkan perhatian khusus saat masuk ke lingkungan produksi terkait penanganan berkas statis (static assets) dan pengaturan header keamanan proxy.
1. Penyesuaian settings.py Produksi #
Kita harus mematikan mode debug dan memberi tahu Django bahwa ia berjalan di belakang reverse proxy HTTPS Caddy:
# settings.py
DEBUG = False
# Batasi domain yang diizinkan mengakses Django
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# PENTING: Beritahu Django untuk mempercayai header secure proxy dari Caddy.
# Tanpa ini, Django akan menganggap koneksi tidak aman dan memicu loop redirect HTTPS.
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
# Pengamanan cookie session dan CSRF token
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Tentukan lokasi penyimpanan berkas statis saat dikompilasi
STATIC_ROOT = '/var/www/django-app/staticfiles'
STATIC_URL = '/static/'
2. Kompilasi Aset Statis Django #
Jalankan perintah pengumpulan aset statis agar seluruh file CSS/JS dari modul admin Django disalin ke direktori target:
cd /var/www/django-app
# Kompilasi aset statis ke STATIC_ROOT
/var/www/django-app/venv/bin/python manage.py collectstatic --noinput
# Jalankan migrasi basis data
/var/www/django-app/venv/bin/python manage.py migrate
Caddyfile untuk Python App #
Setelah server aplikasi Python (Gunicorn/Uvicorn) berjalan dan mendengarkan pada socket UNIX lokal, kita menyusun berkas Caddyfile produksi. Kita mengonfigurasi Caddy untuk menyajikan folder /static/ Django secara langsung dari disk, sedangkan seluruh request lainnya dialirkan ke socket aplikasi Python:
# Konfigurasi Caddyfile produksi untuk Python App
example.com {
log {
output file /var/log/caddy/python-access.log
format json
}
encode zstd gzip
# Keamanan HTTP headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
-Server
}
# 1. Sajikan berkas statis Django langsung oleh Caddy (Sangat Cepat)
@static path /static/* /media/*
handle @static {
root * /var/www/django-app
file_server
# Cache agresif 1 tahun untuk performa maksimal browser
header Cache-Control "public, max-age=31536000, immutable"
}
# 2. Teruskan request dinamis ke socket UNIX Gunicorn
handle {
reverse_proxy unix//run/django-app/gunicorn.sock {
# Tambahkan header proxy esensial untuk Django/FastAPI
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up Host {host}
# Konfigurasi Uji Kesehatan
health_uri /health
health_interval 15s
health_timeout 5s
}
}
}
Systemd Service untuk Gunicorn #
Agar server aplikasi Python otomatis berjalan kembali saat sistem operasi Linux dinyalakan (boot/reboot) atau setelah mengalami crash tidak terduga, kita harus membuat berkas definisi layanan systemd:
# Berkas Layanan Systemd: /etc/systemd/system/myapp.service
[Unit]
Description=Layanan Server Aplikasi Python (Gunicorn)
After=network.target
[Service]
# Jalankan proses di bawah user www-data demi keamanan
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
# Muat file konfigurasi rahasia (.env) di folder terisolasi
EnvironmentFile=/etc/myapp/env
Environment="PATH=/var/www/myapp/venv/bin"
# Siapkan direktori UNIX socket sementara otomatis di /run/
RuntimeDirectory=myapp
RuntimeDirectoryMode=0755
# Perintah eksekusi utama Gunicorn
ExecStart=/var/www/myapp/venv/bin/gunicorn \
--workers 5 \
--bind unix:/run/myapp/gunicorn.sock \
--pid /run/myapp/gunicorn.pid \
--access-logfile /var/log/myapp/gunicorn-access.log \
--error-logfile /var/log/myapp/gunicorn-error.log \
--timeout 120 \
app:app
# Gunakan sinyal HUP untuk pemicu reload konfigurasi aman tanpa interupsi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
# Kebijakan restart otomatis saat terjadi crash
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Jalankan perintah berikut di terminal untuk mendaftarkan dan menghidupkan layanan baru tersebut:
sudo systemctl daemon-reload
sudo systemctl enable myapp.service
sudo systemctl start myapp.service
sudo systemctl status myapp.service
Menentukan Jumlah Workers Gunicorn #
Menentukan jumlah worker processes Gunicorn yang tepat sangat penting untuk mengoptimalkan kinerja aplikasi Python kita. Karena Python secara bawaan dibatasi oleh Global Interpreter Lock (GIL) pada pemrograman sinkronus, kita harus menjalankan beberapa proses worker mandiri agar dapat memanfaatkan multi-core CPU server kita secara maksimal.
Rumus Umum Gunicorn Worker (WSGI/Sinkronus) #
Untuk aplikasi sinkronus seperti Flask atau Django standar, rumus empiris yang direkomendasikan adalah:
[\text{Jumlah Worker} = (2 \times \text{Jumlah Core CPU}) + 1]
Sebagai contoh:
- Jika VM server kita memiliki 2 vCPU cores: [(2 \times 2) + 1 = 5 \text{ workers}]
Rumus ASGI Worker (Asinkronus / FastAPI) #
Untuk framework asinkronus seperti FastAPI yang berbasis event loop non-blocking, satu worker process dapat menangani ribuan koneksi konkuren secara efisien. Oleh karena itu, kita tidak membutuhkan terlalu banyak worker proses. Jumlah optimalnya adalah:
[\text{Jumlah Worker (ASGI)} = \text{Jumlah Core CPU}]
Deployment Update Python (Graceful Reload) #
Setiap kali kita memperbarui kode aplikasi Python kita di server, menghentikan layanan secara kasar menggunakan systemctl restart akan memutus koneksi aktif pengguna dan memicu error di browser mereka.
Gunicorn mendukung fitur Graceful Reload menggunakan sinyal sistem Unix SIGHUP. Ketika Gunicorn menerima perintah reload, proses master akan menelusuri anak proses (worker) satu per satu, menghentikan worker lama setelah koneksi aktifnya selesai, dan membuat worker baru yang membawa kode aplikasi terupdate di latar belakang tanpa menutup socket koneksi utama.
Berikut adalah skrip bash otomatisasi deployment aman yang dapat kita pasang pada server produksi kita:
#!/bin/bash
# deploy-python.sh — Skrip deployment Python zero-downtime
APP_DIR="/var/www/myapp"
SERVICE_NAME="myapp"
echo "=== Memulai Proses Deployment Python ==="
# 1. Masuk ke direktori proyek dan tarik pembaruan Git terbaru
cd "$APP_DIR" || exit 1
git pull origin main
# 2. Perbarui dependensi di dalam virtual environment
echo "Memperbarui dependensi pustaka..."
./venv/bin/pip install -r requirements.txt --quiet
# 3. Jalankan migrasi skema basis data Django
echo "Menjalankan migrasi database..."
./venv/bin/python manage.py migrate --noinput
# 4. Kompilasi ulang aset statis Django
echo "Mengompilasi berkas statis..."
./venv/bin/python manage.py collectstatic --noinput --quiet
# 5. Picu systemd untuk melakukan reload graceful (SIGHUP) ke Gunicorn
echo "Memicu Gunicorn Graceful Reload..."
sudo systemctl reload "$SERVICE_NAME"
# 6. Berikan jeda waktu 3 detik agar proses inisialisasi selesai
sleep 3
# 7. Uji kesehatan API via Caddy
echo "Menguji status kesehatan API..."
HTTP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" https://example.com/health)
if [ "$HTTP_STATUS" != "200" ]; then
echo "✗ Uji kesehatan gagal dengan status: $HTTP_STATUS!"
echo "Deployment mendeteksi kesalahan runtime. Mohon periksa gunicorn-error.log."
exit 1
fi
echo "✓ Proses deployment Python berhasil diselesaikan tanpa downtime!"
Django REST Framework CORS Configuration di Caddy #
Saat kita membangun arsitektur API terpisah menggunakan Django REST Framework (DRF) sebagai backend dan React/Vue sebagai frontend di domain yang berbeda, kita harus menangani kebijakan CORS (Cross-Origin Resource Sharing).
Menangani CORS langsung di sisi Caddy edge server membebaskan kita dari keharusan menginstal pustaka Python tambahan seperti django-cors-headers di basis kode Python kita:
# Konfigurasi Caddy untuk Django REST Framework API
api.example.com {
log {
output file /var/log/caddy/django-api-access.log
format json
}
encode zstd gzip
# 1. Tangani request OPTIONS preflight dari browser frontend
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-CSRFToken"
header Access-Control-Allow-Credentials "true"
header Access-Control-Max-Age "86400"
respond "" 204
}
# 2. Sisipkan header CORS untuk transaksi API dinamis
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Credentials "true"
# 3. Salurkan request ke socket Django Gunicorn
reverse_proxy unix//run/django-api/gunicorn.sock {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
Diagram Alur Pemrosesan Request Python oleh Caddy #
Untuk memvisualisasikan bagaimana sebuah request dialirkan oleh Caddy melewati gerbang UNIX socket menuju ke aplikasi Python kita, perhatikan diagram flowchart berikut:
flowchart TD
A["Request Masuk Browser Klien\n(e.g., GET /api/users)"] --> B["1. Caddy mendekripsi TLS\ndan menerapkan header keamanan"]
B --> C{"2. Apakah rute ditujukan\nuntuk berkas statis?"}
C -- "Ya" --> D["3. Caddy langsung sajikan berkas dari folder\n/var/www/django-app/staticfiles"]
D --> E["Selesai"]
C -- "Tidak" --> F["4. Arahkan request ke reverse_proxy\ntarget unix//run/myapp/gunicorn.sock"]
F --> G["5. Master Process Gunicorn\n(Menerima request di socket buffer)"]
G --> H{"6. Apakah tipe framework\nWSGI atau ASGI?"}
H -- "WSGI (Django/Flask)" --> I["7. Salurkan request ke worker sinkronus\n(Proses eksekusi Python thread standar)"]
H -- "ASGI (FastAPI)" --> J["8. Salurkan request ke UvicornWorker\n(Proses eksekusi event-loop asinkronus)"]
I --> K["9. Aplikasi memproses request\n(Akses basis data PostgreSQL)"]
J --> K
K --> L["10. Kembalikan output biner respons ke Gunicorn"]
L --> M["11. Gunicorn mengalirkan data kembali ke Caddy"]
M --> N["12. Caddy mengompresi respons (Gzip/Zstd)\ndan mengirimkan ke browser Klien"]
N --> E
Ringkasan #
- Komunikasi UNIX Socket: Selalu gunakan koneksi UNIX socket (
unix//run/myapp/gunicorn.sock) untuk integrasi Gunicorn-Caddy demi performa memori lokal yang lebih cepat dan aman dibandingkan TCP socket.- ASGI Worker Class: Konfigurasikan Gunicorn dengan kelas worker Uvicorn (
--worker-class uvicorn.workers.UvicornWorker) untuk melayani aplikasi asinkronus FastAPI secara optimal.- Konfigurasi SSL Django: Jangan lupa menambahkan parameter
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')di filesettings.pyDjango agar tautan HTTPS terdeteksi secara tepat.- Isolasi Aset Statis: Konfigurasikan Caddy untuk membaca folder
/static/langsung dari media penyimpanan disk lokal guna menghindari pemborosan resource Gunicorn untuk file statis.- Tuning Kapasitas Worker: Sesuaikan jumlah worker WSGI menggunakan rumus
(2 × CPU Cores) + 1guna mengoptimalkan utilisasi CPU server tanpa membuang-buang memori RAM.- Graceful Reloading: Gunakan sinyal UNIX
HUP(systemctl reload myapp) pada deployment Gunicorn untuk menerapkan update kode aplikasi tanpa jeda mati (zero-downtime).- Sentralisasi CORS API: Manfaatkan Caddyfile untuk menyisipkan konfigurasi header CORS terpusat guna mengamankan backend API Django REST Framework kita.