243 lines
18 KiB
Markdown
243 lines
18 KiB
Markdown
# 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/<env>/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êm `longhorn`.
|
|
- Namespace convention **lakehouse**: `dbiz-lake-dev`, `dbiz-lake-prod`.
|
|
- (Phân biệt: `dbiz-dev/staging/prod` + `monitoring` là namespace của app **platform** khác, KHÔNG phải lake.)
|
|
|
|
### 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`.** Bucket `dbiz-warehouse`. Key `dbiz`/`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ác `staging_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.
|
|
- 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-cdc` trong 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. **1 cụm K8s, tách namespace dev/prod.** Helm + values per-env. KHÔNG Kustomize.
|
|
2. **GitOps đầy đủ từ đầu:** ArgoCD (app-of-apps) + Woodpecker CI + Sealed Secrets + cert-manager.
|
|
3. **Secret = Sealed Secrets** (mã hoá, commit an toàn vào git). Giải nợ credential plaintext.
|
|
4. **MinIO GIỮ NGUYÊN ngoài K8s.** Không migrate data lake.
|
|
5. **Longhorn** cho block PV nội cụm (Postgres/Kafka/ES/Spark checkpoint). Không ôm data lake → nhu cầu nhỏ.
|
|
6. **Kafka: Strimzi dựng mới trên K8s** (không xài ké Docker cũ). Snapshot CDC lại.
|
|
7. **Spark: Spark Operator** (SparkApplication CRD), image bundled (jar Iceberg/Kafka sẵn trong image).
|
|
8. **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).
|
|
9. **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).
|
|
10. **"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).
|
|
11. **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-path` làm default StorageClass** — Longhorn là StorageClass tường minh, chỉ workload chỉ định `storageClass: longhorn` mớ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ới `root.yaml` (app-of-apps nền) + `00-longhorn.yaml`. Tách khỏi `envs/<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_warehouse`
|
|
- `s3.endpoint: http://minio-lake:9000` (Service nội cụm trỏ ra .37), `region: us-east-1`
|
|
- `checkpoint: storageClass=longhorn, size=5Gi` ← **lý do Longhorn phải xong TRƯỚC khi apply root lake**
|
|
- `image.repository: git.dbiz.com/dbiz/spark-cdc` ← **CẦN đổi sang registry Renolation thật**
|
|
- `imagePullSecret: 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 ls` bucket ✅ đã 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)
|
|
|
|
1. **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: Proxmox `VM→Hardware→Processors→Type` = `host` (perf tối đa) hoặc `x86-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: pin `minio/mc:RELEASE.2023-11-20T16-30-59Z` (chạy trên v1).
|
|
2. **kubeconfig trỏ VIP `.14:8443`, không phải `127.0.0.1:6443`.** `curl 127.0.0.1:6443/healthz` = ok chỉ chứng minh apiserver LOCAL sống; nếu `kubectl` (qua .14) báo `Unable to connect to the server: EOF` thì VIP/LB đang fail-over. Xảy ra khi power-cycle VM master.
|
|
3. **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.)
|
|
4. **Longhorn prerequisite:** `open-iscsi` + `iscsid` active + module `iscsi_tcp` trê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 → volume `Pending`/`faulted`.
|
|
5. **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.
|
|
6. **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.
|
|
7. **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.
|
|
8. **Iceberg từ chối `timestamp(3)` → phải `timestamp(6)`.**
|
|
9. **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.
|
|
10. **OpenMetadata version lock** (OM ↔ ES ↔ Airflow-trong-ingestion). ES phải khớp client (OM 1.13.1 → ES 9.3.0). Restart `openmetadata_server` sau 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.
|
|
11. **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)
|
|
|
|
- [x] Điền IP MinIO `.37` vào `external-services.yaml` (đã có sẵn trong file).
|
|
- [x] Điền `<REPO_URL>` = Renolation, commit + push.
|
|
- [x] iscsi trên mọi node (Longhorn prereq).
|
|
- [ ] **Cài Longhorn (đang làm)** — StorageClass `longhorn` xuấ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.repository` cdc-job từ `git.dbiz.com` sang 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
|
|
|
|
```bash
|
|
# 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`/`dbiz1234` khắp nơi — đừng đưa vào git dạng plaintext, dùng Sealed Secrets. |