Çift MX ve Dovecot dsync Replikasyonuyla Gerçek Yüksek Erişilebilir Posta Altyapısı
İkinci bir MX kaydı eklemek sizi yüksek erişilebilir yapmaz — sadece postayı kuyruğa alır. Gerçek HA, iki tam yetkili düğümün aynı posta kutusunu Dovecot dsync ile canlı replike etmesidir. DNS, Postfix, replicator servisi, master-master DB ve failover sırasındaki UID çakışmalarına kadar sahadan anlatıyoruz.
EvilMail Team29 Temmuz 202614 dk okuma
"Backup MX kurdum, artık yüksek erişilebilirim." Hayır, değilsiniz. İkincil MX kaydı yalnızca bir şey yapar: birincil düğüm ölünce postayı kendi kuyruğunda tutar ve Postfix'in maximal_queue_lifetime değeri boyunca (varsayılan 5 gün) teslim etmeye çalışır. Bu süre içinde kullanıcınız IMAP'e bağlanamaz, gönderdiği mail Sent klasörüne düşmez, arşivine erişemez. Birincil geri geldiğinde kuyruk boşalır ama teslimat sırası bozulmuştur ve o beş gün boyunca posta kutusu pratikte offline'dı. Bu HA değil; felaketi geciktiren bir tampon.
Gerçek yüksek erişilebilirlik tek bir şey demektir: iki düğüm de aynı posta kutusunu — mdbox verisi, index dosyaları ve Dovecot UID'leri dahil — canlı taşır. Hedef mimari "aktif-pasif backup MX" değil; eşit öncelikli iki MX, aralarında Dovecot dsync ile çift yönlü replikasyon ve paylaşımlı bir auth katmanı. Her iki düğüm de MX 10, her ikisi de IMAP ve 587 submission verir, aralarında saniyeler içinde senkron olur. evilmail.pro'da temp-email trafiğini tam olarak bu mimari üzerinde çalıştırdığımız için buradakiler teorik değil, sahadan.
Mimari: neden çift-aktif, neden dsync
İki yaklaşım var. Klasik backup MX (MX 10 / MX 20): ikincil sadece relay yapar, mailbox tutmaz, birincil dönene kadar bekletir. Yukarıda anlattığımız nedenlerle bu bir yanılsama. İkincisi ve doğrusu çift-aktif replikasyon
: iki düğüm de tam yetkili, ikisi de eşit öncelikli MX, aralarında object-level senkron.
Peki neden shared storage — NFS ya da DRBD — değil? Çünkü mdbox (ya da sdbox) formatı, mesaj gövdesinin yanında dovecot.index, dovecot.index.log ve dovecot-uidlist gibi durum dosyaları tutar. İki Dovecot instance'ı aynı filesystem'e aynı anda yazınca bu index'ler bozulur, kilit çakışmaları başlar ve bir noktada Corrupted index cache loglarıyla uyanırsınız. dsync ise filesystem-level değil, object-level replikasyon yapar: mesajları GUID'leriyle eşler, iki tarafın index'ini bağımsız ama tutarlı tutar. Ağ bölünmesinde bile her düğüm kendi tutarlı kopyasıyla ayakta kalır.
Düğüm rolleri net: node-a (mx1.evilmail.pro, 203.0.113.10) ve node-b (mx2.evilmail.pro, ikinci IP). İkisi de Postfix + Dovecot çalıştırır, ikisi de gerçek son teslimat noktasıdır.
DNS katmanı: MX, SPF, PTR ve TTL
Failover'ın büyük kısmı DNS'te başlar. İki MX kaydını kasıtlı olarak eşit öncelikte tutuyoruz. Farklı öncelik verirseniz gönderen MTA'lar önce yüksek öncelikliye vurur; eşit verirseniz round-robin dağılır ve biri düşünce diğerine anında geçerler — ekstra retry gecikmesi yok.
dns
evilmail.pro. 300 IN MX 10 mx1.evilmail.pro.
evilmail.pro. 300 IN MX 10 mx2.evilmail.pro.
mx1.evilmail.pro. 300 IN A 203.0.113.10
mx2.evilmail.pro. 300 IN A <node-b-ip>
; SPF — her iki gönderen IP yetkilendirilir
evilmail.pro. 300 IN TXT "v=spf1 ip4:203.0.113.10 ip4:<node-b-ip> -all"
TTL'i 300 saniyede tutun. Bir düğümü kalıcı olarak devreden çıkarmanız gerekirse kaydın dünyadan silinmesi dakikalar sürer, saatler değil. Kritik iki nokta:
PTR (reverse DNS) her iki IP için zorunlu. mx1 ve mx2 IP'lerinin PTR kayıtları ilgili hostname'e çözülmezse Gmail ve Outlook postayı ya spam'e atar ya doğrudan reddeder. Bu tek başına en sık gördüğümüz deliverability hatası.
DKIM private key iki düğümde identik olmalı. Yeni düğümde ayrı anahtar üretmeyin — mevcut anahtarı rsync -e ssh /etc/dovecot/dkim/ node-b:/etc/dovecot/dkim/ ile kopyalayın. İki düğüm aynı selector'la aynı imzayı üretsin; yoksa node-b'den giden mailin DKIM'i doğrulanmaz.
MTA-STS ve TLS-RPT'yi de eklemenizi öneririm: _mta-sts TXT kaydı ve mta-sts.evilmail.pro üzerinde statik policy dosyası, iki MX hostname'ini de mx: satırlarında listeler. TLS downgrade saldırılarına karşı ucuz sigorta.
Dovecot replikasyon: dsync + replicator servisi
Mimarinin teknik göbeği burası. replication plugin'i, notify plugin'i üzerinden her mailbox değişikliğini yakalar ve replicator servisine "bu kullanıcıyı senkronla" sinyali gönderir. replicator de peer düğüme bir dsync push tetikler. Yani teslimat ya da IMAP yazması anında, saniyeler içinde karşı düğüme yansır.
/etc/dovecot/conf.d/10-replication.conf (iki düğümde de aynı; sadece mail_replica hedefi karşı düğümü gösterir):
SSH anahtarını vmail kullanıcısı için oluşturun ve non-interactive çalıştığını manuel test edin — sudo -u vmail ssh -i /etc/dovecot/dsync.key vmail@mx2 doveadm dsync-server parola sormadan açılmalı. En sık takılan yer burası: replicator sessizce başarısız olur, backlog büyür ve kimse fark etmez.
İzleme komutları günlük araç setiniz:
bash
doveadm replicator status # genel: queued/failed sayıları
doveadm replicator status '*' # kullanıcı bazında backlog
doveadm replicator dsync-status # aktif dsync süreçleri
doveadm sync -u [email protected] tcp:mx2.evilmail.pro:12345 # manuel tam sync
replication_full_sync_interval varsayılan 24 saattir — bu gecelik tam senkrondur, incremental push zaten anlık çalışır. Bu değeri artırmayın; nightly full sync, incremental'in kaçırdığı nadir kenar durumlarını (silme senkronizasyonu, flag drift) yakalayan güvenlik ağıdır.
Postfix: iki düğüm de son teslimat noktasıdır
Kritik tasarım kararı: iki MX birbirine relay etmez. Her ikisi de postayı yerel Dovecot LMTP'ye teslim eder. node-b, node-a için "sadece bir sonraki hop" değildir — kendi başına final destination'dır. Bu yüzden virtual_mailbox_domains iki düğümde de aynı domainleri listeler.
Kullanıcının 587 submission'ı hangi düğüme yaptığı önemsizdir. node-a'dan gönderirse Sent klasörüne yazılan kopya dsync ile node-b'ye replike olur; node-b'den gönderirse tersi. İstemci her iki düğümü de görebilir ve arşivi tutarlıdır. reject_unauth_destination'ı asla düşürmeyin — iki eşit MX'iniz varsa ve biri yanlışlıkla open relay olursa, spam'ciler ikisini birden bulur.
Auth ve veritabanı: replikasyonun görünmeyen yarısı
Posta kutusu replike oluyor, güzel. Peki node-b kullanıcının parolasını nereden biliyor? Mailbox verisi dsync'le taşınıyor ama auth backend'i ayrı bir problem. virtual_users tablosu iki düğümde tutarlı değilse, node-a'da var olan kullanıcı node-b'de yoktur ve failover anında mail bounce eder — HA'nın en sinsi başarısızlığı.
İki gerçekçi seçenek:
MariaDB dual-master replikasyon. İki node birbirini master olarak alır. PK çakışmasını önlemek için auto_increment_increment=2, node-a'da auto_increment_offset=1, node-b'de auto_increment_offset=2. Böylece iki tarafta aynı anda insert olsa bile birincil anahtarlar çakışmaz — biri tek, diğeri çift sayı üretir.
Galera 3-node cluster. Senkron replikasyon ve quorum ister; tek düğüm split-brain vermez ama üçüncü bir düğüm (arbitrator olsa bile) gerektirir. Daha ağır ama iki makinelik kurulumdan büyürken doğru yön.
Mevcut altyapımızda Dovecot password scheme'i PLAIN, vmail uid/gid 5000. Hangi DB modelini seçerseniz seçin, virtual_users ve virtual_domains tablolarının iki düğümde bit-bit aynı olması pazarlık konusu değil. Ölçek büyürse LDAP (replike edilen bir dizin) da geçerli bir alternatif — ama iki node için master-master MariaDB en az bakım isteyen çözüm.
Failover senaryosu: node-a düşünce ne olur
Akış adım adım gerçek: node-a offline olur. DNS'te iki eşit öncelikli MX olduğu için gönderen MTA otomatik node-b'ye düşer — ikincil MX zaten 10 önceliğinde, ekstra retry döngüsü yok. node-b kullanıcıya IMAP verir, Sent'e yazar, ama replike edecek peer yok. Burada dsync değişiklikleri kendi replication queue'sunda biriktirir; doveadm replicator status '*' çıktısında o kullanıcının backlog'u tırmanmaya başlar. node-a geri geldiğinde replicator otomatik catch-up yapar ve backlog tekrar sıfıra iner.
Dürüst olalım: split-brain riski gerçek. node-a tamamen ölmediyse — mesela ağ bölünmesi yaşandıysa — ve iki tarafta da aynı posta kutusuna yazma olursa ne olur? dsync burada sizi kurtaran şeydir: her mesaj filesystem konumuna değil, kalıcı bir GUID'e bağlıdır. Conflict'te dsync iki tarafı merge eder, veri kaybı olmaz. Ancak UIDVALIDITY/UID renumber tetiklenebilir; bu durumda bazı IMAP istemcileri posta kutusunu yeniden indeksler (kullanıcı "mailler bir an kayboldu sonra geldi" der). NFS shared storage'da aynı durum index corruption ile sonuçlanırdı — işte tam bu yüzden shared-storage değil dsync seçiyoruz. Backlog'u sürekli izleyin: bir kullanıcının backlog'u kalıcı olarak sıfıra dönmüyorsa, SSH ya da doveadm auth kopmuş demektir, replikasyon çalışmıyordur.
İzleme, doğrulama ve kontrol listesi
Replikasyonun gerçekten çalıştığını lafla değil, komutla doğrularsınız. Kullanıcıya bir test maili düşürün (swaks ya da normal bir gönderim), sonra INBOX'taki son mesajın GUID'ini iki düğümde de okuyun:
bash
# node-a: INBOX'taki son mesajın GUID'ini al
doveadm fetch -u [email protected] guid mailbox INBOX | tail
# node-b: ~2 saniye sonra aynı GUID burada olmalı
ssh node-b "doveadm fetch -u [email protected] guid mailbox INBOX | tail"
İki taraftaki GUID eşleşiyorsa replikasyon canlı. Production'a almadan önce şu listeyi tek tek işaretleyin:
İki IP için de PTR kaydı doğru hostname'e çözülüyor mu (dig -x <ip> her iki tarafta temiz mi)
DKIM private key iki düğümde identik mi (rsync'le kopyalandı, ayrı üretilmedi)
doveadm replicator status '*' backlog'u iş yükü altında sürekli 0'a dönüyor mu
DB master-master çakışmasız mı (auto_increment_offset 1 ve 2, SHOW SLAVE STATUS iki yönde de Seconds_Behind_Master: 0)
Her iki 587 submission'dan gönderilen mail, diğer düğümün Sent klasöründe görünüyor mu
MX öncelikleri gerçekten eşit mi (10 / 10), TTL 300 mü
dsync_remote_cmd SSH bağlantısı vmail olarak non-interactive, parolasız açılıyor mu
İki MX birbirine relay etmiyor, her ikisi de virtual_mailbox_domains'de final destination mı
Backlog 0, PTR temiz, DKIM iki düğümde aynı, master-master gecikmesi sıfır: HA budur. Bunlardan biri bile tik almıyorsa, geri kalanı umuttan ibarettir — ve umut, node-a saat 03:00'te ölünce çağrı almanızı engellemez.