Deploy Manifest

Deploy Manifest #

Setelah kluster Kubernetes kita berjalan dengan sukses, langkah operasional berikutnya yang paling sering kita lakukan adalah mengelola beban kerja (workload) di atasnya. Kita perlu mendeploy komponen aplikasi kita, mulai dari web server, backend service, database, hingga aturan perutean jaringan luar (Ingress). Di dunia Kubernetes tradisional, kita terbiasa menggunakan perintah kubectl apply -f manifest.yaml. Namun, ketika kita berbicara tentang otomatisasi skala besar dan deployment multi-environment (seperti Dev, Staging, dan Prod), perintah manual kubectl cepat sekali memicu ketidakkonsistenan konfigurasi.

Ansible menyediakan koleksi khusus kubernetes.core yang dirancang untuk menjembatani dunia orkestrasi container dengan kekuatan otomatisasi Ansible. Melalui modul-modul di dalam koleksi ini, kita dapat menulis manifest Kubernetes secara deklaratif di dalam playbook, memanfaatkan mesin templating Jinja2 untuk membuat manifes dinamis, mengintegrasikan data sensitif langsung dari Ansible Vault, serta memastikan alur deployment berjalan secara idempoten dan aman. Artikel ini akan membahas secara mendalam bagaimana kita mengelola seluruh siklus hidup manifest Kubernetes menggunakan Ansible.


Setup Dependencies Koleksi Ansible untuk Kubernetes #

Sebelum playbook Ansible kita dapat berinteraksi dengan API Server Kubernetes, ada beberapa dependensi perangkat lunak yang harus kita pasang di mesin kontrol (control node) tempat Ansible dijalankan. Modul Kubernetes di Ansible tidak memanggil CLI kubectl di latar belakang, melainkan berkomunikasi secara langsung menggunakan REST API melalui pustaka Python resmi Kubernetes.

# Perintah instalasi koleksi Kubernetes untuk Ansible
ansible-galaxy collection install kubernetes.core

# Perintah instalasi modul Python yang dibutuhkan di mesin kontrol
pip install kubernetes PyYAML

[!NOTE] Pastikan pustaka Python kubernetes diinstal pada lingkungan Python yang sama dengan yang digunakan oleh Ansible. Jika kita menggunakan virtual environment Python, pastikan untuk mengaktifkannya terlebih dahulu sebelum menjalankan instalasi pip.

KAPAN MENGGUNAKAN MODUL K8S?
  ✓ Kita ingin mendeploy manifest yang konfigurasinya berubah dinamis berdasarkan environment.
  ✓ Kita perlu menggabungkan pembuatan resource Kubernetes dengan penyediaan infrastruktur luar (seperti cloud storage atau database).
  ✓ Kita ingin mengamankan kredensial rahasia (DB password, API keys) menggunakan Ansible Vault.
  ✓ Kita membutuhkan otomatisasi rollback otomatis jika proses deployment mengalami kegagalan.

KAPAN LEBIH BAIK TIDAK MENGGUNAKANNYA:
  ✗ Untuk testing manifest sekali jalan secara lokal (lebih cepat menggunakan kubectl apply biasa).
  ✗ Ketika tim kita belum memahami struktur objek dasar Kubernetes (sebaiknya pelajari YAML Kubernetes dahulu).

Alur Autentikasi dan Opsi Koneksi K8s di Ansible #

Modul kubernetes.core.k8s mendukung berbagai metode autentikasi untuk terhubung ke API Server Kubernetes. Memilih metode autentikasi yang tepat sangat menentukan tingkat keamanan dan fleksibilitas otomatisasi kita.

flowchart TD
    A["Ansible Playbook Run"] --> B{"Pilih Metode Autentikasi"}
    B -->|"Lokal / CI-CD"| C["Kubeconfig File (admin.conf)"]
    B -->|"Dalam Cluster (Pod)"| D["Service Account Token (In-Cluster)"]
    B -->|"Production Cloud"| E["Dynamic Provider (EKS/GKE Token)"]

1. Menggunakan Kubeconfig (Metode Paling Umum) #

Metode ini adalah yang paling mudah digunakan. Ansible akan membaca file konfigurasi cluster (biasanya berlokasi di ~/.kube/config atau path kustom) untuk mendapatkan alamat API Server, certificate authority, dan token akses.

