Operator Pattern #

Kubernetes dirancang secara native untuk mengelola aplikasi tanpa status (stateless applications) dengan sangat andal. Jika sebuah pod aplikasi web mengalami kegagalan, Kubernetes melalui kontroler bawaan seperti Deployment dapat dengan mudah menghancurkan pod tersebut, membuat ulang pod baru, menghubungkannya ke Service, dan mengaktifkan deteksi keaktifan (liveness probe) secara otomatis. Namun, cerita menjadi sangat berbeda ketika kita mencoba menjalankan aplikasi dengan status (stateful applications) seperti database relasional (PostgreSQL, MySQL), message brokers (Apache Kafka, RabbitMQ), atau mesin pencari (Elasticsearch). Objek-objek ini tidak bisa diperlakukan sebagai entitas anonim yang dapat diganti sewaktu-waktu. Mereka memiliki dependensi urutan startup yang ketat, membutuhkan manajemen sinkronisasi replikasi data aktif-pasif, memerlukan mekanisme pemulihan kegagalan (failover) yang sensitif, serta jadwal pencadangan (backup) dan pemulihan data yang rumit. Operator Pattern hadir untuk mengabstraksikan dan mengotomatiskan pengetahuan operasional manusia (human domain knowledge) tersebut ke dalam bentuk kode biner yang berjalan secara native di dalam Kubernetes.


Masalah Operasional Stateful dan Solusi Operator #

Sebelum adanya Operator Pattern, administrator sistem mengelola database di Kubernetes menggunakan kombinasi StatefulSet, PersistentVolumeClaim, dan sekumpulan skrip CronJob eksternal. Pendekatan manual ini memiliki keterbatasan saat terjadi kegagalan di tengah malam:

Operasional PostgreSQL Tanpa Operator (Manual):
  1. Pod Primary mengalami kegagalan hardware di tingkat Node.
  2. Kubernetes mendeteksi Node mati, namun tidak berani melakukan failover 
     karena risiko split-brain (kedua database mengklaim sebagai Primary).
  3. Administrator harus bangun, memindai data replika secara manual.
  4. Administrator mempromosikan Replica Pod menjadi Primary baru secara manual.
  5. Administrator mengubah rute lalu lintas aplikasi ke pod Primary baru.
  6. Resiko kehilangan data (*data loss*) sangat tinggi akibat kelalaian manusia.

Operasional PostgreSQL dengan CloudNativePG Operator (Otomatis):
  1. Pod Primary mengalami kegagalan hardware di tingkat Node.
  2. Operator Controller mendeteksi kegagalan tersebut melalui Watch API.
  3. Operator secara reaktif mempromosikan Replica Pod yang paling mutakhir (paling rendah lag replikasi) menjadi Primary baru.
  4. Operator mengonfigurasi ulang pod replika lainnya untuk mengarah ke Primary baru.
  5. Operator memperbarui Service Selector secara otomatis untuk mengalihkan lalu lintas.
  6. Proses failover selesai dalam hitungan detik tanpa intervensi manusia.

Anatomi Operator: CRD dan Custom Controller #

Operator dibangun dengan memanfaatkan dua konsep inti Kubernetes: Custom Resource Definition (CRD) dan Custom Controller.

flowchart TD
    User["Developer / Operator (kubectl apply)"] -->|"Kirim Manifest CR"| K8sAPI["Kubernetes API Server"]
    K8sAPI -->|"Menyimpan State di"| Etcd["etcd Database"]
    
    subgraph OperatorPod["Operator Pod (Deployment)"]
        direction TB
        WatchThread["Watch Loop (Mengamati Perubahan CR & Pods)"]
        ReconcileThread["Reconcile Loop (Menyamakan State)"]
        WatchThread --> ReconcileThread
    end
    
    K8sAPI <-->|"API Watch Connection"| WatchThread
    ReconcileThread -->|"Mengelola Resource Bawaan"| ActiveResources["Pods, Services, PVCs, Secret"]

1. Custom Resource Definition (CRD) #

