18 KiB
CLAUDE.md — DBIZ Lakehouse trên Kubernetes (full context)
File ngữ cảnh cho các session sau. Đọc file này là nắm được: đang ở đâu, đã chốt gì, bẫy nào đã gặp, và còn phải làm gì. Cập nhật mục "TRẠNG THÁI" mỗi khi qua một mốc.
Nguyên tắc vàng của cả dự án: rebuild có chủ đích (sửa nợ dev khi lên K8s), KHÔNG lift-and-shift, KHÔNG đập cái đang chạy. GitOps thuần. Helm + YAML thuần, KHÔNG Kustomize. "dev giống prod" = cùng chart, khác values theo env. "Stateful đứng yên, compute di": MinIO (data lake) GIỮ NGUYÊN ngoài K8s, compute di lên K8s trỏ về qua S3.
0. TL;DR — đang ở đâu (cập nhật: 2026-07-08)
Đang ở Phase 0 (nền K8s), sắp xong. Cụ thể:
- ✅ Resize
/các node worker — xong. - ✅ Phép thử sinh tử: pod K8s → MinIO ngoài (.37) — XANH HOÀN TOÀN (list + get + put + delete từ trong pod). Đây là rủi ro #1 của cả kế hoạch, đã gỡ.
- ✅ CPU baseline x86-64-v2 — đã kiểm hết 5 node (xem mục Bẫy #1).
- ✅ iscsi_tcp + iscsid trên cả 5 VM (prerequisite Longhorn) — xong, module đã ghim tự-load-sau-reboot.
- ✅ Sealed Secrets controller — đã có sẵn trên cụm (kube-system).
- ✅ ArgoCD — đã chạy (namespace argocd).
- ✅ Repo GitOps
dbiz-lake-k8s— đã có, public, đã điền<REPO_URL>=https://git.renolation.com/renolation/dbiz-lake-k8s.git, đã commit + push. - ⏳ ĐANG LÀM: cài Longhorn như hạ tầng nền cụm (cách A — tách riêng khỏi envs//apps). Chưa xong.
- ⬜ Sau Longhorn: apply root app tầng lake (
envs/dev/apps/root.yaml).
Bước kế tiếp ngay: cài Longhorn qua ArgoCD Application (tầng infra nền cụm), verify StorageClass longhorn + PVC survive pod delete. RỒI mới apply root lake stack.
1. Bối cảnh hạ tầng
Cụm K8s
- 5 VM K8s trên 1 host Proxmox. (Có Proxmox thứ hai cùng mạng → lộ trình HA thật sau này.)
- Control plane HA, đứng sau VIP/LB tại
192.168.110.14:8443(kubeconfig trỏ vào đây, KHÔNG phải 127.0.0.1:6443). - StorageClass hiện tại:
local-path(default). Sắp thêmlonghorn. - Namespace convention lakehouse:
dbiz-lake-dev,dbiz-lake-prod.- (Phân biệt:
dbiz-dev/staging/prod+monitoringlà namespace của app platform khác, KHÔNG phải lake.)
- (Phân biệt:
Hạ tầng NGOÀI K8s (giữ nguyên, compute trỏ vào qua Service+Endpoints không selector)
- MinIO (data lake): VM
lake=192.168.110.37:9000. Bucketdbiz-warehouse. Keydbiz/dbiz1234(dev — NỢ bảo mật).- Đây là điểm neo dữ liệu xuyên suốt — KHÔNG BAO GIỜ migrate. Toàn bộ compute K8s trỏ về đây như S3 endpoint.
- Trong bucket đã có prefix:
raw_crm/,raw_posthog/,raw_test/,results_posthog/,staging_posthog/, vài prefix UUID, và rácstaging_posthog_staging_posthog/.
- Postgres nguồn CRM:
172.20.109.5:5432— cho Debezium/Strimzi trỏ tới khi làm CDC (Phase 4). Cần xác nhận đúng là PG nguồn CRM trước khi dùng.
Git / CI
- Repo GitOps:
git.renolation.com/renolation/dbiz-lake-k8s(public).- Plan gốc ghi đích cuối là
git.dbiz.com(Gitea built-in registry) — nhưng "URL là biến số". Đang dùng Renolation, đổi sau = một lệnh sed + cập nhật ArgoCD repo creds.
- Plan gốc ghi đích cuối là
- CI: Woodpecker (đã có). File
.woodpecker/. Build + push image spark-cdc. - Registry: placeholder
<IMAGE_REGISTRY>(đích: Gitea built-in). Image cdc-job hiện trỏgit.dbiz.com/dbiz/spark-cdctrong values — cần đổi sang registry Renolation thật khi build image (chưa làm).
2. Kiến trúc đích (K8s)
┌──────────────── K8s cluster (5 VM / 1 host Proxmox) ────────────────┐
│ namespace: dbiz-lake-dev | dbiz-lake-prod │
│ │
│ ArgoCD (app-of-apps) ── sync ──► mọi stack │
│ │
│ Strimzi(Kafka+Connect+Debezium) · Spark Operator · Trino · Polaris · │
│ Airflow(K8sExecutor) · dbt · Superset/Metabase · OpenMetadata │
│ Longhorn (block PV: Postgres / Kafka / ES / Spark checkpoint) │
└──────────────────────────┬───────────────────────────────────────────┘
│ S3 endpoint (qua mạng)
┌──────────────────▼─────────────┐
│ MinIO (GIỮ NGUYÊN, ngoài K8s) │ ← data lake đứng yên
│ 192.168.110.37, dbiz-warehouse │
└────────────────────────────────┘
Postgres CRM (172.20.109.5) ──► Debezium(Strimzi) bắt CDC
Stack đích & mô hình chạy
| Layer | Stack | Ghi chú K8s |
|---|---|---|
| Storage | MinIO | NGOÀI K8s (giữ nguyên), Service+Endpoints minio-lake |
| Table format | Apache Iceberg | thư viện trong engine, không phải service |
| Catalog (L4) | Polaris | chart CHƯA có trong skeleton → thêm app riêng |
| Engine | Trino, Spark (Spark Operator, SparkApplication CRD) | Spark job = pod ephemeral, image bundled |
| Ingestion | Strimzi (Kafka+Connect+Debezium) dựng mới trên K8s | chấp nhận snapshot CDC lại (không migrate offset Docker) |
| Transform | dbt (KubernetesPodOperator) | compute ở Trino |
| Orchestration | Airflow (K8sExecutor, git-sync DAG) | giải nợ version-control DAG |
| Consumption | Superset + Metabase | đọc results qua Trino |
| Governance | OpenMetadata + ES + PG | LÀM SAU CÙNG — version lock mong manh nhất |
⚠️ Lưu ý catalog: tài liệu ARCHITECTURE.md (bản cũ) ghi Lakekeeper, nhưng bản đang chạy thật + mọi tài liệu vận hành/K8s dùng Apache Polaris 1.5.0. → Polaris là bản đúng.
3. Quyết định đã chốt (không bàn lại trừ khi có lý do mới)
- 1 cụm K8s, tách namespace dev/prod. Helm + values per-env. KHÔNG Kustomize.
- GitOps đầy đủ từ đầu: ArgoCD (app-of-apps) + Woodpecker CI + Sealed Secrets + cert-manager.
- Secret = Sealed Secrets (mã hoá, commit an toàn vào git). Giải nợ credential plaintext.
- MinIO GIỮ NGUYÊN ngoài K8s. Không migrate data lake.
- Longhorn cho block PV nội cụm (Postgres/Kafka/ES/Spark checkpoint). Không ôm data lake → nhu cầu nhỏ.
- Kafka: Strimzi dựng mới trên K8s (không xài ké Docker cũ). Snapshot CDC lại.
- Spark: Spark Operator (SparkApplication CRD), image bundled (jar Iceberg/Kafka sẵn trong image).
- Longhorn cài theo "cách A": hạ tầng nền cụm tách riêng khỏi
envs/<env>/apps(nó là singleton toàn cụm, không thuộc riêng dev/prod). - Multi-tenant — phân biệt 2 loại:
- Tenant trong CRM (data DBIZ) → tách bằng cột
tenantId, KHÔNG nhân hạ tầng. Lake DBIZ là MỘT. - Khách mua nền tảng (Vietbank/Gemadept) → isolate per khách qua ApplicationSet. 3 mức cách ly (M1 chung compute/tách data · M2 tách namespace · M3 isolate hoàn toàn kể cả on-prem).
- Tenant trong CRM (data DBIZ) → tách bằng cột
- "Lakehouse as a product", YAGNI có kỷ luật: LÀM NGAY = DBIZ lake "khách số 0", tham số hoá sạch (chart nhận customer/source/bucket/s3.endpoint qua values). CHƯA LÀM = ApplicationSet lake, preset 3 mức, đa cụm (thêm khi có khách thứ 2).
- Chart nhận mọi dependency qua values (S3/nguồn/kafka) — KHÔNG hardcode hạ tầng. Điều kiện để 1 chart phục vụ cả multi-tenant DBIZ lẫn on-prem air-gapped.
Quyết định cho Longhorn (chốt tại session này)
- Cài qua ArgoCD Application (Helm chart upstream
https://charts.longhorn.io), GHIM version. - GIỮ
local-pathlàm default StorageClass — Longhorn là StorageClass tường minh, chỉ workload chỉ địnhstorageClass: longhornmới dùng (tránh vô tình đẩy mọi PVC lên Longhorn). - Replica count: đề xuất 2 (không 3). Lý do: 1 host vật lý → 3 replica chỉ "an toàn giả" (mất host mất hết). 2 replica đủ chống node/pod chết, nhẹ hơn; phần còn lại dựa vào backup (đúng mục E rủi ro #2).
4. Cấu trúc repo dbiz-lake-k8s
charts/lake-cdc-job/ Chart CDC job (SparkApplication + checkpoint PVC) — THAM SỐ HOÁ theo customer
envs/dev/
apps/ ArgoCD Applications (App-of-Apps tầng LAKE)
root.yaml Root → quét envs/dev/apps (path=envs/dev/apps, dest ns=argocd)
infra.yaml → envs/dev/infra (Service+Endpoints ngoài), dest ns=dbiz-lake-dev
strimzi-operator.yaml
spark-operator.yaml
kafka-cluster.yaml → envs/dev/kafka
cdc-job.yaml chart lake-cdc-job + values
infra/external-services.yaml MinIO ngoài .37 + Postgres CRM .5 (ĐÃ điền IP thật)
kafka/kafka.yaml Strimzi KRaft, Longhorn PV
values/cdc-job.yaml values khách số 0 = dbiz
sealed-secrets/ (.gitkeep) — SealedSecrets sẽ để đây
envs/prod/sealed-secrets/ (.gitkeep)
images/spark-cdc/Dockerfile FROM spark:3.5.6 + jar Iceberg/Kafka + cdc_crm_to_raw.py
.woodpecker/ Woodpecker CI
SẮP THÊM (Longhorn, cách A): thư mục tầng nền cụm riêng, ví dụ
infra/cluster/vớiroot.yaml(app-of-apps nền) +00-longhorn.yaml. Tách khỏienvs/<env>/apps. (Chốt tên chính xác khi làm ở session repo.)
File values cdc-job (khách số 0 = dbiz) — điểm cần biết
customer: dbiz,targetTable: iceberg.raw_crm.cdc_events,topicPattern: crm\.public\..*catalog.uri: http://polaris:8181/api/catalog,warehouse: dbiz_warehouses3.endpoint: http://minio-lake:9000(Service nội cụm trỏ ra .37),region: us-east-1checkpoint: storageClass=longhorn, size=5Gi← lý do Longhorn phải xong TRƯỚC khi apply root lakeimage.repository: git.dbiz.com/dbiz/spark-cdc← CẦN đổi sang registry Renolation thậtimagePullSecret: gitea-registry← cần seal secret này
5. Roadmap theo Phase (K8S_MIGRATION_PLAN)
- Phase 0 — Nền K8s (đang ở đây): resize
/✅ · Longhorn ⏳ · Sealed Secrets ✅ · cert-manager + ingress ⬜ · ArgoCD app-of-apps ✅ (một phần). Verify mạng K8s→MinIO ✅. - Phase 1 — Image & MinIO endpoint: Dockerfile spark-cdc + Woodpecker build/push ⬜ · SealedSecret minio-creds ⬜. (Verify pod K8s
mc/aws s3 lsbucket ✅ đã làm sớm.) - Phase 2 — State metadata (Longhorn PV): Postgres K8s (CloudNativePG) — dump Docker → restore K8s. (MinIO không cần.)
- Phase 3 — Catalog + Engine: Polaris (Deployment, chart phải TỰ THÊM) → PG K8s + MinIO ngoài · Spark Operator + Trino (Helm). Verify Trino query Iceberg (data cũ MinIO, catalog Polaris K8s).
- Phase 4 — Ingestion CDC (cắt over): Strimzi (Kafka+Connect+Debezium) PV Longhorn · KafkaConnector Debezium → PG CRM .5 · SparkApplication CDC →
raw_crm.cdc_events. Verify update ở CRM chảy vào lake. RỒI tắt CDC Docker cũ. - Phase 5-7: Airflow (K8sExecutor, git-sync) + dbt (KubernetesPodOperator) · Superset + Metabase · OpenMetadata (SAU CÙNG, ghim version chặt).
- Phase 8 — Dọn dẹp: verify toàn hệ ≥ vài ngày, giữ Docker fallback, tắt dần Docker. MinIO vẫn giữ.
6. Bẫy đã gặp (cheat-sheet — đọc trước khi debug)
- x86-64-v2 glibc fatal error. Image hiện đại (minio/mc mới, aws-cli, sẽ cả Spark/Trino/ClickHouse/ES) build với glibc yêu cầu x86-64-v2. VM Proxmox để CPU type mặc định (
kvm64/qemu64) CHE mất instruction v2 (thiếu sse4_2/popcnt/ssse3/sse4_1). → Sửa gốc: ProxmoxVM→Hardware→Processors→Type=host(perf tối đa) hoặcx86-64-v2-AES(an toàn migrate 2 host), rồi power cycle (reboot guest không đủ). Kiểm:lscpu | grep -oE 'sse4_2|popcnt|ssse3|sse4_1'. Phải đồng nhất MỌI node (pod schedule bất kỳ node nào). Vá tạm qua phép thử mạng: pinminio/mc:RELEASE.2023-11-20T16-30-59Z(chạy trên v1). - kubeconfig trỏ VIP
.14:8443, không phải127.0.0.1:6443.curl 127.0.0.1:6443/healthz= ok chỉ chứng minh apiserver LOCAL sống; nếukubectl(qua .14) báoUnable to connect to the server: EOFthì VIP/LB đang fail-over. Xảy ra khi power-cycle VM master. - etcd 3-node: chỉ đụng MỘT master tại một thời điểm. Đổi CPU/power-cycle tuần tự, đợi node rejoin + quorum ổn rồi mới sang node kế. Tắt 2 master cùng lúc = mất quorum, cả cụm đứng. (Đã thấy restart count etcd/apiserver=5 khi power-cycle — fail-over tạm, tự hồi.)
- Longhorn prerequisite:
open-iscsi+iscsidactive + moduleiscsi_tcptrên MỌI node. Ghim/etc/modules-load.d/iscsi_tcp.confđể tự load sau reboot (modprobe tay chỉ tới reboot kế). Thiếu → volumePending/faulted. - ArgoCD đọc git REMOTE, không đọc file local. Commit mà chưa
git push→ ArgoCD vẫn thấy bản cũ. Luôn push trước khi apply/sync. - MinIO advertised-endpoint:
list(ls) thông KHÔNG chứng minh get/put thông. Phép thử sinh tử phải là put+get+delete object thật TỪ TRONG POD (không phải từ host, không phải ping). Đã test XANH. - Spark ↔ MinIO cần path-style S3 (không virtual-host style) + executor
-Daws.region=us-east-1(system property). Nếu Spark fail đọc S3 dù mc OK → nghi path-style/region. - Iceberg từ chối
timestamp(3)→ phảitimestamp(6). - Polaris chặn DROP-with-purge (an toàn prod). Trino/dbt KHÔNG purge được, chỉ Spark
DROP TABLE ... PURGE. dbt marts phải+on_table_exists: replace(dùng CREATE OR REPLACE), nếu không chạy lần 2 fail. - OpenMetadata version lock (OM ↔ ES ↔ Airflow-trong-ingestion). ES phải khớp client (OM 1.13.1 → ES 9.3.0). Restart
openmetadata_serversau khi đổi ES. Scheduler UI hỏng với Airflow 3.2 → ingestion bằng CLI/YAML. → Để SAU CÙNG, ghim version, không auto-latest. - 1 host Proxmox = điểm chết đơn. HA storage là ảo giác tới khi trải 2 host. Backup thật = Velero (PV/manifest) + pg_dump + mc mirror MinIO → Proxmox 2.
7. NỢ mang từ bản Docker sang (giải trong lúc rebuild, không dựng lại nền)
- Bảo mật (nguy hiểm nhất): creds plaintext (
dbiz1234, POSTHOG_KEY, JWT trong YAML,polaris-secret-dev) → Sealed Secrets. Auth thật cho Trino/Superset/OM/MinIO. - Git-sync DAG: Airflow DAG đang tạo tay → version control + git-sync (giải khi lên Airflow K8s Phase 5).
- Backup: MinIO + Postgres metadata chưa có backup → Velero + pg_dump + mc mirror.
- Monitoring/alert: Prometheus/Grafana đã dựng, chưa gắn alert DAG fail / data trễ.
- Retention Iceberg: expire snapshots + compaction định kỳ.
- Checkpoint Spark: bản Docker để local FS (không migrate được) → K8s đặt PVC Longhorn/MinIO ngay từ đầu; cắt over = snapshot lại.
- Dọn rác: schema ghép cũ
staging_posthog_staging_posthog, bảng*__dbt_backup— dọn bằng Spark PURGE.
8. Việc phải làm trước khi apply root lake stack (checklist)
- Điền IP MinIO
.37vàoexternal-services.yaml(đã có sẵn trong file). - Điền
<REPO_URL>= Renolation, commit + push. - iscsi trên mọi node (Longhorn prereq).
- Cài Longhorn (đang làm) — StorageClass
longhornxuất hiện + PVC survive pod delete. - Kiểm các Application còn lại (strimzi/spark/kafka) — còn placeholder hay version pin tạm nào không (kiểm bản mới nhất Strimzi/Spark Operator chart, cập nhật targetRevision).
- Seal imagePullSecret registry (tên
gitea-registry) →envs/dev/sealed-secrets/. - Đổi
image.repositorycdc-job từgit.dbiz.comsang registry Renolation thật. - Dựng Polaris trên K8s (chart CHƯA có trong skeleton — thêm app riêng, Phase 3).
- cert-manager + ingress (nếu expose UI) — Phase 0 còn lại.
9. Lệnh hay dùng
# Verify cụm lành
kubectl get nodes
kubectl get pods -n kube-system | grep -E 'etcd|apiserver'
# CPU baseline mọi node
for n in $(kubectl get nodes -o name | sed 's|node/||'); do
echo -n "$n: "; ssh k8sadmin@$n "lscpu | grep -oE 'sse4_2|popcnt|ssse3|sse4_1' | tr '\n' ' '"; echo
done
# Phép thử sinh tử MinIO từ pod (image v1-safe)
kubectl run mc-test -n dbiz-lake-dev --rm -it --restart=Never \
--image=minio/mc:RELEASE.2023-11-20T16-30-59Z \
--env="MC_HOST_lake=http://dbiz:dbiz1234@192.168.110.37:9000" \
--command -- /bin/sh
# mc ls lake/dbiz-warehouse
# echo probe > /tmp/p.txt; mc cp /tmp/p.txt lake/dbiz-warehouse/_k8s_probe/p.txt
# mc cat lake/dbiz-warehouse/_k8s_probe/p.txt; mc rm lake/dbiz-warehouse/_k8s_probe/p.txt
# StorageClass / Longhorn
kubectl get storageclass
kubectl get pods -n longhorn-system
# Đổi REPO_URL toàn repo (nếu chuyển registry/repo)
grep -rl '<REPO_URL>' . | xargs sed -i 's#<REPO_URL>#<url-mới>#g'
# Bootstrap app-of-apps (ArgoCD đọc remote → push trước!)
kubectl apply -f envs/dev/apps/root.yaml
10. Lưu ý cho session sau
- Luôn push git trước khi apply/sync ArgoCD.
- Không đập cái đang chạy: CDC Docker cũ giữ chạy tới khi CDC K8s verify xong (Phase 4).
- MinIO không bao giờ đụng — điểm neo dữ liệu.
- Mỗi phase verify kỹ mới sang. "Di toàn bộ" = nhiều tuần.
- Version Helm chart (Strimzi/Spark Operator) đang pin TẠM — kiểm bản mới khi dựng thật.
- Khi tạo file/manifest cho repo: đưa NỘI DUNG ĐẦY ĐỦ + lệnh git + lệnh verify. GitOps thuần, không helm install tay.
- Credential dev đang là
dbiz/dbiz1234khắp nơi — đừng đưa vào git dạng plaintext, dùng Sealed Secrets.