Ansible Playbook Temelleri: Görev Tanımlama ve Çalıştırma

Ansible ile çalışmaya başladığınızda, ansible komutunu kullanarak tek satırlık işlemler yapabilirsiniz. Ama gerçek güç, playbook’larda saklı. Playbook’lar, birden fazla görevi sıralı ve tekrarlanabilir şekilde çalıştırmanızı sağlayan YAML dosyalarıdır. Bu yazıda playbook anatomisini, görev tanımlamanın inceliklerini ve gerçek senaryolarda nasıl kullanacağınızı ele alacağız.

Playbook Nedir, Neden Kullanırız?

Bir sabah 20 sunucuya aynı paketi kurmak, aynı config dosyasını dağıtmak ve servisi yeniden başlatmak zorunda kaldığınızı düşünün. SSH ile teker teker bağlanmak mı? Hayır. Bir bash script mi? Yakın ama yeterli değil. Ansible playbook’u tam olarak bu durumlar için tasarlanmış.

Playbook, Ansible’ın diliyle yazılmış bir otomasyon senaryosudur. YAML formatında yazılır, okunması kolaydır ve idempotent’tir yani aynı playbook’u birden fazla çalıştırırsanız, sistem hep aynı son duruma gelir, tekrar tekrar değişiklik yapmaz.

Playbook’ların öne çıkan avantajları:

  • Tekrarlanabilirlik: Aynı yapılandırmayı 1 sunucuya da 1000 sunucuya da uygulayabilirsiniz
  • Versiyon kontrolü: YAML dosyaları Git’e gider, değişiklik geçmişi tutulur
  • Okunabilirlik: Bash’e kıyasla ne yaptığı çok daha anlaşılır
  • Hata yönetimi: Bir görev başarısız olursa playbook durur, sistemi yarım bırakmaz
  • İdempotency: Güvenle tekrar çalıştırabilirsiniz

Playbook Yapısı ve Anatomisi

Bir playbook en temel haliyle şu şekilde görünür:

---
- name: Web sunucusu kurulumu
  hosts: webservers
  become: yes

  tasks:
    - name: Nginx kur
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Nginx'i başlat ve enable et
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: yes

Buradaki her satırın anlamına bakalım:

  • ---: YAML dosyasının başladığını belirtir, zorunlu değil ama standart bir alışkanlık
  • name: Play’e verdiğiniz açıklayıcı isim, çıktıda görünür
  • hosts: Bu play’in hangi host grubuna uygulanacağı, inventory’deki grupla eşleşir
  • become: Sudo yetkileriyle çalış anlamına gelir
  • tasks: Sırayla çalıştırılacak görevlerin listesi

Her task içinde de benzer bir yapı var:

  • name: Görevin açıklaması, log çıktısında görünür
  • Modül adı: ansible.builtin.package, ansible.builtin.service gibi
  • Modül parametreleri: Modülün ne yapacağını belirleyen key-value çiftleri

Görev Tanımlama: Modüller ile Çalışmak

Ansible’da her görev bir modül çağrısıdır. Modüller, Ansible’ın yapı taşlarıdır. Dosya kopyalamak için copy, paket kurmak için package, servis yönetmek için service kullanırsınız.

Temel Modülleri Tanıyalım

Package modülü ile paket yönetimi:

---
- name: Paket yönetimi örnekleri
  hosts: all
  become: yes

  tasks:
    - name: Birden fazla paket kur
      ansible.builtin.package:
        name:
          - vim
          - curl
          - git
          - htop
        state: present

    - name: Eski paketi kaldır
      ansible.builtin.package:
        name: telnet
        state: absent

    - name: Paketi en son sürüme güncelle
      ansible.builtin.package:
        name: openssl
        state: latest

Copy ve template modülleri:

---
- name: Dosya dağıtımı
  hosts: webservers
  become: yes

  tasks:
    - name: Nginx config dosyasını kopyala
      ansible.builtin.copy:
        src: files/nginx.conf
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        backup: yes

    - name: Virtual host config'i template ile oluştur
      ansible.builtin.template:
        src: templates/vhost.conf.j2
        dest: /etc/nginx/sites-available/myapp.conf
        owner: root
        group: root
        mode: '0644'

copy modülü dosyayı olduğu gibi kopyalar. template modülü ise Jinja2 şablon motorunu kullanarak değişkenleri işler ve dinamik config dosyaları oluşturmanızı sağlar. Production ortamında genellikle template daha kullanışlıdır çünkü her sunucu için farklı değerler atayabilirsiniz.

File modülü ile dosya/dizin yönetimi:

---
- name: Dizin ve dosya yapısı oluştur
  hosts: appservers
  become: yes

  tasks:
    - name: Uygulama dizinini oluştur
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
        owner: appuser
        group: appuser
        mode: '0755'

    - name: Log dizinini oluştur
      ansible.builtin.file:
        path: /var/log/myapp
        state: directory
        owner: appuser
        group: appgroup
        mode: '0750'

    - name: Sembolik link oluştur
      ansible.builtin.file:
        src: /opt/myapp/current
        dest: /usr/local/bin/myapp
        state: link

Handlers: Değişikliğe Tepki Vermek

Ansible’da çok güçlü bir kavram olan handler’lar, yalnızca bir değişiklik olduğunda tetiklenen özel görevlerdir. En klasik örnek şudur: Nginx config’ini değiştirdiniz, servisi yeniden başlatmanız gerekiyor. Ama config değişmediyse yeniden başlatmaya gerek yok.

---
- name: Nginx kurulum ve yapılandırma
  hosts: webservers
  become: yes

  handlers:
    - name: nginx restart
      ansible.builtin.service:
        name: nginx
        state: restarted

    - name: nginx reload
      ansible.builtin.service:
        name: nginx
        state: reloaded

  tasks:
    - name: Nginx kur
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Ana config dosyasını dağıt
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: nginx reload

    - name: SSL sertifikasını kopyala
      ansible.builtin.copy:
        src: files/ssl/myapp.crt
        dest: /etc/nginx/ssl/myapp.crt
        mode: '0644'
      notify: nginx restart

Handler’lar play sonunda çalışır ve bir handler birden fazla task tarafından tetiklense bile yalnızca bir kez çalışır. Bu detay production’da çok işe yarar.

Değişkenler ve Dinamik Görevler

Gerçek dünyada playbook’larınız static değerler içermez. Değişkenler kullanarak playbook’larınızı yeniden kullanılabilir hale getirirsiniz.

---
- name: Uygulama deployment
  hosts: appservers
  become: yes

  vars:
    app_name: mywebapp
    app_version: "2.1.4"
    app_port: 8080
    deploy_dir: /opt/apps

  tasks:
    - name: Deployment dizinini oluştur
      ansible.builtin.file:
        path: "{{ deploy_dir }}/{{ app_name }}"
        state: directory
        mode: '0755'

    - name: Uygulama arşivini indir
      ansible.builtin.get_url:
        url: "https://releases.example.com/{{ app_name }}-{{ app_version }}.tar.gz"
        dest: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
        mode: '0644'

    - name: Arşivi çıkart
      ansible.builtin.unarchive:
        src: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
        dest: "{{ deploy_dir }}/{{ app_name }}"
        remote_src: yes

    - name: Systemd servis dosyasını oluştur
      ansible.builtin.template:
        src: templates/app.service.j2
        dest: "/etc/systemd/system/{{ app_name }}.service"
      notify: systemd reload

  handlers:
    - name: systemd reload
      ansible.builtin.systemd:
        daemon_reload: yes

Değişkenleri farklı yerlerde tanımlayabilirsiniz:

  • vars: Playbook içinde, play seviyesinde
  • vars_files: Ayrı bir YAML dosyasından yükleyerek
  • group_vars/: Host gruplarına özel değişkenler
  • host_vars/: Tek bir host için özel değişkenler
  • Komut satırı: -e "app_version=2.2.0" parametresiyle

Koşullu Görevler: when Kullanımı

Bazen bir görevi yalnızca belirli koşullarda çalıştırmak istersiniz. İşte when direktifi tam burada devreye girer.

---
- name: İşletim sistemine göre yapılandırma
  hosts: all
  become: yes

  tasks:
    - name: Apache kur (Debian tabanlı)
      ansible.builtin.apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"

    - name: Apache kur (RedHat tabanlı)
      ansible.builtin.dnf:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"

    - name: Apache servisini başlat (Debian)
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: yes
      when: ansible_os_family == "Debian"

    - name: Apache servisini başlat (RedHat)
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: yes
      when: ansible_os_family == "RedHat"

    - name: Disk kullanımını kontrol et
      ansible.builtin.command: df -h /
      register: disk_usage

    - name: Disk dolu uyarısı
      ansible.builtin.debug:
        msg: "UYARI: Disk kullanımı yüksek!"
      when: disk_usage.stdout is search("9[0-9]%")

register direktifi bir görevin çıktısını değişkene kaydeder, sonraki görevlerde bu çıktıya göre karar verebilirsiniz. Production’da çok kullandığım bir pattern.

Döngüler: loop ile Tekrarlayan Görevler

Aynı görevi birden fazla öğe için çalıştırmanız gerektiğinde döngüler kullanırsınız.

---
- name: Kullanıcı ve grup yönetimi
  hosts: all
  become: yes

  vars:
    system_users:
      - name: appuser
        groups: www-data
        shell: /bin/bash
      - name: deployuser
        groups: sudo
        shell: /bin/bash
      - name: monitoruser
        groups: adm
        shell: /sbin/nologin

  tasks:
    - name: Sistem kullanıcılarını oluştur
      ansible.builtin.user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        shell: "{{ item.shell }}"
        state: present
        create_home: yes
      loop: "{{ system_users }}"

    - name: Sudo yetkilerini yapılandır
      ansible.builtin.lineinfile:
        path: /etc/sudoers.d/{{ item }}
        line: "{{ item }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl"
        create: yes
        mode: '0440'
      loop:
        - appuser
        - deployuser

Playbook Çalıştırma ve Kontrol Seçenekleri

Playbook yazdıktan sonra çalıştırmadan önce test etmek iyi bir alışkanlıktır.

Syntax kontrolü:

ansible-playbook --syntax-check site.yml

Dry-run (ne olacağını gör, uygulama):

ansible-playbook --check site.yml

Verbose mod (daha fazla çıktı):

ansible-playbook -v site.yml          # Temel verbose
ansible-playbook -vv site.yml         # Daha fazla detay
ansible-playbook -vvv site.yml        # Debug seviyesi

Belirli host’lara veya gruplara uygulama:

ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit "web01,web02"
ansible-playbook site.yml --limit "webservers:!web03"

Belirli tag’leri çalıştırma:

ansible-playbook site.yml --tags "install,configure"
ansible-playbook site.yml --skip-tags "deploy"

Tag kullanımı playbook’larınızı modüler hale getirir. Sadece deployment görevlerini çalıştırmak istiyorsanız sadece o tag’i geçirirsiniz.

Değişken override:

ansible-playbook site.yml -e "app_version=2.2.0 app_port=9090"

Gerçek Dünya Senaryosu: LAMP Stack Kurulumu

Şimdiye kadar öğrendiklerimizi birleştirerek gerçekçi bir playbook yazalım.

---
- name: LAMP Stack Kurulumu
  hosts: webservers
  become: yes

  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    php_version: "8.1"
    web_root: /var/www/html

  handlers:
    - name: apache2 restart
      ansible.builtin.service:
        name: apache2
        state: restarted

    - name: mysql restart
      ansible.builtin.service:
        name: mysql
        state: restarted

  tasks:
    - name: Paket listesini güncelle
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

    - name: Apache kur
      ansible.builtin.apt:
        name: apache2
        state: present
      notify: apache2 restart

    - name: PHP ve modülleri kur
      ansible.builtin.apt:
        name:
          - "php{{ php_version }}"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-curl"
          - "php{{ php_version }}-mbstring"
          - "php{{ php_version }}-xml"
          - libapache2-mod-php
        state: present
      notify: apache2 restart

    - name: MySQL kur
      ansible.builtin.apt:
        name:
          - mysql-server
          - python3-mysqldb
        state: present

    - name: MySQL servisini başlat
      ansible.builtin.service:
        name: mysql
        state: started
        enabled: yes

    - name: Web root dizinini oluştur
      ansible.builtin.file:
        path: "{{ web_root }}/myapp"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: Test PHP dosyası oluştur
      ansible.builtin.copy:
        content: "<?php phpinfo(); ?>"
        dest: "{{ web_root }}/myapp/info.php"
        owner: www-data
        group: www-data
        mode: '0644'

    - name: Apache servisini enable et
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: yes

    - name: Firewall'da HTTP portunu aç
      community.general.ufw:
        rule: allow
        port: '80'
        proto: tcp

Bu playbook Ubuntu/Debian üzerinde çalışan eksiksiz bir LAMP stack kurar. Dikkat edin: mysql_root_password değişkeni vault_mysql_root_password ile geliyor. Ansible Vault konusunu ayrı bir yazıda ele alacağız ama şimdilik şunu bilin: şifreleri playbook’a düz yazmazsınız, Vault ile şifreli saklarsınız.

Playbook Organizasyonu: Best Practices

Playbook’larınız büyüdükçe düzeni korumak önemli hale gelir. Birkaç pratik öneri:

  • Görevleri mantıksal gruplara ayırın: Kurulum, yapılandırma ve deployment için ayrı task blokları kullanın
  • Her göreve açıklayıcı isim verin: “Paketi kur” yerine “Nginx web sunucusunu kur” yazın
  • Handler isimlerini tutarlı tutun: nginx restart gibi service-action formatı kullanın
  • Hassas değişkenleri Vault ile şifreleyin: Şifre, API key gibi değerleri asla düz metin bırakmayın
  • Playbook’larınızı test edin: --check ve --syntax-check kullanmayı alışkanlık haline getirin
  • Tag ekleyin: Büyük playbook’larda tag’ler hayat kurtarır
  • Idempotency’i koruyun: Her modülün tekrar çalıştırılabilir olduğundan emin olun

Dizin yapısı için basit bir öneri:

  • site.yml ana playbook
  • group_vars/all.yml tüm hostlar için ortak değişkenler
  • group_vars/webservers.yml web sunucularına özel değişkenler
  • host_vars/web01.yml tek bir sunucuya özel değişkenler
  • files/ statik dosyalar
  • templates/ Jinja2 template dosyaları
  • handlers/ ortak handler’lar

Sonuç

Ansible playbook’ları, sistem yönetiminde tekrarlayan işleri otomatikleştirmenin en temiz yollarından biri. Bu yazıda temel görev tanımlamayı, modülleri, handler’ları, değişkenleri, koşullu görevleri ve döngüleri ele aldık.

Şimdi yapmanız gereken şey basit: Kendi ortamınızda küçük bir playbook yazın. Belki test sunucunuza bir paket kurma ve bir servis başlatma. Sonra --check ile test edin, sonra gerçekten çalıştırın. Playbook’un idempotent çalıştığını, ikinci çalıştırmada “changed” yerine “ok” çıktısı verdiğini gözlemleyin.

Bir sonraki adım olarak Ansible Roles konusuna geçmenizi öneririm. Role’lar, playbook’larınızı daha modüler ve yeniden kullanılabilir hale getirmenizi sağlar. Özellikle birden fazla projede aynı yapılandırmaları kullanıyorsanız, role’lar büyük zaman tasarrufu sağlar. Ama önce bu temelleri iyice sindirin, çünkü role’lar da sonunda aynı yapı üzerine kurulu.

Yorum yapın