CRD memperluas skema API Server Kubernetes dengan mendaftarkan jenis objek (Kind) baru. Setelah CRD terdaftar di kluster, Kubernetes API Server akan memperlakukan objek baru tersebut seolah-olah ia adalah objek bawaan Kubernetes seperti Pod atau Service. API Server menyediakan validasi OpenAPI v3 untuk memastikan parameter yang dimasukkan oleh pengguna telah memenuhi syarat sebelum disimpan ke dalam database etcd.

Berikut adalah contoh berkas definisi CRD minimal untuk objek PostgresCluster:

# File: k8s-operator/postgres-crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: postgresclusters.database.company.com
spec:
  group: database.company.com
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: ["instances", "storageSize"]
              properties:
                instances:
                  type: integer
                  minimum: 1
                  maximum: 5
                storageSize:
                  type: string
                  pattern: '^[0-9]+(Gi|Mi|Ti)$'
                version:
                  type: string
  scope: Namespaced
  names:
    plural: postgresclusters
    singular: postgrescluster
    kind: PostgresCluster
    shortNames:
      - pgcluster

2. Custom Resource (CR) #

Setelah CRD terdaftar di kluster, pengembang aplikasi dapat membuat instansi dari objek tersebut (disebut dengan Custom Resource) dengan berkas YAML sederhana:

# File: app/my-db-cluster.yaml
apiVersion: database.company.com/v1alpha1
kind: PostgresCluster
metadata:
  name: billing-database
  namespace: production
spec:
  instances: 3
  storageSize: "100Gi"
  version: "15.4"

3. Custom Controller #

CRD di atas hanyalah representasi data statis di dalam database etcd. Tanpa adanya Custom Controller, tidak akan ada aksi apa pun yang terjadi di kluster. Custom Controller adalah program mandiri yang biasanya ditulis menggunakan bahasa Go (atau Python/Ansible) yang berjalan sebagai pod Deployment di dalam kluster. Tugas utamanya adalah memantau (watch) objek PostgresCluster tersebut, lalu membuat Pod, Service, dan Persistent Volume Claim (PVC) nyata yang sesuai dengan spesifikasi yang diminta.


Siklus Rekonsiliasi (Reconciliation Loop) #

Inti dari kecerdasan sebuah Operator terletak pada Reconciliation Loop (Siklus Rekonsiliasi). Ini adalah algoritma tanpa akhir (infinite loop) yang terus-menerus berjalan untuk menyamakan keadaan nyata (actual state) di kluster dengan keadaan yang diinginkan (desired state) oleh pengguna.

Proses rekonsiliasi yang ketat digambarkan dalam diagram alir berikut:

flowchart TD
    WatchEvent["1. Deteksi Perubahan (Watch Event/CRD Trigger)"] --> ObserveState["2. Observasi (Baca Desired State & Actual State)"]
    ObserveState --> AnalyzeState["3. Analisis (Hitung Selisih/Diff)"]
    AnalyzeState --> ActState{"4. Apakah Ada Selisih?"}
    ActState -- "Ya (Actual != Desired)" --> ReconcileAction["5. Rekonsiliasi (Eksekusi Aksi Idempotent)"]
    ReconcileAction --> UpdateStatus["6. Perbarui Status Resource (CRD status)"]
    ActState -- "Tidak (Actual == Desired)" --> IdleState["7. Kembali ke Keadaan Siaga (Idle/Watch)"]
    UpdateStatus --> IdleState
    IdleState -. "Menunggu Event Berikutnya" .-> WatchEvent

Hukum Mutlak: Idempotensi Rekonsiliasi #

Fungsi rekonsiliasi (biasanya didefinisikan sebagai fungsi Reconcile(req ctrl.Request)) harus bersifat Idempotent. Artinya, jika fungsi rekonsiliasi dipanggil seratus kali berturut-turut dengan input data yang sama, ia harus menghasilkan keadaan akhir kluster yang persis sama tanpa memicu efek samping yang tidak diinginkan (seperti membuat ulang objek baru yang duplikat).

Sebagai contoh, penulisan logika rekonsiliasi yang salah dan benar di dalam Controller:

// ANTI-PATTERN: Logika non-idempotent (Memicu pembuatan ulang tak terbatas)
func Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // JANGAN: Selalu membuat ServiceAccount baru tanpa memeriksa keberadaannya terlebih dahulu!
    sa := &corev1.ServiceAccount{
        ObjectMeta: metav1.ObjectMeta{Name: "db-service-account", Namespace: req.Namespace},
    }
    err := r.Create(ctx, sa) // Akan error pada rekonsiliasi ke-2 karena resource sudah ada
    return ctrl.Result{}, err
}

