Files
sicabe-core/deploy
2026-10-13 18:28:09 +07:00
..
2026-10-13 18:28:09 +07:00
2026-10-09 02:35:14 +07:00
2026-10-13 18:28:09 +07:00
2026-10-13 18:28:09 +07:00
2026-10-12 21:42:28 +07:00
2026-10-09 02:35:14 +07:00
2026-10-12 14:43:12 +07:00
2026-10-11 15:09:43 +07:00
2026-10-12 21:10:45 +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.

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:

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

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:

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

⚠️ 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).

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:

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:

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:

# 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 :/tmp/ ./scripts/prefetch-images.sh --load-airflow /tmp/airflow-image.tar

Menempatkan komponen ke beberapa host (Fase 1)

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:

./install.sh --sync                       # ke SEMUA host lain (host ini dideteksi dari alamat lokal)
./install.sh --sync --host state          # hanya satu host
./install.sh --sync --user admin --dest '~/lain/deploy'   # SSH user & tujuan (timpa default)
./install.sh --sync --delete              # hapus di tujuan berkas yang tak ada di sumber

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.

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

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

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):

./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).

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

# 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 · versi interaktif .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

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 · OpenLDAP docs/openldap-existing-requirements.md.

Deploy per phase:

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

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


deploy — hasil merge deploy-legacy/ + 3 eksperimen (2026-09-10).