Ansible ile Otomatik Güvenlik Güncellemeleri
Güvenlik güncellemelerini manuel olarak yapmak, özellikle onlarca veya yüzlerce sunucu yönettiğinizde gerçek bir kabus haline gelebilir. Bir sunucuyu güncelledin, öbürünü unuttun, birinde bağımlılık sorunu çıktı… Bu kaotik sürecin tam ortasında bir güvenlik açığı haberi geliyor ve panikle ne yapacağını şaşırıyorsun. Ansible tam da bu noktada devreye giriyor. Playbook’lar sayesinde güvenlik güncellemelerini standartlaştırabilir, otomatize edebilir ve tüm altyapını tutarlı bir şekilde güncel tutabilirsin.
Neden Ansible ile Güvenlik Güncellemeleri?
Geleneksel yaklaşımda her sunucuya SSH ile bağlanıp apt upgrade veya yum update komutu çalıştırıyordun. Bu yöntemin en büyük problemi tekrarlanabilirlik ve izlenebilirlik eksikliği. Kim ne zaman hangi sunucuyu güncelledi? Hangi paketler güncellendi? Güncelleme sonrası servis yeniden başlatıldı mı?
Ansible bu sorulara net yanıtlar vermeni sağlıyor:
- Idempotency: Aynı playbook’u kaç kez çalıştırırsan çalıştır, sonuç aynı olur
- Merkezi yönetim: Tüm altyapını tek bir yerden yönetirsin
- Versiyon kontrolü: Playbook’ların Git’te duruyor, kim ne değiştirdi görünür
- Rollback imkânı: Bir şeyler ters giderse önceki duruma dönmek kolaylaşır
- Raporlama: Her çalışmanın detaylı logu var
Ortam Hazırlığı ve Inventory Yapılandırması
Önce sağlıklı bir inventory yapısı kurman lazım. Üretim, staging ve geliştirme ortamlarını birbirinden ayırmak, güvenlik güncellemelerini önce staging’de test etmeni sağlar.
# /etc/ansible/inventory/hosts dosyası
[production_web]
web01.ornek.com
web02.ornek.com
web03.ornek.com
[production_db]
db01.ornek.com ansible_user=dbadmin
[staging]
staging01.ornek.com
staging02.ornek.com
[all:vars]
ansible_user=ansible
ansible_ssh_private_key_file=/home/ansible/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
Grup değişkenlerini ayrı dosyalarda tutmak daha temiz bir yapı sağlar:
# group_vars/production_web.yml
ansible_become: true
ansible_become_method: sudo
reboot_required: true
update_cache_timeout: 3600
Temel Güvenlik Güncelleme Playbook’u
İlk playbook’umuz basit ama etkili. Sadece güvenlik yamalarını uyguluyor, gereksiz paketlere dokunmuyor.
# security-updates.yml
---
- name: Otomatik Güvenlik Güncellemeleri
hosts: "{{ target_hosts | default('all') }}"
become: true
serial: "{{ batch_size | default('30%') }}"
vars:
log_dir: /var/log/ansible-updates
pre_tasks:
- name: Log dizinini oluştur
file:
path: "{{ log_dir }}"
state: directory
mode: '0755'
- name: Güncelleme başlangıç zamanını kaydet
set_fact:
update_start_time: "{{ ansible_date_time.iso8601 }}"
tasks:
- name: APT cache güncelle (Debian/Ubuntu)
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Güvenlik güncellemelerini uygula (Debian/Ubuntu)
apt:
upgrade: dist
autoremove: yes
autoclean: yes
register: apt_update_result
when: ansible_os_family == "Debian"
- name: Güvenlik güncellemelerini uygula (RHEL/CentOS)
yum:
name: '*'
security: yes
state: latest
update_cache: yes
register: yum_update_result
when: ansible_os_family == "RedHat"
- name: Yeniden başlatma gerekiyor mu kontrol et
stat:
path: /var/run/reboot-required
register: reboot_required_file
when: ansible_os_family == "Debian"
post_tasks:
- name: Güncelleme sonucunu logla
copy:
content: |
Başlangıç: {{ update_start_time }}
Bitiş: {{ ansible_date_time.iso8601 }}
Sunucu: {{ inventory_hostname }}
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
dest: "{{ log_dir }}/update-{{ ansible_date_time.date }}.log"
Burada serial: "30%" parametresine dikkat et. Bu parametre sunucuların yüzde otuzunu aynı anda günceller. Tüm sunucuları aynı anda güncellemek yerine kademeli ilerliyoruz, böylece bir sorun çıkarsa tüm altyapı etkilenmiyor.
Reboot Yönetimi
Kernel güncellemeleri sonrası sunucuların yeniden başlatılması gerekiyor. Ama bunu kontrollü yapmak şart. Üretim saatlerinde toplu reboot yapamazsın.
# reboot-handler.yml
---
- name: Kontrollü Reboot Yönetimi
hosts: "{{ target_hosts }}"
become: true
vars:
reboot_timeout: 300
maintenance_window: "02:00-04:00"
tasks:
- name: Reboot gerekli mi kontrol et (Debian)
stat:
path: /var/run/reboot-required
register: debian_reboot_check
when: ansible_os_family == "Debian"
- name: Reboot gerekli mi kontrol et (RHEL)
command: needs-restarting -r
register: rhel_reboot_check
changed_when: false
failed_when: false
when: ansible_os_family == "RedHat"
- name: Yük dengeleyiciden çıkar (HAProxy)
haproxy:
state: disabled
host: "{{ inventory_hostname }}"
socket: /var/run/haproxy/admin.sock
wait: yes
delegate_to: "{{ haproxy_server }}"
when:
- haproxy_server is defined
- (debian_reboot_check.stat.exists | default(false)) or
(rhel_reboot_check.rc | default(1) == 1)
- name: Sunucuyu yeniden başlat
reboot:
msg: "Ansible güvenlik güncellemesi sonrası reboot"
connect_timeout: 5
reboot_timeout: "{{ reboot_timeout }}"
pre_reboot_delay: 10
post_reboot_delay: 30
test_command: whoami
when:
- (debian_reboot_check.stat.exists | default(false)) or
(rhel_reboot_check.rc | default(1) == 1)
- name: Yük dengeleyiciye geri ekle
haproxy:
state: enabled
host: "{{ inventory_hostname }}"
socket: /var/run/haproxy/admin.sock
wait: yes
wait_interval: 5
delegate_to: "{{ haproxy_server }}"
when: haproxy_server is defined
Gerçek Dünya Senaryosu: Kritik CVE Müdahalesi
2021’deki Log4Shell açığını hatırlıyorsundur. Binlerce sistem etkilendi ve sistem yöneticileri günlerce uyumadan yamalar uyguladı. Ansible’ın olduğu bir ortamda bu süreç çok daha yönetilebilir hale gelir.
Diyelim ki kritik bir CVE duyurusu geldi ve sadece belirli bir paketi acil güncellemeniz gerekiyor:
# critical-cve-patch.yml
---
- name: Kritik CVE Yaması - Log4j Örneği
hosts: production_web:production_app
become: true
serial: 1
max_fail_percentage: 0
vars:
cve_id: "CVE-2021-44228"
affected_package: "log4j2"
notification_email: "[email protected]"
pre_tasks:
- name: Acil bildirim gönder
mail:
to: "{{ notification_email }}"
subject: "{{ cve_id }} yaması başlıyor - {{ inventory_hostname }}"
body: "{{ inventory_hostname }} sunucusunda {{ cve_id }} yaması uygulanmaya başlandı."
delegate_to: localhost
ignore_errors: yes
- name: Mevcut paket versiyonunu kaydet
command: "dpkg -l | grep -i log4j"
register: pre_patch_version
changed_when: false
failed_when: false
tasks:
- name: Paket listesini güncelle
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: Etkilenen paketi güncelle
apt:
name: "{{ affected_package }}"
state: latest
register: patch_result
when: ansible_os_family == "Debian"
- name: Java uygulamalarını yeniden başlat
systemd:
name: "{{ item }}"
state: restarted
loop:
- tomcat9
- elasticsearch
- logstash
ignore_errors: yes
when: patch_result.changed
- name: Patch sonrası versiyon kontrolü
command: "dpkg -l | grep -i log4j"
register: post_patch_version
changed_when: false
failed_when: false
post_tasks:
- name: Tamamlanma bildirimi
mail:
to: "{{ notification_email }}"
subject: "{{ cve_id }} yaması tamamlandı - {{ inventory_hostname }}"
body: |
Önceki versiyon: {{ pre_patch_version.stdout }}
Yeni versiyon: {{ post_patch_version.stdout }}
Durum: Başarılı
delegate_to: localhost
ignore_errors: yes
Role Tabanlı Yapı
Playbook’larını büyüdükçe bir role yapısına taşıman gerekiyor. security-updates adında bir role oluşturalım:
# Role dizin yapısı
ansible-galaxy init security-updates
# Oluşan yapı:
# security-updates/
# ├── tasks/
# │ ├── main.yml
# │ ├── debian.yml
# │ ├── redhat.yml
# │ └── post_update.yml
# ├── handlers/
# │ └── main.yml
# ├── vars/
# │ └── main.yml
# ├── defaults/
# │ └── main.yml
# └── templates/
# └── update_report.j2
# security-updates/tasks/main.yml
---
- name: OS ailesine göre güncelleme yap
include_tasks: "{{ ansible_os_family | lower }}.yml"
- name: Post-update kontrolleri
include_tasks: post_update.yml
# security-updates/tasks/debian.yml
---
- name: APT güvenlik güncellemeleri
block:
- name: Cache güncelle
apt:
update_cache: yes
cache_valid_time: "{{ cache_valid_time }}"
- name: Sadece güvenlik güncellemelerini filtrele
shell: |
apt-get --just-print upgrade 2>&1 |
grep -i "security" |
awk '{print $2}' |
sort -u
register: security_packages
changed_when: false
- name: Güvenlik paketlerini güncelle
apt:
name: "{{ security_packages.stdout_lines }}"
state: latest
when: security_packages.stdout_lines | length > 0
register: debian_update_result
notify: restart_services
rescue:
- name: Hata bildir
debug:
msg: "Güncelleme sırasında hata: {{ ansible_failed_result }}"
Handler’ları da düzgün tanımlamak önemli:
# security-updates/handlers/main.yml
---
- name: restart_services
systemd:
name: "{{ item }}"
state: restarted
loop: "{{ services_to_restart | default([]) }}"
when: services_to_restart is defined
- name: reload_nginx
systemd:
name: nginx
state: reloaded
- name: reload_apache
systemd:
name: apache2
state: reloaded
CI/CD Pipeline Entegrasyonu
GitLab CI veya Jenkins ile Ansible playbook’larını otomatik tetikleyebilirsin. Şöyle bir senaryo düşün: Her Salı gecesi saat 02:00’de otomatik güvenlik güncellemesi çalışsın, log dosyaları toplanıp güvenlik ekibine rapor gönderilsin.
# .gitlab-ci.yml
stages:
- validate
- staging-update
- production-update
- report
variables:
ANSIBLE_FORCE_COLOR: "1"
ANSIBLE_HOST_KEY_CHECKING: "False"
validate-playbook:
stage: validate
script:
- ansible-playbook --syntax-check security-updates.yml
- ansible-lint security-updates.yml
only:
- merge_requests
- main
staging-security-update:
stage: staging-update
script:
- ansible-playbook -i inventory/staging security-updates.yml
--extra-vars "target_hosts=staging"
--extra-vars "batch_size=50%"
environment:
name: staging
only:
- schedules
- main
production-security-update:
stage: production-update
script:
- ansible-playbook -i inventory/production security-updates.yml
--extra-vars "target_hosts=all"
--extra-vars "batch_size=20%"
environment:
name: production
when: manual
only:
- main
generate-report:
stage: report
script:
- ansible-playbook -i inventory/production collect-update-report.yml
artifacts:
paths:
- reports/
expire_in: 30 days
only:
- schedules
Güncelleme Öncesi ve Sonrası Doğrulama
Güncellemeden sonra servislerin hala çalışıp çalışmadığını kontrol etmek kritik. Özellikle web sunucuları için:
# verification-tasks.yml
---
- name: Güncelleme Sonrası Doğrulama
hosts: production_web
become: true
vars:
health_check_urls:
- "http://localhost:80/health"
- "http://localhost:8080/actuator/health"
expected_services:
- nginx
- php8.1-fpm
- mysql
tasks:
- name: Kritik servislerin durumunu kontrol et
systemd:
name: "{{ item }}"
register: service_status
loop: "{{ expected_services }}"
- name: Çalışmayan servisleri raporla
debug:
msg: "UYARI: {{ item.item }} servisi çalışmıyor!"
loop: "{{ service_status.results }}"
when: item.status.ActiveState != "active"
- name: HTTP health check
uri:
url: "{{ item }}"
method: GET
status_code: 200
timeout: 10
loop: "{{ health_check_urls }}"
register: health_check_result
ignore_errors: yes
retries: 3
delay: 10
- name: Health check başarısız olan URL'leri raporla
debug:
msg: "KRITIK: {{ item.item }} yanıt vermiyor!"
loop: "{{ health_check_result.results }}"
when: item.failed | default(false)
- name: Disk kullanımını kontrol et
shell: df -h / | awk 'NR==2 {print $5}' | tr -d '%'
register: disk_usage
changed_when: false
- name: Disk doluluk uyarısı
fail:
msg: "UYARI: Disk doluluk oranı %{{ disk_usage.stdout }} - güncelleme sonrası temizlik gerekebilir!"
when: disk_usage.stdout | int > 85
Ansible Vault ile Hassas Bilgilerin Korunması
Güvenlik güncellemeleri için kullanılan playbook’larda veritabanı şifreleri, API anahtarları gibi hassas bilgiler olabilir. Bunları asla plain text olarak tutma:
# Vault şifreli değişken dosyası oluştur
ansible-vault create group_vars/production/vault.yml
# İçeriği şifrele
ansible-vault encrypt_string 'super-gizli-sifre' --name 'db_password'
# Playbook'u vault şifresiyle çalıştır
ansible-playbook security-updates.yml --vault-password-file ~/.vault_pass
# Vault şifresini environment variable'dan al
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
ansible-playbook security-updates.yml
# group_vars/production/vault.yml (şifreli dosya içeriği örneği)
vault_notification_api_key: "sk-prod-xxxxxxxxxxxx"
vault_slack_webhook: "https://hooks.slack.com/services/xxx/yyy/zzz"
vault_db_password: "gizli-sifre-buraya"
Loglama ve Raporlama Altyapısı
Güvenlik güncellemelerinin izlenebilir olması şart. Bir Jinja2 template ile güzel bir HTML raporu oluşturabilirsin:
# templates/update_report.j2
<!DOCTYPE html>
<html>
<head><title>Güvenlik Güncelleme Raporu - {{ ansible_date_time.date }}</title></head>
<body>
<h1>Güvenlik Güncelleme Raporu</h1>
<p>Tarih: {{ ansible_date_time.iso8601 }}</p>
<h2>Güncellenen Sunucular</h2>
{% for host in ansible_play_hosts %}
<div class="host">
<h3>{{ host }}</h3>
<p>OS: {{ hostvars[host]['ansible_distribution'] }}</p>
<p>Durum: {% if hostvars[host]['update_result'].changed %}Güncellendi{% else %}Güncel{% endif %}</p>
</div>
{% endfor %}
</body>
</html>
Slack bildirimleri de ekleyebilirsin:
# Slack bildirimi görevi
- name: Slack bildirimi gönder
uri:
url: "{{ vault_slack_webhook }}"
method: POST
body_format: json
body:
text: ":shield: *Güvenlik Güncellemesi Tamamlandı*"
attachments:
- color: "good"
fields:
- title: "Güncellenen Sunucu Sayısı"
value: "{{ ansible_play_hosts | length }}"
short: true
- title: "Tarih"
value: "{{ ansible_date_time.iso8601 }}"
short: true
delegate_to: localhost
run_once: true
Yaygın Hatalar ve Çözümleri
Ansible ile güvenlik güncellemesi yaparken sık karşılaşılan sorunlar:
- SSH bağlantı zaman aşımı:
ansible.cfgdosyasındatimeout = 30vessh_args = -o ConnectTimeout=30 -o ServerAliveInterval=60ekle - Sudo şifre problemi:
ansible_become_passworddeğişkenini vault’ta sakla, playbook’dabecome: truekullan - Paket bağımlılık çakışmaları:
apt-get -f installile önce bağımlılıkları düzelt, bunu bir pre_task olarak ekle - Reboot sonrası bağlantı sorunu:
rebootmodülününpost_reboot_delayparametresini artır, 30-60 saniye yeterli - Kısmi güncelleme sorunu:
max_fail_percentage: 20ile belirli bir hata eşiğinde playbook’u durdur
Düzenli Çalışma İçin Cron Entegrasyonu
GitLab CI olmayan ortamlar için doğrudan cron ile de çalıştırabilirsin:
# /etc/cron.d/ansible-security-updates
# Her Salı ve Cuma saat 02:30'da çalış
30 2 * * 2,5 ansible /usr/bin/ansible-playbook
-i /etc/ansible/inventory/hosts
/opt/ansible/playbooks/security-updates.yml
--extra-vars "target_hosts=all"
--vault-password-file /root/.vault_pass
>> /var/log/ansible-security-updates.log 2>&1
Sonuç
Ansible ile güvenlik güncellemelerini otomatize etmek, başlangıçta biraz setup gerektiriyor ama bu yatırım kısa sürede kendini geri ödüyor. Onlarca sunucuyu tek tek güncelleme derdi ortadan kalkıyor, insan hatası minimuma iniyor ve her güncellemenin izi Git’te ve log dosyalarında kalıyor.
En kritik nokta şu: Playbook’larını önce staging ortamında test et, sonra üretim ortamına geçir. serial parametresiyle kademeli güncelleme yap, max_fail_percentage ile hata eşiği belirle. Reboot gerektiren güncellemeleri bakım pencerelerinde planla.
Bu yazıda anlattıklarımı uyguladıktan sonra güvenlik güncellemelerinin artık seni korkutmadığını göreceksin. Bir CVE duyurusu geldiğinde “Nasıl yapacağım?” yerine “Playbook’u çalıştır, bitsin gitsin” diyebileceksin. Ve bu duygu gerçekten paha biçilmez.
Sorularını ve senaryolarını yorumlara yazabilirsin, elimden geldiğince yanıtlamaya çalışırım.
