Sağladığı esneklik ve ölçeklenebilirlik ile konteynerleştirme teknolojilerinin önemi gün geçtikçe artmaktadır. Konteynerleri çalıştırmak için ihtiyacımız olan Image’ları genellikle popüler Image Repository’si Docker Hub’dan sağlamaktayız. Peki, kendi docker Image’ımızı oluşturmak için ne yapmalıyız? Docker, bir Image’ı oluşturmak için gerekli tüm komutları içeren Dockerfile adı verilen text dosyasını okumaktadır. Dockerfile’da yer alan bu talimatlar ile Image’lar otomatik olarak oluşturulmaktadır. Docker Image’ları, her biri bir Dockerfile talimatını temsil eden Read-Only Layer’lardan oluşur. Layer’ların her biri bir önceki Layer’daki değişikliğin deltasıdır. Aşağıdaki Dockerfile’ı örnek alacak olursak:
Resim-1
Her talimat bir Layer oluşturmaktadır.
FROM talimatı ile ubuntu:18.04 Image’ı temel alınmaktadır.
COPY talimatı, Docker istemcinizin geçerli dizininden dosyaları kopyalamaktadır.
RUN talimatı, uygulamamızı ‘make’ ile yapılandırmaktadır.
CMD talimatı, konteyner içerisinde ‘python /app/app.py’ komutunu çalıştırmaktadır.
Resim-2
Image’ımız çalıştırılıp bir konteyner oluşturulduğunda artık elimizde Read Only olmayan bir konteyner Layer’ı olacaktır. Yeni dosyalar ekleyip çıkarma, mevcut dosyaları düzenleme, komut çalıştırma vb. işlemlerimiz bu çalışan konteyner Layer’ında yapılabilmektedir.
Github üzerinden yaklaşık bir milyon Dockerfile’a ulaşabilmekteyken bu Dockerfile’ları kullanırken yorumlayabilmek, uygulamamızın güvenliği ve CI/CD süreçlerinin verimliliği açısından önemlidir. Mevcut Dockerfile’ları nasıl iyileştirebiliriz, Dockerfile yazarken Image boyutunu ve Build süresini azaltarak, güvenliği ve sürdürülebilirliği ise artırarak verimliliği artırmak için nelere dikkat etmeliyiz, hep beraber maddeler halinde öğrenelim.
Dockerfile’da Verimliliği Artırmak İçin Nelere Dikkat Edilmelidir?
-
Build süresini azaltmak için Cache kullanılmalıdır.
Geliştirme döngüsünde Dockerfile bir kez yapılandırıldıktan sonra kod değişiklikleri yapılıp yeniden yapılandırılırken Cache kullanmak Build süresi açısından önem taşımaktadır. Caching, gerekli olmayan derleme adımlarının tekrar tekrar çalıştırılmamasını sağlar.
-
Caching için talimatların sıralamasına dikkat edilmelidir.
Dockerfile’da kullanılan talimatların sıralaması Caching açısından önemlidir. Dosyalar değiştiğinde veya Dockerfile dosyanızda talimatların sıralaması değiştiğinde Cache, değişiklik olan adımdan sonrası için kırılacaktır. Bu yüzden sıralama, dosya içeriği ve kendisi en az değişenden en sık değişen adıma doğru olmalıdır. Aşağıdaki Dockerfile örneğinde de gördüğümüz gibi COPY talimatı aracılığıyla Image içerisine kopyalanan dosyaların içeriğinin değişme durumu diğer talimatların değişme durumundan daha sık olacağı için bu talimatın diğerlerinin altına alınması Caching’i optimize ederek Build süremizi azaltacaktır.
Resim-3
-
Sadece gerekli dosyalar kopyalanmalıdır.
Mümkün olduğunca ‘COPY .’ talimatından kaçınmalıyız. Gereksiz olarak kopyaladığımız dosyalardan herhangi birinde yaptığımız değişiklik yine Caching’i kıracaktır ve sonraki adımlar için de Caching’den yararlanmamıza engel olacaktır. Build süremizi azaltmak için Image’ımızın içerisine dosya kopyalarken mümkün olduğunca cimri olmalıyız. Aşağıdaki Dockerfile örneğinde gördüğümüz gibi tüm dizini kopyalamaktansa bir sonraki adım için gerekli olan .jar dosyasını kopyalamamız yeterli olacaktır.
Resim-4
-
Layer sayısı minimize edilmelidir.
Docker’ın eski versiyonlarında Layer sayısını minimize etmek performans açısından çok daha fazla önem taşımaktaydı. Ancak artık sadece FROM, RUN, COPY ve ADD talimatları Layer oluşturmaktayken diğer talimatlar geçici ara Image’lar oluşturmaktadır ve Build boyutunu artırmamaktadır. Layer oluşturan bu talimatların kullanımını mümkün olduğunca aza indirmek Build süremizi azaltacaktır.
-
apt-get update & install komutları tek bir RUN talimatında kullanılmalıdır.
Her bir RUN talimatı ayrı Cache birimi olarak değerlendirilir. Paket yöneticilerini kullanarak paketlerimizi yükleyeceğimiz zaman Update ve Install işlemlerimizi tek RUN talimatında yapmak iki işlemi de tek bir Cache’lenebilir Layer üzerinde gerçekleştireceğinden, eski paketlerin yüklenmesi riskini ortadan kaldıracaktır.
Resim-5
-
Gereksiz bağımlılıklar kaldırılmalıdır.
Debug için gerektiğinde zaten çalışan konteyner üzerinde kurulabilmekte olan vim, curl vb. araçları mümkün olduğunca kurmamalıyız. Apt gibi paket yöneticileri önerilen paketleri otomatik olarak yüklemektedir. İhtiyaç olunmayan paketlerin yüklenmemesi için –no-install-recommends Flag’ini kullanmalıyız.
Resim-6
-
Paket yöneticisi Cache’i kaldırılmalıdır.
Paket yöneticileri kendi Cache’lerini Image içerisinde tutmaktadırlar. Image boyutunu azaltmak için paketleri yükleyen RUN talimatına bu Cache’i temizletebiliriz.
Resim-7
-
.Dockerignore dosyası kullanılmalıdır.
Docker CLI, içeriği Docker Daemon’a göndermeden önce yapılandırmayla ilgili olmayan dosyalarımızı dahil etmemek için ana dizinde .dockerignore dosyası aramaktadır. .dockerignore dosyası ADD ve COPY talimatlarını kullanırken, gereksiz dosyaları da Image’ımıza ekleyip Image boyutunun büyümesini önlemektedir.
-
Mümkün olduğunca Official Image’lar kullanılmalıdır.
Best Practice’ler uygulanarak kurulum adımlarının birçoğu tamamlandığından Official Image kullanmak sürdürülebilirlik açısından zaman tasarrufu sağlamaktadır.
Resim-8
-
Latest Tag’i yerine spesifik Tag’ler kullanılmalıdır.
Docker Hub’daki Image’larda zaman içerisinde değişiklikler olmaktadır ve Latest Tag’i otomatik olarak değişiklik yapılan son Image’ı kapsamaktadır. Bu değişikliğin uygulamamızı nasıl etkileyeceğini öngöremediğimizden Latest Tag’i yerine spesifik Tag’ler kullanmalıyız.
Resim-9
-
Tutarlı bir ortamda kaynak koddan Build işlemi yapılmalıdır.
Makalemizde buraya kadar verdiğimiz Dockerfile örnekleri, Jar Artfact’ini kendi host’unuzda yapılandırdığınızı varsayarak hazırlanmıştı. Bu şekilde konteyner tarafından sağlanan tutarlı ortamın avantajlarından yararlanamamaktayız. Örneğin, eğer Java uygulamanız belirli kütüphanelere bağlıysa uygulamanızın hangi bilgisayarda kurulduğuna bağlı olarak istenmeyen tutarsızlıklar ortaya çıkabilir.
Öncelikle uygulamamızı yapılandırmak için gereklilikleri belirlemeliyiz. Örneğimizdeki Java uygulamasında Maven ve JDK kullanılmaktadır. Bu durumda openjdk yerine Docker Hub’da yer alan ve JDK içeren bir Maven Image’ı kullanabiliriz. Daha fazla bağımlılık yüklememiz gerekiyorsa bunları RUN talimatıyla gerçekleştirebiliriz.
-
Build bağımlılıklarını ortadan kaldırmak için Multi-Stage Build kullanılmalıdır.
Multi-Stage Build’lerde Dockerfile dosyasında birden fazla FROM talimatı kullanmaktayız. Her FROM talimatı farklı bir Base Image kullanırken yeni bir Build Stage’i başlamış olur. İstediğimiz artifactleri bir Stage’den diğerine kopyalayabiliyorken, istemediklerimizi geride bırakıp son Image’a dahil etmeyebiliyoruz.
Multi-Stage build’ler Layer ve dosya sayımızı düşürmeden son Image boyutumuzu önemli ölçüde azaltmaktadır.
Resim-10
Daha hızlı deployment ve verimli bir CI/CD süreci için nasıl daha iyi Dockerfile yazabileceğimizi ve elimizdeki Dockerfile’ları nasıl iyileştirebileceğimizi hep beraber incelemiş olduk.
Bu konuyla ilgili sorularınızı alt kısımda bulunan yorumlar alanını kullanarak sorabilirsiniz.
Referanslar
DockerCon Talk Recording: Dockerfile Best Practices
DockerCon Talk Slides: Dockerfile Best Practices
Docker Docs: Dockerfile References
Intro Guide to Dockerfile Best Practices
TAGs: Dockerfile nedir, Dockerfile, Docker Image, Dockerfile Best Practices, Nasıl daha verimli Dockerfile yazılır, Dockerfile yazılırken dikkat edilmesi gerekenler, dockerignore, multi-stage build, Dockerfile Caching, Build Cache, Containers