ekrem özer

her yerde olan şeyler.

.Net Core Docker Kullanımı

Merhaba arkadaşlar bu yazımızda docker'ın temelleri ve .net core uygulamamızı docker üzerinden nasıl sanallaştırabileceğimizi anlatmaya çalışacağım. Docker bir sanallaştırma açık kaynaklı bir sanallaştırma teknolojiisidir, sunucumuzda birden fazla uygulamayı birbirinden izole bir şekilde bağımsız container'larda ayağa kaldırmamızı sağlar.

Bir uygulamayı Dockerize edebilmek için ilk olarak root dizininde bir Dockerfile dosyası oluşturmamız gerekiyor. Bu dosyanın bir uzantısı yoktur ve ilk harfi büyüktür. Dockerfile içerisinde komutlarımızın olduğu bir dosyadır. Bu komutlar uygulamamızın ihtiyaçlarını ve nasıl bir ortamda çalışacağını belirler. Örneğin bir .net core uygulamasının çalışabilmesi için içerisinde .net core uygulamasını çalıştırabilecek gerekli kütüphanelerin olduğu bir işletim sistemi ve uygulamamızın publish dosyalarına ihtiyaç vardır. Bu bileşenleri Dockerfile'da tanımlıyoruz.

Dockerfile'da gerekli komutları yazdıklar sonra Dokcer build komutuyla image dosyamızı oluşturuyoruz. Docker Image içerisinde yazdığımız komutlara göre katmanları barındıran bir dosyadır. Her komut satırı bir katmana karşılık gelir. Docker Image dosyası read only bir dosyadır. 

Yukarıda ki örnekte 4 satırdan oluşan bir Dockerfile'ın oluşturduğu Docker Image'ın bir örneğini görüyoruz. En üsteki katman ise run komutuyla instance aldığımızda docker'ın eklediği katman. Bu katman okunabilen ve yazılabilen katmandır, Uygulamamıza yüklediğimiz  veya oluşturduğumuz dosyaların tutulduğu katmandır. Container'ı sildiğimizde veya kapattığımızda bu dosyalarda silinmiş olur. Data volume ile bu durumun önüne geçebiliriz.

Build komutuyla Image'ımızı aldıktan sonra Docker run komutuyla Docker Container'ımızı ayağa kaldırabiliriz. Container oluşturduğumuz Image'ların çalışan instance'ıdır. Aynı image üzerinden birden fazla container ayağa kaldırabiliriz.

Yukarıdaki görselde aynı image üzerinden alınmış birden fazla instance'ı görüyoruz. Image dosyamız sabit ancak her instance aldığımızda Docker okunabilir/yazılabilir bir katman daha eklelip yeni bir container ayağa kaldırıyor.

Docker CLI Nedir?

Docker CLI Docker ile haberleşmemizi sağlayan komut satırıdır. Docker birden fazla parçadan oluşan bir yapıdır. Bilgisayarımıza Dokcer'ı kurduğumuzda 2 farklı araç yüklenir. Bunlardan bir tanesi server tarafındanki tool olan Docker Daemon ve client tarafındaki tool olan Docker CLI'dır. Docker CLI, Docker daemon ile bir rest api sayesinde haberleşir. Docker CLI Dokcer ile haberleşmemize yardımcı olan bir komut satırıdır.

Docker Registry

Docker registry Image'larımızı kaydedebileceğimiz bir depodur. Yukarıdaki görselde dolabı registy olarak düşünebilirsiniz. Dolabın içindeki kutuların hepsi birer image olarak saklanır. Sizler bu image'lardan istediğinizi alıp kendi uygulamanızı ayağa kaldırabilirsiniz. Image'ların her biri ayrı bir amaca hizmet etmektedir. Örneğin .net core uygulaması için kullanacağınız bir image, nodejs uygulamalarınız için ayrı bir image'ı registry'den çekebilirsiniz. Ayrıca image'ları registry'den çekebildiğimiz gibi kendi uygulamamızın image'ını da registry'e atarak güvenli ve sürdürülebilir olmasını sağlayabiliriz.

Kullanabileceğiniz Registy Servisleri

Microsoft Resmi Docker Image'ları

Microsoft'un image'larına erişebilmek için öncelikle hub.docker.com adresine üye olup giriş yapıyoruz. Giriş yapıp explorer sayfasına girdiğimizde public olan tüm image'lara buradan erişebiliyoruz.

Arama kısmına asp.net core yazıp arattığımızda Microsoftun resmi imagelarına ulaşabiliyoruz. Örnek olarak ASP.NET Core 3.1 Runtime image'ına girdiğimizde detay sayfasında image'ı indircebileceğimiz CLI kodunu görebiliyoruz.

docker pull mcr.microsoft.com/dotnet/aspnet

Bu image'ı indirip üzerinde kendi uygulamızı ekleyerek oluşturuğdumuz yeni image'ımızından dilediğimiz kadar container ayağa kaldırabiliriz.

Docker Kurulumu

Öncelikle bu adrese girip Docker'ın windows için olan kurulum dosyasını indirip kurulumu tamamlıyoruz. Kurulum arayüzünü gerek olmadığı için detaylı olarak anlatmayacağım. Windows işletim sistemi üzerine kurulan docker'larda linux işletim sistemine ait container'ları kullanabiliyoruz ancak linux üzerine kurulu docker'larda windows container'larını kullanamıyoruz. Kurulumu tamamladıktan sonra uygulama üzerinden dockerhub bilgilerimizle giriş yapmamız gerekiyor.

WSL 2 installation is incomplete Hatası

Kurulum yaptıktan sonra WSL 2 installation is incomplete hatası alırsanız aşağıdaki linkle bulunan WSL uygulamasını kurup  bilgisayarınızı yeniden başlatmalısınız.

https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi

Kurulum doğru bir şekilde tamamlandıktan sonra dokcer programının arayüzü v4.10 sürümüyle birlikte aşağıdaki gibi gelmektedir.

.Net Core Console Uygulamasını Dockerize Etme

İlk örneğimizde temel seviyede bir console uygulaması oluşturup onu nasıl dockerize edeceğimizi inceleyeceğiz. NetCoreDocker adında bir solution oluşturup içerisine NetCoreDocker.Console adında bir console projesi ekliyorum. Program.cs in içine basit bir for döngüsü yazarak projemi derliyorum. Sonrasında projemi publish ederek projemin release dosyalarını hazırlıyorum.

Projemi dockerize etmek için ilk önce root dizinine Dockerfile adında bir dosya ekliyorum, bunu için projeme sağ tık add dedikler sonra Docker Support... a tıklıyorum.

Ardından çıkan pencerede Linux işletim sistemini seçiyoruz, windows container üzerinde de çalışabilirsiniz ancak core uygulamaları cross platform olduklarından dolayı linux üzerinden ilerleyeceğiz.

OK dedikten sonra uygulama bizim için root dizine bir Dockerfile dosyası ekleyecektir.

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["NetCoreDocker.Console/NetCoreDocker.Console.csproj", "NetCoreDocker.Console/"]
RUN dotnet restore "NetCoreDocker.Console/NetCoreDocker.Console.csproj"
COPY . .
WORKDIR "/src/NetCoreDocker.Console"
RUN dotnet build "NetCoreDocker.Console.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "NetCoreDocker.Console.csproj" -c Release -o /app/publish

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

Uygulamanın defaullt gelen kodları yukarıdaki gibidir, ancak biz sıfırdan adım adım ilerleyerek kodlarımızı kendimiz yazacağız. Projemi Net 6 da yaptığım için dokcerize etmek için bana Asp.NET Core Runtime imajı lazım bunu da docker hub üzerinden Microsoftun yayınladığı resmi imajı kullanarak temin ediyorum.

https://hub.docker.com/_/microsoft-dotnet-aspnet

Yukarıdaki linki tıklayarak imajın yolunu kopyalıyorum.

mcr.microsoft.com/dotnet/aspnet:6.0

Imaj yolunun sonunda ki :6.0 etiketi kullanmak istediğimiz sürümü ifade eder, eğer etiket kullanmazsan o an yayında olan son sürümü kullanır. Şimdi adım adım Dockefile dosyamızı oluşturalım.

FROM mcr.microsoft.com/dotnet/aspnet:6.0

FROM komutuyla beraber imajın yolunu yazarak kullanacağımız imajı belirtiyoruz.

WORKDIR /App

WORKDIR Komutuyla beraber imajın içine App adında bir klasör oluşturuyoruz.

COPY bin/Release/net6.0/publish /App/

COPY Komutuyla beraber release dosyalarımızı WORKDIR komutuyla bebarer oluşturduğumuz App klasörüne kopyalıyoruz.

ENTRYPOINT ["dotnet","NetCoreDocker.Console.dll"]

ENTRYPOINT Komutuyla beraber container ayağa kalktığı zaman çalışacak olan komutu veriyoruz. "dotnet" ile .net cli komutu çalıştıracağımızı belirtiyoruz. "NetCoreDocker.Console.dll" ilede cli içinde çalıştıracağı komutu vermiş oluyoruz. Uygulamayı Docker kendi içinde çalıştıracağı için exe değil de dll dosyasının adını veriyoruz.

FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY bin/Release/net6.0/publish /App/
ENTRYPOINT ["dotnet","NetCoreDocker.Console.dll"]

Dockerfile'ımızın son hali yukarıdaki gibidir, 4 satır kod ile  birlikte imajımızında 4 tane katman oluşturuluacak ve en üsttede docker yazılabilir bir katman ekleyecek. Böylece 5 katmanlı bir imajımız olmuş olacak.

Imaj oluşturmak için dosyamız hazır. Tools>Command Line>Developer Command Line'a tıklayarak power shell komut ekranını açıyoruz.

Ardından açılan ekranda cd netcoredocker.console komutuyla projemin olduğu dizine giriyorum ve docker build cli komutuyla imajımı oluşturuyorum.

docker build -t netcoreconsoleapp .

docker build yazdıktan sonra -t parametresiyle imajımın adını verdim, imaj isimleri küçük harfle yazılmak zorunda. Ardından bir boşluk bırakıp Dockerfile dosyamın yolunu verdim. PowerShell'in içinde olduğu dizin ile Dockerfile dosyamın içinde olduğu dizin aynı olduğu için sadece .(nokta) yazmam yeterli oldu. Son olarakta enter diyerek imajımı oluşturdum.

Eğer imajıma bir etiket atamam gerekirse imaj ismini verdikten sonra iki nokta üstüste kullanarak etiketimi verebilirdim.

docker build -t netcoreconsoleapp:v1 .

Yukarıdaki komutta imajıma v1 etiketi atatım, aşağıdaki örneklerde etiketsiz imajlardan ilerleyeceğim ancak siz gerçek hayatta kullanmak isterseniz sadece imajın sonunda etiketi eklemeniz yetecektir.

Şimdi docker images komutuyla oluşan imajımı kontrol ediyorum

Yukarıda görüldüğü gibi imaj dosyam oluşmuş. Şimdi oluşturduğum imaj dosyasından bir container oluşturacağım.

docker create --name netcoreconsoleapp_container netcoreconsoleapp

docker create komutuyla birlikte name parametresiyle container'ımın adını verdim, bir boşluk bırakarak bu container'ı hangi imajdan oluşuracağını söyledim ve enter'a bastım.

Görüldüğü gibi container'ımızı oluşturdu ve bir ID atadı. Docker CLI üzerinden docker ps -a komutuyla oluşturduğum container'ları listeyelebilirim.

docker ps -a komutu tüm container'ları listelerken, -a parametleri olmadan yazdığımız komut docker ps sadece çalışan container'ları listeler.

Şimdi docker start komutu ile container'ımızı ayağa kaldıralım.

docker start netcoreconsoleapp_container

docker start yazdıktan sonra hangi containeri ayağa kaldıracağımızı yazıyoruz ve enter'a basıyoruz.

docker ps komutuyla kontrol ettiğimizda Up 5 Seconds durumunda olduğunu görüyoruz.

Çalışan containerimizi durdurmak içinse stop komutunu kullanıyoruz.

docker stop netcoreconsoleapp_container

docker stop yazdıktan sonra durdurmak istediğimiz container'ın adını veriyoruz. Container'ımız ayaktayken console uygulamamızın çıktısını görmek için attach komutunu kullanıyoruz. Öncelikle durdurmuş olduğum containeri tekrar start komutuyla ayağa kaldırıyorum, ardından 

docker attach netcoreconsoleapp_container

docker attach yazdıktan sonra çıktısını görmek istediğim container'ın adını yazıyorum ve enter'a basıyorum.

PowerShell ekranımda projemde yazdığım kodların çıktısını görebiliyorum. 

Docker CLI Komutlar

Yukarıdaki örnekler de build, create, images, ps, ps -a, start, stop ve attach komutlarını gördük, şimdi ise docker işlemlerinde kullanabileceğimiz bazı komutları örneklerle inceleyelim.

Docker Run Komutu

Docker run komutu creta ve start komutlarını ayrı ayrı yazmak yerine bir containeri oluşturup ardından ise çalıştırmaya yarayan cli komutudur.

docker run --name netcoreconsoleapp_container_run netcoreconsoleapp

docker run yazdıktan sonra --name parametresi ile container'ımın adını yazdım ve bir boşluk bırakarak container'ımı hangi imaj ile oluşturacağımı belirttim ve enter'a bastım.

Yukarıdaki çıktıda görüldüğü gibi run komutunu kullanmadığım halde container oluştu, ayağa kalkıp çalışmaya başladı ve attach oldu.

Docker Run --rm Komutu

Bu komut ise run komutuyla aynı işi yapar ancak ilave olarak aldığı --rm parametresiyle birlikte containier'ımızı durdurduğumuzda container'ın docker üzerinden durdurup silinmesine yarar.

docker run --rm --name netcoreconsoleapp_container_run netcoreconsoleapp

Ben bu komutu çalıştırmadan önce netcoreconsoleapp_container_run isimli container'ımı durdurup sildim, böylelikle aynı isimde yeni bir container oluşturabildim.

Docker rmi

docker rmi komutu imaj silmek için kullandığımız komuttur, imaja bağlı olan container'ları durdurduktan sonra bu komut ile imajı silebilirsiniz.

docker rmi netcoreconsoleapp

Dockre rmi/rm --force

Yukarıda anlattığım rmi ve rm komutlarını force ile de kullanabiliriz. Normalde çalışan bir container'ı silemeyiz, önce durdurmamız gerekiyor. Ancak --force parametresi ile çalışan bir container'ı silebiliriz.

docker rm netcoreconsoleapp_container --force

Aynı şekilde bir imaja bağlı bir veya daha fazla container varsa silemeyiz. --force parametresi ile imaja bağlı container'ları durdurduktan sonra silebilriz. Ancak container ayakta ise --force parametresiyle dahil imajı silemeyiz.

docker rmi netcoreconsoleapp --force

Docker Pull Komutu

docker pull komutu docker hub üzerinden imaj çekmeye yarayan komuttur. Önceki örneklerde .net runtime imajını kullanarak ilerlemiştik. Şimdi .net sdk imajını çekelim. Bu adreste microsoftun resmi imajını bulabilirsiniz. https://hub.docker.com/_/microsoft-dotnet-sdk/

Linkte tıkladığınızda sayfada pull etmek için kullanmanız gereken komutu görüyor olacaksınız.

docker pull mcr.microsoft.com/dotnet/sdk

PowerShell üzerinden komutu yazıp enter dediğimde ilgili imajı çekmiş olacağız.

Tüm katmamlar Pull complete olduğunda artık bu imajı kullanabiliyor olacağım. Karşısında Already exists yazan katmanlar halihazırda mevcut olduğu için onları tekrar indirmedi.

Pull işlemi tamamlandığında yukarıdaki gibi bir çıktı alıyorum. Artık dockerize işlemlerinde .net runtime'ın dışında .net sdk'da kullanabilirim. docker images yazarak imajlarımı kontrol ettiğimde .net sdk imajımı da listede görüyor olacağım.

,

Docker push Komutu

docker push komutu Docker Hub repository'sine imajına kendi imajlarınızı public veya private olarak göndermenizi yarayan koddur. Ücretsiz sürümde bir adet private imajınız olabilir. Docker CLI sadece dockerhub üzerinden pull veya push işlemleri yapmaktadır.

Öncelikle dockerhub sitesine girerek repositories kısmından kendimize bir repository oluşturuyoruz. https://hub.docker.com/repository/create

Repositorymin adını ve açıklamasını doldurduktan sonra public'i seçip create ediyorum.

Crete ettikten sonra beni yukarıdaki gibi bir sayfa karşılıyor ve sağ tarafında push işlemi yapabilmem için imaja vermem gereken ismi söylüyor. Bizim oluşturduğumuz imajın adın netcoreconsoleapp o halde bu imajı repositorye göndermek için bir işlem daha yapmam gerekiyor. Bu işlemi de docker cli üzerinden tag komutu ile yapacağız.

docker tag netcoreconsoleapp ekremozer/netcoreconsoleapp:v1

Yukarıdaki komutta docker tag dedim ve ardından etiketlemek istediğim imajı yazdım sonra bir boşluk bırakarak dockerhub üzerinden kopyaladığım etikek ismini yapıştırdım ve iki nokta üstüste diyerek versiyon etiketimi ekledim. Ardından enter dediğimde netcoreconsoleapp imajımı referans alan ekremozer/netcoreconsoleapp isminde yeni bir imaj oluşacaktır.

Yukarıda görüldüğü üzere docker images ile mevcut imajlarımı listeledim ekremozer/netcoreconsoleapp imajım geldi ancak imaj id'si netcoreconsoleapp imajının id'si ile aynı. Çünkü yeni oluşturuğum imaj netcoreconsoleapp imajını referans alıyor. İmajımı oluşturduğuma göre artık push işlemine geçebilirim.

docker push ekremozer/netcoreconsoleapp:v1

PowerShell üzerinden push komutunu yazıp enter dediğimde imajım repositorye upload olacaktır. Bu işlem imajın boyutuna göre uzun sürebilir. ö

