Testing

Testing #

Dalam paradigma modern Infrastructure as Code (IaC), kode infrastruktur kita harus diperlakukan sama persis dengan kode aplikasi perangkat lunak lainnya. Kita tidak boleh membiarkan playbook dideploy langsung ke server produksi tanpa melalui proses pengujian yang ketat. Mengubah konfigurasi firewall, merestart database cluster, atau memperbarui paket pustaka tanpa adanya jaminan testing otomatis adalah sebuah utang teknis (technical debt) yang sangat besar. Kesalahan kecil dalam penulisan variabel atau ketidakcocokan versi sistem operasi dapat dengan mudah menyebabkan mati totalnya layanan kita di lingkungan produksi. Artikel ini membahas strategi pengujian otomatis Ansible secara komprehensif untuk memastikan setiap perubahan kode aman sebelum diterapkan.

Mengapa Strategi Pengujian Kode Infrastruktur Penting #

Menguji kode automasi infrastruktur memiliki tantangannya sendiri karena sifatnya yang berinteraksi langsung dengan state sistem operasi target. Jika kita menguji kode aplikasi web, kita bisa dengan mudah membuat tiruan (mock) dari database. Namun, untuk menguji kode Ansible, kita harus memastikan bahwa ia benar-benar dapat memodifikasi konfigurasi Linux, mengonfigurasi user, dan memvalidasi service yang berjalan di sistem berkas nyata.

Untuk mempermudah pemahaman, kita dapat membagi tingkat pengujian Ansible ke dalam tiga lapisan (testing pyramid) utama:

  1. Lapisan Sintaks dan Gaya (Static Analysis): Memverifikasi kebenaran sintaksis YAML dan kepatuhan terhadap praktik terbaik (best practice) tanpa menjalankan playbook.
  2. Lapisan Simulasi (Dry-Run): Menjalankan playbook dengan mode pemeriksaan simulasi untuk melihat dampak perubahan sebelum benar-benar terjadi di managed node.
  3. Lapisan Pengujian Integrasi (Integration Testing): Membangun kontainer atau mesin virtual tiruan, mengeksekusi playbook secara riil, memverifikasi status akhir (state) server, dan menguji kemampuan idempotensi.

Analisis Statis dengan ansible-lint #

ansible-lint adalah alat analisis statis (linter) baris perintah yang dirancang khusus untuk menganalisis playbook, role, dan variabel Ansible. Linter ini bertindak sebagai pertahanan pertama (first line of defense) kita dalam mendeteksi bug potensial, gaya penulisan yang buruk, pelanggaran keamanan (seperti kebocoran kredensial), dan ketidakpatuhan terhadap standar modern.

Aturan Deteksi ansible-lint yang Penting #

  • name[play]: Memastikan setiap play dan task memiliki deskripsi nama yang jelas agar log eksekusi mudah dibaca.
  • command-instead-of-module: Mendeteksi jika kita menggunakan modul shell/command mentah untuk melakukan tugas yang sebenarnya sudah memiliki modul bawaan Ansible yang idempoten (misalnya memanggil shell: apt-get install alih-alih modul apt).
  • no-changed-when: Memperingatkan jika kita menggunakan modul command/shell tanpa menyertakan atribut changed_when, karena modul tersebut akan selalu melaporkan status “changed” (tidak idempoten) secara default.

Mengonfigurasi ansible-lint #

Kita dapat mengkustomisasi perilaku linter ini secara terperinci menggunakan berkas konfigurasi .ansible-lint yang ditempatkan di root direktori proyek kita:

# .ansible-lint
---
# Menentukan profil ketat tingkat produksi
profile: production

# Mengabaikan aturan tertentu yang tidak sesuai dengan konvensi tim kita
skip_list:
  - yaml[line-length]      # Lewati pemeriksaan panjang baris YAML
  - name[template]         # Abaikan peringatan penggunaan variabel di nama task

# Menentukan jalur direktori yang harus dilewati dari pemindaian linting
exclude_paths:
  - .cache/
  - molecule/
  - tests/

Untuk menjalankan linter pada seluruh proyek, kita cukup mengetikkan perintah berikut di terminal:

# BENAR: Menjalankan linting otomatis sebelum melakukan commit kode
ansible-lint .

Verifikasi Sintaksis dan Uji Coba Dry-Run #

Sebelum kita menargetkan server produksi yang aktif, Ansible menyediakan parameter bawaan yang sangat berguna untuk meminimalkan risiko kesalahan eksekusi: --syntax-check dan --check (Dry-run).

Verifikasi Sintaksis via --syntax-check #

Perintah ini akan membaca dan mengurai seluruh file YAML, memuat semua role dan template terkait, lalu memverifikasi apakah ada kesalahan ketik (typo) atau struktur lekukan (indentation) YAML yang tidak valid. Perintah ini berjalan dengan cepat karena tidak melakukan koneksi SSH ke target server.

