Files
dbiz-lake-k8s/CLAUDE.md
2026-07-08 15:48:18 +07:00

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ê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=5Gilý do Longhorn phải xong TRƯỚC khi apply root lake
  • image.repository: git.dbiz.com/dbiz/spark-cdcCẦ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)

  • Điền IP MinIO .37 vào external-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 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

# 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.