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.