// ==============================================================================
// BENAR: Logika Idempotent (Periksa keberadaan sebelum bertindak)
func Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    existingSA := &corev1.ServiceAccount{}
    err := r.Get(ctx, types.NamespacedName{Name: "db-service-account", Namespace: req.Namespace}, existingSA)
    
    if errors.IsNotFound(err) {
        // Buat baru HANYA JIKA objek tidak ditemukan di kluster
        sa := &corev1.ServiceAccount{
            ObjectMeta: metav1.ObjectMeta{Name: "db-service-account", Namespace: req.Namespace},
        }
        err = r.Create(ctx, sa)
        return ctrl.Result{}, err
    }
    
    // Jika sudah ada, lewati proses pembuatan dan lanjutkan ke resource berikutnya
    return ctrl.Result{}, nil
}

Operator Populer di Tingkat Produksi #

Komunitas Kubernetes telah mengembangkan ratusan Operator siap pakai untuk menyederhanakan operasional perkakas populer. Berikut adalah tiga Operator esensial yang wajib diinstal di kluster produksi:

1. Prometheus Operator (Kategori Monitoring) #

Prometheus Operator mengubah konfigurasi Prometheus yang kompleks menjadi objek Kubernetes yang deklaratif. Alih-alih mengedit file konfigurasi prometheus.yml secara manual dan me-restart pod setiap kali ada aplikasi baru yang ingin dipantau, kita cukup membuat objek ServiceMonitor.

# File: monitoring/app-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: payment-api-monitor
  namespace: production
  labels:
    release: prometheus-stack # Label ini dicocokkan oleh Prometheus Controller
spec:
  selector:
    matchLabels:
      app: payment-api # Memantau Service yang memiliki label ini
  endpoints:
  - port: metrics # Port pada Service yang menyediakan metrik Prometheus
    interval: 15s
    path: /metrics

2. Cert-Manager (Kategori Keamanan TLS) #

Cert-Manager mengotomatiskan siklus hidup sertifikat SSL/TLS. Ia memantau objek Certificate, berkomunikasi dengan Let’s Encrypt menggunakan protokol ACME untuk memvalidasi kepemilikan domain, mengunduh sertifikat, menyimpannya sebagai Kubernetes Secret, dan melakukan perpanjangan masa aktif (renewal) secara otomatis sebelum masa berlaku habis.

# File: security/letsencrypt-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-ssl-certificate
  namespace: production
spec:
  secretName: api-tls-secret # Sertifikat akan disimpan di Secret ini untuk di-mount ke Ingress
  issuerRef:
    name: letsencrypt-production-issuer # Rujukan ke ClusterIssuer ACME Let's Encrypt
    kind: ClusterIssuer
  commonName: api.company.com
  dnsNames:
  - api.company.com

3. Strimzi (Kategori Message Broker Kafka) #

Apache Kafka terkenal sangat sulit dikelola di Kubernetes karena kompleksitas pengorganisasian kluster Zookeeper, KRaft, broker Kafka, topik, dan otorisasi akses. Strimzi membungkus seluruh kerumitan tersebut ke dalam operator deklaratif.

# File: kafka/kafka-cluster.yaml
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
  name: prod-kafka-cluster
  namespace: kafka-system
spec:
  kafka:
    version: 3.6.0
    replicas: 3 # Membuat cluster 3 broker untuk toleransi kegagalan (HA)
    listeners:
      - name: plain
        port: 9092
        type: internal
        tls: false
      - name: tls
        port: 9093
        type: internal
        tls: true
    config:
      offsets.topic.replication.factor: 3
      transaction.state.log.replication.factor: 3
      transaction.state.log.min.isr: 2
    storage:
      type: persistent-claim
      size: 500Gi
      class: premium-rwo # Dynamic storage class
  zookeeper:
    replicas: 3
    storage:
      type: persistent-claim
      size: 50Gi

OLM (Operator Lifecycle Manager) #

