Ansible’da Test ve Doğrulama: assert Modülü Kullanımı

Ansible playbook’larınızı çalıştırıyorsunuz, her şey yeşil gösteriyor, “ok” yazıyor, ama gerçekten beklediğiniz değişiklikler oldu mu? Servis ayakta mı? Konfigürasyon dosyası doğru içeriğe sahip mi? İşte bu soruların cevabını almak için Ansible’ın assert modülü tam da ihtiyacınız olan araç.

assert modülü, Ansible playbook’larınıza test ve doğrulama mantığı eklemenizi sağlar. Bir nevi playbook içindeki “sanity check” mekanizmasıdır. Koşullar sağlandığında sessizce geçer, sağlanmadığında anlamlı hata mesajlarıyla durur ve size tam olarak neyin yanlış gittiğini söyler. Özellikle büyük altyapılarda, üretim ortamlarına deployment yapmadan önce veya karmaşık konfigürasyon değişiklikleri sonrasında bu modül hayat kurtarır.

assert Modülü Nedir ve Neden Kullanmalıyız

Klasik sysadmin yaklaşımıyla düşünelim: Bir shell scripti yazıyorsunuz ve bir servisi başlattıktan sonra systemctl status nginx çıktısını kontrol ediyorsunuz. Bu kontrol başarısız olursa script’i durduruyorsunuz. assert modülü de tam olarak bunu yapıyor, ama Ansible’ın deklaratif yapısı içinde, çok daha okunabilir ve yönetilebilir bir şekilde.

Bu modülü kullanmanın birkaç temel sebebi var:

  • Erken hata yakalama: Sorun küçükken, yanlış bir konfigürasyonun tüm sisteme yayılmadan fark edilmesi
  • Belgelenmiş beklentiler: Kodunuz aynı zamanda beklentilerinizi de belgeler hale gelir
  • Güvenli deployment: Kritik adımlar arasında doğrulama noktaları koyabilirsiniz
  • Debugging kolaylığı: Anlamlı hata mesajları sayesinde neyin nerede bozulduğu hemen anlaşılır
  • Regression testing: Altyapınıza yönelik testler yazabilirsiniz

Temel Kullanım

assert modülünün en basit hali şu şekilde görünür:

---
- name: Temel assert kullanimi
  hosts: webservers
  tasks:
    - name: Nginx'in kurulu olup olmadigini dogrula
      ansible.builtin.assert:
        that:
          - ansible_facts['os_family'] == "Debian"
        fail_msg: "Bu playbook sadece Debian tabanli sistemlerde calisir!"
        success_msg: "Sistem Debian tabanli, devam ediyoruz."

Buradaki parametreleri açıklayalım:

  • that: Kontrol edilecek koşulları listeler. Jinja2 ifadeleri kullanabilirsiniz, birden fazla koşul AND mantığıyla değerlendirilir
  • fail_msg: Koşul sağlanmadığında gösterilecek hata mesajı (eski versiyonlarda msg olarak geçerdi)
  • success_msg: Koşul sağlandığında gösterilecek başarı mesajı (opsiyonel)
  • quiet: True yapıldığında başarılı assert’lerde çıktı üretmez, daha temiz log için kullanışlı

Gerçek Dünya Senaryosu 1: Deployment Öncesi Sistem Kontrolleri

Bir web uygulaması deployment’ı yapmadan önce sistemin hazır olup olmadığını kontrol eden bir playbook yazalım:

---
- name: Pre-deployment sistem dogrulamalari
  hosts: appservers
  gather_facts: yes
  vars:
    minimum_ram_mb: 2048
    minimum_disk_gb: 20
    required_ports:
      - 80
      - 443
      - 8080

  tasks:
    - name: RAM miktarini kontrol et
      ansible.builtin.assert:
        that:
          - ansible_memtotal_mb >= minimum_ram_mb
        fail_msg: >
          Yetersiz RAM! Mevcut: {{ ansible_memtotal_mb }}MB,
          Gerekli: {{ minimum_ram_mb }}MB
        success_msg: "RAM kontrolu gecti: {{ ansible_memtotal_mb }}MB mevcut"

    - name: Disk alani kontrol et
      ansible.builtin.assert:
        that:
          - (ansible_mounts | selectattr('mount', 'equalto', '/') | 
             list | first).size_available > (minimum_disk_gb * 1024 * 1024 * 1024)
        fail_msg: "/ bolumunde yeterli disk alani yok!"
        success_msg: "Disk alani yeterli"

    - name: Isletim sistemi versiyonunu dogrula
      ansible.builtin.assert:
        that:
          - ansible_distribution == "Ubuntu"
          - ansible_distribution_major_version | int >= 20
        fail_msg: >
          Desteklenmeyen OS: {{ ansible_distribution }} 
          {{ ansible_distribution_version }}. Ubuntu 20.04+ gerekli.

