Docker - The neccesarry part (Volume & Network & Environment)

Docker - The neccesarry part (Volume & Network & Environment)

Bu kısımda sizlerle datalarımızı makinada saklamanın yolları yani host ve kullandığımız OS sistemi arasındaki dosya paylaşımlarını, containerlar arası network oluşturup birbirleriyle iletişim kurmalarını, ve projelerimizin olmazsa olmazı environment variable tanımlamalarını öğreneceğiz.

Volume

Volumelar bizim datalarımızı containerımızın çalıştığı sanal makinamız üzerindeki sistemde saklamamıza yarayan yapılardır. Mesela bir web projemiz olduğunu düşünün bu projemizde yüklediğimiz resimlerin veya formlarla eklediğimiz metinlerin, containerı çalıştırdığımız makinada tutulmasını istiyoruz. Ve volumelarda depoladığımız için, containerımız ne zaman çalışıp aktif olduğunda bu depolanmış dataların tekrar uygulamamız üzerinde erişimine sahip olmuş oluyor ve datalarımızı kaybetmemiş oluyoruz.

https://github.com/mucahidyazar/docker

  • Yukarıdaki linkten gerekli repoyu indiriyoruz.
    git clone https://github.com/mucahidyazar/docker.git
  • Ve tutorials branchine geçiyoruz.
    git checkout tutorials
  • Be burada volume-example adlı dizine geliyoruz. Burada 2 klasör var. 1. si starter yani başlangıç klasörü, diğeri fnished yani bitiş dosyalarımız.
  • Takıldığınız yerde fnished klasörüne bakıp kopya çekebilirsiniz.

  • Elimizde aşağıda ki kodlardan oluşan bir server.js dosyası var.

  • Dosya structure yapısı da aşağıdaki gibi.

  • Uygulamamızın görünümü bu şekilde. Eğer biz bir title ve text ile feedback bırakacak olursak, uygulamamız bunu bir metin belgesine dönüştürecek titleda yazan text’e göre ve bunu dosya structure yapısındaki feedback klasörünün içine atacaktır.

  • Ve daha sonra buna biz ulaşmak isteseydik url kısmına aşağıdaki gibi feedback/awesome.txt yazdığımızda kaydettiğimiz txt dosyasını görüntüleyebilecektik.

  • Burada sorun şu. Biz bu dosyayı dockerize edip docker containerında çalıştığı zaman image kendi dosya sistemi üzerinde çalışacak ve kaydedilen awesome.txt gibi dosyalar bizim uygulama dosyalarının olduğu yerde saklanmayacak. İşte volumelarla container image hostu ve kendi hostumuz arasında bağlantıyı sağlayacağız.
  • Şunu iyice anlamalıyız ki imageler sadece read-only’dir. Container ise read-write. Yani biz bir dosya kaydettiğimizde bu dosya containerın file systeminde saklanır. Imagenin file systeminde değil.

  • Eğer bir containerı silersek ve daha sonra aynı image ile yeni container oluşturursak daha önce yaptığımız değişikliklerde containerın silinmesiyle gider. Fakat volume ile bu durumu değiştirebiliriz.
  • Veya container silmeden sadece stop yaparak durdurup daha sonra çalıştırırsak eski dosyalar silinmez ve aynı dosyaları kullanabilir veya bulabiliriz.

  • İki şekilde volume tanımlayabiliriz. Anonymous Volume veya Named volumes.
  • Anonymous volumesları Dockerfile içinde aşağıda ki gibi veya docker run volume create komutunu kullanırken :’den önceki ilk tanımı yapmayarak

  • Ananymous volumeslar container silindikten sonra otomatik olarak silinir.

docker volume -help

  • Aşağıda docker volume ile yapabileceğimiz komutların bazılarını görüyorsunuz.

docker volume ls

  • Docker volumelerimizi listeler.

docker volume create volumeName

  • Docker volumesı oluştururuz.

docker volume prune

  • Kullanılmayan voluemleri siler.

docker volume rm volumeName

  • volume ismine göre volumelerimizi siler.

docker volume inspect volumeName

  • Oluşturduğumuz volume ait bilgileri görürüz yani inspect ederiz.

  • Terminale gelip yeni temiz bir image buildi alıyoruz.

docker run -v volumeName:/volumePathFromWorkdirOnImageFileSystem …

  • Yukarıda ise named yani isimlendirilmiş bir volume oluşturuyoruz, imagemizi container oluşturup çalıştırırken yapıyoruz bu işlemleri. İlk olarak volume için isim veriyoruz, ikinci olarak ise : işaretinden sonra imagemizdeki hangi pathdeki dosyaları bu volumede tutacağımız yolu belirliyoruz.

docker run -v feedback-volume:/app/feedback --name feedback-container -p 3000:80 --rm -d feedback-node:latest

  • Bizim kendi örneğimizzden gidecek olursak,
    1. -v ile feedback-volume adında bir volume oluşturuyoruz ve bu volumenin yolu olarakta : işaretinden sonra imagedeki WORKDIR’den volumede tutulacak konumu belirliyoruz.
    2. --name ile oluşturulup çalıştırılacak containerımıza feedback-container ismini veriyoruz.
    3. -p ile makinamızdaki 3000 portunu containerın çalıştırdığı imagenin 80 portuyla bind ediyoruz
    4. --rm ile container stop olduğunda silinsin istiyoruz.
    5. -d ile detach modda çalışsın istiyoruz. Bu bizi container başlattıktan sonra default olarak attach moddan kurtarır. Böylelikle node terminalinde kalmayız ve direk kendi terminalimize döneriz.
    6. Ve son olarakda feedback-node:latest ile imageName ve tagine göre imagemizi seçiyoruz.
  • Yukarıdaki kodu çalıştırıp detach moda geçtiğimizde
    1. docker ps -a yaparak tüm containerlarımızı görüyorum ve ilk gördüğünüz gibi containerımız çalışıyor.
    2. Daha sonra docker volume ls yaparak volumeleri listeliyorum. Ve gördüğünüz gibi yukarıda imagemizi containerla çalıştırırken oluşturduğumuz volume burada.
    3. Daha sonra docker stop containerId mi yazarak çalışan containerımı durduruyorum ve --rm bu container otomatik olarak siliyor. Hatta 2 alt satırda docker ps -a yaptım bunu görmeniz için.
    4. Daha sonra docker volume ls yapıyoruz ve volumemizin hala silinmediğini ve orada olduğunu görüyoruz. Containerın silinmesine ragmen. Buda bir daha yeni container oluştursak bu volumeyi kaybetmeyeceğimiz için uygulamamız sürekli her çalıştığında aynı verileri bulup saklayabileceği anlamına gelmektedir.
    5. Ve en alt satırda ki gibi, containerımız silindikten sonra tekrar aynı image ile yeni container oluşturup çalıştırırken aynı isim ile volume’ye bağlanmalıyız.

  • Container ve volume etkileşimini iyi anlamamız gerekmektedir.
  • Container ilk açıldığında /app/data da eğer dosya yoksa volüme deki /some-path den alır. Fakat dosya varsa eğer o zaman containerdaki dosyalar volüme deki /some-path üzerine overwrite edilir yani yazılır.
  • Fakat /app/code içinde ilk container çalıştığında dosya yoksa /some-other-path volumedeki dosyalar /app/code içine gönderilerek container her başladığında eklenir.

Sharing Source Code with a Container

  • Ve şimdi şöyle bir sistem yapalım. Lokalimizde proje dosyalarında herhangi bir değişiklik yaptığımız da bunun projeye anında yansımasını sağlayan bir yapı kuralım volumelerle.
  • Aşağıda ki gibi projemizde bir değişiklik yaparsak bu hemen browserda bize yansımaz. Bunun için yeni bir volume tanımalıyız. Öyle bir şey yapacağız ki projedeki herhangi bir değişiklik immediately yani acil olarak containera yansıyacak.

