Deploy Manifest

Deploy Manifest #

Cluster Kubernetes sudah berjalan — sekarang saatnya men-deploy aplikasi ke dalamnya. Kamu bisa menggunakan kubectl apply secara langsung, tapi Ansible memberikan sesuatu yang kubectl tidak punya: idempotency yang terjamin, templating Jinja2 untuk manifest yang dinamis, integrasi dengan Vault untuk secret management, dan kemampuan mengkoordinasikan deployment dengan perubahan infrastruktur lainnya. Artikel ini membahas cara men-deploy Kubernetes manifest menggunakan Ansible.

Setup: Koleksi yang Diperlukan #

ansible-galaxy collection install kubernetes.core
pip install kubernetes    # Library Python untuk Kubernetes API

Pastikan kubeconfig tersedia — Ansible butuh akses ke Kubernetes API:

# group_vars/k8s.yml
k8s_kubeconfig: "{{ playbook_dir }}/kubeconfig/admin.conf"

Apply Manifest dari File #

Module kubernetes.core.k8s adalah cara utama untuk mendeploy resource Kubernetes:

- name: Apply Deployment manifest
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    src: manifests/deployment.yml   # Path ke file manifest

- name: Apply manifest dari direktori
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    src: "{{ item }}"
  with_fileglob:
    - "manifests/*.yml"

Apply Manifest Inline (dengan Templating) #

Keunggulan terbesar Ansible dibanding kubectl murni adalah kemampuan templating Jinja2 langsung di dalam definisi manifest:

- name: Deploy Namespace
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Namespace
      metadata:
        name: "{{ app_namespace }}"
        labels:
          environment: "{{ env }}"
          managed-by: ansible

- name: Deploy ConfigMap dari variabel Ansible
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: "{{ app_name }}-config"
        namespace: "{{ app_namespace }}"
      data:
        APP_PORT: "{{ app_port | string }}"
        LOG_LEVEL: "{{ app_log_level }}"
        DATABASE_HOST: "{{ db_host }}"
        REDIS_HOST: "{{ redis_host }}"

Deploy Secret dari Ansible Vault #

Kubernetes Secret harus dibuat dari nilai yang tersimpan di Ansible Vault — jangan hardcode di manifest:

- name: Deploy database Secret dari Ansible Vault
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Secret
      metadata:
        name: "{{ app_name }}-db-secret"
        namespace: "{{ app_namespace }}"
      type: Opaque
      stringData:                           # stringData di-encode otomatis oleh Kubernetes
        DATABASE_URL: "postgresql://{{ db_user }}:{{ vault_db_password }}@{{ db_host }}:5432/{{ db_name }}"
        SECRET_KEY: "{{ vault_app_secret_key }}"
  no_log: true

Deploy Deployment dan Service #

- name: Deploy aplikasi ke Kubernetes
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: "{{ app_name }}"
        namespace: "{{ app_namespace }}"
        labels:
          app: "{{ app_name }}"
          version: "{{ app_version }}"
      spec:
        replicas: "{{ app_replicas | default(2) }}"
        selector:
          matchLabels:
            app: "{{ app_name }}"
        strategy:
          type: RollingUpdate
          rollingUpdate:
            maxSurge: 1
            maxUnavailable: 0         # Zero-downtime rolling update
        template:
          metadata:
            labels:
              app: "{{ app_name }}"
              version: "{{ app_version }}"
          spec:
            containers:
              - name: "{{ app_name }}"
                image: "{{ app_image }}:{{ app_version }}"
                ports:
                  - containerPort: "{{ app_port }}"
                envFrom:
                  - configMapRef:
                      name: "{{ app_name }}-config"
                  - secretRef:
                      name: "{{ app_name }}-db-secret"
                resources:
                  requests:
                    cpu: "{{ app_cpu_request | default('100m') }}"
                    memory: "{{ app_memory_request | default('128Mi') }}"
                  limits:
                    cpu: "{{ app_cpu_limit | default('500m') }}"
                    memory: "{{ app_memory_limit | default('512Mi') }}"
                livenessProbe:
                  httpGet:
                    path: /health
                    port: "{{ app_port }}"
                  initialDelaySeconds: 15
                  periodSeconds: 10
                readinessProbe:
                  httpGet:
                    path: /ready
                    port: "{{ app_port }}"
                  initialDelaySeconds: 5
                  periodSeconds: 5

- name: Deploy Service
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: present
    definition:
      apiVersion: v1
      kind: Service
      metadata:
        name: "{{ app_name }}"
        namespace: "{{ app_namespace }}"
      spec:
        selector:
          app: "{{ app_name }}"
        ports:
          - port: 80
            targetPort: "{{ app_port }}"
        type: ClusterIP

Setelah apply Deployment, tunggu sampai rollout selesai sebelum melanjutkan:

- name: Tunggu Deployment rollout selesai
  kubernetes.core.k8s_rollout_status:
    kubeconfig: "{{ k8s_kubeconfig }}"
    name: "{{ app_name }}"
    namespace: "{{ app_namespace }}"
    kind: Deployment
    timeout: 300        # Timeout 5 menit

- name: Verifikasi jumlah Pod yang berjalan
  kubernetes.core.k8s_info:
    kubeconfig: "{{ k8s_kubeconfig }}"
    kind: Pod
    namespace: "{{ app_namespace }}"
    label_selectors:
      - "app={{ app_name }}"
      - "version={{ app_version }}"
  register: pod_info

- name: Assert semua Pod dalam kondisi Running
  assert:
    that:
      - pod_info.resources | length == app_replicas
      - pod_info.resources | selectattr('status.phase', 'equalto', 'Running') | list | length == app_replicas
    fail_msg: "Tidak semua Pod dalam kondisi Running setelah deployment!"

Menghapus Resource #

# Hapus resource tertentu
- name: Hapus Deployment lama
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: absent
    kind: Deployment
    name: "{{ app_name }}-old"
    namespace: "{{ app_namespace }}"

# Hapus semua resource dengan label tertentu
- name: Hapus semua resource dengan label version lama
  kubernetes.core.k8s:
    kubeconfig: "{{ k8s_kubeconfig }}"
    state: absent
    kind: "{{ item }}"
    namespace: "{{ app_namespace }}"
    label_selectors:
      - "app={{ app_name }}"
      - "version={{ old_app_version }}"
  loop:
    - Deployment
    - Service
    - ConfigMap

Ringkasan #

  • Instal kubernetes.core collection dan library Python kubernetes sebelum menggunakan module k8s.
  • kubernetes.core.k8s dengan definition: mengizinkan templating Jinja2 penuh dalam manifest — nilai variabel Ansible langsung masuk ke manifest.
  • Buat Kubernetes Secret dari Ansible Vault menggunakan stringData — Kubernetes otomatis meng-encode ke base64, Ansible otomatis mendekripsi vault.
  • Selalu gunakan no_log: true untuk task yang mendeploy Secret agar nilai sensitif tidak muncul di log.
  • maxUnavailable: 0 di rolling update strategy memastikan zero-downtime deployment.
  • Gunakan k8s_rollout_status untuk menunggu deployment selesai sebelum melanjutkan ke task berikutnya.
  • Selalu definisikan resource requests dan limits untuk setiap container — tanpa ini, satu container bisa menghabiskan seluruh resource node.

← Sebelumnya: Provision Cluster   Berikutnya: Helm →

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