# Contoh set konfigurasi path kubeconfig di variabel group
# group_vars/all.yml
---
k8s_kubeconfig: "{{ playbook_dir }}/kubeconfig/admin.conf"

2. Menggunakan Token dan Host API Secara Langsung #

Jika kita menjalankan Ansible dari sistem CI/CD eksternal yang tidak memiliki file kubeconfig fisik, kita dapat menyuntikkan host API Server dan token akses secara dinamis dari variabel lingkungan (env vars):

- name: Deploy namespace menggunakan token akses langsung
  kubernetes.core.k8s:
    host: "https://10.0.1.10:6443"
    validate_certs: false
    api_key: "{{ vault_k8s_api_token }}"
    state: present
    definition:
      apiVersion: v1
      kind: Namespace
      metadata:
        name: production-ci

Membuat Namespace Secara Dinamis #

Namespace adalah cara Kubernetes membagi satu kluster fisik menjadi beberapa kluster logis virtual. Sangat disarankan bagi kita untuk memisahkan setiap aplikasi atau lingkungan ke dalam namespace-nya masing-masing untuk menghindari bentrokan penamaan objek dan mempermudah isolasi keamanan.

Berikut adalah cara mendeploy Namespace secara inline menggunakan parameter definition di Ansible:

# playbooks/deploy-app.yml
---
- name: Persiapkan Lingkungan Kerja Aplikasi
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    app_namespace: "finance-prod"
    env_name: "production"
    k8s_kubeconfig: "{{ playbook_dir }}/kubeconfig/admin.conf"

  tasks:
    - name: Buat Namespace untuk aplikasi secara deklaratif
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_kubeconfig }}"
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: "{{ app_namespace }}"
            labels:
              managed-by: ansible
              environment: "{{ env_name }}"
              team: backend

Mengelola Konfigurasi dengan ConfigMap dan Secret dari Ansible Vault #

Aplikasi modern wajib memisahkan kode aplikasi dari konfigurasi (prinsip Twelve-Factor App). Di Kubernetes, kita menggunakan ConfigMap untuk konfigurasi biasa dan Secret untuk konfigurasi sensitif.

Bahaya Hardcode Secret #

Menyimpan password database, private keys, atau token API dalam bentuk teks biasa di repositori Git adalah salah satu celah keamanan paling kritis dalam operasional IT. Banyak developer terjebak melakukan encoding base64 secara manual lalu menyimpannya langsung di file YAML. Ingat, base64 bukanlah enkripsi, melainkan sekadar format representasi data. Siapa pun yang memiliki akses ke kode Git dapat dengan mudah mendekripsi token tersebut.

Solusi: Kolaborasi Ansible Vault dan Kubernetes Secret #

Dengan menggunakan Ansible, kita dapat menyimpan seluruh data sensitif dalam keadaan terenkripsi penuh menggunakan Ansible Vault. Saat playbook dijalankan, Ansible akan mendekripsi data tersebut di dalam memori secara instan, lalu mendeploynya ke Kubernetes dalam bentuk Secret menggunakan kolom stringData. Kubernetes akan menangani proses encoding base64 secara otomatis saat data disimpan di database etcd.

Mari kita lihat perbandingan langsung antara pendekatan anti-pattern dengan pendekatan yang direkomendasikan.

# ANTI-PATTERN: Menyimpan secret yang sudah di-encode base64 secara hardcode di file manifes Git
# Siapa saja yang membaca file ini dapat mendekripsi password dengan mudah menggunakan perintah base64 -d
- name: Deploy secret dengan cara yang salah (JANGAN DILAKUKAN)
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Secret
      metadata:
        name: app-db-secret-bad
        namespace: "{{ app_namespace }}"
      type: Opaque
      data:
        # ✗ Password ter-expose secara publik di repositori Git
        database-password: "c2FuZ2F0LXJhaGFzaWE=" 

# BENAR: Menggunakan Ansible Vault untuk enkripsi tingkat lanjut dan stringData untuk kemudahan
# Kita menggunakan enkripsi kuat Ansible Vault dan mencegah kebocoran log dengan no_log: true
- name: Deploy database credentials dengan Ansible Vault (Solusi Terbaik)
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Secret
      metadata:
        name: app-db-secret-good
        namespace: "{{ app_namespace }}"
      type: Opaque
      stringData:
        # ✓ Nilai aman didekripsi di memori oleh Ansible saat runtime
        database-password: "{{ vault_production_db_password }}"
        api-token: "{{ vault_payment_api_token }}"
    # JANGAN LUPA: Mencegah output teks didekripsi terekspos di layar terminal console
    no_log: true

Untuk data non-sensitif, kita mendeploy ConfigMap yang mengambil nilai konfigurasi port dan host secara dinamis:

- name: Deploy ConfigMap aplikasi backend
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: app-config
        namespace: "{{ app_namespace }}"
      data:
        APP_PORT: "8080"
        LOG_LEVEL: "info"
        DATABASE_HOST: "postgres-service.database.svc.cluster.local"

Mendeploy Deployment Aplikasi dengan Zero-Downtime Strategy #

Deployment bertanggung jawab untuk membuat dan memperbarui replika Pod aplikasi kita. Agar aplikasi kita dapat diperbarui secara aman tanpa menimbulkan downtime bagi pengguna (zero-downtime), kita harus mengonfigurasi strategi RollingUpdate dengan presisi.

Di dalam manifes Deployment, kita wajib menyetel dua parameter kunci:

  • maxSurge: Menentukan berapa banyak replika Pod baru yang boleh dibuat di atas jumlah replika yang ditargetkan selama proses update berjalan. Kita setel ke 1 agar Pod baru dibuat terlebih dahulu sebelum Pod lama dimatikan.
  • maxUnavailable: Menentukan berapa banyak replika Pod lama yang boleh tidak tersedia selama proses update berjalan. Kita setel ke 0 untuk menjamin kapasitas aplikasi kita selalu 100% terpenuhi selama proses transisi versi.

Selain itu, kita wajib mendefinisikan Liveness dan Readiness Probes agar Kubernetes dapat mendeteksi dengan akurat kapan kontainer kita benar-benar siap menerima traffic jaringan.

- name: Deploy Aplikasi Backend dengan Strategi Rolling Update
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: backend-app
        namespace: "{{ app_namespace }}"
        labels:
          app: backend-app
          version: "2.1.0"
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: backend-app
        strategy:
          type: RollingUpdate
          rollingUpdate:
            maxSurge: 1
            maxUnavailable: 0 # Zero-downtime terjamin
        template:
          metadata:
            labels:
              app: backend-app
              version: "2.1.0"
          spec:
            containers:
              - name: app-container
                image: "registry.company.com/backend:2.1.0"
                imagePullPolicy: IfNotPresent
                ports:
                  - containerPort: 8080
                envFrom:
                  - configMapRef:
                      name: app-config
                  - secretRef:
                      name: app-db-secret-good
                resources:
                  requests:
                    memory: "128Mi"
                    cpu: "100m"
                  limits:
                    memory: "256Mi"
                    cpu: "500m"
                livenessProbe:
                  httpGet:
                    path: /healthz
                    port: 8080
                  initialDelaySeconds: 15
                  periodSeconds: 10
                readinessProbe:
                  httpGet:
                    path: /readyz
                    port: 8080
                  initialDelaySeconds: 5
                  periodSeconds: 5

Mengekspos Aplikasi Menggunakan Service dan Ingress #

Agar pengguna dari luar kluster dapat mengakses aplikasi backend kita, kita harus membuat jalur jaringan yang menghubungkan traffic internet luar menuju Pod di dalam kluster. Kita melakukan ini dengan menggabungkan objek Service (sebagai load balancer internal) dan objek Ingress (sebagai pengatur rute HTTP/HTTPS di gerbang depan).

flowchart TD
    Internet["Traffic Internet Luar"] --> Ingress["Ingress Resource (domain.com)"]
    Ingress -->|"Rute HTTP Path"| Service["Service (ClusterIP)"]
    Service -->|"Load Balancing"| Pod1["Pod Replika 1"]
    Service -->|"Load Balancing"| Pod2["Pod Replika 2"]

Berikut adalah playbook Ansible untuk mendeploy Service dan Ingress untuk aplikasi backend kita:

- name: Deploy Service Internal ClusterIP
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Service
      metadata:
        name: backend-service
        namespace: "{{ app_namespace }}"
        labels:
          app: backend-app
      spec:
        type: ClusterIP
        selector:
          app: backend-app
        ports:
          - protocol: TCP
            port: 80 # Port yang didengar di jaringan Service
            targetPort: 8080 # Port kontainer aplikasi kita