NOTE: Tabi burada unutmamız gereken şey,
önceki volume tanımlamamız orada dataları toplayıp daha sonra uygulamamızın containerı çalışıp mount olduğunda burada voluemdaki dataları uygulamamıza kopyalamak,
burada ki volüme tanımlamamızda ki sebep ise uygulamamız açıldığında, şuan ki dizinin adında bir volüme oluşturup bunu anında uygulamamızın containerı çalıştığında oradaki WORKDIR’e yani /app’e göndermek.

PATH Tanımlamaları

  • İster absolute pathi almak için aşağıdaki gibi uzun adres tanımlayın
    E:\Software\worksheets\docker\examples\volume-example\final
  • İsterseniz de aşağıdaki gibi sistemlere özgü kısa yolları kullanın
    macOS / Linux: -v $(pwd):/app
    Windows: -v “%cd%”:/app
  • Veya image tercihine göre containerımızın virtual machinesinin çalıştıracağı imageye göre aşağıdaki şekillerde kullanabilirsiniz.

FROM node:14 docker run -v feedback-volume:/app/feedback -v ${pwd}:/app --name feedback-container -p 3000:80 --rm -d feedback-node:latest

FROM node:14.16.0-alpine3.13 docker run -v feedback-volume:/app/feedback -v $(pwd):/app --name feedback-container -p 3000:80 --rm -d feedback-node:latest

with absolute path
E:\Software\worksheets\docker\examples\volume-example\final
Özel karakter olur diye “” bu isaret içinde yazmak en güvenilir yoludur volume pathi tanımlarken. Özel karakter yoksa kullanmayada gerek yoktur. Aslında aşağıda da kullanmasak da olurdu.
docker run -v feedback-volume:/app/feedback -v “Software\worksheets\docker\examples\volume-example\final:/app” --name feedback-container -p 3000:80 --rm -d feedback-node:latest

docker run -v /pathThatBindingWithVolume

  • Eger volumeName yazmasak yukarıda ki gibi, anonymous bir volume tanımlamış oluruz.

docker run -v bindedPathOnOurOS:/bindedPathOnVMOS

docker run -v feedback-volume:/app/feedback -v ${pwd}:/app --name feedback-container -p 3000:80 --rm -d feedback-node:latest

  • Evet bunun için ikinci bir volume belirliyoruz -v ile. Birinci volume hatırlarsanız feedbackleri volumeda tutmak içindi.
  • Ve bu volume’yi dynamic olarak node:14 imagesini kullandığım için ${ } arasina pwd yazarak current directorymizle bir volume oluşturuyoruz. Ve bu volumeyi imagedeki app klasörüyle bağlıyoruz. Böylelikle projede yani ${pwd} dizininde yaptığımız her değişiklik anında app klasörüne yansımış olacak.
  • Bu şekilde docker run yaptığımızda uygulamamızın çalışıp ve bir container oluşturup ardından daha sonra hemen stop ederek containerın silindiğini göreceksiniz. Çünkü express hatası verecek aşağıda ki gibi.

docker run -v feedback-volume:/app/feedback -v ${pwd}:/app --name feedback-container -p 3000:80 -d feedback-node:latest

  • Bunu gösterebilmek için ben --rm yi silerek yukarıdaki kodun aynısını çalıştıracağım. Böylelikle uygulamamız hata verdiğinde otomatik kapanmayacak ve container silinmeyecek. Bu şekildede biz containerın çalıştırdığı imagemizin terminale bastırdığı hatanın logunu alabileceğiz.
  • Gördüğünüz gibi Express hatası alıyoruz. Sebebi ikinci volüme(-v ${pwd}:/app --name feedback-container) tanımlamamız yüzünden. Çünkü pwd deki kendi OS sistemimizdeki dizinde bizim node_modules klasörümüz yukarıda tanımladığımız volumenin bizim pwd dizinindeki tüm herşeyi /app ile overwrite etmesinden dolayı yok, yani uygulamamız npm install yapıyor imagenin Dockerfilesına göre ama daha sonra bu node_modules overwrite yüzünden siliniyor ve uygulama mounted olduğunda node_modules olmayan buradaki tüm klasörler ve dosyaları, imagemizin çalıştığı containerdaki WORKDIR deki /app dizinindeki tüm dosyalarla değiştir. Buda Dockerfile ile npm install yapıp yüklediğimiz node_modules de silinmiş oluyor.

  • Bu sorunu gidermek için bir kaç farklı yöntem var. Ya bir user oluşturup, buna çeşitli yetkiler vererek halledebiliriz. Yada aşağıdaki gibi node_modules’u başka bir anonymous volume’a veririz ve silinmesini engelleriz. Ve bu şekilde yukarıdaki hatayı önlemiş oluruz.

docker run -v feedback-volume:/app/feedback -v ${pwd}:/app -v /app/node_modules --name feedback-container -p 3000:80 -d --rm feedback-node:latest

  • Ve şimdi artık uygulamamızda yaptığımız her değişiklik anında çalışan containerdaki dosya sistemimize anında yansıyacaktır. Artık dosyada bir değişiklik yapıp bunu görebilirsiniz.
  • Fakat yinede bir sorunumuz daha var yukarıdaki yaptığımız değişiklikler dosyalara aynı anda hemen yansır fakat bizim projemiz bu yansımayı hemen bize gösterir mi? Pages içindeki html sayfalarında yaparsak eğer bir f5 sornası yaptığımız değişikliği hemen görebiliriz. Fakat biz hatırlarsanız nodeda kullandığımız express serverını node server.js diyerek başlatıyoruz. Yani biz server.js içindeki bir fonksiyon içinde bir console.log yapacak olursak, node server.js ile bunu yaptığımız için değişiklik hemen yansımayacaktır, çünkü node serverını kapatıp tekrar node server.js dememiz gerekecektir içindeki o değişikliği yakalayabilmek için. Veya bunun için nodemon adında bir npm packagei çözüm sunar bize. O zaman buradada bu değişikliği aşağıdaki gibi package.jsona hemen ekliyoruz.

https://gist.github.com/mucahidyazar/ffc479cfbc0b4257ecb5e986604b17b8

  • Ve yukarıda ki yaptığımız değişiklikten ötürü CMD yi artık node server.js yerine yukarıda tanımladığımız script ile kullanacağız.
  • Anonymous volumeyide her seferinde terminalde yazmak yerine artık Dockerfileye aşağıdaki gibi ekliyoruz.

  • Daha sonra imagemizi yeniden build alarak oluşturuyoruz.

  • Ve artık terminale gelip aşağıdaki kodla imagemizi container oluşturarak çalıştırıyoruz.

docker run -v feedback-volume:/app/feedback -v ${pwd}:/app --name feedback-container -p 3000:80 -d --rm feedback-node:latest

  • Aslında (${pwd}:/app) bunu yaparak Dockerfiledaki COPY . . nın işini bir nevi yine yapıyoruz yani Dockerfiledan onu kaldırsakta hiç bir şey olmaz. Fakat biz (${pwd}:/app) bunu developmentda yaptığımız için günün sonunda COPY . . ya bizim productionda yine ihtiyacımız olacak o yüzden kaldırmaya gerek yok yinede tutabiliriz.
  • Ve şimdi server tarafında yaptığımız değişikliklerde anında yansıyacaktır nodemon sayesinde.
  • Bu tarz sorunlar bize farklı uygulamalarda da çıkabilir bunları aşmak için bu tarz yöntemleri araştırıp bulmalıyız veya geliştirmeliyiz.

ro (read-only)

docker run -v volumeName:/projectPath:ro

  • Yukarıda ki gibi tanımlayacağımız volumeleri sadece read-only yapabiliriz.

ENVironment Variables & ARGuments

ENV

  • Environment variablelari ise Dockerfile içerisinde asagidaki gibi ya aralarinda bir bosluk birakarak yada = ile tanimlayabiliriz ben = ile tanımlamayı tercih ediyorum.

  • Ve daha sorna imagemizi oluşturup containerimızı imagemizle çalıştırdığımızda istersek bu environmentleri aşağıda ki gibi görebiliriz.

  • Veya shell ile çalıştırdığımız için istersek yazdırabiliriz de echo yaparak.

docker run --env/-e PORT=80 …

  • Veya yukarıdaki tanımdaki veya aşağıdaki örnekteki gibi docker run yaparken de environment variablesları tanımlayabilirsiniz.
    docker run --rm -p 3000:80 --name feedback-container -v feedback-volume:/app/feedback -v ${pwd}:/app --env PORT=80 feedback-image
    docker run --rm -p 3000:80 --name feedback-container -v feedback-volume:/app/feedback -v ${pwd}:/app -e PORT=80 feedback-image
    docker run --rm -p 3000:80 --name feedback-container -v feedback-volume:/app/feedback -v ${pwd}:/app --env PORT=80 --env ENDPOINT=google.com feedback-image

docker run --env-file ./dev.env …

  • Veya kendi OS sistemimizde terminaldeki konumumuza göre dev.env veya prod.env gibi dosyalarımızı --env-file komutuna tanımlatarak aslında birden fazla environment variable olan dosyamızdaki tüm environment variablesları tanımlatabiliriz.

ARGuments

  • Argslar bizim aslında Dockerfile içinde tanımladığımız variableslardır diyebiliriz.
  • Ve genellikle ENVlerin hemen üstüne tanımlamalıyız çünkü değiştiklerinde aşağısındaki layerların bir daha boş yere hesaplanmasını istemiyoruz.

  • Ve artık aşağıdaki gibi yeni bir build aldığımızda default port env ve expose port 80 olarak hesaplanarak image oluşturulur.

docker build -t feedback-image .

  • Fakat istersekde sadece arg tanımımızı docker run da değiştirerek default portu ezerek yeni bir arg tanımlayabiliriz aşağıda ki gibi.
  • Böylelikle bu buildimiz ise yeni Dockerfile içinde olan DEFAUULT_PORT argını yeni değeri 8000 ile tanımlamış oluruz.

docker build -t feedback-image:defaultArg --build-arg DEFAULT_PORT=8000 .

NETWORKING: (CROSS-) CONTAINER COMMUNICATION

  • Öncelikle githubdan api-example örneğinin starter klasörünü vs codeumuzda indirip açıyoruz.

  • Bu örnekte containerımızla dış dünya arasındaki ilişkileri inceleyeceğiz. Burada 3 çeşit bağlantımız olabilir.

  • Bir API’a istek atıyor olabileceğimiz durumlar. Bu örneğimizde starwars ile ilgili bir apia istek atıyoruz.

2. Local makinamızla kuracağımız communicationlar. Bu örneğimizde mongodb’ye kendi makinamızda bağlanıyoruz.

3. Başka containerlarla olan communicationlar. Bu örneğimizde şuan yok ama ileride bunları da yapabiliyor olacağız.

API Communications

  • Api communicatinoslar için yapmamız gereken her hangi bir özel ayar yoktur.
  • Yukarıdaki örnek için konuşacak olursam, app.listenı en yukarı alarak mongoose’yi tamamen iptal ediyorum çünkü şuanda bu şekilde dockerda çalıştırırsak imagemizi build alıp mongoose, localhost’a bağlanamayacağı için hata verecek ve app.listen serverımızı çalıştırmayacaktır.
  • Aşağıdaki gibi yapıp build alıyoruz,

docker build -t api-image .

  • Daha sorna run yapıyoruz

docker run --rm --name api-container -p 3000:3000 api-image:latest

  • Ve şimdi localhost:3000/movies veya localhost:3000/people ‘a gidersek swapi.dev apiına atyıp aldığımız responseleri, kendi responselerimiz olarak browserda göreceğiz. Yani hiçbir ayar yapmadan api isteklerini atıp alabiliyoruz.

Your Host Machine Communications

  • Şimdi tekrar yukarıdaki satırı eski haline getiriyoruz.
  • Tek yapmamız gereken localhost yerine host.docker.internal kullanmaktır. Bu o anki hostunuzun adresi ne ise onun tarifidir. Localhost ise localhost, www ile başlayan özel bir AWS hostu ise o hostu temsil eder.
  • Ve şimdi tekrar aşağıdaki gibi yapıp build alıyoruz,

docker build -t api-image .

  • Daha sorna run yapıyoruz

docker run --rm --name api-container -p 3000:3000 api-image:latest

  • Ve artık localhost’a attığımız favorites POST isteğinide postman vasıtasıyla atıp, localdeki mongo databasemize birşeyler kaydedebiliriz.

Containers Communications

  • İlk önce mongo imagesini dockerhubdan indirtip makinamızda çalıştıralım.
    docker run --rm --name mongo-container -d mongo
  • Daha sonra docker ps yaptığımızda göreceğiniz üzere mongo-container adında bir container çalışıyor.
  • Daha sonra buna inspect yaparak bu containerın bilgilerini alıyoruz.
    docker inspect mongo-container

  • Ve daha sonra çıkan bilgilerden, Networks’deki bridgeden IPAdress’i alıyoruz ve aşağıdaki gibi mongodb tanımına yazıyoruz.

  • Ve şimdi tekrar uygulamamızın imagesini silip tekrar build alıyoruz ve run yapıyoruz. Göreceksiniz ki artık mongodb miz diğer containerın ip adresine bağlanacaktır. Ve hatta localhost:3000/favorites adresine istek attığınızda göreceksiniz ki boş bir array dönecek çünkü bu mongo imagesinin hostundaki databaseden dönen değerdir.

Networks

  • Containerları dahil edebilip birbirleriyle iletişim kurabilecekleri bir grup gibi düşünebilirsiniz burayı. Birden fazla containerı bir network oluşturup bu networke dahil ederiz run yaptığımız zaman ve daha sonra birbirlerine ulaşmak için tek yapmaları gereken birbir container isimlerini kuyllanmak olacaktır.

docker network create networkName

  • Containerların birbirleriyle iletişim kuracağı network oluşturur

docker network ls

  • Networkleri listeler.

docker network rm networkName

  • network ismi ile networku siler.

  • Yukarıda ki örneğimiz için önce çalışan containerlarımızı durduruyoruz.

  • Ve aşağıdaki gibi bir network kuruyoruz.

docker network create api-network

  • Ve şimdi mongo imagemizi çalıştırıp containerının bu networke dahil olmasını sağlıyoruz. Ve artık mongo imagemizin containerı api-network adında kurduğumuz networke girmiş oluyor.

docker run --rm --name mongo-container -d --network api-network mongo

  • Şimdi uygulamamızdaki app.js’e gelip mongodb bağlantı noktasını mongo imagesi için ayarladığımız container isimi olan mongo-container yapıyoruz aşağıdaki gibi.

  • Ve şimdi bu uygulamamızın imagesinide silip yeniden build alıyoruz.

docker build -t api-image .

  • Ve şimdi uygulamamızı aşağıdaki gibi mongo imagesini run ederken yaptığımız gibi aynı networke dahil edip run yaptırıyoruz.

docker run --rm --name api-container -p 3000:3000 --network api-network api-image:latest

  • Ve artık imagemizde aynı networkde olduğu için diğer containerı container adıyla yani mongo-container ile tanıyım bulup başarılı bir şekilde diğer bir containerla iletişimi kurup isteklerimizi cevap verecektir.