Files

720 lines
39 KiB
Markdown
Raw Permalink Normal View History

2026-10-09 02:35:14 +07:00
# eBdesk SiCABE — `deploy` (Installer Hasil Merge Eksperimen)
**SiCABE** (Solusi Integrasi Cerdas Adaptif Big Data Enterprise) adalah platform
data modern 5-lapisan (lakehouse) untuk analisis harga dan logistik kebutuhan
pokok. Folder `deploy/` adalah **installer hasil merge** dari:
- `deploy-legacy/` — base installer lama (yang sudah berjalan baik),
- `experiments/nessie-dremio/` — **Nessie** (ganti Polaris) + **OPA** + **Dremio**,
- `experiments/starrocks-minio/` — StarRocks **shared-data** (FE + Compute Node),
- `experiments/starrocks-oidc/` — **LDAP group provider**.
> `deploy-legacy/` (base lama) **tidak diubah** — tetap menjadi referensi/rollback.
> Dokumen rencana & mapping: [`../reviews/sicabe-07-merge-experiments-plan.md`](../reviews/sicabe-07-merge-experiments-plan.md).
## Arsitektur
```
L1 INGESTION Airflow (base) ── dbt/Sling ──► Trino
L2 STORAGE MinIO (base) + Nessie (JDBC2/PostgreSQL) + OpenLDAP (identitas)
L3 PROCESSING Trino 483 ──► Nessie catalog; authz Trino ──► OPA
L4 SERVING StarRocks shared-data (FE + CN) | Dremio OSS 25.2.0
L5 GOVERNANCE Keycloak (user lokal) · OpenMetadata · Grafana
IDENTITY OpenLDAP (sumber grup untuk Trino & StarRocks)
DATABASE PostgreSQL shared: keycloak | nessie | airflow | openmetadata_db
```
### Dependency Graph (urutan deploy)
```
Phase 1: Keycloak + OpenLDAP ──────── (tidak ada dependensi)
Phase 2: MinIO ◄──── Nessie ───────── (MinIO dulu, baru Nessie)
Phase 3: Trino ◄──── Nessie + OPA ─── (katalog Nessie, authz OPA)
Phase 4: StarRocks FE + CN (+ Dremio opt-in) ── (shared-data; engine paralel)
Phase 5: Airflow ◄──── Postgres ───── (PostgreSQL sebagai backend DB)
Phase 6: OpenMetadata ◄──── Grafana ─
```
### Matriks Install (Fase × Layer)
**A — Service yang baru diinstall per fase:**
| Fase | Database | Identity | L1 Ingestion | L2 Storage & Catalog | L3 Processing | L4 Serving | L5 Governance |
|---|---|---|---|---|---|---|---|
| **Phase 1** | `postgres` | `openldap` | — | — | — | — | `keycloak` |
| **Phase 2** | — | — | — | `minio`, `minio-init`*, `nessie` | — | — | — |
| **Phase 3** | — | — | — | — | `opa`, `trino` | — | — |
| **Phase 4** | — | — | — | — | — | `starrocks-fe`, `starrocks-cn` | — |
| **Phase 5** | — | — | `airflow-init`, `airflow-webserver`, `airflow-dag-processor`, `airflow-scheduler` | — | — | — | — |
| **Phase 6** | — | — | — | — | — | — | `openmetadata-opensearch`, `openmetadata-migrate`*, `openmetadata-server`, `grafana` |
`*` one-shot (selesai lalu exit): `minio-init` (buat bucket), `openmetadata-migrate` (migrasi schema OM).
**B — Kondisi aktif kumulatif setelah fase N (full deploy `./install.sh`):**
| Fase | Database | Identity | L1 Ingestion | L2 Storage & Catalog | L3 Processing | L4 Serving | L5 Governance |
|---|---|---|---|---|---|---|---|
| **1** | `postgres` | `openldap` | — | — | — | — | `keycloak` |
| **2** | `postgres` | `openldap` | — | `minio`, `nessie` | — | — | `keycloak` |
| **3** | `postgres` | `openldap` | — | `minio`, `nessie` | `opa`, `trino` | — | `keycloak` |
| **4** | `postgres` | `openldap` | — | `minio`, `nessie` | `opa`, `trino` | `starrocks-fe`, `starrocks-cn` | `keycloak` |
| **5** | `postgres` | `openldap` | `airflow-*` | `minio`, `nessie` | `opa`, `trino` | `starrocks-*` | `keycloak` |
| **6** | `postgres` | `openldap` | `airflow-*` | `minio`, `nessie` | `opa`, `trino` | `starrocks-*` | `keycloak`, `openmetadata-*`, `grafana` |
> `./install.sh --phase N` menjalankan profil `phaseN`. Phase 1–4 kumulatif; phase 5–6 lebih sempit
> (disarankan full deploy berurutan). `bootstrap.sh` (9 step) dijalankan **setelah** install.
>
> **Dremio bersifat opt-in**: tidak ikut `--profile all` / `phaseN` (installer-nya
> menolak heap < 4 GB sehingga boros RAM di dev). Aktifkan dengan
> `docker compose --profile dremio up -d dremio`. `bootstrap.sh` step 9 akan
> dilewati bila container Dremio tidak berjalan.
## Stack
| Layer | Komponen | Versi | Catatan |
|-------|----------|-------|---------|
| Database | PostgreSQL | 15 | 1 instance, DB terpisah: keycloak/nessie/airflow/openmetadata_db |
| L2 Catalog | **Project Nessie** | 0.108.4-java | JDBC2 di PostgreSQL; Iceberg REST `/iceberg/` |
| L2 Storage | MinIO | RELEASE.2025-09-07 | Bucket `iceberg-data` (warehouse + StarRocks) |
| L3 Processing | Trino | 483 | Katalog `iceberg` via Nessie |
| L3 Authz | **OPA** | latest | Deny-by-default; policy Rego + ACL `config/opa/data.json` |
| L4 Serving | StarRocks | 4.0.13 | **shared-data** (FE + CN) |
| L4 Serving | **Dremio OSS** *(opt-in)* | 25.2.0 | Engine paralel; auth lokal |
| L5 IAM | Keycloak | 26.7 | OIDC; user lokal |
| Identity | **OpenLDAP** | 1.5.0 | Sumber grup (`data-admin`, dst.) |
| L1 Ingestion | Airflow | **3.3.2** | Sling → Nessie Iceberg REST; **image ber-plugin OpenMetadata** (`/pluginsv2/`); **provider MySQL** (koneksi ke server MySQL eksternal) |
| L5 Metadata | OpenMetadata | **2.0.3** | Konektor Trino + Airflow; jalur DQ = Great Expectations (§8.9) |
| L5 Search | OpenSearch | **3.8.0** | Backend pencarian OM 2.0.x (security plugin dimatikan) |
| L5 Observability | Grafana | 13.1 | Provisioning file |
### Kebutuhan CPU (AVX2)
Hanya **satu** komponen di stack ini yang mensyaratkan **AVX2**: **StarRocks CN (BE)**. Build
resmi StarRocks memakai `USE_AVX2=ON`, dan CPU tanpa AVX2 membuat container-nya **gagal start**
dengan `SIGILL` (illegal instruction) — bukan sekadar lambat, dan saat ini **tidak ada paket
resmi non-AVX2**. StarRocks **FE** tidak terpengaruh (berbasis JVM). **AVX-512 tidak wajib** di
komponen mana pun.
Komponen lain berjalan di x86-64 biasa: Trino (hanya butuh 64-bit Linux + Java 25), OpenSearch
(kita hanya memakai pencarian teks/BM25 — AVX2/AVX-512 hanya relevan untuk plugin k-NN yang
tidak dipakai), OpenMetadata/Keycloak/Nessie (JDK 21), Airflow (CPython 3.11), serta
MinIO/PostgreSQL/Grafana/Prometheus. Dremio opt-in tidak mendokumentasikan syarat ISA.
Periksa CPU calon server **sebelum** deploy:
```bash
grep -o -m1 avx2 /proc/cpuinfo # satu-satunya gate nyata di stack ini
lscpu | grep -o -E 'avx2|avx512[a-z]*|sse4_2|bmi2|popcnt' | sort -u
```
Tanpa AVX2, opsinya: lewati Fase 4 (StarRocks) dan pakai Dremio sebagai engine serving, atau
kompilasi StarRocks sendiri dengan `build.sh --without-avx2`. Catatan arsitektur: **ppc64le**
hanya didukung Trino, PostgreSQL, Prometheus, dan Airflow — komponen lain butuh x86-64 atau ARM64.
## Peta Port (prefix 12xxx)
Aturan: **host port = `12` + 3 digit port kanonik**. Port internal container tidak berubah.
| Service | Internal | Host |
|---|---|---|
| Grafana | 3000 | **12300** |
| Dremio Flight | 32010 | **12011** |
| Dremio JDBC | 31010 | **12010** |
| StarRocks FE HTTP | 8030 | **12030** |
| Keycloak | 8080 | **12080** |
| Airflow | 8080 | **12082** |
| OpenMetadata | 8585 | **12085** |
| Nessie | 19120 | **12120** |
| OPA | 8181 | **12181** |
| Trino HTTPS | 8443 | **12443** |
| OpenSearch | 9200 | **12920** |
| MinIO API | 9000 | **12900** |
| MinIO Console | 9001 | **12901** |
| StarRocks FE MySQL | 9030 | **12930** |
| Dremio UI | 9047 | **12947** |
| OpenLDAP | 389 | **12389** |
## Prasyarat
| Tool | Minimal | Catatan |
|------|---------|---------|
| Docker Engine | 24.x+ | `docker --version` |
| Docker Compose | v2.x+ | `docker compose version` |
| OpenSSL | 1.1+ | generate password `.env` |
| curl | 7.x+ | bootstrap & test |
| python3 | 3.8+ | parsing JSON |
| MinIO Client (mc) | latest | opsional |
| MySQL Client | 8.x+ | opsional (StarRocks) |
## Quick Start
```bash
cd deploy
chmod +x install.sh bootstrap.sh test-verification.sh
# 1. Deploy 6 fase (~15 menit)
./install.sh
# 2. Bootstrap & integrasi (Keycloak, OpenLDAP, MinIO, StarRocks, Airflow, OM)
./bootstrap.sh
# 3. Verifikasi
./scripts/verify-phase1-iam.sh # gate kontrak IAM (Phase 1) — sebelum lanjut fase berikutnya
./scripts/verify-phase2-storage.sh # gate kontrak storage & catalog (Phase 2)
./scripts/verify-phase3-processing.sh # gate kontrak processing: Trino + OPA (Phase 3)
./scripts/verify-phase4-serving.sh # gate kontrak serving: StarRocks shared-data (Phase 4)
./scripts/verify-phase5-ingestion.sh # gate kontrak ingestion: Airflow + connections (Phase 5)
./scripts/verify-phase6-governance.sh # gate kontrak governance: OpenMetadata + Grafana (Phase 6)
# 3b. Setelah restart StarRocks FE — pulihkan dynamic config (auth chain OIDC)
./scripts/starrocks-reapply-dynamic-config.sh
# 3c. Isi katalog OpenMetadata (agent ingestion) — tanpa ini halaman Explore kosong
./scripts/ingest-openmetadata.sh
./test-verification.sh
./test-verification.sh --layer L2 # MinIO + Nessie
./test-verification.sh --layer E2E # End-to-end smoke test
# 3d. ALUR DATA (urutan ini penting)
# Ingest DULU, baru DAG:
./scripts/ingest-openmetadata.sh --all # buat entitas tabel di katalog OM
docker exec sicabe-airflow-webserver airflow dags test sicabe_batch_daily
#
# Mengapa urutannya begitu: action GE (OpenMetadataValidationAction) menempelkan
# test suite/test case ke ENTITAS TABEL di OM. Sebelum ingest, entitas itu belum ada,
# sehingga hasil Great Expectations tidak muncul di OM walau tugas DAG-nya sukses.
# Catatan: bagian LINEAGE dari --all membaca artefak dbt (target/manifest.json) yang
# baru ada SETELAH DAG berjalan — untuk lineage utuh jalankan `--dbt-only` sekali lagi
# setelah DAG, atau ulangi `--all`.
#
# 4. (Opsional) Seed tabel contoh Iceberg — namespace bronze + bronze.orders
./scripts/seed-sample-data.sh
# 5. (Opsional) Seed SUMBER PostgreSQL untuk DAG — sicabe_data.public.orders
# WAJIB bila ingin menjalankan DAG 'sicabe_batch_daily' (task ingest_to_bronze)
./scripts/seed-source-postgres.sh
```
> Memakai komponen yang **sudah ada** (Keycloak/LDAP/MinIO/Nessie/Trino/OPA, atau Postgres
> milik DBA)? Arahkan endpoint-nya di `.env` — `KEYCLOAK_URL`, `LDAP_URL`, `S3_ENDPOINT`,
> `NESSIE_URL`, `TRINO_URL`, `OPA_URL` (atau `EXTERNAL_SERVICES="postgres ..."`). `install.sh`
**Menempatkan komponen kita di host lain (multi-host).** Beda dari komponen EXISTING:
di sini komponen tetap milik klaster ini, hanya dipindah host. Override variabel
`*_HOST` di `.env` dengan alamat host pemiliknya — default-nya nama container, sehingga
**single-host tidak berubah**:
| Variabel | Default | Untuk |
|---|---|---|
| `POSTGRES_HOST` | `postgres` | Keycloak, Nessie, Airflow |
| `MINIO_HOST` | `minio` | Nessie, minio-init |
| `KEYCLOAK_HOST` | `keycloak` | Keycloak backend URL, Grafana OAuth |
| `OPENMETADATA_HOST` | `openmetadata-server` | `OM_API_URL`, `SERVER_HOST_API_URL` |
| `AIRFLOW_WEBSERVER_HOST` | `airflow-webserver` | `AIRFLOW__WEBSERVER__BASE_URL`, pipeline client |
| `STARROCKS_FE_HOST` / `STARROCKS_CN_HOST` | `starrocks-fe` / `starrocks-cn` | registrasi CN, healthcheck |
| `OPENSEARCH_HOST` | `openmetadata-opensearch` | `ELASTICSEARCH_HOST` OpenMetadata |
> File `config/**` yang dibaca **container** (`.properties`/`.yaml`/`.cfg`/`.json`/`.rego`)
> tidak bisa ekspansi env. Untuk multi-host jalankan `scripts/render-config.sh` yang
> menyalinnya ke `generated/host-<x>/config/` sambil mengganti hostname terdaftar:
>
> ```bash
> ./scripts/render-config.sh --out generated/host-compute/config --dry-run \
> --map postgres=10.20.0.12 --map nessie=10.20.0.10
> ```
>
> Penggantian hanya pada konteks jaringan (setelah `//`, setelah `@`, atau `host:port`)
> sehingga path filesystem seperti `/etc/trino/` dan baris komentar tidak ikut berubah.
> Rencana lengkap: `reviews/sicabe-10-multi-host-installer-plan.md`.
2026-10-11 13:59:47 +07:00
> ⚠️ **Satu host = satu stack aktif.** Stack single-host dan multi-host **tidak untuk
> berjalan bersamaan** di host yang sama — **matikan stack lama lebih dulu** sebelum
> menyalakan hasil `generated/host-<x>/`. Berdampingan **tidak cukup** hanya dipisah
> network/port: pada host kecil hal itu juga menabrak RAM (pilot 15 GB, satu stack penuh
> ±10 GB).
2026-10-09 02:35:14 +07:00
**Trino dipisah coordinator/worker.** Sejak 2026-09-28 Trino bukan lagi satu service:
`trino-coordinator` (port host `12443`, HTTPS + OAuth2/Basic, client-facing) dan
`trino-worker` (tanpa port host; mengeksekusi task, `discovery.uri` ke coordinator).
Klien **tidak berubah** (`TRINO_URL` tetap `https://localhost:12443`). Untuk restart:
`./install.sh --restart trino-coordinator` (atau `trino-worker`). Perubahan **nama
service** mewajibkan regenerasi sertifikat: `./scripts/generate-trino-keystore.sh`
(SAN memuat `trino-coordinator`), lalu restart coordinator.
### Menyiapkan server baru (Ubuntu 24.04)
Dua script, dijalankan **di tiap server** sebelum instalasi:
```bash
sudo ./scripts/prepare-host.sh --dry-run # lihat rencananya dulu (tidak mengubah apa pun)
sudo ./scripts/prepare-host.sh # paket apt + Docker + sysctl + precheck
./scripts/prefetch-images.sh # tarik semua image lebih dulu
```
**`prepare-host.sh`** memasang paket yang benar-benar dipakai script deploy
(`htpasswd`/apache2-utils, `ldapsearch`/ldap-utils, klien `mysql`, `openssl`, `curl`,
`python3`+PyYAML), memasang Docker Engine + compose v2 (lewati dengan `--no-docker`),
menetapkan `vm.max_map_count=262144` (**syarat keras OpenSearch**), dan memeriksa
**AVX2** (wajib `starrocks-cn`), jam/NTP, RAM, disk, serta port `12xxx` yang akan dipakai.
Flag `--swap-off` (opsional) mematikan swap sesuai rekomendasi OpenSearch.
**`prefetch-images.sh`** mengambil daftar image dari `docker compose config --images`
(**tidak** di-hardcode, jadi selalu sinkron dengan compose). Default: **14 image ≈ 6,2 GB**
(terukur). Pilihan: `--all` (default) · `--host <nama>` (hanya image host itu, dari
`generated/host-<x>/`) · `--list` (lihat saja).
Tiga image **dikecualikan secara default** dan ditampilkan sebagai catatan
("dikecualikan: …") supaya tidak terkesan terlupa:
| Dikecualikan | Alasan | Mengaktifkan |
|---|---|---|
| `dremio/dremio-oss:25.2.0` | Dremio **opt-in** di compose (default nonaktif) | `--with-dremio` |
| `apache/airflow:slim-3.3.2-python3.11` | hanya base untuk build lokal Airflow | `--with-base` |
| `sicabe/airflow:3.3.2-om203-ol` | **dibangun lokal**; sejak 2026-10-02 **juga tersedia di registry** | `docker pull` dari registry (di bawah), atau build-sekali-salin |
> 📌 **Sejak 2026-10-02 image ini tersedia di registry internal** — jadi host lain bisa
> **menariknya**, tidak harus membangun:
> ```bash
> docker pull registry.sicabe.com/airflow:3.3.2-om203-ol
> # opsi A (disarankan): arahkan compose ke nama registry — tanpa retag
> export AIRFLOW_IMAGE=registry.sicabe.com/airflow:3.3.2-om203-ol
> # opsi B: beri nama yang diharapkan compose
> docker tag registry.sicabe.com/airflow:3.3.2-om203-ol sicabe/airflow:3.3.2-om203-ol
> ```
> ⚠️ `docker-compose.yml` memakai `${AIRFLOW_IMAGE:-sicabe/airflow:3.3.2-om203-ol}` —
> jadi setelah `pull`, **pilih salah satu** dari dua opsi di atas; kalau tidak, compose
> akan mencari tag `sicabe/...` yang belum ada di host itu.
> ⚠️ **`sicabe/airflow:3.3.2-om203-ol` dibangun lokal** (base `apache/airflow:slim-3.3.2-python3.11`),
> jadi **tidak tersedia di Docker Hub**. ✅ Sejak 2026-10-02 fetch bisa lewat registry
> internal (lihat blok di atas); alur build-sekali-salin di bawah tetap tersedia untuk
> host yang **tidak punya akses registry**:
> ```bash
> # di SATU host
> docker build -t sicabe/airflow:3.3.2-om203-ol -f docker/airflow/Dockerfile.332 docker/airflow
> ```
> 📌 Sejak BATCH B (review R18, 2026-10-01) varian image **berdampingan**:
> `Dockerfile.332` (+ `requirements.332.{in,lock}`) = Airflow 3.3.2 / Python 3.11 —
> **yang dipakai sekarang**; `Dockerfile.slim` (+ `requirements.{in,lock}`) tetap
> disimpan sebagai jalur rollback ke Airflow 3.3.1.
> 📌 **2026-10-02 — adopsi opsi C**: tag **`-ol`** = image di atas **+ provider
> OpenLineage + shim `eventTime`**. OpenLineage **AKTIF** mengirim lineage task
> Airflow ke OpenMetadata (rute langsung, tanpa Marquez).
>
> - **Transport dibaca dari KONEKSI Airflow** `openlineage_config` (bukan `.env`),
> sehingga token **tidak** tersebar di berkas. `./bootstrap.sh` membuatnya
> bersama `openmetadata_lineage_api` (password = token bot `lineage-bot`,
> `JWTTokenExpiry=Unlimited`).
> - Aktif/mati: `AIRFLOW_OPENLINEAGE_CONFIG_CONN_ID` (default `openlineage_config`;
> kosongkan untuk mematikan). ⚠️ Koneksinya **harus sudah ada** — pemuatan
> konfigurasi provider tidak dibungkus try/except (bisa menggagalkan task).
> - ⚠️ `AIRFLOW__OPENLINEAGE__EXECUTE_IN_THREAD=true` **wajib**; tanpa itu proses
> emisi HANG lalu dimatikan dan tidak ada event yang keluar.
> - ⚠️ Shim `ol_time_shim.ZuluHttpTransport` **wajib** untuk OM 2.0.3: provider
> memakai `eventTime` berakhiran `+00:00`, sedangkan OM hanya menerima `Z`
> (tanpa shim → `400 Invalid request format`).
> - Lineage **tabel + kolom** muncul untuk task yang memakai **operator SQL**
> (mis. `SQLExecuteQueryOperator`). Task dbt/Sling via `BashOperator` tidak
> menghasilkan lineage dari jalur ini — dbt tetap dari ingestion dbt.
> - ⚠️ OM 2.0.3 mencatat error berulang `null value in column "updatedat"`
> saat mencoba auto-create entitas pipeline (bug OM; tidak berbahaya).
> - Rincian: `experiments/lineage-airflow-om/README.md`.
> Rollback: `sicabe/airflow:3.3.2-om203` (tanpa OpenLineage).
> ./scripts/prefetch-images.sh --save-airflow /tmp/airflow-image.tar # ±835 MB
> # di host lain
> scp /tmp/airflow-image.tar <host>:/tmp/
> ./scripts/prefetch-images.sh --load-airflow /tmp/airflow-image.tar
> ```
### Menempatkan komponen ke beberapa host (Fase 1)
```bash
cp cluster.example.yml cluster.yml # sesuaikan address tiap host
./install.sh --generate # → generated/host-<x>/ (gitignored)
./install.sh --generate --blueprint b5 # preset 5 host (blueprint B)
./install.sh --sync # salin deploy/ (termasuk .env) ke host lain (dari control)
./install.sh --sync --host state --dry-run # lihat saja (tanpa menyalin)
./install.sh --host compute --dry-run # lihat perintahnya tanpa menjalankan
```
**Distribusi ke host lain (`--sync`).** Dijalankan di **host control** (master installer).
Menyalin seluruh `deploy/` — **termasuk `.env` dan `generated/`** — ke tiap host lain di
`cluster.yml` via `rsync` over SSH:
```bash
./install.sh --sync # ke SEMUA host lain (host ini dideteksi dari alamat lokal)
./install.sh --sync --host state # hanya satu host
2026-10-11 13:59:47 +07:00
./install.sh --sync --user admin --dest '~/lain/deploy' # SSH user & tujuan (timpa default)
2026-10-09 02:35:14 +07:00
./install.sh --sync --delete # hapus di tujuan berkas yang tak ada di sumber
```
2026-10-11 13:59:47 +07:00
**Tujuan default = cermin lokasi repo ini.** Nilai `--dest` diturunkan dari letak `install.sh`
di bawah `$HOME` (`sicabe_default_dest`), jadi **nama repo tidak dipatok**:
| Repo di host control | Tujuan `--sync` default di host lain |
|---|---|
| `~/sicabe-doc/deploy` | `~/sicabe-doc/deploy` |
| `~/sicabe-core/deploy` | `~/sicabe-core/deploy` |
Ini membuat **repo distribusi bersih** (mis. `sicabe-core` — hanya folder `deploy/`, tanpa
`docs/`, `reviews/`, `experiments/`) bekerja **tanpa `--dest`**. `deploy/` **self-contained**:
semua script memakai `SCRIPT_DIR`, dan bind-mount compose relatif (`./config/…`) — jadi nama
atau lokasi repo tidak berpengaruh. Timpa tujuan dengan `--dest '~/…/deploy'` bila perlu.
2026-10-09 02:35:14 +07:00
Host yang sedang dijalankan **dikecualikan otomatis** (kecocokan `hostname -I` dengan
`address` di inventory); bila tak cocok, ia menyinkron ke semua host. `.env` **wajib ikut**
(rahasia bersama — OIDC client secret harus identik lintas host). Setelah sync, di tiap host
jalankan `./scripts/prefetch-images.sh --host <nama>` lalu `./install.sh --host <nama>`.
2026-10-11 13:59:47 +07:00
> **Model distribusi: control = sumber tunggal · host lain = PENERIMA (bukan working tree git).**
> `--sync` menimpa berkas yang di-track git di `deploy/` host tujuan dengan versi control,
> sehingga `git status` di sana **selalu kotor** dan `git pull` akan **ditolak** ("local changes
> would be overwritten"). **Jangan `git pull` di host tujuan** — perbarui di **control** lalu
> `--sync` saja (clone repo di host tujuan cukup **sekali**, untuk `prepare-host.sh`).
> Gunakan **`--sync --delete`** agar berkas yang dihapus/di-rename di repo ikut terhapus di
> tujuan (`.git` & `output` dikecualikan, jadi keduanya aman).
2026-10-09 02:35:14 +07:00
Tanpa `cluster.yml`, installer berperilaku **persis** seperti single-host (tidak ada
yang di-generate). Hasil generate berisi per host: `docker-compose.yml` (hanya service
milik host itu), `.env` (endpoint → alamat host), dan `config/` ter-render.
Uji: `./scripts/test-inventory.sh` (validasi inventory) · `./scripts/test-generate.sh`
(semantik hasil generate vs single-host). Rencana lengkap:
`reviews/sicabe-10-multi-host-installer-plan.md`.
**Endpoint per host di skrip (multi-host).** Skrip gate/verifikasi memakai helper
`sicabe_load_env`/`sicabe_local_url`/`sicabe_local_host`, jadi jalankan dengan env host agar
endpoint menunjuk **ALAMAT HOST PEMILIK** (bukan `localhost`):
```bash
./scripts/verify-phase3-processing.sh --env-file generated/host-compute/.env
./scripts/verify-phase6-governance.sh --env-file generated/host-control/.env
./install.sh --status --host compute # status + endpoint host compute
```
`--env-file` juga dihormati lewat env `SICABE_ENV_FILE` — dipakai otomatis oleh
`bootstrap.sh --host`, sehingga sub-script (`seed-*`/`ingest-*`/`reapply`) mewarisi env host
dan tidak lagi menimpanya dengan `deploy/.env`. **Single-host tidak berubah** (tetap
`localhost`) tanpa opsi ini.
> **Skrip host-scoped (multi-host).** Skrip yang `docker exec` container harus dijalankan **DI
> host pemilik container** — mis. `seed-source-postgres.sh` di host **`state`**,
> `seed-starrocks.sh` di **`compute`**, `seed-native-dq.sh` di **`control`**. Bila dijalankan
> di host lain, skrip keluar rapi (kode **3**) + instruksi, bukan error; `bootstrap.sh`
> menampilkannya sebagai WARN. Gate (`verify-phase*`) otomatis **SKIP** cek container milik
> host lain (bukan FAIL).
>
> ```bash
> # di host state:
> ./scripts/seed-source-postgres.sh --env-file generated/host-state/.env
> # di host compute:
> ./scripts/seed-starrocks.sh --env-file generated/host-compute/.env
> ```
> **TLS Trino lintas-host.** Saat `--generate`, keystore + cert Trino dibangkitkan **sekali**
> dengan **SAN alamat IP tiap host**, lalu **disalin identik ke tiap bundle** (keypair harus
> sama lintas host). Jadi klien yang terhubung via IP (mis. ingestion **OpenMetadata**) tetap
> **memverifikasi TLS** — tak perlu `verify: false`. **`DBT_TRINO_HOST`** juga di-set ke IP
> pemilik Trino sehingga dbt (di Airflow, host lain) menyambung lintas-host. Ubah alamat host
> → `./install.sh --generate` → `--sync` → recreate `trino-coordinator` **dan** service Airflow.
> ⚠️ **Setelah mengubah file yang di-MOUNT (cert/config), container WAJIB di-restart manual.**
> `./install.sh --host <x>` menjalankan `docker compose up -d`, yang **hanya** recreate bila
> *definisi* compose berubah — **bukan** bila isi file bind-mount berubah. Contoh: regenerasi
> cert Trino → `docker restart sicabe-trino-coordinator` (di host Trino) dan service Airflow
> (di host control). Cek cepat: fingerprint cert yang **disajikan** vs yang **dipakai**
> container harus sama:
> ```bash
> # disajikan (dari host lain): echo | openssl s_client -connect <ip-trino>:8443 -servername trino 2>/dev/null | openssl x509 -noout -fingerprint -sha256
> # dipakai (di container airflow):
> docker exec sicabe-airflow-webserver openssl x509 -in /etc/ssl/trino/trino.crt -noout -fingerprint -sha256
> ```
> **Endpoint komponen SE-HOST memakai nama service.** `.env` bundle kini menulis `*_HOST`
> komponen **se-host** sebagai **nama service** (mis. `OPENMETADATA_HOST=openmetadata-server`)
> dan komponen **beda host** sebagai **alamat IP**. Sebab port container (mis. OM 8585) tidak
> diterbitkan di host; memakai IP host justru "connection refused" (regresi OpenLineage).
> ⚠️ **`./install.sh --host <nama>` dijalankan DI host itu**, dan hanya setelah stack
> lama dimatikan bila host-nya sama. `depends_on` lintas host sudah dibuang generator —
> penggantinya `preflight` yang **belum ada** (Fase 2), jadi urutan start antar host
> masih manual.
> dan `test-verification.sh` akan **melewati container** komponen tersebut, dan `bootstrap.sh`
> beserta gate fase memakai endpoint yang sama. Detail: `docs/sicabe-install-phases-detail.md`
> §2.8–§2.12.
>
> `scripts/seed-sample-data.sh` (idempoten) membuat namespace + tabel contoh
> `iceberg.bronze.orders` (6 baris) agar catalog tidak kosong dan langsung
> terlihat di Trino, Dremio, dan StarRocks.
>
> `scripts/seed-source-postgres.sh` (idempoten) menyiapkan **sumber** di
> PostgreSQL: database `sicabe_data` + tabel `public.orders` (6 baris). Ini yang
> dibaca task `ingest_to_bronze` (Sling: `POSTGRES_DB.public.orders` →
> `ICEBERG bronze.orders`). Tanpa ini DAG `sicabe_batch_daily` gagal dengan
> pesan database `sicabe_data` tidak ditemukan.
> **Diagram alur instalasi** — `install.sh` (6 fase) → `bootstrap.sh` (9 step) → stack siap:
> [Mermaid `sicabe-install-sequence.mmd`](../docs/diagrams/sicabe-install-sequence.mmd) ·
> [versi interaktif `.drawio`](../docs/diagrams/sicabe-install-sequence.drawio).
> Regenerasi `.drawio` dari `.mmd`:
> `bash docs/diagrams/generate-drawio-from-mermaid.sh sicabe-install-sequence.mmd`
> **Detail per fase & bootstrap step** (prasyarat/dependency, container yang
> dinyalakan, aksi **configure/seed/verify** tiap step):
> [`docs/sicabe-install-phases-detail.md`](../docs/sicabe-install-phases-detail.md)
>
> **Memakai layanan existing?** Variabel `.env` yang perlu ditambahkan:
> Keycloak/LDAP → §2.8 · MinIO/S3 & Nessie → §2.9 pada dokumen yang sama.
> Untuk **IAM existing** (dikelola pihak lain/tanpa akses admin): daftar permintaan ke
> admin → Keycloak [`docs/keycloak-existing-requirements.md`](../docs/keycloak-existing-requirements.md) ·
> OpenLDAP [`docs/openldap-existing-requirements.md`](../docs/openldap-existing-requirements.md).
Deploy per phase:
```bash
./install.sh --phase 1 # Keycloak + OpenLDAP + PostgreSQL
./install.sh --phase 2-4 # MinIO + Nessie + Trino + OPA + StarRocks FE+CN
./install.sh --status
./install.sh --destroy
# Dremio opt-in — tidak ikut phase mana pun (butuh RAM ~4 GB lebih)
docker compose --profile dremio up -d dremio
```
### URL browser-facing (issuer OIDC, redirect, UI)
Enam variabel berisi URL yang dilihat **browser**, bukan lalu lintas container:
`KEYCLOAK_PUBLIC_URL`, `GRAFANA_PUBLIC_URL`, `OPENMETADATA_PUBLIC_URL`,
`AIRFLOW_PUBLIC_URL`, `TRINO_PUBLIC_URL`, `MINIO_CONSOLE_PUBLIC_URL`.
| Kondisi | Nilai `*_PUBLIC_URL` |
|---|---|
| `tls.mode: none` (default) | `http://<alamat host yang menjalankan komponen>:<port>` — diisi otomatis oleh `install.sh --generate`, **sama di semua bundle** |
| `tls.mode: edge` / `full` | `https://<label>.<domain>` dari `cluster.domain` |
| tanpa `cluster.yml` (single-host) | `http://localhost:<port>` |
**Keycloak 26.x (hostname v2):** yang menentukan issuer, discovery, dan redirect adalah
**`KC_HOSTNAME`**, dan isinya **URL lengkap** (`http://host:port`) — diambil dari
`KEYCLOAK_PUBLIC_URL`. Jangan pakai `KC_HOSTNAME_URL`: opsi hostname v1 itu **dihapus di
Keycloak 26.0.0** sehingga tidak berefek; `KC_HOSTNAME_BACKEND_URL` bahkan bukan opsi
Keycloak. `KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true` membuat container lain tetap bisa
memakai nama internal `keycloak:8080` untuk token/JWKS.
**Akses HTTP dari alamat non-lokal (mis. Tailscale).** `sslRequired` adalah atribut
**realm** (default Keycloak `external`) dan dinilai dari **alamat sumber koneksi**, bukan
header Host. Menurut Keycloak, "lokal" = loopback + RFC1918 (`10/8`, `172.16/12`,
`192.168/16`) + link-local + IPv6 `fc00::/7` — sehingga **`100.64.0.0/10` (CGNAT/Tailscale)
dianggap eksternal** dan admin console gagal dengan `HTTPS required`, padahal akses via
`localhost` normal. Keycloak 26 **tidak punya** opsi server-level untuk ini (tidak ada
`KC_HTTPS_REQUIRED`), jadi harus per realm: `bootstrap.sh --step 1` menyetelnya untuk
**`master` dan realm Sicabe** dari `KEYCLOAK_SSL_REQUIRED` (`none` saat `tls.mode: none`,
`external` saat ada TLS/Traefik).
**Mengubah alamat publik berarti mengubah `iss`** → semua konsumen harus ikut dan
**semua pengguna login ulang**:
- Trino: `issuer` + `auth-url` memakai `${ENV:KEYCLOAK_PUBLIC_URL}` (token/JWKS tetap
ke `keycloak:8080`) — lihat `config/trino/config-coordinator.properties`;
- redirect URI + web origin klien: disinkronkan otomatis oleh `bootstrap.sh step 1`
(digabung, idempoten — termasuk client lama yang masih localhost-only);
- Grafana (`GF_SERVER_ROOT_URL`) dan Airflow (`AIRFLOW__WEBSERVER__BASE_URL`) mengikuti
`*_PUBLIC_URL` masing-masing.
Gejala bila salah: browser dibuang ke `localhost`, atau login ditolak dengan
`Invalid parameter: redirect_uri`.
### Ingress + TLS (langkah 2 / T-20)
Traefik berjalan **hanya pada profile `edge`**, dan hanya ikut ke bundle host saat
`tls.mode` != `none`. Setiap host menjalankan Traefik-nya sendiri, dengan rute diambil dari
label container (provider docker) — jadi daftar rute = label pada layanan.
```bash
# 1. inventory: tls.mode: edge (domain: sicabe.com)
# 2. install.sh --generate && install.sh --sync -> distribusi bundle ke host lain
# 3. isi kredensial DNS di .env tiap host (contoh Cloudflare: token Zone:DNS:Edit)
# CF_DNS_API_TOKEN=...
# 4. jalankan ingress
docker compose --profile all --profile edge up -d
```
| Variabel | Arti |
|---|---|
| `CLUSTER_DOMAIN` | domain rute `Host()` (label dari `PUBLIC_URL_SERVICES`: kc/grafana/om/airflow/trino/minio) — hanya di host ingress |
| `tls.ingress_host` | (inventory, opsional) host yang menjalankan Traefik; default pemilik Keycloak |
| `TRAEFIK_BIND_ADDR` | alamat bind port 80/443 — diisi generator dengan alamat host (bukan semua antarmuka) |
| `TRAEFIK_ACME_CASERVER` | **staging** secara default; produksi: `https://acme-v02.api.letsencrypt.org/directory` |
| `CF_DNS_API_TOKEN` | kredensial DNS-01 Cloudflare |
Catatan penting:
- **Tanpa kredensial DNS, Traefik tetap start** dan memakai sertifikat self-signed bawaan,
jadi routing masih bisa diuji: `curl -k -H 'Host: kc.<domain>' https://<host>/`.
Log ACME akan menyebut variabel yang kurang secara eksplisit.
- `acme.json` **wajib mode 0600** (Traefik menolak start bila terlalu terbuka). Compose
memakai named volume, sehingga Traefik membuatnya sendiri dengan mode benar.
- **Ingress dijalankan SATU host saja** (default: host yang menjalankan Keycloak —
`control`; bisa dipatok dengan `tls.ingress_host`). Alasannya bukan hanya kesederhanaan:
apex (`sicabe.com`) dan wildcard (`*.sicabe.com`) memakai **nama TXT DNS-01 yang sama**
(`_acme-challenge.<domain>`), sehingga beberapa Traefik yang menerbitkan wildcard yang
sama akan **saling bertabrakan** — gejalanya `403 Incorrect TXT record ... (and 1 more)`.
Batas Let's Encrypt juga hanya **5 sertifikat per himpunan nama per minggu**.
- Host ingress mengambil rute komponen **lokal** dari label container, dan rute komponen di
**host lain** dari `config/traefik/dynamic.yml` (provider file) yang dibangkitkan generator —
mis. `trino` → `https://<alamat-compute>:12443`, `minio` → `http://<alamat-state>:12901`.
Traefik hanya melihat container di host-nya sendiri, jadi rute lintas host **wajib** lewat
berkas dinamis.
- Karena ingress terpusat, **A-record cukup satu wildcard** `*.sicabe.com` → alamat host
ingress (tidak perlu per label). Port 80/443 hanya perlu dibuka di host ingress.
- Label `traefik.*` tetap tertulis di semua bundle (inert tanpa Traefik) — tidak berbahaya,
tapi jangan menjalankan `--profile edge` di host non-ingress karena akan menggandakan rute.
- Trino internal memakai HTTPS (sertifikat self-signed) -> Traefik memakai `serversTransport`
dari `config/traefik/dynamic.yml` dengan `insecureSkipVerify` **hanya untuk Trino**.
`serversTransports` **tidak bisa** didefinisikan lewat label Docker (menghasilkan error
runtime `servers transport not found`).
- `tls.mode: edge` membuat generator mengisi `KC_PROXY_HEADERS=xforwarded`,
`TRINO_PROCESS_FORWARDED=true`, dan `KEYCLOAK_SSL_REQUIRED=external`. Sebaliknya pada
`none` semuanya kosong/`false` — Trino dan Keycloak **menolak** header `X-Forwarded-*`
dari klien, jadi host bisa dipalsukan tidak.
## Detail per Komponen
### Phase 1 — Keycloak + OpenLDAP
- Realm `sicabe`; OIDC clients: `trino`, `starrocks`, `airflow`, `openmetadata`, `grafana`.
- Users lokal: `admin`, `engineer`, `analyst`, `viewer`.
- **OpenLDAP** = sumber kebenaran **grup** (`data-admin`, `data-engineer`,
`data-analyst`, `data-viewer`). User tidak difederasikan — autentikasi via
Keycloak, resolusi grup via LDAP (opsi 1).
- Akses: `http://localhost:12080/admin/sicabe/console`.
### Phase 2 — Storage: MinIO + Nessie
- MinIO bucket `iceberg-data` (dipakai Nessie warehouse + StarRocks shared-data).
- **Nessie** menyimpan version store di **PostgreSQL** (`nessie` DB, schema `nessie`,
`NESSIE_VERSION_STORE_TYPE=JDBC2`). Tabel Nessie di-auto-create.
- Warehouse: `s3://iceberg-data/warehouse`; S3 MinIO path-style, kredensial server-side.
- Akses: `http://localhost:12120` (`/api/v2`, `/iceberg/`).
### Phase 3 — Processing: Trino + OPA
- Katalog `iceberg` via Nessie (`iceberg.catalog.type=nessie`).
- **Authz OPA** (`access-control.name=opa`, deny-by-default). Policy:
`config/opa/policies/trino.rego`; ACL: `config/opa/data.json`.
- **Group provider LDAP** (`group-provider.name=ldap`) → grup `data-*`.
- **Auth hybrid** (`http-server.authentication.type=oauth2,password`):
- **End user**: OAuth2 SSO Keycloak (Web UI + JDBC) di `https://localhost:12443`.
- **Service/pipeline**: **HTTP Basic** service account (`svc_dbt`, `svc_seed`,
`svc_airflow`, `svc_om`) via `password-authenticator` file
(`config/trino/password.db`, bcrypt; di-generate `bootstrap.sh`/`install.sh`,
pra-syarat `htpasswd`). Otorisasi via OPA `data.users` (user→role).
- **HTTPS-only**: HTTP polos **dimatikan** (`allow-insecure-over-http=false`) —
`http://…:12081` tidak lagi di-expose. Semua akses via `https://localhost:12443`
(self-signed → `curl -k` / terima peringatan browser).
- ⚠️ **Web UI OAuth2 wajib HTTPS** — Trino mengalihkan `/ui/` ke
`/ui/static/disabled.html` bila request tidak secure.
### Phase 4 — Serving: StarRocks (shared-data) + Dremio
**StarRocks shared-data:**
- FE `run_mode=shared_data`, `cloud_native_storage_type=S3`; data tabel internal
disimpan di MinIO via **storage volume** `s3_volume`.
- Cluster = **FE + CN** (Compute Node, `ALTER SYSTEM ADD COMPUTE NODE`).
- OIDC: `CREATE SECURITY INTEGRATION sr_oidc`; **LDAP group provider** `sr_ldap`;
`authentication_chain = native,sr_oidc`; `GRANT role TO EXTERNAL GROUP`.
- External catalog `iceberg_catalog` → **Nessie** (`http://nessie:19120/iceberg/`).
- Akses: `mysql -h localhost -P 12930 -u root -p`.
**Dremio OSS:**
- Engine paralel di atas Nessie yang sama; auth **lokal** (first-run admin via UI).
- Source Nessie: endpoint `http://nessie:19120/api/v2`, MinIO path-style
(`fs.s3a.path.style.access=true`, `dremio.s3.compat=true`).
- Akses: `http://localhost:12947`. Detail: `config/dremio/README.md`.
### Phase 5 — Ingestion: Airflow
- Sling menarik PostgreSQL → Bronze (Iceberg via **Nessie Iceberg REST**).
- dbt via Trino: Bronze → Silver → Gold. Great Expectations validasi.
- Akses: `http://localhost:12082`.
### Phase 6 — Governance: OpenMetadata + Grafana
- OpenMetadata: DB `openmetadata_db`; konektor Trino + Airflow.
- Grafana: datasource provisioning file. Akses `http://localhost:12300`.
## RBAC
| User | Password | Role | Akses |
|------|----------|------|-------|
| `admin` | `KEYCLOAK_ADMIN_PASSWORD` | admin | Penuh (OPA superuser, StarRocks `sr_admin`) |
| `engineer` | `engineer123` | engineer | OPA write+ddl, StarRocks `sr_engineer` |
| `analyst` | `analyst123` | analyst | OPA select (per-kolom), StarRocks `sr_analyst` |
| `viewer` | `viewer123` | viewer | Terbatas (StarRocks tanpa grant → fail-closed) |
Pemetaan grup → akses:
- **Trino/OPA**: grup LDAP `data-*` → `config/opa/data.json` `roles.<grup>.tables`.
- **StarRocks**: external group `data-*` → role `sr_*` (`GRANT ... TO EXTERNAL GROUP`).
## Perbedaan dari `deploy-legacy/`
| Aspek | `deploy-legacy/` (base lama) | `deploy/` (merge) |
|---|---|---|
| Iceberg catalog | Polaris | **Nessie** (JDBC2/Postgres) |
| Trino authz | `rules.json` | **OPA** |
| Trino group provider | plugin Keycloak | **LDAP** |
| StarRocks mode | shared-nothing (FE+BE) | **shared-data (FE+CN)** |
| StarRocks storage | disk lokal | **S3/MinIO (storage volume)** |
| Engine paralel | — | **Dremio OSS** |
| Identitas | user+grup di Keycloak | user di Keycloak, **grup di OpenLDAP** |
| Project/network | `sicabe` / `172.28.0.0/16` | `sicabe` / `172.28.0.0/16` (sama) |
| Host port | 3xxx/8xxx/9xxx | **12xxx** |
## Catatan Penting
- **`run_mode=shared_data` bersifat final** — tidak bisa diubah setelah first
start; `deploy/` mulai dari volume bersih.
- **Nessie tidak mendukung Iceberg spec v3** → selalu `format-version=2`.
- **Dremio OSS** memakai auth lokal & penyimpanan volume (bukan PostgreSQL).
- **Tulis lintas engine** (Trino/StarRocks/Dremio) di Nessie sebaiknya bergantian,
bukan simultan.
- **`.env`** di-generate `install.sh` (JANGAN commit).
- Container & project memakai **`sicabe-*`** (sama dengan `deploy-legacy/`).
Karena nama container/project sama, **jangan jalankan `deploy/` dan
`deploy-legacy/` bersamaan** — `deploy-legacy/` hanya arsip/referensi.
- Host port tetap **12xxx**.
## Troubleshooting (ringkas)
| Gejala | Solusi |
|---|---|
| Nessie unhealthy | Cek DB `nessie` + schema `nessie` ada; cek `NESSIE_DB_PASSWORD` |
| Trino query ditolak semua | Cek OPA reachable (`curl localhost:12181/v1/data/trino/allow`); cek `config/opa/data.json` |
| StarRocks CN tidak terdaftar | `docker logs sicabe-starrocks-cn`; `SHOW COMPUTE NODES` di FE |
| StarRocks S3 400/301 | Pastikan path-style di storage volume + `cn.conf`/env S3 |
| Login OIDC StarRocks gagal | `required_issuer` harus host yang dipakai login (`http://localhost:12080/...`); redirect URI client `starrocks` harus memuat `http://starrocks-fe:8030/api/oauth2` |
| Dremio tidak melihat Nessie | Buat source Nessie manual (first-run); MinIO path-style + `dremio.s3.compat=true` |
| Sling gagal ke katalog | Sling tidak mendukung `catalog_type=nessie`; gunakan `catalog_type: rest` + `rest_uri: http://nessie:19120/iceberg/` |
Detail tambahan: `experiments/*/README.md` dan `config/*/README.md`.
## Referensi
> **Catatan versi & perilaku terverifikasi** — [`docs/sicabe-catatan-versi-dan-perilaku.md`](../docs/sicabe-catatan-versi-dan-perilaku.md):
> hal-hal yang pernah menggigit dan nilai yang benar (Keycloak 26 hostname/sslRequired/cluster
> health, Traefik v3 label/file-provider/ACME, token DNS-01 Cloudflare, env OM & MinIO),
> termasuk **Traefik membuang container yang unhealthy** yang membuat komponen hilang dari
> ingress tanpa error.
- Rencana merge: `../reviews/sicabe-07-merge-experiments-plan.md`
- [Project Nessie](https://projectnessie.org/guides/iceberg-rest/)
- [Trino OPA access control](https://trino.io/docs/current/security/opa-access-control.html)
- [StarRocks shared-data](https://docs.starrocks.io/docs/deployment/deploy_shared_data_manually/)
- [Dremio OSS](https://docs.dremio.com/)
- [Sling Iceberg](https://docs.slingdata.io/connections/datalake-connections/iceberg)
---
*deploy — hasil merge `deploy-legacy/` + 3 eksperimen (2026-09-10).*