Ansible CLI'dan Playbook'a: Temel Kavramlar ve Kullanımları
★ featuredAnsible'ı öğrenmeye başlayanların büyük çoğunluğu doğrudan playbook yazmaya atlar. Mantıklı da; sonuçta Ansible'ın asıl gücü otomasyon senaryoları üretmekte. Ancak CLI'ı ve ad-hoc komutları atlamak, evin temelini dökmeden duvar örmeye çalışmaktan farksızdır. Bu yazıda Ansible'ın komut satırı arayüzünden başlayarak playbook yazımının temel kavramlarına kadar olan her şeyi ele alıyoruz.
Ansible CLI
Ansible'ın CLI ekosistemi dört ana komut üzerine kuruludur. Her birinin belirli bir amaca hizmet ettiğini bilmek gereksiz karmaşıklıktan kaçınmayı sağlar.
| Komut | Ne İşe Yarar? |
|---|---|
ansible |
Tek seferlik, hızlı müdahaleler için ad-hoc komutlar |
ansible-playbook |
YAML tabanlı playbook dosyalarını çalıştırır |
ansible-doc |
Modül belgelerini ve parametrelerini gösterir |
ansible-inventory |
Inventory içeriğini listeler veya doğrular |
Ad-hoc Komutlar
Ad-hoc komutlar, playbook yazmaya gerek kalmadan anlık müdahale için kullanılır. Bir servis başlatmak, dosya kopyalamak, hızlıca sistem bilgisi toplamak gibi işlemler için playbook yazmak genelde gerekmez.
Sık Kullanılan Parametreler
| Parametre | Kısaltma | Ne İşe Yarar? |
|---|---|---|
--inventory |
-i |
Inventory dosyasının yolu |
--module-name |
-m |
Kullanılacak modül adı |
--args |
-a |
Modüle geçilecek parametreler |
--user |
-u |
Hedef sunucuya bağlanacak kullanıcı |
--become |
-b |
Yetki yükselterek çalıştırma (sudo/root) |
--check |
- | Dry-run: ne değişeceğini gösterir, gerçek değişiklik yapmaz |
--diff |
- | Dosya değişikliklerini diff formatında gösterir |
--limit |
- | Komutun çalışacağı host/grubu sınırlar |
--private-key |
- | Özel SSH anahtar dosyasını belirtir |
--ask-pass |
-k |
SSH parolası sorması için |
--extra-vars |
-e |
CLI üzerinden değişken vermek için |
--syntax-check |
- | Sözdizimi hatalarını kontrol eder |
Gerçek Bir Örnek
ansible webservers -i ./hosts \
-u svcansible \
-b \
--private-key ~/.ssh/id_rsa \
-m shell \
-a "apt update && apt upgrade -y" \
--check --diff --limit web1
Bu komut webservers grubundaki web1 sunucusunda apt update && apt upgrade -y komutunu dry-run modunda çalıştırır. --diff parametresi özellikle copy, template, lineinfile gibi dosya değiştiren modüllerle birlikte kullanıldığında eski ve yeni içerik arasındaki farkı diff formatında gösterir:
TASK [Update /etc/motd file]
--- before: /etc/motd
+++ after: /etc/motd
@@
-Old message
+Welcome to production servers!
--check ve --diff birlikte kullanılabilir. Böylece ne değişiklik yapılacağı hem dry-run hem de fark formatında görülür. --limit ise wildcard destekler: --limit web* gibi.
Faydalı CLI Tüyoları
Uzun soluklu Ansible kullanımında işe yarayan bazı kısa komutlar:
# Inventory'deki tüm host'ları görmek
ansible-inventory --list -i ./hosts
# Sadece özel yapılandırılmış (varsayılan dışında) ayarları görmek
ansible-config dump --only-changed
# Daha fazla debug çıktısı
ansible-playbook site.yml -vvv
# Belirli bir görevden başlamak
ansible-playbook deploy.yml --start-at-task="Install packages"
# Her görev öncesinde onay alarak ilerlemek
ansible-playbook update.yml --step
--step özellikle karmaşık playbook'larda veya production ortamında ilk kez çalıştırılan senaryolarda oldukça kullanışlıdır.
ansible-doc: Modül Kılavuzu
Yüzlerce modülü tabii ki ezberlemek gerekmez. ansible-doc komutunu kullanarak modül dokümantasyonuna saniyeler içinde ulaşılabilir:
# Bir modül hakkında genel bilgi
ansible-doc copy
# Kısa sözdizimini görmek
ansible-doc -s user
# Tüm modülleri listelemek
ansible-doc -l
Modül dokümantasyonu ile ilgili detaylı bilgilere aşağıdaki linkcard'dan ulaşabilirsiniz.
State-aware Yapı ve Idempotency
Ansible'ı shell scriptlerden ayıran en kritik özellik budur. Ansible bir görevi çalıştırmadan önce sistemin mevcut durumunu kontrol eder. Hedef durum zaten sağlanmışsa hiçbir şey yapmaz. Buna idempotency denir.
- name: Ensure nginx is running
service:
name: nginx
state: started
Bu görev kaç kez çalıştırılırsa çalıştırılsın sonuç değişmez. Nginx çalışıyorsa ok döner, çalışmıyorsa başlatır ve changed döner. Bunu ad-hoc karşılığıyla karşılaştıralım:
ansible webservers -m shell -a "systemctl start nginx"
Bu komut nginx zaten çalışıyor olsa bile her seferinde start işlemini dener; idempotent değildir. Bu nedenle mümkün olduğunca amaca özel modüller (service, package, file, copy vb.) tercih edilmeli, shell ve command yalnızca başka seçenek kalmadığında kullanılmalıdır.
Ad-hoc komutlar neden idempotent değildir?
Playbook Anatomisi
Playbook, Ansible'ın temel otomasyon birimidir. Tek bir YAML dosyasında hangi sunuculara hangi görevlerin, hangi koşullarla uygulanacağı tanımlanır. Bir playbook birden fazla play içerebilir; her play, belirli bir host grubunu hedef alan bağımsız bir bloktur.
Aşağıda bir playbook'un temel iskeletini inceliyoruz:
---
- name: Web sunucusunu yapılandır # Play'in adı
hosts: webservers # Hedef host grubu
become: true # sudo yetkisiyle çalıştır
gather_facts: true # Sistem bilgilerini topla
vars: # Bu play'e özel değişkenler
http_port: 80
app_user: webadmin
tasks: # Sırayla çalışacak görevler
- name: nginx'i kur
ansible.builtin.package:
name: nginx
state: present
- name: nginx servisini başlat
ansible.builtin.service:
name: nginx
state: started
enabled: true
Her alanın ne işe yaradığına bakalım:
nameplay veya task için okunabilir bir etiket belirler. Zorunlu değildir ancak çıktıyı takip etmeyi kolaylaştırdığı için her zaman yazılması önerilir.hostshangi sunucularda çalışacağını belirler.alltüm inventory'yi,webserversgibi bir değer belirli bir grubu,web1,web2gibi bir değer ise belirli host'ları hedefler.become: truegörevlerin sudo yetkisiyle çalışmasını sağlar. Paket kurulumu, servis yönetimi gibi root erişimi gerektiren işlemler için zorunludur.become_userile hangi kullanıcıya geçileceği de belirtilebilir, varsayılanroot'tur.gather_factsplaybook başında hedef sistemden bilgi toplayıp toplamayacağını belirler. Varsayılanıtrue'dur. Bu bilgilere ihtiyaç yoksafalseyaparak playbook'un daha hızlı başlaması sağlanabilir.varsbu play kapsamındaki değişkenleri tanımlar.{{ http_port }}şeklinde tasks içinde kullanılabilirler.tasksçalışacak görevlerin listesidir. Her görev bir modül çağrısıdır ve yukarıdan aşağıya sırayla çalışır.
Birden Fazla Play
Tek bir playbook dosyasında birden fazla play tanımlanabilir. Her play farklı bir host grubunu hedefleyebilir:
---
- name: Web sunucularını yapılandır
hosts: webservers
become: true
tasks:
- name: nginx'i kur
ansible.builtin.package:
name: nginx
state: present
- name: Veritabanı sunucularını yapılandır
hosts: dbservers
become: true
tasks:
- name: postgresql'i kur
ansible.builtin.package:
name: postgresql
state: present
Bir playbook'ta become: true ne işe yarar?
{{ }} Sözdizimi ve Jinja2
Ansible'daki {{ }} ifadesi, değişkenlerin ve hesaplamaların runtime'da işlenmesini sağlar. Sistem bilgileri (facts), önceki görev çıktıları (register), elle tanımlanan değişkenler (vars, set_fact) ve döngüdeki item gibi özel değişkenler bu yapı sayesinde görevler içinde kullanılabilir hale gelir.
Neden "item" değil "{{ item }}" yazıyoruz? Çünkü "item" düz metin olarak kalır, Ansible bunu değişken olarak değil kelime olarak işler. {{ item }} yazıldığında ise Ansible bunun bir değişken olduğunu anlayarak değerini alır.
Somut bir örnek üzerinden gösterelim:
vars:
app_port: 8080
tasks:
- name: Portu yazdır
debug:
msg: "Uygulama portu: {{ app_port }}"
Çıktı şu şekilde olur:
ok: [web1] => {
"msg": "Uygulama portu: 8080"
}
Bu yapı aslında Jinja2 adlı şablon motoruna dayanır. Yani kısacası {{ }} içine yazılan her şey Ansible tarafından değerlendirilecek bir ifadedir.
Facts ve gather_facts
Ansible Facts Nedir?
Ansible, playbook çalıştırıldığında hedef sistemlerden otomatik olarak bilgi toplar: işletim sistemi türü, hostname, IP adresleri, CPU sayısı, disk alanı... Bunların tümü ansible_facts sözlüğü altında tutulur ve varsayılan olarak her playbook başında setup modülü aracılığıyla çalışır.
Toplanan tüm facts değerlerini görmek için şu ad-hoc komut kullanılabilir:
ansible web1 -m setup
Bu komut oldukça uzun bir çıktı verir. Belirli bir değere ulaşmak için filtre kullanılabilir:
ansible web1 -m setup -a "filter=ansible_distribution*"
gather_facts: false Ne Zaman Kullanılır?
Çok sayıda host bulunan ortamlarda facts toplama süreci, toplam çalışma süresini ciddi şekilde uzatabilir. Bu verilere ihtiyaç yoksa devre dışı bırakmak mantıklıdır:
- name: Run something minimal
hosts: all
gather_facts: false
tasks:
- name: Run a simple command
command: uptime
gather_facts: false yapıldığında ansible_facts sözlüğü mevcut olmaz. ansible_hostname, ansible_distribution gibi değişkenlere erişmek gerekiyorsa ya gather_facts: true bırakılmalı ya da setup modülü ilgili task'tan önce manuel olarak çağrılmalıdır:
- name: Setup'ı manuel çalıştır
ansible.builtin.setup:
Değişken Tanımlama: vars
Playbook'larda tekrar kullanılacak değerler vars anahtarı altında tanımlanır. Bu sayede değerler tek yerden yönetilebilir ve okunabilirlik artar.
Temel Kullanım
- name: Kullanıcıları oluştur
hosts: all
vars:
user_list:
- alice
- bob
tasks:
- name: Kullanıcıları ekle
user:
name: "{{ item }}"
state: present
loop: "{{ user_list }}"
Diğer Değişken Tanımlama Yöntemleri
vars_files
vars: bloğu tek bir play'e ait küçük değişkenler için pratiktir. Ancak aynı değişkenleri birden fazla playbook'ta kullanmak gerektiğinde ya da değişken sayısı arttıkça playbook dosyası şişmeye başlar. vars_files: bu durumda devreye girer; değişkenleri ayrı YAML dosyalarına taşıyarak hem tekrar kullanımını hem de yönetimi kolaylaştırır.
- name: Deploy
hosts: all
vars_files:
- vars/common.yml
- vars/production.yml
vars/common.yml tüm ortamlarda geçerli olan değişkenleri, vars/production.yml ise production'a özgü değerleri içerebilir. Aynı değişken birden fazla dosyada tanımlanmışsa listede sonra gelen dosya kazanır; yani production.yml, common.yml'deki bir değeri ezebilir. Bu özellik environment'a göre değişken katmanlama yaparken oldukça kullanışlıdır.
Örnek bir vars/common.yml:
app_name: myapp
app_port: 8080
log_dir: /var/log/myapp
Örnek bir vars/production.yml:
app_port: 443 # common.yml'deki 8080'i ezer
db_host: prod-db.internal
db_name: myapp_prod
Bu değişkenler yüklendikten sonra playbook içinde normal vars: değişkenlerinden hiçbir farkı olmadan kullanılır:
tasks:
- name: Konfigürasyonu oluştur
ansible.builtin.template:
src: templates/app.conf.j2
dest: /etc/myapp/app.conf
# template içinde {{ app_name }}, {{ app_port }}, {{ db_host }} kullanılabilir
extra-vars (-e)
-e ile CLI üzerinden değişken geçilir. Playbook'u değiştirmeden dışarıdan parametre vermek gerektiğinde kullanılır. CI/CD pipeline'larında özellikle yaygındır; aynı playbook farklı version veya env değerleriyle çalıştırılır.
# Tek değişken
ansible-playbook deploy.yml -e "app_version=2.1.0"
# Birden fazla değişken
ansible-playbook deploy.yml -e "app_version=2.1.0 env=staging"
# YAML dosyasından okuma
ansible-playbook deploy.yml -e "@vars/override.yml"
-e ile geçilen değişkenler en yüksek önceliğe sahiptir; vars:, vars_files: veya inventory'den gelen aynı isimli değişkenlerin tamamını ezer. Bu hem gücü hem de dikkat gerektiren yanıdır.
Öncelik Sırası
Ansible'da değişkenler birden fazla yerden tanımlanabildiği için hangisinin kazanacağını bilmek önemlidir. Tam liste oldukça uzundur, ancak pratikte en sık karşılaşılan sıralama şöyledir (yukarıdan aşağıya öncelik artar):
| Öncelik | Kaynak |
|---|---|
| En düşük | vars_files: |
vars: (play seviyesi) |
|
set_fact |
|
| En yüksek | extra-vars (-e) |
vars_files: ile vars: aynı öncelik seviyesindedir; play içinde vars_files: önce yüklendiği için vars: bloğu onu ezebilir. Bir değişkenin beklediğiniz değeri taşımadığı durumlarda öncelik sırası ilk bakılacak yerdir.
set_fact ve register
Bu iki yapı birbirine karıştırılabiliyor. Aralarındaki temel fark şu: register bir görevin çıktısını yakalar, set_fact ise playbook içinde istediğimiz zaman kendi tanımladığımız bir değişken oluşturur.
register
register, bir görevden dönen çıktıyı yakalar ve bir değişkende saklar. Daha sonra bu değişkenin içeriğine göre karar verilebilir ya da çıktı başka bir görevde kullanılabilir:
- name: Hostname'i al
command: hostname
register: hostname_out
- name: Hostname'i göster
debug:
msg: "Hostname: {{ hostname_out.stdout }}"
register ile kaydedilen değişkenler genellikle şu alanları içerir:
| Alan | İçeriği |
|---|---|
stdout |
Komutun standart çıktısı |
stderr |
Hata çıktısı |
rc |
Return code (0 başarılı, 0 dışı hata) |
changed |
Değişiklik yapılıp yapılmadığı |
set_fact
set_fact modülü runtime'da dinamik olarak yeni bir değişken tanımlar. Playbook'un kalan kısmında kullanılabilir. Genellikle facts'ten veya register çıktısından türetilmiş değerleri daha okunabilir bir isimle saklamak için kullanılır:
- name: IP adresini değişkene ata
set_fact:
my_ip: "{{ ansible_facts['default_ipv4']['address'] }}"
- name: IP adresini göster
debug:
msg: "Sunucu IP: {{ my_ip }}"
Koşullu Çalıştırma: when
Gerçek ortamlarda envanterdeki tüm sunucular aynı işletim sistemini çalıştırmayabilir. Bazı görevlerin yalnızca RHEL sistemlerde, bazılarının yalnızca Debian sistemlerde çalışması gerekebilir. Ya da bir önceki görev başarısız olduğunda sonraki adımı atlamak isteyebilirsiniz. when ifadesi tam olarak bu ihtiyaç için var; bir task'ın yalnızca belirli bir koşul sağlandığında çalışmasını sağlar.
Facts ile Kullanım
- name: RHEL sistemlerde httpd kur
ansible.builtin.package:
name: httpd
state: present
when: ansible_facts['os_family'] == "RedHat"
- name: Debian sistemlerde apache2 kur
ansible.builtin.package:
name: apache2
state: present
when: ansible_facts['os_family'] == "Debian"
Bu sayede hem RHEL hem Debian makineleri aynı inventory'de tutulabilir, Ansible hangi task'ın nerede çalışacağına kendisi karar verir.
register Çıktısıyla Kullanım
- name: Servis durumunu kontrol et
command: systemctl is-active nginx
register: nginx_status
ignore_errors: true
- name: Nginx çalışmıyorsa başlat
service:
name: nginx
state: started
when: nginx_status.rc != 0
ignore_errors: true olmadan ilk task başarısız olduğunda playbook durur. Burada amacımız durumu kontrol etmek olduğundan hatayı görmezden gelip sonucu when ile değerlendiriyoruz.
Birden Fazla Koşul
and ve or operatörleri ya da liste formatı kullanılabilir. Liste formatında her satır and ile birleştirilmiş sayılır:
# and operatörü
when: ansible_facts['distribution'] == "Ubuntu" and ansible_facts['distribution_major_version'] == "22"
# Liste formatı (and ile aynı anlama gelir, daha okunabilir)
when:
- ansible_facts['distribution'] == "Ubuntu"
- ansible_facts['distribution_major_version'] == "22"
# or operatörü
when: ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "Debian"
when ifadesinde liste formatı kullanıldığında koşullar nasıl değerlendirilir?
Loop Kullanımı
Aynı görevi birden fazla öğe için tekrar tekrar yazmak yerine loop kullanılır. Onlarca paketi tek tek kurmak ya da onlarca kullanıcıyı ayrı ayrı tanımlamak yerine listeyi loop'a verip tek bir task yazmak yeterlidir.
Liste ile Loop
- name: Paketleri kur
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- git
- curl
- htop
Sözlük ile Loop
Birden fazla parametreyi birlikte değiştirmek gerektiğinde sözlük yapısı kullanılır:
- name: Kullanıcıları yönet
ansible.builtin.user:
name: "{{ item.name }}"
state: "{{ item.state }}"
shell: "{{ item.shell }}"
loop:
- { name: "alice", state: "present", shell: "/bin/bash" }
- { name: "bob", state: "present", shell: "/bin/zsh" }
- { name: "carol", state: "absent", shell: "/bin/bash" }
loop_control ile Çıktıyı Okunabilir Hale Getirme
Varsayılan olarak Ansible loop çıktısında item değişkeninin tamamını basar. Sözlük kullandığınızda bu çıktı uzun ve okuması zor bir JSON bloğuna dönüşür. loop_control ile hangi alanın gösterileceği belirtilebilir:
- name: Kullanıcıları yönet
ansible.builtin.user:
name: "{{ item.name }}"
state: "{{ item.state }}"
loop:
- { name: "alice", state: "present" }
- { name: "bob", state: "absent" }
loop_control:
label: "{{ item.name }}"
loop_control olmadan çıktı şöyle görünür:
TASK [Kullanıcıları yönet] *****
ok: [web1] => (item={'name': 'alice', 'state': 'present'})
ok: [web1] => (item={'name': 'bob', 'state': 'absent'})
loop_control ile:
TASK [Kullanıcıları yönet] *****
ok: [web1] => (item=alice)
ok: [web1] => (item=bob)
Birkaç öğede fark küçük görünse de onlarca satırdan oluşan sözlüklerde çıktıyı takip etmek çok kolaylaşır.
with_items mi, loop mu?
Eski Ansible dokümantasyonlarında ve internetteki örneklerin büyük bölümünde loop yerine with_items kullanıldığını göreceksiniz. with_items işlevsel olarak loop ile aynıdır ancak Ansible 2.5'ten itibaren loop tercih edilen yöntem haline gelmiştir. with_items hâlâ çalışıyor olsa da yeni yazılan playbook'larda loop kullanılması önerilir.
Loop ile register Kullanımı
Loop içinde register kullanıldığında her iterasyonun sonucu ayrı ayrı biriktirilir ve tek bir değişken altında .results listesi olarak saklanır. Normal bir register kullanımında son task'ın çıktısı değişkenin üzerine yazılır; loop ile kullanıldığında ise Ansible otomatik olarak biriktirme moduna geçer. Her öğenin sonucuna .results üzerinden ulaşılır:
- name: Servisleri kontrol et
command: systemctl is-active "{{ item }}"
register: service_status
loop:
- nginx
- postgresql
ignore_errors: true # Çalışmayan servis rc != 0 döneceğinden playbook'un durmaması için gerekli
- name: Sonuçları göster
debug:
msg: "{{ item.item }}: {{ item.stdout }}"
loop: "{{ service_status.results }}"
loop_control ile label tanımlamanın amacı nedir?
Handlers
Konfigürasyon değişikliklerinden sonra servislerin yeniden başlatılması çok yaygın bir ihtiyaçtır. Bunu normal bir task olarak yazsaydık servis her playbook çalıştırıldığında gereksiz yere restart olurdu. Handler'lar tam olarak bu sorunu çözmek için var; yalnızca bir değişiklik gerçekten gerçekleştiğinde, yani ilgili task changed döndüğünde tetiklenirler.
---
- name: nginx yapılandırması
hosts: webservers
become: true
tasks:
- name: nginx konfigürasyonunu kopyala
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
notify: nginx'i yeniden başlat
- name: SSL sertifikasını kopyala
ansible.builtin.copy:
src: files/cert.pem
dest: /etc/nginx/cert.pem
notify: nginx'i yeniden başlat
handlers:
- name: nginx'i yeniden başlat
ansible.builtin.service:
name: nginx
state: restarted
Handler'ların birkaç önemli özelliği vardır. Aynı handler birden fazla task tarafından tetiklense bile play sonunda yalnızca bir kez çalışır; yukarıdaki örnekte hem konfigürasyon hem sertifika değişse de handler sadece bir defa çalışır. Handler'lar tüm tasks tamamlandıktan sonra devreye girer, task sırasında değil. Herhangi bir task failed olursa handler varsayılan olarak çalışmaz; --force-handlers parametresiyle bu davranış değiştirilebilir.
Büyük/küçük harf ve boşluk dahil olmak üzere notify değeri ile handlers bölümündeki name değeri birebir aynı olmalıdır. Bu uyumsuzluk handler'ların sessizce çalışmamasının en yaygın sebebidir.
Bir handler aynı play içinde 3 farklı task tarafından notify edilirse kaç kez çalışır?
Tags
Fazla task içeren bir playbook'u her değişiklikte baştan sona çalıştırmak hem zaman kaybettirir hem de gereksiz değişiklik riski yaratır. Tags, playbook'un yalnızca belirli bölümlerini çalıştırmayı sağlar. Örneğin sadece konfigürasyon dosyalarını güncellemek istiyorsunuz; paket kurulum adımlarını ve servis yönetimini atlamak için --tags config ile çalıştırmak yeterlidir.
tasks:
- name: nginx'i kur
ansible.builtin.package:
name: nginx
state: present
tags:
- install
- nginx
- name: nginx konfigürasyonunu güncelle
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags:
- config
- nginx
- name: nginx'i başlat
ansible.builtin.service:
name: nginx
state: started
tags:
- service
- nginx
Kullanım örnekleri:
# Sadece nginx tag'li tüm görevleri çalıştır
ansible-playbook site.yml --tags nginx
# Sadece kurulum görevlerini çalıştır
ansible-playbook site.yml --tags install
# Config görevlerini atla, geri kalanını çalıştır
ansible-playbook site.yml --skip-tags config
always ve never
Ansible'ın iki özel tag'i vardır. always tag'i taşıyan bir task, --tags ile başka bir şey belirtilse bile her zaman çalışır. never tag'i taşıyan bir task ise tam tersine, açıkça --tags never ile çağrılmadıkça hiçbir zaman çalışmaz. never genellikle debug amaçlı ya da tehlikeli olduğu için yanlışlıkla tetiklenmesini istemediğiniz görevler için kullanılır.
- name: Değişken değerlerini göster
ansible.builtin.debug:
msg: "app_name={{ app_name }}, nginx_port={{ nginx_port }}"
tags:
- never
- debug
- name: Fact'leri topla
ansible.builtin.setup:
tags:
- always
when, loop ve register: Birlikte Kullanım
Bu üç yapının ayrı ayrı nasıl çalıştığını gördük. Gerçek playbook'larda ise çoğunlukla bir arada kullanılırlar. Küçük bir örnek üzerinden gösterelim. Birden fazla servisin durumunu kontrol edip yalnızca çalışmayanları yeniden başlatalım.
- name: Kritik servisleri kontrol et
ansible.builtin.command:
cmd: systemctl is-active "{{ item }}"
register: service_checks
loop:
- nginx
- postgresql
- redis
ignore_errors: true # Çalışmayan servis rc != 0 döneceğinden playbook'un durmaması için gerekli
changed_when: false
- name: Çalışmayan servisleri başlat
ansible.builtin.service:
name: "{{ item.item }}"
state: started
loop: "{{ service_checks.results }}"
when: item.rc != 0
loop_control:
label: "{{ item.item }}"
İlk task tüm servislerin durumunu kontrol eder, sonuçları service_checks değişkenine kaydeder ve changed_when: false ile bu adımın hiçbir zaman changed dönmemesini sağlar. İkinci task ise bu sonuçlar üzerinde dönerek yalnızca rc != 0 dönen, yani çalışmayan servisleri başlatır. loop_control ile de çıktıda servis adından fazlası görünmez.
Tam Senaryo: nginx Kurulumu ve Yapılandırması
Şimdiye kadar anlattığımız tüm kavramları bir arada kullanan, gerçek senaryolarda çalışan bir playbook örneği görelim. Bu playbook web sunucularına nginx kurar, bir konfigürasyon dosyası kopyalar ve servisi ayağa kaldırır.
---
- name: Web sunucusunu hazırla
hosts: webservers
become: true
gather_facts: true
vars:
nginx_port: 80
nginx_user: www-data
app_name: myapp
tasks:
- name: Sadece Debian tabanlı sistemlerde devam et
ansible.builtin.fail:
msg: "Bu playbook yalnızca Debian/Ubuntu sistemleri destekler."
when: ansible_facts['os_family'] != "Debian"
- name: Paket listesini güncelle
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
tags: install
- name: nginx'i kur
ansible.builtin.apt:
name: nginx
state: present
tags: install
- name: nginx konfigürasyonunu kopyala
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
owner: root
group: root
mode: "0644"
notify: nginx'i yeniden yükle
tags: config
- name: Siteyi etkinleştir
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: nginx'i yeniden yükle
tags: config
- name: nginx servisini başlat ve boot'ta otomatik başlamasını sağla
ansible.builtin.service:
name: nginx
state: started
enabled: true
tags: service
- name: nginx'in çalıştığını doğrula
ansible.builtin.command:
cmd: systemctl is-active nginx
register: nginx_check
changed_when: false
- name: Sonucu göster
ansible.builtin.debug:
msg: "nginx durumu: {{ nginx_check.stdout }}"
handlers:
- name: nginx'i yeniden yükle
ansible.builtin.service:
name: nginx
state: reloaded
Bu playbook'ta dikkat edilmesi gereken birkaç nokta var. cache_valid_time: 3600 parametresi paket listesinin son 1 saat içinde güncellenmiş olması durumunda tekrar güncelleme yapılmasını engeller, böylece her çalıştırmada gereksiz ağ trafiği oluşmaz. changed_when: false ise command modülünün doğrulama amacıyla kullanıldığını ve bir değişiklik yapmadığını Ansible'a belirtir; aksi halde her çalıştırmada changed dönerdi. state: link ile sembolik link oluşturulması, Debian/Ubuntu'nun nginx site etkinleştirme mekanizmasının idempotent bir şekilde kullanılmasını sağlar.
YAML Format ve Doğrulama
Ansible playbook'ları YAML formatında yazılır. YAML'ın sözdizimi basit görünse de küçük hatalar saatlerce süren debug süreçlerine yol açabilir.
Temel Kurallar
| Kural | Açıklama |
|---|---|
| Girinti | 2 space kullanın, TAB kesinlikle kullanmayın |
| Liste | - ile başlar, altındaki satırlarla hizalanır |
| Anahtar | key: value formatındadır |
| Boolean | true/false küçük harfle yazılır |
| String | Özel karakter içeriyorsa tırnak içine alınmalı |
| Boş satır | Anlam taşımaz, okunabilirlik için kullanılabilir |
Sık Yapılan Hatalar
Yanlış girinti
En yaygın hatadır. YAML'da girinti anlam taşır; bir key'in hangi bloğa ait olduğu tamamen girintiye göre belirlenir.
# YANLIŞ
tasks:
- name: nginx kur # tasks altında olmalı, hizalama yanlış
package:
name: nginx
state: present
# DOĞRU
tasks:
- name: nginx kur
package:
name: nginx
state: present
TAB karakteri kullanımı
YAML, TAB karakterini desteklemez. Editörünüzün TAB tuşunu space'e çevirdiğinden emin olun.
Tırnaksız özel karakterler
:, {, }, [, ], # gibi karakterler değer içinde kullanılıyorsa tırnak zorunludur.
# YANLIŞ
msg: Sunucu: {{ hostname }}
# DOĞRU
msg: "Sunucu: {{ hostname }}"
Boolean karmaşası
Yes, No, TRUE, FALSE, on, off gibi değerler YAML'da boolean olarak yorumlanır. String olarak kullanılması gerekiyorsa tırnak içine alınmalıdır.
# "yes" string değil boolean true olarak yorumlanır
state: yes # Dikkatli olun
# String olarak kullanmak için
state: "yes"
yamllint ile Doğrulama
yamllint, YAML dosyalarını çalıştırmadan önce sözdizimi ve stil hatalarını tespit eden bir araçtır. Ansible'ın kendi syntax kontrolünden daha kapsamlıdır; girinti tutarsızlıkları, gereksiz boşluklar ve satır uzunluğu gibi stil sorunlarını da raporlar.
pip install yamllint
yamllint myplaybook.yml
Örnek çıktı:
myplaybook.yml
3:1 warning missing document start "---" (document-start)
8:81 error line too long (85 > 80 characters) (line-length)
12:5 error wrong indentation: expected 4 but found 2 (indentation)
Projeye özel kurallar .yamllint adlı bir konfigürasyon dosyasıyla tanımlanabilir:
# .yamllint
extends: default
rules:
line-length:
max: 120 # Satır uzunluğu limitini artır
truthy:
allowed-values: # Yalnızca bu boolean değerlerine izin ver
- "true"
- "false"
comments:
min-spaces-from-content: 1
ansible-lint ile Daha Derin Analiz
yamllint sözdizimini kontrol ederken ansible-lint Ansible'a özgü en iyi pratikleri denetler. Deprecated modül kullanımı, eksik name alanları, idempotency sorunları gibi konularda uyarı verir.
pip install ansible-lint
ansible-lint myplaybook.yml
Örnek çıktı:
WARNING Listing 2 violation(s) that are fatal
yaml[truthy]: Use "true" or "false" instead of "yes" or "no"
fqcn[action-core]: Use FQCN for builtin actions (ansible.builtin.package)
İkisi birbirini tamamlar: yamllint önce çalıştırılır, ardından ansible-lint ile Ansible'a özgü kontroller yapılır.
yamllint ve ansible-lint arasındaki temel fark nedir?
Sık Sorulan Sorular
set_fact ile register arasındaki fark nedir?
register bir görevin çıktısını olduğu gibi yakalar. set_fact ise istediğimiz zaman, istediğimiz değerle yeni bir değişken tanımlamamızı sağlar. Genellikle register çıktısından ihtiyaç duyulan kısım set_fact ile daha okunabilir bir isme atanır.
set_fact ile tanımlanan değişkenler ne kadar süre geçerlidir?
Playbook boyunca geçerlidir. Başka bir playbook çağrıldığında geçerliliğini yitirir.
--diff tüm modüllerde işe yarar mı?
Hayır. Sadece state-aware modüllerde ve dosya üzerinde değişiklik yapan modüllerde (copy, template, lineinfile) etkilidir.
Handler çalışmadı, neden olabilir?
En yaygın sebep notify değeriyle handlers bölümündeki name değerinin birebir eşleşmemesidir. Büyük/küçük harf ve boşluk dahil tamamen aynı olmalıdır. İkinci sebep ise ilgili task'ın changed değil ok dönmesidir; handler yalnızca changed durumunda tetiklenir.
Ansible Windows sistemleri yönetebilir mi?
Evet, ancak yapılandırma gerektirir. Linux/macOS için SSH kullanılırken Windows için WinRM protokolü üzerinden iletişim sağlanır.
ansible-playbook çalıştırıldığında her zaman gather_facts yapılır mı?
Varsayılan olarak evet. gather_facts: false ile devre dışı bırakılabilir.
Bu yazıda Ansible'ın komut satırı arayüzünden başlayarak playbook yazımının temel kavramlarına kadar olan her şeyi ele aldık. Playbook anatomisi, facts yönetimi, koşullu çalıştırma, handler'lar ve tags gibi konular, ilerleyen yazılarda ele alacağımız rol mimarisi ve Vault ile secret yönetimi için sağlam bir temel oluşturuyor.