SSD Nodes Learn 🎉 VPS $5.50/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-07

Ansible Playbook ve Role Arasındaki Farklar

Ansible playbook ile role yapısı arasındaki farkları ve hangisinin ne zaman kullanılacağını öğrenin. Kod tekrarını önlemek için 100 satır kuralı ve dizin yapısı detayları.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Ansible playbook ve role farkı

Ansible playbook, ansible-playbook ile çalıştırdığınız dosyadır. Bir grup sunucuyu yapılması gereken işlerle eşleştirir. Ansible role ise görevleri, şablonları, işleyicileri ve varsayılan değişkenleri barındıran, sabit bir dizin yapısına sahip bir yapıdır ve bir playbook tarafından ismiyle çağrılır. Her ikisinin içindeki görev sözdizimi aynıdır, dolayısıyla bu neyi ifade edebileceğinizle ilgili bir soru değildir. Bu, yeniden kullanılabilirlik ile ilgili bir sorudur.

Düz bir playbook ile başlayın. Bir site.yml içinde yer alan bir tasks: listesi, ilk otomasyonunuz için doğru biçimdir ve çoğu kişinin beklediğinden daha uzun süre yeterli olur. Aynı görev bloğunun ikinci bir sunucu grubu için çalıştırılması gerektiğinde veya dosya yaklaşık 100 satırı aşıp artık kaydırarak bir görevi bulamaz hale geldiğinizde role yapısına geçin.

Henüz bir tane yazmadıysanız, tek bir VPS üzerinde ilk playbook ile başlayın ve dosyanız büyümeye başladığında tekrar buraya dönün.

Düz bir playbook ne zaman doğru tercihtir

Düz bir playbook; iş tek seferlikse, tek bir sunucu üzerinde gerçekleşiyorsa veya başka kimse tarafından okunmayacaksa doğru tercihtir. Tek bir uygulama sunucusunun hazırlanması veya bir bakım penceresi öncesinde bir makinenin yamalanması gibi durumlar, bir dizin ağacını hak etmez. Bir rol, yedi dizin ve bir dolaylılık katmanı ekler. Eğer tek çağırıcı yanındaki playbook ise, bu dolaylılık hiçbir fayda sağlamaz; aksine neyin çalıştığını görmek istediğiniz her seferinde fazladan bir adım atmanıza neden olur.

Düz bir playbook'un doğru tercih olmaktan çıktığı an bellidir ve bu anı tespit etmek kolaydır. Bir görev bloğunu ikinci bir playbook'a kopyaladığınızda, bu kopyalama bir sinyaldir. O andan itibaren her düzeltmenin iki kez yapılması gerekir ve bir gün mutlaka sadece bir kez yapılacaktır.

Bir rol dizini aslında ne içerir

roles/common/
  defaults/main.yml
  vars/main.yml
  tasks/main.yml
  handlers/main.yml
  templates/99-hardening.conf.j2
  files/
  meta/main.yml
  • tasks/main.yml giriş noktasıdır. Bir rol çağrıldığında Ansible bu dosyayı çalıştırır; diğer tüm dizinler isteğe bağlıdır.
  • defaults/main.yml, çağrı yapanın geçersiz kılması beklenen değişkenleri tutar. Ansible içindeki en düşük öncelikli kaynak olduğundan, neredeyse her şey bunun önüne geçer.
  • vars/main.yml, çağrı yapanın geçersiz kılması beklenmeyen değişkenleri tutar. Öncelik sırasına göre envanterin üzerinde yer alır; bu oldukça güçlü bir ifadedir. Nadiren kullanın.
  • handlers/main.yml, notify tarafından tetiklenen görevleri tutar. Bir handler, kaç görev onu bildirse bildirsin, oyunun (play) sonunda yalnızca bir kez çalışır.
  • files/, copy modülü tarafından olduğu gibi kopyalanan dosyaları tutar; templates/ ise template modülü tarafından işlenen Jinja2 şablonlarını tutar. Bir rol içerisinde her ikisine de yol belirtmeden, sadece dosya adıyla referans verirsiniz; çünkü Ansible öncelikle rolün kendi dizinlerinde arama yapar.
  • meta/main.yml, rol bağımlılıklarını ve Ansible Galaxy tarafından okunan meta verileri tanımlar.

Bu yapı bir stil tercihi değildir. Ansible tam olarak bu yollara bakar; dolayısıyla roles/common/template/ (tekil) içine koyduğunuz bir şablon asla bulunamaz.

ansible-galaxy init ile ortak rolü oluşturun

mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles common

