Ansible Playbook Geliştirme
Giriş
Ansible, modern IT altyapılarının yönetiminde en yaygın kullanılan açık kaynak otomasyon araçlarından biridir. Red Hat tarafından desteklenen bu araç, idempotent yapısı, agentless mimarisi ve basit YAML syntax'ı sayesinde hem küçük ölçekli sistemlerde hem de enterprise ortamlarda tercih edilmektedir. Bu makalede, Ansible'ın temel bileşenlerinden playbook geliştirme sürecine, modül yazımından en iyi uygulamalara kadar geniş bir yelpazede bilgi paylaşılacaktır.
Ansible'ın sunduğu en büyük avantajlardan biri, altyapıyı kod olarak tanımlama (Infrastructure as Code - IaC) imkanıdır. Bu yaklaşım, manuel yapılandırma süreçlerinin getirdiği hata oranını minimize eder, tekrarlanabilirlik sağlar ve versiyon kontrol sistemleriyle entegrasyonu kolaylaştırır. Özellikle büyük ölçekli sunucu farm'larını yöneten ekipler için Ansible, vazgeçilmez bir araç haline gelmiştir.
Bu makale boyunca, gerçek dünya senaryolarıyla desteklenmiş pratik örnekler, yaygın hatalar ve bunların çözümleri, performans optimizasyon teknikleri ve production ortamları için öneriler sunulacaktır. İster Ansible ile yeni başlıyor olun, ister deneyimli bir kullanıcı olun, bu içerik size yeni bakış açıları kazandıracaktır.
Ansible Temel Kavramları ve Mimarisi
Inventory ve Host Grupları
Ansible'ın çalışma prensibi oldukça basittir: bir kontrol makinesi (control node), hedef sunuculara (managed nodes) SSH üzerinden bağlanır ve tanımlanan görevleri gerçekleştirir. Bu sürecin ilk adımı, hedef sistemleri tanımlamaktır. Inventory dosyası, Ansible'ın hangi sunuculara bağlanacağını belirleyen temel konfigürasyon dosyasıdır.
Inventory dosyası static veya dynamic olabilir. Static inventory, sunucu IP'lerini veya hostname'lerini elle tanımlarken, dynamic inventory bulut sağlayıcıları veya CMDB sistemlerinden bu bilgileri otomatik olarak çeker. Özellikle AWS, Azure veya GCP gibi dinamik ortamlarda dynamic inventory kullanımı yaygındır.
``ini
[web_servers]
web01.example.com ansible_host=192.168.1.10 ansible_user=deploy
web02.example.com ansible_host=192.168.1.11 ansible_user=deploy
web03.example.com ansible_host=192.168.1.12 ansible_user=deploy
[database_servers]
db01.example.com ansible_host=192.168.2.10 ansible_user=dbadmin
db02.example.com ansible_host=192.168.2.11 ansible_user=dbadmin
[web_servers:vars]
ansible_python_interpreter=/usr/bin/python3
http_port=80
[all:vars]
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
`
Yukarıdaki örnekte, sunucular gruplara ayrılmış ve her grup için ortak değişkenler tanımlanmıştır. Host grupları, playbook'ların hedef kitlesğini belirlemek için kullanılır. Örneğin, sadece web sunucularına nginx kurulumu yapmak istediğimizde hosts: web_servers ifadesini kullanırız.
Gruplama için nested gruplar da oluşturulabilir. Örneğin, tüm production sunucularını içeren bir [production:children] grubu, web ve database gruplarını birleştirebilir. Bu yapı, büyük ölçekli ortamlarda esneklik sağlar.
Modül Yapısı
Ansible modülleri, Ansible'ın iş yapan temel bileşenleridir. Her modül, belirli bir görevi yerine getirmek için tasarlanmıştır. Örneğin, yum modülü paket yönetimi için, copy modülü dosya kopyalama için, service modülü sistem servisi yönetimi için kullanılır.
Ansible, 3000'den fazla built-in modül ile birlikte gelir. Bu modüller sistem, network, cloud, database, web, container ve daha birçok kategoriyi kapsar. Bunun yanı sıra, topluluk tarafından geliştirilen binlerce custom modül de mevcuttur.
Modüller genellikle idempotent çalışır. Yani, aynı görev birden fazla çalıştırıldığında, sistem durumu değişmediyse tekrar bir değişiklik yapmazlar. Bu özellik, Ansible'ın en güçlü avantajlarından biridir ve "change only when needed" felsefesini destekler.
`yaml
- name: Nginx kurulumu
yum:
name: nginx
state: present
enablerepo: epel
notify: restart nginx
`
Yukarıdaki örnekte, nginx paketi yoksa kurulur, varsa herhangi bir işlem yapılmaz. state: present parametresi, paketin mevcut olmasını garanti eder. Benzer şekilde, state: absent parametresi paketin kaldırılmasını sağlar.
Playbook Yapısı
Playbook, Ansible'ın kalbidir. YAML formatında yazılan playbook'lar, Ansible'a hangi görevleri hangi sırayla gerçekleştireceğini söyler. Bir playbook, bir veya birden fazla play içerir ve her play, belirli bir host grubunda çalışır.
`yaml
---
- name: Web sunucuları yapılandırması
hosts: web_servers
become: yes
vars:
http_port: 8080
server_name: example.com
tasks:
- name: Epel reposu kontrolü
yum_repository:
name: epel
description: Extra Packages for Enterprise Linux
baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/
gpgcheck: no
enabled: yes
- name: Nginx kurulumu
yum:
name: nginx
state: latest
- name: Nginx konfigürasyon dosyası
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: restart nginx
- name: Nginx servisi başlatma
service:
name: nginx
state: started
enabled: yes
- name: Firewall kuralları
firewalld:
service: http
permanent: yes
state: enabled
- name: SELinux ayarları
selinux:
policy: targeted
state: permissive
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
`
Bu playbook örneği, web sunucularına nginx kurulumu ve yapılandırmasını adım adım göstermektedir. become: yes ile privilege escalation sağlanır, vars bölümünde değişkenler tanımlanır, tasks bölümünde görevler sıralanır ve handlers bölümünde tetiklenen eylemler belirtilir.
Ansible Playbook Geliştirme
Task ve Module Kullanımı
Playbook geliştirirken, görevleri mantıksal gruplara ayırmak önemlidir. Her task, mümkün olduğunca küçük ve tek amaçlı olmalıdır. Bu yaklaşım, hata ayıklamayı kolaylaştırır ve playbook'un okunabilirliğini artırır.
`yaml
tasks:
- name: Sistem paketlerini güncelle
yum:
name: '*'
state: latest
tags: [update]
- name: Gerekli paketleri kur
yum:
name:
- python3
- python3-pip
- git
- curl
- wget
state: present
tags: [packages]
- name: Python virtualenv oluştur
pip:
name: virtualenv
virtualenv: /opt/myapp/venv
virtualenv_command: python3 -m venv
tags: [python]
`
Task'lerde name parametresi kullanmak zorunlu değildir, ancak debug sürecini kolaylaştırmak için mutlaka kullanılmalıdır. tags parametresi, playbook'un belirli bölümlerini çalıştırmak için kullanılır. Örneğin, ansible-playbook site.yml --tags packages komutu sadece paket kurulumu task'ini çalıştırır.
Değişkenler ve Facts
Ansible'da değişkenler, playbook'ların dinamik ve esnek olmasını sağlar. Değişkenler, inventory dosyasında, playbook içinde veya ayrı variable dosyalarında tanımlanabilir. Ansible facts'ları ise, hedef sistemlerden otomatik olarak toplanan bilgilerdir.
`yaml
- name: Sistem bilgilerini topla
hosts: all
gather_facts: yes
tasks:
- name: İşletim sistemi bilgisi
debug:
msg: "OS: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
- name: IP adresi bilgisi
debug:
msg: "IP: {{ ansible_facts['default_ipv4']['address'] }}"
- name: Toplam bellek
debug:
msg: "RAM: {{ ansible_facts['memtotal_mb'] }} MB"
- name: CPU sayısı
debug:
msg: "CPU Core: {{ ansible_facts['processor_count'] }}"
`
gather_facts: yes (varsayılan), Ansible'ın her playbook çalışmasında hedef sistem hakkında bilgi toplamasını sağlar. Facts'lar, koşullu çalıştırma (when), döngüler ve template'lerde yaygın olarak kullanılır.
`yaml
- name: Ubuntu için nginx kurulumu
apt:
name: nginx
state: present
when: ansible_facts['os_family'] == "Debian"
- name: CentOS için nginx kurulumu
yum:
name: nginx
state: present
when: ansible_facts['os_family'] == "RedHat"
`
Yukarıdaki örnekte, işletim sistemine göre farklı modüller kullanılarak platform bağımsız bir playbook oluşturulmuştur.
Döngüler (Loops)
Tekrarlayan görevler için döngüler kullanılır. Ansible, loop keyword'ü ile basit döngüler ve loop_control ile gelişmiş kontrol mekanizmaları sunar.
`yaml
- name: Birden fazla kullanıcı oluştur
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
shell: "{{ item.shell | default('/bin/bash') }}"
groups: "{{ item.groups | default([]) }}"
state: present
loop:
- { name: 'deploy', uid: 1001, shell: '/bin/bash', groups: ['docker'] }
- { name: 'backup', uid: 1002, shell: '/bin/sh', groups: ['backup'] }
- { name: 'monitor', uid: 1003, shell: '/bin/false', groups: [] }
- name: Birden fazla paket kur
yum:
name: "{{ packages }}"
state: present
vars:
packages:
- nginx
- git
- wget
- curl
- htop
`
Döngülerin item değişkeni, her iterasyonda sıradaki elemanı temsil eder. Dictionary veya list yapıları kullanılabilir. loop_control ile döngü indeksine erişilebilir veya çıktı formatı özelleştirilebilir.
Koşullar (Conditionals)
Belirli koşullara göre görevlerin çalıştırılması veya atlanması için when keyword'ü kullanılır.
`yaml
- name: Web sunucusu yapılandırması
hosts: all
vars:
environment: production
tasks:
- name: Debug modunu devre dışı bırak (production)
template:
src: app_prod.conf.j2
dest: /etc/app.conf
when: environment == "production"
- name: Debug modunu etkinleştir (development)
template:
src: app_dev.conf.j2
dest: /etc/app.conf
when: environment == "development"
- name: Diski kontrol et
shell: df -h / | tail -1 | awk '{{print $5}}' | sed 's/%//'
register: disk_usage
changed_when: false
- name: Disk uyarısı
debug:
msg: "Uyarı: Disk kullanımı %{{ disk_usage.stdout }} seviyesinde!"
when: disk_usage.stdout | int > 80
`
Koşullar, facts karşılaştırmaları, register değişkenleri ve mantıksal operatörler (and, or, not) ile birlikte kullanılabilir. changed_when ile, bir görevin "changed" durumunu manuel olarak kontrol edilebilir.
Block ve Rescue
Hata yönetimi için block, rescue ve always keyword'leri kullanılır. Bu yapı, try-catch benzeri bir hata yakalama mekanizması sağlar.
`yaml
- name: Kritik veritabanı yedekleme
block:
- name: Veritabanı dump al
shell: pg_dump -U postgres myapp > /backup/db_{{ ansible_date_time.iso8601 }}.sql
register: backup_result
- name: Yedek boyutunu kontrol et
assert:
that:
- backup_result.stat.size > 1000
fail_msg: "Yedek dosyası çok küçük, işlem başarısız olmuş olabilir!"
rescue:
- name: Yedekleme hatası
mail:
to: admin@example.com
subject: "Yedekleme Başarısız"
body: "Veritabanı yedekleme işlemi başarısız oldu. Lütfen kontrol edin."
always:
- name: Geçici dosyaları temizle
shell: rm -f /tmp/backup_*.sql
`
block içindeki görevler çalıştırılırken hata oluşursa, rescue bölümü çalışır. always bölümü ise hata olsun veya olmasın her durumda çalışır. Bu yapı, kritik operasyonlarda hata durumlarını ele almak için idealdir.
Ansible Modül Geliştirme
Custom Modül Yazımı
Ansible, ihtiyaç duyduğunuz özel fonksiyonlar için custom modül geliştirmeye olanak tanır. Modüller, Python dilinde yazılır ve Ansible control node'dan hedef sunuculara gönderilerek çalıştırılır.
`python
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import json
def run_module():
module_args = dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
port=dict(type='int', default=8080),
config=dict(type='dict', required=False)
)
result = dict(
changed=False,
message='',
original_message='',
config={}
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
if module.check_mode:
module.exit_json(**result)
name = module.params['name']
state = module.params['state']
port = module.params['port']
config = module.params.get('config', {})
# İş mantığı buraya
if state == 'present':
result['changed'] = True
result['message'] = f"{name} servisi yapılandırıldı"
result['config'] = {'name': name, 'port': port, **config}
else:
result['changed'] = True
result['message'] = f"{name} servisi kaldırıldı"
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()
`
Custom modüller, library/ dizininde veya playbook ile aynı dizinde tutulabilir. Modül çıktısı JSON formatında olmalı ve changed, msg gibi standart ansible çıktı alanlarını içermelidir.
Modül Argument Spec
Ansible modüllerinin argument spec'i, modülün aldığı parametreleri tanımlar. Bu spec, tip kontrolü, zorunlu alanlar, varsayılan değerler ve validasyon kurallarını içerir.
`python
def get_module_argspec():
return dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent', 'started', 'stopped']),
enabled=dict(type='bool', default=True),
port=dict(type='int', required=True, aliases=['listen']),
config=dict(type='dict', required=False, default={}),
restart_on_change=dict(type='bool', default=False),
force=dict(type='bool', default=False),
)
`
Argüman spec'i, modülün kullanımını belgelerken ve hata mesajlarını standardize ederken önemli bir rol oynar. Ansible Galaxy'de yayınlanan modüllerin büyük çoğunluğu bu yapıyı kullanır.
Ansible Vault ile Güvenlik
Secrets Şifreleme
Ansible Vault, sensitive verilerin (şifreler, API anahtarları, sertifikalar) güvenli şekilde saklanmasını sağlar. Vault ile şifrelenmiş dosyalar, yalnızca doğru şifre ile çözülebilir.
`bash
ansible-vault create secrets.yml
ansible-vault encrypt vars/production.yml
ansible-vault decrypt vars/production.yml
ansible-vault rekey secrets.yml
`
Vault dosyaları, playbook veya inventory ile aynı şekilde kullanılır. Şifre, komut satırında --ask-vault-pass flag'i ile veya vault_password_file ile sağlanabilir.
vault_id ve Multiple Vaults
Büyük projelerde, farklı ortamlar için farklı vault'lar kullanılabilir. Örneğin, development, staging ve production için ayrı şifreler.
`bash
ansible-vault create --vault-id dev@prompt secrets_dev.yml
ansible-vault create --vault-id prod@prompt secrets_prod.yml
`
En İyi Uygulamalar ve İpuçları
Playbook Organizasyonu
Büyük projelerde playbook'ların modüler yapıda tutulması önemlidir. Roller (roles), playbook bileşenlerinin yeniden kullanılabilir paketler halinde organize edilmesini sağlar.
`
production/
├── inventory
├── group_vars/
│ └── all.yml
├── host_vars/
├── roles/
│ ├── common/
│ │ ├── tasks/
│ │ ├── handlers/
│ │ ├── templates/
│ │ └── vars/
│ ├── webserver/
│ ├── database/
│ └── monitoring/
└── site.yml
`
Her rol, belirli bir fonksiyonu yerine getirir ve bağımsız olarak test edilebilir. Bu yapı, büyük ekiplerin parallel olarak çalışmasını kolaylaştırır.
Performans Optimizasyonu
Ansible playbook'larının performansını artırmak için birkaç teknik kullanılabilir:
1. Pipelining: SSH bağlantı sayısını azaltır.
2. Async/Parallel: Bağımsız görevlerin eşzamanlı çalışmasını sağlar.
3. Fork sayısı: Eşzamanlı hedef sayısını ayarlar.
4. Fact caching: Facts'ların yeniden toplanmasını önler.
`yaml
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
pipelining = True
forks = 50
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
`
`yaml
- name: Uzun süren görevler
shell: "/usr/local/bin/long_running_script.sh"
async: 3600
poll: 30
register: long_task
`
Debug ve Hata Ayıklama
Ansible'da hata ayıklama için debug modülü ve --check (dry-run) modu kullanılır.
`bash
ansible-playbook site.yml --check
ansible-playbook site.yml -vvv
ansible-playbook site.yml --tags "nginx,security"
ansible-playbook site.yml --limit web01.example.com
``
Sonuç
Ansible, modern IT altyapı otomasyonunun temel taşlarından biri olmaya devam etmektedir. Bu makalede, playbook geliştirmeden modül yazımına, Vault güvenliğinden performans optimizasyonuna kadar geniş bir yelpazede bilgi paylaşıldı.
Ansible'ı etkili kullanmanın anahtarı, idempotent yapıyı anlamak, modüler playbook'lar yazmak ve güvenlik en iyi uygulamalarını takip etmektir. Özellikle büyük ölçekli ortamlarda, rol tabanlı organizasyon, fact caching ve parallel execution teknikleri kritik önem taşımaktadır.
Sonraki adımlar olarak, Ansible Tower veya AWX ile web tabanlı yönetim arayüzü keşfedilebilir, Ansible Galaxy üzerinden topluluk rolleri incelenebilir ve CI/CD pipeline'larına Ansible entegrasyonu denenebilir.