Ansible’da Değişken Yönetimi: vars, defaults ve group_vars Kullanımı

Ansible ile çalışmaya başladığınızda ilk başta her şey basit görünür. Birkaç task, birkaç host, işler yürür. Ama ortam büyüdükçe, “bu değeri nereye yazmıştım?” sorusuyla boğuşmaya başlarsınız. Geliştirme ortamında farklı bir veritabanı portu, production’da farklı bir şifre, staging’de farklı bir domain… Tüm bunları yönetmenin sistematik bir yolu olmadan Ansible playbook’ları kaotik bir hale gelir. İşte bu noktada değişken yönetimi devreye giriyor ve doğru yapılandırıldığında hayatınızı gerçekten kolaylaştırıyor.

Ansible’da Değişken Öncelik Sırası

Ansible’da değişkenlerin nasıl çalıştığını anlamak için önce öncelik sırasını kavramak gerekiyor. Ansible’ın 22 farklı değişken öncelik seviyesi var. Bunların hepsini ezberlemenize gerek yok, ama temel mantığı anlamak kritik.

En basit şekilde şöyle özetleyebiliriz:

  • Extra vars (-e ile komut satırından verilenlər) en yüksek önceliğe sahip
  • Task vars ve block vars bunların altında
  • Role vars (vars/main.yml) oldukça yüksek öncelikte
  • Play vars (vars: bloğu) orta seviyede
  • Host vars ve group_vars ortanın altında
  • Role defaults (defaults/main.yml) en düşük öncelikte

Bu hiyerarşiyi aklınızda tutun çünkü yazının devamında sürekli buna döneceğiz.

vars Kullanımı: Playbook İçi Değişkenler

En temel değişken tanımlama yöntemi, doğrudan playbook içinde vars: bloğu kullanmaktır. Küçük projelerde veya hızlı testlerde işe yarar ama scale etmez.

# site.yml
---
- name: Web sunucu kurulumu
  hosts: webservers
  vars:
    http_port: 80
    https_port: 443
    max_clients: 200
    app_name: "myapp"
    
  tasks:
    - name: Nginx konfigurasyonu oluştur
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

Bu yöntemin en büyük sorunu şu: Aynı değişkeni farklı playbook’larda kullanmak istediğinizde kopyala-yapıştır cehennemine giriyorsunuz. Bir yerde değiştirdiniz, diğerini unuttunuz. Klasik bakım kabusu.

Daha organize bir yaklaşım için vars_files kullanabilirsiniz:

# site.yml
---
- name: Web sunucu kurulumu
  hosts: webservers
  vars_files:
    - vars/common.yml
    - vars/webserver.yml
    
  tasks:
    - name: Paket kurulumu
      apt:
        name: "{{ packages }}"
        state: present
# vars/webserver.yml
packages:
  - nginx
  - php-fpm
  - php-mysql

nginx_worker_processes: auto
nginx_worker_connections: 1024
log_format: combined

Bu yaklaşım biraz daha iyi ama hâlâ ortam bazlı farklılıkları yönetmek zorlaşıyor. Production için farklı, development için farklı değerler istediğinizde tıkanıyorsunuz.

Role Defaults: Akıllı Varsayılanların Gücü

Role defaults, Ansible değişken sisteminin en zarif parçasıdır. defaults/main.yml dosyasındaki değişkenler her şey tarafından override edilebilir. Bu kasıtlı bir tasarım.

Düşünün: Bir nginx rolü yazıyorsunuz. Bu rolü hem kendi projelerinizde hem de başkalarının projelerinde kullanmak istiyorsunuz. Varsayılan değerleri makul seçersiniz ama kullanıcı isterse her şeyi değiştirebilsin istersiniz. İşte defaults bunun için var.

# roles/nginx/defaults/main.yml
---
nginx_port: 80
nginx_ssl_port: 443
nginx_worker_processes: "auto"
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: "10m"
nginx_access_log: "/var/log/nginx/access.log"
nginx_error_log: "/var/log/nginx/error.log"
nginx_ssl_enabled: false
nginx_gzip_enabled: true

Bu değerleri kullanan template:

# roles/nginx/templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};

events {
    worker_connections {{ nginx_worker_connections }};
}