Untuk mengelola instalasi, peningkatan versi (upgrade), dan izin RBAC dari berbagai Operator di dalam satu kluster, kita direkomendasikan menggunakan Operator Lifecycle Manager (OLM). OLM bertindak sebagai toko aplikasi (App Store) internal di dalam kluster Kubernetes.

Kita dapat menginstal OLM dan memantau katalog OperatorHub.io melalui baris perintah:

# 1. Instalasi OLM ke dalam kluster Kubernetes
kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.27.0/crds.yaml
kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.27.0/olm.yaml

# 2. Menginstal Operator (misalnya cert-manager) via OLM menggunakan Subscription
cat <<EOF | kubectl apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: cert-manager
  namespace: operators
spec:
  channel: stable
  name: cert-manager
  source: operatorhubio-catalog
  sourceNamespace: olm
EOF

Panduan Memutuskan: Kapan Menulis Operator Sendiri? #

Menulis Operator kustom memerlukan investasi waktu dan pemeliharaan kode yang tidak sedikit. Kita harus mengevaluasi kebutuhan kita secara rasional sebelum mulai membuild Operator sendiri.

KITA HARUS MENULIS Operator sendiri jika:
  ✓ Aplikasi internal adalah sistem stateful kompleks yang memiliki runbook pemulihan bencana rumit.
  ✓ Ada kebutuhan otomatisasi integrasi reaktif dengan sistem eksternal perusahaan (misal: provisioning DB di K8s harus mendaftarkan IP ke firewall hardware eksternal).
  ✓ Kita ingin menyediakan platform-as-a-service (PaaS) internal mandiri bagi developer di perusahaan.

KITA DILAYANGKAN MENULIS Operator jika:
  ✗ Aplikasi bersifat stateless (cukup gunakan Deployment, HPA, dan Kustomize/Helm).
  ✗ Sudah ada Operator open-source yang matang dan didukung komunitas untuk database pilihan kita.
  ✗ Tim pengembang tidak memiliki kapasitas atau pemahaman mendalam tentang siklus internal Kubernetes API Client.

Perkakas untuk Menulis Operator #

Jika kita memutuskan untuk menulis Operator kustom, jangan membangunnya dari nol menggunakan HTTP client mentah. Gunakan SDK standard industri berikut:

  • Kubebuilder (Rekomendasi Utama): Framework resmi dari Kubernetes SIGs yang menggunakan bahasa Go dan pustaka controller-runtime. Menyediakan code generator dan scaffolding terlengkap untuk validasi CRD dan unit testing menggunakan kubebuilder envtest.
  • Operator SDK: Bagian dari proyek CNCF yang dikelola oleh Red Hat. Mendukung pembuatan Operator menggunakan bahasa Go, manifest Helm (tanpa nulis kode Go), atau Ansible (sangat cocok untuk tim sysadmin).
  • Kopf (Kubernetes Operator Framework): Framework berbasis Python bagi tim yang tidak ingin menggunakan bahasa Go namun membutuhkan logika kontroler kustom yang cepat dibangun.

Anti-Pattern dalam Penerapan Operator Pattern #

Hindari kesalahan desain arsitektur berikut saat mengoperasikan atau membangun Operator:

1. Kegagalan Status Pemblokiran Rekonsiliasi (Blocking Reconciliation) #

Menulis logika rekonsiliasi yang melakukan panggilan API HTTP sinkron ke pihak ketiga atau menjalankan proses backup eksternal yang memakan waktu berjam-jam secara langsung di dalam thread utama controller.

// ANTI-PATTERN: Melakukan operasi eksternal sinkron yang lambat di dalam Reconcile loop
func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // JANGAN: Panggilan backup database memblokir siklus rekonsiliasi untuk objek pgcluster lainnya!
    err := r.runDatabaseBackupToS3Blocking() 
    return ctrl.Result{}, err
}
Risiko Pemblokiran Jaringan:
- Seluruh siklus rekonsiliasi untuk objek PostgresCluster lain di kluster akan tertunda (*freeze*).
- Jika ada Pod database lain yang crash, Operator tidak dapat mendeteksinya dengan cepat karena thread sedang sibuk memproses backup S3.
✓ SOLUSI: Jalankan proses berat di dalam Kubernetes Job terpisah. 
Controller cukup membuat objek 'Job' Kubernetes bawaan, lalu mengakhiri siklus 
rekonsiliasi saat itu juga. Ketika Job tersebut selesai, Controller akan mendapatkan 
notifikasi watch event baru untuk memperbarui status CRD-nya.

