Ansible · Değişkenler
Ansible değişkenlerini doğru yerde tutmak
Ansible'da 22 farklı değişken katmanı var. Asıl soru “hangisi kazanır?” değil — belgelerde yazıyor. Asıl soru şu: bu değişkeni nereye koymalıyım?
Ansible öğrenirken herkesin karşılaştığı bir an vardır: bir değişkeni değiştirirsiniz ama hiçbir şey olmaz. Başka bir yerde tanımlı aynı isimli değişken sizinkini eziyordur. Bu noktada çoğu kişi öncelik tablosunu ezberlemeye çalışır. Oysa tabloyu ezberlemek semptomu tedavi eder; hastalığı değil.
Hastalık şu: değişkeni ne olduğuna değil, o an aklınıza gelen yere koymak. Bu yazıda gerçek bir projeden — RHEL 9 ve RHEL 10 için osbuild-composer ile imaj üreten bir rolden — örneklerle, bir değişkenin yerini nasıl belirleyeceğinizi anlatacağım. Kendi yaptığım hatayı da göstereceğim.
Önce kısa hatırlatma: öncelik sırası
Ansible değişkenleri en düşükten en yükseğe doğru şu sırayla uygular. Sondakiler öncekileri ezer:
| # | Katman | Ne zaman kullanılır |
|---|---|---|
| 2 | Rol defaults/ | Rolün makul varsayılanı |
| 3 | Envanter dosyasındaki grup değişkenleri | Nadiren; dağınık olur |
| 4 | Envanter group_vars/all | Tüm makinelere ortak makine bilgisi |
| 5 | Playbook group_vars/all | Playbook'a özgü, tüm gruplara |
| 6 | Envanter group_vars/* | Ortama/gruba özgü makine bilgisi |
| 7 | Playbook group_vars/* | Playbook'un gruba özgü zorlaması |
| 9 | Envanter host_vars/* | Tek makineye özgü |
| 11 | Facts ve set_fact | Çalışma anında keşfedilen |
| 12 | Play vars: | Playbook'un kendi bağlamı |
| 15 | Rol vars/ | Rolün ezilmemesi gereken sabitleri |
| 18 | include_vars | Koşullu yükleme (ör. sürüme göre) |
| 22 | -e extra vars | Tek çalıştırmaya özgü; her şeyi ezer |
Dikkat edilecek iki nokta var. Birincisi: group_vars/ ve host_vars/ iki ayrı yerde bulunabilir — envanterin yanında ve playbook'un yanında. İkincisi: playbook'un yanındaki, envanterinkini ezer.
Bunu kanıtlayalım
İddiaya güvenmeyin, ölçün. Üç katmanda aynı değişkeni tanımlayıp tek tek kaldıralım:
$ tree
├── inventory/
│ ├── hosts.yml
│ └── group_vars/all.yml → nereden: "2-INVENTORY group_vars/all"
├── group_vars/all.yml → nereden: "3-PLAYBOOK group_vars/all"
├── roles/demo/defaults/main.yml → nereden: "1-ROLE defaults"
└── pb.yml
$ ansible-playbook -i inventory/hosts.yml pb.yml
kazanan -> 3-PLAYBOOK group_vars/all
$ rm group_vars/all.yml && ansible-playbook ...
kazanan -> 2-INVENTORY group_vars/all
$ rm inventory/group_vars/all.yml && ansible-playbook ...
kazanan -> 1-ROLE defaults
group_vars > envanter group_vars > rol defaults. Beklendiği gibi.Pratik ipucu. Bir değerin nereden geldiğini merak ettiğinizde tahmin etmeyin: ansible-inventory --graph --vars ve ansible-inventory --host <makine> size envanterden gelenlerin tamamını gösterir. Rol katmanları için de ansible-playbook --list-tasks ve bir debug görevi en hızlı yoldur.
Asıl soru: bu değişken neyi tarif ediyor?
Karar kuralı bu kadar basit. Değişkenin adına değil, anlamına bakın:
| Değişken neyi tarif ediyor? | Yeri | Neden |
|---|---|---|
| Makineyi / ortamı “RHEL 9 sunucusu şudur” |
Envanter group_vars/ |
Envanterle birlikte seyahat eder. Aynı envanteri kullanan her playbook aynı bilgiyi alır. |
| Rolün makul varsayılanını “aksi söylenmedikçe böyle davran” |
Rol defaults/ |
En düşük öncelik = her katmandan ezilebilir. Rol kendi kendine yeter hale gelir. |
| Playbook'un kendi bağlamını “çıktıları buraya yaz” |
Play vars: |
Hedef makineyle ilgisi yoktur; envantere konursa yanlış yere yayılır. |
| Bu çalıştırmaya özgü seçimi “şimdi RHEL 10 üret” |
-e |
Her şeyi ezer, kalıcı değildir. Tam da geçici seçim için. |
| Ezilmemesi gerekeni iç sabitler, eşleme tabloları |
Rol vars/ |
Yüksek önceliklidir; kullanıcının kazara ezmesini istemediğiniz şeyler. |
Gerçek bir örnek: osbuild imaj üretim rolü
Bu rol, parametre alarak RHEL 9 veya RHEL 10 için ISO / qcow2 / vmdk imajları üretiyor:
ansible-playbook build-image.yml \
-e rhel_version=10 -e image_template=devel \
-e '{"image_types":["qcow2","vmdk"]}'
rhel_version doğrudan hedef grubu seçiyor: RHEL 9 imajı RHEL 9 sunucusunda üretilmeli, çünkü osbuild-composer kendi host sürümünü varsayılan distro olarak alır.
Envanterde ne var
# inventory/group_vars/osbuild_rhel10.yml
ansible_user: remzi
ansible_become: true
osbuild_expected_major: "10"
osbuild_extra_sources:
- id: crb
url: https://cdn.redhat.com/.../codeready-builder/os
required_for_templates: ["devel"]
Bunların hepsi makineyi tarif ediyor. “RHEL 10 sunucusunda CRB deposu gerekir” bilgisi, hangi playbook'u çalıştırdığınızdan bağımsızdır. Yarın bir cleanup.yml yazarsanız o da aynı envanteri kullanıp aynı bilgiyi alır.
Rol varsayılanlarında ne var
# roles/osbuild_image/defaults/main.yml
run_boot_test: true
enable_kdump: true
kdump_crashkernel: "2G-64G:256M,64G-:512M"
min_free_gib: {minimal: 12, devel: 30}
lvm_volumes:
- {name: rootlv, mount: /, size_gib: 30, label: root}
- {name: varcrashlv, mount: /var/crash, size_gib: 2, label: var_crash}
# ... 11 mantıksal birim
Disk şeması burada olmalı, çünkü rolün ne ürettiğini tarif ediyor. Üstelik tek kaynak olması sayesinde hem customizations.disk (qcow2/vmdk) hem de kickstart logvol satırları (ISO) aynı listeden üretiliyor — ikisinin birbirinden ayrışması yapısal olarak imkânsız hale geliyor.
Playbook'ta ne var
# build-image.yml
vars:
image_fetch_dir: "{{ playbook_dir }}/imajlar"
artifact_dir: "{{ playbook_dir }}/artifacts"
Bunlar kontrol makinesinin yolları. Hedef sunucuyla hiçbir ilgileri yok. Ayrıca playbook_dir ancak playbook bağlamında anlamlıdır — envantere koyarsanız, aynı envanter başka bir dizinden kullanıldığında yanlış çözülür.
Benim yaptığım hata
İlk versiyonda politika değişkenlerinin hepsini envanterin group_vars/all.yml dosyasına koymuştum:
# inventory/group_vars/all.yml ← YANLIŞ
run_boot_test: true
enable_kdump: true
min_free_gib: {minimal: 12, devel: 30}
compose_timeout_min: 90
image_fetch_dir: "{{ playbook_dir }}/imajlar"
Çalışıyordu. Ama iki gerçek sakıncası vardı:
- Ezme zinciri yanlış yerden başlıyordu.
enable_kdumprolün varsayılanı olmalıydı. Envantere konunca roldefaults'ı sessizce eziliyor ve rol tek başına anlamsız hale geliyor — birisi rolü başka bir projede kullanmak istese, hangi değişkenleri tanımlaması gerektiğini bilemez. - İkinci bir ortam eklendiğinde kopyalama zorunluluğu.
inventory/prod/veinventory/test/açtığınızda, ortamla hiç ilgisi olmayan politika değişkenlerini de iki kez yazmanız gerekir. Kopya olan yerde er geç ikisi birbirinden ayrışır.
Düzeltilmiş hali:
| Değişken | Önce | Sonra |
|---|---|---|
ansible_user, osbuild_extra_sources | envanter group_vars | aynı — doğruydu |
enable_kdump, run_boot_test, min_free_gib | envanter group_vars/all | rol defaults |
image_fetch_dir, artifact_dir | envanter group_vars/all | play vars: |
rhel_version, image_types | -e | aynı — doğruydu |
Envanterin group_vars/all.yml dosyası artık boş — ama silmedim. İleride gerçekten tüm makinelere ortak bir makine ayarı gerektiğinde (proxy, zaman sunucusu, ortak depo) yeri orasıdır. Dosyanın içine bunu bir yorum olarak yazdım; boş bir dosyadan çok, niyeti belgelenmiş bir dosya bırakmak daha iyidir.
Sürüme göre değişenler: include_vars
Bu projede öğrendiğim en değerli şeylerden biri, sürüm farklarını veri olarak tutmaktı. RHEL 10'da kdump, kexec-tools paketinden ayrılıp kdump-utils'e taşınmış. Bunu göreve gömmek yerine:
- name: "Sürüme özel değişkenleri yükle"
ansible.builtin.include_vars:
file: "rhel{{ rhel_version }}.yml"
Böylece vars/rhel9.yml ve vars/rhel10.yml sürüm farklarını taşır, görevler sürümden bağımsız kalır. include_vars'ın önceliği yüksektir (18. sıra) — bu tam olarak istediğimiz şey: sürüme özgü veri, genel varsayılanı ezmeli.
Daha da iyisi: bu projede kdump paketini sabit yazmak yerine çalışma anında sorduk — dnf repoquery --file /usr/lib/systemd/system/kdump.service. Sonuç: RHEL 9'da kexec-tools, RHEL 10'da kdump-utils bulundu, hiçbir yere sabit yazılmadan. Değişkene hiç gerek kalmadı. En iyi değişken, yazmak zorunda kalmadığınız değişkendir.
Kaçınılması gereken üç tuzak
1. set_fact tipi sessizce değiştirir
Bir JSON dosyasını okuyup metin araması yapmak istedim:
- set_fact:
manifest_text: "{{ manifest_raw.content | b64decode }}"
- assert:
that: "'org.osbuild.lvm2.create' in manifest_text" # her zaman False!
İçerik JSON'a benzediği için Ansible onu otomatik olarak dict'e çevirdi. Dolayısıyla in operatörü metin değil anahtar araması yaptı. Aradığım şey dosyada vardı ama testi hiç geçmiyordu. Çözüm: metin araması yerine from_json ile ayrıştırıp yapısal denetim yapmak.
2. delegate_to bağlantı değişkenlerini değiştirir
- command: "{{ 'cp ...' if ansible_connection == 'local' else 'scp ...' }}"
delegate_to: localhost # ← ansible_connection artık 'local'!
Delegasyon yapılan görevde ansible_connection, delegasyon hedefinin bağlantı türüdür. Asıl hedefin bilgisini kullanacaksanız, onu delegasyondan önce bir set_fact ile saklayın. Bu hatayı fark etmem, aynı kodun bir sunucuda çalışıp diğerinde patlamasını görmemi gerektirdi.
3. -e geri alınamaz
Extra vars her şeyi ezer ve playbook içinden değiştirilemez. Bu yüzden -e'yi yalnızca o çalıştırmaya özgü seçimler için kullanın (rhel_version, image_types). Kalıcı politikayı -e ile taşımaya başlarsanız, her çalıştırmada uzun bir komut satırı ezberlemek zorunda kalırsınız — ve bir gün birini yazmayı unutursunuz.
Özet: karar ağacı
| Soru | Cevap |
|---|---|
| Makineyi/ortamı mı tarif ediyor? | Envanter group_vars/ · host_vars/ |
| Rolün makul varsayılanı mı? | Rol defaults/ |
| Sürüme/platforma göre mi değişiyor? | Rol vars/ + include_vars |
| Playbook'un kendi bağlamı mı? | Play vars: |
| Bu çalıştırmaya mı özgü? | -e |
| Kullanıcı ezerse bozulur mu? | Rol vars/ (defaults değil) |
Bu kuralı uyguladığınızda öncelik tablosunu ezberlemenize gerek kalmaz — çünkü çakışma çıkmaz. Tablo, ancak aynı değişkeni birden fazla katmanda tanımladığınızda gerekir; ve bunu yapıyorsanız muhtemelen değişkeni yanlış yere koymuşsunuzdur.


