Docker & Docker Compose Püf Noktaları: Sorunsuz Konteynerleştirme

Docker & Docker Compose Tricks: Seamless Containerization

Cihat Solak
Intertech

--

Docker uygulamasını kurduğumuzda, Docker CLI ve Docker Compose CLI özellikleri de beraberinde gelir. Bu iki komut satırı arabirimi birbirine benzer, ancak aynı işlevi yerine getirmezler.

Docker Compose, çoklu konteyner uygulamalarını tanımlamak, yönetmek ve çalıştırmak için kullanılan bir araçtır.

Daha önceki Docker makalelerini görmemişseniz, linkleri kullanarak ulaşabilirsiniz.

Docker Compose CLI, bir araç (tool) olarak değerlendirilir ve bu araç, container ve image’larımızı basit bir şekilde yönetme olanağı sağlar. Normal şartlarda, tek bir container’ı ayağa kaldırmak için docker run komutunu kullanırız.

Docker Compose, tek bir komut ile birden fazla container’ı oluşturmayı, silmeyi veya durdurmayı sağlayabilmenin güzelliğini sunar. Aksi takdirde, birden fazla container oluşturmak için tek tek docker run komutu kullanarak uğraşmamız gerekecekti ki bu da oldukça zaman alıcı bir süreç olacaktı. İşte bu zorlu süreci ortadan kaldırmak ve daha basitleştirmek amacıyla karşımıza Docker Compose çıkmaktadır.

Docker Compose’u sadece geliştirme amaçlarıyla, yani yalnızca development ortamında kullanmaktayız. Development sürecinde birden fazla container ve image’yi yönetmek için faydalıdır. Şu anda akıllara gelebilecek bir sorunun farkındayım: “Peki, production ortamında ne yapacağız?”

Production ortamında genellikle Docker Swarm, Kubernetes gibi sistemler kullanılmaktadır. Docker Compose, teorik olarak production ortamında da kullanılabilir, ancak best practices açısından uygun değildir.

Docker Compose formatı YML formatında tanımlanır. Bu format, JSON formatına göre daha basit ve anlaşılırdır. Ayrıca, JSON formatındaki süslü parantezlerin yerine, YML formatında girintiler kullanılmaktadır.

docker compose version komutuyla birlikte versiyon kontrol edilebilir.

Docker Compose, çoklu konteyner uygulamalarını hızlı ve etkili bir şekilde yönetmek için kullanılan bir araçtır.

Docker Compose, docker’ın bir eklentisi olarak çalışır ve docker rest servisleriyle etkileşimde bulunmak için Docker CLI ile iletişim kurar. Bu sayede kullanıcılar (bizler), komutların ayrıntılarına veya REST servislerinin detaylarına hakim olmak zorunda kalmayız.

Dockerfile

Dotnet Web API Projesi oluşturalım ve IDE tarafından sunulan Dockerfile dosyasını inceleyelim.

Hedef işletim sistemini seçelim.

Dockerfile dosyasını inceleyelim.

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base 
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Medium.Microservice1.API.csproj", "Medium.Microservice1.API/"]
RUN dotnet restore "Medium.Microservice1.API/Medium.Microservice1.API.csproj"
COPY . ./Medium.Microservice1.API/
WORKDIR "/src/Medium.Microservice1.API"
RUN dotnet build "Medium.Microservice1.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Medium.Microservice1.API.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Medium.Microservice1.API.dll"]

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base

Dotnet runtime image’i kopyalanıp ‘base’ adında bir takma isim belirlenmiş.

WORKDIR /app

Image içerisinde app adında klasör oluşturulmuş. Klasör isminin önünde bulunan slash [ / ] işareti olması önemlidir. Çünkü slash, app adında bir klasör yoksa oluştur anlamını taşır.

EXPOSE 80

Container’ın 80 portu dış dünyaya açılmış. Eğer bu ifade olmasaydı docker içerisindeki farklı containerlar buradaki Medium.Microservice1.API projesine erişemezlerdi. Expose komutuyla birlikte docker içerisinde çalışan bu container’na farklı container içerisinde çalışan uygulamalar 80 portundan erişebilirler. Anlaşılan o ki, docker içerisindeki containerların birbiriyle hangi port üzerinden haberleşeceğini expose ile belirlenebilir. Eğer HTTPS üzerinden haberleşmesini isteseydik, expose 443 ile bunu halledebilirdik.

Dockerfile içerisindeki her bir satır docker tarafından cache’lenmektedir. Çünkü bizler docker build komutunu kullandığımızda değişmeyen satırlar cache’den geleceği için build süreci kısalacak ve image’ler daha hızlı oluşacaktır.

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build

WORKDIR /src

COPY [“Medium.Microservice1.API.csproj”, “Medium.Microservice1.API/”]

Medium.Microservice1.API.csproj isimli .csprojdosya Medium.Microservice1.API/ isimli klasöre kopyalanmaktadır. Dikkat ederseniz başında slash [ / ] işareti bulunmamaktadır. Eğer slash işareti son da değil de başta olsaydı src klasörü ile aynı seviyede ikinci bir klasör oluşurdu. Dolayısıyla .csproj dosyasının src klasörünün altında olmasını istiyorsak dosya isminin sonuna [ / ] işareti koymalıyız. Mevcutta slash işareti sonda olduğundan dolayı src klasörünün altına yeni bir klasör oluşturulacaktır.

RUN dotnet restore “Medium.Microservice1.API/Medium.Microservice1.API.csproj”

dotnet restore komutuyla beraber proje içerisindeki paketler kullanılabilir hale getirilmektedir. Restore komutu yüklü olan paketleri image içerisinde kullanılabilir hale getirmektedir. Bu işlemi tüm projeyi kopyaladıktan sonra yapabilirdik. Ancak docker her bir satırı ayrı ayrı cachelediğinden ötürü performans açısından restore işlemini farklı satırda yapmak best practices açısından uygun olacaktır.

COPY . ./Medium.Microservice1.API/

Nokta [ . ] ile birlikte copy komutu mevcut projeyi sağdaki dizinin içerisine kopyalar. Nokta ifadesi dockerfile dosyasının bulunduğu dizini ifade eder.

WORKDIR “/src/Medium.Microservice1.API”

Çalışma klasörü artık src dizini altındaki Medium.Microservice1.API klasörü oldu.

RUN dotnet build “Medium.Microservice1.API.csproj” -c Release -o /app/build

Buradaki slash [ / ] ile birlikte Medium.Microservice1.API/app/build dizinine .csproj dosyasını realese modda build etmektedir.

FROM build AS publish

build takma ismine sahip image’den publish takma ismiyle nitelendirilecek yeni bir sdk image’ı yaratılmış.

RUN dotnet publish “Medium.Microservice1.API.csproj” -c Release -o /app/publish /p:UseAppHost=false

Medium.Microservice1.API.csproj projesi /app/publish dizinine release modda publish edilmiş. Buradaki /p:UseAppHost=false komutu, publish edilen dosyalardan .exe dosyasına ihtiyaç olmadığını ve oluşturulmamasını ifade etmektedir. Çünkü projenin çalışması için .exe dosyasına ihtiyaç yoktur, docker .dll dosyasıyla çalışmaktadır.

FROM base AS final

Base takma ismine sahip dotnet runtime image’inden final isminde yeni bir image oluşturulmuş.

WORKDIR /app

Çalışma dizini app klasörü olarak belirlenmiş.

COPY — from=publish /app/publish .

Publish ismine sahip image içerisinde, app/publish dizininde yer alan tüm dosyalar mevcut image içerisindeki app klasörüne kopyalanmış.

ENTRYPOINT [“dotnet”, “Medium.Microservice1.API.dll”]

.dll dosyanını yardımıyla container oluşturulmuş. Çünkü docker’ın .exe dosyasına ihtiyacı bulunmamaktadır.

Dockerfile İle Container Oluşturalım

docker build -t mediummicroservice1:v1 . komutuyla birlikte yeni bir image oluşturuyoruz.

docker run -d -p 5080:80 --name microservice-container mediummicroservice1:v1 komutuyla birlikte yeni bir container oluşturalım.

İşte bu kadar! Unutmadan, docker üzerinde uygulamaların default environment’ı productiondır. Dotnet core’da 3 adet varsayılan ortam vardır. Production, Staging ve Development.