2. Memberikan Hak Akses RBAC Wildcard (Over-Privileged Operator) #

Menulis manifest keamanan Operator dengan memberikan izin RBAC wildcard (*) pada bagian apiGroups, resources, dan verbs untuk mempermudah instalasi awal.

# ANTI-PATTERN: Konfigurasi Role Operator yang terlalu longgar dan berbahaya
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: over-privileged-operator-role
rules:
- apiGroups: ["*"]
  resources: ["*"] # JANGAN: Menawarkan celah eskalasi hak akses fatal!
  verbs: ["*"]
✓ SOLUSI: Terapkan Prinsip Least Privilege.
Batasi hak akses Operator hanya pada apiGroup dan resource spesifik yang dikelolanya. 
Misalnya, jika Operator hanya mengelola PostgresCluster, ia hanya butuh izin read/write 
pada pgclusters, pods, services, secrets, dan pvc. Jangan pernah memberikan izin 
ke tingkat cluster-wide resources (seperti nodes atau namespaces) kecuali benar-benar dibutuhkan.

Checklist Audit Implementasi Operator Pattern #

Gunakan daftar checklist ini untuk mengevaluasi kesehatan operasional Operator di kluster Anda:

HARDENING RBAC & KEAMANAN OPERATOR:
  □ Hak akses Operator (ClusterRole) dibatasi hanya pada resource spesifik yang dikelola (least privilege).
  □ Kontainer Operator dijalankan menggunakan SecurityContext non-root ('runAsNonRoot: true').
  □ Operator diisolasi ke dalam namespace khusus dan tidak dicampur dengan workload aplikasi bisnis.
  □ Kredensial sensitif untuk database yang dikelola disimpan secara terenkripsi menggunakan Secret K8s.

DESAIN CONTROLLER & REKONSILIASI:
  □ Seluruh logika di dalam fungsi Reconcile bersifat idempotent dan aman dieksekusi berulang kali.
  □ Tidak ada panggilan API eksternal pemblokir (blocking calls) sinkron di dalam loop utama.
  □ Proses berat (seperti backup/restore) didelegasikan ke objek Job terpisah.
  □ Menggunakan mekanisme status subresource pada CRD untuk merekam progress rekonsiliasi secara terpisah.

VALIDASI CRD & SIKLUS HIDUP:
  □ Struktur data spec CRD memiliki validasi OpenAPI v3 yang ketat (tipe data, pola regex, batas minimum).
  □ Menggunakan biner Kubebuilder / Operator SDK resmi untuk menjamin kepatuhan struktur kode.
  □ Upgrade versi Operator diuji secara bertahap di lingkungan staging menggunakan OLM atau Helm.
  □ Menghapus Custom Resource (CR) secara bersih juga membersihkan resource bawaan terkait via ownerReferences.

Ringkasan #

  • Otomatisasi Pengetahuan Operasional — Operator Pattern bertugas menerjemahkan runbook operasional manusia (failover, backup, cluster scaling) ke dalam perangkat lunak otomatis.
  • CRD dan Controller adalah Satu Kesatuan — CRD mendefinisikan objek deklaratif baru di etcd, sedangkan Controller bertugas mewujudkan spesifikasi objek tersebut di kluster nyata.
  • Idempotensi Adalah Hukum Utama — Pastikan logika rekonsiliasi Anda aman dieksekusi berulang kali tanpa memicu galat duplikasi resource kluster.
  • Gunakan Job untuk Tugas Berat — Hindari memblokir thread rekonsiliasi; delegasikan tugas pemulihan data dan backup ke objek Kubernetes Job terpisah.
  • Gunakan Solusi Komunitas Terlebih Dahulu — Jelajahi OperatorHub.io sebelum menulis Operator kustom; komunitas telah menyediakan Operator matang untuk sebagian besar database.
  • Batasi Izin RBAC Operator — Lindungi kluster Anda dari risiko privilege escalation dengan membatasi izin akses biner Operator seminimal mungkin.

← Sebelumnya: Local Development Tools   Berikutnya: Managed Kubernetes →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact