1. Ý Tưởng Ban Đầu
Quản lý nhiều server HAProxy thủ công qua SSH là công việc nhàm chán và dễ sai. Mỗi lần thay đổi config phải SSH từng con, không có lịch sử, không có rollback, không biết node nào đang chạy version nào.
HA-Master ra đời để giải quyết bài toán đó: hệ thống quản lý HAProxy tập trung theo mô hình Master–Agent, hoàn toàn tự động, có versioning và audit log đầy đủ.
Nguyên tắc cốt lõi: Config nằm trong DB (source of truth), không trên node. Agent luôn initiate kết nối ra ngoài — Master không bao giờ SSH vào node. Mọi deployment đều idempotent dựa trên config hash.
2. Kiến Trúc Hệ Thống
Toàn bộ system chạy trên Kubernetes. Frontend (React + nginx) nhận request từ Cloudflare Tunnel, proxy /api/ và /auth/ vào Master. Master giao tiếp với Agent trên các HAProxy node qua gRPC mTLS.
3. Vibe Coding Với AI
Toàn bộ codebase được xây dựng theo phong cách vibe coding — mô tả yêu cầu bằng ngôn ngữ tự nhiên, AI generate, review và điều chỉnh theo kiến trúc đã định trong CLAUDE.md.
Key là define constraints trước: architectural decisions, system boundaries, coding guidelines đều được ghi rõ. AI không tự ý thay đổi communication model hay thêm dependency không cần thiết.
Stack
| Backend | Go 1.25, Echo, pgx/v5, gRPC, golang-migrate |
| Frontend | React 18 + TypeScript, Vite, TanStack Query, shadcn/ui |
| Database | PostgreSQL 16, pgcrypto, nfs-storage PVC |
| Comms | gRPC over HTTP/2 với mTLS, Agent-initiated outbound |
| Container | Docker multi-stage build, 3 images: master, agent, frontend |
| GitOps | GitLab CI + ArgoCD, Kustomize, auto image tag update |
Cấu trúc thư mục
haproxy-gui/ ├── cmd/ │ ├── master/main.go — Master service entrypoint │ └── agent/main.go — Agent service entrypoint ├── internal/ │ ├── master/ — HTTP handlers, gRPC server │ ├── agent/ — Agent logic, heartbeat │ ├── store/ — DB layer (pgx) │ ├── engine/ — Deployment engine (idempotent) │ ├── auth/ — JWT + refresh token │ └── crypto/ — AES-256-GCM cert encryption ├── frontend/src/ │ ├── pages/ — configs, deployments, certs, nodes │ └── lib/api-client.ts — typed API client ├── manifests/ — Kubernetes manifests (Kustomize) ├── migrations/ — PostgreSQL migrations ├── Dockerfile.master / Dockerfile.agent / Dockerfile.frontend └── .gitlab-ci.yml
4. CI/CD Pipeline (GitLab)
GitLab CI tự động build 3 Docker images khi có code push. Mỗi image được tag bằng $CI_COMMIT_SHORT_SHA. Deploy stage dùng Kustomize để update tag trong kustomization.yaml rồi push lại repo — ArgoCD pick up và sync.
deploy:
stage: deploy
image: alpine/git
script:
- cd manifests
- kustomize edit set image $IMAGE_MASTER:$CI_COMMIT_SHORT_SHA
- kustomize edit set image $IMAGE_AGENT:$CI_COMMIT_SHORT_SHA
- kustomize edit set image $IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA
- git commit -m "chore: update image tags [skip ci]"
- git push origin HEAD:$CI_DEFAULT_BRANCH
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
5. Kubernetes Deployment
Cluster 3 nodes tại 10.100.1.21/22/23. Trước khi deploy cần bootstrap 3 Kubernetes secrets thủ công.
-
Registry credentials
Pull images từ private registry
registry.perfectkey.vnvới robot accountkubectl create secret docker-registry ha-registry-creds \ --docker-server=registry.perfectkey.vn \ --docker-username='robot$pkm+web' \ --docker-password='<token>' \ -n ha-master
-
App secrets
DATABASE_URL, JWT_SECRET, CERT_ENCRYPTION_KEY (AES-256-GCM), POSTGRES_PASSWORD
kubectl create secret generic ha-master-secrets \ --from-literal=DATABASE_URL='postgres://hamaster:pass@postgres:5432/hamaster' \ --from-literal=JWT_SECRET='<32-char-random>' \ --from-literal=CERT_ENCRYPTION_KEY='<32-byte-hex>' \ --from-literal=POSTGRES_PASSWORD='<db-pass>' \ -n ha-master
-
mTLS certificates
CA cert/key + Master server cert — phải generate trong cùng một session để tránh mismatch.
⚠ Lưu ý: ca.crt và ca.key từ 2 session khác nhau sẽ gây lỗitls: private key does not match public key. Luôn generate trong một lần. Trên Windows Git Bash cầnMSYS_NO_PATHCONV=1.MSYS_NO_PATHCONV=1 openssl req -x509 -newkey rsa:4096 -days 3650 \ -keyout ca.key -out ca.crt -nodes -subj "/CN=ha-master-ca" MSYS_NO_PATHCONV=1 openssl req -newkey rsa:4096 -nodes \ -keyout master.key -out master.csr -subj "/CN=master" MSYS_NO_PATHCONV=1 openssl x509 -req -in master.csr \ -CA ca.crt -CAkey ca.key -CAcreateserial \ -out master.crt -days 3650 kubectl create secret generic ha-tls-certs \ --from-file=ca.crt --from-file=ca.key \ --from-file=master.crt --from-file=master.key \ -n ha-master
-
Apply Kustomize manifests
Frontend dùng nginx serve SPA và reverse proxy API về Master service nội bộ.
nginx.conf — SPA + reverse proxylocation /api/ { proxy_pass http://master-http.ha-master.svc.cluster.local/api/; proxy_read_timeout 600s; } location /auth/ { proxy_pass http://master-http.ha-master.svc.cluster.local/auth/; } location / { try_files $uri $uri/ /index.html; # SPA fallback }
6. Cài Đặt ArgoCD
ArgoCD watch repo GitLab, tự đồng bộ Kubernetes khi kustomization.yaml thay đổi. Chạy ở chế độ insecure — TLS terminate tại Cloudflare Tunnel, không cần self-signed cert bên trong.
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Expose UI qua NodePort 32141
kubectl patch svc argocd-server -n argocd \
-p '{"spec":{"type":"NodePort","ports":[{"name":"https","port":443,"targetPort":8080,"nodePort":32141}]}}'
# Tắt TLS nội bộ — Cloudflare xử lý HTTPS bên ngoài
kubectl patch configmap argocd-cmd-params-cm -n argocd \
-p '{"data":{"server.insecure":"true"}}'
kubectl rollout restart deployment argocd-server -n argocd
# Lấy password admin
kubectl get secret -n argocd argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: haproxy-ui
namespace: argocd
spec:
source:
repoURL: https://git.perfectkey.vn/pkmx/haproxy-ui.git
targetRevision: master
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: ha-master
syncPolicy:
automated:
prune: true
selfHeal: true
7. Expose Qua Cloudflare ZeroTrust
Cloudflare Tunnel cho phép expose ứng dụng ra internet không cần public IP, không cần mở firewall port. Chỉ cần cloudflared agent kết nối outbound đến Cloudflare edge.
Cấu hình trên Cloudflare Dashboard
Vào Zero Trust → Access → Tunnels → chọn tunnel → Public Hostname → thêm route:
- Subdomain:
haproxy-gui - Domain:
diendo.pro.vn - Service:
http://frontend.ha-master.svc.cluster.local:80
Hoặc deploy cloudflared trực tiếp trong cluster để tunnel đi qua K8s DNS:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: cloudflared
spec:
replicas: 2 # HA — 2 tunnel instances
template:
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args: [tunnel, --config, /etc/cloudflared/config.yaml, run]
---
# ConfigMap config.yaml
tunnel: <tunnel-id>
credentials-file: /etc/cloudflared/credentials.json
ingress:
- hostname: haproxy-gui.diendo.pro.vn
service: http://frontend.ha-master.svc.cluster.local:80
- service: http_status:404
8. Kết Quả
Sau khoảng 1 ngày vibe coding + deploy, hệ thống đang chạy ổn định trên cluster 3 nodes.
# kubectl get pods -n ha-master
NAME READY STATUS RESTARTS AGE
frontend-c6ccfd7d-grcf9 1/1 Running 0 2h
master-85fc9f6979-chfxs 1/1 Running 0 2h
postgres-0 1/1 Running 0 2h
# curl http://10.100.1.22:30880/health
{"status":"ok"}
GitOps flow hoạt động:
9. Vấn Đề Gặp Phải & Cách Xử Lý
| Vấn đề | Nguyên nhân | Giải pháp | Type |
|---|---|---|---|
go.mod requires go 1.25 | CI dùng golang:1.23 | Đổi CI image sang golang:1.25-alpine | CI |
| PVC Pending mãi | Không có StorageClass | Thêm storageClassName: nfs-storage | K8s |
tls: private key mismatch | CA cert/key từ 2 session khác nhau | Regenerate certs trong 1 session, dùng MSYS_NO_PATHCONV=1 | TLS |
| Image pull từ docker.io | Kustomize không apply tag đúng | Hardcode full registry URL vào manifest | K8s |
| NodePort 30080 conflict | WooCommerce đang dùng cổng đó | Đổi sang NodePort 30880 | K8s |
| Liveness probe restart loop | Probe timeout < startup time (~55s) | Bỏ liveness probe, giữ readiness | K8s |
/health trả 404 | Old image chưa có endpoint | Build lại image, thêm handler Go, push thủ công | Go |
robot$pkm+web encoding lỗi | PowerShell escape ký tự $ | Dùng Python chr(36) thay vì shell expansion | Ops |
| Docker context sai | dockerDesktopLinuxEngine pipe lỗi | docker context use default | Docker |
| Build context 252MB | node_modules không bị exclude | Tạo Dockerfile.frontend.dockerignore | Docker |
10. Bài Học Rút Ra
Define constraints trước khi vibe code. CLAUDE.md với architectural decisions rõ ràng giúp AI không đi lạc, không tự ý thay đổi communication model.
Secrets là điểm dễ sai nhất. mTLS certs phải generate đồng bộ. Encoding ký tự đặc biệt trong password cần cẩn thận qua nhiều shell layer.
GitOps + ArgoCD = deploy không lo. Một khi pipeline setup xong, chỉ cần git push là cluster tự đồng bộ. Zero manual kubectl.
Cloudflare ZeroTrust thay Ingress + cert-manager. Đơn giản hơn, không cần public IP, HTTPS miễn phí, không cần LoadBalancer.
NodePort đủ dùng cho internal tools. Không cần setup LoadBalancer phức tạp khi đã có tunnel.
Vibe coding nhanh nhưng cần verify từng bước. Build → test → check logs → next. Đừng giả sử AI generate đúng 100% lần đầu.