Ansible’da Koşullu Görevler: when Direktifi ile Akış Kontrolü

Ansible playbook’larınızı yazdıkça kaçınılmaz olarak şu soruyla karşılaşırsınız: “Bu görev her sunucuda çalışmalı mı, yoksa sadece belirli koşullarda mı?” İşte tam bu noktada when direktifi devreye giriyor. Basit görünen bu tek kelime, Ansible’ı gerçek anlamda güçlü kılan özelliklerden biri. Bugün when ile akış kontrolünü, gerçek dünya senaryolarıyla birlikte derinlemesine inceleyeceğiz.

when Direktifi Nedir ve Neden Kullanırız?

Ansible varsayılan olarak bir play’deki tüm görevleri, tanımlı tüm host’larda sırasıyla çalıştırır. Ama gerçek dünya bu kadar düz değil. Elinde hem Ubuntu hem CentOS sunucuları var, hem production hem de staging ortamları mevcut, bazı sunucularda belirli servisler kurulu iken diğerlerinde yok. Bu kaotik gerçekliği yönetmek için koşullu mantığa ihtiyaç duyarsınız.

when direktifi, bir görevin çalışıp çalışmayacağını belirleyen bir Jinja2 ifadesidir. Koşul doğru (true) olduğunda görev çalışır, yanlış (false) olduğunda görev atlanır ve Ansible bunu playbook çıktısında skipping olarak gösterir.

Temel sözdizimi son derece basit:

- name: Sadece Debian sistemlerde Apache kur
  apt:
    name: apache2
    state: present
  when: ansible_facts['os_family'] == "Debian"

Burada dikkat edilmesi gereken önemli bir nokta var: when ifadesinde değişkenleri çift süslü parantez {{ }} içine almıyoruz. Çünkü when zaten bir Jinja2 ifadesi olarak değerlendiriliyor, ekstra paranteze gerek yok.

Ansible Facts ile Koşullu Kontrol

En yaygın kullanım senaryosu, sistem bilgilerini (facts) kullanarak platform bazlı koşullar oluşturmak. setup modülünden gelen bu bilgiler altın değerinde.

İşletim Sistemi Bazlı Koşullar

---
- name: Web sunucu kurulumu
  hosts: webservers
  become: yes
  tasks:
    - name: Apache kurulumu - Debian/Ubuntu
      apt:
        name: apache2
        state: present
        update_cache: yes
      when: ansible_facts['os_family'] == "Debian"

    - name: Apache kurulumu - RedHat/CentOS
      yum:
        name: httpd
        state: present
      when: ansible_facts['os_family'] == "RedHat"

    - name: Apache servisini başlat - Debian
      service:
        name: apache2
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == "Debian"

    - name: Apache servisini başlat - RedHat
      service:
        name: httpd
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == "RedHat"

Bu klasik bir senaryo ama biraz daha akıllıca yapabilirsiniz. Değişkenlerle birleştirerek kodu sadeleştirelim:

---
- name: Web sunucu kurulumu - gelişmiş versiyon
  hosts: webservers
  become: yes
  vars:
    apache_package:
      Debian: apache2
      RedHat: httpd
    apache_service:
      Debian: apache2
      RedHat: httpd

  tasks:
    - name: Apache paketini kur
      package:
        name: "{{ apache_package[ansible_facts['os_family']] }}"
        state: present
      when: ansible_facts['os_family'] in ['Debian', 'RedHat']

    - name: Apache servisini başlat
      service:
        name: "{{ apache_service[ansible_facts['os_family']] }}"
        state: started
        enabled: yes
      when: ansible_facts['os_family'] in ['Debian', 'RedHat']

Kernel Versiyonu ve Donanım Koşulları

Bazen belirli bir kernel versiyonu üzerinde çalışmanız gerekebilir. Örneğin belirli bir güvenlik yaması sadece 5.x kernel’lerde geçerli:

- name: Yeni kernel özelliğini etkinleştir
  sysctl:
    name: net.core.default_qdisc
    value: fq
    state: present
  when: ansible_kernel.split('.')[0] | int >= 5

- name: Yüksek RAM için büyük sayfa boyutu ayarla
  sysctl:
    name: vm.nr_hugepages
    value: "{{ (ansible_memtotal_mb / 2048) | int }}"
    state: present
  when: ansible_memtotal_mb > 16384

Değişken Tabanlı Koşullar

Facts dışında, kendi tanımladığınız değişkenleri de koşul olarak kullanabilirsiniz. Ortam yönetimi için bu çok işe yarıyor.

---
- name: Ortam bazlı uygulama dağıtımı
  hosts: all
  vars:
    environment: production
    debug_mode: false
    backup_enabled: true

  tasks:
    - name: Debug loglarını etkinleştir
      lineinfile:
        path: /etc/myapp/config.ini
        line: "debug = true"
        state: present
      when: debug_mode | bool

    - name: Yedekleme scriptini çalıştır
      script: /scripts/backup.sh
      when:
        - backup_enabled | bool
        - environment == "production"

    - name: Performans ayarlarını uygula
      template:
        src: prod_performance.conf.j2
        dest: /etc/myapp/performance.conf
      when: environment in ['production', 'staging']

    - name: Test verisi yükle
      command: /usr/local/bin/load_test_data.sh
      when: environment == "development"

Inventory Değişkenleriyle Koşullar

Gerçek dünyada host’larınızı gruplarken inventory’de değişkenler tanımlarsınız. Bu değişkenlerle güçlü koşullar kurabilirsiniz:

# inventory/hosts.yml
all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.10
          role: primary
          ssl_enabled: true
        web02:
          ansible_host: 192.168.1.11
          role: secondary
          ssl_enabled: false
    dbservers:
      hosts:
        db01:
          ansible_host: 192.168.1.20
          db_role: master
        db02:
          ansible_host: 192.168.1.21
          db_role: slave
---
- name: Web sunucu SSL yapılandırması
  hosts: webservers
  become: yes
  tasks:
    - name: SSL sertifikası kopyala
      copy:
        src: ssl/cert.pem
        dest: /etc/ssl/certs/myapp.pem
      when: ssl_enabled | default(false) | bool

    - name: SSL konfigürasyonunu etkinleştir
      template:
        src: apache-ssl.conf.j2
        dest: /etc/apache2/sites-enabled/ssl.conf
      when:
        - ssl_enabled | default(false) | bool
        - ansible_facts['os_family'] == "Debian"

    - name: Primary sunucu özel ayarları
      template:
        src: primary-config.j2
        dest: /etc/myapp/primary.conf
      when: role is defined and role == "primary"

Çoklu Koşullar: AND ve OR Mantığı

Birden fazla koşulu birleştirmeniz gerektiğinde iki yöntem var.

AND Koşulları (liste formatı)

Liste formatında yazdığınızda tüm koşullar AND ile birleştirilir. Hepsi doğru olmak zorunda:

- name: Production veritabanı yedeklemesi
  command: /usr/local/bin/db_backup.sh
  when:
    - environment == "production"
    - ansible_facts['os_family'] == "RedHat"
    - db_role == "master"
    - backup_hour == ansible_date_time.hour

OR Koşulları

OR mantığı için or anahtar kelimesini kullanırsınız:

- name: Firewall servisini yeniden başlat
  service:
    name: firewalld
    state: restarted
  when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "Rocky"

# Daha temiz yazımı:
- name: Firewall servisini yeniden başlat
  service:
    name: firewalld
    state: restarted
  when: ansible_facts['distribution'] in ["CentOS", "Rocky", "AlmaLinux"]

Karma Koşullar

AND ve OR’u birleştirdiğinizde parantez kullanımı kritik önem kazanıyor:

- name: Özel monitoring kurulumu
  include_tasks: monitoring_setup.yml
  when: >
    (environment == "production" and ansible_memtotal_mb > 8192)
    or
    (environment == "staging" and monitoring_forced | default(false) | bool)

> karakteri YAML’da çok satırlı string için kullanılıyor. Bu sayede uzun koşulları okunabilir şekilde yazabiliyorsunuz.

register ve when Kombinasyonu

En güçlü kullanım senaryolarından biri, bir görevin çıktısını register ile saklayıp sonraki görevde when ile kontrol etmek. Bu pattern gerçekten çok işe yarıyor.

---
- name: Servis durumuna göre işlem yap
  hosts: webservers
  become: yes
  tasks:
    - name: Apache servis durumunu kontrol et
      command: systemctl is-active apache2
      register: apache_status
      ignore_errors: yes
      changed_when: false

    - name: Apache çalışmıyorsa yeniden başlat
      service:
        name: apache2
        state: started
      when: apache_status.rc != 0

    - name: Apache loglarını temizle (sadece servis çalışıyorsa)
      file:
        path: /var/log/apache2/access.log
        state: absent
      when: apache_status.rc == 0

    - name: Disk kullanımını kontrol et
      command: df -h /var/log
      register: disk_usage
      changed_when: false

    - name: Log rotasyonu zorla (disk %80 doluysa)
      command: logrotate -f /etc/logrotate.conf
      when: "'8' in disk_usage.stdout or '9' in disk_usage.stdout"

Daha gerçekçi bir senaryo: Bir paket kurulu mu değil mi kontrol edip buna göre işlem yapmak:

---
- name: Node.js uygulama dağıtımı
  hosts: appservers
  become: yes
  tasks:
    - name: Node.js kurulu mu kontrol et
      command: node --version
      register: node_check
      ignore_errors: yes
      changed_when: false

    - name: Node.js kurulu değilse kur
      apt:
        name: nodejs
        state: present
        update_cache: yes
      when:
        - node_check.rc != 0
        - ansible_facts['os_family'] == "Debian"

    - name: Mevcut Node versiyonunu göster
      debug:
        msg: "Node.js versiyonu: {{ node_check.stdout }}"
      when: node_check.rc == 0

    - name: PM2 global olarak kurulu mu kontrol et
      command: pm2 --version
      register: pm2_check
      ignore_errors: yes
      changed_when: false

    - name: PM2 kur
      npm:
        name: pm2
        global: yes
        state: present
      when: pm2_check.rc != 0

    - name: Uygulamayı PM2 ile başlat
      command: pm2 start /opt/myapp/app.js --name myapp
      when:
        - pm2_check.rc == 0 or pm2_check.rc != 0
        - node_check.rc == 0

Döngülerle Koşullu Görevler

loop ile when birlikte kullanıldığında dikkatli olmak lazım. when koşulu her döngü iterasyonu için ayrı ayrı değerlendirilir:

---
- name: Seçici paket kurulumu
  hosts: all
  become: yes
  vars:
    packages:
      - name: nginx
        install_on: webservers
        required: true
      - name: postgresql
        install_on: dbservers
        required: true
      - name: redis
        install_on: webservers
        required: false
      - name: htop
        install_on: all
        required: true

  tasks:
    - name: Paketleri koşullu olarak kur
      package:
        name: "{{ item.name }}"
        state: present
      loop: "{{ packages }}"
      when:
        - item.required | bool
        - item.install_on == "all" or item.install_on == group_names[0]

Handler’larla Koşullu Tetikleme

Handler’lar zaten değişiklik durumunda çalışır ama ekstra koşullar ekleyebilirsiniz:

---
- name: Nginx yapılandırma yönetimi
  hosts: webservers
  become: yes

  handlers:
    - name: nginx yeniden başlat
      service:
        name: nginx
        state: restarted
      when: nginx_restart_enabled | default(true) | bool

    - name: nginx konfigürasyonu doğrula
      command: nginx -t
      register: nginx_test
      changed_when: false

  tasks:
    - name: Nginx ana konfigürasyonunu dağıt
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        validate: nginx -t -c %s
      notify:
        - nginx konfigürasyonu doğrula
        - nginx yeniden başlat
      when: ansible_facts['os_family'] in ['Debian', 'RedHat']

    - name: Sanal host konfigürasyonunu dağıt
      template:
        src: vhost.conf.j2
        dest: "/etc/nginx/sites-enabled/{{ domain_name }}.conf"
      notify: nginx yeniden başlat
      when:
        - domain_name is defined
        - ssl_certificate_exists | default(false) | bool

Gerçek Dünya Senaryosu: Çok Katmanlı Deployment Playbook

Şimdiye kadar öğrendiklerimizi birleştiren kapsamlı bir örnek:

---
- name: Uygulama deployment - tam senaryo
  hosts: all
  become: yes
  vars:
    app_version: "2.1.0"
    deploy_environment: "{{ env | default('staging') }}"
    rollback_enabled: false
    health_check_url: "http://localhost:8080/health"

  pre_tasks:
    - name: Deployment öncesi disk alanı kontrolü
      command: df -BG /opt
      register: disk_info
      changed_when: false

    - name: Yetersiz disk alanı uyarısı
      fail:
        msg: "Yetersiz disk alanı! En az 5GB gerekli."
      when: disk_info.stdout | regex_search('([0-9]+)G', '\1') | first | int < 5

  tasks:
    - name: Mevcut uygulama versiyonunu kontrol et
      command: cat /opt/myapp/VERSION
      register: current_version
      ignore_errors: yes
      changed_when: false

    - name: Versiyon zaten güncel, atla
      debug:
        msg: "Uygulama zaten {{ app_version }} versiyonunda çalışıyor."
      when:
        - current_version.rc == 0
        - current_version.stdout == app_version

    - name: Eski versiyonu yedekle
      archive:
        path: /opt/myapp
        dest: "/opt/backups/myapp-{{ current_version.stdout }}-{{ ansible_date_time.date }}.tar.gz"
      when:
        - current_version.rc == 0
        - current_version.stdout != app_version
        - deploy_environment == "production"

    - name: Yeni versiyonu indir
      get_url:
        url: "https://releases.example.com/myapp-{{ app_version }}.tar.gz"
        dest: /tmp/myapp-new.tar.gz
      when:
        - current_version.rc != 0 or current_version.stdout != app_version

    - name: Uygulamayı dağıt
      unarchive:
        src: /tmp/myapp-new.tar.gz
        dest: /opt/myapp
        remote_src: yes
      when:
        - current_version.rc != 0 or current_version.stdout != app_version

    - name: Production konfigürasyonu uygula
      template:
        src: "config-{{ deploy_environment }}.j2"
        dest: /opt/myapp/config/app.conf
      when: deploy_environment in ['production', 'staging']

    - name: Uygulamayı yeniden başlat
      service:
        name: myapp
        state: restarted
      when:
        - current_version.rc != 0 or current_version.stdout != app_version

    - name: Health check yap
      uri:
        url: "{{ health_check_url }}"
        method: GET
        status_code: 200
      register: health_check
      retries: 5
      delay: 10
      until: health_check.status == 200
      when: deploy_environment == "production"

    - name: Deployment başarısız, rollback yap
      command: /opt/scripts/rollback.sh
      when:
        - health_check is defined
        - health_check.status != 200
        - rollback_enabled | bool

Sık Yapılan Hatalar ve İpuçları

Birkaç kritik noktayı paylaşmak istiyorum:

  • Tanımsız değişken hatası: Bir değişkenin tanımlı olup olmadığından emin değilseniz is defined kontrolü yapın. when: myvar is defined and myvar == "value" şeklinde.
  • Boolean karmaşası: YAML’da true, True, yes, on hepsi boolean true ama Ansible’a string olarak geçebilir. Güvenli taraf için | bool filtresini kullanın.
  • Facts toplamayı kapatmayın: gather_facts: no kullanıyorsanız ansible_facts erişemezsiniz. Bunu bilmeden when: ansible_facts['os_family'] == "Debian" yazarsanız hata alırsınız.
  • Koşul her zaman string değil: when: "{{ myvar }}" yerine when: myvar yazın. String interpolasyon bazen beklenmedik davranışlara yol açar.
  • register sonuçlarını debug edin: Beklenmeyen davranış görüyorsanız debug: var=kayit_degiskeni ile sonucu inceleyin.
# Değişken tanımlı mı kontrolü
- name: İsteğe bağlı konfigürasyon
  template:
    src: optional.conf.j2
    dest: /etc/myapp/optional.conf
  when:
    - optional_feature is defined
    - optional_feature | bool

# Versiyon karşılaştırması
- name: Yeni özelliği etkinleştir (versiyon 2.0+)
  command: myapp --enable-new-feature
  when: app_version is version('2.0', '>=')

Sonuç

when direktifi, Ansible playbook’larınızı gerçek dünya karmaşıklığına uyum sağlayabilen esnek araçlara dönüştürüyor. Başlangıçta basit platform kontrollerinden başlayıp, register ile dinamik koşullara, çoklu mantık operatörlerine ve kapsamlı deployment senaryolarına kadar uzanan geniş bir kullanım alanı var.

En önemli tavsiyem: Koşullarınızı mümkün olduğunca basit tutun. Karmaşık when ifadeleri genellikle playbook yapınızın yeniden düzenlenmesi gerektiğinin işareti. Roles ve include_tasks kullanarak koşullu mantığı daha yönetilebilir parçalara bölmek uzun vadede çok daha iyi sonuç veriyor.

when ile akış kontrolüne hakim olduğunuzda, Ansible’ın gerçek gücünü keşfetmiş oluyorsunuz. Artık tek bir playbook ile onlarca farklı ortamı, işletim sistemini ve yapılandırma senaryosunu yönetebilir hale geliyorsunuz. Bu da sysadmin olarak en değerli varlığınızı koruyorsunuz: zamanınızı.

Yorum yapın