Docker Multistage Build Örneği: Caddy Web Sunucusunun Dockerlaştırılması

Docker’ın şu an(Haziran 2017) sadece son birkaç CE Edge sürümünde yer alan (Güncelleme) 17.06 CE’den itibaren stabil olarak açıklanan multistage build özelliği, imaj boyutlarının küçülmesi ve build işleminin kısalması/optimizasyonu için kolaylık  sağlıyor. Bu yazıda, Caddy Web sunucusunun Docker imajını, eski ve yeni yöntemler ile gerçekleştirip, multistage build kavramını karşılaştırmalı olarak inceleyeceğiz.

1. Giriş

Caddy Web sunucusu, Go diliyle yazılan, TLS aktif olarak gelen, Let’s Encrypt üzerinden sertifikası olmayan alan adları için otomatik sertifika alan, HTTP/2 desteği direkt olarak aktif ve şu anlık (Haziran 2017) deneysel QUIC desteği ile oldukça ilgi çekici bir Web sunucusu. Caddy aynı zamanda Apache-2.0 lisansına sahip.

Caddy sunucusuna ayar dosyası olarak Caddyfile isminde bir dosya sağlıyoruz. Bu dosyanın formatı olabildiğince basit tutulmaya çalışılmış. Aslında hiçbir ayar dosyası sağlamasak bile, bulunduğu dizindeki dosyaları sunmaya başlaması için sadece çalıştırmamız yeterli oluyor.

Caddy’nin özellikleri konumuzun kapsamı dışında olduğu için, kısaca bahsettik. Bunlardan birkaç tanesi birazdan deneme yaparken işimize yarayacak.

2. Ön Bilgi

Öncelikle, bu yazının amacının Docker’daki multistage build olduğunu tekrarlayalım. Caddy’nin web sitesi üzerinden derlenmiş halde dağıtımı yapılıyor. Bu şekilde indirip direkt olarak çalıştırılabilir bir haline erişmiş oluyorsunuz. Haliyle, ilk olarak bu şekilde nasıl yapacağımızdan bahsedelim ve karşılaşılabilecek bazı sorunlara bakalım.

3. Derlenmiş, Çalıştırılabilir Caddy’nin İndirilmesi

Bir web sitesinden nasıl program indirilebileceğinden bahsetmekten ziyade, bu başlıkta Caddy’nin indirme ekranı seçeneklerinden bahsedeceğiz. Caddy’nin web sitesine https://caddyserver.com adresinden ulaşabilirsiniz. İndirme sayfasında, bize kullanacağımız mimariyi, işletim sistemini ve eklentileri seçme şansı veriyor. Öyle ki Raspberry Pi’de çalıştırabileceğimiz versiyonu bile mevcut.

Bu sayfada önemli olan, seçim yaparken kendi bilgisayarınızın mimarisi için değil, Docker konteynırlarının çalışacağı mimariye göre seçim yapmak. Bu yüzden şu an Linux 64-bit yazılı olanı indirmemiz gerekiyor. Bunu yaparken, indirmek için kullandığınız linki de bir kenara not edin. Bende gözüken haliyle(ki muhtemelen sizde de aynı olacak), şöyle bir link:

Not: Mac OS kullanıcılarının bu kısımda dikkatli olmasını öneririm, zira kendi bilgisayarlarında doğrudan çalıştırdıkları ikili dosyaları indirirlerse, Linux konteynırlarında bu dosyalar çalışmayacak. Bu yüzden platformu doğru seçtiğinizden emin olun.

Şu anlık bir eklenti kullanma planımız yok. İhtiyacınız olanları dokümantasyonlarına ve açıklamalarına bularak kolayca ekleyebilirsiniz. Şimdilik bu kısımla zaman kaybetmeden, herhangi bir eklenti olmadan ilerleyelim ve dosyayı indirelim.

İndirdiğiniz dosya gzip ile sıkıştırılmış tar arşivi(tar.gz) formatında, haliyle açarak başlamak gerekiyor. Dosyayı açınca içerisinde caddy adında çalıştırılabilir bir dosya bulacaksınız. Bu dosya, Go programlama dilinde yazılmış Caddy web sunucusunun statik linklenmiş versiyonunu içeriyor. Bunun bize sağladığı avantaj ise son durumda elde edeceğimiz imajın bağımlılıkları az oluyor ve haliyle imaj boyutu küçük oluyor. Bu dosyanın çalıştırılabilir olduğundan emin olalım. Aslında tüm bunlar için baştan sona şunları yazmak yeterli:

Çalıştırılabilir olduğunu da bildiğimize göre, denemek için kendi bilgisayarımızda ufak bir sunucuyu hemen kurabiliriz. Bunun için bu dosyayı çalıştırmak yeterli:

Bu noktadan sonra tarayıcınızdan localhost:2015 adresine girerek size bir 404 gelmesini beklemeniz gerekiyor. Veya hazır terminalde çalışırken curl ile deneyebiliriz:

Caddy varsayılanda bulunduğu dizini sunmaya başladığı için isterseniz hemen bir HTML dosyası oluşturup ona ulaşmaya çalışabilirsiniz. Bu, şu an konu dışında kaldığı için geçiyoruz.

