İçeriğe geç
13 dk okuma DevOps

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.