Ansible ile ilk playbook'unuzu yazdıktan bir süre sonra hep aynı yere gelirsiniz: site.yml dosyası altı yüz satırı geçmiştir, aynı Nginx görevlerini ikinci bir projeye kopyalamışsınızdır ve iki kopya çoktan birbirinden ayrılmaya başlamıştır. Ansible rol yapısı tam olarak bu noktada devreye girer; görevleri, şablonları, dosyaları ve değişkenleri belirli bir dizin düzeninde paketleyip tek bir isimle çağrılabilir hâle getirir.
Bu rehberde rolün ne olduğunu, standart dizin yapısındaki her klasörün ne işe yaradığını, sıfırdan bir rol yazmayı, defaults ile vars arasındaki kritik farkı ve değişken önceliklerini, rolleri playbook'ta çağırmanın üç ayrı yolunu, rol bağımlılıklarını ve Galaxy üzerinden paylaşımı ele alacağım. Sonunda da yeniden kullanılabilir bir rolü, yalnızca dizinlere bölünmüş bir playbook'tan ayıran tasarım kararlarına ve sık düşülen tuzaklara değineceğim.
Rol Nedir ve Ne Zaman Role Geçmelisiniz#
Bir rol, tek bir sorumluluğu yerine getiren Ansible bileşenidir: "bu sunucuya Nginx kur ve yapılandır", "bu sunucuyu güvenlik temeline oturt", "PostgreSQL kur". Teknik olarak rol, önceden belirlenmiş isimlere sahip alt dizinlerden oluşan bir klasördür. Ansible bu isimleri tanır ve içindeki main.yml dosyalarını kendiliğinden yükler; siz hiçbir yerde "şu dosyayı da oku" demezsiniz.
Role geçme zamanının geldiğini üç işaretten anlarsınız. Birincisi, aynı görev bloğunu ikinci bir playbook'a kopyalamış olmanız. İkincisi, playbook dosyanızın tek ekrana sığmaması ve içinde arama yapmaya başlamanız. Üçüncüsü, aynı yapılandırmayı iki farklı ortamda (test ve üretim) küçük farklarla kullanmanız gerekmesi. Bu üçünden biri olduysa rol yazmanın maliyeti, yazmamanın maliyetinin altına inmiş demektir.
Rolün getirdiği asıl kazanç dizin düzeni değil, arayüz kavramıdır. İyi yazılmış bir rolün defaults/main.yml dosyası, o rolün ayarlanabilir tüm parametrelerini listeler; rolü kullanan kişi rolün içine hiç bakmadan sadece bu değişkenleri ezerek davranışı değiştirir. Bu, tıpkı bir fonksiyonun imzası gibidir.
| Yapı | Ne zaman uygun | Sınırı |
|---|---|---|
| Tek playbook | Tek sunucu, tek seferlik iş | Büyüdükçe okunmaz olur |
include_tasks ile bölünmüş playbook | Orta boy, tek proje | Şablon ve değişkenler dağınık kalır |
| Rol | Tekrar kullanılan her yapılandırma | Öğrenme eşiği biraz yüksek |
| Collection | Rol + modül + eklenti paketi dağıtımı | Küçük projeler için fazla ağır |
Standart Rol Dizin Yapısı#
Ansible bir rolü çağırdığında sabit bir dizin listesine bakar. Hepsini oluşturmak zorunda değilsiniz; olmayan dizin sessizce atlanır. ansible-galaxy komutu iskeleti sizin için üretir:
mkdir -p roles && cd roles
ansible-galaxy init nginx
# - Role nginx was created successfully
tree nginx
# nginx/
# ├── defaults/main.yml
# ├── files/
# ├── handlers/main.yml
# ├── meta/main.yml
# ├── tasks/main.yml
# ├── templates/
# ├── tests/
# └── vars/main.yml
Her dizinin görevi net biçimde ayrılmıştır ve bu ayrımı bilmek rolün doğru yazılması için şarttır:
| Dizin | İçeriği | Otomatik yüklenir mi |
|---|---|---|
tasks/ | Rolün ana görev listesi (main.yml) | Evet |
handlers/ | notify ile tetiklenen görevler | Evet |
defaults/ | En düşük öncelikli değişkenler | Evet |
vars/ | Yüksek öncelikli, ezilmemesi gereken değişkenler | Evet |
files/ | copy modülünün olduğu gibi kopyaladığı dosyalar | Yol otomatik çözülür |
templates/ | Jinja2 şablonları (.j2) | Yol otomatik çözülür |
meta/ | Rol bağımlılıkları ve üst veri | Evet |
library/ | Role özel Python modülleri | Evet |
files/ ve templates/ dizinlerinin "yol otomatik çözülür" özelliği pratikte çok işe yarar: rol içindeki bir görevde src: site.conf.j2 yazdığınızda Ansible dosyayı önce roles/nginx/templates/ altında arar. Tam yol yazmanıza gerek kalmaz ve rolü başka bir projeye taşıdığınızda yollar bozulmaz.
Sıfırdan Bir Rol Yazmak#
Nginx rolünü gerçek içerikle dolduralım. Önce ayarlanabilir arayüzü, yani defaults/main.yml dosyasını yazın. Buraya rolün tüm parametreleri, makul varsayılan değerleriyle girer:
# roles/nginx/defaults/main.yml
---
nginx_paket_adi: nginx
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_client_max_body_size: 16m
nginx_gzip: true
# Rolü kullanan kişi bu listeyi ezerek kendi sitelerini tanımlar
nginx_siteler:
- ad: firmaniz.com
kok: /var/www/firmaniz.com
port: 80
Ardından görevler. Görevleri okunabilir tutmak için alt dosyalara bölmek, rol büyüdükçe hayat kurtarır:
# roles/nginx/tasks/main.yml
---
- name: Dağıtıma özel değişkenleri yükle
ansible.builtin.include_vars: "{{ ansible_facts['os_family'] }}.yml"
failed_when: false
- name: Nginx kurulumu
ansible.builtin.include_tasks: kurulum.yml
tags: [nginx, kurulum]
- name: Site yapılandırmaları
ansible.builtin.include_tasks: siteler.yml
tags: [nginx, yapilandirma]
# roles/nginx/tasks/kurulum.yml
---
- name: Paketi kur
ansible.builtin.package:
name: "{{ nginx_paket_adi }}"
state: present
- name: Ana yapılandırmayı üret
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: "nginx -t -c %s"
notify: nginx yeniden yükle
- name: Servisi etkinleştir
ansible.builtin.service:
name: nginx
state: started
enabled: true
Buradaki validate parametresine dikkat edin: dosyayı hedefe yazmadan önce geçici bir kopyada nginx -t çalıştırır, söz dizimi hatalıysa dosyayı hiç yerleştirmez. Bu tek satır, bozuk bir şablonun sunucuyu yeniden başlatılamaz hâle getirmesini önler ve rol yazarken edinilecek en değerli alışkanlıklardan biridir.
Siteleri döngüyle üretelim:
# roles/nginx/tasks/siteler.yml
---
- name: Site yapılandırma dosyalarını oluştur
ansible.builtin.template:
src: site.conf.j2
dest: "/etc/nginx/conf.d/{{ item.ad }}.conf"
mode: "0644"
loop: "{{ nginx_siteler }}"
loop_control:
label: "{{ item.ad }}"
notify: nginx yeniden yükle
Ve handler:
# roles/nginx/handlers/main.yml
---
- name: nginx yeniden yükle
ansible.builtin.service:
name: nginx
state: reloaded
loop_control.label küçük ama önemli bir ayrıntıdır: onsuz çıktıda tüm sözlük yapısı satır satır basılır ve ekran okunmaz hâle gelir. Playbook temelleri konusunda tazelenmek isterseniz Ansible kurulumu ve ilk playbook yazısı görev, modül ve handler kavramlarını sıfırdan ele alıyor. Nginx yerine Apache tercih ediyorsanız Apache ve Nginx karşılaştırması yazısı seçimde yardımcı olur.
defaults mı vars mı: Değişken Öncelikleri#
Yeni başlayanların en çok karıştırdığı konu budur ve doğru anlaşılmadığında "değişkenim neden ezilmiyor" sorusuyla saatler kaybedilir. Ansible'da onlarca değişken kaynağı vardır ve hepsinin kesin bir öncelik sırası bulunur. Rol yazarken bilmeniz gereken kısmı şudur:
| Kaynak | Öncelik | Kullanım amacı |
|---|---|---|
roles/x/defaults/main.yml | En düşük | Rolün ezilebilir varsayılanları |
group_vars/all | Düşük | Tüm ortam için ortak ayar |
group_vars/<grup> | Orta | Gruba özel ayar |
host_vars/<host> | Orta-yüksek | Tek sunucuya özel ayar |
Play içindeki vars: | Yüksek | Bu çalıştırmaya özel |
roles/x/vars/main.yml | Yüksek | Rolün ezilmemesi gereken sabitleri |
-e komut satırı değişkeni | En yüksek | Acil müdahale, tek seferlik |
Kural basit: rolün ayarlanabilir her parametresi defaults/ içinde olmalıdır. vars/ dizinini yalnızca kullanıcının değiştirmemesi gereken, dağıtıma bağlı sabitler için kullanın; örneğin paket adının Debian'da nginx, RedHat ailesinde de nginx ama servis dosyası yolunun farklı olması gibi.
# roles/nginx/vars/Debian.yml
nginx_conf_dizini: /etc/nginx/conf.d
nginx_kullanici: www-data
# roles/nginx/vars/RedHat.yml
nginx_conf_dizini: /etc/nginx/conf.d
nginx_kullanici: nginx
Bir değişkenin gerçekte hangi değeri aldığını merak ettiğinizde tahmin yürütmeyin, sorun:
# Tek bir hostta değişkenin son hâlini gör
ansible web1 -m ansible.builtin.debug -a "var=nginx_worker_processes"
# Bir hostun aldığı tüm değişkenleri listele
ansible-inventory --host web1 -y
Değişken adlarını rol adıyla öneklemek de yazılı olmayan ama çok değerli bir kuraldır: nginx_port yerine sadece port yazarsanız, aynı playbook'ta çalışan başka bir rolün port değişkeniyle çakışır ve teşhisi zor bir hata üretirsiniz. Ansible'da değişkenler global bir alan paylaşır; önek koymak tek korumanızdır.
Rolleri Playbook'ta Çağırmanın Üç Yolu#
Rolü kullanmanın üç ayrı yöntemi vardır ve aralarındaki fark yalnızca söz dizimi değildir.
---
- name: Web sunucularını hazırla
hosts: web
become: true
# 1) Klasik yöntem: statik, play başında çözülür
roles:
- common
- role: nginx
nginx_siteler:
- { ad: firmaniz.com, kok: /var/www/firmaniz.com, port: 80 }
tasks:
# 2) import_role: statik, derleme anında çözülür
- name: Sertifika rolünü içe aktar
ansible.builtin.import_role:
name: certbot
# 3) include_role: dinamik, çalışma anında çözülür
- name: Yalnızca veritabanı sunucularında
ansible.builtin.include_role:
name: postgresql
when: "'db' in group_names"
Aradaki fark şöyle özetlenir: roles: ve import_role statiktir, playbook derlenirken çözülür; bu yüzden --list-tasks çıktısında görünürler ve etiketler (tags) beklendiği gibi çalışır. include_role ise dinamiktir, sıra oraya geldiğinde çözülür; when koşulu, döngü ve çalışma anında hesaplanan değişkenlerle kullanılabilir ama çalıştırmadan önce görev listesinde görünmez.
Pratik tavsiye: ne yapacağınızı önceden biliyorsanız statik kullanın, çünkü hata ayıklamak ve etiketlemek kolaydır. Karar çalışma anında veriye bağlıysa dinamik kullanın. roles: bloğundaki roller, tasks: bloğundan önce çalışır; bu sıralama sık atlanır ve "rol görevden sonra çalışsın istiyordum" şaşkınlığına yol açar. Öyle bir ihtiyacınız varsa post_tasks kullanın ya da rolü import_role ile görev sırasına yerleştirin.
Bağımlılıklar, Galaxy ve Collection#
Bir rol başka bir role ihtiyaç duyabilir. Örneğin wordpress rolü çalışmadan önce php ve mysql rollerinin çalışmış olması gerekir. Bunu meta/main.yml dosyasında ilan edersiniz:
# roles/wordpress/meta/main.yml
---
galaxy_info:
role_name: wordpress
author: firmaniz
description: WordPress kurulumu ve yapılandırması
license: MIT
min_ansible_version: "2.14"
platforms:
- name: Debian
versions: [bookworm]
- name: EL
versions: ["9"]
dependencies:
- role: php
php_surum: "8.3"
- role: mysql
⚠️ Bağımlılıklar varsayılan olarak yalnızca bir kez çalışır; aynı rol birden fazla yerden bağımlılık olarak çağrılsa bile tekrar edilmez. Bu davranışı değiştirmek isterseniz allow_duplicates: true kullanabilirsiniz, ama genellikle istemezsiniz. Ayrıca bağımlılık zinciri derinleştikçe rollerin hangi sırada çalıştığını izlemek zorlaşır; iki seviyeden fazla bağımlılıktan kaçının ve sıralamayı playbook'ta açıkça yazmayı tercih edin.
Dışarıdan hazır rol veya koleksiyon kullanmak için requirements.yml dosyası yazıp topluca kurabilirsiniz:
# requirements.yml
---
collections:
- name: community.general
version: ">=8.0.0"
- name: ansible.posix
roles:
- name: firmaniz.temel_guvenlik
src: https://github.com/ornek/ansible-temel-guvenlik
scm: git
version: v1.4.0
# Hepsini kur
ansible-galaxy install -r requirements.yml
# Kurulu rolleri listele
ansible-galaxy role list
# Yalnızca koleksiyonları belirli bir dizine kur
ansible-galaxy collection install -r requirements.yml -p ./collections
Kendi rollerinizi Git deposunda tutup sürüm etiketiyle sabitlemek en sağlıklı yöntemdir; version: v1.4.0 yazdığınızda rolün ana dalındaki bir değişiklik altınızdan zemini çekmez. Rolleri ayrı depolarda tutup ana projeye bağlamanın bir başka yolu da alt modüldür; artıları ve tuzakları için Git submodule kullanımı yazısına bakabilirsiniz. Gizli değerleri rol değişkenlerine düz metin koymayın; onlar Ansible Vault ile şifrelenir.
Sık Yapılan Hatalar ve Tasarım Tuzakları#
Her şeyi tek role tıkmak. "Sunucu kurulum" adında, içinde güvenlik duvarı, PHP, MySQL, Nginx ve yedekleme olan bir rol, aslında role bölünmemiş bir playbook'tur. Bir rolün adı tek bir isim olabiliyorsa (nginx, redis, firewall) sınırı doğru çizmişsiniz demektir; adında "ve" geçiyorsa bölün.
defaults yerine vars kullanmak. Rolün parametrelerini vars/main.yml içine koyarsanız, kullanan kişi group_vars ile bunları ezemez, çünkü vars daha yüksek önceliklidir. Sonuç: rol yeniden kullanılamaz hâle gelir. Ezilebilir olması gereken her şey defaults içinde durur.
Değişken adlarını öneksiz bırakmak. port, kullanici, dizin gibi genel adlar başka rollerle çakışır. Rol adıyla öneklemek (nginx_port) çakışmayı yapısal olarak önler.
Rolü belirli bir ortama gömmek. İçinde 185.12.34.56 gibi sabit IP, /home/deploy gibi sabit yol ya da tek bir alan adı geçen rol, ikinci projede çalışmaz. Ortama bağlı her değer bir değişken olmalı, gerçek değeri group_vars ya da host_vars içinde durmalıdır.
Handler adlarını değiştirmek. notify ile handler adı birebir eşleşir. Handler adını düzenleyip notify satırını güncellemeyi unutmak sessiz bir hatadır: Ansible eşleşmeyen bir notify için uyarı verir ama play başarılı sayılır, servis ise hiç yeniden yüklenmez.
| Belirti | Olası neden | Kontrol |
|---|---|---|
| Değişken ezilmiyor | vars/ içinde tanımlı | ansible-inventory --host |
| Şablon bulunamıyor | Dosya templates/ dışında | Rol dizin ağacını kontrol edin |
| Handler çalışmıyor | Ad eşleşmiyor veya değişiklik yok | notify adını birebir karşılaştırın |
| Rol iki kez çalışıyor | allow_duplicates açık | meta/main.yml bakın |
| Etiket işe yaramıyor | include_role dinamik çözülüyor | import_role ile deneyin |
Son bir tavsiye: her rol için kısa bir README.md yazın ve içinde yalnızca iki şey bulunsun — rolün ne yaptığı ve defaults içindeki değişkenlerin tablosu. Altı ay sonra o rolü kullanacak kişi (muhtemelen siz olacaksınız) rolün kodunu okumak zorunda kalmaz.
Sıkça Sorulan Sorular#
Ansible rolü ile playbook arasındaki fark nedir#
Playbook, hangi sunucularda hangi işlerin yapılacağını tanımlayan üst düzey dosyadır; hedefleri, sırayı ve genel akışı belirler. Rol ise tek bir sorumluluğu yerine getiren, görev, şablon, dosya ve varsayılan değişkenlerini birlikte taşıyan yeniden kullanılabilir bir pakettir. Yani playbook "ne zaman ve nerede", rol "nasıl" sorusunu cevaplar. Bir playbook birden çok rolü çağırır, roller ise playbook'tan bağımsız olarak başka projelerde de kullanılabilir.
defaults ve vars dizinleri arasındaki fark nedir#
İkisi de değişken tanımlar ama öncelikleri taban tabana zıttır. defaults/main.yml Ansible'daki en düşük öncelikli kaynaktır, dolayısıyla oraya yazdığınız her değer group_vars, host_vars veya playbook tarafından kolayca ezilir; rolün ayarlanabilir arayüzü burasıdır. vars/main.yml ise çok yüksek önceliklidir ve neredeyse hiçbir şeyle ezilemez; yalnızca dağıtıma bağlı sabitler gibi değiştirilmemesi gereken değerler için kullanılmalıdır.
Rolleri nasıl sürümlerim ve paylaşırım#
En pratik yöntem her rolü kendi Git deposunda tutmak ve anlamlı sürüm etiketleri atmaktır. Ardından projelerinizde bir requirements.yml dosyası oluşturup rolü depo adresi ve version alanıyla sabitlersiniz; ansible-galaxy install -r requirements.yml komutu hepsini kurar. Sürümü sabitlemek kritiktir, çünkü ana dalda yapılan bir değişikliğin bir gün habersizce üretim playbook'unuza girmesini önler.
include_role ile import_role arasında hangisini seçmeliyim#
Rolün çalışıp çalışmayacağı önceden belliyse import_role (ya da roles: bloğu) kullanın; statik olduğu için --list-tasks çıktısında görünür, etiketler beklendiği gibi çalışır ve hata mesajları daha anlaşılır olur. Karar çalışma anında hesaplanan bir değişkene, bir döngüye ya da hedefe göre değişen bir koşula bağlıysa include_role gerekir. Şüphedeyseniz statik olanı tercih edin.
Bir rolü test etmeden üretime almamak için ne yapabilirim#
En basit yol, rolü önce tek bir test sunucusunda --limit ile çalıştırmak ve --check --diff ile ne değişeceğini görmektir. Bir adım ötesi, rolü her değişiklikte temiz bir konteyner ya da sanal makinede baştan uygulayıp ikinci çalıştırmada changed sayısının sıfır olduğunu doğrulamaktır; bu, hem çalıştığını hem de idempotan olduğunu kanıtlar. Ekipçe çalışıyorsanız bu iki adımı sürekli entegrasyon hattınıza taşıyın.
Aynı rolü aynı playbook'ta iki kez çalıştırabilir miyim#
Evet, roles: bloğunda ya da include_role ile farklı değişken setleriyle iki kez çağırabilirsiniz; örneğin iki ayrı siteyi ayrı ayrı kurmak için. Ancak bağımlılık olarak çağrılan roller varsayılan olarak yalnızca bir kez çalışır; bunu değiştirmek için rolün meta/main.yml dosyasında allow_duplicates: true yazmanız gerekir. Çoğu durumda daha temiz çözüm, rolün içinde bir döngü kullanıp tek çağrıyla birden fazla örneği yapılandırmaktır.
Rol içinde hangi görevleri etiketlemeliyim#
Rolün doğal aşamalarını etiketleyin: kurulum, yapılandırma, servis yönetimi gibi. Böylece --tags yapilandirma diyerek yalnızca dosyaları güncelleyebilir, paket kurulumunu atlayabilirsiniz. Etiketleri tek tek göreve değil, include_tasks bloklarına vermek daha sürdürülebilirdir. Unutmayın ki include_role ile dinamik çağrılan rollerde etiket davranışı beklediğinizden farklı olabilir; etiketlere güveniyorsanız statik çağrı kullanın.
Kapanış#
Rol yapısı, Ansible'da bir eşiği geçmenin adıdır: kopyala-yapıştır otomasyondan, tekrar kullanılabilir yapılandırma bileşenlerine geçersiniz. Aklınızda kalması gereken dört alışkanlık şunlar: her rol tek bir sorumluluk üstlensin ve adı tek kelimeyle söylenebilsin; ayarlanabilir her şey defaults içinde dursun, vars yalnızca sabitler için kullanılsın; değişken adlarını rol adıyla önekleyin; ve rolleri Git'te sürüm etiketiyle sabitleyerek projelerinize bağlayın. Bunlar yerine oturduğunda yeni bir sunucu kurmak, birkaç satır YAML yazmaktan ibaret hâle gelir.
Rollerinizi uygulayacağınız altyapı için tam root erişimli VDS, sanal sunucu ve bulut sunucu paketlerimize göz atabilirsiniz; hem test hem üretim ortamını aynı rollerle kurmak isteyenler için uygun bir zemin sunarlar. Kurulum ve sürekli bakımı bizim üstlenmemizi tercih ederseniz sunucu yönetimi hizmetimiz bu işi devralır, yapılandırma değişikliklerinden önce güvenli bir nokta için yedekleme çözümlerimizi kullanabilirsiniz.