Bài 5: Cloud Deployment (Compute Engine + Secret Manager)¶
Tổng quan¶
Bài này đưa container từ Bài 4 lên một máy chủ cloud có public URL + HTTPS:
- Cloud landscape: Compute Engine vs Cloud Run vs GKE vs Vertex AI - chọn theo trục nào.
- Deploy trên Compute Engine: startup script, chạy container auto-restart.
- Production patterns: nginx reverse proxy, HTTPS (Let's Encrypt), firewall, health check.
- Secrets: vì sao
.envtrên server là nợ, và cách dùng GCP Secret Manager.
1. Cloud Landscape¶
graph TD
Q{Đặc điểm workload?} --> GPU_Q[Cần GPU / chạy liên tục /<br/>kiểm soát OS]
Q --> STATELESS_Q[Stateless, traffic co giãn,<br/>chấp nhận cold start]
Q --> MULTI_Q[Nhiều service,<br/>cần orchestration]
Q --> MANAGED_Q[Muốn dùng model +<br/>endpoint có sẵn]
GPU_Q --> CE[Compute Engine]
STATELESS_Q --> CR[Cloud Run]
MULTI_Q --> GKE[GKE]
MANAGED_Q --> VX[Vertex AI]
| Dịch vụ | Mô hình | Ưu | Nhược | Hợp với LLM app khi |
|---|---|---|---|---|
| Compute Engine | VM, bạn quản OS | Toàn quyền, gắn GPU dễ, giá VM ổn định, chạy 24/7 rẻ | Tự lo OS patch, scaling, monitoring | Self-host vLLM (cần GPU), workload chạy liên tục, muốn 1 máy đơn giản |
| Cloud Run | Container serverless, scale-to-zero | Không quản máy, tự scale theo request, trả theo dùng | Cold start; giới hạn thời gian request; GPU hạn chế | API stateless gọi provider ngoài, traffic thất thường, muốn ít vận hành |
| GKE | Kubernetes managed | Orchestration mạnh, autoscale pod/node, GPU node pool | Phức tạp, cần biết K8s | Nhiều service (API + worker + vLLM), team có kinh nghiệm K8s |
| Vertex AI | Nền tảng ML/LLM managed | Model Garden, endpoint có sẵn, tích hợp GCP | Khoá vào hệ sinh thái, giá endpoint cao | Muốn gọi Gemini/model open qua endpoint managed, có pipeline ML sẵn |
Module này dùng Compute Engine + Secret Manager làm mẫu vì nó minh hoạ trọn vẹn các production pattern (systemd, nginx, HTTPS, firewall) mà bạn phải hiểu dù sau này chuyển sang Cloud Run/GKE.
2. Deploy trên Compute Engine¶
Tạo VM¶
gcloud compute instances create llm-app \
--zone=asia-southeast1-a \
--machine-type=e2-standard-2 \
--boot-disk-size=30GB \
--image-family=debian-12 --image-project=debian-cloud \
--tags=http-server,https-server \
--service-account=llm-app-sa@PROJECT.iam.gserviceaccount.com \
--scopes=cloud-platform
--tagsđể gắn firewall rule.--service-account+--scopes=cloud-platform: cấp danh tính để VM đọc Secret Manager (mục 4). Không nhét key vào VM.
Startup script - cài Docker & chạy container khi máy khởi động¶
#!/bin/bash
# startup.sh - gắn qua --metadata-from-file startup-script=startup.sh
set -e
apt-get update && apt-get install -y docker.io
systemctl enable --now docker
gcloud auth configure-docker asia-southeast1-docker.pkg.dev -q
docker pull asia-southeast1-docker.pkg.dev/PROJECT/repo/llm-app:latest
# Chạy như systemd unit (mục 3) thay vì docker run trực tiếp
cat >/etc/systemd/system/llm-app.service <<'UNIT'
[Unit]
Description=LLM App
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=5
ExecStartPre=-/usr/bin/docker rm -f llm-app
ExecStart=/usr/bin/docker run --rm --name llm-app \
-p 127.0.0.1:8000:8000 \
-v /data/index:/app/data/index:ro \
--env-file /dev/null \
asia-southeast1-docker.pkg.dev/PROJECT/repo/llm-app:latest
ExecStop=/usr/bin/docker stop llm-app
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now llm-app
Lưu ý: container bind vào 127.0.0.1:8000 - không expose ra ngoài trực tiếp; nginx (mục 3) mới là thứ nghe 443.
3. Production Patterns¶
graph LR
NET[Internet] -->|443| NGINX[nginx<br/>TLS termination + reverse proxy]
NGINX -->|127.0.0.1:8000| APP[Container llm-app<br/>systemd Restart=always]
FW[Firewall: chỉ mở 80/443] -.bảo vệ.- NGINX
CERT[certbot / Let's Encrypt<br/>auto-renew] -.cấp cert.-> NGINX
Auto-restart¶
| Cách | Ghi chú |
|---|---|
systemd unit với Restart=always (như mục 2) |
Khuyến nghị - quản lý log, phụ thuộc, restart policy tập trung |
docker run --restart=always |
Đơn giản hơn nhưng khó gắn health-based restart và log |
docker compose với restart: always + healthcheck |
Tốt khi có nhiều service (app + nginx + redis) |
nginx reverse proxy + HTTPS¶
# /etc/nginx/sites-available/llm-app
server {
listen 80;
server_name chat.example.com;
location / { return 301 https://$host$request_uri; }
location /.well-known/acme-challenge/ { root /var/www/certbot; } # cho certbot
}
server {
listen 443 ssl;
server_name chat.example.com;
ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# QUAN TRỌNG cho SSE streaming ([Bài 4])
proxy_buffering off;
proxy_read_timeout 300s;
proxy_set_header Connection "";
}
}
# Cấp cert lần đầu + bật auto-renew
apt-get install -y certbot python3-certbot-nginx
certbot --nginx -d chat.example.com --non-interactive --agree-tos -m ops@example.com
systemctl status certbot.timer # renew tự động 2 lần/ngày, chỉ gia hạn khi < 30 ngày
SSE + nginx: phải tắt buffering
Mặc định nginx buffer response → client không thấy token nào cho đến khi xong. Bắt buộc proxy_buffering off; và tăng proxy_read_timeout cho endpoint streaming.
Firewall¶
# Chỉ cho phép 80/443 từ Internet; SSH giới hạn IP văn phòng
gcloud compute firewall-rules create allow-web \
--allow=tcp:80,tcp:443 --target-tags=https-server --source-ranges=0.0.0.0/0
gcloud compute firewall-rules create allow-ssh-office \
--allow=tcp:22 --target-tags=https-server --source-ranges=<VP_IP>/32
Port 8000 của app không có rule → không thể truy cập từ ngoài, chỉ nginx (localhost) gọi được.
Health check¶
- App expose
/health(Bài 4). - nginx / uptime monitor gọi định kỳ.
- Smoke test sau deploy (Bài 6) gọi
/health+ 1 câu hỏi thật.
4. Secrets Management¶
Vì sao .env trên server là nợ kỹ thuật¶
Vấn đề của .env trên VM |
Hệ quả |
|---|---|
| Nằm trong snapshot/backup của disk | Key lộ theo mọi bản backup, khó thu hồi |
| Không có audit | Không biết ai đọc, khi nào |
| Xoay vòng key phải SSH sửa tay từng máy | Ngại xoay → key sống quá lâu |
Dễ lọt vào image nếu COPY . . |
Key nằm vĩnh viễn trong layer Docker, push lên registry |
| Chung một file cho mọi secret | Không phân quyền theo secret |
GCP Secret Manager¶
graph LR
DEV[Người có quyền] -->|tạo / cập nhật version| SM[(Secret Manager<br/>OPENAI_API_KEY, DB_URL, ...)]
VM[Compute Engine VM] -->|Service Account + IAM<br/>secretmanager.secretAccessor| SM
SM -->|đọc lúc runtime| APP[App: nạp vào env / config trong bộ nhớ]
# Tạo secret + version
echo -n "sk-..." | gcloud secrets create OPENAI_API_KEY --data-file=-
echo -n "sk-new" | gcloud secrets versions add OPENAI_API_KEY --data-file=-
# Cấp cho service account của VM quyền chỉ-đọc secret này
gcloud secrets add-iam-policy-binding OPENAI_API_KEY \
--member="serviceAccount:llm-app-sa@PROJECT.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
Đọc secret lúc runtime (không nướng vào image/build)¶
# src/config.py
from functools import lru_cache
from google.cloud import secretmanager
@lru_cache
def _client():
return secretmanager.SecretManagerServiceClient()
def get_secret(name: str, project: str = "PROJECT", version: str = "latest") -> str:
path = f"projects/{project}/secrets/{name}/versions/{version}"
return _client().access_secret_version(name=path).payload.data.decode()
# Nạp 1 lần lúc app khởi động
class Settings:
OPENAI_API_KEY = get_secret("OPENAI_API_KEY")
DATABASE_URL = get_secret("DATABASE_URL")
settings = Settings()
| Nguyên tắc | Vì sao |
|---|---|
Đọc lúc runtime (khởi động app), không phải lúc docker build |
Secret không nằm trong image layer |
| VM dùng Service Account + IAM, không có key file trên máy | Không có gì để lộ qua backup |
| Một secret = một resource, cấp quyền riêng | Phân quyền tối thiểu; audit theo secret |
Xoay vòng = versions add + app đọc latest khi restart |
Không cần SSH sửa file |
Local dev: dùng .env không commit + .env.example |
Chỉ production mới bắt buộc Secret Manager |
Cloud Run làm việc này gọn hơn
Trên Cloud Run bạn map secret vào biến môi trường hoặc file bằng cấu hình (--set-secrets), không cần code gọi API. Nguyên tắc không đổi: danh tính (service account) + IAM, đọc lúc chạy, không vào image.
5. Vận hành cơ bản¶
| Việc | Cách |
|---|---|
| Xem log | journalctl -u llm-app -f (systemd) hoặc docker logs -f llm-app; đẩy lên Cloud Logging bằng logging agent |
| Log rotation | Docker --log-opt max-size=10m --log-opt max-file=3; hoặc logrotate cho nginx |
| Restart an toàn | systemctl restart llm-app; với nhiều instance sau LB: rolling (drain 1 máy → restart → chờ health → máy kế) |
| Backup vector index | gsutil rsync /data/index gs://backup/index/$(date +%F) theo cron; hoặc snapshot disk |
| Cập nhật app | Đẩy image tag mới → docker pull + systemctl restart (tự động hoá ở Bài 6) |
6. Hands-on: Deploy Vietnamese RAG chatbot có public URL¶
Dùng image từ Bài 4 (RAG chatbot Capstone Module I).
Bước 1 - Chuẩn bị secret¶
gcloud secrets create OPENAI_API_KEY(vàEMBEDDING_API_KEYnếu tách).- Tạo service account
llm-app-sa, cấproles/secretmanager.secretAccessorcho từng secret. - Sửa
src/config.pyđọc secret qua Secret Manager; local vẫn fallback.env.
Bước 2 - Tạo VM + startup script¶
gcloud compute instances createvới--tags=https-server, gắn service account.- Startup script: cài Docker,
docker pull, tạollm-app.service(Restart=always, bind127.0.0.1:8000). - Copy/tải vector index vào
/data/indextrên VM (scp hoặcgsutil cp).
Bước 3 - nginx + HTTPS¶
- Trỏ 1 subdomain (hoặc dùng DNS tạm /
nip.io) về IP tĩnh của VM. - Cài nginx, cấu hình reverse proxy
127.0.0.1:8000,proxy_buffering offcho/chat. certbot --nginx -d <domain>cấp cert; xác nhậncertbot.timerchạy.
Bước 4 - Firewall + verify¶
- Firewall: mở 80/443 cho
0.0.0.0/0, SSH giới hạn IP của bạn. - Xác nhận port 8000 không truy cập được từ ngoài (
curl http://<IP>:8000timeout). curl https://<domain>/health→ ok; hỏi 1 câu qua UI → thấy token stream dần.
Bước 5 - Vận hành thử¶
journalctl -u llm-app -f, thửkillcontainer → systemd tự dựng lại trong ~5s.- Xoay
OPENAI_API_KEY(versions add) →systemctl restart llm-app→ app dùng key mới. - Script backup
/data/indexlên GCS.
Tiêu chí hoàn thành¶
- Public URL
https://...trả lời được, cert hợp lệ, tự renew - Không có API key nào nằm trên VM dưới dạng file / trong image
- Port app (8000) không truy cập được từ Internet
-
/chatstream token qua nginx (buffering đã tắt) - Kill container → tự restart; xoay secret → chỉ cần restart, không sửa file
Tóm tắt¶
graph LR
IMG[(Image trong Artifact Registry)] --> VM[Compute Engine VM<br/>Service Account, tags firewall]
VM --> SYSD[systemd: container Restart=always, bind 127.0.0.1:8000]
NGINX[nginx :443<br/>TLS + proxy, buffering off] --> SYSD
LE[Let's Encrypt auto-renew] --> NGINX
FW[Firewall: chỉ 80/443 + SSH hạn chế] --> NGINX
SM[(Secret Manager)] -->|IAM secretAccessor| SYSD
| Chủ đề | Cốt lõi |
|---|---|
| Cloud landscape | CE (GPU, chạy liên tục) · Cloud Run (stateless, scale-to-zero) · GKE (nhiều service) · Vertex (managed model) |
| Compute Engine deploy | Startup script + systemd Restart=always; container bind localhost, không expose |
| nginx + HTTPS | TLS termination, reverse proxy; proxy_buffering off cho SSE; certbot auto-renew |
| Firewall | Chỉ mở 80/443; port app không có rule → private |
| Secrets | .env trên server = nợ; Secret Manager + Service Account + IAM; đọc lúc runtime, không vào image |
| Vận hành | journalctl/docker logs, log rotation, rolling restart, backup index |