Bu aşamaya kadar, normal şartlarda Caddy’i herhangi bir otomasyona tabii tutmadan indirdik. Artık devam edebiliriz.

4. Caddy’nin bir Docker konteynırında çalıştırılması – 1. Deneme

Normal şartlarda yapılabilecek ilk işlem, bu indirilen programın bir konteynır içine aktarılarak çalıştırılması olarak akla gelebilir. Şimdi bunu deneyelim, çalıştıralım ve iyi/kötü yanlarına bakalım. Basit bir Dockerfile oluşturarak içerisine şunları yazalım:

Şimdi bu Dockerfile iles imajı oluşturalım:

Oluşturduğumuz imajı çalıştırırsak, Caddy web sunucusunun başladığını görebiliriz:

Kontrol etmek için yine cURL kullanırsak(yapılan isteği ve gelen cevabı detaylıca görüntülemek için -v parametresini ekledim):

Bu süreci uygulamanın güzel yanı; klasik, alışıldık ve kolay uygulanır bir yapıda olması. Altyapıya Ubuntu’nun son sürümünü ekleyerek işin içinden kolayca kurtulmamızı da sayabiliriz. Kötü yanlarına bakacak olursak; Caddy’i elle indirdik, imajı her oluşturduğumuzda aynı Caddy versiyonu kalıyor ve imaj boyutu büyük. Şöyle bir kıyaslama yapacak olursak, Caddy’nin statik linklenmiş halinin boyutu 15MB civarında:

Ama biz statik linkli bir uygulama çalıştırmamıza rağmen imaj boyutu 135MB:

Haliyle durduk yere büyüyen imaj boyutlarıyla karşılaşıyoruz. Bu durum yerine göre çok sıkıntı olmayabiliyor ancak ufak uygulamaların büyük imaj boyutlarına sahip olması yine de birçok sebepten tercih edilecek bir yöntem değil. Bunu nasıl düzelteceğimize ilerki aşamalarda bakacağız.

5. Sürecin otomasyonu – 2. Deneme

Caddy için yaptığımız Docker imaj denemelerinin ilkinde, imaj boyutunun büyüklüğünden ve işlemin otomatik hale getirilmemesinden şikayet ettik. Şimdi sürecin otomasyonu için yeni bir Dockerfile hazırlayalım:

İmajı oluşturalım:

Şimdi bu imajın boyutuna bakalım:

En başta karşımızda olan sorun büyüdü ve 186 MB oldu. Caddy’nin güncel sürümünü edinebilmek için sürekli tekrardan indiriyoruz ama boyut konusunda bize zararı oluyor.

6. İmaj boyutunun düşürülmesi

İmaj boyutunu düşürebilmek için, Dockerfile oluştururken genellikle yapılan işlemlerden biri olan, apt’ın güncelleme sırasında indirdiği repo verilerini silmek olsa da, bizim senaryomuzda işi olabildiğince küçültmek var. Bunu yapmak içinse, hazır elimizde statik linklenmiş bir program varken, kullandığımız base imajını değiştirerek daha ufak bir tane seçebiliriz. Ufak denildiğinde akla çoğumuzda ilk Alpine Linux geliyor ancak aslında daha da küçük olan scratch imajını baz alacağız. Yalnız bu baz imajının içerisinde bir paket yöneticisi, paket güncelleyici veya paket yönetici yardımcı araçları bulunmayacağı için, Caddy’yi indirme işlemini direkt olarak bu imajı baz alarak yapamıyoruz.

Bir önceki aşamada gördüğümüz, güncel paketin indirilmesini içeren aşamayı şimdilik bir kenara bırakıp boyut düşürmeyle ilgilenelim. En başta indirdiğimiz caddy ikili dosyasını kopyalayarak çalıştıracak bir Dockerfile oluşturalım:

Şimdi bu dosyadan imajı oluşturursak:

Yeni imajın boyutuna bakacak olursak:

İmajın boyu 16.3 MB’ye indi. 16MB’lik Caddy ile birlikte düşünürsek oldukça düşük boyutlarda imajı oluşturmuş olduk.

7. Gözden Kaçanlar

Bu kısma kadar 2 farklı senaryonun birinde imajın oluşturulması sürecine uygulamanın güncel halinin alınmasını dahil ettik, ancak bu senaryoda boyut büyük oldu. Diğer senaryoda ise boyutu küçültürken uygulamanın son halini otomatik olarak alamaz durumda bir Dockerfile elde ettik.

Bu iki sebep zaten bildiklerimizdi. Ancak esas sorun, çalışacak programın başka programlara/dosyalara ihtiyacı olduğunda ortaya çıkıyor. Bunun en basit örneği ise, Caddy web sunucusunu vekil sunucu olarak kullanırken ortaya çıkıyor. Eğer arka tarafta yer alan uygulama sunucularına erişim TLS üzerinden yapılıyorsa, Caddy’nin de kök sertifikalara erişebilmesi gerekiyor, aksi takdirde bağlantıyı doğrulayamadığı için düzgün çalışamıyor.