Bu örnekte birden fazla koşulu aynı that listesinde verdiğimizde hepsi AND mantığıyla çalışır. Ubuntu olması VE versiyon 20’den büyük olması gerekiyor.

Gerçek Dünya Senaryosu 2: Servis Konfigürasyon Doğrulaması

Nginx konfigürasyonu değiştirdikten sonra servisi doğrulayan bir örnek:

---
- name: Nginx konfigurasyonu dogrula
  hosts: webservers
  tasks:
    - name: Nginx'i kur ve yapilandir
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Nginx konfigürasyon dosyasini kopyala
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: reload nginx

    - name: Nginx konfigurasyonunu test et
      ansible.builtin.command: nginx -t
      register: nginx_config_test
      changed_when: false

    - name: Nginx konfig test sonucunu dogrula
      ansible.builtin.assert:
        that:
          - nginx_config_test.rc == 0
        fail_msg: >
          Nginx konfigurasyon testi basarisiz!
          Hata: {{ nginx_config_test.stderr }}
        success_msg: "Nginx konfigurasyonu gecerli"

    - name: Nginx servisinin calistigini dogrula
      ansible.builtin.service_facts:

    - name: Nginx servis durumunu kontrol et
      ansible.builtin.assert:
        that:
          - "'nginx' in services"
          - "services['nginx'].state == 'running'"
          - "services['nginx'].status == 'enabled'"
        fail_msg: "Nginx servisi beklenen durumda degil!"
        success_msg: "Nginx calisiyor ve aktif"

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Burada dikkat çekici bir nokta var: register ile komut çıktısını kaydediyoruz ve ardından assert ile bu çıktıyı analiz ediyoruz. Bu pattern çok güçlü bir kombinasyon.

Gerçek Dünya Senaryosu 3: Güvenlik Kontrolleri

Güvenlik odaklı kontroller yazmak, compliance gereksinimleri için çok önemli:

---
- name: Guvenlik dogrulamalari
  hosts: all
  become: yes
  tasks:
    - name: SSH konfigurasyonunu oku
      ansible.builtin.slurp:
        src: /etc/ssh/sshd_config
      register: sshd_config_raw

    - name: SSH konfig icerigini decode et
      ansible.builtin.set_fact:
        sshd_config: "{{ sshd_config_raw.content | b64decode }}"

    - name: Root SSH girisinin kapali oldugunu dogrula
      ansible.builtin.assert:
        that:
          - "'PermitRootLogin no' in sshd_config or 
             'PermitRootLogin prohibit-password' in sshd_config"
        fail_msg: >
          GUVENLIK UYARISI: Root SSH girisi aktif!
          /etc/ssh/sshd_config dosyasinda PermitRootLogin ayarini kontrol edin.
        success_msg: "Root SSH girisi devre disi"

    - name: Sifre ile SSH girisinin kapali oldugunu dogrula
      ansible.builtin.assert:
        that:
          - "'PasswordAuthentication no' in sshd_config"
        fail_msg: "GUVENLIK UYARISI: SSH sifre ile girise izin veriyor!"
        success_msg: "SSH sifre girisi devre disi"

    - name: UFW durumunu kontrol et
      ansible.builtin.command: ufw status
      register: ufw_status
      changed_when: false
      when: ansible_distribution == "Ubuntu"

    - name: Guvenliq duvari aktif mi dogrula
      ansible.builtin.assert:
        that:
          - "'Status: active' in ufw_status.stdout"
        fail_msg: "Guvenlik duvari (UFW) aktif degil!"
        success_msg: "Guvenlik duvari aktif"
      when: ansible_distribution == "Ubuntu"

assert ile fail_when Farkı

Burada sık karşılaşılan bir kavram karmaşasını açıklığa kavuşturalım. fail_when ve assert benzer işler yapsa da farklı kullanım senaryoları var:

fail_when daha çok bir task’ın başarısız sayılması gereken durumu belirtirken, assert bağımsız bir doğrulama noktası olarak kullanılır. assert her zaman önce koşulu değerlendirir ve sonucu bildirir. Anlamlı mesajlar ekleyebilme kapasitesi açısından assert çok daha esnek.

---
- name: fail_when vs assert karsilastirma
  hosts: localhost
  tasks:
    # fail_when yaklasimi
    - name: Dosya varligini kontrol et (fail_when ile)
      ansible.builtin.stat:
        path: /etc/myapp/config.yml
      register: config_file
      failed_when: not config_file.stat.exists

    # assert yaklasimi - daha aciklayici
    - name: Dosya varligini dogrula (assert ile)
      ansible.builtin.assert:
        that:
          - config_file.stat.exists
          - config_file.stat.size > 0
        fail_msg: >
          Konfigürasyon dosyasi /etc/myapp/config.yml bulunamadi
          veya bos! Lutfen uygulama kurulumunu kontrol edin.
        success_msg: "Konfigürasyon dosyasi mevcut ve bos degil"

Değişken Doğrulama: assert ile Input Validation

Playbook değişkenlerini doğrulamak, özellikle role yazarken çok kritik:

---
# roles/webapp/tasks/validate.yml
- name: Gerekli degiskenleri dogrula
  ansible.builtin.assert:
    that:
      - webapp_name is defined
      - webapp_name | length > 0
      - webapp_port is defined
      - webapp_port | int > 1024
      - webapp_port | int < 65535
      - webapp_environment in ['development', 'staging', 'production']
      - webapp_db_host is defined
      - webapp_db_name is defined
    fail_msg: >
      Gerekli degiskenler eksik veya gecersiz!
      Kontrol edilmesi gerekenler:
      - webapp_name: {{ webapp_name | default('TANIMLANMAMIS') }}
      - webapp_port: {{ webapp_port | default('TANIMLANMAMIS') }}
      - webapp_environment: {{ webapp_environment | default('TANIMLANMAMIS') }}

- name: Production ortaminda ekstra kontroller
  ansible.builtin.assert:
    that:
      - webapp_ssl_cert is defined
      - webapp_ssl_key is defined
      - webapp_backup_enabled | bool == true
    fail_msg: >
      Production ortaminda SSL sertifikasi ve yedekleme zorunlu!
  when: webapp_environment == 'production'

Bu pattern özellikle başkalarının kullanacağı role’ler yazarken çok değerli. Role’ü kullanan kişi yanlış değer girdiğinde, anlamlı bir hata mesajı görür.

Kompleks Koşullar ve Jinja2 Filtreleri

assert modülünün gerçek gücü, Jinja2’nin tüm filtre ve test kapasitesini kullanabilmesinden geliyor:

---
- name: Kompleks assert ornekleri
  hosts: dbservers
  vars:
    allowed_mysql_versions:
      - "8.0"
      - "8.1"
      - "8.2"

  tasks:
    - name: MySQL versiyon bilgisini al
      ansible.builtin.command: mysql --version
      register: mysql_version_output
      changed_when: false

    - name: MySQL versiyon formatini isle
      ansible.builtin.set_fact:
        mysql_major_minor: >-
          {{ mysql_version_output.stdout | 
             regex_search('(d+.d+)', '1') | first }}

    - name: MySQL versiyonunu dogrula
      ansible.builtin.assert:
        that:
          - mysql_major_minor in allowed_mysql_versions
        fail_msg: >
          Desteklenmeyen MySQL versiyonu: {{ mysql_major_minor }}
          Desteklenen versiyonlar: {{ allowed_mysql_versions | join(', ') }}

    - name: Veritabani baglantisini test et
      ansible.builtin.command: >
        mysql -u {{ db_user }} -p{{ db_password }} 
        -h {{ db_host }} -e "SELECT 1"
      register: db_connection_test
      changed_when: false
      no_log: true

    - name: Veritabani baglantisini dogrula
      ansible.builtin.assert:
        that:
          - db_connection_test.rc == 0
        fail_msg: >
          Veritabanina baglanamiyorum!
          Host: {{ db_host }}, User: {{ db_user }}
          Lutfen baglanti bilgilerini ve ag ayarlarini kontrol edin.
        success_msg: "Veritabani baglantisi basarili"

quiet Parametresi ile Temiz Çıktı

Çok fazla assert kullandığınızda terminal çıktısı gürültülü olabilir. quiet: true parametresi başarılı assert’lerin çıktısını bastırır:

---
- name: Sessiz dogrulama ornegi
  hosts: all
  tasks:
    - name: Temel sistem kontrolleri (sessiz)
      ansible.builtin.assert:
        that:
          - ansible_python_version is version('3.6', '>=')
        fail_msg: "Python 3.6+ gerekli, mevcut: {{ ansible_python_version }}"
        quiet: true

    - name: Kritik guvenlik kontrolu (sesli - gormek istiyoruz)
      ansible.builtin.assert:
        that:
          - ansible_selinux.status == 'enabled' or 
            ansible_distribution == 'Ubuntu'
        fail_msg: "SELinux aktif degil!"
        success_msg: "Guvenlik kontrolu gecti"
        quiet: false

Genel tavsiye: rutin kontroller için quiet: true, güvenlik ve kritik kontroller için quiet: false kullanın.

assert ile Test Driven Infrastructure

assert modülünü kullanarak gerçek anlamda altyapı testi yapabilirsiniz. Bu yaklaşım, “Testinfra” veya “ServerSpec” gibi araçların yaptığı şeyi Ansible içinde native olarak yapmanızı sağlar:

---
# tests/webserver_tests.yml
- name: Web sunucu kabul testleri
  hosts: webservers
  gather_facts: yes
  tasks:
    - name: HTTP portunu kontrol et
      ansible.builtin.wait_for:
        port: 80
        timeout: 5
      register: http_port_check
      ignore_errors: yes

    - name: HTTP portu acik mi
      ansible.builtin.assert:
        that:
          - http_port_check.failed == false
        fail_msg: "HTTP portu (80) kapali veya servis cevap vermiyor!"

    - name: HTTPS portunu kontrol et
      ansible.builtin.wait_for:
        port: 443
        timeout: 5
      register: https_port_check
      ignore_errors: yes

    - name: HTTPS portu acik mi
      ansible.builtin.assert:
        that:
          - https_port_check.failed == false
        fail_msg: "HTTPS portu (443) kapali!"

    - name: Web uygulamasina HTTP istegi gonder
      ansible.builtin.uri:
        url: "http://{{ ansible_default_ipv4.address }}/health"
        method: GET
        status_code: 200
        timeout: 10
      register: health_check
      delegate_to: localhost

    - name: Health check endpoint dogrula
      ansible.builtin.assert:
        that:
          - health_check.status == 200
          - health_check.json.status == 'healthy'
        fail_msg: >
          Uygulama health check basarisiz!
          HTTP Status: {{ health_check.status }}
          Response: {{ health_check.json | default('Yanit alinamadi') }}
        success_msg: "Uygulama saglikli calisiyor"

Hata Ayıklama İpuçları

assert ile çalışırken bazı pratik ipuçları:

verbose modda çalıştırın: ansible-playbook -v ile çalıştırdığınızda başarılı assert’lerin detaylarını da görürsünüz.

Koşulları adlandırın: Uzun that listeleri yerine, koşulları önce set_fact ile anlamlı isimler altında saklayın:

---
- name: Koşulları adlandirarak assert kullanimi
  hosts: all
  tasks:
    - name: Sistem metriklerini topla
      ansible.builtin.set_fact:
        ram_yeterli: "{{ ansible_memtotal_mb >= 2048 }}"
        swap_aktif: "{{ ansible_swaptotal_mb > 0 }}"
        x86_64: "{{ ansible_architecture == 'x86_64' }}"

    - name: Donanım gereksinimlerini kontrol et
      ansible.builtin.assert:
        that:
          - ram_yeterli
          - swap_aktif
          - x86_64
        fail_msg: >
          Donanim gereksinimleri karsilanmiyor!
          RAM yeterli: {{ ram_yeterli }}
          Swap aktif: {{ swap_aktif }}
          x86_64 mimari: {{ x86_64 }}

Bu yaklaşım hem daha okunabilir hem de hata mesajında hangi koşulun neden başarısız olduğunu açıkça gösteriyor.

ignore_errors ile kombinasyon: Bazen bir assert başarısız olsa bile devam etmek istersiniz, özellikle raporlama amaçlı:

    - name: Opsiyonel ozellik kontrolu
      ansible.builtin.assert:
        that:
          - "'avx2' in ansible_processor_flags"
        fail_msg: "AVX2 destegi yok, bazi ozellikler devre disi olacak"
        success_msg: "AVX2 destegi mevcut"
      ignore_errors: yes
      register: avx2_check

    - name: AVX2 sonucunu kaydet
      ansible.builtin.set_fact:
        avx2_available: "{{ avx2_check.failed == false }}"

Sonuç

assert modülü, Ansible playbook’larınızı sadece konfigürasyon uygulayan araçlardan, kendi kendini doğrulayan akıllı otomasyon sistemlerine dönüştürür. Küçük projelerde bile kullanmaya başlamanızı tavsiye ederim çünkü alışkanlık kazanmak önemli.

Pratikte şunu görüyorum: assert kullanan playbook’lar başlangıçta biraz daha uzun oluyor, ama üretim ortamında bir şeyler ters gittiğinde saatlerce debug yapmak yerine dakikalar içinde sorunu bulabiliyorsunuz. Özellikle ekip olarak çalıştığınızda, başka birinin yazdığı playbook’u çalıştırırken neyin beklendiğini anlamak çok daha kolay oluyor.

Ansible’da infrastructure as code yaklaşımının tam anlamıyla olgunlaşması için testlerin de kod gibi ele alınması gerekiyor. assert modülü bu olgunluğun en pratik ve erişilebilir yolu. Bir sonraki playbook’unuzu yazarken, her kritik adımın ardından bir assert bloğu eklemeyi alışkanlık haline getirin. Birkaç ay sonra geriye baktığınızda, bu alışkanlığın size ne kadar zaman ve stres kurtardığını göreceksiniz.

Yorum yapın