http {
    keepalive_timeout {{ nginx_keepalive_timeout }};
    client_max_body_size {{ nginx_client_max_body_size }};
    
    gzip {% if nginx_gzip_enabled %}on{% else %}off{% endif %};
    
    access_log {{ nginx_access_log }};
    error_log {{ nginx_error_log }};
    
    server {
        listen {{ nginx_port }};
        {% if nginx_ssl_enabled %}
        listen {{ nginx_ssl_port }} ssl;
        {% endif %}
    }
}

Şimdi bir kullanıcı bu rolü kullanırken sadece değiştirmek istediği şeyi belirtir, geri kalanı default’tan gelir:

# playbook.yml
- name: Production web sunucu
  hosts: prod_web
  roles:
    - role: nginx
      vars:
        nginx_worker_processes: 4
        nginx_worker_connections: 4096
        nginx_ssl_enabled: true
        nginx_client_max_body_size: "50m"

defaults/main.yml ile vars/main.yml arasındaki farkı iyice kavramak önemli. vars/main.yml çok daha yüksek önceliğe sahip ve çoğu şey tarafından override edilemez. Role’ün iç mantığı için sabit kalması gereken değerleri oraya koyun. Kullanıcının özelleştirebilmesini istediğiniz her şey defaults/main.yml‘a gitsin.

group_vars: Ortam Bazlı Konfigürasyonun Kalbi

Gerçek dünya senaryolarında en çok değer katan yapı group_vars‘tır. Envanter dosyanızdaki gruplara özel değişkenler tanımlamanızı sağlar.

Tipik bir proje yapısı şöyle görünür:

# Proje dizin yapısı
inventory/
├── production/
│   ├── hosts
│   └── group_vars/
│       ├── all/
│       │   ├── main.yml
│       │   └── vault.yml
│       ├── webservers/
│       │   └── main.yml
│       ├── dbservers/
│       │   └── main.yml
│       └── loadbalancers/
│           └── main.yml
├── staging/
│   ├── hosts
│   └── group_vars/
│       ├── all/
│       │   └── main.yml
│       └── webservers/
│           └── main.yml

Production envanterleri için örnek:

# inventory/production/hosts
[webservers]
web01.prod.example.com
web02.prod.example.com
web03.prod.example.com

[dbservers]
db01.prod.example.com
db02.prod.example.com

[loadbalancers]
lb01.prod.example.com

[production:children]
webservers
dbservers
loadbalancers
# inventory/production/group_vars/all/main.yml
---
environment: production
timezone: Europe/Istanbul
ntp_servers:
  - 0.tr.pool.ntp.org
  - 1.tr.pool.ntp.org

# Monitoring
monitoring_enabled: true
alertmanager_url: "https://alertmanager.prod.example.com"

# Log yönetimi
log_retention_days: 90
log_level: "warn"

# Ortak paketler
common_packages:
  - vim
  - htop
  - curl
  - wget
  - net-tools
  - tcpdump
# inventory/production/group_vars/webservers/main.yml
---
nginx_worker_processes: 4
nginx_worker_connections: 4096
nginx_ssl_enabled: true
nginx_client_max_body_size: "100m"

php_memory_limit: "512M"
php_max_execution_time: 60
php_opcache_enabled: true
php_opcache_memory: 256

app_environment: production
app_debug: false
app_cache_driver: redis
app_session_lifetime: 120

redis_host: "redis.prod.internal"
redis_port: 6379
# inventory/production/group_vars/dbservers/main.yml
---
mysql_max_connections: 500
mysql_innodb_buffer_pool_size: "8G"
mysql_slow_query_log: true
mysql_slow_query_time: 2
mysql_binlog_enabled: true
mysql_binlog_expire_days: 7

backup_enabled: true
backup_schedule: "0 2 * * *"
backup_retention: 30
backup_destination: "s3://prod-backups/mysql"

Staging ortamında ise değerler çok daha mütevazı:

# inventory/staging/group_vars/all/main.yml
---
environment: staging
timezone: Europe/Istanbul

monitoring_enabled: true
log_retention_days: 7
log_level: "debug"

common_packages:
  - vim
  - htop
  - curl
# inventory/staging/group_vars/webservers/main.yml
---
nginx_worker_processes: 2
nginx_worker_connections: 1024
nginx_ssl_enabled: false
nginx_client_max_body_size: "10m"

php_memory_limit: "256M"
php_max_execution_time: 30
php_opcache_enabled: false

app_environment: staging
app_debug: true
app_cache_driver: file

Artık aynı playbook’u farklı ortamlarda çalıştırabilirsiniz, her şey doğru envanter dizininden gelir:

# Production deploy
ansible-playbook -i inventory/production site.yml

# Staging deploy
ansible-playbook -i inventory/staging site.yml

host_vars: Sunucu Bazlı İnce Ayarlar

Bazı durumlarda tek bir sunucunun grubundan farklı bir konfigürasyona ihtiyacı olur. host_vars tam bu iş için:

# inventory/production/host_vars/web01.prod.example.com/main.yml
---
# Bu sunucu daha güçlü, daha fazla worker alabiliriz
nginx_worker_processes: 8
nginx_worker_connections: 8192

# Bu sunucu aynı zamanda static dosya sunucu olarak görev yapıyor
nginx_static_files_enabled: true
nginx_static_root: "/var/www/static"

Bu yaklaşımı aşırıya kaçmadan kullanın. Her sunucu için onlarca özel değişken tanımlamaya başlarsanız, bir süre sonra ne nerede ne zaman değiştiğini takip edemez hale gelirsiniz.

Gerçek Dünya Senaryosu: Çok Katmanlı Uygulama

Bir e-ticaret platformu yönettiğinizi düşünelim. Frontend, backend API, veritabanı ve cache katmanları var. Hem production hem de staging ortamları mevcut.

# roles/app/defaults/main.yml
---
# Uygulama ayarları - override edilebilir varsayılanlar
app_port: 8080
app_workers: 4
app_timeout: 30
app_max_requests: 1000

# Veritabanı bağlantısı
db_port: 3306
db_pool_size: 10
db_pool_overflow: 5
db_pool_timeout: 30

# Cache
cache_ttl: 3600
cache_max_entries: 10000

# Rate limiting
rate_limit_enabled: false
rate_limit_requests: 100
rate_limit_window: 60

Production’da bu değerleri group_vars ile ezeriz:

# inventory/production/group_vars/app_servers/main.yml
---
app_workers: 8
app_timeout: 60
app_max_requests: 5000

db_pool_size: 25
db_pool_overflow: 10

cache_ttl: 7200
cache_max_entries: 50000

rate_limit_enabled: true
rate_limit_requests: 1000
rate_limit_window: 60

Şimdi gizli değişkenleri nasıl yöneteceğimize bakalım. Şifreler, API anahtarları ve benzeri hassas bilgileri asla düz metin olarak Git’e commit etmeyin. Ansible Vault burada devreye girer:

# Vault dosyası oluştur
ansible-vault create inventory/production/group_vars/all/vault.yml

# Mevcut dosyayı şifrele
ansible-vault encrypt inventory/production/group_vars/all/vault.yml
# inventory/production/group_vars/all/vault.yml (şifrelenmiş içerik)
---
vault_db_password: "UltraGuvenliSifre123!"
vault_api_key: "sk-prod-abcdef123456"
vault_redis_password: "RedisGuvenliSifre456!"
vault_jwt_secret: "jwt-super-secret-key-production"
# inventory/production/group_vars/all/main.yml içinde vault değişkenlerini referans al
---
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
redis_password: "{{ vault_redis_password }}"
jwt_secret: "{{ vault_jwt_secret }}"

Bu pattern önemli: Vault değişkenlerini vault_ prefix’iyle adlandırın ve bunları “normal” değişkenlere atayın. Böylece hangi değerlerin şifreli olduğunu hemen anlayabilirsiniz ve template’larda her zaman şifresiz ismi kullanırsınız.

Değişken Debug ve Troubleshooting

Değişken önceliği kafanızı karıştırdığında birkaç pratik yöntem var:

# Tüm değişkenleri bir host için listele
ansible webservers -i inventory/production -m debug -a "var=hostvars[inventory_hostname]"

# Belirli bir değişkenin değerini kontrol et
ansible web01.prod.example.com -i inventory/production -m debug -a "var=nginx_worker_processes"

# Playbook'ta hangi değişkenlerin geldiğini görmek için
ansible-playbook -i inventory/production site.yml --tags debug -v

Playbook içinde de debug task ekleyebilirsiniz:

# site.yml içine geçici debug task
- name: Değişken değerlerini göster
  debug:
    msg: |
      Environment: {{ environment }}
      App Workers: {{ app_workers }}
      DB Pool Size: {{ db_pool_size }}
      Rate Limit: {{ rate_limit_enabled }}
  tags: debug

İyi Pratikler ve Dikkat Edilecekler

Yıllarca Ansible kullanan biri olarak edindiğim bazı dersleri paylaşayım:

Defaults’ı cömertçe kullanın: Role defaults’a her değişken için makul bir varsayılan koyun. Kullanıcı override etmezse bile role çalışabilir olsun.

İsimlendirme konvansiyonu: Role değişkenlerini her zaman role adıyla prefix’leyin. nginx_ ile başlayan değişkenler nginx rolüne ait, mysql_ ile başlayanlar mysql rolüne ait. Karışıklık olmaz.

group_vars/all dikkatli kullanın: all grubundaki değişkenler tüm hostlara uygulanır. Buraya sadece gerçekten evrensel olan değerleri koyun.

Değişken hiyerarşisini belgelendirin: Projenize bir VARIABLES.md dosyası ekleyin. Hangi değişkenin nerede tanımlandığını, neyi kontrol ettiğini yazın. Altı ay sonra o projeye döndüğünüzde kendinize teşekkür edersiniz.

Vault dosyalarını ayrı tutun: group_vars içinde main.yml ve vault.yml olarak ikiye ayırın. main.yml Git’e açık commit edilir, vault.yml şifreli gider. Bu ayrımı her zaman koruyun.

Extra vars’ı dikkatli kullanın: -e ile verilen değişkenler her şeyi ezer. CI/CD pipeline’larında ortam bazlı değerleri bu şekilde geçirmeyin, bunun için group_vars var. Extra vars’ı gerçekten acil override gereken durumlar için saklayın.

# Bu kötü pratik - ortam değerlerini extra vars ile geçirmek
ansible-playbook site.yml -e "db_password=sifre123 app_workers=8"

# Bu iyi pratik - ortam bazlı envanterle çalışmak
ansible-playbook -i inventory/production site.yml

Değişkenleri test edin: Yeni bir değişken hiyerarşisi kurduğunuzda, --check ve --diff flag’leriyle önce ne olacağını görün:

ansible-playbook -i inventory/staging site.yml --check --diff

Dinamik Değişkenler: set_fact Kullanımı

Bazen değişkenleri task çalışırken hesaplamanız gerekir. set_fact modülü bu iş için:

# tasks/main.yml
- name: Sistem RAM'ini al
  setup:
    filter: ansible_memtotal_mb

- name: MySQL buffer pool boyutunu hesapla
  set_fact:
    calculated_buffer_pool: "{{ (ansible_memtotal_mb * 0.7) | int }}M"
  when: mysql_innodb_buffer_pool_size is not defined

- name: Buffer pool değerini kullan
  set_fact:
    mysql_innodb_buffer_pool_size: "{{ calculated_buffer_pool }}"
  when: mysql_innodb_buffer_pool_size is not defined

Bu yaklaşım özellikle heterojen ortamlarda, her sunucunun kapasitesine göre otomatik optimizasyon yapmak için çok kullanışlı.

Sonuç

Ansible’da değişken yönetimi, başlangıçta kafa karıştırıcı görünebilir ama mantığını bir kez oturttuğunuzda inanılmaz esnek bir sistem elde ediyorsunuz. Özet olarak şöyle düşünebilirsiniz:

  • Role defaults: “Makul varsayılanlarım bunlar, istersen değiştir”
  • group_vars: “Bu ortam veya grup için değerler şunlar”
  • host_vars: “Bu spesifik sunucu için ince ayarlar”
  • vars_files / play vars: “Bu spesifik playbook için değerler”
  • extra vars: “Acil override, dikkatli kullan”

Bu hiyerarşiyi doğru oturttuğunuzda, aynı role’ü development’tan production’a kadar sorunsuz kullanabilir, ortam bazlı konfigürasyonları temiz bir şekilde yönetebilir ve ekip arkadaşlarınızın “bu değer nereden geliyor?” diye sorduğunda rahatça cevap verebilirsiniz.

Vault ile hassas değişkenleri şifrelemeyı ihmal etmeyin. Bir kez şifresiz credential’ı Git geçmişine gömmüş biri olarak söylüyorum, o acıyı yaşamanıza gerek yok.

Yorum yapın