- name: Deploy Ingress untuk Perutean Domain Luar
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: app-ingress
        namespace: "{{ app_namespace }}"
        annotations:
          kubernetes.io/ingress.class: "nginx"
          nginx.ingress.kubernetes.io/ssl-redirect: "true"
      spec:
        rules:
          - host: "app.company.com"
            http:
              paths:
                - path: /
                  pathType: Prefix
                  backend:
                    service:
                      name: backend-service
                      port:
                        number: 80

Memastikan Siklus Rollout Selesai dengan k8s_rollout_status #

Salah satu kesalahan umum yang sering kita jumpai dalam otomatisasi deployment adalah menerapkan manifest lalu langsung menganggap proses selesai begitu saja (pola fire-and-forget). Jika image container baru kita mengalami crash loop saat booting, API Server Kubernetes akan tetap menerima manifest tersebut, namun aplikasi kita di lapangan akan hancur dan tidak dapat diakses.

Untuk mencegah hal ini, Ansible menyediakan modul k8s_rollout_status. Modul ini akan memblokir eksekusi playbook dan menunggu hingga seluruh replika Pod baru di bawah Deployment dilaporkan berjalan dengan sehat (sesuai spesifikasi readiness probe). Jika proses update mengalami stuck atau kegagalan, task ini akan mendeteksi kegagalan tersebut dan playbook akan berhenti sehingga kita dapat segera memicu tindakan perbaikan (rollback).

- name: Tunggu hingga proses rollout Deployment backend selesai dengan sukses
  kubernetes.core.k8s_rollout_status:
    kubeconfig: "{{ k8s_kubeconfig }}"
    name: backend-app
    namespace: "{{ app_namespace }}"
    kind: Deployment
    timeout: 300 # Tunggu maksimal 5 menit

- name: Dapatkan informasi Pod saat ini untuk verifikasi
  kubernetes.core.k8s_info:
    kubeconfig: "{{ k8s_kubeconfig }}"
    kind: Pod
    namespace: "{{ app_namespace }}"
    label_selectors:
      - "app=backend-app"
  register: active_pods_info

- name: Validasi status kesehatan seluruh Pod
  assert:
    that:
      - active_pods_info.resources | length == 3
      - active_pods_info.resources | selectattr('status.phase', 'equalto', 'Running') | list | length == 3
    fail_msg: "Bahaya! Beberapa Pod dilaporkan tidak berjalan dengan sehat!"
    success_msg: "Sukses! Seluruh replika Pod berjalan dengan status Running."

Pembersihan Resource yang Tidak Diperlukan (State Absent) #

Dalam siklus pengembangan aplikasi, adakalanya kita harus menghapus komponen lama atau mendelete resource environment lama agar tidak membuang kapasitas kluster kita. Kita dapat melakukannya dengan mudah menggunakan parameter state: absent pada modul k8s.

- name: Bersihkan Deployment lama yang sudah deprecated
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: absent
    kind: Deployment
    name: legacy-frontend
    namespace: "{{ app_namespace }}"

- name: Hapus seluruh Service lama berdasarkan label selector
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: absent
    kind: Service
    namespace: "{{ app_namespace }}"
    label_selectors:
      - "app=legacy-frontend"

Ringkasan #

  • Instal koleksi kubernetes.core dan pustaka Python kubernetes pada mesin kontrol Ansible sebagai prasyarat utama interaksi API.
  • Gunakan modul kubernetes.core.k8s secara deklaratif untuk mendeploy resource Kubernetes dengan validasi idempotensi bawaan.
  • Selalu pisahkan kode dari konfigurasi menggunakan objek ConfigMap untuk data umum dan Secret untuk data kredensial sensitif.
  • Wajib gunakan Ansible Vault untuk mengenkripsi password sensitif sebelum disimpan di repositori Git, dan deploy menggunakan parameter stringData.
  • Pastikan menyetel no_log: true pada task yang mendeploy data sensitif agar password tidak bocor ke output terminal log.
  • Terapkan strategi RollingUpdate dengan maxSurge: 1 dan maxUnavailable: 0 guna menjamin deployment aplikasi berjalan tanpa downtime (zero-downtime).
  • Hubungkan objek Service (ClusterIP) dan Ingress untuk mengekspos aplikasi internal kita secara aman ke jaringan domain publik luar.
  • Manfaatkan modul k8s_rollout_status setelah mendeploy Deployment guna memantau kesehatan transisi replika Pod baru sebelum melanjutkan alur kerja.

← Sebelumnya: Provision Cluster   Berikutnya: Helm →

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