Bỏ qua

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 .env trê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_KEY nếu tách).
  • Tạo service account llm-app-sa, cấp roles/secretmanager.secretAccessor cho 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 create với --tags=https-server, gắn service account.
  • Startup script: cài Docker, docker pull, tạo llm-app.service (Restart=always, bind 127.0.0.1:8000).
  • Copy/tải vector index vào /data/index trên VM (scp hoặc gsutil 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 off cho /chat.
  • certbot --nginx -d <domain> cấp cert; xác nhận certbot.timer chạ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>:8000 timeout).
  • 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ử kill container → 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/index lê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
  • /chat stream 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