Docker Nedir? Nasıl Kullanılır?

Uygulamalar zamanında fiziksel sunucular üzerinde yapılandırılarak barındırılmaktaydı. İzole uygulamalar ilave sunuculara ihtiyaç duymaktadır. Bu süreç hem maliyeti arttırıyor hem de deployment işlemlerini yavaşlatarak yönetimi zorlaştırıyordu. Sanallaştırmanın hayatımıza girmesiyle sunucularda birden fazla işletim sistemi ayağa kaldırılarak kaynaklar daha verimli kullanılmaya başlandı. Ölçeklendirme ve deployment işlemleri hızlansada her sanal makinanın işletim sistemi gerektirmesi, taşınabilirlik gibi sorunlar devam etmekteydi. Bu gibi sorunlar container-based virtualization ile çözüme kavuşmuştur. Go diliyle yazılan Docker bu noktada karşımıza çıkmaktadır.

Docker, işletim sistemi üzerine eklediği Container Engine ile uygulamaları ihtiyaç duyduğu paketlerle çalıştırmaktadır. Bu yapıyla birlikte dağıtım ve taşınabilirlik sorunları çözülürken uygulamalar arasında izolasyon da sağlanmaktadır.

Docker client-server mimarisini kullanır. Kullanıcı client aracılığıyla daemon ile iletişim kurar. Client (kinematic, command-line) aldığı komutları Daemon’a ileten kullanıcı arabirimidir. Daemon (docker server, docker engine) host üzerinde sürekli çalışarak container run, build ve distribution işlemlerini üstlenmektedir.

Image ve container olmak üzere iki ana bölümden oluşmaktadır. Aralarındaki ayrıma class ve instance ilişkisi örnek gösterilebilir. Image bir sınıf şeklinde düşünülürse sınıfın örneklenmesiyle container oluşmaktadır.

Kurulum versiyondan versiyona farklılık gösterebileceğinden resmi sitesindeki adımlara göre kurulum sağlanmalıdır.

Images

İmajlar, oluşturulacak her container için read-only şablon görevindedir. Uygulama ve gerekli ortam bilgilerini (işletim sistemi, runtime, v.b.) içermektedir. Container oluşturulmaksızın kendi kendilerine çalıştırılamazlar. Dockerfile kullanılarak oluşturulmaktadırlar.

İmajlar özel bir Registry üzerinde depolanacağı gibi Docker Registry üzerinde depolanabilmektedirler. Registry içerisinde yer alan her repository çeşitli tag’lara göre derlenmiş sürümlerden oluşmaktadır. Imaj oluşturulurken bir tag belirtilmemesi durumunda varsayılan tag latest olacaktır.

docker build .Dockerfile adımlarına göre bir imaj oluşturur. -t NAME:TAG parametresiyle oluşturulacak imaja bir isim ve tag verilmektedir.
docker run IMAGEIMAGE ile belirtilen imajdan bir container oluşturarak başlatır. Mevcut imaj çalışma ortamında bulunmuyorsa registry üzerinden indirilir. --name NAME parametresiyle oluşturulacak container’a bir isim verilir. -p OUT:IN parametresiyle kullanılacak iç ve dış portlar belirtilir. -d parametresi kullanılarak container detached mod ile başlatılırsa konsol container’ın durmasını beklemez. -it parametresiyle container interactive mod ile başlatılarak container’a konsol üzerinden komut gönderilebilir. --rm parametresiyle container durduğunda otomatik silinmesi sağlanır. --env KEY=VALUE parametresiyle environment variable ayarlanabilir, toplu şekilde bir dosya ile göndermek için --env-file PATH parametresi kullanılmalıdır. -v PATH parametresiyle bir anonymous volume, -v NAME:PATH ile bir named volume, -v "FROM:TO" ile bir bind mount oluşturulur. -v $(pwd):/app şeklinde kullanılabilir.
docker imagesÇalışma ortamındaki imajları listeler.
docker rmi IMAGEIMAGE ile belirtilen (name, id) imajı siler.
docker image pruneBir taga sahip olmayan (untagged) tüm imajları siler. -a parametresi kullanılırsa tüm imajlar silinir.
docker image inspect IMAGEIMAGE ile belirtilen imaj metadata bilgilerini döner.
docker tag NAME NEW_NAMENAME ile belirtlen imaj ismini NEW_NAME olarak değiştirir.
docker loginDocker Hub hesabına giriş yapılmasını sağlar.
docker logoutDocker Hub hesabından çıkış yapılmasını sağlar.
docker push REPOSITORY/IMAGERegistry’e belirtilen imajı gönderir.
docker pull REPOSITORY/IMAGERegistry’den belirtilen imajı çeker.

Containers

Imajların çalışan örnekleridir. Bir container oluşturulduğunda imajın üstüne ince bir read-write katmanı eklenmektedir. Aynı imaja bağlı birden fazla container birbirinden izole şekilde ayağa kaldırılabilir. Uygulamalar arası herhangi bir state veya data paylaşımı olmaz.

docker psÇalışan tüm container’ları listeler. -a parametresi kullanılırsa durmuş container’lar da listeye dahil edilir.
docker create [OPTIONS] IMAGEIMAGE ile belirtilen imajdan bir container oluşturur. -n parametresiyle bir container ismi verilir.
docker start CONTAINERCONTAINER ile belirtilen container’i detached olarak çalıştırır. -a parametresi kullanılırsa attached olarak çalıştırılır.
docker stop CONTAINERCONTAINER ile belirtilen container’ı durdurur.
docker attach CONTAINERdetached olarak çalışan CONTAINER ismindeki container’ı attach eder.
docker logs CONTAINERCONTAINER ile belirtilen container’in geçmiş log kayıtlarını listeler. -f parametresiyle yeni kayıtları dinlemeye devam eder.
docker rm CONTAINERCONTAINER ile belirtilen (name, id) container’ı siler.
docker container pruneÇalışmayan tüm container’ları siler.
docker exec PARAMS CNTR CMDCNTR isimli container içerisinde CMD ile belirtilen komutu çalıştırmak için kullanılır. PARAMS yerine -it değeri ve CMD yerine /bin/bash değeri verilirse container içerisinde terminal açar.
docker cp SRC CONTAINER:DESTSRC ile belirtilen dizinden CONTAINER ile belirtilen container içerisindeki DEST dizinine dosyaları kopyalar. Parametreler yer değiştirebilir, CONTAINER:SRC DEST ile container içerisinden dışarıya kopyalama yapılır.

Volumes

Imajlar bir kez oluşturulduğunda read-only olduklarından değiştirilemezler. Bir değişiklik için yeniden build edilmelidirler. Container’lar imajların üstüne ince bir read-write katmanı eklediklerinden okuma ve yazma yapabilirler. Bu imajı değiştirmeden imaj içerisinde dosya işlemleri yapılabileceği anlamına gelir. Okuma yazma işlemi yapılabilmesine rağmen bir kaç sorun mevcuttur. Container’ın silinmesi durumunda yazılmış veriler de kaybolacaktır, bu sorun volume kullanılarak çözülmektedir. Container, host dosya sistemiyle etkileşime giremez. Proje dizininde yapılacak değişiklikler de çalışan container içerisine yansımayacaktır. Yeni bir imaj build işlemiyle yeni bir container oluşturulup başlatılması yerine bind mount kullanılabilir.

Volume, bir container içerisindeki klasör ve dosyalara bağlı, host tarafından yönetilen klasör ve dosyalardır. İki tür volume vardır.

  • Anonymous volume container silindiğinde otomatik olarak içerisindeki veriyle birlikte volume da silinir. -v /PATH parametresiyle oluşturulmaktadır. Bind mount tarafından container içi verilerin override edilmemesi adına yararlı olabilir.
  • Named volume container’dan bağımsız olarak var olmakta ve komut aracılığıyla silinebilmektedirler. -v NAME:/PATH parametresiyle oluşturulurlar. Upload edilen dosyalar, veritabanı dosyaları gibi container verilerinin tutulması amacıyla kullanılırlar.

