Postfix ile Yüksek Erişilebilirlik Kurulumu

Mail altyapısı, bir organizasyonun en kritik bileşenlerinden biridir. Postfix’in tek bir sunucuda çalıştığı bir kurulumda, o sunucu çöktüğünde tüm mail akışı durur. Müşteri bildirimleri gitmez, siparişler onaylanmaz, güvenlik uyarıları iletilmez. Bu senaryo, özellikle e-ticaret ve SaaS şirketleri için kabul edilemez bir durumdur. Bu yazıda, Postfix ile gerçek anlamda yüksek erişilebilirlik sağlayan bir mimari kuracağız; aktif-aktif yapılandırma, shared storage, Keepalived ile VIP yönetimi ve otomatik failover mekanizmalarını adım adım ele alacağız.

Mimariyi Anlamak

Yüksek erişilebilirlik (HA) kurulumunda hedefimiz tek hata noktasını (single point of failure) ortadan kaldırmaktır. Temel mimarimiz şu bileşenlerden oluşacak:

  • İki Postfix sunucusu: mail1.ornek.com ve mail2.ornek.com
  • Keepalived ile Virtual IP (VIP): 192.168.1.100 adresi aktif sunucuya yönlenir
  • Paylaşımlı mail kuyruğu: NFS veya GlusterFS üzerinde
  • MySQL ile ortak konfigürasyon veritabanı: Sanal kullanıcılar ve domain bilgileri
  • Rsync tabanlı konfigürasyon senkronizasyonu: Gerçek zamanlı config eşitleme

Mail1 aktif, mail2 pasif olarak başlar. Mail1 çöktüğünde Keepalived devreye girer, VIP mail2’ye taşınır ve mail akışı kesintisiz devam eder. Failback işlemi manuel veya otomatik yapılabilir.

Sunucu Hazırlığı

Her iki sunucuda da aşağıdaki adımları uygulayın. Örneklerimiz Ubuntu 22.04 LTS üzerinde yapılacak.

# Her iki sunucuda çalıştır
hostnamectl set-hostname mail1.ornek.com   # mail2 için mail2.ornek.com
apt update && apt upgrade -y
apt install -y postfix postfix-mysql keepalived rsync nfs-common 
    mysql-client libsasl2-modules sasl2-bin mailutils

# /etc/hosts dosyasını güncelle
cat >> /etc/hosts << 'EOF'
192.168.1.101 mail1.ornek.com mail1
192.168.1.102 mail2.ornek.com mail2
192.168.1.100 mail.ornek.com mail
EOF

Postfix kurulumu sırasında “Internet Site” seçeneğini seçin ve sistem mail adı olarak mail.ornek.com yazın. Bu isim daha sonra main.cf içinde override edilecek, ancak kurulum için gerekli.

NFS ile Paylaşımlı Mail Kuyruğu

Mail kuyruğunu paylaşmak, her iki sunucunun da aynı kuyruktaki mailleri işleyebilmesi için kritiktir. Bir NFS sunucusu (veya mevcut depolama altyapınız) üzerinde bu dizini paylaşıyoruz.

# NFS sunucusunda (ayrı bir storage sunucusu veya mail1 üzerinde)
apt install -y nfs-kernel-server
mkdir -p /export/mailqueue
chown -R postfix:postfix /export/mailqueue
chmod 700 /export/mailqueue

cat >> /etc/exports << 'EOF'
/export/mailqueue 192.168.1.101(rw,sync,no_subtree_check,no_root_squash)
/export/mailqueue 192.168.1.102(rw,sync,no_subtree_check,no_root_squash)
EOF

exportfs -ra
systemctl enable --now nfs-kernel-server

Her iki mail sunucusunda NFS mount işlemini yapılandırın:

# Her iki mail sunucusunda
mkdir -p /var/spool/postfix/deferred-shared

cat >> /etc/fstab << 'EOF'
storage.ornek.com:/export/mailqueue /var/spool/postfix/deferred-shared nfs 
    rw,sync,hard,intr,rsize=8192,wsize=8192,timeo=14 0 0
EOF

mount -a

# Mount kontrolü
df -h | grep mailqueue

Önemli not: Aktif-aktif bir kuyruk paylaşımında lock mekanizmasına dikkat etmek gerekir. Aynı maili iki sunucu aynı anda işlemeye çalışırsa sorun çıkabilir. Bu yüzden tipik HA kurulumlarında kuyruk VIP ile birlikte taşınır; aktif olmayan sunucu o kuyruğa dokunmaz.

MySQL ile Ortak Konfigürasyon

Sanal kullanıcı ve domain bilgilerini her iki sunucunun da okuyabileceği ortak bir MySQL veritabanında tutacağız. MySQL Replication veya Galera Cluster kullanmanızı tavsiye ederim, ancak bu yazıda tek bir MySQL sunucusunu referans alıyoruz.

-- MySQL sunucusunda çalıştır
CREATE DATABASE mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'mailuser'@'192.168.1.%' IDENTIFIED BY 'GucluSifre123!';
GRANT SELECT ON mailserver.* TO 'mailuser'@'192.168.1.%';
FLUSH PRIVILEGES;

USE mailserver;

CREATE TABLE virtual_domains (
    id INT AUTO_INCREMENT PRIMARY KEY,
    domain VARCHAR(255) NOT NULL UNIQUE,
    aktif TINYINT(1) DEFAULT 1
);

CREATE TABLE virtual_users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    domain_id INT NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    kota_mb INT DEFAULT 1024,
    aktif TINYINT(1) DEFAULT 1,
    FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
);

CREATE TABLE virtual_aliases (
    id INT AUTO_INCREMENT PRIMARY KEY,
    domain_id INT NOT NULL,
    kaynak VARCHAR(255) NOT NULL,
    hedef VARCHAR(255) NOT NULL,
    FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
);

-- Örnek veri ekle
INSERT INTO virtual_domains (domain) VALUES ('ornek.com');
INSERT INTO virtual_users (domain_id, email, password) 
VALUES (1, '[email protected]', '{SHA512-CRYPT}$6$rounds=50000$...');

Postfix Ana Yapılandırması

Her iki sunucuda aynı main.cf kullanılacak, sadece myhostname değeri farklı olacak. Bu yüzden konfigürasyonu senkronize ederken bu satırı hariç tutacağız.

# /etc/postfix/main.cf - Her iki sunucuda temel yapı aynı
cat > /etc/postfix/main.cf << 'EOF'
# Temel ayarlar
myhostname = mail1.ornek.com          # mail2'de mail2.ornek.com olacak
myorigin = ornek.com
mydomain = ornek.com
inet_interfaces = all
inet_protocols = ipv4

# Virtual mailbox ayarları
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

# Mail teslim ayarları
virtual_mailbox_base = /var/mail/vhosts
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

# SMTP güvenlik
smtpd_tls_cert_file = /etc/ssl/certs/mail.ornek.com.crt
smtpd_tls_key_file = /etc/ssl/private/mail.ornek.com.key
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# SASL kimlik doğrulama
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous

# Spam koruması
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_recipient_restrictions = 
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    reject_invalid_hostname,
    reject_non_fqdn_hostname,
    reject_non_fqdn_sender,
    reject_non_fqdn_recipient,
    reject_unknown_sender_domain

# Kuyruk ayarları - HA için kritik
maximal_queue_lifetime = 5d
bounce_queue_lifetime = 1d
queue_run_delay = 300s
minimal_backoff_time = 300s
maximal_backoff_time = 4000s

# Bağlantı limitleri
default_process_limit = 100
smtpd_client_connection_count_limit = 50
smtpd_client_connection_rate_limit = 30
EOF

MySQL bağlantı dosyalarını oluşturun:

# /etc/postfix/mysql-virtual-mailbox-domains.cf
cat > /etc/postfix/mysql-virtual-mailbox-domains.cf << 'EOF'
user = mailuser
password = GucluSifre123!
hosts = 192.168.1.200
dbname = mailserver
query = SELECT domain FROM virtual_domains WHERE domain='%s' AND aktif=1
EOF

# /etc/postfix/mysql-virtual-mailbox-maps.cf
cat > /etc/postfix/mysql-virtual-mailbox-maps.cf << 'EOF'
user = mailuser
password = GucluSifre123!
hosts = 192.168.1.200
dbname = mailserver
query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') 
        FROM virtual_users WHERE email='%s' AND aktif=1
EOF

chmod 640 /etc/postfix/mysql-*.cf
chown root:postfix /etc/postfix/mysql-*.cf

Keepalived ile VIP Yönetimi

Keepalived, VRRP protokolü kullanarak iki sunucu arasında sanal IP adresini yönetir. Mail1 master, mail2 backup rolünde başlar.

# mail1 sunucusunda - /etc/keepalived/keepalived.conf
cat > /etc/keepalived/keepalived.conf << 'EOF'
global_defs {
    router_id MAIL_HA
    enable_script_security
    script_user root
}

vrrp_script check_postfix {
    script "/etc/keepalived/check_postfix.sh"
    interval 5
    weight -20
    fall 3
    rise 2
}

vrrp_instance VI_MAIL {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    
    authentication {
        auth_type PASS
        auth_pass MailHA2024!
    }
    
    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:1
    }
    
    track_script {
        check_postfix
    }
    
    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault "/etc/keepalived/notify.sh FAULT"
}
EOF
# mail2 sunucusunda - sadece state ve priority farklı
# state BACKUP, priority 90 olarak değiştir

# Postfix sağlık kontrol scripti - her iki sunucuda
cat > /etc/keepalived/check_postfix.sh << 'EOF'
#!/bin/bash
# Postfix servisini kontrol et
if ! systemctl is-active --quiet postfix; then
    logger "Keepalived: Postfix servisi çalışmıyor, failover tetikleniyor"
    exit 1
fi

# SMTP portunu kontrol et
if ! nc -z -w5 localhost 25; then
    logger "Keepalived: SMTP port yanıt vermiyor, failover tetikleniyor"
    exit 1
fi

# Test maili gönder (opsiyonel, agresif kontrol)
# echo "test" | sendmail -v postmaster@localhost > /dev/null 2>&1

exit 0
EOF
chmod +x /etc/keepalived/check_postfix.sh

Notify scripti, failover anında yapılması gereken işlemleri yönetir:

cat > /etc/keepalived/notify.sh << 'EOF'
#!/bin/bash
DURUM=$1
ZAMAN=$(date '+%Y-%m-%d %H:%M:%S')
SUNUCU=$(hostname)

logger "Keepalived: $SUNUCU $DURUM durumuna gecti - $ZAMAN"

case $DURUM in
    MASTER)
        # NFS paylaşımlı kuyruğu aktif et
        mount | grep -q mailqueue || mount /var/spool/postfix/deferred-shared
        # Postfix'i yeniden başlat ve kuyruk işlemeyi aktif et
        systemctl restart postfix
        postfix flush
        # Mail gönder - ekibe bildir
        echo "UYARI: $SUNUCU MASTER oldu - $ZAMAN" | 
            mail -s "Mail HA Failover" [email protected]
        ;;
    BACKUP)
        logger "Keepalived: $SUNUCU BACKUP moduna gecti"
        # Kuyruk işlemeyi durdur (aktif sunucu üstlendi)
        postfix stop
        sleep 2
        postfix start
        ;;
    FAULT)
        logger "Keepalived: $SUNUCU FAULT durumunda!"
        echo "KRITIK: $SUNUCU FAULT durumunda - $ZAMAN" | 
            mail -s "KRITIK: Mail HA Sorunu" [email protected]
        ;;
esac
EOF
chmod +x /etc/keepalived/notify.sh
systemctl enable --now keepalived

Konfigürasyon Senkronizasyonu

İki sunucu arasında konfigürasyon değişikliklerini senkronize etmek için rsync ve inotifywait kullanacağız. Mail1’de yapılan değişiklikler otomatik olarak mail2’ye yansıyacak.

# mail1'de SSH key oluştur ve mail2'ye kopyala
ssh-keygen -t ed25519 -f /root/.ssh/mail_ha_key -N ""
ssh-copy-id -i /root/.ssh/mail_ha_key.pub [email protected]

# Senkronizasyon scripti - /usr/local/bin/sync-mailconfig.sh
cat > /usr/local/bin/sync-mailconfig.sh << 'EOF'
#!/bin/bash
KAYNAK="/etc/postfix/"
HEDEF="[email protected]:/etc/postfix/"
SSH_KEY="/root/.ssh/mail_ha_key"
LOG="/var/log/mail-sync.log"

echo "$(date '+%Y-%m-%d %H:%M:%S') - Konfigürasyon senkronizasyonu başlıyor" >> $LOG

rsync -avz --delete 
    --exclude="main.cf.bak" 
    --exclude="*.db" 
    --filter="protect myhostname" 
    -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" 
    $KAYNAK $HEDEF >> $LOG 2>&1

if [ $? -eq 0 ]; then
    # mail2'de postfix'i yeniden yükle
    ssh -i $SSH_KEY [email protected] "postfix reload" >> $LOG 2>&1
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Senkronizasyon başarılı" >> $LOG
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - HATA: Senkronizasyon başarısız!" >> $LOG
    echo "Mail konfigürasyon senkronizasyonu başarısız!" | 
        mail -s "UYARI: Config Sync Hatası" [email protected]
fi
EOF
chmod +x /usr/local/bin/sync-mailconfig.sh

myhostname satırını senkronizasyondan korumak için mail2’de bir wrapper kullanın:

# mail2'de myhostname'i koru
cat > /usr/local/bin/postfix-reload-safe.sh << 'EOF'
#!/bin/bash
# myhostname'i al ve koru
MYHOSTNAME=$(grep "^myhostname" /etc/postfix/main.cf | awk '{print $3}')

# Senkronizasyondan sonra myhostname'i düzelt
sed -i "s/^myhostname = .*/myhostname = $MYHOSTNAME/" /etc/postfix/main.cf

# Postfix'i yeniden yükle
postfix reload
EOF
chmod +x /usr/local/bin/postfix-reload-safe.sh

Monitoring ve Alerting

HA kurulumunun en önemli parçalarından biri izlemedir. Sorun çıkmadan önce fark etmek için kapsamlı bir izleme scripti yazalım.

cat > /usr/local/bin/mail-ha-monitor.sh << 'EOF'
#!/bin/bash
# Mail HA izleme scripti - her 5 dakikada çalıştır
UYARI_ESIK=100      # Kuyrukta bu kadar mail varsa uyar
KRITIK_ESIK=500     # Kuyrukta bu kadar mail varsa kritik
LOG="/var/log/mail-ha-monitor.log"
ALICI="[email protected]"

check_servis() {
    if ! systemctl is-active --quiet postfix; then
        echo "KRITIK: Postfix servisi çalışmıyor!" | 
            mail -s "KRITIK: Postfix Durdu" $ALICI
        logger "mail-monitor: Postfix servisi durdu!"
        return 1
    fi
    return 0
}

check_kuyruk() {
    KUYRUK=$(mailq | tail -1 | awk '{print $1}')
    # "Mail queue is empty" durumu
    if [ "$KUYRUK" = "Mail" ]; then
        KUYRUK=0
    fi
    
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Kuyruk: $KUYRUK mail" >> $LOG
    
    if [ "$KUYRUK" -gt "$KRITIK_ESIK" ] 2>/dev/null; then
        echo "Postfix kuyruğunda $KUYRUK mail birikmiş!" | 
            mail -s "KRITIK: Mail Kuyruğu Doldu" $ALICI
    elif [ "$KUYRUK" -gt "$UYARI_ESIK" ] 2>/dev/null; then
        echo "Postfix kuyruğunda $KUYRUK mail var" | 
            mail -s "UYARI: Mail Kuyruğu Yüksek" $ALICI
    fi
}

check_vip() {
    # VIP hangi sunucuda?
    if ip addr show | grep -q "192.168.1.100"; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - VIP bu sunucuda (MASTER)" >> $LOG
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - VIP bu sunucuda değil (BACKUP)" >> $LOG
    fi
}

check_mysql_baglantisi() {
    if ! mysql -h 192.168.1.200 -u mailuser -pGucluSifre123! 
        -e "SELECT 1" mailserver > /dev/null 2>&1; then
        echo "Mail MySQL bağlantısı koptu!" | 
            mail -s "KRITIK: Mail DB Bağlantı Sorunu" $ALICI
        logger "mail-monitor: MySQL bağlantısı başarısız!"
    fi
}

# Tüm kontrolleri çalıştır
check_servis
check_kuyruk
check_vip
check_mysql_baglantisi
EOF

chmod +x /usr/local/bin/mail-ha-monitor.sh

# Crontab'a ekle
echo "*/5 * * * * root /usr/local/bin/mail-ha-monitor.sh" >> /etc/cron.d/mail-ha

Failover Testi

Kurulumu tamamladıktan sonra mutlaka failover testini yapın. Gerçek bir kesintide sürprizle karşılaşmak istemezsiniz.

# Failover testi prosedürü
# 1. Mevcut durumu kaydet
ip addr show | grep 192.168.1
mailq
systemctl status keepalived postfix

# 2. mail1'de Postfix'i durdur ve failover'ı gözlemle
systemctl stop postfix

# 3. mail2'de VIP'in transfer edildiğini doğrula (10-15 saniye bekle)
# mail2'de çalıştır:
ip addr show | grep 192.168.1.100   # Bu çıktıyı görmeli

# 4. Mail akışını test et
echo "Failover test maili" | sendmail -v [email protected]

# 5. mail1'i geri getir ve MASTER rolünü gözlemle
systemctl start postfix
# Keepalived preempt modundaysa mail1 MASTER'ı geri alır

# 6. Kuyruk flush
postfix flush
mailq

DNS ve MX Kaydı Yapılandırması

VIP üzerinden çalışan HA kurulumunda DNS ayarları kritiktir. Harici MX kaydınız VIP adresini göstermelidir.

  • MX kaydı: ornek.com MX 10 mail.ornek.com
  • A kaydı: mail.ornek.com A 192.168.1.100 (VIP adresi)
  • PTR kaydı: 100.1.168.192.in-addr.arpa PTR mail.ornek.com
  • SPF kaydı: v=spf1 ip4:192.168.1.100 include:ornek.com ~all

Dikkat edilmesi gereken nokta, A kaydının VIP adresini göstermesidir. Bireysel sunucu IP’lerini MX’e koymayın. VIP devre dışı kalırsa DNS TTL süresince mail alımı kesintiye uğrar.

Gerçek Dünya Notları

Üretim ortamında bu kurulumu kurarken karşılaştığım birkaç önemli noktayı paylaşmak istiyorum:

  • NFS performansı: Yoğun mail trafiğinde NFS darboğaz olabilir. Mümkünse 10GbE ağ ve SSD tabanlı NFS sunucusu kullanın. GlusterFS replikasyonu daha iyi bir alternatif sunabilir.
  • Keepalived ve ağ yapılandırması: Bazı bulut ortamlarında (AWS, GCP) VRRP paketleri engellenebilir. Bu durumlarda Keepalived yerine Elastic IP veya Floating IP mekanizmalarını kullanmanız gerekir.
  • Konfigürasyon kayması: Zaman içinde iki sunucunun konfigürasyonu farklılaşabilir. Ansible veya Chef ile konfigürasyon yönetimi yapmanızı şiddetle tavsiye ederim.
  • Sertifika yönetimi: TLS sertifikanız her iki sunucuda da güncel olmalı. Let’s Encrypt kullanıyorsanız, certbot’un her iki sunucuda da çalıştığından emin olun ya da sertifikayı birinden diğerine kopyalayan bir mekanizma kurun.
  • Split-brain senaryosu: Her iki sunucunun da kendini MASTER zannettiği durum en tehlikeli senaryodur. Keepalived’in authentication konfigürasyonunu doğru yapın ve ağ bölünmesi durumunda ne olacağını test edin.

Sonuç

Postfix ile HA kurulumu, birkaç temel bileşenin doğru bir şekilde bir araya gelmesiyle mümkün olur. Keepalived VIP yönetimini, paylaşımlı depolama kuyruk sürekliliğini, ortak MySQL ise konfigürasyon tutarlılığını sağlar. Bu üç bileşen sağlam kurulduğunda, single point of failure sorunundan büyük ölçüde kurtulmuş olursunuz.

Kurulumu yaptıktan sonra asla canlı ortamı test etmeden bırakmayın. Her ay en az bir kez kontrollü failover testi yapın, testleri belgeleyin ve ekibinizin bu senaryolara nasıl müdahale edeceğini öğrenmesini sağlayın. İyi bir HA kurulumu, sadece teknik değil aynı zamanda operasyonel bir olgunluk meselesidir. Sistemler çalışır durumda kalsa bile, insanların doğru tepkiyi vermesi için pratik yapmaları gerekir.

Yorum yapın