Bu komut, roles/common altında, kullanmayacağınız dizinler ve yalnızca --- içeren main.yml taslakları dahil olmak üzere tüm iskeleti oluşturur. Boş bıraktıklarınızı silin. Boş bir vars/main.yml dosyası Ansible için zararsızdır ancak rolde hangi dosyaların gerçekten önemli olduğunu gizler.

Şimdi işi yapan dosyaları doldurun. Önce varsayılanlar (defaults), çünkü bunlar rolün genel arayüzüdür.

# roles/common/defaults/main.yml
---
common_packages:
  - ufw
  - fail2ban
  - unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"

"no" ve "yes" değerlerini tırnak içine alın. Ansible, YAML dosyalarını PyYAML ile ayrıştırır; bu kütüphane çıplak bir no ifadesini boolean false olarak okur, bu durumda oluşturulan yapılandırma satırı PermitRootLogin False haline gelir ve sshd bunu reddeder. Tırnak işaretleri değerin string olarak kalmasını sağlar.

# roles/common/tasks/main.yml
---
- name: Install the base packages
  ansible.builtin.apt:
    name: "{{ common_packages }}"
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Create the admin group
  ansible.builtin.group:
    name: "{{ common_admin_group }}"
    state: present

- name: Install the sshd hardening drop-in
  ansible.builtin.template:
    src: 99-hardening.conf.j2
    dest: /etc/ssh/sshd_config.d/99-hardening.conf
    owner: root
    group: root
    mode: "0644"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd
# roles/common/handlers/main.yml
---
- name: Restart sshd
  ansible.builtin.service:
    name: ssh
    state: restarted
# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}

Debian ve Ubuntu sistemlerinde systemd birimi ssh olarak adlandırılırken, RHEL ailesi sistemlerde bu isim sshd şeklindedir. Yanlış ismi kullanan bir handler, yalnızca şablonda bir değişiklik yapıldığında hata verir; bu nedenle sorun genellikle haftalar sonra ortaya çıkar.

validate satırı, bu görevdeki en yararlı kısımdır. Ansible şablonu geçici bir dosyaya işler, %s yerine bu dosyanın yolunu koyar ve komutu çalıştırır. Hedef dosya, yalnızca komut 0 çıkış koduyla biterse değiştirilir. Şablona anlamsız bir direktif ekleyip tekrar çalıştırın: görev failed to validate hatasıyla başarısız olur, gerçek /etc/ssh/sshd_config.d/99-hardening.conf dosyasına dokunulmaz ve sunucuya giriş yapmaya devam edebilirsiniz. Bu kontrolün sadece sözdiziminden fazlasını test ettiğini unutmayın. Eğer sshd -t ana bilgisayar anahtarlarını okuyamazsa sshd: no hostkeys available -- exiting. ile çıkar ve Ansible aynı failed to validate hatasını raporlar; bu nedenle şablonu suçlamadan önce modülün msg belgelerini okuyun.

Bir playbook bir rolü nasıl çağırır

# site.yml
---
- name: Base configuration for every server
  hosts: all
  become: true
  roles:
    - common
# inventory.ini
[local]
localhost ansible_connection=local
ansible-playbook -i inventory.ini site.yml

Play, özet kısmında failed=0 ile bitmelidir. Parametreleri çağrı noktasında genişletilmiş biçimle iletin; bir rolün iki farklı ana bilgisayar grubuna bu şekilde hizmet vermesi sağlanır:

  roles:
    - role: common
      common_admin_group: ops
      common_permit_root_login: prohibit-password

Neredeyse herkesi şaşırtan bir sıralama kuralı vardır. Bir play pre_tasks, roles, tasks ve post_tasks içerebilir; Ansible bunları dosyada hangi sırada yazmış olursanız olun bu sırayla çalıştırır. tasks: kısmını roles: kısmının üzerine koysanız bile roller yine de ilk sırada çalışır. Bu nedenle, bir şeyin bir rolden önce gerçekleşmesi gerekiyorsa, bu tasks: dosyasının en üstünde değil, pre_tasks: içinde yer almalıdır.

- name: Ordering demonstration
  hosts: local
  gather_facts: false
  pre_tasks:
    - name: Runs first
      ansible.builtin.debug:
        msg: pre
  roles:
    - common
  tasks:
    - name: Runs after the role
      ansible.builtin.debug:
        msg: task
  post_tasks:
    - name: Runs last
      ansible.builtin.debug:
        msg: post

Bir rolü roles: anahtarı yerine bir görev listesinin içinden çağırmak için import_role veya include_role kullanın.

  tasks:
    - name: Static, read when the playbook is parsed
      ansible.builtin.import_role:
        name: common

    - name: Dynamic, resolved when the task runs
      ansible.builtin.include_role:
        name: postgres
      when: "'db' in group_names"