Docker Compose

Birden fazla image’den tek bir komut ile container’lar oluşturmak mümkündür. Özellikle mikroservis mimarisinde geliştirme yaparken, birden fazla servisi (örneğin, .NET uygulamalarını) tek bir komutla ayağa kaldırabilir, kapatabilir ve başlatabiliriz. Ayrıca, environment yapılandırmasına bağlı olarak şekillenebilir. Örneğin, production ortamında prod veri tabanına, test ortamında ise test veri tabanına bağlanabilir gibi yaklaşımlar mümkündür.

Dockerfile da herhangi bir değişiklik yapıldığında docker compose’u yeniden build etmek daha sağlıklı olacaktır. docker-compose up — build -d

soru: docker-compose.yml ile docker-compose.override.yml arasında nasıl bir ilişki vardır?

docker compose’ları farklı farklı ortamlar ile ayağa kaldırabiliriz. docker-compose.yml dosyası base işlemleri kapsar, docker-compose.override.yml ise farklı konfigürasyonlara göre şekillendirmek isteğimiz dosya olacaktır.

Bahsi geçen konuyu varsayılan olarak dotnet projelerinde sunulan appsetting.json yapılanması şeklinde düşünebiliriz. appsetting.json dosyasında base tanımlamaları yaparken, örneğin appsettings.staging.json dosyasında staging ortamına özel tanımlamaları yaparız.

docker-compose.yml ve docker-compose.override.yml dosyalarının 2 adet olması zorunlu değil, best practices’dir.

docker-compose.yml

#docker-compose.yml

version: '3.4'

services:
medium.microservice1.api:
image: mediummicroservice1-image
container_name: microservice1-container
build:
context: .
dockerfile: Medium.Microservice1.API/Dockerfile

medium.microservice2.api:
image: mediummicroservice2-image
container_name: microservice2-container
build:
context: .
dockerfile: Medium.Microservice2.API/Dockerfile

Servis tanımları (isimleri), docker içindeki mikro hizmetlerin birbirleriyle iletişim kurarken kullandıkları isimlerdir ve bu isimler, mikro hizmetler arasındaki iletişimin temelindeki gizli alan adları (DNS) gibidir.

#docker-compose.override.yml

version: '3.4'

services:
medium.microservice1.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5000:80"

medium.microservice2.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5010:80"

medium.microservice1.api servisinden medium.microservice2.api adresine http isteği yapmak istediğinizde url şu formatta olacaktır. http://medium.microservice1.api:5000:/api/book/1/detail gibi. (port rastgele yazılmıştır.)

“Context” kısmı, Docker Compose dosyasının bulunduğu dizini ifade eder. Bu, projenin kök dizini gibi düşünülebilir. Bu nedenle “context” değeri, Docker Compose’un projenin hangi dizininden Docker dosyalarını bulması gerektiğini belirtir. Örneğin, “.” (nokta) kullanıldığında, Docker Compose, bu dosyaların aynı dizinde olduğunu anlar. “dockerfile” kısmı ise belirli bir servisin Docker dosyasının nerede bulunacağını gösterir. Bu şekilde, Docker Compose, ilgili Docker dosyasını projenin referans noktasına göre bulur.

Yani özetle, bu yapılar, mikro servislerin birbirleriyle iletişim kurmasını sağlayan ve Docker Compose’un hangi dosyaları ve nereden kullanacağını belirten önemli ayarlar içerir.

Build, Create, Start, Stop, Rm Komutları

Build: Docker dosyalarını (Dockerfile) kullanarak imajlar oluşturan bir işlemi gerçekleştirir. Docker Compose ise bu işlemi, servislerin nasıl oluşturulacağını ve yapılandırılacağını tanımlayan bir araçtır.

Create: Docker imajları oluşturulduysa, bu komut containerları oluşturmakla ilgilidir. Ancak containerları hemen çalıştırmaz.

Start: Containerları çalıştırarak ayağa kaldırır.

Stop: Çalışan containerları durdurur.

RM: Durdurulmuş containerları siler.

Up (build,recreate,start) ve Down (stop,remove) Komutları

