commit 4ca7a9a58e0523e8808a0920ce31dc6422e61d6c Author: Subroto Date: Fri Oct 9 02:35:14 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f23316 --- /dev/null +++ b/.gitignore @@ -0,0 +1,217 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +#lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python + +# Auto-generated OPA secrets (berisi kc_client_secret dari .env) +deploy/config/opa/policies/secrets.rego + +# opencode +install.sh +# Pengecualian: installer deploy* tetap di-track +!deploy/install.sh +!deploy-legacy/install.sh +.opencode/ +*:Zone.Identifier +*.Zone.Identifier + +# Auto-generated credentials (JANGAN commit — di-generate saat deploy) +deploy/config/airflow/sling/env.yaml +deploy/config/airflow/sling/.sling_cred +deploy/config/airflow/simple_auth_manager_passwords.json +# Trino service-account password file (bcrypt) — di-generate dari .env +deploy/config/trino/password.db + +# Eksperimen (secret lokal, jangan commit) +experiments/**/.env + +# Backup DB (B1 / review R18) — berisi DATA PRODUKSI (dump openmetadata_db & airflow). +# JANGAN pernah di-commit: isinya data nyata (bisa memuat hash kredensial & data pribadi). +deploy/output/merge-airflow332-backup/ + +# Temp & cache +.tmp/ +__pycache__/ +*.pyc + +# Backup otomatis editor draw.io +*.bkp +*.bak + +# Multi-host (Fase 0/1 R10): hasil render compose+config per host — jangan di-commit +deploy/generated/ + +# Trino keystore dipersistenkan saat --generate (jangan di-commit) +deploy/.trino-keystore/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..50cdb6f --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,697 @@ +# 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-/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`. + +> ⚠️ **Pilot multi-host di host yang SAMA tidak boleh dua stack berjalan bersamaan.** +> Memisah network/port saja **tidak cukup** — RAM tidak akan cukup (host pilot 15 GB, +> satu stack penuh ±10 GB). **Matikan stack lama lebih dulu** sebelum menyalakan hasil +> `generated/host-/`. + +**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 ` (hanya image host itu, dari +`generated/host-/`) · `--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 :/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-/ (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 +./install.sh --sync --user admin --dest '~/sicabe-doc/deploy' # SSH user & tujuan +./install.sh --sync --delete # hapus di tujuan berkas yang tak ada di sumber +``` + +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 ` lalu `./install.sh --host `. + +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 ` 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 :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 ` 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://:` — diisi otomatis oleh `install.sh --generate`, **sama di semua bundle** | +| `tls.mode: edge` / `full` | `https://