Docker Lazy Loading: Konteyner Başlatma Sürelerini Hızlandırma

Giriş
Grab'da, veri platformlarımız için konteyner başlatma sürelerini dramatik şekilde azaltmanın yollarını araştırıyoruz. Airflow ve Spark Connect gibi servisler için büyük konteyner imajları indirmek dakikalar alıyor, bu da yavaş cold start'lara ve zayıf otomatik ölçeklendirme performansına neden oluyordu. Bu blog yazısı, eStargz ve Seekable OCI (SOCI) teknolojilerini kullanarak Docker imaj lazy loading implementasyonumuzu, elde ettiğimiz sonuçları ve yol boyunca öğrendiğimiz dersleri paylaşıyor.
Sonuçlar: Rakamlar Kendileri Konuşuyor
Benchmark Sonuçları
Temiz node'lardaki (imaj önbelleği olmayan node'lar) ilk testlerimiz, imaj çekme sürelerinde dramatik iyileştirmeler gösterdi (Şekil 1).
Şekil 1. Sonuç Tablosu:
| Servis | Snapshotter | İmaj Boyutu | İmaj Pull Süresi | Uygulama Başlatma | Toplam Süre |
|---|---|---|---|---|---|
| Airflow | overlayFS | 2.1 GB | 120s | 5.0s | 125s |
| Airflow | eStargz | 2.1 GB | 8.0s | 25.0s | 33s |
| Airflow | SOCI | 2.1 GB | 24s | 5.0s | 29s |
| Spark Connect | overlayFS | 1.8 GB | 95s | 8.0s | 103s |
| Spark Connect | eStargz | 1.8 GB | 6.0s | 20.0s | 26s |
| Spark Connect | SOCI | 1.8 GB | 18s | 8.0s | 26s |
SOCI benchmark testlerimiz sırasında, SOCI ve eStargz arasında önemli bir fark gözlemledik: SOCI, standart imajlarla aynı uygulama başlatma süresini korurken, eStargz daha uzun sürüyor. Örneğin, Airflow ile hem overlayFS hem de SOCI 5.0 saniye başlatma süresi elde ederken, eStargz 25.0 saniye aldı. Bu, lazy loading'in indirme süresini ortadan kaldırmadığını, yeniden dağıttığını gösteriyor. SOCI'nin ayrı indeksler tutma yaklaşımı, indirme-başlatma süresi dengesini daha etkili optimize etmesine olanak tanıyor ve uygulama başlatma performansını standart imajlarla aynı seviyede tutarken imaj çekme süresini dramatik şekilde azaltıyor.
Üretim Performansı
SOCI lazy loading'in üretim dağıtımı, veri platformlarımız genelinde önemli, ölçülebilir iyileştirmeler sağladı. Hem Airflow hem de Spark Connect artık %30-40 daha hızlı başlatma süreleri yaşıyor, bu da trafik artışlarını ele alma ve verimli ölçeklendirme yeteneğimizi doğrudan iyileştiriyor. Bu iyileştirmeler, daha iyi otomatik ölçeklendirme yanıt verme, başlatma sırasında azaltılmış kaynak israfı ve veri işleme iş yükleri için geliştirilmiş kullanıcı deneyimi anlamına geliyor. Zaman içinde gözlemlenen sürekli performans kazanımları, lazy loading'in tutarlı değer sağlayan kararlı, üretime hazır bir optimizasyon olduğunu gösteriyor.
Şekil 2 ve 3, her iki servis için P95 başlatma süresi iyileştirmelerini gösteriyor:
Şekil 2. Üretim Sonuçları: Airflow P95 Başlatma Süresi
- Öncesi: ~125 saniye
- Sonrası: ~85 saniye
- İyileştirme: %32
Şekil 3. Üretim Sonuçları: Spark Connect P95 Başlatma Süresi
- Öncesi: ~103 saniye
- Sonrası: ~68 saniye
- İyileştirme: %34
P95 başlatma süresinin hem imaj indirme/çekme süresini hem de uygulamanın kendi başlatma süresini içerdiğini belirtmek önemlidir. Bu metrik, temiz ve sıcak node'larda hem cold hem de hot start'lar için tüm sistem performansını yakalar ve sadece cold start performansından ziyade genel sistem iyileştirmesini gösterir.
Üretim dağıtımı ve izleme sırasında, SOCI yapılandırma ayarlaması konusunda değerli içgörüler kazandık. AWS'nin Amazon EKS için Seekable OCI Tanıtımı blog yazısındaki önerilen yapılandırmayı takip ederek, SOCI snapshotter ayarlarımızı optimize ettik:
max_concurrent_downloads_per_imagedeğerini 5'ten 10'a çıkardıkmax_concurrent_unpacks_per_imagedeğerini 3'ten 10'a çıkardıkconcurrent_download_chunk_sizedeğerini 8MB'den 16MB'ye çıkardık (AWS'nin Elastic Container Registry (ECR) için önerisiyle uyumlu)
Bu yapılandırma ayarlaması önemli bir performans iyileştirmesine yol açtı: temiz bir node'da imaj indirme süresi 60 saniyeden 24 saniyeye düşürüldü, bu da %60'lık bir iyileştirmeyi temsil ediyor. Buradaki temel ders, varsayılan SOCI yapılandırmalarının tüm ortamlar için optimal olmayabileceği ve bu parametreleri altyapınıza göre (özellikle ECR kullanırken) ayarlamanın önemli kazançlar sağlayabileceğidir.
Teknik Arka Plan: Docker Lazy Loading Nasıl Çalışır?
Konteyner Root Filesystem (rootfs) ve Dosya Organizasyonu
Bir konteynerin root filesystem'i veya rootfs'i, konteynerin kök (/) olarak gördüğü dizin yapısıdır. Bir uygulamanın çalışması için gerekli tüm dosyaları ve dizinleri içerir; uygulamanın kendisi, bağımlılıkları, sistem kütüphaneleri ve yapılandırma dosyaları dahil. Host makinenin filesystem'inden ayrı, izole bir filesystem'dir.
Rootfs, konteyner imajından gelen bir dizi salt okunur katmandan oluşturulur. İmajın Dockerfile'ındaki her komut, bir dizi filesystem değişikliğini temsil eden yeni bir katman oluşturur. Bir konteyner başlatıldığında, genellikle "konteyner katmanı" olarak adlandırılan yeni bir yazılabilir katman, salt okunur imaj katmanları yığınının üstüne eklenir. Çalışan konteynerde yapılan herhangi bir değişiklik, yeni dosyalar yazmak veya mevcut dosyaları değiştirmek gibi, bu yazılabilir katmana yazılır. Alttaki imaj katmanları dokunulmadan kalır. Bu, copy-on-write (CoW) mekanizması olarak bilinir.
Containerd'de, bir snapshotter, konteyner filesystem'lerini yönetmekten sorumlu bir eklentidir. Birincil görevi, bir imajın katmanlarını alıp bir konteyner için rootfs'e birleştirmektir. Containerd'deki varsayılan snapshotter, katmanları verimli bir şekilde yığmak için Linux çekirdeğinin OverlayFS sürücüsünü kullanan overlayFS'dir. Rootfs'i birleştirmek için, overlayFS snapshotter, salt okunur imaj katmanlarının "birleştirilmiş" bir görünümünü oluşturur:
Şekil 4. OverlayFS Konteyner Filesystem'ini Nasıl Birleştirir:
- lowerdir: Salt okunur imaj katmanları, OverlayFS'de lowerdir olarak kullanılır. Bunlar konteyner imajından değişmez katmanlardır.
- upperdir: Upperdir olması için yeni, boş bir dizin oluşturulur. Bu, herhangi bir değişikliğin saklandığı konteyner için yazılabilir katmandır.
- merged: Merged dizini, lowerdir ve upperdir'in birleşik görünümüdür. Bu, konteynere rootfs'i olarak sunulan şeydir.
Bir konteyner bir dosya okuduğunda, merged görünümden okunur. Bir konteyner bir dosya yazdığında, copy-on-write mekanizması kullanılarak upperdir'e yazılır. Bu, konteyner filesystem'lerini yönetmenin verimli bir yoludur, çünkü dosyaları çoğaltmaktan kaçınır ve hızlı konteyner başlatmaya olanak tanır.
Problem: Geleneksel Konteyner İmaj Çekme
Lazy loading'in faydalarını anlamak için, önce geleneksel konteyner imaj çekme sürecini anlamamız gerekir:
1. Katmanları İndir: Konteyner runtime, imajı oluşturan tüm katman tarball'larını indirir. 2. Katmanları Aç: Her katman açılır ve host'un diskine çıkarılır. 3. Snapshot Oluştur: Snapshotter, bu katmanları konteynerin rootfs'i olarak bilinen tek, birleşik bir filesystem'e birleştirir. 4. Konteyneri Başlat: Tüm katmanlar indirildikten ve açıldıktan sonra konteyner başlayabilir.
Bu süreç, özellikle büyük imajlar için yavaştır, çünkü konteyner başlatılmadan önce tüm imajın host'ta mevcut olması gerekir.
Çözüm: Remote Snapshotter
Büyük imajlarla yavaş başlatma sorununu ele almak için bir remote snapshotter çözümü kullanıyoruz. Remote snapshotter, tüm imaj verilerinin yerel olarak mevcut olmasını gerektirmeyen özel bir snapshotter türüdür. Tüm katmanları indirip açmak yerine, verinin uzak konumunu (konteyner registry'si gibi) işaret eden bir "snapshot" oluşturur. Gerçek dosya içeriği, konteyner ilk kez bir dosyayı okumaya çalıştığında talep üzerine getirilir.
Geleneksel bir snapshotter olan overlayFS, lowerdir olarak yerel diskteki dizinleri kullanırken, remote snapshotter, uzak registry tarafından desteklenen sanal bir lowerdir oluşturur. Bu genellikle FUSE (Filesystem in Userspace) kullanılarak yapılır. Remote snapshotter, uzak katmanın içeriğini yerel bir dizinmiş gibi sunan bir FUSE filesystem'i oluşturur. Bu FUSE mount daha sonra overlayFS sürücüsü için lowerdir olarak kullanılır. Bu, remote snapshotter'ın mevcut overlayFS altyapısıyla entegre olmasına ve uzak bir kaynaktan veri lazy-loading yeteneği eklemesine olanak tanır.
Remote snapshotter'ları etkinleştiren iki ana format vardır: eStargz ve SOCI.
eStargz Formatı
eStargz, standart OCI tar.gz katman formatının geriye dönük uyumlu bir uzantısıdır. Lazy loading'i etkinleştiren birkaç temel özelliğe sahiptir:
- Ayrı ayrı sıkıştırılmış dosyalar: Katman içindeki her dosya (ve hatta büyük dosyaların parçaları) ayrı ayrı sıkıştırılır. Bu, dosya içeriğine rastgele erişime izin veren anahtardır.
- TOC (içindekiler tablosu): Katmanın sonunda bulunan
stargz.index.jsonadlı bir JSON dosyası. Bu TOC, her dosya için adı, boyutu ve en önemlisi katman blob'u içindeki offset'i dahil olmak üzere metadata içerir. - Footer: Katmanın en sonundaki küçük bir footer, TOC'nin offset'ini içerir ve katmanın son birkaç byte'ını okuyarak kolayca bulunmasını sağlar.
- Chunking ve doğrulama: Büyük dosyalar, her biri TOC'de kendi girişine sahip daha küçük parçalara bölünebilir. Her parça ayrıca TOC girişinde bir
chunkDigest'e sahiptir ve indirilen her veri parçasının bağımsız doğrulamasına olanak tanır. - Prefetch landmark: Katmana
.prefetch.landmarkadlı özel bir dosya yerleştirilebilir ve "öncelikli dosyaların" sonunu işaretler. Bu, snapshotter'ın konteynerin iş yükü için en önemli dosyaları akıllıca önceden getirmesine olanak tanır.
Stargz snapshotter, lazy loading'i etkinleştirmek için eStargz formatını kullanır. İşte nasıl çalıştığı:
1. Mount isteği: Containerd Mount fonksiyonunu çağırdığında, bir katman için yeni bir filesystem oluşturmanın ana giriş noktasıdır.
2. TOC'yi çöz ve oku: Snapshotter, katmanın footer'ını getirir, ardından uzak registry'den stargz.index.json TOC'sini getirir. Bu TOC, sanal bir filesystem oluşturmak için gereken tüm dosya metadata'sını içerir.
3. FUSE filesystem'i mount et: TOC bellekte olduğunda, snapshotter FUSE kullanarak sanal bir filesystem oluşturur. Konteyner artık başlayabilir, çünkü dosya içeriğinin çoğu indirilmemiş olsa bile geçerli bir rootfs'e sahiptir.
4. Talep üzerine getirme: Konteyner read() gibi bir dosya işlemi gerçekleştirdiğinde, FUSE filesystem çağrıyı yakalar. Snapshotter, istenen byte'lar için yerel disk önbelleğini kontrol eder. Veri önbelleğe alınmamışsa, katmanın yalnızca gerekli parçasını indirmek için konteyner registry'sine bir HTTP Range isteği gönderir.
5. Uzaktan getirme ve önbelleğe alma: İndirilen veri konteynere döndürülür ve sonraki okumalar için yerel önbelleğe de yazılır.
6. Optimizasyon için prefetching: FUSE filesystem mount edildikten sonra, bir arka plan goroutine öncelikli dosyaları (.prefetch.landmark'a kadar) indirmeye başlar ve ayrıca arka planda katmanın geri kalanının tamamını indirmek için yapılandırılabilir.
eStargz formatı ve stargz snapshotter hakkında daha derin bir anlayış için, stargz-snapshotter genel bakış belgelerine bakın.
SOCI Formatı
SOCI, AWS tarafından açık kaynak olarak yayınlanan ve konteynerlerin imajı lazy loading yaparak daha hızlı başlamasını sağlayan bir teknolojidir. SOCI, mevcut bir konteyner imajı içindeki dosyaların bir indeksini (SOCI Index) oluşturarak çalışır. SOCI, stargz-snapshotter'dan bazı tasarım ilkelerini ödünç alır ancak farklı bir yaklaşım benimser:
- Ayrı indeks: Bir SOCI indeksi, konteyner imajından ayrı olarak oluşturulur ve OCI Reference Types tarafından konteyner imajına geri bağlanan bir OCI Artifact olarak registry'de saklanır.
- İmaj dönüşümü yok: Bu, konteyner imajlarının dönüştürülmesine gerek olmadığı, imaj digest'lerinin değişmediği ve imaj imzalarının geçerli kaldığı anlamına gelir.
- Native Bottlerocket desteği: SOCI, Bottlerocket OS'de yerel olarak desteklenir.
SOCI formatı hakkında daha derin bir anlayış için, soci-snapshotter belgelerine bakın.
Lazy-Loaded İmajları Oluşturma ve Dağıtma
EKS'de Snapshotter'ları Kurma
Konteyner runtime'ı olarak containerd ile EKS kullanırken, lazy loading'i etkinleştirmek için remote snapshotter'ları yapılandırabilirsiniz. İşte nasıl kurulacakları:
Stargz-snapshotter (eStargz) için: Önce containerd-stargz-grpc servisini kurmanız, ardından containerd'in yapılandırmasında bir proxy eklentisi olarak kaydetmeniz gerekir:
[proxy_plugins]
[proxy_plugins.stargz]
type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
Detaylı kurulum talimatları için, stargz-snapshotter kurulum belgelerine bakın. Kurulum, üretim kullanımı için bir AMI'ye pişirilebilir veya node bootstrap scriptlerinden user data aracılığıyla test edilebilir.
SOCI snapshotter (Bottlerocket) için: Bottlerocket node'larında, user data aracılığıyla SOCI snapshotter'ı etkinleştirin:
# SOCI snapshotter'ı etkinleştir
[settings.container-runtime]
snapshotter = "soci"
SOCI, Bottlerocket'te yerel olarak desteklenir, bu nedenle ek daemon kurulumu gerekmez.
Lazy-Loaded İmajları Oluşturma
eStargz imajları, çıktı sıkıştırmasını estargz olarak ayarlayarak Docker Buildx kullanılarak yerel olarak oluşturulabilir:
docker buildx build \
--platform linux/amd64 \
--output type=registry,oci-mediatypes=true,compression=estargz,force-compression=true \
--tag $ECR_REGISTRY/airflow:$TAG \
.
SOCI, imajları yeniden oluşturmayı gerektirmez; yalnızca mevcut imajlar için bir SOCI indeksi oluşturmanız gerekir. Docker henüz SOCI indeks oluşturmayı yerel olarak desteklemediğinden, geçici çözümler arasında AWS SOCI Index Builder Using Lambda Functions kullanmak veya bu blog yazısında açıklandığı gibi SOCI indeks oluşturmayı CI/CD pipeline'ınıza entegre etmek yer alır.
Önemli Çıkarım: Neden SOCI'yi Seçtik?
Keşfimize eStargz ile başladık ancak sonunda üretim dağıtımı için SOCI'yi seçtik. Temel neden, ölçeklenebilirlik ve Kubernetes pod başlatma ve güvenliğini artırmak için Bottlerocket OS kullanma stratejimizle uyumdur. SOCI, Bottlerocket tarafından yerel olarak desteklenir, bu da servis ekiplerinin tüm EKS kümeleri genelinde daha karmaşık stargz snapshotter'ı kurmak ve sürdürmek zorunda kalmadığı anlamına gelir. Bu, implementasyonu sürdürmeyi kolaylaştırır ve AWS'den daha iyi destek sağlar.
Ek olarak, lazy loading'in imaj verilerini indirmek için gereken süreyi ortadan kaldırmadığını, yeniden dağıttığını öğrendik; başlatma süresinden runtime'a. Bu, cold start performansını dramatik şekilde iyileştirirken, uygulama performansını yakından izlemek ve yapılandırma parametrelerini iş yükünüze ve altyapınıza göre ayarlamak önemlidir. SOCI'nin paralel çekme modu ayarlarını optimize ederek %60'lık bir iyileştirme elde ettik, bu da uygun yapılandırma ayarlamasının değerini gösteriyor.
SOCI ile Docker imaj lazy loading, Grab'daki servislerimizin performansını ve verimliliğini iyileştirmek için önemli bir fırsat sunuyor. Testlerimiz ve üretim dağıtımlarımız şunları gösterdi:
- Temiz node'larda 4 kat daha hızlı imaj çekme süreleri
- Üretim iş yükleri için P95 başlatma sürelerinde %29-34 iyileştirme
- Uygun yapılandırma ayarlaması ile imaj indirme sürelerinde %60 iyileştirme
Implementasyon yolu açık, düşük riskli ve kanıtlanmış bileşenler üzerine inşa edilmiş. Bu teknoloji üretime hazır ve daha fazla servise ölçeklendirmeye devam ediyoruz.
Kaynaklar
- Databricks: Booting Databricks VMs 7x Faster for Serverless Compute - Büyük teknoloji şirketlerinin ölçekte hızlı konteyner başlatmayı nasıl başardığını gösteren endüstri vaka çalışması
- BytePlus: Container Image Lazy Loading Solution - Üretim Kubernetes ortamlarında lazy loading için kurumsal implementasyon kılavuzu
- AWS: Introducing Seekable OCI: Parallel Pull Mode for Amazon EKS - AWS'nin SOCI yapılandırması ve optimizasyonu kılavuzu