Volume kullanılarak container içerisine veri taşınabilir ve container tarafından yazılan veriler korunabilir; ancak bu değişiklikler host’a yansımaz. Volume, Docker tarafından oluşturulur ve yönetilir.

docker volume lsTüm volume’ları listeler.
docker volume create NAMENAME ile belirtilen isimde bir named volume oluşturur. Container çalıştırılırken mevcut değilse otomatik olarak oluşturulacağından genellikle buna gerek kalmaz.
docker volume rm NAMENAME ile belirtilen (nameid) volume’u siler.
docker volume pruneKullanılmayan tüm volume’ları siler.

Bind Mounts

Volume ile benzerlik göstermektedir. Aralarındaki fark host’a container içerisindeki bir yola bağlanması gereken yolun ayarlanmasıdır. -v /ABS_HOST_PATH:CONTAINER_PATH parametresiyle oluşturulmaktadır.

Development ortamında Container çalışırken paylaşılan verinin (source code) değişeceği durumlarda yararlıdırlar. Production ortamında kullanılmaması tavsiye edilmektedir.

Networks

Gerçek hayat senaryolarında birden fazla container’a ihtiyaç duyulacaktır. Birden fazla ana işleve sahip bir container’ın yönetimi zorlaşacağından her ana işlevin (web server, database) kendi container’ına sahip olması best-practice olarak kabul edilmektedir. Bu durum container’lerın kendi aralarında, host ile ve www ile iletişime geçme ihtiyacını doğurmaktadır.

www ile iletişim kurmak (Http, gRPC istekleri gibi) oldukça kolaydır. Hangi programlama dili kullanılırsa kullanılsın Container içerisinde ekstra bir yapılandırma gerekmeksizin normalde olduğu gibi istek gönderilebilmektedir.

Host ile iletişim kurmak (Host üzerinde çalışan bir veritabanı) için ufak bir değişiklik yapılması yeterli olacaktır. Host üzerinde localhost:3000 adresinde çalışan bir web server’a container dışından istek atıldığını düşünelim. Container içerisinde localhost container’ın kendisi olacağından istek başarısız olacaktır. Bu sorunu çözmek adına host.docker.internal özel adresi kullanılmalıdır. Bu adres Docker tarafından Host IP adresine dönüştürülmektedir. Böylece host.docker.internal:3000 adresine yapılan istekler Docker tarafından çözümlenerek başarıyla sonuçlanacaktır.

Container’ler arasında iletişim kurmak için iki seçecek mevcuttur. İletişim kurulacak Container IP adresi elde edilerek istek gönderilebilir, ancak bu adres zamanla değişebileceğinden kalıcı çözüm sunmaz. Bir diğer yöntemde Docker Network kullanmaktır. Bir network oluşturulup mevcut container’lar bu ağa eklenerek container’ların birbirleriyle haberleşmesi sağlanabilir.

docker run --network my-network --name container1 some-image
docker run --network my-network --name container2 other-image

Böylece iki container aynı network altında olacaktır. Birbirleriyle haberleşirken kullanacağı adreslere de container ismi yazılması (container1/api/products gibi) yeterli olacaktır. Docker IP adreslerini bizim için çözümleyecektir.

Docker komutuyla ilgili tüm komut listesi, konsola ilgili komuttan sonra --help flagı eklenerek docker --help docker run --help görülebilmektedir. Detaylı bilgi için dokümantasyon taranabilir.

docker network lsTüm network’leri listeler.
docker network create NAMENAME ile belirtilen isimde bir network oluşturur.
docker run –network NAME IMAGEIMAGE ile belirtilen imajdan NAME ile belirtilen network’e bir container oluşturur.

Dockerfile

Dockerfile instruction adındaki adımlardan oluşmaktadır. Bu adımlar İmajın nasıl oluşturulacağını belirtmektedir. Imaj oluşturulduğunda bu adımlar birer katmana dönüşmektedir. Oluşturulan katmanlar sonrasında imajları verimli şekilde yeniden oluşturmak ve imajlar arasında paylaşmak için kullanılmaktadır. Docker Cache sayesinde eğer adımlar değiştirilmemişse bir sonraki imaj oluşturma işleminde önceden oluşturulmuş mevcut katman kullanılacaktır. Bu sayede build süresi kısalmaktadır. Bu adımlarda kullanılabilecek komutlarla birlikte örnek bir Dockerfile’a göz atalım.

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source

# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore

# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app --no-restore

# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]

Yorum satırlarının # işaretiyle başladığına ve uygulamanın son satırdaki ENTRYPOINT ["dotnet", "aspnetapp.dll"] komutuyla ayağa kalktığına dikkat edelim. Burada ENTRYPOINT yerine RUN komutu kullanılsaydı bu komut container içerisinde değil image içerisinde çalıştırılmış olacaktı.

Docker Cache konusunun iyi anlaşılması adına aşağıda örnek bir Node.js uygulamasını ayağa kaldıran bir Dockerfile yer almaktadır.

FROM node
WORKDIR /app
COPY . /app
RUN npm install
CMD ["node", "app.js"]

Yukarıdaki adımlar incelendiğinde kaynak kodların /app dizinine kopyalandığı ve sonrasında gerekli paketlerin kurulduğu görülmektedir. Adımlar dikkatli incelenirse bir optimizasyon sorunu gözlemlenmektedir. Bir adım değiştiğinde sonraki katmanlar da yeniden derlendiğinden; codebase içerisinde değişiklik yapıldığında paketlerin tekrar kurulmasına gerek yoktur.

FROM node
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
CMD ["node", "app.js"]

Codebase içerisinde bir değişiklik yapıldığında paket kurulumlarını içeren adım cache üzerinden okunacağından performans artmaktadır. Yalnızca package.json içerisinde bir değişiklik yapıldığında ilgili ve sonraki adımlar tekrar derlenecektir.

FROM

FROM komutu kendisinden sonra gelen adımlar için base image ayarlar. Dockerfile bir FROM komutuyla başlamalıdır. Birden fazla base image belirtilebilir.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR

WORKDIR komutu, RUN CMD ENTRYPOINT COPY ve ADD komutlarının hangi dizinde çalıştırılacağını belirtir. WORKDIR belirtilmemişse sonraki adımlar tarafından kullanılmayacak olsa dahi oluşturulacaktır. Birden çok kez kullanılabilirler. Belirtilen dizinlerin relative olması durumunda bir önceki dizin baz alınacaktır.

WORKDIR /app
ADD

ADD komutu kaynak olarak belirtilen dosya, klasör ve internet üzerindeki verileri imaj içerisinde belirtilen hedef dizine kopyalar. Birden çok kaynak belirtilebilir. Bu komuta gerçekten ihtiyaç varsa kullanılması önerilmektedir. Aksi durumda COPY komutu tercih edilmelidir.

ADD . /app
COPY

COPY komutu kaynak olarak belirtilen dizinden hedef dizine mevcut dosya ve klasörleri kopyalayarak taşır. ADD komutuna göre farkı internet üzerinden değil yalnızca host içerisinden kopyalama yapabilmesidir. Birden fazla kaynak belirtilebilir.

COPY . /app
EXPOSE

EXPOSE komutu container’ın çalışma zamanında hangi portu kullanacağını bildirir. Bir protokol belirtilmediğinde varsayılan TCP protokolü kullanılır. Bu komut dokümantasyon amacıyla hangi portların kullanılacağını bildirir, belirtilen portlar yayınlanmaz. Bir container çalıştırılacağında port yayınlamak için -p parametresi kullanılmalıdır.

EXPOSE 8081
ENV

ENV komutu girilen değerleri environment variable olarak ayarlar. Bu değerler build aşamasında kendisinden sonra gelen komutlar tarafından kullanılabilir ve sonrasında değiştirilebilir.

ENV PORT 80
EXPOSE $PORT
VOLUME

