Ansible ile çalışırken en sık yapılan hatalardan biri, her görevin ardından servisi yeniden başlatmak için ayrı bir task yazmak. Bu hem verimsiz hem de tehlikeli. Bir konfigürasyon değişmemişse neden servisi gereksiz yere restart edelim ki? İşte tam bu noktada handler kavramı devreye giriyor ve Ansible’ın en zarif özelliklerinden biri haline geliyor.
Handler Nedir ve Neden Kullanırız?
Handler’lar, yalnızca bir görev tarafından tetiklendiğinde çalışan özel Ansible görevleridir. Normal task’lardan temel farkı şu: bir handler, play boyunca kaç kez tetiklenirse tetiklensin, yalnızca bir kez çalışır ve bu çalışma play’in sonunda gerçekleşir.
Bunu gerçek hayattan bir örnekle açıklayalım. Nginx konfigürasyonunu değiştirdiniz, SSL sertifikasını güncellediniz ve bir upstream bloğu eklediniz. Bu üç görev de “nginx reload” istiyor. Handler olmadan bu üç task ardı ardına nginx’i yeniden yüklerdi. Handler ile bu işlem en sona ertelenir ve yalnızca bir kez yapılır.
Handler kullanmanın temel faydaları:
- İdempotency korunur: Değişiklik yoksa servis restart edilmez
- Performans artar: Gereksiz servis yeniden başlatmaları önlenir
- Güvenilirlik sağlanır: Play sonunda tek bir temiz restart yapılır
- Bağımlılıklar yönetilir: Birden fazla değişiklik tek bir handler ile toplanır
Temel Handler Sözdizimi
Handler tanımlamak için playbook içinde handlers bloğu kullanılır. Yapısı task’larla neredeyse aynıdır, sadece name değeri hem tanımlayıcı hem de tetikleyici olarak kullanılır.
---
- name: Nginx Kurulum ve Yapılandırma
hosts: webservers
become: yes
tasks:
- name: Nginx konfigurasyonunu kopyala
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: nginx'i yeniden baslat
handlers:
- name: nginx'i yeniden baslat
ansible.builtin.service:
name: nginx
state: restarted
Burada kritik nokta notify direktifidir. Task başarılı olduğunda ve bir değişiklik gerçekleştiğinde (changed durumu), notify ile belirtilen handler kuyruğa eklenir. Task herhangi bir değişiklik yapmazsa (ok durumu), handler hiç çalışmaz.
Birden Fazla Handler Kullanımı
Gerçek dünya senaryolarında tek bir handler yetmez. Farklı servisler için farklı handler’lar tanımlamak gerekir.
---
- name: LAMP Stack Yapilandirmasi
hosts: appservers
become: yes
tasks:
- name: Apache konfigurasyonunu guncelle
ansible.builtin.template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify: apache'yi yeniden yukle
- name: PHP-FPM havuz konfigurasyonu
ansible.builtin.template:
src: www.conf.j2
dest: /etc/php-fpm.d/www.conf
notify: php-fpm'i yeniden baslat
- name: MySQL konfigurasyonunu guncelle
ansible.builtin.template:
src: my.cnf.j2
dest: /etc/my.cnf
notify: mysql'i yeniden baslat
handlers:
- name: apache'yi yeniden yukle
ansible.builtin.service:
name: httpd
state: reloaded
- name: php-fpm'i yeniden baslat
ansible.builtin.service:
name: php-fpm
state: restarted
- name: mysql'i yeniden baslat
ansible.builtin.service:
name: mysqld
state: restarted
Dikkat edin: Apache için reloaded, PHP-FPM ve MySQL için restarted kullandık. Bu fark önemlidir. reload servisi durdurmadan konfigürasyonu yeniden yükler, restart ise tamamen durdurur ve başlatır. Üretim ortamında mümkün olduğunca reload tercih etmelisiniz.
Bir Task’tan Birden Fazla Handler Tetikleme
Tek bir task birden fazla handler tetikleyebilir. Bunun için notify direktifine liste şeklinde handler isimleri verilir.
---
- name: SSL Sertifika Guncelleme
hosts: loadbalancers
become: yes
tasks:
- name: SSL sertifikasini guncelle
ansible.builtin.copy:
src: "files/ssl/{{ inventory_hostname }}.crt"
dest: /etc/ssl/certs/site.crt
mode: '0644'
notify:
- nginx'i yeniden yukle
- sertifika degisikligini logla
- name: SSL ozel anahtarini guncelle
ansible.builtin.copy:
src: "files/ssl/{{ inventory_hostname }}.key"
dest: /etc/ssl/private/site.key
mode: '0600'
notify:
- nginx'i yeniden yukle
- sertifika degisikligini logla
handlers:
- name: nginx'i yeniden yukle
ansible.builtin.service:
name: nginx
state: reloaded
- name: sertifika degisikligini logla
ansible.builtin.shell:
cmd: echo "SSL sertifikasi guncellendi: $(date)" >> /var/log/ssl-changes.log
Burada hem sertifika hem de anahtar değişse bile nginx'i yeniden yukle handler’ı yalnızca bir kez çalışır. Ansible akıllıca bu duplikasyonu ortadan kaldırır.
Handler Zincirleme: Handler’dan Handler Tetikleme
Ansible 2.2 ve sonrasında handler’lar birbirini tetikleyebilir. Bu özellik karmaşık bağımlılık zincirlerini yönetmek için son derece kullanışlıdır.
---
- name: Systemd Servis Yonetimi
hosts: all
become: yes
tasks:
- name: Uygulama binary dosyasini guncelle
ansible.builtin.copy:
src: myapp
dest: /usr/local/bin/myapp
mode: '0755'
notify: systemd daemon-reload yap
handlers:
- name: systemd daemon-reload yap
ansible.builtin.systemd:
daemon_reload: yes
notify: uygulamayi yeniden baslat
- name: uygulamayi yeniden baslat
ansible.builtin.service:
name: myapp
state: restarted
Bu zincirde: binary değişir, daemon-reload yapılır, ardından uygulama yeniden başlatılır. Sıralama önemlidir; handler’lar tanımlandıkları sırayla değil, tetiklendikleri sırayla çalışır. Ancak zincirleme handler’larda sıra garantisi için tanım sırasına dikkat etmek gerekir.
Role İçinde Handler Kullanımı
Ansible role’lerinde handler’lar handlers/main.yml dosyasında tanımlanır. Bu yapı hem düzenli hem de yeniden kullanılabilir kod yazmanızı sağlar.
# roles/nginx/handlers/main.yml
---
- name: nginx'i yeniden baslat
ansible.builtin.service:
name: nginx
state: restarted
listen: "nginx restart"
- name: nginx'i yeniden yukle
ansible.builtin.service:
name: nginx
state: reloaded
listen: "nginx reload"
- name: nginx yapilandirmasini test et
ansible.builtin.command: nginx -t
changed_when: false
listen: "nginx config test"
# roles/nginx/tasks/main.yml
---
- name: Nginx ana konfigurasyonu
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: "nginx reload"
- name: Virtual host konfigurasyonu
ansible.builtin.template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ site_name }}.conf"
notify: "nginx reload"
- name: Nginx servis dosyasini guncelle
ansible.builtin.template:
src: nginx.service.j2
dest: /etc/systemd/system/nginx.service
notify: "nginx restart"
Burada listen direktifini kullandık. Bu, birden fazla handler’ın aynı tetikleyiciye yanıt vermesini sağlayan güçlü bir özelliktir.
Listen Direktifi ile Esnek Handler Grupları
listen direktifi, handler isimlerinizi değiştirmeden farklı tetikleyiciler oluşturmanızı sağlar. Özellikle büyük projelerde çok işe yarar.
---
- name: Uygulama Stack Yeniden Baslat
hosts: production
become: yes
tasks:
- name: Uygulama konfigurasyonunu guncelle
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.conf
notify: "stack yeniden baslat"
- name: Cache konfigurasyonunu guncelle
ansible.builtin.template:
src: redis.conf.j2
dest: /etc/redis/redis.conf
notify: "stack yeniden baslat"
handlers:
- name: uygulamayi yeniden baslat
ansible.builtin.service:
name: myapp
state: restarted
listen: "stack yeniden baslat"
- name: redis'i yeniden baslat
ansible.builtin.service:
name: redis
state: restarted
listen: "stack yeniden baslat"
- name: nginx'i yeniden yukle
ansible.builtin.service:
name: nginx
state: reloaded
listen: "stack yeniden baslat"
“Stack yeniden baslat” tetiklendiğinde tüm bu handler’lar sırayla çalışır. Tek bir notify ile tüm stack’i koordineli şekilde yeniden başlatmış olursunuz.
Force Handler ve Meta Modülü
Bazen play sonunu beklemeden handler’ı hemen çalıştırmak gerekebilir. Örneğin bir sonraki task, handler’ın tamamlanmasına bağlıysa bu durum ortaya çıkar.
---
- name: Veritabani Migrasyon Senaryosu
hosts: dbservers
become: yes
tasks:
- name: Yeni veritabani schema dosyasini kopyala
ansible.builtin.copy:
src: schema_v2.sql
dest: /tmp/schema_v2.sql
notify: veritabanini hazirla
- name: Handler'lari hemen calistir
ansible.builtin.meta: flush_handlers
- name: Migrasyon scriptini calistir
ansible.builtin.command: /usr/local/bin/run_migration.sh
args:
creates: /var/log/migration_v2.done
handlers:
- name: veritabanini hazirla
ansible.builtin.command: mysql -u root < /tmp/schema_v2.sql
args:
creates: /var/lib/mysql/myapp/new_table.frm
meta: flush_handlers komutu o ana kadar birikmiş tüm handler’ları hemen çalıştırır. Migrasyon scripti ancak schema hazırlandıktan sonra çalışabildiği için bu pattern zorunlu hale gelir.
Hata Durumlarında Handler Davranışı
Varsayılan olarak play başarısız olursa pending handler’lar çalışmaz. Bu bazen istenen davranış olmayabilir. Örneğin konfigürasyon değişti, sonraki task hata verdi ama servisi restart etmemiz gerekiyor.
---
- name: Kritik Servis Guncelleme
hosts: webservers
become: yes
force_handlers: true # Hata olsa bile handler'lari calistir
tasks:
- name: Nginx konfigurasyonunu guncelle
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: nginx'i yeniden yukle
- name: Belki basarisiz olabilecek bir task
ansible.builtin.command: /opt/scripts/risky_operation.sh
ignore_errors: yes
handlers:
- name: nginx'i yeniden yukle
ansible.builtin.service:
name: nginx
state: reloaded
force_handlers: true ayarı play’de hata oluşsa bile handler’ların çalışmasını garantiler. Bu özellikle rollback senaryolarında veya konfigürasyon değişikliklerinin mutlaka uygulanması gereken durumlarda kritiktir.
Gerçek Dünya Senaryosu: Kubernetes Node Yönetimi
Büyük bir cluster’da node konfigürasyonlarını yönetirken handler’ların gücü gerçekten ortaya çıkıyor.
---
- name: Kubernetes Worker Node Yapilandirmasi
hosts: k8s_workers
become: yes
serial: 1 # Bir seferde bir node
tasks:
- name: Kubelet konfigurasyonunu guncelle
ansible.builtin.template:
src: kubelet-config.yaml.j2
dest: /etc/kubernetes/kubelet-config.yaml
notify:
- kubelet'i yeniden baslat
- node durumunu dogrula
- name: Containerd konfigurasyonunu guncelle
ansible.builtin.template:
src: config.toml.j2
dest: /etc/containerd/config.toml
notify: containerd'yi yeniden baslat
- name: Node'u cordon et (is almayacak sekilde isaretl)
ansible.builtin.command: kubectl cordon {{ inventory_hostname }}
delegate_to: "{{ groups['k8s_masters'][0] }}"
changed_when: true
notify: node'u uncordon et
- name: Handler'lari hemen uygula (restart oncesi cordon tamamlansin)
ansible.builtin.meta: flush_handlers
handlers:
- name: containerd'yi yeniden baslat
ansible.builtin.service:
name: containerd
state: restarted
- name: kubelet'i yeniden baslat
ansible.builtin.service:
name: kubelet
state: restarted
- name: node durumunu dogrula
ansible.builtin.command: kubectl get node {{ inventory_hostname }}
delegate_to: "{{ groups['k8s_masters'][0] }}"
changed_when: false
retries: 5
delay: 10
register: node_status
until: "'Ready' in node_status.stdout"
- name: node'u uncordon et
ansible.builtin.command: kubectl uncordon {{ inventory_hostname }}
delegate_to: "{{ groups['k8s_masters'][0] }}"
changed_when: true
Bu senaryoda serial: 1 ile bir seferde tek node üzerinde çalışıyoruz. Node cordon edilmeden restart yapılmaz, restart tamamlanmadan uncordon edilmez. Handler zinciri bu koordinasyonu sağlıyor.
Handler Hata Ayıklama İpuçları
Handler’larla çalışırken karşılaşılan yaygın sorunlar ve çözümleri:
Handler hiç çalışmıyor: Task’ın “changed” durumuna geçip geçmediğini kontrol edin. ansible-playbook -v ile verbose çıktı alarak hangi task’ların changed döndürdüğünü görün.
Handler beklenmedik sırada çalışıyor: Handler’lar tanım sıralarına göre değil, ilk tetiklenme sıralarına göre çalışır. Sıra önemliyse listen gruplaması veya meta: flush_handlers kullanın.
Handler birden fazla kez çalışıyor: Bu genellikle farklı host’larda paralel çalışmadan kaynaklanır. Her host için handler bir kez çalışır, bu normaldir.
Role handler’ları çalışmıyor: Role’ü import mı yoksa include mı ettiğinizi kontrol edin. import_role ve include_role arasındaki fark handler görünürlüğünü etkiler.
Handlers ile İyi Pratikler
Uzun yıllar boyunca edinilen deneyimlerle şekillenen bazı önemli kurallar:
- Anlamlı isimler kullanın: Handler ismi ne yaptığını açıkça belirtmeli. “nginx restart” yerine “nginx konfigurasyonu degisti, yeniden yukle” gibi açıklayıcı isimler tercih edin.
- Reload vs Restart ayrımına dikkat edin: Mümkün olan her durumda
reloadkullanın. Özellikle üretim ortamında nginx, apache, haproxy gibi servislerdereloaddowntime yaratmaz.
- Handler’ları role’lerde merkezileştirin: Aynı handler’ı birden fazla playbook’ta tanımlamak yerine role içinde tutun.
- İdempotency kontrolü yapın: Handler içindeki komutlar da idempotent olmalı.
createsveyawhendirektifleriyle gereksiz çalışmaları önleyin.
- Test ortamında force_handlers kullanmayın: Bu ayar üretimde belirli senaryolar için kullanışlı olsa da test ortamında normal akışı taklit etmek için kapalı tutun.
- Zincirleme handler’ları belgelendirin: Handler zincirleri karmaşıklaşabilir. Comment ekleyerek hangi handler’ın neden tetiklendiğini açıklayın.
Sonuç
Handler’lar, Ansible’ın “değişiklik yoksa dokunma” felsefesinin en somut uygulamasıdır. Onlarsız bir Ansible playbook, her çalışmasında servisleri gereksiz yere restart eden, log’ları kalabalıklaştıran ve üretim ortamında gereksiz kesintilere yol açan bir araç haline gelir.
Temel kullanımdan başlayarak listen direktifi, handler zincirleme ve meta: flush_handlers gibi ileri seviye özelliklere kadar handler’lar her ölçekteki Ansible projesinin vazgeçilmez parçasıdır. Özellikle yüzlerce sunucu yöneten ortamlarda, bir konfigürasyon değişikliğinin yalnızca değişen sunucularda ve yalnızca bir kez servis restart’ına yol açması hem operasyonel güvenlik hem de sistem kararlılığı açısından büyük önem taşır.
Bir sonraki playbook’unuzu yazarken kendinize şu soruyu sorun: “Bu servis restart’ı gerçekten bir değişikliğe bağlı mı?” Eğer cevap evet ise, o task’ı doğrudan değil bir handler aracılığıyla çalıştırın. Hem kendinize hem de yönettiğiniz sistemlere iyilik yapmış olursunuz.