Ansible ile Paket Güncelleme Otomasyonu

Her sistem yöneticisinin korkulu rüyası olan “şu sunucuları bir güncelleme geçeyim” anı, özellikle onlarca veya yüzlerce makineyi yönetiyorsanız gerçek bir işkenceye dönüşebilir. Bir sunucuda SSH açarsın, apt upgrade çalıştırırsın, beklersin, bir sonrakine geçersin. Saatler içinde belki 20-30 sunucu güncellersin ve o sırada aklının bir köşesinde “ya birinde bir şeyler bozulduysa” sorusu sürekli dönüp durur. İşte tam bu noktada Ansible devreye giriyor ve hayatınızı kökten değiştiriyor.

Bu yazıda Ansible ile paket güncelleme otomasyonunu tüm detaylarıyla ele alacağız. Basit tek satırlık komutlardan başlayıp, production ortamlarında güvenle çalışabilecek rolling update stratejilerine kadar gerçek dünya senaryolarını inceleyeceğiz.

Neden Ansible ile Paket Güncelleme?

Manuel güncelleme sürecinin ne kadar verimsiz olduğunu hepimiz biliyoruz. Ama Ansible’ın bu işi sadece “daha hızlı yapmak” değil, aynı zamanda tutarlı, tekrarlanabilir ve izlenebilir hale getirmesi asıl değer.

Bir düşünün: 50 sunucunuzun 48’ini güncelldiniz, 2’sinde bir şeyler ters gitti. Manuel yöntemde hangi sunucuların güncellendiğini, hangilerinde sorun çıktığını takip etmek için ayrı bir not tutmanız gerekiyor. Ansible ile tüm bu süreç log altına alınıyor, idempotent çalışıyor ve hata durumunda tam olarak nerede durduğunu size söylüyor.

Ansible’ın bu konudaki temel avantajları:

  • Agentless çalışma: Hedef sunuculara sadece SSH erişimi ve Python yeterli
  • Idempotency: Aynı playbook’u kaç kez çalıştırırsanız çalıştırın, sonuç değişmez
  • Paralel yürütme: Onlarca sunucu aynı anda güncellenebilir
  • Hata yönetimi: Bir sunucuda sorun çıktığında ne yapılacağını önceden tanımlayabilirsiniz
  • Raporlama: Neyin güncellendiğini, neyin değişmediğini açıkça görebilirsiniz

Temel Yapıyı Kuralım

Önce inventory dosyamızı düzgün şekilde organize edelim. Gerçek dünya senaryolarında sunucularınızı gruplamak kritik önem taşıyor.

# /etc/ansible/hosts veya proje dizinindeki inventory.ini

[webservers]
web01.example.com
web02.example.com
web03.example.com

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

[appservers]
app01.example.com
app02.example.com

[production:children]
webservers
dbservers
appservers

[staging]
staging01.example.com
staging02.example.com

Bu yapı sayesinde hem grup bazında hem de tüm production ortamını kapsayan güncellemeler yapabilirsiniz.

İlk Playbook: Basit Paket Güncelleme

En basit haliyle bir güncelleme playbook’u yazalım. Bu başlangıç noktası olarak iyi olsa da production’da doğrudan kullanmayın henüz.

# update_packages.yml
---
- name: Paket guncellemesi yap
  hosts: all
  become: yes
  
  tasks:
    - name: APT cache'ini guncelle (Debian/Ubuntu)
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

    - name: Tum paketleri guncelle (Debian/Ubuntu)
      apt:
        upgrade: dist
        autoremove: yes
        autoclean: yes
      when: ansible_os_family == "Debian"

    - name: YUM/DNF ile tum paketleri guncelle (RHEL/CentOS)
      yum:
        name: "*"
        state: latest
      when: ansible_os_family == "RedHat"

Bu playbook’u çalıştırmak için:

ansible-playbook update_packages.yml -i inventory.ini --ask-become-pass

# Sadece web sunucularini guncellemek icin
ansible-playbook update_packages.yml -i inventory.ini -l webservers

# Once ne yapacagini gormek icin (dry-run)
ansible-playbook update_packages.yml -i inventory.ini --check

Reboot Yönetimi: Kernel Güncellemelerinden Sonra

Paket güncellemelerinin en kritik kısmı kernel güncellemeleri sonrası reboot yönetimidir. Sunucu reboot olmadan yeni kernel aktif olmaz, ama ne zaman reboot’a izin verileceği de önemli bir karar.

# update_with_reboot.yml
---
- name: Paket guncellemesi ve gerekirse reboot
  hosts: all
  become: yes
  serial: 1  # Sunuculari tek tek isle, hepsini ayni anda degil
  
  tasks:
    - name: APT cache guncelle
      apt:
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: Paketleri guncelle
      apt:
        upgrade: full
        autoremove: yes
      register: apt_upgrade_result
      when: ansible_os_family == "Debian"

    - name: Reboot gerekli mi kontrol et (Debian/Ubuntu)
      stat:
        path: /var/run/reboot-required
      register: reboot_required_file
      when: ansible_os_family == "Debian"

    - name: Reboot gerekiyorsa yeniden basla
      reboot:
        reboot_timeout: 300
        pre_reboot_delay: 5
        post_reboot_delay: 30
        msg: "Ansible tarafindan kernel guncelleme sonrasi reboot"
      when: 
        - ansible_os_family == "Debian"
        - reboot_required_file.stat.exists

    - name: Sunucunun tekrar ayaga kalktigini dogrula
      wait_for_connection:
        connect_timeout: 20
        sleep: 5
        delay: 5
        timeout: 300

serial: 1 parametresine özellikle dikkat edin. Bu değer, Ansible’ın kaç sunucuyu aynı anda işleyeceğini belirler. Yüzde olarak da yazabilirsiniz: serial: "20%" yazarsanız tüm sunucuların %20’si aynı anda işlenir. Bu özellikle load balancer arkasındaki web sunucuları için hayat kurtarıcı.

Gerçek Dünya Senaryosu: Rolling Update ile Sıfır Kesinti

Şimdi işleri biraz daha karmaşık ama gerçekçi bir seviyeye taşıyalım. Diyelim ki bir HAProxy load balancer arkasında 4 web sunucunuz var ve güncelleme sırasında servis kesintisi yaşanmaması gerekiyor.

# rolling_update.yml
---
- name: HAProxy uzerinden web sunucularini rolling update et
  hosts: webservers
  become: yes
  serial: 1
  max_fail_percentage: 0  # Herhangi bir hata durumunda dur

  pre_tasks:
    - name: Sunucuyu load balancer'dan cikar
      shell: |
        echo "disable server webfarm/{{ inventory_hostname }}" | 
        socat stdio /var/run/haproxy/admin.sock
      delegate_to: haproxy01.example.com
      ignore_errors: yes

    - name: Mevcut baglantilarin bitmesini bekle
      pause:
        seconds: 15

  tasks:
    - name: APT cache guncelle
      apt:
        update_cache: yes
        cache_valid_time: 1800
      when: ansible_os_family == "Debian"

    - name: Paketleri guncelle
      apt:
        upgrade: dist
        autoremove: yes
      register: update_result

    - name: Guncelleme sonucunu logla
      debug:
        msg: "{{ inventory_hostname }} uzerinde {{ update_result.stdout_lines | default([]) | length }} satir degisiklik oldu"

    - name: Reboot gerektiriyor mu?
      stat:
        path: /var/run/reboot-required
      register: reboot_check

    - name: Gerekirse reboot
      reboot:
        reboot_timeout: 400
        pre_reboot_delay: 3
      when: reboot_check.stat.exists

    - name: Uygulama servisinin calistigini kontrol et
      systemd:
        name: nginx
        state: started
      register: nginx_status

    - name: HTTP health check
      uri:
        url: "http://localhost/health"
        status_code: 200
        timeout: 10
      retries: 5
      delay: 10
      register: health_check_result

  post_tasks:
    - name: Sunucuyu load balancer'a geri ekle
      shell: |
        echo "enable server webfarm/{{ inventory_hostname }}" | 
        socat stdio /var/run/haproxy/admin.sock
      delegate_to: haproxy01.example.com
      when: health_check_result.status == 200

    - name: Guncelleme tamamlandi bildirimi
      debug:
        msg: "{{ inventory_hostname }} basariyla guncellendi ve load balancer'a eklendi"

Bu playbook production ortamınızda sıfır kesinti ile güncelleme yapmanızı sağlar. Her sunucu load balancer’dan çıkarılır, güncellenir, health check’ten geçer ve ancak o zaman geri eklenir.

Güncelleme Öncesi Snapshot veya Yedek Alma

Özellikle kritik sistemlerde güncelleme öncesi bir güvenlik ağı kurmak akıllıca bir yaklaşım. VMware veya cloud ortamlarında snapshot alabilirsiniz.

# update_with_snapshot.yml
---
- name: Snapshot al, guncelle, dogrula
  hosts: production
  become: yes
  gather_facts: yes

  vars:
    update_timestamp: "{{ ansible_date_time.iso8601_basic_short }}"
    snapshot_name: "pre_update_{{ update_timestamp }}"

  tasks:
    - name: AWS EC2 snapshot al
      ec2_snapshot:
        region: eu-west-1
        instance_id: "{{ ec2_instance_id }}"
        description: "Ansible otomatik guncelleme oncesi - {{ update_timestamp }}"
        snapshot_tags:
          Name: "{{ snapshot_name }}"
          CreatedBy: "ansible-automation"
          Purpose: "pre-update-backup"
      delegate_to: localhost
      register: snapshot_result
      when: cloud_provider == "aws"

    - name: Snapshot ID'yi kaydet
      set_fact:
        created_snapshot_id: "{{ snapshot_result.snapshot_id }}"
      when: cloud_provider == "aws"

    - name: Paket guncellemesi yap
      apt:
        upgrade: dist
        update_cache: yes
        autoremove: yes
      register: upgrade_output
      when: ansible_os_family == "Debian"

    - name: Kritik servisleri kontrol et
      systemd:
        name: "{{ item }}"
        state: started
      loop:
        - nginx
        - postgresql
        - redis-server
      register: service_check
      failed_when: service_check.state != "started"

    - name: Tum servisler calisiyor mu ozet
      debug:
        msg: "Tum kritik servisler guncelleme sonrasi calisiyor. Snapshot: {{ created_snapshot_id | default('N/A') }}"

Belirli Paketleri Güncelleme ve Versiyon Sabitleme

Bazen tüm sistemi değil, sadece belirli paketleri güncellemek ya da bazı paketleri belirli bir versiyonda sabitlemek isteyebilirsiniz. Bu özellikle veritabanı sunucularında veya bağımlılığı olan uygulamalarda kritik öneme sahip.

# selective_update.yml
---
- name: Secici paket guncelleme ve versiyon yonetimi
  hosts: all
  become: yes

  vars:
    security_packages_debian:
      - openssl
      - libssl1.1
      - openssh-server
      - curl

    pinned_packages_debian:
      - { name: "mysql-server", version: "8.0.32-0ubuntu0.22.04.2" }
      - { name: "postgresql-14", version: "14.8-0ubuntu0.22.04.1" }

  tasks:
    - name: Sadece guvenlik paketlerini guncelle (Debian)
      apt:
        name: "{{ security_packages_debian }}"
        state: latest
        update_cache: yes
      when: ansible_os_family == "Debian"

    - name: Belirli paketleri sabitle (hold et)
      dpkg_selections:
        name: "{{ item }}"
        selection: hold
      loop:
        - mysql-server
        - postgresql-14
        - docker-ce
      when: ansible_os_family == "Debian"

    - name: Hangi paketlerin hold'da oldugunu listele
      shell: dpkg --get-selections | grep hold
      register: held_packages
      changed_when: false
      when: ansible_os_family == "Debian"

    - name: Hold'daki paketleri goster
      debug:
        var: held_packages.stdout_lines
      when: ansible_os_family == "Debian"

    - name: Yalnizca guvenlik guncellemelerini uygula (RHEL/CentOS 8+)
      dnf:
        security: yes
        state: latest
        name: "*"
      when: 
        - ansible_os_family == "RedHat"
        - ansible_distribution_major_version | int >= 8

Güncelleme Sonrası Raporlama

Hangi sunucuda ne değişti, hangi paket hangi versiyona geçti? Bu bilgileri toplamak hem audit açısından hem de sorun giderme için çok değerli.