VOLUME komutu container içerisinde belirtilen dizinle host üzerinde oluşturduğu dizini birbirlerine bağlayarak anonymous volume oluşturur. Alacağı değer array ya da string tipinde olabilir. Volume komutunun kullanılması yerine container başlatılırken -v parametresiyle ayarlanması önerilmektedir.

VOLUME /app
VOLUME ["/app", "/data"]
RUN

RUN komutu imaj oluşturulurken çalıştırılması istenen komutları yürütmektedir. Yeni bir imaj katmanı oluşturduklarından sayılarının az tutulması önerilmektedir.

RUN npm install
CMD

CMD komutu container başlatıldığında çalıştırılması istenen komutlardır. Dockerfile içerisinde CMD komutu belirtilmediğinde FROM komutuyla belirlenmiş base image default komutu çalıştırılacaktır.

CMD ["node", "app.js"]

Container çalıştırılırken imaj isminden sonra bir command belirtilmişse Dockerfile içerisindeki CMD komutu override edilecektir.

docker run some_image <command>
ENTRYPOINT

ENTRYPOINT komutu yine container başlatıldığında çalıştırılması istenen komutlardır. CMD komutundan farkıysa imaj isminden sonra belirtilen command override edilmek yerine belirtilen komutun sonuna eklenmesidir.

ENTRYPOINT ["dotnet", "app.dll"]

Dockerignore

Docker Client, Daemon ile iletişime geçmeden önce root dizinde .dockerignore isminde bir dosya arar, mevcut olması durumunda içerisinde belirtilen dosya veya klasörlerin ADD veya COPY komutlarıyla imaja taşınması engellenmektedir. Böylece istenmeyen büyük ve hassas dosya veya dizinlerin imaja taşınmasının önüne geçilebilmektedir.

Docker Compose

Docker Compose multi-container Docker uygulamarını çalıştırmaya yarayan bir araçtır. Bu araçla birlikte container tanımları tek bir YAML dosyasından yapılarak uygulamalar bir komutla çalıştırılabilir ve durdurulabilir.

Birden fazla container’a sahip olduğumuzu düşünelim, Docker Compose kullanılmadığı taktirde network oluşturma, imaj build işlemleri, container oluşturma ve oluşturulan network’e dahil etmek gibi bir çok komut yazmamız ve bu komutları hatırlamamız gerekmektedir. Üstelik yapılacak en ufak değişikliklerde bu sürecin tekrarlanması gerekecektir. Docker Compose kullanıldığındaysa container konfigürasyonlarının docker-compose.yml dosyasına belirtilmesi yeterli olacaktır.

version: '3.9'
services:
  mongodb:
    image: 'mongo'
    volumes: 
      - data:/data/db
    environment: 
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
      args:
        some-arg: 1
    ports:
      - '80:80'
    volumes: 
      - logs:/app/logs
      - ./backend:/app
      - /app/node_modules
    env_file: 
      - ./env/backend.env
    depends_on:
      - mongodb
  frontend:
    build: ./frontend
    ports: 
      - '3000:3000'
    volumes: 
      - ./frontend/src:/app/src
    stdin_open: true
    tty: true
    depends_on: 
      - backend

volumes: 
  data:
  logs:

Bu dosya istenildiğinde tek bir yerden düzenlenerek docker-compose up komutuyla ayağa kaldırılabilir. Tüm konfigürasyonlara buradan ulaşabilirsiniz.

Docker Compose ile çalışırken adımıza bir network oluşturulmakta ve tüm container’lar bu network’e dahil edilmektedir. Birden fazla network’e ihtiyacımız yoksa ekstradan bir network oluşturmamıza gerek kalmamaktadır.

docker compose upYAML dosyasında belirtilen container’ları başlatır.
docker compose downİlgili container’ları durdurur.
docker compose psYAML dosyasında belirtilen container’ları listeler.
docker compose logsCompose tarafından yönetilen container log bilgilerini gösterir. -f parametresi kullanılırsa yeni gelenler de eklenir. Bir container ismi eklenirse yalnızca ilgili container’a ait log bilgileri sunulur.
docker compose rmİlgili container’ları siler.
docker compose buildCompose tarafından yönetilen imajları build eder.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir