Fondasi Infrastruktur K8s — Skeleton Django

Status: SUDAH DIIMPLEMENTASIKAN (14 Juli 2026)

Berbeda dari halaman-halaman sebelumnya (desain skema di atas kertas), halaman ini mendokumentasikan infrastruktur nyata yang sudah berjalan — Modul 0/9 (Fondasi) dan §2 Roadmap ("Fondasi Infrastruktur K8s") sekarang punya skeleton Django yang benar-benar hidup, terhubung ke appdb, dan bisa diakses publik.

1. Namespace

Namespace baru sia-multitenant — terpisah dari demo-app/galera-system/postgres-system yang sudah ada, sesuai Roadmap §2.

2. Database — Pakai appdb/appuser yang Sudah Ada

Sesuai kesimpulan uji isolasi (Skema Data — Fondasi §11): tidak ada database/role baru dibuat — SIA memakai appdb/appuser yang sudah ada di postgres-cluster (sebelumnya kosong, belum dipakai apa pun).

Kredensial disalin sebagai Secret di namespace sia-multitenant (bukan referensi cross-namespace langsung — Secret tidak bisa dibaca lintas-namespace di Kubernetes):

kubectl create secret generic sia-db-credentials -n sia-multitenant \
  --from-literal=host=postgres-cluster.postgres-system.svc.cluster.local \
  --from-literal=port=5432 \
  --from-literal=dbname=appdb \
  --from-literal=user=appuser \
  --from-literal=password='<disalin dari Secret appuser.postgres-cluster... di postgres-system>'

Pengingat wajib: FORCE ROW LEVEL SECURITY di setiap migrasi

Karena appuser adalah pemilik tabel-tabel yang akan dibuat Django (migrasi berjalan sebagai appuser), setiap CREATE TABLE wajib diikuti ALTER TABLE ... FORCE ROW LEVEL SECURITY — bukan cuma ENABLE. Skeleton di halaman ini belum punya tabel aplikasi (baru migrasi bawaan Django: auth, admin, contenttypes, sessions) sehingga belum relevan, tapi ini wajib diterapkan begitu model pertama (mis. Tenant, Fakultas) mulai diimplementasikan sebagai migrasi Django nyata.

3. Struktur Skeleton Django

sia-skeleton/
  manage.py
  core/
    settings.py   # DATABASES dari env var (Secret), LANGUAGE_CODE = "id"
    urls.py       # admin, health check, site_header/site_title
    wsgi.py
  akademik/
    apps.py       # app kosong, placeholder untuk model Modul 2/3 nanti
    migrations/

Belum ada model aplikasi (Tenant, Fakultas, dst. dari skema data) — migrasi yang berjalan baru migrasi bawaan Django (auth, admin, contenttypes, sessions), membuktikan koneksi ke appdb bekerja tanpa mengunci desain skema final ke kode terlalu awal.

3.1 Health Check

def healthz(request):
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
            cursor.fetchone()
        db_status = "connected"
    except Exception as e:
        return JsonResponse({"status": "error", "database": str(e)}, status=503)
    return JsonResponse({"status": "ok", "database": db_status})

Dipakai sebagai readinessProbe dan livenessProbe di Deployment — bukan sekadar endpoint statis, benar-benar menguji konektivitas appdb tiap kali Kubernetes memeriksa kesehatan pod.

4. Deployment — Tanpa Build Image Kustom

Pola yang sama seperti status-api dan preview Unfold/Jazzmin sebelumnya — initContainer yang pip install ke volume bersama, kode aplikasi lewat ConfigMap (di-tar terlebih dahulu supaya struktur direktori tetap terjaga):

initContainers:
  - name: setup
    image: python:3.12-slim
    command: ['sh', '-c', 'mkdir -p /app && tar xzf /code/app.tar.gz -C /app && pip install --target=/deps --no-cache-dir django psycopg2-binary']
containers:
  - name: django
    image: python:3.12-slim
    command: ['sh', '-c', 'python manage.py migrate --noinput && python manage.py collectstatic --noinput && (python manage.py createsuperuser --noinput --username admin --email admin@oneke.ums.id || true) && python manage.py runserver 0.0.0.0:8000']
    readinessProbe: { httpGet: { path: /sia/healthz/, port: 8000 } }
    livenessProbe: { httpGet: { path: /sia/healthz/, port: 8000 } }

createsuperuser --noinput dengan || true — aman dijalankan berulang tiap restart pod (gagal senyap kalau superuser sudah ada dari run sebelumnya, tanpa menghentikan startup).

5. Routing — Path /sia/, Bukan Subdomain Baru

Roadmap awal menyebut contoh sia.oneke.ums.id (subdomain). Untuk tahap skeleton ini, dipakai path /sia/ di domain oneke.ums.id yang sudah ada — konsisten dengan phpMyAdmin/pgAdmin/ Grafana yang sudah lebih dulu memakai pola ini, dan menghindari provisioning DNS/sertifikat baru untuk sekadar smoke-test infrastruktur.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: sia-skeleton-ingressroute
  namespace: sia-multitenant
spec:
  entryPoints: [web, websecure]
  routes:
    - match: Host(`oneke.ums.id`) && PathPrefix(`/sia`)
      kind: Rule
      priority: 100
      services: [{ name: sia-skeleton-svc, port: 8000 }]
  tls:
    secretName: oneke-root-tls

Pola URL Django mengikuti gaya pgAdmin/Grafana (path lengkap ditulis langsung di urls.py, STATIC_URL = "/sia/static/") — tanpa stripPrefix, sama seperti alasan yang sudah dibahas di Instalasi PostgreSQL + Patroni.

Subdomain dedicated tetap bisa ditambahkan nanti

Keputusan path vs subdomain di sini murni soal kemudahan tahap awal — arsitektur multi-tenant (custom domain per kampus, lihat Keputusan Arsitektur §2) tidak bergantung pada pilihan ini sama sekali, karena custom domain tenant di-resolve dari Host header terlepas dari pola akses default yang dipakai tim oneKE sendiri.

6. Label Admin — Bahasa Indonesia

admin.site.site_header = "Sistem Informasi Akademik Multi-Tenant"
admin.site.site_title = "SIA Admin"
admin.site.index_title = "Panel Administrasi"

Temuan: Django sudah punya terjemahan Indonesia bawaan

Dengan LANGUAGE_CODE = "id" di settings.py, seluruh teks bawaan admin (label field, tombol) otomatis diterjemahkan tanpa perlu file terjemahan tambahan — "Username" → "Nama pengguna", "Password" → "Sandi", "Log in" → "Masuk", dst. Django sendiri sudah menyertakan berkas .po/.mo Bahasa Indonesia untuk seluruh string bawaannya.

7. Verifikasi

Pemeriksaan Hasil
GET /sia/healthz/ {"status": "ok", "database": "connected"}
GET /sia/admin/login/ ✅ 200, tampil dengan label Indonesia + site_header custom
Migrasi bawaan Django ke appdb ✅ Berhasil (auth, admin, contenttypes, sessions)
Superuser dibuat admin (kredensial di Secret sia-admin-password, tidak dituliskan plaintext di sini)

8. Ringkasan Kredensial

Secret Namespace Isi
sia-db-credentials sia-multitenant Host/port/dbname/user/password appdb
sia-django-secret sia-multitenant SECRET_KEY Django
sia-admin-password sia-multitenant Password superuser admin
kubectl get secret sia-admin-password -n sia-multitenant -o jsonpath='{.data.password}' | base64 -d

9. Langkah Berikutnya

Sesuai Roadmap §2, tersisa: runbook custom domain per tenant (§3), lalu integrasi monitoring & homepage (§4). Implementasi model aplikasi nyata (Tenant, Fakultas, dst. dari enam halaman skema data) menyusul sebagai pekerjaan pengembangan aplikasi — di luar cakupan "fondasi infrastruktur" yang selesai di halaman ini.


Kembali ke Roadmap Implementasi atau Skema Data — Fondasi.