Ansible Playbook ve Role Arasındaki Farklar Nelerdir?
Ansible playbook ve role yapılarının ne zaman kullanılması gerektiğini öğrenin. 100 satırı aşan playbook dosyalarında role yapısına geçişin neden gerekli olduğunu keşfedin.
Ansible playbook ve role: fark nedir
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 tutulan 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ı geçtiğinde ve artık kaydırarak bir görevi bulamadığınızda role yapısına geçiş yapın.
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
İşlem tek seferlikse, tek bir sunucu üzerinde gerçekleşiyorsa veya playbook başka kimse tarafından okunmayacaksa düz bir playbook kullanımı doğrudur. Tek bir uygulama sunucusunun provizyonu veya bakım penceresi öncesinde bir sistemin yamalanması gibi durumlar, bir dizin ağacı oluşturmayı gerektirmez. Bir rol; yedi dizin ve bir dolaylı katman ekler. Eğer rolü çağıran tek yer yanındaki playbook ise, bu dolaylı katman hiçbir fayda sağlamaz; aksine, gerçekte neyin çalıştığını görmek istediğiniz her seferinde fazladan bir dosya açmanı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ız an, o an gelmiştir. 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 neleri barındırır
roles/common/
defaults/main.yml
vars/main.yml
tasks/main.yml
handlers/main.yml
templates/99-hardening.conf.j2
files/
meta/main.ymltasks/main.ymlgiriş noktasıdır. Ansible, rol çağrıldığında 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'da en düşük önceliğe sahip kaynak olduğu için neredeyse her şey bunun önüne geçer.vars/main.yml, çağrı yapanın geçersiz kılmaması beklenen değişkenleri tutar. Öncelik açısından envanterin üzerinde yer alır ki bu oldukça güçlü bir ifadedir. Nadiren kullanın.handlers/main.yml,notifytarafından tetiklenen görevleri tutar. Bir handler, kaç görev tarafından bildirilirse bildirilsin, oyunun (play) sonunda bir kez çalışır.files/,copymodülü tarafından olduğu gibi kopyalanan dosyaları;templates/isetemplatemodü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'nin okuduğu meta verileri tanımlar.
Bu düzen 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 commonBu komut, kullanmayacağınız dizinler ve yalnızca --- içeren main.yml taslakları dahil olmak üzere tüm iskeleti roles/common altında oluşturur. Boş bıraktıklarınızı silin. Boş bir vars/main.yml dosyası Ansible için bir sorun teşkil etmez ancak rolde hangi dosyaların gerçekten önemli olduğunu gizler.
Şimdi işlevsel dosyaları doldurun. Önce varsayılanları (defaults) tanımlayın, çü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 tamamlanırsa 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 (syntax) 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 yüzden şablonu suçlamadan önce modülün msg kısmını okuyun.
Bir playbook'un rol çağırma yöntemi
# site.yml
---
- name: Base configuration for every server
hosts: all
become: true
roles:
- common# inventory.ini
[local]
localhost ansible_connection=localansible-playbook -i inventory.ini site.ymlPlay, özet kısmında failed=0 ile sonlanmalıdır. Parametreleri çağrı noktasında genişletilmiş biçimle geçin; bir rolün iki farklı ana makine grubuna bu şekilde hizmet vermesi sağlanır:
roles:
- role: common
common_admin_group: ops
common_permit_root_login: prohibit-passwordNeredeyse herkesi şaşırtan tek bir sıralama kuralı vardır. Bir play; pre_tasks, roles, tasks ve post_tasks içerebilir ve Ansible, bunları dosyada hangi sırada yazarsanız yazın, her zaman bu sabit 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 işlemin rolden önce gerçekleşmesi gerekiyorsa, bu işlem 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: postBir rolü roles: anahtarı yerine görev listesi 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) aşamasında okur ve görevleri play'in bir parçası haline getirir; bu nedenle ansible-playbook --list-tasks site.yml bu görevleri listeler ve import işlemine eklenen bir etiket, rol içindeki her göreve uygulanır. include_role dinamiktir. Görev çalışana kadar hiçbir şey okunmaz; bu da rol ismini bir değişken veya döngü ile belirlemenize olanak tanır. Bunun bedeli ise, 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ına girmeden önce değerlendirilir. when: common_packages | length > 0 ifadesini include üzerine yazarsanız, söz konusu değişken tam olarak dahil ettiğiniz rolün içinde tanımlanmış olsa bile çalışma 'common_packages' is undefined hatasıyla durur. Bunun çözümü, 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 çakışmaları çözer; aşağıda en zayıftan en güçlüye doğru sıralanmıştır.
roles/<name>/defaults/main.ymlhiyerarşinin en alt kısımlarında 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/vehost_vars/orta seviyededir. Sitenize özgü değerler burada yer almalı ve rol varsayılanlarını temiz bir şekilde geçersiz kılmalıdır.roles/<name>/vars/main.yml,host_varsseviyesinin üzerindedir. Buraya yazdığınız bir değer, envanter üzerinden geçersiz kılınamaz. Burayı, rolün kendi içinde tutarlı kalması gereken durumlar için ayırın; örneğin, servis ismiyle eşleşmesi zorunlu olan bir paket ismi gibi.- Çağrı noktasında iletilen bir rol parametresi
vars/main.ymldeğerini yener; komut satırındaki-eise rol parametreleri dahil her şeyin önüne geçer.
Bu öncelik sırasının nasıl işlediğini bir dakika içinde 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 tepededir ve altındaki hiçbir şey bunu değiştiremez. -e kullanımının tek seferlik işlemler için uygun, ancak kalıcı bir betik için yanlış olmasının nedeni de budur: deponuzdaki tüm planlanmış yapılandırmaları sessizce geçersiz kılar.
Çalışma kuralı şudur: Bir değerin değiştirilebilir olmasını istiyorsanız, onu defaults/ içine koyun. Değeri vars/ içine yerleştirmek, rolü kullanacak herkese envanterin bu değeri 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 raporunu inceleyin.
ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.ymlİkinci özet raporu şu şekilde görünmelidir:
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0, her modülün mevcut durumu incelediği ve işin zaten tamamlanmış 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ı anlayamadığı anlamına gelir; bu durumda görevler dosyaları sürekli yeniden yazmaya ve servisleri yeniden başlatmaya devam edecektir. 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.txtPlaybook'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ç yürütülmez ve sonucu skipped, since /tmp/guarded.txt exists mesajını taşır; çünkü creates, modüle kontrol etmesi için 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ı göz önünde bulundurun: shell ve command görevleri check modunda atlanır, bu nedenle temiz görünen bir plan yine de arka planda iş yapabilir.
Özet raporundaki bir diğer sütun da aynı özeni hak eder: Ansible'ın bağlanamadığı bir sunucu failed yerine unreachable altında sayılır ve hiçbir görevi çalıştırılmaz. Bu nedenle, rolü birkaç makineden fazlasına yönlendirmeden önce erişilemeyen bir sunucunun tüm çalışmayı durdurup durdurmayacağına önceden karar verin.
Ansible neden rolün bulunamadığını belirtiyor
Ansible, roles/ dizinini playbook dosyasının hemen yanında, ardından roles_path içerisinde arar. Arama işlemi shell dizininizi değil, playbook dosyasının konumunu takip eder.
ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonelyBu mesaj, site.yml ve roles/ dosyalarının birbirinden koptuğu anlamına gelir; Ansible denediği yolları çıktı olarak vererek yardımcı olur. İkisini aynı dizinde tutun. Bir üst dizinden çalıştırmak sorun yaratmaz, çünkü esas olan playbook yoludur:
ansible-playbook -i infra/inventory.ini infra/site.ymlAynı sorunun daha sessiz bir versiyonu daha vardır. Ansible, mevcut dizin herkes tarafından yazılabilir olduğunda oradaki ansible.cfg dosyasını görmezden gelir. Bunun nedeni, sunucudaki herhangi bir kullanıcının oraya bir yapılandırma dosyası bırakarak çalıştırdığınız işlemin sonucunu 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 gerçekten yüklenen config file dosyasını, ansible-config dump --only-changed komutu ise yerleşik varsayılanlardan farklı olan tüm ayarları listeler. Bir çalıştırma, yapılandırmanız yokmuş gibi davrandığında her ikisini de 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.0ansible-galaxy install -r requirements.yml -p galaxy_rolesHer 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_rolesPlaybook'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 yeniden üretilebilir ve bir etikete sabitlenmiş indirmeler olarak yönetilir.
Rollerin çözüm olmaktan çıktığı noktalar
Bir rol, tek 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 kurduğu sıkılaştırma (hardening) işlemleri de kendi içinde özel kararlar gerektirir. Yukarıdaki ek yapılandırma yalnızca iki direktif ayarlar; bu nedenle, sahip olduğunuz her ana makine 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çinde veya ikinci bir sunucu grubuna karşı çalıştırılması gerektiğinde. Görevleri playbook'lar arasında kopyalamak, bir sorun olduğunda her iki yerde de düzeltme yapmanız gerekeceği ve bir gün birini güncellemeyi unutacağınız anlamına gelir. Yaklaşık 100 satırın altında olan ve sadece tek bir grubu hedefleyen bir playbook, role yapısına dönüştürüldüğünde 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çine ekleyin.
Neden group_vars değerim role içindeki değişkeni geçersiz kılmıyor?
Değişkenin role'ün defaults/main.yml dosyası yerine vars/main.yml dosyasında 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 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 dosyasına 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 yüksek önceliğe sahip 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ından 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 denenen 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 üzerinden 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 içinde bir tasks/main.yml dosyası olması, çalışan bir role için yeterlidir. ansible-galaxy init --init-path roles common komutu yazım yükünü azaltır ve meta/main.yml ile bir README taslağı dahil olmak üzere tam iskeleti sunar. Boş bıraktığınız dizinleri silin, çünkü boş bir vars/main.yml dizini, role içindeki hangi dosyaların gerçekten bir işlev gördüğünü gizler.