Bỏ qua

Infrastructure Reading Guide

Đọc file này trước khi đọc bất cứ thứ gì khác.
Mục đích: chỉ bạn đọc theo thứ tự đúng để xây dựng mental model, và biết tìm ở đâu khi debug.


Phần 1 — Đọc theo thứ tự để hiểu kiến trúc

Đọc tuần tự từ trên xuống. Mỗi bước xây trên nền bước trước.


Bước 1 — Hiểu bức tranh toàn cảnh (5 phút)

Đọc: ARCHITECTURE.mdSection 1 (Tổng quan) và Section 15 (Port reference)

Câu hỏi cần trả lời sau bước này: - Có mấy node? Node nào chạy gì? - Stateful services (DB, cache) chạy bằng công nghệ gì, ở đâu? - Stateless apps (NestJS, FastAPI) chạy bằng công nghệ gì, ở đâu? - Traffic từ browser đi qua những điểm nào trước khi tới code?

Mental model sau bước này:

Browser → nginx-ingress (K3s) → api-gateway pod → services pods
                                                         │
                                               connects to MASTER_IP:PORT
                                                         │
                                              Docker Compose (postgres, redis, kafka...)

Bước 2 — Hiểu môi trường (3 phút)

Đọc: inventory/utm/hosts.iniinventory/utm/group_vars/all.yml

Câu hỏi cần trả lời sau bước này: - Ba node có IP gì? - k3s_master_ip được dùng ở đâu? - env_name: "utm-local" ảnh hưởng gì đến deploy?

Điều quan trọng nhất: Biến k3s_master_ip là trung tâm của toàn bộ hệ thống. Nó được inject vào .env (cho Docker Compose) và vào K8s Secrets (cho pods). Bất cứ service nào cần kết nối ra ngoài K3s cluster đều dùng IP này.


Bước 3 — Hiểu flow deploy toàn bộ (10 phút)

Đọc: playbooks/site.yml — đọc hết, chỉ 30 dòng

Câu hỏi cần trả lời sau bước này: - Có mấy bước? Bước nào chạy trên node nào? - Tại sao Step 6 (stateful stack) phải chạy trước Step 7 (helm apps)? - Tại sao Step 5 (kubeconfig) phải trước Step 7?

Mental model sau bước này — 8 bước theo thứ tự bắt buộc:

[1] common      → mọi node  → OS chuẩn bị
[2] docker      → master    → cần Docker để chạy Compose
[3] k3s_master  → master    → tạo cluster, sinh node-token
[4] k3s_worker  → workers   → join cluster bằng token
[5] kubeconfig  → local Mac → lấy kubeconfig để chạy kubectl
[6] stateful    → master    → khởi động DB/cache/kafka TRƯỚC khi apps lên
[7] helm_apps   → master    → tạo K8s Secrets, deploy pods
[8] ingress     → master    → mở cổng ra ngoài

Bước 4 — Hiểu hạ tầng stateful (15 phút)

Đọc theo thứ tự:

  1. roles/stateful_stack/templates/env.production.j2
    → Đây là danh sách tất cả secrets server cần. Đọc để biết có những biến gì.

  2. secrets/vault.yml
    → Source of truth cho mọi secret. Mỗi vault_xxx ở đây tương ứng với {{ vault_xxx }} trong template trên.

  3. docker-compose/stateful-stack.yml
    → Đọc phần environment: của từng service để hiểu chúng nhận config nào.
    → Chú ý comment tại sao Kafka cần 2 listeners (PLAINTEXT vs EXTERNAL).

  4. docker-compose/init-scripts/create-multiple-dbs.sh
    → Script này chạy 1 lần duy nhất khi PostgreSQL khởi tạo. Hiểu nó để biết 7 databases được tạo thế nào.

Câu hỏi cần trả lời sau bước này: - Secret đi từ đâu đến đâu? (vault.yml → j2 template → .env file → Docker Compose → container) - Tại sao Kafka cần port 9093 thay vì 9092? - MASTER_IP trong KAFKA_ADVERTISED_LISTENERS đến từ đâu?


Bước 5 — Hiểu hạ tầng observability (10 phút)

Đọc: docker-compose/observability-stack.yml

