Football Booking — Infrastructure Architecture
Mục đích: Document này mô tả toàn bộ kiến trúc hạ tầng K3s của hệ thống Football Booking, bao gồm vai trò từng file, flow triển khai, và cách các thành phần kết nối với nhau. Được thiết kế để deploy lên 3 UTM VMs local (development) hoặc 3 Oracle Cloud VMs (production-equivalent).
1. Tổng quan kiến trúc
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET / Local Browser │
└─────────────────────────────┬───────────────────────────────────────────┘
│ HTTP :80 / :443
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ MASTER NODE (192.168.64.10 / Oracle VM) │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ nginx-ingress-controller (K3s, namespace: ingress-nginx) │ │
│ │ ServiceLB klipper-lb → gán node IP tự động │ │
│ └───────────────────────┬──────────────────────────────────────────┘ │
│ │ routes / → api-gateway:3000 │
│ ┌───────────────────────▼──────────────────────────────────────────┐ │
│ │ K3s Pods (namespace: football-booking) │ │
│ │ │ │
│ │ api-gateway:3000 ──► user-service:3001 │ │
│ │ ──► field-service:3002 │ │
│ │ ──► booking-service:3003 │ │
│ │ ──► payment-service:3004 │ │
│ │ ──► notification-service:3005 │ │
│ │ ──► chatbot-service:3007 │ │
│ └───────────────────────┬──────────────────────────────────────────┘ │
│ │ MASTER_IP:PORT │
│ ┌───────────────────────▼──────────────────────────────────────────┐ │
│ │ Docker Compose — Stateful Stack │ │
│ │ │ │
│ │ postgres:5432 mongodb:27017 redis:6379 kafka:9093 │ │
│ │ keycloak:8080 omni-route:20128 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Docker Compose — Observability Stack │ │
│ │ │ │
│ │ clickhouse:8123/9000 otel-collector:4317/4318 │ │
│ │ signoz-frontend:3301 langfuse-web:3100 │ │
│ │ chatbot-redis:6380 langfuse-minio:9090 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Docker Compose — Woodpecker CI (worker node) │ │
│ │ woodpecker-server:8000 woodpecker-agent │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
WORKER NODE 1 (192.168.64.11) ──► chạy K3s workloads (pods)
WORKER NODE 2 (192.168.64.12) ──► chạy K3s workloads + Woodpecker CI
Nguyên tắc phân tách
| Layer | Công nghệ | Node | Lý do |
|---|---|---|---|
| Stateful services (DB, cache, queue, auth) | Docker Compose | Master | Cần persist data, không scale ngang |
| Stateless apps (NestJS, FastAPI) | K3s + Helm | Workers | Scale ngang, rolling deploy, health-check tự động |
| Observability (SigNoz, LangFuse) | Docker Compose | Master | Tách stack, start/stop độc lập |
| CI/CD (Woodpecker) | Docker Compose | Worker[0] | Cần Docker socket, tách khỏi app workloads |
2. Cấu trúc thư mục
football-booking-infra-k3s/
│
├── ansible.cfg # Ansible global config
├── ARCHITECTURE.md # File này
│
├── inventory/ # Định nghĩa môi trường
│ ├── utm/ # Local VMs tạo bằng UTM (development)
│ │ ├── hosts.ini # IP 192.168.64.10/11/12
│ │ └── group_vars/
│ │ ├── all.yml # Biến chung: k3s_master_ip, versions, env_name
│ │ ├── k3s_master.yml # Biến riêng master node
│ │ └── k3s_workers.yml # Biến riêng worker nodes
│ └── oracle/ # Oracle Cloud VMs (production-equivalent)
│ ├── hosts.ini # Public IP Oracle instances
│ └── group_vars/
│ └── all.yml # Giống utm nhưng env_name: "oracle"
│
├── playbooks/ # Entry points — chạy từ đây
│ ├── site.yml # Full cluster setup (lần đầu hoặc rebuild)
│ ├── deploy.yml # Re-deploy tất cả app services (CI/CD trigger)
│ ├── deploy_service.yml # Re-deploy một service cụ thể (CI/CD trigger)
│ └── woodpecker.yml # Cài Woodpecker CI riêng trên worker[0]
│
├── roles/ # Ansible roles — từng bước setup
│ ├── common/ # OS baseline cho tất cả nodes
│ ├── docker/ # Cài Docker CE trên master
│ ├── k3s_master/ # Cài K3s server trên master
│ ├── k3s_worker/ # Join K3s cluster từ workers
│ ├── kubeconfig/ # Fetch kubeconfig về Mac local
│ ├── stateful_stack/ # Deploy Docker Compose stateful stack
│ ├── helm_apps/ # Tạo K8s Secrets + Deploy Helm charts
│ ├── ingress/ # Cài nginx-ingress + deploy Ingress resource
│ └── woodpecker/ # Cài Woodpecker CI
│
├── helm/ # Helm charts cho K3s workloads
│ ├── backend/ # Chart dùng chung cho 6 NestJS microservices
│ ├── chatbot/ # Chart riêng cho FastAPI chatbot service
│ └── ingress/ # Chart định nghĩa Ingress routing rules
│
├── docker-compose/ # Docker Compose cho stateful services trên master
│ ├── stateful-stack.yml # Core infra: PG, Mongo, Redis, Kafka, Keycloak, OmniRoute
│ ├── observability-stack.yml # Monitoring: ClickHouse, SigNoz, LangFuse, MinIO
│ ├── config/
│ │ └── otel-collector-config.yaml # OpenTelemetry Collector pipeline config
│ └── init-scripts/
│ ├── create-multiple-dbs.sh # Tạo 7 PG databases + chatbot user khi init
│ └── mongo-init.js # Tạo notification_db collections khi init
│
├── secrets/
│ ├── vault.yml # Tất cả secrets (PHẢI encrypt trước khi commit)
│ └── .gitignore # Chặn commit vault.yml plaintext
│
└── scripts/
├── setup-utm-network.sh # Cấu hình mạng UTM bridged/shared
└── verify-cluster.sh # Kiểm tra health cluster sau deploy
3. Inventory — Quản lý môi trường
inventory/utm/hosts.ini
Định nghĩa 3 UTM VMs với IP tĩnh:
[k3s_master] 192.168.64.10
[k3s_workers] 192.168.64.11, 192.168.64.12
SSH key: ~/.ssh/utm_k3s
inventory/oracle/hosts.ini
Cùng cấu trúc nhưng dùng public IP Oracle Cloud và key ~/.ssh/oracle_football_booking.
inventory/utm/group_vars/all.yml — Biến quan trọng
| Biến | Giá trị | Dùng ở đâu |
|---|---|---|
k3s_master_ip |
192.168.64.10 |
Inject vào .env → Kafka EXTERNAL, K8s Secrets |
k3s_version |
v1.29.4+k3s1 |
Cài K3s đúng version |
env_name |
utm-local |
Quyết định values file: utm hay oracle |
backend_image_prefix |
sangtrandev00 |
Docker Hub registry prefix |
Cơ chế env_short
Roles đọc env_name để chọn Helm values file phù hợp:
env_short: "{{ 'utm' if 'utm' in env_name else 'oracle' }}"
# → "utm" khi dùng inventory/utm
# → "oracle" khi dùng inventory/oracle
Sau đó Helm deploy dùng: -f values.yaml -f values.{{ env_short }}.yaml
4. Playbooks — Entry Points
playbooks/site.yml — Full cluster setup
Chạy lần đầu khi tạo VMs mới hoặc rebuild từ đầu. Gồm 8 bước tuần tự:
Step 1: common → tất cả nodes → OS baseline, swap, firewall, sysctl
Step 2: docker → master only → Cài Docker CE + Compose plugin
Step 3: k3s_master → master only → Cài K3s server, disable Traefik
Step 4: k3s_worker → workers → Join K3s cluster bằng node-token
Step 5: kubeconfig → master → local → Copy ~/.kube/football-k3s.yaml về Mac
Step 6: stateful_stack → master only → Render .env, copy files, docker compose up
Step 7: helm_apps → master only → Tạo K8s Secrets, deploy Helm charts
Step 8: ingress → master only → Cài nginx-ingress, deploy Ingress rules
# Chạy full setup
ansible-playbook -i inventory/utm playbooks/site.yml --vault-password-file ~/.vault_pass
playbooks/deploy.yml — Re-deploy tất cả apps
Dùng khi cần roll out image mới cho toàn bộ services:
ansible-playbook -i inventory/utm playbooks/deploy.yml \
-e "backend_image_tag=abc1234" -e "chatbot_image_tag=abc1234" \
--vault-password-file ~/.vault_pass
Chỉ chạy role helm_apps → update Helm releases với tag mới.
playbooks/deploy_service.yml — Re-deploy 1 service
Được Woodpecker CI gọi sau mỗi build thành công:
ansible-playbook -i inventory/utm playbooks/deploy_service.yml \
-e "service=user-service" -e "image_tag=abc1234"
Chạy helm upgrade --install cho đúng 1 service rồi verify rollout.
playbooks/woodpecker.yml — Cài CI/CD server
Chạy riêng trên k3s_workers[0]:
ansible-playbook -i inventory/utm playbooks/woodpecker.yml --vault-password-file ~/.vault_pass
5. Roles — Từng bước chi tiết
roles/common/ — OS baseline
Chạy trên: tất cả nodes
Làm gì:
- Update apt, cài packages cơ bản (curl, wget, ufw, htop)
- Cấu hình sysctl cho K3s: net.ipv4.ip_forward=1, bridge-nf-call-iptables=1
- Tạo swap file 2GB (K3s cần RAM)
- Mở firewall ports: 22 (SSH), 6443 (K3s API), 8472 (Flannel VXLAN), 10250 (Kubelet)
File quan trọng:
- templates/sysctl-k3s.conf.j2 → /etc/sysctl.d/99-k3s.conf
roles/docker/ — Docker trên master
Chạy trên: master only
Làm gì: Cài Docker CE + Compose plugin, thêm user ubuntu vào group docker
roles/k3s_master/ — K3s control plane
Chạy trên: master only
Làm gì:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.29.4+k3s1 sh -s - server \
--tls-san 192.168.64.10 \
--disable traefik # ← Tắt Traefik, dùng nginx-ingress thay thế
Sau đó đọc /var/lib/rancher/k3s/server/node-token và set fact k3s_token để workers dùng.
Tại sao disable Traefik? K3s mặc định bundle Traefik, nhưng project này dùng nginx-ingress để có config linh hoạt hơn (proxy-body-size, timeout, path rewrite).
roles/k3s_worker/ — Join cluster
Chạy trên: workers
Làm gì:
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.64.10:6443 \
K3S_TOKEN={{ k3s_token }} sh -
roles/kubeconfig/ — Fetch kubeconfig
Chạy trên: master → copy về local
Làm gì: Copy /etc/rancher/k3s/k3s.yaml về ~/.kube/football-k3s.yaml, thay 127.0.0.1 bằng k3s_master_ip để dùng từ Mac.
roles/stateful_stack/ — Docker Compose infra
Chạy trên: master only
Làm gì (theo thứ tự trong tasks/main.yml):
1. Tạo thư mục /home/ubuntu/stacks/football-booking/
2. Render .env từ Ansible Vault → /home/ubuntu/stacks/football-booking/.env
3. Copy stateful-stack.yml, observability-stack.yml
4. Copy init-scripts/ (PostgreSQL + MongoDB init)
5. Copy config/otel-collector-config.yaml
6. docker compose up -d cho stateful-stack
7. docker compose up -d cho observability-stack
File quan trọng:
- templates/env.production.j2 → Jinja2 template render từ Vault secrets thành .env file trên server
- vars/vault.yml → Local copy vault vars (dùng khi chỉ chạy riêng role này)
roles/helm_apps/ — Deploy K8s workloads
Chạy trên: master only
Làm gì (theo thứ tự trong tasks/main.yml):
1. Cài Helm nếu chưa có
2. Tạo namespace football-booking
3. Copy toàn bộ helm/ lên /tmp/helm/
4. Tạo K8s Secret backend-secrets (30+ env vars cho NestJS services)
5. Tạo K8s Secret chatbot-secrets (LLM keys, Redis, DB cho chatbot)
6. Deploy chatbot: helm upgrade --install chatbot-service /tmp/helm/chatbot ...
7. Deploy backend: loop qua 6 services với helm upgrade --install
Tại sao dùng --dry-run=client -o yaml | kubectl apply -f -?
Để idempotent — chạy nhiều lần không bị lỗi "already exists". K8s Secrets không thể kubectl create 2 lần.
roles/ingress/ — Nginx Ingress Controller
Chạy trên: master only
Làm gì:
1. Set env_short fact
2. Add Helm repo ingress-nginx
3. helm upgrade --install ingress-nginx với service.type=LoadBalancer
4. K3s ServiceLB (klipper-lb) tự động assign node IP cho LoadBalancer service
5. Deploy football-ingress Helm chart → tạo Ingress resource routing / → api-gateway:3000
roles/woodpecker/ — CI/CD server
Chạy trên: k3s_workers[0]
Làm gì: Render và deploy docker-compose.woodpecker.yml với GitHub OAuth + agent secret.
6. Docker Compose Stacks
docker-compose/stateful-stack.yml — Core Infrastructure
| Service | Image | Port (host) | Vai trò |
|---|---|---|---|
postgres |
postgres:16-alpine |
5432 |
7 databases (user, field, booking, payment, keycloak, langfuse, chatbot) |
mongodb |
mongo:7 |
27017 |
notification_db cho notification-service |
redis |
redis:7-alpine |
6379 |
Cache, rate limiting, session (allkeys-lru) |
kafka |
apache/kafka:4.3.1 |
9093 (EXTERNAL) |
Event bus giữa microservices — chế độ KRaft (không còn Zookeeper) |
keycloak |
keycloak:24.0.1 |
8080 |
OAuth2/OIDC Identity Provider |
omni-route |
diegosouzapw/omniroute |
20128 |
Multi-provider LLM router (Groq, Gemini) |
Kafka dual listener — tại sao cần?
K3s pods chạy trên worker nodes không thể resolve hostname kafka (Docker DNS chỉ hoạt động trong Docker network). Cần 2 listeners:
PLAINTEXT://kafka:9092 → Docker containers dùng nhau
EXTERNAL://MASTER_IP:9093 → K3s pods kết nối qua host IP
MASTER_IP được inject từ .env (render bởi Ansible từ k3s_master_ip).
Port bindings mở 0.0.0.0:
Tất cả services bind 0.0.0.0 (không phải 127.0.0.1) để K3s pods trên worker nodes kết nối được qua MASTER_IP:PORT. Trên production cần firewall/VPC rules để restrict access.
docker-compose/observability-stack.yml — Monitoring & LLM Observability
| Service | Image | Port (host) | Vai trò |
|---|---|---|---|
clickhouse |
clickhouse:24.8-alpine |
8123, 9000 |
OLAP store cho SigNoz traces/metrics + LangFuse analytics |
clickhouse-init-databases |
same | - | One-time init: tạo signoz_metrics, signoz_traces, signoz_logs |
otel-collector |
signoz-otel-collector |
4317, 4318 |
Nhận OTLP traces từ K3s pods, ghi vào ClickHouse |
query-service |
signoz/query-service |
internal | SigNoz backend API |
signoz-frontend |
signoz/frontend |
3301 |
SigNoz dashboard UI |
chatbot-redis |
redis:7-alpine |
6380 |
Session store riêng cho chatbot (allkeys-lru) |
langfuse-redis |
redis:7-alpine |
internal | Ingestion queue cho LangFuse (noeviction) |
minio |
cgr.dev/chainguard/minio |
9090, 9091 |
S3 blob store cho LangFuse trace attachments |
langfuse-web |
langfuse/langfuse:3 |
3100 |
LangFuse UI + API |
langfuse-worker |
langfuse/langfuse-worker:3 |
internal | Async processor cho LangFuse traces |
Tại sao 2 Redis riêng?
- redis (6379, stateful-stack): Backend services dùng, policy allkeys-lru
- chatbot-redis (6380, observability-stack): Chatbot session store, policy allkeys-lru
- langfuse-redis (internal): LangFuse queue, phải dùng noeviction để không mất trace data
clickhouse-init-databases:
SigNoz cần 3 databases riêng biệt. Container này chạy clickhouse-client --query="CREATE DATABASE IF NOT EXISTS ..." rồi exit. otel-collector và query-service depend vào service_completed_successfully.
docker-compose/init-scripts/
create-multiple-dbs.sh — Chạy 1 lần khi PostgreSQL init:
1. Đọc POSTGRES_MULTIPLE_DATABASES=user_db,field_db,...,chatbot_db
2. Tạo từng database bằng psql CREATE DATABASE
3. Tạo dedicated user chatbot với password từ CHATBOT_DB_PASSWORD
4. Grant CONNECT + schema permissions trên chatbot_db cho user chatbot
Tại sao chatbot cần user riêng? Least-privilege — chatbot chỉ cần đọc/ghi
chatbot_db, không cần admin access. Mirror với production setup.
mongo-init.js — Chạy 1 lần khi MongoDB init:
Tạo notification_db, collections notifications + email_logs, indexes cần thiết.
7. Helm Charts — K3s Workloads
helm/backend/ — NestJS Microservices
Dùng chung cho 6 services: api-gateway, user-service, field-service, booking-service, payment-service, notification-service.
Chart.yaml → metadata: name=backend, version=0.1.0
values.yaml → defaults: replicaCount=1, image, resources, HPA disabled
values.utm.yaml → UTM overrides (nếu cần khác dev)
values.oracle.yaml → Oracle overrides (resource limits cao hơn, HPA enabled)
templates/
deployment.yaml → K8s Deployment, envFrom: backend-secrets
service.yaml → ClusterIP service
hpa.yaml → HorizontalPodAutoscaler (optional)
_helpers.tpl → Template helpers: fullname, labels
Mỗi service deploy thành một Helm release riêng:
helm upgrade --install user-service /tmp/helm/backend \
--set image.repository=sangtrandev00/user-service \
--set image.tag=abc1234
Env vars: Tất cả được inject qua envFrom: [{secretRef: {name: backend-secrets}}].
K8s pods không có env var hard-code — tất cả từ Secret backend-secrets.
helm/chatbot/ — FastAPI Chatbot
Tương tự backend nhưng:
- Port: 3007 (service) → 3006 (container)
- HPA: enabled mặc định, scale 1→5 replicas theo CPU 70%
- Secret: chatbot-secrets (LLM keys, Pinecone, Redis 6380, LangFuse)
helm/ingress/ — Routing Rules
values.yaml → host: "" (IP-based), apiGateway.service=api-gateway, port=3000
values.utm.yaml → host: "" (dùng IP trực tiếp)
values.oracle.yaml → host: "" (có thể set domain nếu có)
templates/
ingress.yaml → Ingress resource với ingressClassName: nginx
Traffic flow sau khi deploy:
Request → nginx-ingress-controller → api-gateway:3000 → (internal K8s DNS) → services
8. Secrets Management
Cấu trúc 2 vault files
secrets/vault.yml ← MAIN file, load bởi site.yml/deploy.yml
roles/stateful_stack/vars/vault.yml ← Copy subset, load khi chạy riêng role này
Cả 2 file phải có cùng set variables. Khi thêm secret mới, update cả 2.
secrets/vault.yml — Tất cả secrets
| Group | Variables |
|---|---|
| PostgreSQL | vault_db_admin_password |
| Redis main | vault_redis_password |
| Chatbot Redis | vault_chatbot_redis_password |
| Chatbot DB user | vault_chatbot_db_password |
| Kafka UI | vault_kafka_ui_password |
| ClickHouse | vault_clickhouse_password |
| Keycloak | vault_keycloak_admin_password, vault_keycloak_client_secret |
| Backend secrets | vault_internal_service_secret, vault_jwt_secret, vault_jwt_encryption_key |
| MinIO | vault_minio_root_user, vault_minio_root_password |
| LangFuse | vault_langfuse_nextauth_secret, vault_langfuse_salt, vault_langfuse_encryption_key, vault_langfuse_redis_auth, vault_langfuse_init_email, vault_langfuse_password, vault_langfuse_public_key, vault_langfuse_secret_key |
| LLM | vault_groq_api_key, vault_pinecone_api_key, vault_pinecone_index_name |
| Woodpecker CI | vault_woodpecker_agent_secret, vault_gh_oauth_client_id, vault_gh_oauth_secret |
Flow secrets → server
secrets/vault.yml (encrypted)
│ ansible-vault decrypt (runtime)
▼
roles/stateful_stack/templates/env.production.j2
│ Ansible template render (Jinja2)
▼
/home/ubuntu/stacks/football-booking/.env (mode 0600, trên server)
│ Docker Compose ${VAR} expansion
▼
Environment variables trong containers
secrets/vault.yml (encrypted)
│ ansible-vault decrypt (runtime)
▼
roles/helm_apps/tasks/main.yml
│ kubectl create secret --from-literal=KEY=VALUE
▼
K8s Secret: backend-secrets (namespace: football-booking)
K8s Secret: chatbot-secrets (namespace: football-booking)
│ envFrom: secretRef
▼
Environment variables trong K3s pods
Ansible Vault commands
# Encrypt trước khi commit
ansible-vault encrypt secrets/vault.yml
# Decrypt để edit
ansible-vault edit secrets/vault.yml
# Chạy playbook với vault
ansible-playbook -i inventory/utm playbooks/site.yml \
--vault-password-file ~/.vault_pass
9. K8s Secrets — Connection Strings
backend-secrets — cho 6 NestJS microservices
| Key | Giá trị mẫu | Service dùng |
|---|---|---|
DB_HOST |
192.168.64.10 |
Tất cả |
DATABASE_URL_USER |
postgresql://football_admin:pw@MASTER_IP:5432/user_db |
user-service |
MONGODB_URI |
mongodb://football_admin:pw@MASTER_IP:27017/notification_db |
notification-service |
REDIS_URL |
redis://:pw@MASTER_IP:6379 |
Tất cả |
KAFKA_BROKERS |
MASTER_IP:9093 |
field, booking, payment, notification |
KEYCLOAK_URL |
http://MASTER_IP:8080 |
api-gateway, user-service |
USER_SERVICE_URL |
http://user-service:3001 |
api-gateway (K8s DNS) |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://MASTER_IP:4318 |
Tất cả |
INTERNAL_SERVICE_SECRET |
random 32-char | Header X-Internal-Service |
chatbot-secrets — cho FastAPI chatbot
| Key | Giá trị mẫu | Ghi chú |
|---|---|---|
LLM_PROVIDERS |
omniroute |
Dùng OmniRoute router |
OMNIROUTE_BASE_URL |
http://MASTER_IP:20128/v1 |
API keys qua OmniRoute dashboard |
GROQ_API_KEY |
gsk_... |
Fallback / thêm vào OmniRoute |
REDIS_URL |
redis://:pw@MASTER_IP:6380 |
chatbot-redis riêng (port 6380) |
CHATBOT_DB_URL |
postgresql://chatbot:pw@MASTER_IP:5432/chatbot_db |
Dedicated chatbot user |
LANGFUSE_HOST |
http://MASTER_IP:3100 |
LLM observability |
LANGFUSE_PUBLIC_KEY |
pk-lf-football-prod |
Phải match LANGFUSE_INIT_PROJECT_PUBLIC_KEY |
PINECONE_API_KEY |
pcsk_... |
Vector DB cho RAG |
10. Network Communication
┌─────────────────────────────────────────────────────────┐
│ football-booking-network (Docker bridge) │
│ │
│ postgres mongodb redis kafka keycloak │
│ omni-route clickhouse otel-collector │
│ langfuse-web langfuse-worker minio │
│ chatbot-redis langfuse-redis │
│ signoz-frontend query-service │
└──────────────────────────┬──────────────────────────────┘
│ Tất cả containers trong cùng
│ Docker network dùng hostname
│ (postgres:5432, kafka:9092, ...)
│
┌──────────────────────────▼──────────────────────────────┐
│ K3s Pods (worker nodes) │
│ │
│ Kết nối qua MASTER_IP:PORT (host binding 0.0.0.0) │
│ MASTER_IP:5432 → postgres │
│ MASTER_IP:27017 → mongodb │
│ MASTER_IP:6379 → redis │
│ MASTER_IP:6380 → chatbot-redis │
│ MASTER_IP:9093 → kafka EXTERNAL listener │
│ MASTER_IP:8080 → keycloak │
│ MASTER_IP:4318 → otel-collector HTTP │
│ MASTER_IP:3100 → langfuse-web │
│ MASTER_IP:20128 → omni-route LLM router │
│ │
│ K8s internal DNS (ClusterIP): │
│ api-gateway → user-service:3001 (K8s DNS) │
│ api-gateway → chatbot-service:3007 (K8s DNS) │
└─────────────────────────────────────────────────────────┘
11. OmniRoute — LLM Router
Vai trò: Multi-provider LLM router. Chatbot không gọi Groq/Gemini trực tiếp mà gọi OmniRoute, OmniRoute route đến provider phù hợp.
chatbot K3s pod
│ POST http://MASTER_IP:20128/v1/chat/completions
│ model: "football-fast" hoặc "football-power"
▼
omni-route:20128 (Docker Compose, master node)
│ Route dựa trên model alias đã config
├─► Groq (llama-3.1-8b-instant)
├─► Gemini (gemini-2.0-flash)
└─► Anthropic (claude-haiku-4-5)
API keys được thêm qua OmniRoute dashboard (http://MASTER_IP:20128), không hardcode trong env. Stored trong volume omniroute_data.
12. CI/CD Flow (GitHub Actions)
Developer push → GitHub main branch (app repo)
│
▼
GitHub Actions — CI job (chạy trên GitHub cloud)
│ dorny/paths-filter → detect service nào thay đổi
│ docker/build-push-action → build image
│ Push sangtrandev00/<service>:<git-sha> → Docker Hub
│
▼
GitHub Actions — CD job (gọi reusable workflow từ infra-k3s repo)
uses: .../football-booking-infra-k3s/.github/workflows/cd-backend.yml@main
│
▼
appleboy/ssh-action → SSH vào master node
helm upgrade --install <service> /tmp/helm/backend \
--set image.tag=<git-sha> --atomic
│
▼
K3s rolling update → 0 downtime
old pod running → new pod starts → health check pass → old pod terminate
(--atomic: tự rollback nếu health check fail)
Reusable workflows nằm trong repo này:
- .github/workflows/cd-backend.yml — deploy 1 backend service
- .github/workflows/cd-chatbot.yml — deploy chatbot service
App repos không tự chứa deploy logic — chỉ gọi workflows từ đây, đảm bảo deploy logic tập trung 1 chỗ.
Deploy thủ công (fallback không cần GitHub Actions):
ansible-playbook -i inventory/oracle playbooks/deploy_service.yml \
-e "service=user-service" -e "image_tag=abc1234"
GitHub Secrets cần set trong mỗi app repo:
- DEPLOY_SSH_KEY — private key SSH vào master
- K3S_MASTER_HOST — IP master node
- DOCKER_HUB_USERNAME + DOCKER_HUB_TOKEN
Setup chi tiết: xem .github/workflows/README.md
13. Hướng dẫn deploy lần đầu (UTM)
Bước 1: Chuẩn bị
# Tạo 3 UTM VMs (Ubuntu 22.04), cấu hình IP tĩnh:
# Master: 192.168.64.10
# Worker1: 192.168.64.11
# Worker2: 192.168.64.12
# Tạo SSH key
ssh-keygen -t ed25519 -f ~/.ssh/utm_k3s
# Copy public key vào các VMs
ssh-copy-id -i ~/.ssh/utm_k3s.pub ubuntu@192.168.64.10
ssh-copy-id -i ~/.ssh/utm_k3s.pub ubuntu@192.168.64.11
ssh-copy-id -i ~/.ssh/utm_k3s.pub ubuntu@192.168.64.12
Bước 2: Điền secrets
# Copy template và điền giá trị thật
cp secrets/vault.yml secrets/vault.yml.bak
# Edit secrets/vault.yml — thay tất cả "CHANGE_ME" bằng giá trị thật
# Đặc biệt cần generate:
openssl rand -hex 32 # → LANGFUSE_ENCRYPTION_KEY (64 chars)
openssl rand -base64 32 # → JWT_SECRET, NEXTAUTH_SECRET, ...
# Encrypt
ansible-vault encrypt secrets/vault.yml
echo "my-vault-password" > ~/.vault_pass
chmod 600 ~/.vault_pass
Bước 3: Deploy
# Full cluster setup (~15-20 phút)
ansible-playbook -i inventory/utm playbooks/site.yml \
--vault-password-file ~/.vault_pass
# Verify
./scripts/verify-cluster.sh utm
kubectl --kubeconfig ~/.kube/football-k3s.yaml get pods -n football-booking
Bước 4: Cấu hình OmniRoute
# Truy cập dashboard
open http://192.168.64.10:20128
# Thêm API keys qua UI:
# - Groq: gsk_xxx → alias "football-fast" (llama-3.1-8b-instant)
# - Gemini: AIzaxxx → alias "football-power"
Bước 5: Verify services
# Test API Gateway
curl http://192.168.64.10/health
# LangFuse UI
open http://192.168.64.10:3100
# SigNoz APM
open http://192.168.64.10:3301
# Keycloak Admin
open http://192.168.64.10:8080
14. Vận hành thường ngày
# Re-deploy sau push code
ansible-playbook -i inventory/utm playbooks/deploy.yml \
-e "backend_image_tag=$(git rev-parse --short HEAD)"
# Deploy 1 service cụ thể
ansible-playbook -i inventory/utm playbooks/deploy_service.yml \
-e "service=user-service" -e "image_tag=abc1234"
# Xem logs K3s pod
kubectl --kubeconfig ~/.kube/football-k3s.yaml logs \
-n football-booking deployment/user-service -f
# Xem Docker Compose logs trên master
ssh ubuntu@192.168.64.10 \
"cd /home/ubuntu/stacks/football-booking && docker compose -f stateful-stack.yml logs kafka -f"
# Scale chatbot
kubectl --kubeconfig ~/.kube/football-k3s.yaml scale \
deployment/chatbot-service --replicas=3 -n football-booking
# Restart một service
kubectl --kubeconfig ~/.kube/football-k3s.yaml rollout restart \
deployment/api-gateway -n football-booking
15. Ports tham chiếu nhanh
| Port | Service | Accessible from |
|---|---|---|
| 80/443 | nginx-ingress | Internet / Browser |
| 3000 | api-gateway (K8s ClusterIP) | Internal K8s only |
| 3100 | langfuse-web | Browser |
| 3301 | signoz-frontend | Browser |
| 4317 | otel-collector gRPC | K3s pods |
| 4318 | otel-collector HTTP | K3s pods |
| 5432 | PostgreSQL | K3s pods, local dev |
| 6379 | Redis (backend) | K3s pods |
| 6380 | chatbot-redis | K3s pods (chatbot) |
| 8080 | Keycloak | K3s pods, browser |
| 9093 | Kafka EXTERNAL | K3s pods |
| 20128 | OmniRoute dashboard | Browser, K3s pods |
| 27017 | MongoDB | K3s pods |