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
kubernetesdiinstal pada lingkungan Python yang sama dengan yang digunakan oleh Ansible. Jika kita menggunakan virtual environment Python, pastikan untuk mengaktifkannya terlebih dahulu sebelum menjalankan instalasipip.
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 ke1agar 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 ke0untuk 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.coredan pustaka Pythonkubernetespada mesin kontrol Ansible sebagai prasyarat utama interaksi API.- Gunakan modul
kubernetes.core.k8ssecara 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: truepada task yang mendeploy data sensitif agar password tidak bocor ke output terminal log.- Terapkan strategi RollingUpdate dengan
maxSurge: 1danmaxUnavailable: 0guna 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_statussetelah mendeploy Deployment guna memantau kesehatan transisi replika Pod baru sebelum melanjutkan alur kerja.