Tập trung vào: - clickhouse-init-databases — tại sao cần init container riêng? - Tại sao có 2 Redis (chatbot-redis port 6380 và langfuse-redis internal)? - minio — LangFuse v3 cần S3 để lưu gì? - langfuse-web depends on gì? Order khởi động thế nào?

Câu hỏi cần trả lời sau bước này: - Trace từ NestJS pod → OTel collector → ClickHouse đi qua ports nào? - Tại sao langfuse-redis phải dùng noeviction còn chatbot-redis dùng allkeys-lru?


Bước 6 — Hiểu K3s workloads (10 phút)

Đọc theo thứ tự:

  1. roles/helm_apps/tasks/main.yml
    → Đọc phần tạo backend-secretschatbot-secrets. Đây là danh sách đầy đủ env vars mà pods nhận được.

  2. helm/backend/values.yaml
    → 20 dòng, hiểu defaults: replicaCount, image, resources, envFrom.

  3. helm/backend/templates/deployment.yaml
    → Thấy envFrom: secretRef: backend-secrets → pods lấy config từ K8s Secret.

  4. helm/chatbot/values.yaml
    → So sánh với backend: HPA enabled, port khác (3007/3006).

  5. helm/ingress/templates/ingress.yaml
    → Thấy ingressClassName: nginx, route /api-gateway:3000.

Câu hỏi cần trả lời sau bước này: - 6 NestJS services dùng chung 1 Helm chart — làm thế nào để phân biệt? - Tại sao service discovery giữa các pods dùng http://user-service:3001 (không phải IP)? - Chatbot HPA scale dựa trên metric gì, min/max bao nhiêu?


Bước 7 — Hiểu CI/CD flow (5 phút)

Đọc: 1. .github/workflows/README.md — tổng quan setup GitHub Actions 2. .github/workflows/cd-backend.yml — reusable workflow thực sự chạy helm 3. .github/workflows/_example-caller-backend.yml — app repo gọi workflow này thế nào 4. playbooks/deploy_service.yml — fallback deploy thủ công từ local

Mental model CI/CD:

git push → GitHub (app repo)
  → GitHub Actions CI: build image → push Docker Hub
  → GitHub Actions CD: gọi reusable workflow từ infra-k3s repo
    → SSH vào master → helm upgrade --atomic
    → K3s rolling update (tự rollback nếu fail)

Điểm khác với Woodpecker cũ: - Không cần server CI chạy riêng trên worker node - Deploy logic tập trung trong repo này (cd-backend.yml, cd-chatbot.yml) - App repos chỉ gọi (uses: infra-k3s/.github/workflows/cd-backend.yml@main) - --atomic đảm bảo tự rollback nếu health check fail


Bước 8 — Đọc sâu ARCHITECTURE.md (30 phút)

Sau khi đã có mental model từ bước 1-7, đọc ARCHITECTURE.md từ đầu đến cuối.

Lúc này bạn sẽ hiểu tất cả các section thay vì chỉ đọc qua.


Phần 2 — Debug Map: bị stuck ở đâu, tìm ở đâu

Pod không start được

kubectl get pods -n football-booking
kubectl describe pod <pod-name> -n football-booking   # xem Events
kubectl logs <pod-name> -n football-booking           # xem app logs

Nguyên nhân phổ biến → file liên quan:

Triệu chứng Tìm ở đâu
ImagePullBackOff Kiểm tra image.repository + image.tag trong helm/backend/values.yaml hoặc values.utm.yaml
CrashLoopBackOff Xem logs pod. Thường do sai env var → kiểm tra roles/helm_apps/tasks/main.yml phần backend-secrets
Pending (không schedule) kubectl describe pod → xem node có đủ resources không → helm/backend/values.yaml phần resources.requests
Secret không tồn tại kubectl get secret -n football-booking → nếu thiếu thì role helm_apps chưa chạy

Service không kết nối được tới database

Kiểm tra theo thứ tự:

# 1. DB container đang chạy không?
ssh ubuntu@192.168.64.10 "docker ps | grep postgres"

# 2. Port có mở không?
ssh ubuntu@192.168.64.10 "ss -tlnp | grep 5432"

# 3. Test kết nối từ trong pod
kubectl exec -it deployment/user-service -n football-booking -- \
  nc -zv 192.168.64.10 5432

# 4. Xem connection string trong Secret
kubectl get secret backend-secrets -n football-booking -o jsonpath='{.data.DATABASE_URL_USER}' | base64 -d

File cần xem: - docker-compose/stateful-stack.yml — port binding, healthcheck - roles/helm_apps/tasks/main.yml — connection string được build thế nào - inventory/utm/group_vars/all.ymlk3s_master_ip đúng chưa


Kafka không nhận/gửi được message

Kafka dual-listener là nguyên nhân phổ biến nhất.

# Test từ pod
kubectl exec -it deployment/booking-service -n football-booking -- \
  nc -zv 192.168.64.10 9093

# Xem Kafka logs
ssh ubuntu@192.168.64.10 \
  "docker logs kafka 2>&1 | grep -i 'listener\|advertised\|error' | tail -30"

# Xem MASTER_IP đã được inject đúng chưa
ssh ubuntu@192.168.64.10 "grep MASTER_IP /home/ubuntu/stacks/football-booking/.env"

File cần xem: - docker-compose/stateful-stack.ymlKAFKA_ADVERTISED_LISTENERS phải chứa EXTERNAL://${MASTER_IP}:9093 - roles/stateful_stack/templates/env.production.j2MASTER_IP={{ k3s_master_ip }} - inventory/utm/group_vars/all.ymlk3s_master_ip đúng không - roles/helm_apps/tasks/main.ymlKAFKA_BROKERS="{{ k3s_master_ip }}:9093"


Ingress không route được traffic

# 1. nginx-ingress controller có chạy không?
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx   # LoadBalancer đã có EXTERNAL-IP chưa?

# 2. Ingress resource đã tạo chưa?
kubectl get ingress -n football-booking
kubectl describe ingress football-booking-ingress -n football-booking

# 3. api-gateway service có healthy không?
kubectl get svc api-gateway -n football-booking
kubectl get endpoints api-gateway -n football-booking

File cần xem: - roles/ingress/tasks/main.yml — cài nginx-ingress và deploy Ingress - helm/ingress/templates/ingress.yaml — routing rules - helm/ingress/values.utm.yaml — host config


LangFuse không hoạt động

Dependency chain của LangFuse:

postgres (langfuse_db) → langfuse-redis → minio → langfuse-web → langfuse-worker
# Check thứ tự start
ssh ubuntu@192.168.64.10 \
  "docker compose -f /home/ubuntu/stacks/football-booking/observability-stack.yml ps"

# Xem logs langfuse-web
ssh ubuntu@192.168.64.10 "docker logs langfuse-web 2>&1 | tail -50"

# Test MinIO
curl http://192.168.64.10:9090/minio/health/live

File cần xem: - docker-compose/observability-stack.yml — env vars LANGFUSE_S3_EVENT_UPLOAD_*, DATABASE_URL, CLICKHOUSE_URL - roles/stateful_stack/templates/env.production.j2MINIO_ROOT_USER, LANGFUSE_INIT_* - secrets/vault.yml — các biến vault_langfuse_*, vault_minio_*

Lỗi phổ biến: - LANGFUSE_ENCRYPTION_KEY phải đúng 64 ký tự hex (openssl rand -hex 32) - LangFuse auto-init chỉ chạy 1 lần. Nếu public/secret key bị thay đổi sau khi đã init, phải xóa data volume và restart.


Chatbot không gọi LLM được

Flow: chatbot pod → OmniRoute (port 20128) → Groq/Gemini

# 1. OmniRoute có chạy không?
curl http://192.168.64.10:20128/

# 2. Test từ pod
kubectl exec -it deployment/chatbot-service -n football-booking -- \
  curl -s http://192.168.64.10:20128/

# 3. Xem chatbot env
kubectl get secret chatbot-secrets -n football-booking \
  -o jsonpath='{.data.OMNIROUTE_BASE_URL}' | base64 -d

File cần xem: - roles/helm_apps/tasks/main.ymlLLM_PROVIDERS, OMNIROUTE_BASE_URL - docker-compose/stateful-stack.yml — service omni-route

OmniRoute API keys không ở trong file — được thêm qua dashboard http://MASTER_IP:20128.
Volume omniroute_data phải còn tồn tại. Nếu mất volume → phải thêm keys lại.


K3s cluster unhealthy

# Xem trạng thái nodes
kubectl get nodes -o wide

