Ansible ile Log Yönetimi Otomasyonu
Prodüksiyon ortamlarında log yönetimi, çoğu zaman “nasıl olsa çalışıyor” diye ertelenen ama bir gün patlak veren sorunların başında gelir. Diskler dolup servisler çöker, eski loglar silinmez birikir, farklı sunuculardaki log konfigürasyonları birbirinden kopar. Ansible ile bu kaosa düzen getirmek hem mümkün hem de düşündüğünden çok daha kolay. Bu yazıda gerçek dünya senaryoları üzerinden Ansible ile kapsamlı bir log yönetimi otomasyonu kuracağız.
Neden Ansible ile Log Yönetimi?
Manuel log yönetiminin acısını yaşadıysan ne demek istediğimi anlarsın. 50 sunucuda logrotate konfigürasyonunu tek tek güncellemek, her sunucuda farklı log rotasyon sürelerinin oluşması, bazı makinelerde rsyslog’un farklı versiyonlarda olması… Bunlar gerçek sorunlar.
Ansible bu konuda şu avantajları getirir:
- Tekrarlanabilirlik: Aynı konfigürasyonu 5 veya 500 sunucuya aynı şekilde uygularsın
- Idempotency: Playbook’u defalarca çalıştırsan bile sonuç değişmez
- Versiyon kontrolü: Log konfigürasyonların Git’te yaşar, kim ne değiştirdi görebilirsin
- Hızlı rollback: Bir şeyler ters giderse önceki konfigürasyona dönmek birkaç saniye
- Merkezi yönetim: Tüm sunuculardaki log yapısını tek yerden kontrol edersin
Proje Yapısını Kuralım
Önce mantıklı bir dizin yapısı oluşturalım. Ansible’da düzensiz playbook yığını yerine role tabanlı yapı kullanmak uzun vadede hayat kurtarır.
mkdir -p ansible-log-management/{roles,inventories,group_vars,host_vars}
cd ansible-log-management
# Role yapısını oluştur
ansible-galaxy init roles/log_management
ansible-galaxy init roles/logrotate
ansible-galaxy init roles/rsyslog
ansible-galaxy init roles/log_shipper
Dizin yapımız şöyle görünmeli:
ansible-log-management/
├── site.yml
├── inventories/
│ ├── production/
│ │ ├── hosts
│ │ └── group_vars/
│ └── staging/
│ ├── hosts
│ └── group_vars/
├── group_vars/
│ └── all.yml
└── roles/
├── log_management/
├── logrotate/
├── rsyslog/
└── log_shipper/
Inventory ve Değişken Yapısı
Gerçek bir ortamda sunucularını mantıklı gruplara ayırmak kritik. Web sunucuları, veritabanı sunucuları ve uygulama sunucularının log ihtiyaçları birbirinden farklı.
# inventories/production/hosts
[webservers]
web01.example.com
web02.example.com
web03.example.com
[dbservers]
db01.example.com
db02.example.com
[appservers]
app01.example.com
app02.example.com
[all:vars]
ansible_user=ansible
ansible_become=yes
ansible_become_method=sudo
Global değişkenleri tanımlayalım:
# group_vars/all.yml
---
# Log rotasyon ayarları - tüm sunucular için defaults
log_rotate_frequency: daily
log_rotate_keep: 14
log_rotate_compress: true
log_rotate_missingok: true
log_rotate_notifempty: true
# Log dizinleri
log_base_dir: /var/log
custom_log_dir: /var/log/apps
# Rsyslog ayarları
rsyslog_remote_host: "logserver.example.com"
rsyslog_remote_port: 514
rsyslog_protocol: "tcp"
# Log seviyesi eşikleri
alert_log_size_mb: 500
critical_log_size_mb: 1000
# Logrotate binary yolu
logrotate_bin: /usr/sbin/logrotate
Web sunucuları için özel değişkenler:
# inventories/production/group_vars/webservers.yml
---
log_rotate_keep: 30
nginx_log_dir: /var/log/nginx
apache_log_dir: /var/log/apache2
nginx_logs:
- path: "{{ nginx_log_dir }}/access.log"
frequency: daily
keep: 30
postrotate: "nginx -s reopen"
- path: "{{ nginx_log_dir }}/error.log"
frequency: daily
keep: 60
postrotate: "nginx -s reopen"
Logrotate Role’ünü Yazalım
Logrotate yönetimi için template tabanlı bir yaklaşım kullanalım. Her servis için ayrı konfigürasyon dosyası oluşturacağız.
# roles/logrotate/tasks/main.yml
---
- name: Logrotate paketinin kurulu olduğunu doğrula
ansible.builtin.package:
name: logrotate
state: present
- name: Global logrotate konfigürasyonunu ayarla
ansible.builtin.template:
src: logrotate.conf.j2
dest: /etc/logrotate.conf
owner: root
group: root
mode: '0644'
notify: test logrotate config
- name: Uygulama log rotasyon konfigürasyonlarını oluştur
ansible.builtin.template:
src: app_logrotate.j2
dest: "/etc/logrotate.d/{{ item.name }}"
owner: root
group: root
mode: '0644'
loop: "{{ logrotate_configs }}"
when: logrotate_configs is defined
notify: test logrotate config
- name: Custom log dizininin varlığını garantile
ansible.builtin.file:
path: "{{ custom_log_dir }}"
state: directory
owner: root
group: adm
mode: '0755'
- name: Logrotate cron job'ının aktif olduğunu kontrol et
ansible.builtin.stat:
path: /etc/cron.daily/logrotate
register: logrotate_cron
- name: Logrotate cron job yoksa uyar
ansible.builtin.debug:
msg: "UYARI: Logrotate daily cron job bulunamadı!"
when: not logrotate_cron.stat.exists
Logrotate template’i:
# roles/logrotate/templates/logrotate.conf.j2
# Ansible tarafından yönetilmektedir - manuel değişiklik yapmayın
# Son güncelleme: {{ ansible_date_time.iso8601 }}
weekly
rotate {{ log_rotate_keep }}
{% if log_rotate_compress %}
compress
delaycompress
{% endif %}
{% if log_rotate_missingok %}
missingok
{% endif %}
{% if log_rotate_notifempty %}
notifempty
{% endif %}
create
dateext
dateformat -%Y%m%d
include /etc/logrotate.d
Uygulama bazlı logrotate template:
# roles/logrotate/templates/app_logrotate.j2
# {{ item.name }} için logrotate konfigürasyonu
# Ansible yönetimindedir
{{ item.log_path }} {
{{ item.frequency | default(log_rotate_frequency) }}
rotate {{ item.keep | default(log_rotate_keep) }}
{% if item.compress | default(log_rotate_compress) %}
compress
delaycompress
{% endif %}
missingok
notifempty
{% if item.create_mode is defined %}
create {{ item.create_mode }} {{ item.create_owner | default('root') }} {{ item.create_group | default('root') }}
{% endif %}
{% if item.postrotate is defined %}
postrotate
{{ item.postrotate }}
endscript
{% endif %}
{% if item.sharedscripts | default(false) %}
sharedscripts
{% endif %}
}
Handler ekleyelim:
# roles/logrotate/handlers/main.yml
---
- name: test logrotate config
ansible.builtin.command: "{{ logrotate_bin }} --debug /etc/logrotate.conf"
register: logrotate_test
changed_when: false
failed_when: logrotate_test.rc != 0
Rsyslog Role’ü
Merkezi log toplama için rsyslog konfigürasyonu kritik bir parça. Tüm sunuculardan logları merkezi bir noktaya yönlendireceğiz.
# roles/rsyslog/tasks/main.yml
---
- name: Rsyslog kurulumu
ansible.builtin.package:
name: rsyslog
state: present
- name: Rsyslog TLS için gerekli paketleri kur
ansible.builtin.package:
name:
- rsyslog-gnutls
- ca-certificates
state: present
when: rsyslog_use_tls | default(false)
- name: Rsyslog ana konfigürasyonunu yönet
ansible.builtin.template:
src: rsyslog.conf.j2
dest: /etc/rsyslog.conf
owner: root
group: root
mode: '0644'
backup: yes
notify:
- validate rsyslog config
- restart rsyslog
- name: Uygulama bazlı rsyslog konfigürasyonları
ansible.builtin.template:
src: rsyslog_app.conf.j2
dest: "/etc/rsyslog.d/{{ item.priority | default('50') }}-{{ item.name }}.conf"
owner: root
group: root
mode: '0644'
loop: "{{ rsyslog_app_configs | default([]) }}"
notify:
- validate rsyslog config
- restart rsyslog
- name: Rsyslog servisinin çalıştığından emin ol
ansible.builtin.service:
name: rsyslog
state: started
enabled: yes
- name: Rsyslog log dizin izinlerini düzelt
ansible.builtin.file:
path: "{{ log_base_dir }}"
state: directory
owner: root
group: adm
mode: '0755'
Rsyslog template:
# roles/rsyslog/templates/rsyslog.conf.j2
# Ansible yönetimindedir - {{ inventory_hostname }}
# Oluşturulma: {{ ansible_date_time.iso8601 }}
$ModLoad imuxsock
$ModLoad imklog
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
$FileOwner root
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022
$WorkDirectory /var/spool/rsyslog
# Uzak log sunucusuna yönlendirme
{% if rsyslog_remote_host is defined %}
{% if rsyslog_protocol == 'tcp' %}
*.* @@{{ rsyslog_remote_host }}:{{ rsyslog_remote_port }}
{% else %}
*.* @{{ rsyslog_remote_host }}:{{ rsyslog_remote_port }}
{% endif %}
{% endif %}
# Standart log hedefleri
auth,authpriv.* /var/log/auth.log
*.*;auth,authpriv.none -/var/log/syslog
kern.* -/var/log/kern.log
mail.* -/var/log/mail.log
mail.err /var/log/mail.err
$IncludeConfig /etc/rsyslog.d/*.conf
Disk Doluluk İzleme ve Otomatik Temizlik
Prodüksiyonda en sık yaşanan sorunlardan biri disk dolması. Bunu proaktif olarak yönetelim.
# roles/log_management/tasks/disk_management.yml
---
- name: Log dizini disk kullanımını kontrol et
ansible.builtin.shell: |
df -m {{ log_base_dir }} | awk 'NR==2 {print $5}' | tr -d '%'
register: log_disk_usage
changed_when: false
- name: Disk kullanım durumunu kaydet
ansible.builtin.set_fact:
current_disk_usage: "{{ log_disk_usage.stdout | int }}"
- name: Yüksek disk kullanımı uyarısı
ansible.builtin.debug:
msg: |
UYARI: {{ inventory_hostname }} üzerinde log dizini disk kullanımı %{{ current_disk_usage }}
Eşik değeri: %{{ disk_usage_warning_threshold | default(75) }}
when: current_disk_usage | int >= (disk_usage_warning_threshold | default(75)) | int
- name: Kritik disk kullanımında eski logları temizle
ansible.builtin.find:
paths: "{{ log_base_dir }}"
patterns: "*.log.*.gz"
age: "{{ emergency_cleanup_age | default('30d') }}"
recurse: yes
register: old_compressed_logs
when: current_disk_usage | int >= (disk_usage_critical_threshold | default(90)) | int
- name: Eski sıkıştırılmış logları kaldır
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_compressed_logs.files | default([]) }}"
when:
- current_disk_usage | int >= (disk_usage_critical_threshold | default(90)) | int
- old_compressed_logs.files is defined
register: cleanup_result
- name: Temizlik sonucu raporu
ansible.builtin.debug:
msg: "{{ cleanup_result.results | length }} adet eski log dosyası silindi"
when:
- cleanup_result is defined
- cleanup_result.results is defined
Log Arşivleme ve S3’e Yükleme
Uzun dönem saklama için logları S3 veya uyumlu bir obje deposuna atalım.
# roles/log_management/tasks/archive_logs.yml
---
- name: AWS CLI kurulumunu kontrol et
ansible.builtin.command: which aws
register: aws_cli_check
changed_when: false
failed_when: false
- name: Log arşiv dizinini oluştur
ansible.builtin.file:
path: "{{ log_archive_dir | default('/var/log/archive') }}"
state: directory
owner: root
group: root
mode: '0750'
- name: Eski logları arşiv dizinine taşı
ansible.builtin.find:
paths: "{{ log_base_dir }}"
patterns: "*.log.*.gz"
age: "{{ archive_after_days | default('7d') }}"
recurse: yes
register: logs_to_archive
- name: Arşiv script'ini oluştur
ansible.builtin.template:
src: archive_logs.sh.j2
dest: /usr/local/bin/archive_logs.sh
owner: root
group: root
mode: '0750'
- name: Haftalık arşiv cron job'ı kur
ansible.builtin.cron:
name: "Log arşivleme - {{ inventory_hostname }}"
minute: "0"
hour: "2"
weekday: "0"
job: "/usr/local/bin/archive_logs.sh >> /var/log/archive_logs.log 2>&1"
user: root
state: present
Arşiv script template’i:
# roles/log_management/templates/archive_logs.sh.j2
#!/bin/bash
# Ansible tarafından oluşturulmuştur - {{ ansible_date_time.iso8601 }}
# Log arşivleme scripti - {{ inventory_hostname }}
set -euo pipefail
LOG_DIR="{{ log_base_dir }}"
ARCHIVE_DIR="{{ log_archive_dir | default('/var/log/archive') }}"
S3_BUCKET="{{ s3_log_bucket | default('') }}"
HOSTNAME="{{ inventory_hostname }}"
DATE=$(date +%Y%m%d)
RETENTION_DAYS="{{ local_archive_retention | default(30) }}"
echo "[$(date)] Arşivleme başladı"
# Arşiv oluştur
ARCHIVE_FILE="${ARCHIVE_DIR}/${HOSTNAME}_logs_${DATE}.tar.gz"
find "${LOG_DIR}" -name "*.log.*" -mtime +7 -print0 |
tar czf "${ARCHIVE_FILE}" --null -T - 2>/dev/null || true
if [ -f "${ARCHIVE_FILE}" ]; then
echo "[$(date)] Arşiv oluşturuldu: ${ARCHIVE_FILE}"
# S3'e yükle (eğer bucket tanımlıysa)
if [ -n "${S3_BUCKET}" ]; then
aws s3 cp "${ARCHIVE_FILE}"
"s3://${S3_BUCKET}/logs/${HOSTNAME}/${DATE}/"
--storage-class STANDARD_IA
echo "[$(date)] S3'e yükleme tamamlandı"
fi
# Eski arşivleri temizle
find "${ARCHIVE_DIR}" -name "*.tar.gz" -mtime +"${RETENTION_DAYS}" -delete
echo "[$(date)] Eski arşivler temizlendi"
fi
echo "[$(date)] Arşivleme tamamlandı"
Ana Playbook’u Bir Araya Getirelim
# site.yml
---
- name: Tüm sunuculara temel log yönetimi uygula
hosts: all
become: yes
roles:
- log_management
- logrotate
- rsyslog
- name: Web sunucuları için özel log konfigürasyonu
hosts: webservers
become: yes
vars:
logrotate_configs:
- name: nginx
log_path: /var/log/nginx/*.log
frequency: daily
keep: 30
compress: true
postrotate: "[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)"
sharedscripts: true
- name: apache2
log_path: /var/log/apache2/*.log
frequency: daily
keep: 14
compress: true
postrotate: "apache2ctl graceful"
sharedscripts: true
roles:
- logrotate
- name: Veritabanı sunucuları için log ayarları
hosts: dbservers
become: yes
vars:
log_rotate_keep: 60
logrotate_configs:
- name: postgresql
log_path: /var/log/postgresql/*.log
frequency: weekly
keep: 12
compress: true
create_mode: "0640"
create_owner: postgres
create_group: postgres
- name: mysql
log_path: /var/log/mysql/*.log
frequency: daily
keep: 30
compress: true
create_mode: "0640"
create_owner: mysql
create_group: adm
roles:
- logrotate
Log Konfigürasyon Doğrulama Playbook’u
Uyguladıktan sonra her şeyin düzgün çalıştığını doğrulamak için ayrı bir playbook kullanmak iyi pratik.
# verify_logs.yml
---
- name: Log yönetimi konfigürasyonunu doğrula
hosts: all
become: yes
gather_facts: yes
tasks:
- name: Logrotate konfigürasyonunu test et
ansible.builtin.command: "logrotate --debug /etc/logrotate.conf"
register: logrotate_verify
changed_when: false
failed_when: false
- name: Logrotate test sonuçlarını göster
ansible.builtin.debug:
msg: "{{ inventory_hostname }}: Logrotate {{ 'OK' if logrotate_verify.rc == 0 else 'HATA' }}"
- name: Rsyslog konfigürasyonunu doğrula
ansible.builtin.command: rsyslogd -N1
register: rsyslog_verify
changed_when: false
failed_when: false
- name: Kritik log dosyalarının var olduğunu kontrol et
ansible.builtin.stat:
path: "{{ item }}"
loop:
- /var/log/syslog
- /var/log/auth.log
- /etc/logrotate.conf
register: critical_log_files
- name: Eksik kritik log dosyalarını raporla
ansible.builtin.debug:
msg: "EKSIK: {{ item.item }}"
loop: "{{ critical_log_files.results }}"
when: not item.stat.exists
- name: Disk durumu özeti
ansible.builtin.shell: df -h {{ log_base_dir }} | tail -1
register: disk_status
changed_when: false
- name: Disk durumunu göster
ansible.builtin.debug:
msg: "{{ inventory_hostname }} disk durumu: {{ disk_status.stdout }}"
CI/CD Pipeline ile Entegrasyon
Ansible playbook’larını GitLab CI veya GitHub Actions ile entegre etmek, log konfigürasyon değişikliklerinin otomatik deploy edilmesini sağlar.
# .gitlab-ci.yml
stages:
- lint
- test
- deploy-staging
- verify-staging
- deploy-production
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
ANSIBLE_STDOUT_CALLBACK: "yaml"
ansible-lint:
stage: lint
image: cytopia/ansible-lint
script:
- ansible-lint site.yml
only:
- merge_requests
- main
syntax-check:
stage: test
image: willhallonline/ansible:latest
script:
- ansible-playbook site.yml --syntax-check -i inventories/staging/hosts
only:
- merge_requests
- main
deploy-staging:
stage: deploy-staging
image: willhallonline/ansible:latest
script:
- ansible-playbook site.yml
-i inventories/staging/hosts
--vault-password-file /run/secrets/vault_pass
-e "env=staging"
environment:
name: staging
only:
- main
verify-staging:
stage: verify-staging
image: willhallonline/ansible:latest
script:
- ansible-playbook verify_logs.yml
-i inventories/staging/hosts
--vault-password-file /run/secrets/vault_pass
only:
- main
deploy-production:
stage: deploy-production
image: willhallonline/ansible:latest
script:
- ansible-playbook site.yml
-i inventories/production/hosts
--vault-password-file /run/secrets/vault_pass
-e "env=production"
environment:
name: production
when: manual
only:
- main
Ansible Vault ile Hassas Verileri Koruma
Log sunucusu adresi, S3 kimlik bilgileri gibi hassas verileri düz metin bırakmak olmaz.
# Vault şifreli değişken dosyası oluştur
ansible-vault create group_vars/all/vault.yml
# İçerik (şifrelenmeden önce):
# vault_rsyslog_remote_host: "logs.internal.example.com"
# vault_s3_access_key: "AKIAIOSFODNN7EXAMPLE"
# vault_s3_secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# vault_s3_log_bucket: "company-prod-logs"
# group_vars/all.yml içinde vault değişkenlerine referans ver:
rsyslog_remote_host: "{{ vault_rsyslog_remote_host }}"
s3_access_key: "{{ vault_s3_access_key }}"
s3_secret_key: "{{ vault_s3_secret_key }}"
s3_log_bucket: "{{ vault_s3_log_bucket }}"
# Playbook'u vault ile çalıştır
ansible-playbook site.yml -i inventories/production/hosts --ask-vault-pass
# Veya vault password dosyası ile
ansible-playbook site.yml -i inventories/production/hosts --vault-password-file ~/.vault_pass
Pratik İpuçları ve Yaygın Hatalar
Gerçek ortamlarda bu kurulumu yaparken karşılaşacağın birkaç önemli noktayı paylaşayım.
Check mode kullanımını alışkanlık edin: Değişiklikleri uygulamadan önce her zaman --check ile ne değişeceğini gör. Özellikle prodüksiyon ortamlarında bu alışkanlık seni ciddi sorunlardan kurtarır.
Backup direktifini unutma: Rsyslog ve logrotate template’lerinde backup: yes parametresi kritik. Ansible konfigürasyon dosyasını değiştirmeden önce yedek alır, geri dönüşünü kolaylaştırır.
Servis restart’larına dikkat: Log konfigürasyon değişikliklerinde rsyslog’u restart etmek yerine reload tercih et. Restart sırasında kısa bir süre log kaybı yaşanabilir.
Logrotate test alışkanlığı: Her deploy sonrası logrotate --debug /etc/logrotate.conf çalıştırarak konfigürasyonun geçerli olduğunu doğrula. Handler’a bu testi eklemek bunu otomatik hale getirir.
İzin sorunlarına karşı dikkatli ol: Farklı Linux dağıtımlarında log dosyası izinleri değişebilir. Ubuntu’da adm grubu, CentOS/RHEL’de farklı gruplar kullanılır. ansible_os_family değişkenini kullanarak dağıtım bazlı ayarlar yapabilirsin.
Idempotency testleri yap: Playbook’unu arka arkaya iki kez çalıştır. İkinci çalıştırmada hiçbir değişiklik olmamalı. Değişiklik görüyorsan bir şeyler yanlış.
Sonuç
Ansible ile log yönetimini otomatize etmek, başlangıçta biraz zaman alsa da uzun vadede ciddi bir zaman ve stres tasarrufu sağlar. Bu yazıda ele aldığımız yaklaşımla şunları elde etmiş oldun:
- Tüm sunucularda tutarlı logrotate konfigürasyonu
- Merkezi log toplama için otomatize rsyslog ayarları
- Disk doluluk izleme ve proaktif temizlik mekanizması
- Uzun dönem arşivleme için S3 entegrasyonu
- CI/CD pipeline’a entegre edilmiş, kod review sürecinden geçen log yönetimi
En önemli nokta şu: Log yönetimini bir kez doğru kurduğunda saat 3’te “disk doldu, servis çöktü” alarmıyla uyanmazsın. Ve “bu sunucuya logrotate kurmuştum ama şunun konfigürasyonu eksik kalmış” gibi durumlar ortadan kalkar.
Yapıyı kendi ortamına uyarlarken önce staging’de test et, sonra prodüksiyona geç. Ve her değişikliği Git’e commit’le, gelecekteki sen sana teşekkür eder.