import_role statiktir. Ansible rolü ayrıştırma (parse) zamanında okur ve görevleri play'in bir parçası haline gelir; bu nedenle ansible-playbook --list-tasks site.yml bunları listeler ve import işlemine eklenen bir etiket, içerideki her göreve uygulanır. include_role dinamiktir. Görev çalışana kadar hiçbir şey okunmaz; bu da rol adını bir değişken veya döngü ile belirlemenize olanak tanır. Bunun bedeli, söz konusu görevlerin --list-tasks ve --start-at-task tarafından görülememesidir.

Burada bir tuzak mevcuttur. Bir include_role görevi üzerindeki when:, dahil edilen rolün defaults/main.yml kapsamı dahil edilmeden önce değerlendirilir. Include işlemi üzerine when: common_packages | length > 0 yazarsanız, söz konusu değişken dahil ettiğiniz rolün içinde tanımlanmış olsa bile çalışma 'common_packages' is undefined hatasıyla durur. Çözüm, bu anahtarı rolün dışına taşımaktır: değişkeni, her yerde kapsam dahilinde olan group_vars/all.yml içine yerleştirin ve rolün kendi varsayılanlarını, rolün bizzat tükettiği değerler için bırakın.

Hangi değişken önceliklidir: defaults, group_vars, vars, extra vars

Ansible, yirmiden fazla değişken öncelik seviyesi tanımlar. Bunlardan dördü, pratikteki hemen hemen tüm durumları belirler; en zayıftan en güçlüye doğru sıralaması şöyledir:

  • roles/<name>/defaults/main.yml en alt seviyelerde yer alır. Başka herhangi bir yerde tanımladığınız hemen hemen her şey bunun önüne geçer; bu nedenle bir rolün ayarlanabilir parametreleri için en doğru yer burasıdır.
  • group_vars/ ve host_vars/ orta seviyededir. Sitenize özgü değerler burada yer almalı ve bunlar rol varsayılanlarını temiz bir şekilde geçersiz kılmalıdır.
  • roles/<name>/vars/main.yml, host_vars üzerinde yer alır. Burada tanımladığınız bir değer, envanter üzerinden geçersiz kılınamaz. Burayı, bir servis ismiyle eşleşmesi gereken paket ismi gibi, rolün kendi içinde tutarlı kalması gereken öğeler için ayırın.
  • Çağrı noktasında iletilen bir rol parametresi vars/main.yml değerini yener; komut satırındaki -e ise rol parametreleri dahil her şeyin önüne geçer.

Bu çözümleme sürecini yaklaşık bir dakikada gözlemleyebilirsiniz. Küçük bir role bir varsayılan ve bir rol değişkeni atayın, ardından aynı isimleri host_vars içinde tanımlayın.

# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults
# roles/prec/vars/main.yml
---
prec_internal: from-rolevars
# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars
# roles/prec/tasks/main.yml
---
- name: Show which value survived
  ansible.builtin.debug:
    msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"
ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cli

İlk çalıştırma tunable=from-hostvars internal=from-rolevars değerini yazdırır. Envanter, rol varsayılanını yener ancak rol değişkenine karşı kaybeder. İkinci çalıştırma internal=from-cli değerini yazdırır; çünkü extra vars en üst seviyededir ve altındaki hiçbir şey buna engel olamaz. -e kullanımının tek seferlik çalıştırmalar için uygun, ancak kalıcı bir betik için yanlış olmasının nedeni budur: depodaki tüm planlı kararları sessizce geçersiz kılar.

Çalışma kuralı şudur: Bir değerin değiştirilebilir olmasını istiyorsanız, onu defaults/ içine yerleştirin. Değeri vars/ içine koymak, rolün gelecekteki tüm kullanıcılarına envanterin bunu değiştiremeyeceğini bildirir. Bu bazen amaçlanan bir durum olsa da, genellikle bir hatadır.

Rolün idempotent olduğunu kanıtlayın: iki kez çalıştırın

Güvenilir bir Ansible çalışması, ikinci kez çalıştırıldığında aynı sonucu üretmeli ve hiçbir değişiklik yapmadığını raporlamalıdır. Playbook'u iki kez çalıştırın ve özet kısmını inceleyin.

ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml

İkinci özet şu şekilde görünmelidir:

PLAY RECAP *********************************************************************
localhost   : ok=4  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0, her modülün mevcut durumu incelediği ve işin zaten yapılmış olduğunu tespit ettiği anlamına gelir. İkinci çalıştırmada changed=2 görülmesi, iki görevin mevcut durum ile hedef durum arasındaki farkı ayırt edemediği anlamına gelir; bu durumda dosyalar sürekli yeniden yazılır ve servisler sürekli yeniden başlatılır. Bunun yaygın nedeni command veya shell kullanımıdır, çünkü Ansible rastgele bir komutun ne yaptığını bilemez.

# traps.yml
---
- name: Command modules do not know what they changed
  hosts: local
  gather_facts: false
  tasks:
    - name: This appends a line on every run
      ansible.builtin.shell: "echo run >> /tmp/grow.txt"

    - name: This appends a line only once
      ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
      args:
        creates: /tmp/guarded.txt

Playbook'u iki kez çalıştırın ve ardından wc -l /tmp/grow.txt /tmp/guarded.txt içeren satırları sayın. /tmp/grow.txt iki satır, /tmp/guarded.txt ise bir satır içerir. İkinci çalıştırmada korumalı görev hiç çalışmaz ve sonucu skipped, since /tmp/guarded.txt exists mesajını taşır; çünkü creates, modüle ilk olarak bakması gereken görünür bir ürün sunar. Bir komut böyle bir ürün bırakmadığında, çıktısını kaydedin ve changed_when ile kararı kendiniz verin.

ansible-playbook --check --diff site.yml, değişiklikleri yapmadan önce tahmin eder ve --diff, bir şablonun yeniden yazacağı tam satırları yazdırır. Çıktıyı okurken şu uyarıyı dikkate alın: shell ve command görevleri check mode içerisinde atlanır, bu nedenle temiz görünen bir plan yine de arka planda işlem yapabilir.

Ansible neden rolün bulunamadığını söylüyor

Ansible, roles/ dizinini playbook dosyasının hemen yanında, ardından roles_path içinde arar. Arama işlemi shell dizininize göre değil, playbook dosyasının konumuna göre yapılır.

ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely

Bu mesaj, site.yml ve roles/ dosyalarının birbirinden koptuğu anlamına gelir; Ansible denediği yolları çıktı olarak vererek yardımcı olur. İki dosyayı aynı dizinde tutun. Bir üst dizinden çalıştırmak sorun yaratmaz, çünkü esas olan playbook yoludur:

ansible-playbook -i infra/inventory.ini infra/site.yml

Aynı sorunun daha sessiz bir versiyonu daha vardır. Ansible, mevcut dizin herkes tarafından yazılabilir (world writable) olduğunda, o dizindeki ansible.cfg dosyasını görmezden gelir. Bunun nedeni, sunucudaki herhangi bir kullanıcının oraya bir yapılandırma dosyası bırakıp çalıştırdığınız komutun işleyişini değiştirebilme riskidir.

[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.

Bu durumda roles_path ve inventory ayarlarınız sessizce devre dışı kalır ve rol arama işlemi, rollerle hiçbir ilgisi olmayan bir nedenden dolayı başarısız olur. ansible --version komutu fiilen yüklenen config file dosyasını, ansible-config dump --only-changed komutu ise varsayılan ayarlardan farklı olan tüm ayarları listeler. Bir çalıştırma, yapılandırmanız yokmuş gibi davranıyorsa her iki komutu da kontrol edin.

Rolleri paylaşma: requirements.yml ve sabitlenmiş sürüm

Başkasının yazdığı bir rol kopyalanmaz, kurulur. Bunu bir kez tanımlayın:

# requirements.yml
---
roles:
  - name: postgres
    src: https://github.com/example/ansible-role-postgres
    scm: git
    version: v1.4.0
ansible-galaxy install -r requirements.yml -p galaxy_roles

Her zaman version ayarını yapın. Bu ayar olmadan, komutu çalıştırdığınız gün varsayılan dalda ne varsa onu alırsınız; bu da geçen ay çalışan bir dağıtımın, kendi deponuzda hiçbir değişiklik yapmasanız bile bozulmasına neden olur. roles_path değerini indirme dizinine yönlendirin ve bu dizini git dışında tutun:

# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_roles

Playbook'un yanındaki roles/ dizininde bulunan roller yine de bulunur, çünkü bu yol roles_path dizinine ek olarak her zaman aranır. Böylece kendi rolleriniz commit edilip gözden geçirilebilir durumda kalırken, üçüncü taraf roller bir etikete sabitlenmiş, yeniden üretilebilir indirmeler olarak yönetilir.

Rollerin yetersiz kaldığı durumlar

Rol, bir Ansible çalışması içindeki yeniden kullanılabilirlik birimidir. Sağlayıcınız üzerinde sunucu veya DNS kaydı oluşturmaz; bunu yapmaya çalışmak, playbook'ların kimsenin bakımını yapmak istemediği bir yapıya dönüşmesine neden olur. Başlamadan önce Ansible ve Terraform arasındaki iş bölümü konusunu okumak faydalıdır. Bir rol, envanter tasarımının yerini de tutmaz: birkaç makineyi geçtikten sonra, sunucuları nasıl gruplandırdığınız ve onlara nasıl eriştiğiniz, görevlerin nasıl dosyalandığından daha önemli hale gelir.

Bu common rolünün uyguladığı sıkılaştırma işlemleri de kendi içinde özel kararlar gerektirir. Yukarıdaki ek yapılandırma sadece iki direktif belirler ve fazlasını yapmaz; bu nedenle, sahip olduğunuz her sunucu için role nelerin dahil edilmesi gerektiğine karar vermeden önce hangi SSH ayarlarının değiştirilmeye değer olduğu ve Ubuntu'nun güvenlik güncellemelerini nasıl otomatik uygulayacağı konularını inceleyin.

FAQ

Bir Ansible playbook'unu ne zaman role yapısına dönüştürmeliyim?

Aynı görev bloğunun ikinci bir play içerisinde veya ikinci bir sunucu grubuna karşı çalıştırılması gerektiğinde. Görevleri playbook'lar arasında kopyalamak bir uyarı işaretidir; çünkü o andan itibaren her düzeltmenin iki kez uygulanması gerekir ve bir gün mutlaka sadece birine uygulanacaktır. Yaklaşık 100 satırın altında olan ve sadece tek bir grubu hedefleyen bir playbook, role yapısından bir kazanç sağlamaz; aksine fazladan dizinler okunabilirliği zorlaştırır.

Role'ler aynı play içindeki görevlerden önce mi çalışır?

Evet. Ansible önce pre_tasks, ardından roles: altında listelenen her şeyi, sonra tasks: ve en son post_tasks: kısmını çalıştırır; bu anahtarların dosyanızdaki sıralamasını dikkate almaz. tasks: kısmını roles: üzerinde yazmak, o görevlerin önce çalışmasını sağlamaz. Bir işlemin role'den önce gerçekleşmesi gerekiyorsa, bunu pre_tasks: içerisine yerleştirin.

Neden group_vars değerim role içindeki değeri geçersiz kılmıyor?

Değişkenin role'ün defaults/main.yml dosyası yerine vars/main.yml içerisinde tanımlanıp tanımlanmadığını kontrol edin. vars/, Ansible'ın öncelik sıralamasında group_vars ve host_vars üzerinde yer alır; bu nedenle envanter dosyası bunu geçersiz kılamaz. Değişkeni, öncelik sıralamasının en alt kısımlarında yer alan ve çağıran tarafın değiştirebilmesi gereken her şey için doğru yer olan defaults/main.yml içerisine taşıyın. Sorunun bir yazım hatası değil de öncelik sıralaması olduğunu doğrulamak için, diğer tüm kaynaklardan daha üstün olan -e name=value ile bir kez çalıştırın.

Ansible neden role'ün bulunamadığını söylüyor?

Arama işlemi playbook dosyasının yanında başlar; bu nedenle site.yml ve roles/ aynı dizinde bulunmalıdır. Hata mesajı, the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely örneğinde olduğu gibi denediği yolları yazdırır. Playbook'u bir üst dizinden çalıştırmak sorun yaratmaz, çünkü arama işlemi shell'in çalışma dizinini değil, playbook'un yolunu takip eder. Eğer ansible.cfg içerisindeki roles_path dosyasına bağımlıysanız, dosyanın ansible --version ile yüklendiğini doğrulayın; çünkü herkesin yazabileceği (world writable) bir çalışma dizini, Ansible'ın bu dosyayı görmezden gelmesine neden olur.

Bir role oluşturmak için ansible-galaxy init kullanmam gerekiyor mu?

Hayır. Bir role sadece beklenen isimlere sahip dizinlerden ibarettir; dolayısıyla mkdir -p roles/common/tasks ve bir tasks/main.yml dosyası zaten çalışan bir role oluşturur. ansible-galaxy init --init-path roles common, yazım yükünü azaltır ve meta/main.yml ile bir README taslağı da dahil olmak üzere tam bir iskelet sunar. Boş bıraktığınız dizinleri silin; çünkü boş bir vars/main.yml, role içerisindeki hangi dosyaların gerçekten bir işlev gördüğünü gizler.

#ansible#roles#playbook#structure#automation