# Node NotReady → xem kubelet
ssh ubuntu@192.168.64.11 "sudo systemctl status k3s-agent"
ssh ubuntu@192.168.64.11 "sudo journalctl -u k3s-agent -n 50"

# Master API không respond
ssh ubuntu@192.168.64.10 "sudo systemctl status k3s"
ssh ubuntu@192.168.64.10 "sudo journalctl -u k3s -n 50"

File cần xem: - roles/common/tasks/main.yml — firewall ports, sysctl settings - inventory/utm/group_vars/all.ymlk3s_required_ports - roles/k3s_master/tasks/main.yml — install flags, --tls-san


Secret sai hoặc thiếu

# Xem tất cả secrets
kubectl get secrets -n football-booking

# Decode một key cụ thể
kubectl get secret backend-secrets -n football-booking \
  -o jsonpath='{.data.REDIS_URL}' | base64 -d

# Re-tạo secrets (idempotent)
ansible-playbook -i inventory/utm playbooks/deploy.yml \
  --vault-password-file ~/.vault_pass

File cần xem: - secrets/vault.yml — giá trị thật của secrets - roles/helm_apps/tasks/main.yml — xem key nào map vào literal nào


Docker Compose service không start

# Xem trạng thái
ssh ubuntu@192.168.64.10 \
  "cd /home/ubuntu/stacks/football-booking && \
   docker compose -f stateful-stack.yml ps && \
   docker compose -f observability-stack.yml ps"

# Xem logs service cụ thể
ssh ubuntu@192.168.64.10 \
  "docker logs <container-name> 2>&1 | tail -100"

# Xem .env đã render đúng chưa (chú ý: file này chứa secrets)
ssh ubuntu@192.168.64.10 \
  "grep -v PASSWORD /home/ubuntu/stacks/football-booking/.env"

# Re-render .env và restart
ansible-playbook -i inventory/utm playbooks/site.yml \
  --tags stateful_stack --vault-password-file ~/.vault_pass

File cần xem: - roles/stateful_stack/templates/env.production.j2 — template .env - secrets/vault.yml — source of truth secrets - docker-compose/stateful-stack.yml hoặc observability-stack.yml — config service


Phần 3 — Mental Model Tổng hợp

Đọc xong hết rồi? Đây là cách nhớ toàn bộ hệ thống trong 1 sơ đồ:

SOURCE OF TRUTH
───────────────
secrets/vault.yml                     ← tất cả passwords & API keys ở đây
inventory/utm/group_vars/all.yml      ← tất cả IPs & versions ở đây

        │
        │ Ansible render + copy
        ▼

MASTER NODE (/home/ubuntu/stacks/football-booking/)
────────────────────────────────────────────────────
.env                                  ← vault.yml → j2 template → đây
stateful-stack.yml  ──docker compose──► postgres, mongodb, redis,
observability-stack.yml ──────────────► kafka, keycloak, omni-route,
                                        clickhouse, otel-collector,
                                        langfuse, signoz, minio,
                                        chatbot-redis, langfuse-redis

        │ K3s connects via MASTER_IP:PORT
        │
        ▼

K3s CLUSTER (namespace: football-booking)
─────────────────────────────────────────
backend-secrets  ──envFrom──► api-gateway pod      :3000
chatbot-secrets  ──envFrom──► user-service pod     :3001
                              field-service pod     :3002
                              booking-service pod   :3003
                              payment-service pod   :3004
                              notification-service  :3005
                              chatbot-service pod   :3007

        │ ingressClassName: nginx
        │
        ▼

nginx-ingress-controller (namespace: ingress-nginx)
────────────────────────────────────────────────────
/ → api-gateway:3000     ← tất cả traffic vào đây đầu tiên

Phần 4 — Checklist khi bắt đầu session debug

Copy-paste và tick từng dòng:

[ ] Xác định: vấn đề ở tầng nào? (ingress / K3s pod / Docker Compose / network / secret)
[ ] kubectl get pods -n football-booking          → pods có Running không?
[ ] kubectl get nodes                             → nodes có Ready không?
[ ] ssh master: docker ps | grep -c "Up"          → Compose services có Up không?
[ ] ssh master: grep MASTER_IP stacks/.../env     → IP đúng chưa?
[ ] kubectl get secret backend-secrets -n football-booking → secret tồn tại không?
[ ] curl http://MASTER_IP/health                  → API gateway reach được không?