Bu yazı, index/journal/dosya sistemi hiçbirinin kalmadığı bir Veeam kurtarma vakasında yazdığım iki aracın teknik iç yapısını ele alıyor. Amaç hikaye anlatmak değil; kararların neden öyle alındığını ve uygulamanın nasıl kurulduğunu göstermek.
Metadata'sız RAID ve Yedek Kurtarma: Mimari ve Uygulama Notları
Bu yazı, index/journal/dosya sistemi hiçbirinin kalmadığı bir Veeam kurtarma vakasında yazdığım iki aracın teknik iç yapısını ele alıyor. Amaç hikaye anlatmak değil; kararların neden öyle alındığını ve uygulamanın nasıl kurulduğunu göstermek.
Problem Tanımı
Girdi durumu:
- RAID metadata'sı kayıp, üstüne yanlış parametrelerle (muhtemelen yanlış stripe size / disk sırası ile) bir "rebuild" denemesi yapılmış — yani üst üste iki bozulma katmanı var.
- Dizideki bir kısım disk Windows'ta hızlı formatlanmış, ardından üzerine yeni veri yazılmış.
- Hedef veri bir Veeam backup repository'si (.vbk/.vib zinciri), ama repository'nin kendi index/metadata katmanı da kayıp.
İki ayrı problem sınıfı var burada ve birbirine kenetli: (1) blind RAID reconstruction — hiçbir üst seviye metadata olmadan disk dizisinin geometrisini tespit etmek, (2) metadata'sız içerik carving — dosya sistemi ya da backup index'i olmadan, ham bloklardan orijinal yedek yapısını geri kurmak.
Kısım 1: RAID Geometri Tespiti
Neden mevcut araçlar yetmedi
PC-3000 ve benzerlerinin RAID reconstruction modülleri temelde şunu yapar: disk başına belirli offsetlerde dosya sistemi imzaları arar (NTFS $MFT kayıtlarının periyodikliği, FAT tablo tekrarları vb.), farklı stripe size / disk sırası / rotasyon kombinasyonlarını dener, hangi kombinasyonda bu imzalar "tutarlı" bir şekilde diziliyor diye bakar. Bu, istatistiksel bir arama uzayı problemi — N disk için disk sırası permütasyonu × olası stripe size seti × rotasyon yönü kombinasyonlarını tarayıp bir "confidence score" ile en iyi adayı seçiyor.
Bu yöntemin temel varsayımı: disk üzerindeki veri deseni bozulmamış. Benim vakamda bu varsayım geçersizdi — bazı diskler formatlanıp üzerine yazılmıştı, yani üzerlerindeki "desen" zaten farklı bir dosya sistemi kurulumuna aitti. Algoritma orada tutarlı bir sonuç bulamıyor, çünkü aradığı imzalar gerçekten yok.
Yaklaşım: Desen aramak yerine iz okumak
Kendi aracımda stratejiyi tersine çevirdim: dosya sistemi içeriğine bakıp geometriyi tahmin etmek yerine, RAID controller'ların disk üzerinde bıraktığı kendi metadata artıklarını doğrudan okumak.
Pratikte bu şu katmanlardan oluşuyor:
a) WWN tabanlı disk kimliklendirme
Her fiziksel diskten WWN (World Wide Name) okunuyor ve bu, dizideki mantıksal disk pozisyonuyla eşleştiriliyor. RAID controller'ların çoğu (LSI/Avago, Adaptec, Intel RST) config bloklarında disk kimliklerini saklar; bu bloklar formatla silinmemişse (genelde disk sonunda ayrı bir reserved alanda dururlar) buradan disk sırası doğrudan okunabiliyor. Go tarafında bu, /dev/sdX üzerinde ioctl ile SCSI INQUIRY komutu (page 0x83) çağırarak WWN çekmek şeklinde uygulandı — üçüncü parti bağımlılık yok, doğrudan syscall seviyesinde.
b) Config artıklarından stripe/parity geometrisi
Disklerin controller-reserved alanlarında (genelde disk sonunda, birkaç MB'lık bir bölge) eski RAID config yapıları kalıntı halinde duruyordu. Buradan stripe size ve parity rotasyon yönü doğrudan parse edildi — tahmin değil, kayıt okuma. Format işlemi genelde bu alanlara dokunmuyor çünkü işletim sistemi bu sektörleri "kullanılabilir alan" olarak görmüyor.
c) COW katmanı — sanal montaj
Bu, mimarinin can alıcı noktası. Elimde formatlanıp üzerine yazılmış diskler olduğu için, herhangi bir yanlış geometri denemesi gerçek diske yazılırsa geri dönüşü olmayan bir hasar riski var. Bu yüzden RAID'i hiçbir zaman fiziksel diske yazmadan kurdum:
- Her fiziksel disk salt-okunur (
O_RDONLY) açılıyor. - Üzerine bir sparse overlay dosyası (fiziksel disk başına bir "diff" dosyası) tanımlanıyor; okunan her sektör önce overlay'de var mı diye kontrol ediliyor, yoksa fiziksel diskten okunup overlay'e gerektiğinde yazılıyor (yalnızca RAID kurulum sürecinin kendi ürettiği meta-yazılar overlay'e gidiyor, kullanıcı verisine asla dokunulmuyor).
- Bu sanal katman, bir NBD (Network Block Device) server olarak expose ediliyor. Go'da bu, NBD protokolünü (basit bir TCP üzerinden request/reply —
NBD_CMD_READ,NBD_CMD_WRITE,NBD_CMD_DISC) doğrudan implemente ederek yapıldı; harici bir bağımlılık gerekmedi. - Linux tarafında
nbd-clientile bu sanal cihaz/dev/nbdXolarak mount edilip üzerinde normal bir blok cihazı gibi çalışılabiliyor —mdadm --assemble, loop mount, hatta doğrudan dosya sistemi recovery araçları bu sanal cihaza karşı çalıştırılabiliyor.
Bunun getirdiği pratik fayda: onlarca farklı stripe size / disk sırası / rotasyon kombinasyonunu, her denemede gerçek diske hiç dokunmadan test edebildim. Yanlış bir kombinasyon sadece "boş" ya da tutarsız bir dosya sistemi görüntüsü veriyordu, hasar vermiyordu.
d) Formatlanmış disklerin toparlanması
Format (özellikle hızlı format) diski sıfırlamaz; yalnızca dosya sistemi metadata yapılarını (MFT, boot sector, cluster bitmap) yeniden yazar. Kullanıcı verisinin büyük kısmı, yeni format tarafından henüz üzerine yazılmamış cluster'larda hâlâ duruyordu. Bu diskler RAID'e COW katmanı üzerinden dahil edildikten sonra, RAID seviyesinde toparlanan stripe'lar zaten eski veriyi içeriyordu — çünkü format sadece dosya sistemi seviyesinde bir işlemdi, RAID'in stripe düzenini değiştirmemişti.
Kısım 2: Metadata'sız Veeam Blok Carving
RAID toparlandıktan sonra elimde tutarlı, okunabilir bir ham disk imajı vardı — ama içinde ne bir dosya sistemi, ne çalışan bir .vbk, ne index, ne journal.
Veeam blok yapısına dair gözlemler
Veeam backup dosyaları (.vbk/.vib) içeride sabit veya değişken boyutlu "extent" bloklarından oluşur; her blok genelde bir header/checksum ile başlar ve sıkıştırma (genelde zlib/lz4 varyantları) uygulanmış olabilir. Format sürümüne göre detaylar değişir, ama pratikte her bloğun kendi içinde bütünlük kontrolüne izin veren bir imza/checksum alanı var. Index kaybolsa bile bu, blokları tek tek tanımayı mümkün kılıyor.
Carving stratejisi
Araç şu akışla çalışıyor:
- Ham tarama: Disk imajı sabit boyutlu pencerelerle (chunk) taranıyor, her pencerede bilinen blok header imzaları aranıyor.
- Blok doğrulama: Bir imza bulunduğunda, blok boyutu header'dan okunup checksum ile içerik doğrulanıyor. Checksum tutmuyorsa blok "bozuk/kısmi" olarak işaretleniyor, atılmıyor — çünkü ileride başka bir kaynaktan tamamlanabilir.
- Blok haritası (block map) oluşturma: Bulunan her blok, orijinal VM disk offset'ine (blok header'ında genelde bu bilgi de taşınır) göre bir haritaya yerleştiriliyor. Bu harita aslında kaybolan index'in yeniden inşası.
- Çapraz yedekten tamamlama: Bir offset için hiçbir sağlam blok bulunamadığında, aynı VM'in farklı bir restore point'ine ait ikinci bir ham taramadan aynı offsete karşılık gelen blok aranıyor. Incremental backup yapısı gereği bloklar arasında yüksek örtüşme var, bu da eksik parçaların büyük kısmının başka bir yedekte sağlam halde bulunmasını sağlıyor.
- Yeniden birleştirme (reassembly): Blok haritası tamamlandığında, bloklar doğru offsetlerde birleştirilip ya doğrudan okunabilir bir disk imajı olarak ya da yeni bir .vbk kabuğu içine yazılarak Veeam'in kendisinin tanıyabileceği bir formata sokuluyor.
Ölçek ve gözlemlenebilirlik
Terabaytlarca ham veriyi taramak I/O-bound bir iş. Go tarafında bunu şöyle yapılandırdım:
- Disk imajı sabit boyutlu segmentlere bölünüp her segment ayrı bir worker goroutine'e veriliyor (
sync.WaitGroup+ sınırlı sayıda worker, disk I/O bant genişliğini boğmamak içinsemaphore/buffered channel ile sınırlandı). - Her worker kendi bulduğu blokları bir sonuç channel'ına yazıyor; ayrı bir "collector" goroutine bunları merkezi blok haritasına (bir
sync.Mapya da kilitli bir B-tree benzeri yapı) işliyor. - İlerleme durumu (taranan byte, bulunan blok sayısı, doğrulanamayan/eksik blok sayısı, çapraz yedekten tamamlanan blok sayısı) periyodik olarak tek bir terminal/TUI ekranına push ediliyor — büyük ölçekli, uzun süren bir işlemde "ne kadar kaldı, nerede hata var" görünürlüğü kritik.
İki aracın da ortak noktası şu: metadata'ya güvenmemek, ham veriden çıkan izlere güvenmek. RAID tarafında bu, config artıklarından geometri okumak ve COW ile hasarsız deneme yapmak anlamına geldi. Backup tarafında ise, blok içeriğinin kendi imzalarından bir index'i yeniden inşa etmek ve eksik parçaları çapraz yedekten kapatmak anlamına geldi.
Piyasadaki genel amaçlı araçlar "temiz veri, sağlam metadata" varsayımıyla optimize edilmiş durumda. O varsayım çöktüğünde, iş orada bitmiyor — aslında custom tooling'in gerçekten gerekli olduğu nokta orası oluyor.