Instalasi MariaDB Galera Cluster

Status: SUDAH DIIMPLEMENTASIKAN (8 Juli 2026)

Cluster Galera 3-node sudah berjalan sehat di namespace galera-system, backup terjadwal ke S3 sudah aktif, dan uji failover sudah lulus. Halaman ini didokumentasikan sebagai catatan implementasi — bukan lagi rencana.

1. Keputusan Teknis Awal

Item Keputusan
Operator mariadb-operator v26.6.0 (CRD-native: MariaDB, Backup, Restore, User, Database, Grant)
Namespace galera-system
Jumlah node 3 (multi-master, sesuai SEKSI-01)
Sizing per node Request 0,5 vCPU / 1GB — Limit 1 vCPU / 2GB (sesuai rencana resmi)
Storage per node 50Gi, StorageClass khusus longhorn-db-single
Image mariadb:11.8.8 (default operator)

Kenapa bukan Percona XtraDB Cluster Operator?

Percona XtraDB adalah fork MySQL, bukan MariaDB. Karena keputusan teknologi resmi adalah MariaDB Galera, dipakai mariadb-operator yang memang dibangun khusus untuk MariaDB.

2. Storage — Hindari Double-Replication

Galera sudah melakukan replikasi data sendiri (3 node). Bila memakai StorageClass Longhorn default (replika 3×), total duplikasi menjadi 3×3 = — sangat boros. Dibuat StorageClass khusus dengan replika 1×:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-db-single
provisioner: driver.longhorn.io
parameters:
  numberOfReplicas: "1"
  staleReplicaTimeout: "30"
allowVolumeExpansion: true

3. Install mariadb-operator

helm repo add mariadb-operator https://mariadb-operator.github.io/mariadb-operator
helm repo update
helm install mariadb-operator mariadb-operator/mariadb-operator -n mariadb-operator --create-namespace

Gotcha: CRD tidak ikut terpasang otomatis

Chart utama tidak menyertakan CRD secara default (crds.enabled: false) — operator akan CrashLoopBackOff dengan error "no matches for k8s.mariadb.com/v1alpha1" bila CRD belum ada. Perlu chart terpisah: bash helm install mariadb-operator-crds mariadb-operator/mariadb-operator-crds -n mariadb-operator kubectl rollout restart deployment mariadb-operator -n mariadb-operator

4. Deploy Cluster Galera (Custom Resource)

apiVersion: k8s.mariadb.com/v1alpha1
kind: MariaDB
metadata:
  name: galera-cluster
  namespace: galera-system
spec:
  rootPasswordSecretKeyRef:
    name: mariadb-root-credentials
    key: password
    generate: true          # operator generate password otomatis
  database: appdb
  username: appuser
  passwordSecretKeyRef:
    name: appuser-credentials
    key: password
    generate: true
  replicas: 3
  galera:
    enabled: true
  affinity:
    antiAffinityEnabled: true   # shortcut bawaan operator, tak perlu tulis manual podAntiAffinity
  storage:
    size: 50Gi
    storageClassName: longhorn-db-single
  resources:
    requests:
      cpu: 500m
      memory: 1Gi
    limits:
      cpu: 1000m
      memory: 2Gi

database dan username di level atas membuat operator otomatis membuat database awal + user dengan ALL PRIVILEGES di database tersebut — tidak perlu resource Database/User/Grant terpisah untuk kasus sederhana ini.

5. Verifikasi Cluster Sehat ✅

kubectl get pods -n galera-system
# galera-cluster-0   2/2   Running
# galera-cluster-1   2/2   Running
# galera-cluster-2   2/2   Running

kubectl exec -n galera-system galera-cluster-0 -c mariadb -- \
  mariadb -uroot -p"$ROOTPASS" -e "SHOW STATUS LIKE 'wsrep_cluster_size';"

Gotcha: mysql bukan lagi nama command

Image mariadb:11.8.8 tidak punya binary mysql — sudah diganti nama jadi mariadb (dan mariadb-dump menggantikan mysqldump). Sesuaikan semua script/dokumentasi lama yang masih menyebut mysql.

Hasil verifikasi: wsrep_cluster_size = 3, wsrep_ready = ON, wsrep_local_state_comment = Synced di ketiga node. Pod anti-affinity terkonfirmasi bekerja — 3 pod tersebar di 3 node fisik berbeda (10.0.0.3, 10.0.0.5, 10.0.0.6).

6. Database & User Aplikasi ✅

Dibuat otomatis oleh operator saat cluster pertama kali bootstrap (lihat langkah 4):

Item Nilai
Database appdb
User appuser (ALL PRIVILEGES di appdb)
Password Auto-generated, tersimpan di Secret appuser-credentials (key password)
Root password Auto-generated, tersimpan di Secret mariadb-root-credentials (key password)

7. ProxySQL — Ditunda

Sesuai keputusan, ProxySQL belum dipasang di sesi ini — fokus dulu ke Galera inti sampai stabil. Bisa ditambahkan kapan saja mengikuti sizing pada SEKSI-01 (0,5 vCPU/512MB request, 1 vCPU/1GB limit).

8. Backup Terjadwal ke S3 ✅

8.1 Arsitektur Endpoint S3

Endpoint S3 khusus oneKE (https://oneke-s3.ums.id/, bucket oneke-backups) dibangun di atas MinIO milik infrastruktur UMS (server bastion, 172.16.64.228:9100) — detail lengkap arsitektur, alasan DNS-only (bypass Cloudflare JS Challenge), dan pengecualian firewall ada di Rencana Arsitektur Implementasi.

8.2 Bug: Backup CRD Bawaan Operator Crash

Bug ditemukan: nil pointer panic di SDK minio-go

Resource Backup bawaan mariadb-operator (mode S3) konsisten crash saat upload — panic invalid memory address or nil pointer dereference di dalam minio-go/v7/pkg/signer.(*StreamingReader).signChunk. Direproduksi 3× berturut-turut, bukan hiccup jaringan (jalur nginx/firewall yang sama sudah terbukti lancar untuk upload manual via mc, termasuk file 20MB). Ini bug di SDK yang dipakai operator versi 26.6.0, bukan masalah di infrastruktur yang kita bangun.

Solusi: CronJob kustom yang menjalankan mariadb-dump dipipe langsung ke mc (client yang sudah terbukti bekerja normal), bypass mekanisme Backup CRD yang bermasalah:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: galera-backup-mc
  namespace: galera-system
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          initContainers:
          - name: install-mc
            image: curlimages/curl:latest
            command: ["sh", "-c", "curl -sL -o /shared/mc https://dl.min.io/client/mc/release/linux-amd64/mc && chmod +x /shared/mc"]
            volumeMounts:
            - name: shared
              mountPath: /shared
          containers:
          - name: backup
            image: mariadb:11.8.8
            command:
            - sh
            - -c
            - |
              set -e
              TS=$(date +%Y%m%d-%H%M%S)
              /shared/mc alias set oneke https://oneke-s3.ums.id "$ONEKE_ACCESS_KEY" "$ONEKE_SECRET_KEY"
              mariadb-dump -h galera-cluster.galera-system.svc.cluster.local -u root -p"$MARIADB_ROOT_PASSWORD" \
                --all-databases --single-transaction | gzip > /tmp/backup-$TS.sql.gz
              /shared/mc cp /tmp/backup-$TS.sql.gz oneke/oneke-backups/galera-dumps/backup-$TS.sql.gz
            env:
            - name: MARIADB_ROOT_PASSWORD
              valueFrom: { secretKeyRef: { name: mariadb-root-credentials, key: password } }
            - name: ONEKE_ACCESS_KEY
              valueFrom: { secretKeyRef: { name: oneke-s3-credentials, key: access-key-id } }
            - name: ONEKE_SECRET_KEY
              valueFrom: { secretKeyRef: { name: oneke-s3-credentials, key: secret-access-key } }
            volumeMounts:
            - name: shared
              mountPath: /shared
          volumes:
          - name: shared
            emptyDir: {}

Gotcha kedua: image minio/mc:latest tidak jalan di CPU virtual ini

Percobaan pertama pakai image resmi minio/mc:latest sebagai initContainer gagal dengan error Fatal glibc error: CPU does not support x86-64-v2 — image itu di-build dengan requirement instruksi CPU yang tidak didukung CPU virtual OpenNebula di sini. Solusinya: download binary mc release langsung via curl (binary generic, bukan image Docker ter-optimasi) memakai base image curlimages/curl yang ringan.

Hasil uji manual (8 Juli 2026): backup berhasil, file backup-20260708-145101.sql.gz (517KB) terkonfirmasi ada di oneke-backups/galera-dumps/.

8.3 Fallback: PVC Lokal

Bila endpoint S3 eksternal bermasalah, opsi cadangan ke storage lokal cluster:

storage:
  persistentVolumeClaim:
    storageClassName: longhorn-hdd
    resources:
      requests:
        storage: 20Gi

9. Uji Failover ✅

kubectl delete pod galera-cluster-1 -n galera-system --force --grace-period=0

Hasil: pod baru galera-cluster-1 otomatis dibuat StatefulSet, rejoin cluster dalam ~41 detik (IST — Incremental State Transfer), kembali ke 2/2 Running. Setelah recovery: wsrep_cluster_size = 3 dan Synced di ketiga node, database appdb tetap utuh — tanpa intervensi manual sama sekali.

10. Ringkasan Kredensial

Semua kredensial tersimpan sebagai Kubernetes Secret di namespace galera-systemtidak dituliskan plaintext di dokumen ini:

Secret Key Isi
mariadb-root-credentials password Root password MariaDB (auto-generated)
appuser-credentials password Password user appuser (auto-generated)
oneke-s3-credentials access-key-id, secret-access-key Kredensial S3 oneke-svc
# Ambil kredensial saat dibutuhkan (jangan disimpan di file/chat terbuka)
kubectl get secret mariadb-root-credentials -n galera-system -o jsonpath='{.data.password}' | base64 -d

11. phpMyAdmin ✅

Dipasang untuk kebutuhan penelitian (akses mudah, belum tahap produksi) — akses via https://oneke.ums.id/phpmyadmin/.

11.1 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: phpmyadmin
  namespace: galera-system
spec:
  replicas: 1
  selector:
    matchLabels: { app: phpmyadmin }
  template:
    metadata:
      labels: { app: phpmyadmin }
    spec:
      containers:
      - name: phpmyadmin
        image: phpmyadmin/phpmyadmin:latest
        env:
        - name: PMA_HOST
          value: galera-cluster.galera-system.svc.cluster.local
        - name: PMA_PORT
          value: "3306"
        - name: PMA_ABSOLUTE_URI      # wajib diisi karena diakses lewat subpath, bukan root domain
          value: https://oneke.ums.id/phpmyadmin/
        ports:
        - containerPort: 80
        resources:
          requests: { cpu: 100m, memory: 256Mi }
          limits: { cpu: 500m, memory: 512Mi }

Terhubung ke galera-cluster.galera-system.svc.cluster.local:3306 — koneksi bisa ke node mana saja karena Galera multi-master, operator/Service yang mengatur routing.

11.2 Routing (subpath, bukan subdomain baru)

Sama seperti dokumentasi /plan/, dipakai path /phpmyadmin/ di domain oneke.ums.id yang sudah ada (bukan subdomain baru) — via Middleware stripPrefix + redirect trailing-slash, reuse sertifikat TLS yang sudah ada (oneke-root-tls).

11.3 Status Keamanan — Sengaja Longgar Sementara

Keputusan sadar: belum ada IP whitelist/Basic Auth

Atas keputusan eksplisit untuk memudahkan akses selama fase penelitian, /phpmyadmin/ belum diberi lapis proteksi tambahan (IP whitelist + Basic Auth) seperti pada Longhorn UI. Satu-satunya gerbang saat ini adalah login form phpMyAdmin sendiri (masih butuh kredensial database asli — root atau appuser — bukan benar-benar terbuka tanpa syarat).

Wajib ditambahkan sebelum dipakai untuk data produksi sungguhan — pola sudah tersedia, tinggal replikasi dari Web Security Longhorn (IP whitelist di HAProxy VNF + Basic Auth via Traefik Middleware).

Login yang direkomendasikan untuk penelitian sehari-hari: user appuser (akses terbatas hanya ke appdb, bukan root) — password di Secret appuser-credentials.

11.4 Gotcha yang Dicek (tidak terjadi)

Mengingat image minio/mc:latest sebelumnya gagal karena requirement CPU x86-64-v2 (lihat §8.2), image phpmyadmin/phpmyadmin:latest sempat dicurigai berisiko sama — namun terbukti berjalan normal di CPU virtual OpenNebula ini tanpa masalah kompatibilitas.

Ringkasan Eksekusi

Langkah Status
StorageClass longhorn-db-single
Namespace galera-system
mariadb-operator + CRDs
Cluster Galera 3 node ✅ Healthy, Synced
Database appdb + user appuser ✅ Auto-created
ProxySQL ⏸️ Ditunda (sesuai keputusan)
Backup terjadwal ke S3 ✅ (via CronJob kustom, bukan Backup CRD bawaan — lihat 8.2)
Uji failover ✅ Lulus, recovery ~41 detik
phpMyAdmin (/phpmyadmin/) ✅ Live, proteksi tambahan tertunda (sengaja, fase penelitian)

Diimplementasikan 8 Juli 2026. Lihat Errata & Rekonsiliasi untuk temuan lain di sesi yang sama (disk VNF/master penuh, proses .vscode-server macet).