# BENAR: Memeriksa validitas sintaks berkas konfigurasi playbook
ansible-playbook site.yml --syntax-check

Simulasi Perubahan via Dry-Run (--check) #

Menjalankan playbook dengan parameter -C atau --check akan menaruh Ansible dalam mode simulasi. Ansible akan mencoba menghubungi server target, membaca status sistem saat ini, dan melaporkan perubahan apa saja yang akan dilakukan oleh modul tanpa benar-benar memodifikasi apa pun di server. Jika kita menggabungkannya dengan opsi --diff, kita dapat melihat perbandingan teks (diff output) baris demi baris dari template konfigurasi yang akan dipasang.

# BENAR: Menjalankan simulasi kering disertai visualisasi perbedaan konfigurasi
ansible-playbook site.yml --check --diff

Mengatasi Limitasi Check Mode #

Beberapa task yang bergantung pada output dari task sebelumnya yang belum dieksekusi akan memicu error fatal saat dijalankan dalam check mode. Sebagai contoh, jika task pertama bertugas mengunduh file installer, dan task kedua bertugas mengekstrak file tersebut, maka task kedua akan gagal dalam mode --check karena file installer fisiknya belum pernah ada di disk.

Untuk mengatasi masalah ini, kita bisa menggunakan direktif check_mode: false untuk memaksa task tertentu tetap berjalan secara riil meskipun playbook dijalankan dengan parameter --check.

# ANTI-PATTERN: Membiarkan task pengecekan status mati saat simulasi
- name: Unduh berkas sertifikat SSL
  ansible.builtin.get_url:
    url: "https://kita.internal/cert.pem"
    dest: /etc/ssl/certs/app.pem

- name: Validasi masa kedaluwarsa sertifikat
  ansible.builtin.command: openssl x509 -enddate -noout -in /etc/ssl/certs/app.pem
  register: cert_check
  # JANGAN: Task ini akan crash saat check mode karena berkas /etc/ssl/certs/app.pem tidak terunduh secara riil.


# BENAR: Memaksa task persiapan tetap dieksekusi dengan check_mode: false
- name: Unduh berkas sertifikat SSL secara riil demi simulasi lancar
  ansible.builtin.get_url:
    url: "https://kita.internal/cert.pem"
    dest: /etc/ssl/certs/app.pem
  check_mode: false # ✓ Tetap jalankan task ini secara riil meskipun dalam mode simulasi

- name: Validasi masa kedaluwarsa sertifikat dengan aman
  ansible.builtin.command: openssl x509 -enddate -noout -in /etc/ssl/certs/app.pem
  register: cert_check
  failed_when: cert_check.rc != 0
  changed_when: false

Molecule: Framework Pengujian Integrasi untuk Role #

Molecule adalah framework pengujian paling populer dan direkomendasikan di ekosistem Ansible. Ia dirancang untuk mempermudah proses pembuatan pengujian integrasi yang terisolasi untuk masing-masing Ansible Role secara modular.

Molecule bekerja dengan alur kerja (workflow) otomatis: ia akan memicu pembuatan infrastruktur kontainer Docker (atau VM), menjalankan (converge) role kita di dalam kontainer tersebut, menjalankan asersi verifikasi untuk memastikan sistem terkonfigurasi dengan benar, menguji idempotensi (menjalankan role kedua kali untuk memastikan tidak ada perubahan status tambahan), dan menghancurkan (destroy) kembali kontainer pengujian setelah selesai.

Alur siklus hidup pengujian Molecule ini divisualisasikan secara runut lewat diagram berikut:

flowchart TD
    Start["Mulai: molecule test"] --> DestroyOld["1. Destroy (Bersihkan Kontainer Lama)"]
    DestroyOld --> Dependency["2. Dependency (Unduh Role Lain jika Butuh)"]
    Dependency --> Syntax["3. Syntax (Check Sintaks)"]
    Syntax --> Create["4. Create (Nyalakan Kontainer Pengujian Baru)"]
    Create --> Converge["5. Converge (Jalankan Playbook/Role secara Riil)"]
    Converge --> Idempotence["6. Idempotence (Jalankan Kedua Kali: Harus 'changed=0')"]
    Idempotence --> Verify["7. Verify (Jalankan Asersi Pengujian)"]
    Verify --> DestroyNew["8. Destroy (Hancurkan Kontainer Pengujian)"]
    DestroyNew --> End["Selesai: Sukses"]

Struktur Direktori Pengujian Molecule #

Saat kita menginisialisasi skenario Molecule di dalam sebuah role, ia akan melahirkan struktur direktori pengujian sebagai berikut:

roles/mysql_server/
  ├── tasks/
  │   └── main.yml
  └── molecule/
      └── default/
          ├── molecule.yml       # File konfigurasi driver dan platforms
          ├── converge.yml       # Playbook untuk menerapkan role
          └── verify.yml         # Playbook asersi pengujian

Berikut adalah contoh berkas konfigurasi utama molecule.yml yang menggunakan Docker sebagai driver pengujian kita:

# roles/mysql_server/molecule/default/molecule.yml
---
dependency:
  name: galaxy

driver:
  name: docker # ✓ Menggunakan kontainer Docker untuk performa pengujian yang sangat cepat

platforms:
  # Kita bisa menguji role pada berbagai variasi sistem operasi target secara paralel
  - name: ubuntu-22-node
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns: host
    command: /lib/systemd/systemd

provisioner:
  name: ansible
  playbooks:
    converge: converge.yml
    verify: verify.yml

verifier:
  name: ansible # Menggunakan playbook asersi Ansible untuk verifikasi status

File converge.yml bertugas memanggil role yang ingin kita uji:

# roles/mysql_server/molecule/default/converge.yml
---
- name: Jalankan Penerapan Role MySQL Server
  hosts: all
  become: true
  roles:
    - role: mysql_server

Setelah Molecule berhasil menerapkan role (converge), kita harus memvalidasi apakah server target benar-benar telah mencapai kondisi state yang kita harapkan. Kita menulis asersi pengujian ini di dalam file verify.yml menggunakan modul ansible.builtin.assert dan modul asersi lainnya.

Berikut adalah contoh penulisan skenario asersi verifikasi yang komprehensif untuk menguji keabsahan konfigurasi server database MySQL:

# roles/mysql_server/molecule/default/verify.yml
---
- name: Jalankan Asersi Verifikasi Konfigurasi MySQL
  hosts: all
  gather_facts: false
  tasks:
    - name: 1. Verifikasi paket mysql-server telah terpasang
      ansible.builtin.package_facts:
        manager: auto

    - name: Pastikan mysql-server ada di dalam daftar paket terinstal
      ansible.builtin.assert:
        that:
          - "'mysql-server' in ansible_facts.packages"
        fail_msg: "Paket mysql-server tidak terpasang di sistem!"

    - name: 2. Verifikasi service mysql berjalan dan disetel aktif (auto-start)
      ansible.builtin.service_facts:

    - name: Pastikan daemon mysql aktif
      ansible.builtin.assert:
        that:
          - "ansible_facts.services['mysql.service'].state == 'running'"
          - "ansible_facts.services['mysql.service'].status == 'enabled'"
        fail_msg: "Service daemon mysql tidak berjalan atau tidak aktif!"

    - name: 3. Verifikasi port default database (3306) mendengarkan koneksi
      ansible.builtin.wait_for:
        port: 3306
        timeout: 5
        state: started
      register: port_check

    - name: Pastikan port berhasil merespons koneksi
      ansible.builtin.assert:
        that:
          - "port_check.state == 'started'"
        fail_msg: "Port 3306 MySQL tertutup!"

    - name: 4. Verifikasi file konfigurasi utama terbuat dengan hak akses aman
      ansible.builtin.stat:
        path: /etc/mysql/mysql.conf.d/mysqld.cnf
      register: config_file

    - name: Pastikan file konfigurasi ada dan bermode 0640
      ansible.builtin.assert:
        that:
          - "config_file.stat.exists"
          - "config_file.stat.mode == '0640'"
          - "config_file.stat.pw_name == 'root'"
        fail_msg: "File konfigurasi mysql tidak ada atau hak aksesnya terlalu longgar!"

Dengan asersi di atas, kita menguji state sesungguhnya secara detail. Jika ada kesalahan konfigurasi perizinan file berkas atau port mati, pengujian integrasi Molecule akan langsung memunculkan tanda gagal merah (failed) secara otomatis.


Pengujian Lanjutan Menggunakan testinfra #

Bagi tim operasional yang sudah terbiasa dengan ekosistem bahasa pemrograman Python, menggunakan Ansible playbook untuk memverifikasi server terkadang terasa kurang fleksibel. Kita bisa menggunakan alternatif verifier bernama testinfra.

Testinfra adalah modul Python berbasis framework testing pytest yang dirancang khusus untuk memverifikasi konfigurasi server. Menulis test menggunakan Testinfra terasa sangat ekspresif, rapi, dan mudah dibaca oleh developer Python.

Berikut contoh penulisan skrip pengujian /tests/test_mysql.py menggunakan Testinfra:

# BENAR: Menulis pengujian infrastruktur terstruktur menggunakan pytest-testinfra
import pytest