Eğer kendi imzaladığınız(self-signed) sertifikalarınız yoksa, dağıtımlarla birlikte gelen kök sertifika paketindeki dosyalara ihtiyacınız olacak. Aslında bu da, yukarıda tanımladığımız ancak henüz birleştirme işlemini yapmadığımız iki aşamaya yeni bir tane daha sorun/çözüm ikilisi ekliyor.

8. Eski yöntemle çözüm

Sorunların sayısını 3 tane ana durumda toparladık. Şimdi bunları eski yöntemle tek seferde nasıl çözebileceğimize bakalım:

  • Bir Dockerfile üzerinde Caddy web sunucusunun güncel sürümü indirilir ve çalıştığı makineye bir Volume üzerinden aktarılır. (terminalden de yapılabilir)
  • TLS sertifikaları örneğindeki gibi, gerekli diğer dosyalar bir container üzerinde üretilir/indirilir ve ana makineye yine bir Volume üzerinden(veya alternatif yöntemlerle) aktarılır.
  • İlk iki aşamada aktarılan dosyalar düşük boyutlu bir base imaja eklenerek çalışabilir bir imaj oluşturulur.

Bu 3 temel aşama elle uygularken bizi zorlamaya çok müsait duruyor. Bizler de tabii ki bunu tek bir çalıştırmada sıra sıra yapacak Bash scriptleri yazabileceğimiz için, aklımıza ilk çözüm olarak bu aşamaları bir script içerisinde toparlamak geliyor. Yalnız her proje için bu scriptlerde farklılıklar olabileceği gibi, Dockerfile’lar üzerinde olabilecek değişiklikler de yine scriptlere her an yansımaya aday durumdalar. Yani bu haliyle yapı biraz kırılgan ve zorlayıcı duruyor. İşte bu aşamada devreye esas kahramanımız olan multistage build özelliği geliyor.

9. Yeni yöntemle çözüm

Multistage build, bir imajda kullanılacak programlar/dosyalar, uzun derleme süreçleri ve büyük boyutlu araçlarla üretiliyorsa bu süreci uygulama konteynırı yerine, geçici bir konteynırda tutma mantığını uyguluyor. Aslında bu şekilde bakınca eski yöntemden bir farkı gözükmüyor. Yalnız bunları aşama aşama scriptler üzerinden yaparak hata ayıklamak yerine, tek bir Dockerfile üzerinde yapmaya imkan sağlıyor. Lafı uzatmadan Dockerfile örneğini gösterip kod üzerinden açıklamaya devam edelim:

Bu dosyayı FROM ile başlayan satırlarla gruplara ayırırsak, elimizde 3 farklı grup kalıyor. FROM ile başlayan satırların sonunda karşımıza çıkan yeni yazım ise as İSİM şeklinde. Mesela build isimli aşamadan elde edilen imajda üretilen bir dosyayı kullanmak istediğimizde direk kopyalayıp kullanabiliyoruz.

İkinci kısımda ise, serfitikaların indirilmesini kolaylaştırması için(aslında bu uygulamada, örnek olması için) boş bir ubuntu imajında sertifikalar yükleniyor/güncelleniyor. Bunu son kısımda sertifikaları kopyalarken alınacak yer olarak söyleyeceğiz.

Son olarak, ilk 2 kısımdaki as İSİM şeklinde yer alan imaj oluştururken elde edilen sonuçlardan istenen dosyalar COPY komutuna –from=İSİM şeklinde verilerek ilgili kısımdan istenen dosyaların alınması sağlanıyor. İlk COPY komutu, derlenen Caddy ikili dosyasını alırken, ikinci COPY komutu sertifika otoritelerinin sertifikalarını alarak ilerliyor. Böylece istediğimiz kısımlarda içerikleri oluşturup sadece gerektiği kadarını tek bir imajda toplayabiliyoruz.

Şimdi oluşturduğumuz yeni Dockerfile’ı imaj haline getirelim:

Boyutu da hala küçük kalmış:

10. Sonuç

Bahsettiğimiz 3 aşamanın her birine stage deniyor. Normalde bir Dockerfile içerisinde bu şekilde tek aşama varken buna single-stage build derken, yeni haline de multi-stage build ismi veriliyor.

Not 1: Bu örnekte, uygulamanın derlenme aşamasını da Docker’a aktarmış olduk. Bu sayede güncel veya istenen etiketlere sahip Git branchları üzerinden test çalıştırarak başlatılan bir derleme süreci, paketlemenin sonunda oluşan imajda bu araçların hiçbiri olmadan temiz ve sade bir halde elde edilebilir.

Not 2: Bu yazıda, Go programlama dilinde yazılmış Caddy web sunucusunu özellikle seçtim, statik bağlanmış bir programın Docker tarafındaki uygulamasını görme ve kendi dokümantasyonunda yazılandan farklı mantıktaki bir uygulamayı bu kısma nasıl aktarabileceğimizi göstermeye çalıştım. Farklı bir örnek için kendi dokümantasyonuna da bakmanızı öneririm.

Sorularınızı yorum kısmından veya iletişim sayfasındaki e-posta adresi üzerinden iletebilirsin.