# update_with_report.yml
---
- name: Guncelleme ve rapor olusturma
  hosts: all
  become: yes
  gather_facts: yes

  tasks:
    - name: Guncelleme oncesi paket listesi al
      shell: dpkg -l | grep '^ii' | awk '{print $2"="$3}'
      register: packages_before
      changed_when: false
      when: ansible_os_family == "Debian"

    - name: Paketleri guncelle
      apt:
        upgrade: dist
        update_cache: yes
        autoremove: yes
      register: update_output
      when: ansible_os_family == "Debian"

    - name: Guncelleme sonrasi paket listesi al
      shell: dpkg -l | grep '^ii' | awk '{print $2"="$3}'
      register: packages_after
      changed_when: false
      when: ansible_os_family == "Debian"

    - name: Guncellenen paketleri bul
      set_fact:
        updated_packages: "{{ packages_after.stdout_lines | difference(packages_before.stdout_lines) }}"
      when: ansible_os_family == "Debian"

    - name: Raporu yerel dosyaya kaydet
      copy:
        content: |
          Sunucu: {{ inventory_hostname }}
          Tarih: {{ ansible_date_time.iso8601 }}
          OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
          Guncellenen Paket Sayisi: {{ updated_packages | length }}
          
          Guncellenen Paketler:
          {% for pkg in updated_packages %}
          - {{ pkg }}
          {% endfor %}
        dest: "/tmp/update_report_{{ inventory_hostname }}_{{ ansible_date_time.date }}.txt"
      delegate_to: localhost

    - name: Slack bildirimi gonder
      uri:
        url: "{{ slack_webhook_url }}"
        method: POST
        body_format: json
        body:
          text: "✅ {{ inventory_hostname }} guncellendi. {{ updated_packages | length }} paket degisti."
      delegate_to: localhost
      when: 
        - slack_webhook_url is defined
        - updated_packages | length > 0

Cron veya Systemd Timer ile Otomatik Zamanlama

Playbook’unuzu yazdınız, şimdi bunu düzenli aralıklarla otomatik çalıştırmanın zamanı. Benim tercihim genellikle systemd timer, ama cron da gayet iyi çalışır.

# Ansible'i cron ile calistirmak icin wrapper script olusturalim
# /usr/local/bin/run_ansible_updates.sh

#!/bin/bash

set -euo pipefail

LOG_DIR="/var/log/ansible"
LOG_FILE="${LOG_DIR}/update_$(date +%Y%m%d_%H%M%S).log"
PLAYBOOK="/opt/ansible/playbooks/update_packages.yml"
INVENTORY="/opt/ansible/inventory/production.ini"
VAULT_PASS_FILE="/root/.ansible_vault_pass"

mkdir -p "${LOG_DIR}"

echo "=== Ansible Paket Guncelleme Basladi: $(date) ===" >> "${LOG_FILE}"

ansible-playbook 
    "${PLAYBOOK}" 
    -i "${INVENTORY}" 
    --vault-password-file "${VAULT_PASS_FILE}" 
    --diff 
    2>&1 | tee -a "${LOG_FILE}"

EXIT_CODE=${PIPESTATUS[0]}

if [ ${EXIT_CODE} -ne 0 ]; then
    echo "HATA: Ansible playbook basarisiz oldu! Exit code: ${EXIT_CODE}" >> "${LOG_FILE}"
    # Mail gonder
    mail -s "HATA: Ansible Update Basarisiz" [email protected] < "${LOG_FILE}"
fi

echo "=== Bitti: $(date) ===" >> "${LOG_FILE}"

# 30 gunden eski loglari temizle
find "${LOG_DIR}" -name "*.log" -mtime +30 -delete

exit ${EXIT_CODE}
# Crontab'a ekle - Her Pazar sabah 03:00'te calistir
# crontab -e ile ekle:
0 3 * * 0 /usr/local/bin/run_ansible_updates.sh

# Veya systemd timer ile (daha modern yaklasim)
# /etc/systemd/system/ansible-update.service
[Unit]
Description=Ansible Otomatik Paket Guncelleme
After=network.target

[Service]
Type=oneshot
User=ansible
ExecStart=/usr/local/bin/run_ansible_updates.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/ansible-update.timer
[Unit]
Description=Haftalik Ansible Guncelleme Timer

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=1800

[Install]
WantedBy=timers.target

# Aktive etmek icin:
systemctl daemon-reload
systemctl enable ansible-update.timer
systemctl start ansible-update.timer

# Durumu kontrol et
systemctl list-timers | grep ansible

RandomizedDelaySec=1800 parametresi çok önemli. Eğer çok sayıda sunucunuz varsa ve hepsi tam aynı anda güncellemeye başlarsa, ortak kaynaklarda (package repository mirror gibi) yük yaratabilirsiniz. Bu parametre ile her sunucu 30 dakikaya kadar rastgele bir gecikmeyle başlar.