def test_mysql_package_installed(host):
    """Memastikan paket mysql-server terpasang dengan benar di sistem operasi"""
    mysql = host.package("mysql-server")
    assert mysql.is_installed

def test_mysql_service_is_running_and_enabled(host):
    """Memastikan service mysql sedang menyala aktif dan diatur auto-start"""
    mysql_service = host.service("mysql")
    assert mysql_service.is_running
    assert mysql_service.is_enabled

def test_mysql_port_is_listening(host):
    """Memastikan port default 3306 terbuka untuk lalu lintas jaringan tcp"""
    socket = host.socket("tcp://127.0.0.1:3306")
    assert socket.is_listening

def test_mysql_config_file_permissions(host):
    """Memastikan file konfigurasi mysqld.cnf ada dan terlindungi dengan hak akses root"""
    config = host.file("/etc/mysql/mysql.conf.d/mysqld.cnf")
    assert config.exists
    assert config.user == "root"
    assert config.group == "root"
    assert config.mode == 0o640

Untuk menjalankan skrip pengujian Python tersebut ke mesin target, kita dapat memicu pytest secara langsung dari terminal cli kita:

# BENAR: Menjalankan testinfra menggunakan pytest via koneksi SSH target
pytest -v --hosts="ssh://[email protected]" tests/test_mysql.py

Integrasi CI/CD Pipeline (GitHub Actions) #

Strategi pengujian otomatis tidak akan memberikan manfaat maksimal jika tidak dijalankan secara rutin pada setiap perubahan kode. Kita harus mengintegrasikan pengujian ansible-lint dan Molecule ke dalam alur kerja CI/CD (Continuous Integration) di server repositori Git kita.

Berikut adalah konfigurasi alur kerja (workflow) lengkap untuk GitHub Actions yang akan memicu proses pengujian secara otomatis setiap kali ada anggota tim kita yang membuat Pull Request baru:

# .github/workflows/ansible-ci.yml
---
name: Ansible Quality Control CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  static_analysis:
    name: 1. Static Analysis (Linter)
    runs-on: ubuntu-latest
    steps:
      - name: Tarik kode repositori
        uses: actions/checkout@v4

      - name: Setup lingkungan Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Pasang dependensi linting
        run: |
          pip install --upgrade pip
          pip install ansible ansible-lint          

      - name: Jalankan analisis kode ansible-lint
        run: ansible-lint .

  integration_test:
    name: 2. Integration Test (Molecule)
    needs: static_analysis
    runs-on: ubuntu-latest
    steps:
      - name: Tarik kode repositori
        uses: actions/checkout@v4

      - name: Setup lingkungan Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Pasang Docker daemon
        uses: docker/setup-buildx-action@v3

      - name: Pasang dependensi pengujian Molecule
        run: |
          pip install --upgrade pip
          pip install ansible molecule molecule-plugins[docker]          

      - name: Jalankan Molecule test pada seluruh scenario
        run: |
          cd roles/mysql_server
          molecule test          
        env:
          PY_COLORS: "1"
          ANSIBLE_FORCE_COLOR: "1"

Dengan pipa automasi di atas, repositori utama kita akan terlindungi sepenuhnya dari bug. Pull Request tidak akan bisa di-merge jika linter mendeteksi gaya penulisan kode yang salah, atau jika asersi Molecule gagal saat diuji coba di kontainer Docker.


Ringkasan #

  • ansible-lint digunakan untuk menganalisis sintaks dan kepatuhan terhadap praktik terbaik (IaC best practice) secara statis tanpa perlu menghubungkan ke server target.
  • Gunakan --syntax-check dan --check (Dry-run) untuk menguji validitas file YAML serta menyimulasikan dampak perubahan konfigurasi sebelum dieksekusi di produksi.
  • check_mode: false penting dipasang pada task penyiapan (seperti unduh paket) agar proses simulasi dry-run tidak mengalami kegagalan palsu (false-positive).
  • Molecule adalah framework pengujian integrasi standar Ansible untuk menguji fungsionalitas Role secara terisolasi di dalam lingkungan kontainer Docker instan.
  • Proses pengujian Molecule mencakup siklus hidup yang lengkap: Create -> Converge -> Idempotence -> Verify -> Destroy.
  • Gunakan modul ansible.builtin.assert di dalam berkas verify.yml untuk memvalidasi port terbuka, status keaktifan daemon service, dan integritas file berkas.
  • testinfra menawarkan penulisan skrip pengujian berbasis Python pytest yang sangat ekspresif dan ramah pengembang untuk memverifikasi infrastruktur nyata.
  • Integrasikan seluruh rangkaian pengujian ke dalam pipeline CI/CD (seperti GitHub Actions) untuk memvalidasi setiap perubahan kode secara otomatis saat pembuatan Pull Request.

← Sebelumnya: Performance   Berikutnya: Logging →

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