Ansible ile çalışmaya başladığınızda, ansible komutunu kullanarak tek satırlık işlemler yapabilirsiniz. Ama gerçek güç, playbook’larda saklı. Playbook’lar, birden fazla görevi sıralı ve tekrarlanabilir şekilde çalıştırmanızı sağlayan YAML dosyalarıdır. Bu yazıda playbook anatomisini, görev tanımlamanın inceliklerini ve gerçek senaryolarda nasıl kullanacağınızı ele alacağız.
Playbook Nedir, Neden Kullanırız?
Bir sabah 20 sunucuya aynı paketi kurmak, aynı config dosyasını dağıtmak ve servisi yeniden başlatmak zorunda kaldığınızı düşünün. SSH ile teker teker bağlanmak mı? Hayır. Bir bash script mi? Yakın ama yeterli değil. Ansible playbook’u tam olarak bu durumlar için tasarlanmış.
Playbook, Ansible’ın diliyle yazılmış bir otomasyon senaryosudur. YAML formatında yazılır, okunması kolaydır ve idempotent’tir yani aynı playbook’u birden fazla çalıştırırsanız, sistem hep aynı son duruma gelir, tekrar tekrar değişiklik yapmaz.
Playbook’ların öne çıkan avantajları:
- Tekrarlanabilirlik: Aynı yapılandırmayı 1 sunucuya da 1000 sunucuya da uygulayabilirsiniz
- Versiyon kontrolü: YAML dosyaları Git’e gider, değişiklik geçmişi tutulur
- Okunabilirlik: Bash’e kıyasla ne yaptığı çok daha anlaşılır
- Hata yönetimi: Bir görev başarısız olursa playbook durur, sistemi yarım bırakmaz
- İdempotency: Güvenle tekrar çalıştırabilirsiniz
Playbook Yapısı ve Anatomisi
Bir playbook en temel haliyle şu şekilde görünür:
---
- name: Web sunucusu kurulumu
hosts: webservers
become: yes
tasks:
- name: Nginx kur
ansible.builtin.package:
name: nginx
state: present
- name: Nginx'i başlat ve enable et
ansible.builtin.service:
name: nginx
state: started
enabled: yes
Buradaki her satırın anlamına bakalım:
---: YAML dosyasının başladığını belirtir, zorunlu değil ama standart bir alışkanlıkname: Play’e verdiğiniz açıklayıcı isim, çıktıda görünürhosts: Bu play’in hangi host grubuna uygulanacağı, inventory’deki grupla eşleşirbecome: Sudo yetkileriyle çalış anlamına gelirtasks: Sırayla çalıştırılacak görevlerin listesi
Her task içinde de benzer bir yapı var:
name: Görevin açıklaması, log çıktısında görünür- Modül adı:
ansible.builtin.package,ansible.builtin.servicegibi - Modül parametreleri: Modülün ne yapacağını belirleyen key-value çiftleri
Görev Tanımlama: Modüller ile Çalışmak
Ansible’da her görev bir modül çağrısıdır. Modüller, Ansible’ın yapı taşlarıdır. Dosya kopyalamak için copy, paket kurmak için package, servis yönetmek için service kullanırsınız.
Temel Modülleri Tanıyalım
Package modülü ile paket yönetimi:
---
- name: Paket yönetimi örnekleri
hosts: all
become: yes
tasks:
- name: Birden fazla paket kur
ansible.builtin.package:
name:
- vim
- curl
- git
- htop
state: present
- name: Eski paketi kaldır
ansible.builtin.package:
name: telnet
state: absent
- name: Paketi en son sürüme güncelle
ansible.builtin.package:
name: openssl
state: latest
Copy ve template modülleri:
---
- name: Dosya dağıtımı
hosts: webservers
become: yes
tasks:
- name: Nginx config dosyasını kopyala
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
- name: Virtual host config'i template ile oluştur
ansible.builtin.template:
src: templates/vhost.conf.j2
dest: /etc/nginx/sites-available/myapp.conf
owner: root
group: root
mode: '0644'
copy modülü dosyayı olduğu gibi kopyalar. template modülü ise Jinja2 şablon motorunu kullanarak değişkenleri işler ve dinamik config dosyaları oluşturmanızı sağlar. Production ortamında genellikle template daha kullanışlıdır çünkü her sunucu için farklı değerler atayabilirsiniz.
File modülü ile dosya/dizin yönetimi:
---
- name: Dizin ve dosya yapısı oluştur
hosts: appservers
become: yes
tasks:
- name: Uygulama dizinini oluştur
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: '0755'
- name: Log dizinini oluştur
ansible.builtin.file:
path: /var/log/myapp
state: directory
owner: appuser
group: appgroup
mode: '0750'
- name: Sembolik link oluştur
ansible.builtin.file:
src: /opt/myapp/current
dest: /usr/local/bin/myapp
state: link
Handlers: Değişikliğe Tepki Vermek
Ansible’da çok güçlü bir kavram olan handler’lar, yalnızca bir değişiklik olduğunda tetiklenen özel görevlerdir. En klasik örnek şudur: Nginx config’ini değiştirdiniz, servisi yeniden başlatmanız gerekiyor. Ama config değişmediyse yeniden başlatmaya gerek yok.
---
- name: Nginx kurulum ve yapılandırma
hosts: webservers
become: yes
handlers:
- name: nginx restart
ansible.builtin.service:
name: nginx
state: restarted
- name: nginx reload
ansible.builtin.service:
name: nginx
state: reloaded
tasks:
- name: Nginx kur
ansible.builtin.package:
name: nginx
state: present
- name: Ana config dosyasını dağıt
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: nginx reload
- name: SSL sertifikasını kopyala
ansible.builtin.copy:
src: files/ssl/myapp.crt
dest: /etc/nginx/ssl/myapp.crt
mode: '0644'
notify: nginx restart
Handler’lar play sonunda çalışır ve bir handler birden fazla task tarafından tetiklense bile yalnızca bir kez çalışır. Bu detay production’da çok işe yarar.
Değişkenler ve Dinamik Görevler
Gerçek dünyada playbook’larınız static değerler içermez. Değişkenler kullanarak playbook’larınızı yeniden kullanılabilir hale getirirsiniz.
---
- name: Uygulama deployment
hosts: appservers
become: yes
vars:
app_name: mywebapp
app_version: "2.1.4"
app_port: 8080
deploy_dir: /opt/apps
tasks:
- name: Deployment dizinini oluştur
ansible.builtin.file:
path: "{{ deploy_dir }}/{{ app_name }}"
state: directory
mode: '0755'
- name: Uygulama arşivini indir
ansible.builtin.get_url:
url: "https://releases.example.com/{{ app_name }}-{{ app_version }}.tar.gz"
dest: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
mode: '0644'
- name: Arşivi çıkart
ansible.builtin.unarchive:
src: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
dest: "{{ deploy_dir }}/{{ app_name }}"
remote_src: yes
- name: Systemd servis dosyasını oluştur
ansible.builtin.template:
src: templates/app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
notify: systemd reload
handlers:
- name: systemd reload
ansible.builtin.systemd:
daemon_reload: yes
Değişkenleri farklı yerlerde tanımlayabilirsiniz:
vars: Playbook içinde, play seviyesindevars_files: Ayrı bir YAML dosyasından yükleyerekgroup_vars/: Host gruplarına özel değişkenlerhost_vars/: Tek bir host için özel değişkenler- Komut satırı:
-e "app_version=2.2.0"parametresiyle
Koşullu Görevler: when Kullanımı
Bazen bir görevi yalnızca belirli koşullarda çalıştırmak istersiniz. İşte when direktifi tam burada devreye girer.
---
- name: İşletim sistemine göre yapılandırma
hosts: all
become: yes
tasks:
- name: Apache kur (Debian tabanlı)
ansible.builtin.apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Apache kur (RedHat tabanlı)
ansible.builtin.dnf:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Apache servisini başlat (Debian)
ansible.builtin.service:
name: apache2
state: started
enabled: yes
when: ansible_os_family == "Debian"
- name: Apache servisini başlat (RedHat)
ansible.builtin.service:
name: httpd
state: started
enabled: yes
when: ansible_os_family == "RedHat"
- name: Disk kullanımını kontrol et
ansible.builtin.command: df -h /
register: disk_usage
- name: Disk dolu uyarısı
ansible.builtin.debug:
msg: "UYARI: Disk kullanımı yüksek!"
when: disk_usage.stdout is search("9[0-9]%")
register direktifi bir görevin çıktısını değişkene kaydeder, sonraki görevlerde bu çıktıya göre karar verebilirsiniz. Production’da çok kullandığım bir pattern.
Döngüler: loop ile Tekrarlayan Görevler
Aynı görevi birden fazla öğe için çalıştırmanız gerektiğinde döngüler kullanırsınız.
---
- name: Kullanıcı ve grup yönetimi
hosts: all
become: yes
vars:
system_users:
- name: appuser
groups: www-data
shell: /bin/bash
- name: deployuser
groups: sudo
shell: /bin/bash
- name: monitoruser
groups: adm
shell: /sbin/nologin
tasks:
- name: Sistem kullanıcılarını oluştur
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
state: present
create_home: yes
loop: "{{ system_users }}"
- name: Sudo yetkilerini yapılandır
ansible.builtin.lineinfile:
path: /etc/sudoers.d/{{ item }}
line: "{{ item }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl"
create: yes
mode: '0440'
loop:
- appuser
- deployuser
Playbook Çalıştırma ve Kontrol Seçenekleri
Playbook yazdıktan sonra çalıştırmadan önce test etmek iyi bir alışkanlıktır.
Syntax kontrolü:
ansible-playbook --syntax-check site.yml
Dry-run (ne olacağını gör, uygulama):
ansible-playbook --check site.yml
Verbose mod (daha fazla çıktı):
ansible-playbook -v site.yml # Temel verbose
ansible-playbook -vv site.yml # Daha fazla detay
ansible-playbook -vvv site.yml # Debug seviyesi
Belirli host’lara veya gruplara uygulama:
ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit "web01,web02"
ansible-playbook site.yml --limit "webservers:!web03"
Belirli tag’leri çalıştırma:
ansible-playbook site.yml --tags "install,configure"
ansible-playbook site.yml --skip-tags "deploy"
Tag kullanımı playbook’larınızı modüler hale getirir. Sadece deployment görevlerini çalıştırmak istiyorsanız sadece o tag’i geçirirsiniz.
Değişken override:
ansible-playbook site.yml -e "app_version=2.2.0 app_port=9090"
Gerçek Dünya Senaryosu: LAMP Stack Kurulumu
Şimdiye kadar öğrendiklerimizi birleştirerek gerçekçi bir playbook yazalım.
---
- name: LAMP Stack Kurulumu
hosts: webservers
become: yes
vars:
mysql_root_password: "{{ vault_mysql_root_password }}"
php_version: "8.1"
web_root: /var/www/html
handlers:
- name: apache2 restart
ansible.builtin.service:
name: apache2
state: restarted
- name: mysql restart
ansible.builtin.service:
name: mysql
state: restarted
tasks:
- name: Paket listesini güncelle
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Apache kur
ansible.builtin.apt:
name: apache2
state: present
notify: apache2 restart
- name: PHP ve modülleri kur
ansible.builtin.apt:
name:
- "php{{ php_version }}"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-curl"
- "php{{ php_version }}-mbstring"
- "php{{ php_version }}-xml"
- libapache2-mod-php
state: present
notify: apache2 restart
- name: MySQL kur
ansible.builtin.apt:
name:
- mysql-server
- python3-mysqldb
state: present
- name: MySQL servisini başlat
ansible.builtin.service:
name: mysql
state: started
enabled: yes
- name: Web root dizinini oluştur
ansible.builtin.file:
path: "{{ web_root }}/myapp"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Test PHP dosyası oluştur
ansible.builtin.copy:
content: "<?php phpinfo(); ?>"
dest: "{{ web_root }}/myapp/info.php"
owner: www-data
group: www-data
mode: '0644'
- name: Apache servisini enable et
ansible.builtin.service:
name: apache2
state: started
enabled: yes
- name: Firewall'da HTTP portunu aç
community.general.ufw:
rule: allow
port: '80'
proto: tcp
Bu playbook Ubuntu/Debian üzerinde çalışan eksiksiz bir LAMP stack kurar. Dikkat edin: mysql_root_password değişkeni vault_mysql_root_password ile geliyor. Ansible Vault konusunu ayrı bir yazıda ele alacağız ama şimdilik şunu bilin: şifreleri playbook’a düz yazmazsınız, Vault ile şifreli saklarsınız.
Playbook Organizasyonu: Best Practices
Playbook’larınız büyüdükçe düzeni korumak önemli hale gelir. Birkaç pratik öneri:
- Görevleri mantıksal gruplara ayırın: Kurulum, yapılandırma ve deployment için ayrı task blokları kullanın
- Her göreve açıklayıcı isim verin: “Paketi kur” yerine “Nginx web sunucusunu kur” yazın
- Handler isimlerini tutarlı tutun:
nginx restartgibi service-action formatı kullanın - Hassas değişkenleri Vault ile şifreleyin: Şifre, API key gibi değerleri asla düz metin bırakmayın
- Playbook’larınızı test edin:
--checkve--syntax-checkkullanmayı alışkanlık haline getirin - Tag ekleyin: Büyük playbook’larda tag’ler hayat kurtarır
- Idempotency’i koruyun: Her modülün tekrar çalıştırılabilir olduğundan emin olun
Dizin yapısı için basit bir öneri:
site.ymlana playbookgroup_vars/all.ymltüm hostlar için ortak değişkenlergroup_vars/webservers.ymlweb sunucularına özel değişkenlerhost_vars/web01.ymltek bir sunucuya özel değişkenlerfiles/statik dosyalartemplates/Jinja2 template dosyalarıhandlers/ortak handler’lar
Sonuç
Ansible playbook’ları, sistem yönetiminde tekrarlayan işleri otomatikleştirmenin en temiz yollarından biri. Bu yazıda temel görev tanımlamayı, modülleri, handler’ları, değişkenleri, koşullu görevleri ve döngüleri ele aldık.
Şimdi yapmanız gereken şey basit: Kendi ortamınızda küçük bir playbook yazın. Belki test sunucunuza bir paket kurma ve bir servis başlatma. Sonra --check ile test edin, sonra gerçekten çalıştırın. Playbook’un idempotent çalıştığını, ikinci çalıştırmada “changed” yerine “ok” çıktısı verdiğini gözlemleyin.
Bir sonraki adım olarak Ansible Roles konusuna geçmenizi öneririm. Role’lar, playbook’larınızı daha modüler ve yeniden kullanılabilir hale getirmenizi sağlar. Özellikle birden fazla projede aynı yapılandırmaları kullanıyorsanız, role’lar büyük zaman tasarrufu sağlar. Ama önce bu temelleri iyice sindirin, çünkü role’lar da sonunda aynı yapı üzerine kurulu.