Ansible Vault ile Şifreli Değişkenler

Playbook’larınızda SSH anahtarları, API token’ları veya webhook URL’leri gibi hassas bilgiler bulunuyor olabilir. Bunları asla düz metin olarak tutmayın.

# Vault ile sifreleme
ansible-vault create group_vars/all/vault.yml

# Icerik ornegi:
# vault_slack_webhook: "https://hooks.slack.com/services/xxxxx/yyyyy/zzzzz"
# vault_pagerduty_token: "secure_token_here"

# Var olan dosyayi sifreleme
ansible-vault encrypt group_vars/production/secrets.yml

# Sifrelenmis dosyayi duzenleme
ansible-vault edit group_vars/production/secrets.yml

# Playbook'u vault ile calistirma
ansible-playbook update_packages.yml --ask-vault-pass

# Veya dosyadan okuma (CI/CD icin)
ansible-playbook update_packages.yml --vault-password-file ~/.vault_pass

Sık Karşılaşılan Sorunlar ve Çözümleri

Lock dosyası sorunu: Ubuntu/Debian sistemlerde bazen apt lock dosyası başka bir işlem tarafından tutulabilir.

- name: APT lock dosyasini kontrol et ve bekle
  shell: |
    while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
      echo "APT lock bekleniyor..."
      sleep 5
    done
  changed_when: false
  timeout: 120

Kernel güncelleme sonrası GRUB sorunları: Bazı sistemlerde kernel güncellemesi sonrası GRUB menüsü beklenmedik şekilde davranabilir. Bunu önlemek için:

- name: GRUB default'u aktif kernel'a sabitle
  lineinfile:
    path: /etc/default/grub
    regexp: '^GRUB_DEFAULT='
    line: 'GRUB_DEFAULT=0'
  notify: update grub

handlers:
  - name: update grub
    command: update-grub

Bant genişliği yönetimi: Çok sayıda sunucuyu aynı anda güncelliyorsanız ve bant genişliği bir endişeyse, apt-cacher-ng veya Nexus Repository gibi bir lokal proxy kurarak tüm paket indirmelerini bunun üzerinden yapabilirsiniz. Inventory’nizdeki sunuculara şu değişkeni tanımlayın:

# group_vars/all.yml
apt_proxy: "http://apt-proxy.internal:3142"
- name: APT proxy yapilandirmasi
  copy:
    content: |
      Acquire::http::Proxy "{{ apt_proxy }}";
    dest: /etc/apt/apt.conf.d/01proxy
  when: apt_proxy is defined

Sonuç

Ansible ile paket güncelleme otomasyonu, sistem yöneticiliğinin en temel ama aynı zamanda en etkisi büyük otomasyonlarından birini temsil ediyor. Başlangıçta basit bir apt upgrade çalıştırır gibi görünse de, production ortamlarında güvenli bir güncelleme süreci tasarlamak gerçekten ciddi bir düşünce gerektiriyor.

Bu yazıda ele aldıklarımızı özetlersek:

  • Temel inventory organizasyonu ve basit güncelleme playbook’ları
  • Kernel güncellemeleri sonrası akıllı reboot yönetimi
  • Load balancer entegrasyonlu rolling update stratejisi
  • Snapshot tabanlı güvenlik ağı kurma
  • Belirli paketleri sabitleme ve seçici güncelleme
  • Güncelleme sonrası raporlama ve bildirim sistemi
  • Systemd timer ile otomatik zamanlama
  • Vault ile hassas verilerin korunması

Öneri olarak söyleyeyim: Playbook’larınızı önce staging ortamında test edin, --check ve --diff parametrelerini bolca kullanın ve her şeyi Git’e koyun. Bir playbook değişikliğini commit mesajıyla birlikte takip etmek, bir sorun çıktığında altın değerinde olur. Ayrıca ilk rolling update’i production’da çalıştırmadan önce mutlaka birinin izlemesini tavsiye ederim, her ne kadar otomatik olsa da.

Sistem otomasyonu korkutucu görünebilir, ama doğru kurulmuş bir Ansible altyapısı gece 3’te uyandırmaz, tam tersine gece 3’te sizi rahat uyuturken sunucularınızı güncel tutar.

Yorum yapın