Yukarıda görüldüğü gibi push işlemim tamamlandı, şimdi dockerhub üzerinden kontrol edelim.

İmajım v1 etiketiyle repositoryme push edilmiş. Artık aşağıdaki komutla imajıımı pull edebilirim.

docker pull ekremozer/netcoreconsoleapp:v1

Repository'e ikinci versiyonu atmak için etiketleme işlemini tekrar yapıp yeniden push etmeniz gerekmektedir. Bu işlemlerin de cli kodları aşağıdaki gibidir.

docker tag netcoreconsoleapp ekremozer/netcoreconsoleapp:v2

docker push ekremozer/netcoreconsoleapp:v2

Docker CLI komutları buraya kadar incelediklerimizle sınırlı değil tabiki, diğer tüm cli komutlarına aşağıdaki linkten ulaşabilirsiniz.

https://docs.docker.com/engine/reference/commandline/cli/

Asp. Net MVC Projesini Dockerize Etmek

Yukarıdak örneklerde basit bir console uygulamasını dockerize ettik. Şimdi solulitonuma .net core mvc projesi ekleyerek makalemize web tabanlı uygulamadan devam edelim. Hemen projemi oluşturup daha önce gösterdiğim şekilde projeme Dockerfile ekliyorum ve içerisini boşaltıyorum.

FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY bin/Release/net6.0/publish /App/
ENTRYPOINT ["dotnet","NetCoreDocker.Web.dll"]

Tıpkı console uygulamasında ki gibi publish alıp yolunu dockerfile'a veriyorum ve docker build komutuyla imajımı oluşturuyorum.

docker build -t netcorewebapp .

build komutumu yazıp enter diyorum ve imajım oluşuyor.

docker images komutuyla imajımı kontrol ediyorum

Oluşturdum imaj listede geldi. Şimdi bu imaj üzerinden bir container ayağa kaldıralım.

docker run -p 6006:80 netcorewebapp

Yukarıdaki komutla containerimi oluşturup ayağa kaldırdım. docker run komutunu yazarak -p parametresinyle container'ı dinleyeceğim portu belirtim iki nokta üstüste yazarakta container'da hangi portu dinleyeceğimi belirttim ve bir boşluk bırakarak containerimi oluşturacağımın imajı belirttim ve enter'a bastım.

Artık http://localhost:6006/ adresinden containerım da çalışan uygulamaya erişebileceğim.

Farkettiyseniz console uygulamadaki gibi containere bir isim vermedim, bu komutla docker continerime rastgele bir isim verdi. Eğer container'ıma ben isim vermek istesersem aşağıdaki komutla bunu yapabilirm.

docker run -d --name netcorewebapp_con -p 6007:80 netcorewebapp

Yukarıdaki komutta farklı olarak -d parametresini verdik, bu container ayağa kalktıktan sonra attach olmamasını sağlıyor ardından --name parametresi ile netcorewebapp_con olarak containerimin ismini belirledim ve bu sefer farklı bir portta ayağa kaldırmış oldum. docker ps komutuyla ayakla olan container'lerimi listeliyorum;

İki adet containerim ayakta ve son kısımda names alanına baktığımızda biri rastgele oluşturulmuş bir isim, diğer de benim verdiğim isim.

Dockerfile Üzerinde .Net Core CLI Komutları Kullanma

Yukarıdaki örneklerde uygulamanın publish halini alıp imajımıza o şekilde kopyalıyorduk. Her versiyonda publish alma işleminden kurtulmamız için dokcerfile dosyamıza .net core cli kodları yazabilirsek eğer docker önce uygulamamızı publish eder sonrada dosyaları kopyalayarak bizi bu külfetten kurtarabilir. Şimdi bu işlemleri nasıl yapacağımıza birlikte bakalım.

.Net Core CLI bu makalenin kosunu olmadığı için detaylara burada girmeyeceğim, ancak incelemek isteyenler olursa microsoftun resmi sitesinden inceleyebilir.

https://docs.microsoft.com/tr-tr/dotnet/core/tools/

Build işlemi yapabilmemiz için önceki örneklerde kullandığımız runtime imajı işimizi görmeyecektir. Bu işlem için .net sdk imajımı kullanmamız gerekiyor. Bu imajı yine dockerhub üzerinden Microsoftun yanınladığı resmi versiyonu indirerek kullanıyoruz.

https://hub.docker.com/_/microsoft-dotnet-sdk/

Bunun için dockerfile dosyamı tekrar yazıyorum.

FROM mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR /App
COPY . .
RUN dotnet restore
RUN dotnet publish NetCoreDocker.Web.csproj -c Release -o publishfolder
WORKDIR publishfolder
ENV ASPNETCORE_URLS="http://*:4600"
ENTRYPOINT ["dotnet","NetCoreDocker.Web.dll"]

Yukarıdaki kodları inlecelediğimizde;

İlk satırsa sdk imajımı nereden alacağımız yazıyor

İkinci satırda çalışacağımız klasörü seçiyoruz, WORKDIR klasör varsa içine girer yoksa oluştutup öyle girer

Üçüncü satırda ki ilk nokta dockerfile'ın olduğu dizini yani kopyalacak dosyaların root dizini, ikinci nokta ise kopyalanacak klasörün dizinini temsil ediyor. /App klasöründe olduğumuz için nokta işareti root'u ifade eder ve projenin dosyalarını /App klasörüne kopyalamaya yarar.

Dördüncü satır .net core cli kodumuz, uygulamamızı restore ediyor, eksik bi paket varsa nuget üzerinden indirme vs işlemleri.

Beşinci satır yine .net core cli kodumuz, bu kod publish işlemini yapıyor. donet publish'den sonra yazdığımız "NetCoreDocker.Web.csproj" ile hangi projenin publisini alacağımızı belirtiyoruz -c Release komutu ile publishin türünü belirtiyoruz, debug/release. -o publishfolder parametresinde ise publishi alacağımız klasörü belirtiyoruz. Proje publishfolder isimli klasöre publish edilecek. Klasörün başına / işaretini koymadığım için publish /app/publishfolder dizinine publis olacaktır.

Altıncı satırda publishfolder dizininin içine giriyoruz.

Yedinci satırda, uygulamamıza bir envoriment tanımlaması yapıyoruz. Container içindeki uygulama localhost üzerinden çalışacağı için doğrudan erişebilmemiz mümkün olmuyor. Bu nedenle container üzerindeki uygulamaya dışarıdan erişebilmemiz için bir port ataması yapıyoruz. http://*:4600 http üzerinde tüm urllerden 4600 portu üzerinden gelen isteklere bu containerimizdeki uygulama cevap verecek şekilde envoriment tanımlıyoruz.

Sekizinci satırda ise uygulamanın hangi dll üzerinden çalışacağını belirtiyoruz.

Dockerfile dosyamız hazır olduğuna göre imajımızı oluşturalım. Yine PowerShell üzerinden projemin olduğu dizine girip daha önce oluşturduğum netcorewebapp imajını bu sefer v2 olarak tekrar oluşturuyorum.

docker build -t netcorewebapp:v2 .

Komutu yazıp entera bastığımızda imajımız oluşuyor.

docker images komutuyla oluşan imajımı kontrol ediyorum.

İmajım sorunsuz oluştu, şimdi bu imaj ile yeni bir container ayağa kaldıralım.

docker run -d -p 6008:4600 --name netcorewebapp_build_con netcorewebapp:v2

Bu komutta yukarıdaki örneklerden farklı olarak port verirken dockerfile'da belirttirğimiz 4600 portunu dışarıdan 6008 portuyla dinleyeceğimizi belirttik ve imajımızın v2 etikeklisini kullandık. Kodu çalıştırdığımda ise containerim oluştu.

Artık http://localhost:6008/ adresimden uygulamama erişebiliyorum.

Docker MultiStage Build

Dockerfile üzerinde uygulamanızı oluştururken birden fazla imaj kullanmak isteseniz bunu multistage yöntemiyle yapabilirsiniz. Örneğin yukarıda ki örnekde .net core cli komutlarını çalıştırmak için .net sdk imajını kullandık bu imaj .net runtime'a göre boyutu hayli büyük bir imaj. Biz burada imajı hazırlarken .net cli kodlarını çalıştırdıktan sonra uygulamamızı .net core runtime imajına taşıyarak boyuttan ciddi oranda tasarruf elde etmiş olabiliriz. Şimdi bunun için hazırlamamız gereken dockerfile'ı inceleyelim.

FROM mcr.microsoft.com/dotnet/sdk:6.0 as build
WORKDIR /App
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish NetCoreDocker.Web.csproj -c Release -o publishfolder
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY --from=build /App/publishfolder .
ENV ASPNETCORE_URLS="http://*:4600"
ENTRYPOINT ["dotnet","NetCoreDocker.Web.dll"]

Yukarıdaki komutlarda daha önce görmediğimiz kısımlara değinelim. Öncelikle 3. satırda WORDIR komutundan sonra kullandığım COPY *.csproj . komutu ilgili projenin sadece .csproj dosyasını App klasörüne kopyalıyor ardından çalışan build komutu projeyi derliyor sonrasında da tüm dosyaları imaja kopyalıyor.

Bu işlemi optimizasyon açısından yaptık, şöyleki docker her imajdaki her layer'ı cache'de tutar ve bir değişiklik varsa yeniden oluşturur. Bir önceki örnekte doğrudan tüm dosyaları kopyalayıp sonra derliyorduk. Bu haliyle şunu yapmış olduk, örneğin projeme yeni bir controller eklediğim zaman .csproj'u kopyalayan layer proje seviyesinde bir değişiklik yaptığım için cache'den gelmeyecek ancak diğer copy komutunda ki katman cache'den gelecek buda projedeki tüm statik dosyalara ait katmanı tekrar oluşturmanın önüne geçecek. Tam tersi olarak düşünürsekte projemize bi .js dosyası eklediğimiz zaman .csproj kopyalama ve restore işlemi yapan katmanlar cache'den gelecek bir sonraki copy işlemini yapan katman tekrar oluşacak.

RUN dotnet publish... komutundan sonra da 7. satırda imajımıza .net runtime imajını eklemiş oluyoruz ve artık bir nevi sdk imajını kullanmıyoruz. Yine bu imaja app adında bir klasör ekleyerek 9. satırda .net sdk imajının içinde aldığımız publis dosyalarını publishfolder'dan runtime imajında ki app klasörüne kopyalıyoruz --from=build parametresinde ki build değeri ilk satırda .net sdk imajımıza verdiğimiz as build alias name'den geliyor. Kopyalama işleminden sonrada url tanımlaması ve derlenecek dll'i veriyoruz. Dockerfile'ımız hazır olduğuna göre netcorewebapp imajımın v3 versiyonunu bu imaja göre hazırlayalım.

docker build -t netcorewebapp:v3 .

Komutumu çalıştırdım. Şimdi docker images ile imajlarımızı kontrol edelim.

Yukarıda görüldüğü gibi v2 imajımın boyutu sdk olduğu için 779 MB v3 imajım ise 217 MB. Arada ciddi bir fark var.

.dockerignore Dosyası Oluşturmak

.dockerignore dosyası isminden de anlaşılacağı gibi tıpkı .gitignore mantığında çalışan bir dosyadır. Projenizdeki dosyaları imaja kopyalarken kopyalanmasını istemediğiniz dosya ve/veya dizinleri bu dosyada belirtiyorsunuz. Projemize dockerfile eklediğimizde solutionun root dizinine bu dosya otomatik olarak oluşuyor ve temel seviyede tüm igrone işlemlerini yapıyor, ilave olarak eklemek istediğiniz dosya ve klasörleri bu dosyayı açıp ekleyebilirsiniz. Dockerignore dosyasına solution explorer'dan aşağıdaki şekilde erişebilirsiniz.

Defaultta gelen dosya aşağıdaki gibidir.

 

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

Satırbaşlarında ki ** ifadesi projedeki tüm dizinlerde ara manasına gelmektedir, yani bu satır **/bin projedeki her hangi bir klasörün altındaki bin klasörünü imajımıza copy işlemi sırasında taşıma anlamına geliyor. **/Dockerfile*  bu satırda ise sondaki * ifadesi dockerfile'dan sonra hangi uzantı gelirse gelsin bu dosyayı taşıma anlamına geliyor. Son olarakta README.md şeklinde doğrudan taşınmasını istemediğiniz dosyanın tam adını verebilirsiniz.

Docker Volume

Docker volume container'larımızda verileri kalıcı olarak saklamamıza ve container'lar arası veri paylaşım işlemlerinde kullandığımız bir yöntemdir. Containerin kendi içinde sakladığı veriler container silindiği zaman kalıcı olarak silinir bunun önüne geçmek için verilerimizi ayrı bir yerde tutmamız gerekiyor. Bu noktada da docker volume yöntemi devreye giriyor.

Şimdi .net mvc projemize basit bir file upload sistemi yapıp, bu dosyaları docker volume üzerinde saklamayı yapalım. Projemin ana sayfasına bir file input ekleyerek resim yükleme işlemi yapıyorum ve aynı sayfada yüklenen resimleri gösterdiğim bir foreach döngüsü ekliyorum. Bu kısımları nasıl kodladığıma konuyla alakalı olmadığı için değinmeyeceğim.

Projemin ana sayfası yukarıdaki gibi oldu, iki adet resim yükledim ve projemin bu haliyle tekrar image oluşturuyorum.

docker build --no-cache -t netcorewebapp:v4 .

netcorewebapp imajımın v4 versiyonunu oluşturdum, bu sefer --no-cache parametlesiyle tüm layerları tekrar oluşturmasını cache'den okumamasını sağladım. Şimdi aşağıdaki komutla bu imajımdan yeni bir container ayağa kaldırıyorum.

docker run -d -p 6009:4600 --name netcorewebapp_build_con2 netcorewebapp:v4

Görüldüğü gibi uygulama 6009 portunda ayağa kalktı, debug modda eklediğim iki resim imajın içine eklendiği için uygulamayla birlikte geldi. Üçüncü resmi ise ben ekledim. Şimdi aynı imajdan bir container daha ayağa kaldırıyorum

Uygulamam aynı imaj üzerinden farklı bir container'da 6010 portu üzerinde ayağa kaltı, ancak burada bir sorun var. Önceki uygulamaya eklediğim üçüncü resim gelmedi ve o üçüncü resimde containerimi sildiğimiz zaman silinecek. Bu sorunları docker volume ile nasıl çözeriz şimdi ona bakalım.

Docker Bind-Mount Volume

Docker volume tanımı yaptığımız başlıkta ki paylaştığımız görselde görüldüğü üzere dockerda dosyaları saklamanın 3 yolu vardır;

  1. Volume
  2. Bind-Mount
  3. Tmpfs Mount

Biz gerçek hayatta çok sık kullanılan ilk ikisi inceleyeceğiz, Tmpfs Mount dosyaları cache'de tuttuğu için çok tercih edilen bir yöntem değil. Şimdi docker'ın kurulu olduğu işletim sistemine resimlerimi kaydetmek için bir klasör oluşturuyorum. C'nin altına images adınad bir klasör açtım. Docker üzerinden bu klasöre erişim izni vermem gerekiyor. Ayarlar kısmından Resources/Advanced/File Shararing kısmına geliyorum. Ancak şöyle bir hata ile karşılaştım.

WLS 2 Alt yapısında file sharing desteklenmiyormuş. İnternetten araştırma yaptığımda Hyper-V yi aktif etmem gerektiğini öğrendim. PowerShell'i yönetici olarak çalıştırıp aşağıdaki komutları sırayla çalıştırdım.

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

Get-Service LxssManager | Restart-Service

Sonra docker ayarlar üzerinden WLS 2 seçeneğini pasif yaptım ve docker'ı restart ettim. Şimdi ayarlara girerek images klasörüme erişim izni verdim.

Şimdi ayarlara geldiğimde File Sharing kısmını görebiliyorum. Buradan işletim sistemine açtığım images klasörünü ekleyerek Apply & Restart diyorum ve 

Docker üzerindeki ayarlarım artık tamam. Şimdi mount işlemini yaparak yeni bir container ayağa kaldıralım. Docker'ı restart edince imajlarım silindi bu nedenle imajımı v5 olarak tekrar oluşturdum.

docker build --no-cache -t netcorewebapp:v5 .

Şimdi de bu imajımdan yeni bir container ayağa kaldırıyorum.

docker run -d -p 6011:4600 --name netcorewebapp_build_con4 --mount type=bind,source="C:\images",target="/App/wwwroot/images" netcorewebapp:v5

Yukarıdaki komutu incelediğimizde --mount parametresini yazıyoruz ardından type=bind diyerek mount işleminin tipini veriyoruz. Sonra kaynak kısmı olarak soruce parametresiyle işletim sistemine açtığımız klasörün yolunu veriyoruz. Son olarakta target komutuyla hedefi belirtiyoruz. Bu yolda imajımızın içinde oluşturduğumuz App klasörünün altındaki wwwroot/images klasörü oluyor. Komutu yazıp enter dediğimde container 6011 portunda ayağa kalkıyor.

http://localhost:6011/ adresine girdiğim de uygulamaya erişebiliyorum. 

Ancak imajımım içinde olan iki adet görsel gelmemiş, bunun sebebi wwwroot/images klasörü artık benim bilgisayarımdaki c:\images klasörüne bakıyor olmasıdır. Şimdi bir resim yükleyip c:\images klasörünü kontrol ediyorum.

Görüldüğü gibi resim benim oluşturduğum klasöre yüklenmiş, artık container'ımız silinse dahil yüklediğimiz fiziksel dosyaları korumuş oluyoruz. Ve ilave olarak ben aynı komutla başka bir portta uygulama ayağa kaldırdığımda resimleri tek bir yerden okuyacağı için uygulamar birbirinin dosyalarına ulaşabiliyor olacak. 6012 portunda yeni bir container ayağa kaldırıp kontrol ediyorum.

docker run -d -p 6011:4600 --name netcorewebapp_build_con4 --mount type=bind,source="C:\images",target="/App/wwwroot/images" netcorewebapp:v5

http://localhost:6012/ adresine girdiğim zamanda diğer container'dan eklediğim resmi görebilyorum. Çünkü iki container'da fiziksel olarak aynı yerden okuyor dosyaları.

Volume

Volume'ler bir diğer dosyalarımızı kalıcı olarak kaydetmenin diğer bir yolu. Volumelar doğrudan docker tarafından yönetilebilir ve kendi işletim sistemimizle birlikte cloud ortamlarda da saklanabilir. İlk olarak docker cli üzerinden bir volume oluşturalım

docker volume create images

Yukarıdaki komutla images adında bir volume oluşturdum, şimdi fiziksel dosyaları bu volume'dan okuyan bir container ayağa kaldıralım.

docker run -d -p 6013:4600 --name netcorewebapp_build_con6 -v images:/App/wwwroot/images netcorewebapp:v5

6013 portunda uygulamamı ayağa kaldırdım.

Burada bind-mount'dan farklı olarka imajımın içerisindeki resimlerde container ayağa kalkarken oluşturduğumuz volume'a kopyalandı. Aynı volume'e bind edilmiş farklı bir container ayağa kaldırdığımda da yine birbirlerinin yüklediği resimleri görüyor olabilecekler.

Containerda Environment Tanımlama

.Net de bir uygulama geliştirdiğinizde debug moddayken uygulamamız Development environmet'inde çalışır, publish alıp canlı ortama çıktığımız zaman ise Production environment'inde çalışır. Uygulamamıza ihtiyaca göre farklı environmentlerde ekleyebiliriz. Visual Studio üzerinden de projeye sağ tık Properties>Debug>General üzerinden güncelleyebiliriz.

Basit bir örnek vermek gerekirse debug modda uygulamada hata aldığımız zaman hatanın tüm detaylarını sayfada görebiliriz ama canlıya veya test ortamına publish alıp çıktımız zaman hatayı göremeyiz ve yakalamak için tekrar debug etmemiz gerekir (Tabi hata loglama olmadığını varsayarsak). İşte bu noktada uygulamamızı dockerize ederken container'ın environmet'ini development yaparsak hatayı görebiliriz.

Uygulamanın hangi environment'de çalıştığını görmek için index.cshtml'e aşağıdaki kodu ekliyorum.

<environment names="Development">
   Environment: Development
</environment>

<environment names="Production">
  Environment: Production
</environment>

Hangi environment'de çalışıyorsa o tag'in içini gösterecek. Şimdi environment belirterek yeni bir container ayağa kaldıralım. Uygulamada değişiklik yaptığım için v6 etiketiyle yeni bir imaj oluşturuyorum ve bu imajdan bir container ayağa kaldırıyorum.

docker run -d -p 6015:4600 --env ASPNETCORE_ENVIRONMENT=DEVELOPMENT --name netcorewebapp_build_con8 netcorewebapp:v6

Şimdi 6015 portundan uygulamaya giriş yapıyorum.

Görüldüğü gibi uygulamam Development environment'inde ayağa kalmış. Enviroment'lerin bir diğer kullanım şeklide kendinize özel enviromentleri tanımlayıp uygulama içinde tıplı appsettings.json'dan veri çekermiş gibi çekip kullanabilirsiniz. Örneğin;

docker run -d -p 6015:4600 --env MySite=ekremozer.com --name netcorewebapp_build_con8 netcorewebapp:v6

Yukarıdaki komutla bir container ayağa kaldırdığımızda sanki appsetting.jsonda MySite adında bir değişken varmış gibi bu değeri okuyabilirsiniz. Bu tanımlamayı dockerfile üzerinden de yapabilirsiniz.

ENV MySite="ekremozer.com"

Dockerfile'ınıza yukarıdaki kodu eklediğinizde uygulamanızın içerisinde IConfiguration ile appsettings.jsondan veri okur gibi bu değişkenide okuyabilirsiniz.

Çok Katmanlı Projeleri Dockerize Yapmak

Console ve tek katmanlı projeleri nasıl dockerize edeceğimizi gördük, şimdi ise çok katmanlı bir web projesini nasıl dockerize edeceğimizi inceleyelim. Bunun için projeme NetCoreDocker.Utility adında bir class library ekliyorum ve içerisine StringHelper adında bir class ekliyorum. Classıma GetRandomFileName adında bir metod tanımlayarak resim yükleme işleminde dosya ismini türetirken bu metotdan yararlanıyorum. Konunun anlaşılmasında çok önemli bir faktör olmadığı için örneği olabildiğince basit tuttum. Ardından solutionumun olduğu dizine Dockerfile dosyamı ekliyorum. Yukarıdaki örneklerde dosyayı projenin olduğu dizine eklemiştik. Bu sefer bir üst dizine yani solutionun olduğu dizine ekleyip dosyayı visual studio üzerinden açıyorum. Şimdi Dockerfile'a eklediğim komutları tek tek inceleyelim.

FROM mcr.microsoft.com/dotnet/sdk:6.0 as build
WORKDIR /App
COPY ./NetCoreDocker.Web/*.csproj ./NetCoreDocker.Web/
COPY ./NetCoreDocker.Utility/*.csproj ./NetCoreDocker.Utility/
COPY *.sln .
RUN dotnet restore
COPY . .
RUN dotnet publish ./NetCoreDocker.Web/*.csproj -c Release -o /publishfolder/
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY --from=build /publishfolder .
ENV ASPNETCORE_URLS="http://*:4600"
ENTRYPOINT ["dotnet","NetCoreDocker.Web.dll"]

Birinci satırda net sdk imajını kullanarak  as tagıyla build aliasını veriyorum.

İkinci satırda kök dizine App adında bir klasör oluşturup içerisine giriyorum.

Üçüncü satırda Uygulama klasörümdeki NetCoreDocker.Web klasörüne girerek projemin .csproj dosyasını imajımdaki oluşturduğum App klasörünün içerisine NetCoreDocker.Web klasörü açarak onun altına kopyalıyorum.

Dördüncü satırda aynı işlemi NetCoreDocker.Utility projem için yapıyorum.

Beşinci satırda projemin ana dosyası olan .sln dosyasını App dizinine kopyalıyorum. Diğer dosyaları kopyalarken her proje için tıplı solutionumuzdaki gibi bir hiyeraşide klasör oluştururken .sln dosyamı projelerin bir üst dizinine kopyalıyorum. COPY komutundan sonra kaynak ve hedef dizin verirken ifadelerin başında bulunan nokta işareti bulunduğumuz dizini ifade eder. Yani kopyalanacak kaynağın başına nokta koyduğumuz zaman Dockerfile'ın olduğu dizin hedefin başına nokta koyduğumuz zaman WORKDIR'i yani bu örnekteki App klasörünü ifade ederim.

Altıncı satırda uygulamamı restore ederek bağımlılıkları ve dll'leri oluşturuyorum.

Yedinci satırda kalan tüm klasör ve dosyaları birebir app klasörümün içerisine kopyalıyorum.

Sekizinci satırda ana projem NetCoreDocker.Web olduğu için bu projeden publish alarak kök dizindeki publishfolder'a dosyaları atıyorum.

Dokuzuncu satırda sdk imajıyla artık işim bittiği için runtime imajımı kullanıyorum.

Onuncu satırda bu imajımda da App adında bir klasör oluşturup içerisine giriyorum.

Onbirinci satırda sdk imajımın içerisinde ki publishfolderdaki dosya ve klasörleri yeni imajımdaki App klasörüne kopyalıyorum.

Onikinci satırda container içerisindeki çalışacak uygulamamın portunu belirleyen environmet'i yazıyorum.

Onüçüncü satırda projemin ENTRYPOINT'ini belirliyorum, yani ana projenin dll'i olan NetCoreDocker.Web.dll

Dockerfile'ımızın artık hazır, dosyamızın olduğu dizini PowerShell ile açarak artık imajımızı oluşturabiliriz.

docker build -t netcorenlayerwebapp .

Yukarıdaki komutumla imajımı oluşturdum ancak şöyle bir hata aldım, Console uygulamamın csproj dosyasını Dockerfile'da vermediğim için ve bu bağımlılık .sln dosyasında olduğu için dotnet restore işlemi yapmadı. Bende console uygulamasını solutiondan silerek imajımı bu şekilde oluşturdum.

docker images ile imajlarımı kontrol ediyorum.

Görüldüğü gibi netcorenlayerwebapp imajım oluşmuş. Şimdi bu imajdan bir container ayağa kaldıralım.

docker run -d -p 6016:4600 --name netcorenlayerwebapp_con1 netcorenlayerwebapp

Yukarıdaki komutu çalıştırdığımda container 6016 portunda ayağa kalkmış olacak.

Görüldüğü gibi uygulama ayakta, test etmek içinde bir resim yükledim ve bir sorun gözükmüyor. Eğer uygulamamızda bir test katmanı olsaydı docker üzerinden imajı oluşturmadan önce .net core cli komutuyla testi çalıştırıp hata alıp almadığınızı kontrol edebilirdiniz, eğer testte hata çıkarsa zaten imajı oluşturmaya devam etmez. Benim uygulamam çok küçük olduğu için bir test katmanına ihtiyaç duyulmadı ancak NetCoreDocker.Test adında bir test katmanı olduğunu varsaysaydık ve bu testleri imaj oluştururken çalıştırmak isteseydik Dockerfile'ım aşağıdaki gibi olacaktı.

FROM mcr.microsoft.com/dotnet/sdk:6.0 as build
WORKDIR /App
COPY ./NetCoreDocker.Web/*.csproj ./NetCoreDocker.Web/
COPY ./NetCoreDocker.Utility/*.csproj ./NetCoreDocker.Utility/
COPY *.sln .
RUN dotnet restore
COPY . .
RUN dotnet test ./NetCoreDocker.Test/*.csproj
RUN dotnet publish ./NetCoreDocker.Web/*.csproj -c Release -o /publishfolder/
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY --from=build /publishfolder .
ENV ASPNETCORE_URLS="http://*:4600"
ENTRYPOINT ["dotnet","NetCoreDocker.Web.dll"]

Yukarıdaki kodları incelediğimizde ilave olarka sekizinci satırı ekledik, bu kod test katmanımızın yolunu alarak dotnet test komuduyla tüm test süreçlerini visual studio üzerinden yapıyormuş gibi çalıştırıyor, hata alması durumunda da diğer katmanları oluşturmayarak işlermi yarıda kesiyor.

Docker <none> Image Nedir?

Docker imajları oluştururken her katmanı tekrar oluşturma işlemleri için cache'e atar. Eğer bir katmanda değişiklik yoksa onu cache'den getirir varsa yeniden oluşturur. Cache'den getirirken'de none imajları kullanır. None imajlar Dangling Image ya da Dangling Layer olarakta adlandırılır, docker images kodumuzu çalıştırdığımızda aşağıdaki listede <none> adında bir imaj görebiliyoruz.

none imajları silebilirsiniz, dockerize işlemlerinizde bir sorun teşkil etmez. none imajları listelemek için aşağıdaki komutu kullanabilirsiniz.

docker images -f "dangling=true"

none imajları toplu olarak silmek içinde aşağıdaki komutu kullanabilirisiniz.

docker rmi $(docker images -f "dangling=true" -q)

Yukarıdaki komutta rmi den sonra $ işaretiyle aslında bir sorgu yazıyoruz. -f "dangling=true" ile sadece none imajları getiriyoruz.  -q parametresi ile de imagelerin sadece id değerlerini getiriyoruz. Böylece rmi komutuyla ilgili imageler siliniyor. Biz örneklerimizde imajlar üzerinde işlem yaparken hep ismini kullandık ancak ID ile de ve ID'nin ilk üç karakteriylede tıpkı imajların isimlerini yapmış gibi işlemlerimizi yapabiliriz.

Benim bu makalemde anlatacaklarım bu kadar Docker hakkında bildiklerimi anlatmaya çalıştım, umarım faydalı olmuştur. Projenin kaynak kodlarına bu linkten ulaşabilirsiniz.

https://github.com/ekremozer/NetCoreDocker