Docker Compose’ta “up” komutu, Docker imajlarını yeniden oluşturur, yeni containerları oluşturur ve bunları başlatır. Yani, mevcut durumdan sıfırdan bir çalışma ortamı oluşturur.

Öte yandan, “down” komutu, çalışan containerları durdurur ve ardından bu containerları siler. Yani, “up” komutuyla oluşturulan çalışma ortamını tam tersine çözer ve temizler.

docker compose up 
docker compose up -d #-d ifadesi container'a bağlanmadan (detach) anlamına gelir
docker compose down

Pause/Unpause & Stop Komutları

Stop: Bir konteynırı durdurduğunuzda, konteynırın kullandığı bellek (memory) serbest bırakılır ve bellekte tutulan veriler silinir. Yani, konteynır durduğunda bellekteki tüm bilgiler temizlenir.

Pause/Unpause: Konteynırı “pause” durumuna aldığınızda, bellek serbest bırakılmaz ve bellekteki veriler korunur. Konteynırı “unpause” durumuna getirdiğinizde, konteynır kaldığı yerden devam eder. Ancak konteynırı tamamen “stop” durumuna getirdiğinizde, bellekteki veriler silinir ve konteynırı tekrar başlattığınızda sıfırdan başlar.

Exec Komutu

Bir konteynırın içinde komut çalıştırmamıza olanak tanır. Bu genellikle, bir konteynırın içindeki işletim sistemine bağlanmak veya içeride değişiklikler yapmak istediğimizde kullanılır.

docker compose exec medium.microservice1.api /bin/bash

Scale Komutu

Docker Compose’un “scale” özelliği, tek bir konteynırın birden fazla örneğini aynı anda başlatmamıza ve her bir örneğin sayısını ayrı ayrı belirlememize olanak tanır. Örneğin, “A” servisinden 5, “B” servisinden ise 3 örnek aynı anda başlatmak istediğimizde, bu örnekleri Docker kendi isimlendirmeleriyle oluşturur. Yani, Docker Compose dosyasında her bir örnek için ayrı ayrı konteynır adı belirtmemiz gerekmez.

docker compose up --scale medium.microservice1.api=3 --scale medium.microservice2.api=2

Belirli bir port aralığını kullanarak ölçeklendirmek isterseniz, port çakışma riski artabilir. Bu nedenle, Docker’a port atama görevini tamamen bırakmak daha iyi bir çözüm olabilir. Docker, rastgele boş portlara atanarak, port çakışması olasılığını daha düşük tutar.

Push Komutu

Tüm bu imajları tek bir komutla Docker Hub gibi herhangi bir konteynır kayıt defteri servisine göndermemizi sağlar. Bu, Docker Hub, Azure Container Registry, AWS Container Registry gibi farklı hizmetler olabilir. Özetle, Docker Compose ile birden fazla imajı tek bir işlemle gönderme kolaylığını sağlar. Docker komutu ile “docker push” kullanıldığında, sadece tek bir konteynırı gönderebilirken, Docker Compose ile tüm bu imajları aynı anda gönderebilirsiniz.

BONUS (Windows Cihazlar)

Docker kullanırken bilgisayarınız aşırı yavaşlıyor mu? ~/.wslconfig dosyasını oluşturmak ve WSL 2 için belirli bir RAM ve işlemci kaynağı ayarlamak, performansı ve kaynak kullanımını optimize etmeye yardımcı olabilir.

Powershell aracılığıyla aşağıdaki komutu çalıştırınız.

notepad "$env:USERPROFILE/.wslconfig"

Bu komutu çalıştırdığınızda, eğer dosya mevcut değilse, “Bu dosya bulunamadı. Yeni bir dosya oluşturmak ister misiniz?” sorusuyla karşılaşacaksınız. Evet’e tıklayın.

[wsl2]
memory=2GB
processors=2

Bu örnekte, WSL 2 için 2 GB RAM ve 2 işlemci çekirdeği ayarlandı. İhtiyacınıza göre bu değerleri artırabilir veya azaltabilirsiniz.

Bu adımları takip ederek WSL 2'nin RAM tüketimini sınırlayabilir ve performansını optimize edebilirsiniz. İhtiyaçlarınıza göre RAM ve işlemci ayarlarını istediğiniz şekilde özelleştirebilirsiniz.

--

--