Archive

Tag Archives: load balancer

Bu blog serisini takip edenler bilir ki sunucu işletim sistemi olarak Ubuntu Server tercih ediyorum. Linux/Unix (Nix) tabanlı sistemler sunucu uygulamalarından Windows’tan katrilyon yıl ilerde (abarttığımı düşünebilirsiniz ama tüm sunucu yüklerinin dağılımına bakarsanız hele Cloud üzerinde çalışan işletim sistemi istatistiklerine ne demek istediğimi görürsünüz). Günlük kullanımda ise tercihim Windows 10 işletim sistemi. Docker üzerinde bir şeyler test ederken veya hafta sonu evde notebook ile çalışırken  tercihim Docker Desktop kullanmak. Docker Desktop tercih etmemim sebebi, kurulumunun basit olması ve Kubernetes desteğinin de beraberinde gelmesi.   Bu esnada Ubuntu da kullanmak istersem burada tercihim ise Vmware Workstation üzerinde sanal olarak kurmak ve kullanmak.

Docker Desktop, Windows ve MacOS üzerinde çalışan ve Container ve Mikro Servisler çalıştırmanıza olanak veren bir uygulama. Docker Engine, CLI (komut satırı desteği), Docker Compose ve Kubernetes komponentlerinden oluşuyor. Kurduktan hemen sonra komut satırından hem Docker hem de Kubernetes komutları verip kullanmaya başlayabilirsiniz. Tabiki MacOS kullanıyorsanız. MacOS zaten bir Nix (Linux/Unix) çekirdeği üzerinde çalıştığı için Docker Desktop’un gerek duyduğu çekirdeği zaten otomatik olarak sağlar. Bu sayede de MacOS üzerinde çalışan uygulamalar doğal yollardan bu Docker ortamına erişir ve kullanılır.

Ama Windows üzerinde Docker Desktop kullanmak isterseniz HyperV de kurmanız gerekir(di). HyperV kurduğunuz zaman ise Vmware Workstation kullanamıyorsunuz. Yani Win10 +Docker Desktop + Vmware Workstation hayal (idi). Bu açıdan bu sorunu çözmek için MacOS üzerinde Docker Desktop kullanıyordum. 

MacOS üzerindeki komut satırı doğrudan Docker Engine erişebilir. Yukarıdaki resimde bunu görebilirsiniz. Ayrıca Visual Studio Code gibi editör uygulamaları da zahmetsizce konfig dosyalarına veya bu alt yapıya erişir. Eğer ayrıca Visual Studio Code içine Docker ve Kubernetes plug-in’lerini eklerseniz, bu ortamları çok kolay yönetebilirsiniz.

Bu işlemleri Windows tarafında yapmanın WSL2 çıkana kadar pek imkan yoktu.

WSL  Nedir?

WSL2 nedir diye açıklamadan önce bizim ne işimiz yarar diye başlamamda fayda var. Windows içinde bir Linux çekirdeği olmadığı için Docker Desktop gibi uygulamaları çalıştırmak için farklı yollar izlemeniz gerekir. Bu yollar da Windows üzerinde çalışan uygulamaların bu Docker Destop ortamına direkt olarak erişimini engeller. Bu yüzden bu araçları tam kapasite ile kullanamazsınız. Ta ki WSL 2 gelene kadar.

Windows Subsystem for Linux, Microsoft tarafından Windows işletim sistemi üzerinde (W10 ve W2K19) Linux kodu çalıştırmak için kullanılan bir teknolojidir. Bir sanallaştırma altyapısı üzerinde çalışan tam sürüm bir Linux çekirdeğidir. İlk versiyonu WSL 1 dir ama bu versiyon çok başarılı olmadığı için daha doğrusu gerçek bir Linux çekirdek taşımadığı için (The first release of WSL provides a Linux-compatible kernel interface developed by Microsoft, containing no Linux kernel code/WSL’nin ilk sürümü, Microsoft tarafından geliştirilen ve Linux çekirdek kodu içermeyen Linux uyumlu bir çekirdek arabirimi sağlar) direkt olarak WSL2 ‘yi anlatmayı tercih ettim.

 

WSL 2

WSL 2, sisteme çok fazla yük getirmeyen bir sanallaştırma altyapısı üzerinde çalışan gerçek bir Linux çekirdeği (kernel.org adresinden alınmış ve en son kararlı sürüm) sağlar. Bu sayede aşağıda görülen Microsoft’tan indirebileceğiniz 25 farklı (yazıyı yazarken 16 tanesi ücretsiz, 9 tanesi ücretli, ilerde dah afazla çıkacaktır) Linux dağıtımını çalıştırabilirsiniz. Bu işlemi Windows üzerinde çalışan Vmware Workstation veya herhangi bir sanallaştırma platformu üzerinde çalışan bir Linux dağıtımından ayıran en büyük özellik, hem Windows hem de bu Linux dağıtımı birbirine sorunsuzca erişir.

Bunu basitçe göstermek için aşağıdakii resme bakabilirsiniz. Bu resimde Windows 10 üzerinde çalışan WSL 2 ve yine bunun üzerinde çalışan Ubuntu 20.04 LTS versiyonunu görebilirsiniz. Ubuntu komut satırından explorer.exe çalıştırıp (komutun sonundaki . nokta bulunduğu klasör anlamına gelir) Windows File Explorer uygulamasını açtım.

 

oktay@DTVH-OE:~$ explorer.exe .
oktay@DTVH-OE:~$

 

WSL 2 Üzerinde Docker Desktop

İkinci fayda ve bu yazının amacı Docker Desktop uygulamasının bu WSL 2 Linux Çekirdeği üzerinden çalıştırılması. WSL 2 kurulduktan sonra Docker Desktop kurulurken bir tik atmanız yeterli.

Yukarıdaki resimde bu ayarı görebilirsiniz. Bu ayardan sonra Docker Desktop WSL2 Linux çekirdeğini kullanmaya başlayacak ve Windwos uygulamaları ile başka bir ayar gerekmeden entegre olacak.

Hemen bir Container çalıştıralım.

PS C:\WINDOWS\system32> docker container run -d -p 8080:80 --name oktay_web_test nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
8559a31e96f4: Pull complete
8d69e59170f7: Pull complete
3f9f1ec1d262: Pull complete
d1f5ff4f210d: Pull complete
1e22bfa8652e: Pull complete
Digest: sha256:21f32f6c08406306d822a0e6e8b7dc81f53f336570e852e25fbe1e3e3d0d0133
Status: Downloaded newer image for nginx:latest
26d5bb7571e9c0c481681f504a28eaee0f558f771824d90a5c4d83bd93c96bf4
PS C:\WINDOWS\system32> docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
26d5bb7571e9 nginx "/docker-entrypoint.…" 10 seconds ago Up 8 seconds 0.0.0.0:8080->80/tcp oktay_web_test
PS C:\WINDOWS\system32>

Komutu direkt olarak PowerShell den girdim.

 

PS C:\> docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
26d5bb7571e9 nginx "/docker-entrypoint.…" 3 hours ago Up 3 hours 0.0.0.0:8080->80/tcp oktay_web_test
PS C:\> curl.exe http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
PS C:\>

Yukarıda PowerShell çıktısından görebileceğiniz gibi hem komut verebiliyorum hem de çalışan Container’e erişebiliyorum.

Yine aynı şekilde Win10 üzerindeki browser ile dah önceden çalıştırdığım Nginx web servisine ulaşabiliyorum.

 

Ek Okuma:

  1. https://medium.com/codefiction/windows-subsystem-for-linux-2-wsl-2-en-b%C3%BCy%C3%BCk-a%C5%9Fklar-kavgayla-ba%C5%9Flar-4cdc11770c7f
  2. https://devblogs.microsoft.com/commandline/shipping-a-linux-kernel-with-windows/
  3. https://ulsoy.org/blog/experiencing-wsl-as-a-linux-veteran-part-1/
  4. https://devblogs.microsoft.com/commandline/whats-new-for-wsl-in-windows-10-version-1903/

2020 yılının Nisan ayında tüm Dünyada Covid19 ile mücadele devam ederken bir yandan da 23 Nisan günü 2 güzel şeyi birlikte yaşadık: bunlardan birincisi,  başta Ulu Önder Ebedi Başkomutan Gazi Mareşal Mustafa Kemal Atatürk  olmak üzere bir avuç demokrasi aşağı insan tarafından Egemenlik Kayıtsız Şartsız Milletindir ilkesi ile Millet Meclisi’nin açılmasının 100ncü Yılı idi. İkinci ise uzun zamandır beklediğimiz Ubuntu 20.04 LTS versiyonunun yayınlanması oldu. Ubuntu 20.04 LTS konusunda fırsat bulursam ayrı bir yazı yazarım, pek çok yeni özellik geldiği için birkaç kısa cümle ile anlatmak zor.

Yazının bu bölümünde Portainer konusuna Stack özellikleri ile devam ederken örnek olarak daha önce kullandığımız Oylama/Voting uygulaması ile devam edeceğim.  Bu uygulama ile ilgili yazdığım iki yazıdan ilkine  Ubuntu Üzerinde DOCKER 7 Nginx Yük Dengeleme Teknikleri ve Swarm Başlangıç  ve ikincisine  Docker Swarm 5 – Cluster Özellikleri Paylaşımlı Volume bu linklerden  erişebilirsiniz. Bu yazıda ise bu uygulamayı Portainer üzerindeki Stack özelliği ile çalıştıracağım. Stack konusunu daha önce Docker Swarm 8 – Stack Başlangıç ve Docker Swarm 9 – Stack Devam diye iki yazı yazmıştım.

Portainer üzerinde Stack çalıştırmak için aşağıdaki yolu kullanarak Stack menüsüne gibilirsiniz.

Yukarıdaki  resimde gördüğünüz gibi, Stack çalıştırmak için 3 yol izleyebilirsiniz. Bunlardan birincisi açılan editör bölümüne direkt olarak YAML dosyası içeriğini yapıştırmak, ikincisi bu YAML dosyasını yüklemek, üçüncüsü ise GitHub üzerindeki,bir YAML dosyanon URL’ini girmek.

Bu yazılara tekrar göz gezdirirseniz, Docker Stack için YAML formatında bir tanım dosyasına ihtiyacımız vardı. Örnek dosyayı  bu linkten yükleyebilirsiniz. Bu yazıda, Voting App için hazır olan bu dosya üzerinde bazı değişiklikler yapacağım. Hatırlarsanız, Voting App bu şekilde kullanıldığında, oyları tutan Postgres database başka bir Docker Node üzerine taşındığında veri kaybı oluyordu. Daha önceki yazılarda bu soruna çözüm olarak, Postgres dosyalarını yerelde değil de NFS sunucu üzerinde tutmuştuk.

Portainer Stack

Aşağıda Voting/Oylama uygulamasını çalıştırmak için hazırlanmış örenek YAML  dosyayı görebilirsiniz. Bu örnek dosyada, services: bölümü altında toplam 5 servis tanımlanıyor. networks: bölümünde 2 Network ve volumes: bölümünde Postgres için bir named volume tanımlanıyor.

version: "3"
services:

  redis:
    image: redis:alpine
    ports:
      - "6379"
    networks:
      - frontend
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
  db:
    image: postgres:9.4
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        constraints: [node.role == manager]
  vote:
    image: dockersamples/examplevotingapp_vote:before
    ports:
      - 5000:80
    networks:
      - frontend
    depends_on:
      - redis
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
      restart_policy:
        condition: on-failure
  result:
    image: dockersamples/examplevotingapp_result:before
    ports:
      - 5001:80
    networks:
      - backend
    depends_on:
      - db
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - frontend
      - backend
    depends_on:
      - db
      - redis
    deploy:
      mode: replicated
      replicas: 1
      labels: [APP=VOTING]
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s
      placement:
        constraints: [node.role == manager]

networks:
  frontend:
  backend:

volumes:
  db-data:

Yukarıdaki örnekteki kırımız ve bold kısımları alırsam, Postgres DB çalışması için gereken miminum konfig dosyasını elde etmiş olurum. Yani yeni hali şöyle olur.

version: "3"
services:
  db:
    image: postgres:9.4
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        constraints: [node.role == manager]
 
networks:
  frontend:
  backend:

volumes:
  db-data:

Ben bu konfig YAML dosyasını kopyala yapıştır ile editör dosyasına yapıştırırsam, çalışmaya hazır bir Stack oluşur. Tek yapmam gereken bu Stack için bir isim vermek ve sonra Deploy the stack  tuşuna basmak.
Burada çok önemli bir ayrıntıyı belirtmek istiyorum: Portainer, sadece kendi üzerinden oluşturulmuş olan Stack’leri yönetebilir. Eğer başka bir yöntemle, mesela komut satırından bir Stack oluşturursanız, bunu listeler ama yönetemez. 

Bu aşamaya geldiyseniz, aşağıdaki resimlerde olduğu gibi bu çalışan Stack’i kontrol edebilirsiniz. Önce Stacks list tabında tüm Stack’leri listeleyin. Benim test için çalıştırdığım postgres_test_stack’ ı seçin (siz kendi test ortamında ne ad verdiyseniz bunu seçin) ve sonra da menüden logs‘u seçin. Logu kontrol ettiğinizde postgresdb’nin çalıştığını ve bağlantılara hazır olduğunu görürsünüz. (LOG: database system is ready to accept connections)

 

 

Postgres ve NFS

Şimdi de Postgres’i NFS üzerinde çalıştırmak için YAML dosyasına gerekli eklemeleri yapalım. Öncesinde hatırlatma yapmakta fayda var: test ortamında üzerinde NFS çalışan 172.30.1.111 Ip Adresli Ubuntu sunucu vardı. Bu örnekte yine aynı sunucuyu kullanacağım.

root@nfs-server:/mnt/sharedfolder# ll
total 12
drwx------ 3 999 docker 4096 Apr 20 15:04 ./
drwxr-xr-x 3 root root 4096 Sep 28 2019 ../
drwx------ 18 999 docker 4096 Apr 28 10:59 postgres-folder/
root@nfs-server:/mnt/sharedfolder#

Yukarıdaki gibi /mnt/sharedfolder yolu altında postgres-folder adında bir klasör oluşturdum, Postgres dosyalarını bunun içine yazdıracağım, bu sayede burada farklı servilser çalışırsa dosyalar birbirine karışmamış olur.

version: "3"
services:
 db:
    image: postgres:9.4
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"
    volumes:
      - nfsmountpostgres:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        constraints: [node.role == manager]
networks:
  frontend:
  backend:
#volumes:
#  db-data:
volumes:
  nfsmountpostgres:
    driver: local
    driver_opts:
      type: nfs
      o: addr=172.30.1.111       # NFS Sunucu IP Adresi  
      device: ":/mnt/sharedfolder/postgres-folder"  # NFS klasör

Örnek dosya yukardaki gibi olur.

Yukarıdaki resimde bu YAML dosyasının yapıştırılmış hali var.

Bu aşamada Volume tabından oluşturmak istediğimiz volume özelliklerine bakalım:

Yukarıdaki resimde turuncu renk ile belirtilmiş alanda görüldüğü gibi, NFS sunucu üzerinde volume oluştu.

Her Şeyi Bir Araya Getirelim

Artık her şeyi bir araya getirme vakti geldi. Dosya uzun olduğu için kopyala/yapıştır yerine bu GitHub linkinden indirebilirsiniz. Dosyayı  indirdikten sonra çalıştırğınızda Stack ayağa kalkacaktır.

Yukarıdaki resimdeki gibi bu Stack içindeki 5 servis oluştu ve çalışır hale geldi. Bu 5 servise ait olan Containerleri görmek istersek de Containers tabını seçeriz.

 

Kurulum Ortamı

Bir önceki yazıda Docker ortamını GUİ ile yönetmemizi sağlayan Portainer aracını anlatmaya başlamış ve kurulum yapmıştık. Kurulum yaptığım ortamı tekrar hatırlatmakta fayda var:

Bu yazı dizisinin başından beri kullandığım ve isimleri snode1, snode2 ve snode3 olan ve IP Adresleri sırası ile 172.30.1.31, 172.30.1.32, 172.30.1.33 olan 3 sunucu üzerindeki Docker Container ve bunlar üzerinde kurulu olan Docker Swarm Cluster’i kullanıyorum. Yine bu ortamda bulunan ve 172.30.1.111 IP Adresli olan ve üzerinde yine Docker kurulu olan ama Swarm Mode aktif olmayan ayrı bir sunucu daha var.

Normalde Portainer, üzerinde çalıştığı Docker Containeri’de yönetebilir (ilk sayfada gelen Local/Yerel seçeneği). Yani ben 3 node Docker Swarm üzerinde bu Portainer imajını çalıştırıp yine bu Swarm Cluster’i yönetebilirim ama Portainer konfigürasyonun tutulacağı Volume, node’lar üzerinde replike edilmediğinden ve Portainer farklı bir node üzerine taşındığında  veri kaybı olacağından, benim tercihim bu topoloji değil. Portainer imajını Swarm Cluster içinde olmayan bağımsız bir Docker üzerinde çalıştırıyorum. Swarm Cluster içindeki nodeları yönetmek için ise bir bunlar üzerinde Portainer Agent kurdum. Tüm bun ayrıntılar için bir önceki makaleye başvurabilirsiniz.

Docker İmaj Versiyonlamada Latest  Etiketi Sorunu

İlk makaledeki kurulumdan sonra Docker Portainer İmajı’nın yeni versiyonu geldi. Web sayfasına login olduğunuzda bu konudaki bilgilendirme notunu ekranda görebilirsiniz.

Yukarıdaki resimde olduğu gibi yeni bir sürüm var. Benim kurduğum versiyon 1.23.1 ve yeni versiyon (bu yazıyı yazdığım 2020 Mart ayında) 1.23.2.  Container tarafının belki de en güzel tarafı, var olan veriyi kaybetmeden ve hızlıca yeni sürüme geçebiliyor olmamız. Yeni versiyonda ne gibi düzeltmeler olduğunu bu linkten görebilirsiniz.

Önce bizim çalıştırdığımız versiyonu kontrol edelim. Aşağıdaki gibi, docker image ls komutu ile mevcut imajları ve versiyon etiketlerini kontrol edelim. Benim kullandığım imaj için latest etiketi konulmuş ama ben biliyorumki bu imajın versiyonu 1.23.1 ve yenisi 1.23.2.

oktay@nfs-server:~$ docker image ls
REPOSITORY       TAG    IMAGE ID        CREATED     SIZE
portainer/portainer  latest 10383f5b5720  5 weeks ago  78.6MB

Hatırlarsanız, Docker İmaj isimlendirme ile ilgili daha önce bahsettiğim bir sorun var. Bir Container İmajını yüklediğinizde, sizin yüklediğiniz andaki en son imaj latest etiketini alır. Bu imajın yeni versiyonu çıktığında, bu en yeni olan imaj da latest etiketini alır. Yani latest etiketi sadece imajın hazırlandığı için geçerlidir, zaman içinde yeni versyionlar çıktıkça bu etiket kaybolmaz, dinamik değildir.

Aşağıdaki 3 komut ile önce çalışan Portainer Container’in kullandığı imajın ID’sini göstereceğim (komut çıktısı çok uzun olduğu için grep komutu ile ilgili bölümü filtreleyeceğim).

oktay@nfs-server:~$ docker inspect portainer |grep Image
“Image”: “sha256:10383f5b5720d7e1f5f824137034c69b7f6d82cc8aa33afcc4e9d508b561af77“,
“Image”: “portainer/portainer”,

İkinci komut ile benim sunucum üzerinde kullandığım imajın ID’sini göstereceğim (yine grep ile kısalttım)

oktay@nfs-server:~$ docker inspect image portainer |grep Image
Error: No such object: image
“Image”: “sha256:10383f5b5720d7e1f5f824137034c69b7f6d82cc8aa33afcc4e9d508b561af77“,
“Image”: “portainer/portainer”,

Yukarıdaki iki çıktıya bakarsanız imaj ID’leri aynı, yani benim çalıştırdığım Container bu imaj ile çalıştırılmış.

oktay@nfs-server:~$ docker inspect image portainer |grep Created
Error: No such object: image
“Created”: “2020-02-28T23:53:44.570221781Z”,
oktay@nfs-server:~$

Yukarıda gördüğünüz  3ncü komut çıktısında, bu imajın hangi tarihte oluşturulduğunu kontrol ettim, çıktıya göre 28 Şubat 2020, etiket latest diyor ama Portainer sitesinde imaj 1.23.2 mevcut. O zaman güncelleme yapalım.

Portainer Güncelleme

Portainer Container’i ilk çalıştırdığımda, komut satırında aşağıdaki gibi Named Volume parametresi vardı.

-v portainer_data:/data portainer/portainer

Docker, bu volüm üzerinde tuttuğum dosyaları, container stop/start edildiğinde veya silinip tekrar oluşturulduğunda silmez, tutar. Ben bir Containeri silip yeni bir imaj ile tekrar oluşturursam, bu dosyalar silinmez ve bu sayede güncelleme vs yapılırken konfig ve diğer verilerin kaybı önlenir. Yani güncülleme yaparken, Named Volume kullanılarak oluşturulmuş bir Containeri silip, yeni imaj ile tekar aynı Named Volumleri kullanarak oluşturmam yeterli. Aşağıdaki komut ile volümleri listeleyebiliriz:

oktay@nfs-server:~$ docker volume ls
DRIVER VOLUME NAME
local portainer_data

Yukarıdaki volümü kulanarak güncellemeyi veri kaybı olmadan yapacağız.

Önce mevcut Portainer Container’i durduralım ve silelim

oktay@nfs-server:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0e0ebe43ecd0 portainer/portainer “/portainer” 4 weeks ago Up 9 hours 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp portainer
oktay@nfs-server:~$ docker container stop portainer
portainer
oktay@nfs-server:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
oktay@nfs-server:~$

Şimdi de bu Containeri silelim:

oktay@nfs-server:~$ docker container rm portainer
portainer

Volume yerinde mi bakalım:
oktay@nfs-server:~$ docker volume ls
DRIVER VOLUME NAME
local portainer_data
oktay@nfs-server:~$

Şimdi Portaineri yeni imaj ile çalıştıralım. Önce yeni imajı aşağıdaki komut ile indirelim. Dikkat ederseniz versiyon belirttim: 1.23.2

oktay@nfs-server:~$ docker image pull portainer/portainer:1.23.2
1.23.2: Pulling from portainer/portainer
d1e017099d17: Already exists
a7dca5b5a9e8: Pull complete
Digest: sha256:4ae7f14330b56ffc8728e63d355bc4bc7381417fa45ba0597e5dd32682901080
Status: Downloaded newer image for portainer/portainer:1.23.2
docker.io/portainer/portainer:1.23.2

Mevcut imajları listelediğimizde bahsettiğim latest etiket sorunu aşağıda kendini gösterdi. Bu makaleyi yazarken en son imaj 1.23.2 olmasına rağmen daha önce yüklediğim imaj latest etiketini taşıyor hala.

oktay@nfs-server:~$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
portainer/portainer 1.23.2 2869fc110bf7 10 days ago 78.6MB
portainer/portainer latest 10383f5b5720 5 weeks ago 78.6MB
hello-world latest fce289e99eb9 15 months ago 1.84kB
oktay@nfs-server:~$

Şimdi aşağıdaki komut ile, Portainer Containeri tekrar çalıştıracağım. Bu komut aslında bir önceki makalede kullandığım komut ile aynı. Tek fark önceden yeni Portainer imajını kullandım ve komutun en sonunda yer alan kullanılacak olan imaj bölümünde portainer/portainer:1.23.2 ile bunu belirttim. Yine ilk komutta kullandığım Named Volume burada da kullandım. Bu sayede veri kaybı olmayacak.

oktay@nfs-server:~$ docker container run -d -p 8000:8000 \
-p 9000:9000 \
--name=portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer:1.23.2

Yukarıdaki komutta imajın versiyonunu da belirtim. Şimdi aşağıdaki komut ile çalışan containerlere bakalım:

oktay@nfs-server:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e2da94c743c portainer/portainer:1.23.2 “/portainer” 3 minutes ago Up 3 minutes 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp portainer

Yeni versiyoyonu kontrol edelim. Tekrar Portainer Web sayfasına login olun. Eğer konfigürasyon kaybolmadı ise, ilk kurulumdaki kullanıcı/şifre ve diğer ayarlar kalmış olmalı.

Test ortamında, yeni Container başlarken belirtilen Named Volume‘leri bulduğu zaman bunları silip yeniden oluşturmaz, bunları kullanır. Bu sayede veri ve konfigürasyon kaybı olmaz. Yukarıda gördüğünüz gibi, hem versiyon bilgisi güncellendi hem de ilk eklediğim 3 Node Swarm Cluster’da listelendi. Yani veri kaybı yok.

Portainer Container İşlemleri

Portainer,  sadece Container yönetmek için değil hem Stack hem de   Docker Swarm Mode üzerinde Services çalıştırmak için de kullanılır. Yazının bu bölümünde ağırlıklı olarak Container işlemlerinden bahsedeceğim.

İlk sayfada, yani Home ekranında Endpoints bölümünde daha önceden eklediğimiz 3 Node Swarm Cluster var. Bu bölümde ayrıca aşağıdaki resimde de gördüğünüz gibi özet bilgi var. 0 Stack, 1 Service, 14 Container gibi.

Bu ekranda 3NodeDockerSwarmOktayLab bölümüne tıklarsak, ayrıntıları göreceğimiz ekran gelir. Bu ekranda bu Cluster’ın özellikleri listelenir. Mesela Containers bölümünde, 14 Container için 3 çalışan ve 11 durmuş bilgisi var. Ayrıca 2 Volume ve 5 İmaj bulunduğu bilgisi de bu ekranda var.

Buradan örneğin Containers sekmesine tıklarsak tüm çalışan ve durmuş olan containerleri, volümleri, imajları, networkleri, servisleri görürüz. Yani genel bilgi ekranından daha fazla bilgi alabileceğimiz ayrıntı ekranlarına ulaşabiliriz.

Yukarıdaki resimdeki gibi, her 3 Swarm Cluster nodeları üzerindeki çalışan ve durmuş tüm Containerler burada listeleniyor. Bu ekranda artık işlem yapabiliriz. Ben yer kaplamasınlar diye durmuş olan tüm Containerleri sileceğim. Bunları seçip Remove tuşuna basmam yeter. Bu arada ekranda kaç satır listeleneceğini bu akranın sağ alt tarafından seçebilirsiniz. Bu sayede büyük ekran ile çalışırken daha fazla satır görme imkanınız olur.

Yukarıdaki 2 resimde sileceğim Containerleri seçtim ve Remove tuşuna bastım. Alttaki resimdeki gibi benden Non-Persistent volümleri silmemi isteyip istemediğini sordu. Burada hemen şunu anlatmam gerek. docker container rm <container_adı> ile Containerleri sildiğiniz zaman, bu komut volümleri silmez. Aynı şekilde docker system prune komutu da  volümleri silmez. Bu işi için ya docker volume rm <volume_adı> komutu ile manuel silme işlemi yapılmalı ya da docker volume prune komutu ile temizlenmeli. Silme işlemi yapılmaz ise zaman içinde bu volümler sunucu üzerinde ciddi yer kaplamaya başlar.

Container Ekleme

Bu sayfada yapabileceğiniz bir diğe işlem de Container oluşturma. Normalde docker container run –parametre_1 –parametre_n imaj_adı şeklindeki format ile bir container çalıştırabiliriz.

 

Yukarıdaki resimde de görüldüğü gibi, Container Adı, imajın hangi registeryden çekileceği (normalde burada Hub.Docker.Com da kullanılabilir veya kendi özel registry’niz varsa o da kullanılabilir), hangi node üzerinde çalıştırılacağı, hangi portların açılacağı gibi ayarlar var.

Access Control

Bir alt bölümde Access Control/Erişim Kontrolü var. Burada bu Container’e kimlerin erişebileceği var. Bu bölümde kullanıcı belirtmeden önce aşağıda anlattığım gibi kullanıcı oluşturmak gerek.

Yukarıdaki resimde görüldüğü gibi, sol taraftaki komut ağaçında User seçilirse, açılan ekranda kullanıcı oluşturup bu kullanıcıya gerekli yetkileri, gerekirse admin yetkisi verebilirsiniz. Bu sayede, farklı Container’lere kimlerin hangi yetki ile erişileceği kontrol edilmiş olur.

İleri Seviye Ayarlar

Sayfanın daha alt tarafında ise daha da ayrıntılı ayar yapılabilecek olan bölümler var.

Yukarıda görüldüğü gibi, bu bölümde Command & logging, Volumes, Network, Env, Labels, Restart policy, Runtime & Resources, Capabilities başlıkları altında 8 bölüm var.

Yazıyı bu aşamada bitiriyorum. Bir sonraki bölümde ağırlıklı olarak Swarm Servislerinden bahsedeceğim.

Docker Swarm ile ilgi bu yazı dizisinde ve daha önceki Docker yazılarında, Container ile ilgili çalıştırma, ayar ve yönetim işlemini hep komut satırı (CLI) üzerinden yaptım. Yazının bu bölümünde Docker ve Swarm için kullanılabilecek pek çok grafik kullanıcı ara birimden (GUI) biri olan Portainer‘ı anlatmaya çalışacağım. Eğer Internet üzerinde kısa bir araştırma yaparsanız, Portainer gibi pek çok farklı yazılım olduğunu görürsünüz. Bu yazıda da daha önceki yazılarda kullandığım 3 node Docker Swarm altyapısını kullanacağım.

Portainer‘in kendisi de Container olarak çalıştığı için ve bir web arayüz sağladığı için, aşağıdaki gibi tek bir komut ile çalıştırmak ve hemen web sayfasından ulaşıp kullanmak mümkündür: (Bu komutun en basit hali olup test ortamlarında kullanılabilir ama production ortamlarında veri kaybı olmaması için Volume atanması vb. kurulum bölümünde ayrıntılı anlatacağım gibi ek parametreler gerekecetir)

 docker run -d -p 9000:9000 portainer/portainer

Yukarıdaki komut, Portainer çalıştırmanın en yalın hali ve sonrasında Container IP adresi üzerinden web arayüzüne erişip kullanmaya başlayabilirsiniz. Portanir web arabirimi 9000 nolu port üzerinde çalışır. Benim örneğimde Portainer çalışan Docker Container IP 172.30.1.111 olduğu için web arabirimine bu IP Adresini ve Portainer portu olan :9000‘i ekleyip erişiyorum. Yani 172.30.1.111:9000

Portainer default olarak admin adlı bir kullanıcı ile gelir ama bu kullanıcı için bir şifre atanmamıştır. Bu ilk web sayfasında  dikkat ederseniz sizden ilk yapmanız istenilen admin default kullanıcısı için en az 8 karakter uzunluğunda bir şifre girmenizdir. 

Şifreyi girdikten sonra gelen ilk sayfa aşağıdaki gibidir. Bu sayfada dikkat ederseniz Lokal, Uzak, Agent ve Azure üzerindeki ortamları yönetebiliriz.

Şimdi artık ayrıntılı kuruluma başlayabiliriz.

Kurulum

Kurulum için bu linkteki Portainer sayfasında ayrıntılı açıklama bulabilirsiniz.

Kurulumu başlamadan önce kurulum yapacağım ortamı anlatmakta fayda var: Bu yazı dizisinin başından beri kullandığım ve isimleri snode1, snode2 ve snode3 olan ve IP Adresleri sıra ile 172.30.1.31, 172.30.1.32, 172.30.1.33 olan 3 sunucu üzerindeki Docker Container ve bunlar üzerinde kurulu olan Docker Swarm Cluster’i kullanacağım. Yine bu ortamda bulunan ve 172.30.1.111 IP Adresli olan ve üzerinde yine Docker kurulu olan ama Swarm olmayan ayrı bir sunucu kullanacağım.

Normalde Portainer, üzerinde çalıştığı Docker Containeri’de yönetebilir (ilk sayfada gelen Local/Yerel seçeneği). Yani ben 3 node Docker Swarm üzerinde bu Portainer imajını çalıştırıp yine bu Swarm Cluster’i yönetebilirim ama Portainer konfigürasyonun tutulacağı Volume, node’lar üzerinde replike edilmediğinden ve Portainer farklı bir node üzerine taşındığında  veri kaybı olacağından, benim tercihim buı topoloji değil. Portainer imajını Swarm Cluster içinde olmayan bağımsız bir Docker üzerinde çalıştıracağım.

Önce aşağıdaki komut ile Portainer için bir Named Volume oluşturacağım: Bu sayede eğer bir aşağıda çalıştıracağım Portainer Container durup tekrar başlar ise veri kaybıu olmayacak.

oktay@nfs-server:~$ docker volume create portainer_data
portainer_data
oktay@nfs-server:~$

Şimdi de Portainer için Containeri çalıştıralım:

oktay@nfs-server:~$ docker run -d -p 8000:8000 -p 9000:9000 \
                    --name=portainer --restart=always \
                    -v /var/run/docker.sock:/var/run/docker.sock \
                    -v portainer_data:/data portainer/portainer
1afc1f94480d0483ef3b1d42a12f7585c36995ef9a6c0d8949c50f6c405d586e
oktay@nfs-server:~$

Yukarıdaki komutta, Swarm Cluster dışında bulunan ve tek başına çalışan bir Docker üzerinde Portainer servisini kurdum. Bundan sonra yapmam gereken şey ise, Swarm Cluster üzerinde Portainer Agent çalıştırmak. İşin güzel tarafı bu Agent da Container olarak geliyor. Bu sayede ilk aşamada kurduğum Portainer servisi bu agent ile konuşup yönetim işlemini yapacak. Önce Agent servisi için bir network oluşturalım:

docker network create portainer_agent_network --driver overlay

Şimdi de Portainer Agent’i Swarm Servisi olarak kuralım:

docker service create --name portainer_agent \
  --network portainer_agent_network \
  --publish mode=host,target=9001,published=9001 \
  -e AGENT_CLUSTER_ADDR=tasks.portainer_agent --mode global \
  --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
  --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \
  portainer/agent
syzkr9xd52kaipr5p0zpdkya0
overall progress: 3 out of 3 tasks
oqccatavyxxp: running [==================================================>]
fzu4bfa64e6g: running [==================================================>]
purwpk2xz25x: running [==================================================>]
verify: Service converged
oktay@snode1:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
syzkr9xd52ka portainer_agent global 3/3 portainer/agent:latest
oktay@snode1:~$

Komut gördüğünüz gibi uzun, eğer web sayfasında görünüm bozuk ise bu bölümü copy/paste ile bir text dosyaya yapıştırıp görebilirsiniz. Bu komut ile Manager rolünde olan tüm Docker Swarm node’ları üzerine bu servis kuruldu. Bu test ortamında her 3 node da Manager rolünde olduğu için hepsinin üzerine bu servis kuruldu.

Servisin port numarası 9001. Bu servis Load Balancer arkasında olduğu için her 3 fiziksel node Ip Adresi üzerinden erişebilirim.

Yukarıdaki resimdeki bağlanma seçeneklerinden Agent olan bölümü seçeceğim. Burada bağlantı yapacağım sistem için bir isim ve Ip Adresi ve Port bilgisi girmem gerek.  Ben kendi test ortamımda isim olarak 3NodeDockerSwarmOktayLab adını seçtim. Ip adresi ve port olarak ta manager nodelardan biri olan snode1 ip adresini yani 172.30.1.31 ve port olarak ta 9001 girdim.

Bu bilgileri girdikten sonra Connect tuşuna basınca Home/Endpoints ekranı geldi ve özet halinde test ortamındaki Swarm Cluster geldi.

Yukarıdaki resimdeki gibi özet ekranı üzerinde Stack, Servis, Container adetleri görünüyor. Eğer 3NodeDockerSwarmOktayLab kısmına tıklarsam ayrıntı sayfası gelir.

Yazının bir sonraki bölümünde her bölümü ayrıntılı olarak inceleyeceğiz.

 

EK OKUMA:

Portainer kurulum sayfası. 

Bu 9ncu bölümde, 8nci bölümde başladığımız Docker Stack konusuna devam edeceğiz. Aslında 8nci bölüm, Swarm Stack konusunun daha iyi anlaşılabilmesi için Docker Compose konusuna giriş üzerine idi. Aralarında pek çok fark olmasına rağmen her iki yöntem de, YAML formatı kullanan dosyalar ile çalıştığından, konuya bu şekilde girmenin daha faydalı olacağını düşündüm.

Öncelilke Docker Compose, Docker Engine üzerinde çalışır; Docker Stack ise Swarm üzerinde çalışır.

Swarm ile ilgili önemli bir not: Tek bilgisayarda çalışsa bile, Docker için Swarm Mode’u tercih edin. Secrets, Layer 4 Node Balancer (Docker Enterprise versiyonda Layer 7 Load Balancer), Overlay Network gibi bazı teknolojiler sadece Swarm Mode aktif iken çalışır. En önemlisi ise, istediğniz anda tek bir komut ile bu tek başına çalışan Docker Swarn Mode sunucuya istediğiniz kadar Node ekleyebilirsiniz.

Docker Compose ayrı bir proje olup (Python temelli) bu yüzden Docker Engine üzerine ayrıca yüklenmesi gerekir. Docker Stack ise Docker Engine içinde gelir ve Swarm Mode aktif edildiğinde kullanıma hazırdır.

Docker compose için kullandığınız YAML dosyasını Docker Swarm için de kullanabilirsiniz. Dikkat etmeniz gereken konu, bu YAML dosyanın Versiyon 3 kurallarına göre yazılmış olması gerekir.

Docker Compose içinde build komutunu kullanabilirsiniz ama Swarm Stack build komutunu dikkate almaz, daha önceden hazırlanmış olan Container imajı ister.

Docker Stack Tek Node Örnek

Bu bölümde Docker Stac için tek node bir Swarm Cluster kullanacağım. Bunun için, yani Swarm Mode’u aktif etmek için Docker Engine kurulu bir makinede docker swarm init komutunu vermeniz yeterli

Bu bilgilerden sonra bir örnek yapalım. Aşağıdaki komut ile Swarm içindeki node’ları listeleyelim:

oktay@ubuntu1804:~/stack_test$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
3ls5zt36sg0uw23nlt0yhloic * ubuntu1804 Ready Active Leader 18.09.7
oktay@ubuntu1804:~/stack_test$

Görüldüğü gibi tek node bir Swarm Cluster var.

Bu aşamada yazı disinin bir önceki bölümünde Docker Compose için kullandığımız örnek Compose yaml dosyasını indirip Stack için kullanalım: Aşağıdaki komutta bu örnek dosyanın github linki var. wget komutu ile bu dosyayı bulunduğunuz klasöre indirebilirsiniz.

oktay@ubuntu1804:~/stack_test$ wget https://raw.githubusercontent.com/OktayEgi/docker-compose/master/nginx2.yaml
–2020-01-31 10:58:25– https://raw.githubusercontent.com/OktayEgi/docker-compose/master/nginx2.yaml
HTTP request sent, awaiting response… 200 OK
Length: 199 [text/plain]
Saving to: ‘nginx2.yaml’

nginx2.yaml 100%[====================================================================================================================================>] 199 –.-KB/s in 0s

2020-01-31 10:58:26 (12.4 MB/s) – ‘nginx2.yaml’ saved [199/199]

Bu yaml dosyasının içine bakalım. Biz bu dosyayı Compose örneği için yazmıştık ama Stack için de kullanabiliriz. Hatrılarsanız bu dosyada web ve db adında 2 servis tanımlamıştık. Web isimli servis için çalışan Nginx imajının bulunduğu Container’a  webserver adı vermiş ve dış dünyaya 8080 portundan açmıştık. Yine aynı şekilde db adında bir servis için postgres imaj kullanmış ve çalıştığı Container’ın adını dbserver koymuştuk. Ve dış dünyayay 5432 portundan açmıştık.

oktay@ubuntu1804:~/stack_test$ cat nginx2.yaml
version : “3.3”
services:
web:
container_name: “webserver”
image: nginx
ports:
– “8080:80”
db:
container_name: “dbserver”
image: postgres
ports:
– “5432:5432”

Şimdi docker stack komutu ile bu stack’i çalıştıralım. Komut formatı

docker stack --compose-file <konfig dosyasının adı>  <stack_adı>

Yukarıdaki örneğe göre bizim komutumuz aşağıdaki gibi olacak: Bir önceki yazıdan indirdiğimiz dosya adı nginx2.yaml ve bu stack için verdiğimiz ad webdb_test.

docker stack deploy --compose-file nginx2.yaml webdb_test

Ignoring deprecated options:

container_name: Setting the container name is not supported.

Creating network webdb_test_default
Creating service webdb_test_web
Creating service webdb_test_db

Yukarıdaki komut çıktısında, STACK bu YAML dosya içinde bulunan container_name opsiyonunun deskteklemediğini ve kullanmayacağını söyledi. Bunun yerine yine çıktıdaki container isimlerine bakarsak bunlara otomatik olarak oluşturduğu <stack_adı>_<servis_adı><.><kopya_no> formatında isim verdi. Bizim STACK adımız webdb_test ve ilk servis  adımız web olduğundan  ve sadece 1 kopya çalıştırdığımızdan adı webdb_test_web.1 oldu. Diğer db servisi için çalışan Container de aynı şekilde isimlendirildi.

Ayrıca bu Stack için bir network oluşturuldu ve bu networke <stack_adı> verildi.

Şimdi çalışan Stack’leri listeleyelim: Bunun için dockser stack ls komutunu kullanacağız. Aşağıdaki çıktıda çalışan Stack’lerin isimleri ve toplam kaç servisten oluştuğu var.

oktay@ubuntu1804:~/stack_test$ docker stack ls
NAME          SERVICES ORCHESTRATOR
webdb_test  2                 Swarm

Bu Stack içinde kaç container var ne hangi node’lar üzerinde görelim. Bu Swarm Cluster tek node üzerinde olduğu için her iki container bu node üzerinde çalışıyor. Bunun için docker stack ps <stack_adı> kullanacağız.

oktay@ubuntu1804:~/stack_test$ docker stack ps webdb_test
ID                   NAME                    IMAGE              NODE          DESIRED STATE CURRENT STATE ERROR PORTS
wzdhbcjfbzzs webdb_test_db.1   postgres:latest  ubuntu1804  Running Running 2 seconds ago
1ys5vran3t6r webdb_test_web.1 nginx:latest       ubuntu1804  Running Running 15 seconds ago

Bu node üzerinde normal container listeler gibi çalışanları listeleyelim:

oktay@ubuntu1804:~/stack_test$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
57a9aa3e4a03 postgres:latest “docker-entrypoint.s…” 22 seconds ago Up 19 seconds 5432/tcp webdb_test_db.1.wzdhbcjfbzzsjpu54p9nkz3c8
bbbde8d61c95 nginx:latest “nginx -g ‘daemon of…” 34 seconds ago Up 32 seconds 80/tcp webdb_test_web.1.1ys5vran3t6rna0hnvvuf1npk
oktay@ubuntu1804:~/stack_test$

Networkleri listeleyelim: Dikkat ederseniz stack adı verilmiş olan bir network var

oktay@ubuntu1804:~/stack_test$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c1c203f719e5 bridge bridge local
d708fd32204c docker_gwbridge bridge local
a38c11f94152 host host local
kdu62dqt9deu ingress overlay swarm
69b4acf65bc9 none null local
xhj7ulvbk1yd webdb_test_default overlay swarm

Şimdi bu Stack’i kapatalım. Bunun için docker stack rm<stack_adı>  komutunu kullanacağız

oktay@ubuntu1804:~/stack_test$ docker stack rm webdb_test
Removing service webdb_test_db
Removing service webdb_test_web
Removing network webdb_test_default
oktay@ubuntu1804:~/stack_test$

Çıktıda gördüğünüz  gibi hem Container’ler hem de Network temizlendi.

3 Node Docker Swarm Üzerinde Örnek

Bu örneği 3 Node’Lu Swarm Cluster üzerinde çalıştırcağız ve aşağıdaki  bu linkteki örnek dosyayı kullanacağız.  Aşağıdaki komut ile örnek dosyayı yereldeki klaöre indirebilirsiniz.

oktay@snode2:~/stack-test$ wget https://raw.githubusercontent.com/OktayEgi/docker-stack-samples/master/docker-stack.yaml
–2020-01-31 12:53:25– https://raw.githubusercontent.com/OktayEgi/docker-stack-samples/master/docker-stack.yaml
HTTP request sent, awaiting response… 200 OK
Length: 124 [text/plain]
Saving to: ‘docker-stack.yaml’

docker-stack.yaml 100%[====================================================================================================================================>] 124 –.-KB/s in 0s

2020-01-31 12:53:25 (11.9 MB/s) – ‘docker-stack.yaml’ saved [124/124]

Dosyanın içeriğini görmek isterseniz aşağıdaki gibi. Dosya içinde web adında bir servis tanımlanıyor, container imaj olarak nginx kullanılıyor, 3 kopya çalıştırılıyor ve 8080 portundan kullanıma açılıyor.

oktay@snode2:~/stack-test$ cat docker-stack.yaml
version: ‘3’

services:
 web:
 image: nginx
 deploy:
  replicas: 3
 ports:
  – “8080:80”

oktay@snode2:~/stack-test$

Aşağıdaki komut ile bu dosyayı çalıştıralım:

docker stack deploy --compose-file docker-stack.yaml 3websrv

Aşağıdaki komut çıktısında da gördüğümüz gibi  network ve servis oluşturuldu

Creating network 3websrv_default
Creating service 3websrv_web

Şimdi stackleri listeleyelim:

oktay@snode2:~/stack-test$ docker stack ls
NAME    SERVICES ORCHESTRATOR
3websrv 1                 Swarm

Bu Stack’in ayrıntılarına bakalım. Swarm Cluseter’dan beklendiği gibi yükü 3 node üzerine dağıttı.

oktay@snode2:~/stack-test$ docker stack ps 3websrv
ID                       NAME               IMAGE        NODE   DESIRED STATE CURRENT STATE ERROR PORTS
lumxmn0xqp60 3websrv_web.1 nginx:latest  snode1  Running Preparing 14 seconds ago
rt4732fh7q4f     3websrv_web.2 nginx:latest  snode3  Running Starting less than a second ago
d9aqjpyly69s    3websrv_web.3 nginx:latest  snode2  Running Preparing 14 seconds ago

Eğer bu Stack’i servisler çalışırken tekrar yapılandırmak istersek ne olur? Bunun için bu linkteki örnek dosyada nginx sunucu sayısını 6 yaptım.  Aşağıdaki komut ile bu yeni yaml dosyayı yerele indirelim:

wget https://raw.githubusercontent.com/OktayEgi/docker-stack-samples/master/docker-stack_6node.yaml

oktay@snode2:~/stack-test$ ll
total 16
drwxrwxr-x 2 oktay oktay 4096 Jan 31 13:46 ./
drwxr-xr-x 7 oktay oktay 4096 Jan 31 12:53 ../
-rw-rw-r– 1 oktay oktay 124 Jan 31 13:46 docker-stack_6node.yaml
-rw-rw-r– 1 oktay oktay 124 Jan 31 12:53 docker-stack.yaml

Yeni dosyanın içine bakalım:

oktay@snode2:~/stack-test$ cat docker-stack_6node.yaml
version: ‘3’

services:
web:
image: nginx
deploy:
replicas: 6
ports:
– “8080:80”

Dikkat edersniz ik dosya arasındaki fark çalışan nginx sunucu sayısının 3’ten 6’ya çıkması:

Şimdi de aşağıdaki komut ile bu dosyayı aynı stack adı ile çalıştıralım: Bizim stack adımız 3websrv idi.

docker stack deploy --compose-file docker-stack_6node.yaml 3websrv
Updating service 3websrv_web (id: 4zznaru96redyouqg1r1oj2ek)

Bu komutu çalıştırdıktan sonra, Swarm Cluster aynı isimde bir stack olduğu için, bunun yeni bir stack oluşturma komutu değil de mevcut stack’i güncellemek için verildiğini anladı ve çalışan stack ile yeni konfig dosyası içindeki konfigürasyonu eşit hale getirdi. Eski ile yeni dosya arasındaki fark sunucu sayısının 3 yerine 6 olması idi ve Swarm sunucu sayısını güncelledi.

Kontrol etmek için aşağıdaki komutu verelim:

Önce Swarm tarafından oluşturulmuş olan servise bakalım: Dikkat ederseniz ID önceki komtunun çıktısındaki Servis ID ile aynı  4zznaru96red

oktay@snode2:~/stack-test$ docker service ls
ID                     NAME               MODE       REPLICAS  IMAGE           PORTS
4zznaru96red 3websrv_web    replicated  6/6                nginx:latest    *:8080->80/tcp

oktay@snode2:~/stack-test$ docker stack ps 3websrv
ID                       NAME IMAGE                       NODE DESIRED STATE CURRENT STATE ERROR PORTS
lumxmn0xqp60  3websrv_web.1 nginx:latest  snode1 Running Running 42 minutes ago
rt4732fh7q4f      3websrv_web.2 nginx:latest  snode3 Running Running 43 minutes ago
d9aqjpyly69s     3websrv_web.3 nginx:latest  snode2 Running Running 42 minutes ago
b3w71vkzrxqp   3websrv_web.4 nginx:latest  snode1 Running Running 18 minutes ago
9epqoe60mz8y  3websrv_web.5 nginx:latest  snode3 Running Running 18 minutes ago
je9g21dwrw1o  3websrv_web.6 nginx:latest  snode2 Running Running 18 minutes ago
oktay@snode2:~/stack-test$

Çıktıdan gördüğünüz gibi çalışan sunucu sayısı 6 oldu.

Yazı dizisinin bu 8nci bölümünde, Docker Swarm Stack ile devam edeceğim. Stack konusu nedir diye başlamadan önce, daha önce yazdığım Docker Container serisi esnasında bahsetmediğim Docker Compose özelliğinden bahsedeceğim. Compose konusunu anlayınca (arada bir sürü fark var olsa da), Stack konusunu anlamak daha kolay olacak. Öyleyse  bir süreliğine Container konusuna geri dönelim.

Docker Compose

Docker Compose, oluşturulacak olan Containerlerin ve bağlı komponentlerin (network, volume, ortam değişikenleri vb.) YAML formatında bir dosya içinden çalıştırılmasıdır. Bu yazıya kadar yazdığım tüm yazılarda, komutları komut satırından tek tek girdim. Bu tek Container veya  az argümanlı komutlar için sorun olmasa da çoklu Container ve çoklu parametre gerektiren durumda sorun olur. Örnek vermek gerekirse, tek bir MySql Comtainer çalıştırmak için aşağıdaki gibi karmaşık ve uzun bir komut girmeniz gerekebilir:

docker run \
 --detach \
 --name=test-mysql \
 --network=mysqlnet \
 --memory=2G \
 --env="MYSQL_ROOT_PASSWORD=mypassword" \
 --env="MYSQL_DATABASE=test" \
 --env="MYSQL_USER=testuseroktay" \
 --env="MYSQL_PASSWORD=testpassoktay" \
 --volume=$(pwd)/test-mysql/conf.d:/etc/mysql/conf.d \
 --volume=$(pwd)/mysql-datadir:/var/lib/mysql \
 --restart unless-stopped \
 --publish 3306:3306 \
 mysql:8 \
 --max-connections=200 \
 --character_set_server=utf8 \
 --collation_server=utf8_unicode_ci

Yukarıdaki örnekteki gibi bir Container çalıştırma komutunu CLI’dan (komut satırında) girmek ve hata ayıklamak zor olacaktır. Bir de Voting/Oylama uygulamasındaki gibi 5 Container, 2 Network ve Volume oluşturulması gerektiren bir kurulumda komutları tek tek girmek çok daha zor olacaktır.

Halbuki bu komutları komut satırından değil de daha önceden hazırladığımız bir dosya üzerinden çalıştırsak daha kolay olmaz mı?

Bunun için 2 şeye ihtiyacımız var:

  1. Container çalıştırmak veya network/volume  oluşturmak için girilen komutların bulunduğu bir dosya:docker-compose.yaml
  2. Bu dosyayı okuyup gerekli Docker komutlarına çevirecek bir yazılım: docker-compose komut satırı aracı 

Docker-compose komut satırı aracı

Bu komut satırı aracı, aşağıda ayrınıtılı şekilde anlatacağım docker-compose.yaml dosyasını işler ve Docker Api ile iletişim kurarak bunları Docker komutlarına dönüştürür. Normalde sadece docker-compose up komutu verilmesi yeterlidir. Komutun bu hali, bulunduğu klasör içindeki docker-compose.yaml dosyasını açar ve talimatları yerine getirir. Eğer yaml dosyasının adı docker-compose değilse veya başka bir lokasyonda ise -f parametresi ile belirtilebilir.

YAML Dosyası

Bu dosya bir text dosyası olup YAM kurallarını kullanır. Dosya uzantısı yaml veya yml olabilir.Kısaca 4 bölümden oluşur.

version:
services:

volumes:

networks:

version : bu bölümde belirrtiğiniz versiyon numarası, docker engine uyumluluğu ile alakalı olup versiyon numaraları 1.0 ile 3.7 arasında olabilir. Ek bilgiye bu linkten erişebilirsiniz. Eğer version kısmı boş olursa version 1.0 olarak işlem görür. Ben bu yazıyı yazarken en yüksek versiyon 3.7 idi.

services: bu bölümde docker container run ile çalıştırdığımız imaj adı belirtilir ve varsa diğer parametreler belirtilir. .

volumes: oluşturulacak olan volumler bu kısımda belirtilir

networks: oluşturulacak olan networkler bu ksımda belirtilir.

Şimdi çok kısa bir docker-compose.yaml file yazalım: Bu dosyayı bu linki kullanarak github üzerinden indirebilirsiniz.

oktay@ubuntu1804:~/composetest/shortsample$ cat docker-compose.yaml
version : "3.3"
services:
   web:
    image: nginx

Yukarıdaki yaml dosyası birebir docker container run nginx ile aynı. YAML formatı, yukardaki dosyada da görebileceğiniz gibi, ana bölümü en sola bitişik ister (bizim örneğimizde version: ve services: bölümü), bu ana bölümün altındaki alt başlıklar sağa doğru daha içerden başlar.

Bu dosyayı çalıştırmadan önce mevcut network, volume ve içinde çalışılan klasörleri kontrol edelim: İçinde bulunduğum klasör shortsample adında ve default network haricinde network ve volume yok.

oktay@ubuntu1804:~/composetest/shortsample$ docker network ls
NETWORK ID NAME DRIVER SCOPE
55aa0273e754 bridge bridge local
a38c11f94152 host host local
69b4acf65bc9 none null local
oktay@ubuntu1804:~/composetest/shortsample$ docker volume ls
DRIVER VOLUME NAME
oktay@ubuntu1804:~/composetest/shortsample$ pwd
/home/oktay/composetest/shortsample

Şimdi bu yaml dosyayı çalıştıralım: Bunun için docker-compose up dememiz yeterli. Bu komut içinde çalıştığı klasörde bir docker-compose.yaml (uzantı .yml de olabilir) arar ve çalıştırır.

oktay@ubuntu1804:~/composetest/shortsample$ docker-compose up
Creating network "shortsample_default" with the default driver
Creating shortsample_web_1 ... done
Attaching to shortsample_web_1

Control-c ile çıktım. Şimdi aynı komutu docker-compose up -d olarak çalıştıracağım. Yukarıdaki örnekten farklı olarak komutta -d (detach) parametresini kullandık. Bu aynen docker run -d komutundaki gibi container geri planda çalıştırılır, sonra komut satırına döner.

oktay@ubuntu1804:~/composetest/shortsample$ docker-compose up -d
shortsample_web_1 is up-to-date
oktay@ubuntu1804:~/composetest/shortsample$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
538611df621c nginx "nginx -g 'daemon of…" 50 seconds ago Up 47 seconds 80/tcp shortsample_web_1
oktay@ubuntu1804:~/composetest/shortsample$ docker volume ls
DRIVER VOLUME NAME
oktay@ubuntu1804:~/composetest/shortsample$ docker network ls
NETWORK ID NAME DRIVER SCOPE
065043251b63 bridge bridge local
a38c11f94152 host host local
69b4acf65bc9 none null local
9250d2c0727d shortsample_default bridge local
oktay@ubuntu1804:~/composetest/shortsample$

Yukarıdaki çıktıda dikkat etmeniz gereken şu: Komutun çalıştığı klasörün adı shortsample ve oluşturulan Container adı shortsample_web_1 ve oluşturulan network adı da shortsample. Yani Docker Compose, klasör adını hem imaj adının ön-eki hem de network adı olarak kullandı. Şimdi bu çalışan docker-compose’u kapatalım. Çıktıya dikkat edin:

oktay@ubuntu1804:~/composetest/shortsample$ docker-compose down
Stopping shortsample_web_1 ... done
Removing shortsample_web_1 ... done
Removing network shortsample_default
oktay@ubuntu1804:~/composetest/shortsample$

Docker-compose down komutu çalışan containeri durdurdu, bu containeri sildi ve ayrıca oluşturmuş olduğu networkü de sildi.

Şimdi yeni bir örnek yapalım. Aynı klasör içinde nginx1.yaml diye bir dosya oluşturuyorum ve buna ports parametresi ekliyorum. Bu dosyayı bu github linkinden indirebilirsiniz.

oktay@ubuntu1804:~/composetest/shortsample$ cat nginx1.yaml

version : "3.3"
services:
 web:
  image: nginx
  ports:
   - "8080:80"

oktay@ubuntu1804:~/composetest/shortsample$

Şİmdi çalıştıralım. Ama dosya adı default ad olan docker-compose.yaml olmadığı için -f (filename) parametresi ile dosya adını belirtiyorum.

/shortsample$ docker-compose -f nginx1.yaml up -d
Creating network "shortsample_default" with the default driver
Creating shortsample_web_1 ... done
oktay@ubuntu1804:~/composetest/shortsample$

Şimdi container ve network adına bakalım:

oktay@ubuntu1804:~/composetest/shortsample$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b54c8be97cfa nginx “nginx -g ‘daemon of…” 29 minutes ago Up 29 minutes 0.0.0.0:8080->80/tcp shortsample_web_1
oktay@ubuntu1804:~/composetest/shortsample$ docker network ls
NETWORK ID NAME DRIVER SCOPE
065043251b63 bridge bridge local
a38c11f94152 host host local
69b4acf65bc9 none null local
d8af3e52dfbb shortsample_default bridge local
oktay@ubuntu1804:~/composetest/shortsample$

Görüldüğü gibi yine klasör adı container adı ön-eki ve network adı oldu.

Çalıştırırken dosya adı belirtitiğimiz gibi kapatırken de dosya adı belirtmemiz gerekir:

docker-compose -f nginx1.yaml down

 

Bu örnekte services için container_name parametresini kullanalım. Ayrıca bu örnekte 2 servis tanımlayacağız, web ve db: Bu örneği bu github linkiden indirebilirsiniz. 

oktay@ubuntu1804:~/composetest/shortsample$ cat nginx2.yaml
version : "3.3"
services:
 web:
  container_name: "webserver"
  image: nginx
  ports:
   - "8080:80"
db:
 container_name: "dbserver"
 image: postgres
 ports:
  - "5432:5432"
oktay@ubuntu1804:~/composetest/shortsample$

Container isimlerine bakalım

oktay@ubuntu1804:~/composetest/shortsample$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5cf51767dbe0 nginx “nginx -g ‘daemon of…” About a minute ago Up About a minute 0.0.0.0:8080->80/tcp webserver
133a5e4a1a09 postgres “docker-entrypoint.s…” About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp dbserver

Container isimleri, container_name parametresinde belirttiğimiz değerler oldu.

Bu yazı serisinin ilk 4 bölümünde Docker Swarm ile ilgili genel konuları yazdıktan sonra 4ncü bölümün kapanışında 5 ayrı servisten oluşan Oylama/Voting uygulaması göstermiş ve Swarm’un fiziksel node çökünce bu node üzerinde bulunan ve kapanan servisi başka bir node üzerinde ayağa kaldırdığından bahsetmiştim. Bunu gösterirken de, çöken fiziksel node üzerindeki veriler diğer fiziksel node’lara  aktarılmadığı için database veri dosyaları gibi önemli verilerin kaybolduğunu anlatmıştım. Bu davranışın, Container mimarisinin doğal bir özelliği olduğunu da eklemiştim.

Serinin 5nci bölümünde,  ortama Ubuntu 18.04 LTS üzerinde çalışan bir NFS Sunucu eklemiş ve oluştruduğumuz paylaşım klasörününe Swarm Cluster içinde yer alan her 3 fiziksel node’un  da erişmesi için gerekli ayarları yapmıştık.

Yine bu yazı serisinin  6ncı bölümünde, Swarm Cluster üzerinde çalıştırdığım Docker Container fiziksel node’lar üzerinde yer değiştirdiğinde veri kaybı yaşamasın diye NFS üzerinde oluşturduğum volume’ü atayarak Mysql Database Container çalıştırmıştım.  Bu sayede bir node üzerinde çöken Database Container, başka bir node üzerinde çalıştırıldığında veri kaybı olup olmayacağını test etmiş ve veri kaybı olmadığını görmüştük. Bu işlemi yaparken NFS Server üzerindeki NFS paylaşımı, snode1/2/3 adlı fiziksel node’lara yerelde bağlı idi. Yani NFS Sunucu üzerindeki dosya, fiziksel node’lara mount edilmiş ve df -H komutu ile baktığımda aşağıdaki komut çıktısında olduğu gibi bunu görebiliyordum. Yazının bu yeni bölümünde ise fiziksel node üzerine mount edilmemişken NFS paylaşım kullanmayı anlatacağım.

@snode3:~$ df -H
Filesystem Size Used Avail Use% Mounted on
172.30.1.111:/mnt/sharedfolder 53G 19G 32G 38% /mnt/sharedfolder_client

Uyarı

 Bu makalede kullanacağım bazı ayar ve parametreler genel geçer 
kabul görmüş BT Pratikleri açısından risk teşkil eder.  Örnek olarak
aşağıdaki parametrenin eklenmesi ile NFS servisi her yerden erişilebilir
olur ve uzaktan bağlanan kullanıcının NFS için kullanılan klasörlerde 
yereldeki root kullanıcı haklarına sahip olmasına olanak tanır. Ayrıca
yine makalenin devamında mysql komut satırında kullanıcı adı ve 
şifresinin açık olarak yazılması bir güvenlik açığıdır. 
    Bu sebeple production ortamında bu parametreleri kullanmayın. 
/mnt/sharedfolder *(rw,all_squash,insecure,async,no_subtree_check,
no_root_squash,anonuid=0,anongid=0)

Ön Hazırlık

Bu yöntemde Docker çalışan fiziksel Swarm node’ları üzerinde değil de Swarm Servisi üzerinden NFS bağlantısı yapacağız. Bunun için daha önce snode1/2/3 üzerinde yaptığımız NFS Mount işlemini iptal edeceğiz. Bunun için aşağdaki adımları izleyip önce NFS ortak klasöründeki eski dosyaları sileceğiz ve mount işlemini iptal edeceğiz.

Aşağıdaki komut ile mount edilmiş diskleri ve NFS Server paylaşımını görebiliriz:

oktay@snode2:~$ df -H
Filesystem Size Used Avail Use% Mounted on
udev 2.1G 0 2.1G 0% /dev
tmpfs 414M 1.2M 413M 1% /run
/dev/sda2 43G 8.1G 32G 21% /
tmpfs 2.1G 0 2.1G 0% /dev/shm
tmpfs 5.3M 0 5.3M 0% /run/lock
tmpfs 2.1G 0 2.1G 0% /sys/fs/cgroup
/dev/loop0 94M 94M 0 100% /snap/core/8039
/dev/loop1 94M 94M 0 100% /snap/core/7917
172.30.1.111:/mnt/sharedfolder 53G 19G 32G 38% /mnt/sharedfolder_client
tmpfs 414M 0 414M 0% /run/user/1000
oktay@snode2:~$

Aşağıdaki komut ile bu klasör boş mu diye kontrol ediyorum. Eğer içinde eski çalışmalardan dosya kalmış ise silin, bu sayede yeni Docker Swarm MySql servisini bu NFS Paylaşımı üzerinde oluşturduğumuzda karışıklık olmasın. Çıktıda görüldüğü gibi klasör boş.

oktay@snode2:~$ ls -al /mnt/sharedfolder_client/
total 8
drwxr-xr-x 2 999 docker 4096 Nov 28 07:18 .
drwxr-xr-x 3 root root 4096 Oct 4 09:27 ..
oktay@snode2:~$

Fiziksel nodelar her yeniden başladığında NFS otomatik olarak bağlansın diye /etc/fstab dosyasına aşağıdaki satırı yazmıştık. Bu satırı, başına  # koyarak  açıklama satırı haline getireceğim, bu sayede fiziksel node restart edince NFS mount otomatik olarak bağlanmayacak.

oktay@snode2:~$ cat /etc/fstab
UUID=09895686-f8e5-4beb-96e4-f8180a41d8fa / ext4 defaults 0 0
/swap.img none swap sw 0 0

172.30.1.111:/mnt/sharedfolder /mnt/sharedfolder_client nfs auto rw,sync 0 0

oktay@snode2:~$

UUID=09895686-f8e5-4beb-96e4-f8180a41d8fa / ext4 defaults 0 0
/swap.img none swap sw 0 0

#172.30.1.111:/mnt/sharedfolder /mnt/sharedfolder_client nfs auto rw,sync 0 0

oktay@snode2:~$ cat /etc/fstab
UUID=09895686-f8e5-4beb-96e4-f8180a41d8fa / ext4 defaults 0 0
/swap.img none swap sw 0 0

#172.30.1.111:/mnt/sharedfolder /mnt/sharedfolder_client nfs auto rw,sync 0 0

oktay@snode2:~$

Fiziksel node’u tekrar başlatıyorum ve NFS mount etmiş mi diye kontrol ediyorum.

oktay@snode2:~$ sudo reboot
Connection to 172.30.1.32 closed by remote host.
Connection to 172.30.1.32 closed.

oktay@snode2:~$ df -H
Filesystem Size Used Avail Use% Mounted on
udev 2.1G 0 2.1G 0% /dev
tmpfs 414M 1.2M 413M 1% /run
/dev/sda2 43G 8.1G 32G 21% /
tmpfs 2.1G 0 2.1G 0% /dev/shm
tmpfs 5.3M 0 5.3M 0% /run/lock
tmpfs 2.1G 0 2.1G 0% /sys/fs/cgroup
/dev/loop0 94M 94M 0 100% /snap/core/7917
/dev/loop1 94M 94M 0 100% /snap/core/8039
tmpfs 414M 0 414M 0% /run/user/1000
oktay@snode2:~$

Yukarıdaki çıktıdan gördüğünüz gibi, fiziksel node üzerinde NFS mount edilmedi. Bu işlemi her 3 node üzerinde de yaparak Swarm Cluster’i hazır hale getiriyorum.

İkinci Yöntem İçin Kurulum

docker node ls --format "{{.Hostname}}\
{{.Availability}} {{.Status}}"

oktay@snode2:~$ docker node ls –format “{{.Hostname}}\
> {{.Availability}} {{.Status}}”
snode1   Drain Ready
snode2  Drain Ready
snode3  Active Ready
oktay@snode2:~$

Önce yukarıdaki komut ile NODE durumlarına bakalım: Her üç node hazır ve snode1 ve snode 2 Drain modunda olduğu ve sadece snode3 Active olduğu için eğer bir servis oluşturursam, bu snode3  üzerinde çalışacak. Hatırlarsanız önceki yazıda belirtmiştim, Drain modundaki fiziksel node’lar Manager olarak görev yapmaya devam ederler ama Worker olsalar bile üzerlerinde Swarm Servis Container çalıştırmazlar. Ama docker container run …. komutu ile container çalıştrabilirler.

Aşağıda komut ile bir MySql servisi başlatacağım ve volume olarak NFS Server üzerindeki paylaşımı (172.30.1.111:/mnt/sharedfolder)  kullanmasını belirteceğim.

Komut parametrelerini kısaca anlatmak gerekirse:

src=mysql_vol  mysql_vol adında bir volume oluştur. docker volume ls komutu  verdiğimde bu volum’ü görürüm.

dst=/var/lib/mysql   bir üst satırda bahsettiğm bu volumü Container içinde /var/lib/mysql yoluna bağla. Hatırlarsanız Mysql servisi oluşturacağı dosyaları aksi belirtilmezse deafult olarak bu klasörde oluşturur.

volume-opt=device=172.30.1.111:/mnt/sharedfolder  Oluşturacağın volümü 172.30.1.111 ip adresli NFS Sunucu üzerindeki /mnt/sharedfolder klasörü üzerinde oluştur.

oktay@snode2:~$ docker service create --mount 'type=volume,
                 src=mysql_vol,dst=/var/lib/mysql,
                 volume-driver=local,volume-opt=type=nfs,
                 volume-opt=device=172.30.1.111:/mnt/sharedfolder,
                 "volume-opt=o=addr=172.30.1.111,vers=4,soft,
                 timeo=180,bg,tcp,rw"' --name oktaymysql 
                 --env="MYSQL_ROOT_PASSWORD=mypassword" 
                 --publish 3306:3306 mysql

yj5g33zjehktzxdtzhq40ebvj
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged

Bu komuttn sonra servisin çalışıp çalışmadığına ve hangi node üzerinde olduğuna bakalım:

oktay@snode2:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yj5g33zjehkt oktaymysql replicated 1/1 mysql:latest *:3306->3306/tcp
oktay@snode2:~$ docker service ps oktaymysql
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
i8vv9kaybnlm oktaymysql.1 mysql:latest snode3 Running Running 49 seconds ago
oktay@snode2:~$

Docker Swarm komutlarını Manger rolünde olan herhangi bir node üzerinde çalıştırabilirsiniz. Benim test ortamımda her 3 node Manager rolünde olduğu için komutları herhangi bir node üzerinden verebilirim. Zaten dikkat ettiyseniz snode2 üzerinden komut verdim ve Swarm Cluster en uygun node (tek Active node zaten bu) snode3 olduğu için servisi bunun üzerinde çalıştırdı.

Bu aşamada snode3’e bağlanıp kontrol yapmaya başlayacağım: Önce servisi inspect komutu ile inceleyeceğim (çıktıyı kısalttım)

docker inspect oktaymysql

"Type": "volume",
"Source": "mysql_vol",
"Target": "/var/lib/mysql",
"VolumeOptions": {
"DriverConfig": {
"Name": "local",
"Options": {
"device": "172.30.1.111:/mnt/sharedfolder",
"o": "addr=172.30.1.111,vers=4,soft,timeo=180,bg,tcp,rw",
"type": "nfs"

Aşağıdaki komut ile oluşmuş olan volume’ü listeleyelim:

oktay@snode3:~$ docker volume ls
DRIVER VOLUME NAME
local mysql_vol

Aşağıdaki komut ile bu volume’ü ayrıntılı ineleyelim:

oktay@snode3:~$ docker inspect mysql_vol
[
{
"CreatedAt": "2019-11-28T12:07:56Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mysql_vol/_data",
"Name": "mysql_vol",
"Options": {
"device": "172.30.1.111:/mnt/sharedfolder",
"o": "addr=172.30.1.111,vers=4,soft,timeo=180,bg,tcp,rw",
"type": "nfs"
},
"Scope": "local"
}
]
oktay@snode3:~$

Şimdi de NFS sunucu üzerinde oluşan dosyalara bakalım: Bunun için NFS Sunucuya bağlanıp /mnt/sharedfolder/ yolundaki klasörü listeleyeceğim.

oktay@nfs-server:~$ ls -al /mnt/sharedfolder/
total 178196
drwxr-xr-x 6 999 docker 4096 Nov 28 12:07 .
drwxr-xr-x 3 root root 4096 Sep 28 18:55 ..
-rw-r----- 1 root root 56 Nov 28 12:07 auto.cnf
-rw-r----- 1 root root 3084516 Nov 28 12:07 binlog.000001
-rw-r----- 1 root root 155 Nov 28 12:07 binlog.000002
-rw-r----- 1 root root 32 Nov 28 12:07 binlog.index
-rw------- 1 root root 1676 Nov 28 12:07 ca-key.pem
-rw-r--r-- 1 root root 1112 Nov 28 12:07 ca.pem
-rw-r--r-- 1 root root 1112 Nov 28 12:07 client-cert.pem
-rw------- 1 root root 1676 Nov 28 12:07 client-key.pem
-rw-r----- 1 root root 5387 Nov 28 12:07 ib_buffer_pool
-rw-r----- 1 root root 12582912 Nov 28 12:07 ibdata1
-rw-r----- 1 root root 50331648 Nov 28 12:07 ib_logfile0
-rw-r----- 1 root root 50331648 Nov 28 12:07 ib_logfile1
-rw-r----- 1 root root 12582912 Nov 28 12:08 ibtmp1
drwxr-x--- 2 root root 4096 Nov 28 12:07 '#innodb_temp'
drwxr-x--- 2 root root 4096 Nov 28 12:07 mysql
-rw-r----- 1 root root 30408704 Nov 28 12:07 mysql.ibd
drwxr-x--- 2 root root 4096 Nov 28 12:07 performance_schema
-rw------- 1 root root 1676 Nov 28 12:07 private_key.pem
-rw-r--r-- 1 root root 452 Nov 28 12:07 public_key.pem
-rw-r--r-- 1 root root 1112 Nov 28 12:07 server-cert.pem
-rw------- 1 root root 1680 Nov 28 12:07 server-key.pem
drwxr-x--- 2 root root 4096 Nov 28 12:07 sys
-rw-r----- 1 root root 12582912 Nov 28 12:07 undo_001
-rw-r----- 1 root root 10485760 Nov 28 12:07 undo_002
oktay@nfs-server:~$

Gördüğünüz gibi Mysql dosyaları oluşmuş. Artık testlere başlayabiliriz.

Test Aşaması

Şimdi daha önceki makalede yaptığımız gibi önce Mysql üzerinde table/kayıt oluşturup sonrasında Mysql Swarm Servisini diğer node üzerinden aktif edip veri kaybı olup olmadığına bakacağız. Burada bir noktayı tekrar açıklamakta fayda var. Aşağıdaki örneklerde Mysql sunucuya erişim adresi olarak snode1/2/3 adresleri olan 172.30.1.31, 172.30.1.32, 172.30.1.33 ve  komutları çalıştırdığım snode3 üzerinde 127.0.0.1 (lokal adres) adreslerini kullandım. Daha önce yazdığım gibi, Swarm kurulurken L4 Load Balancer kurduğu için, fiziksel node’lara ait olan tüm IP Adresleri bu Load Balancer üzerinde kullanılır ve siz bizim örneğimizde Mysql olduğu için, gönderdiğiniz tüm komutlar bu Load Balancer tarafından karşılanır ve geride Servis hangi node üzerinde olursa ona doğru yönlendirilir. Bizim örneğimizde Mysql şu anda snode3 üzerinde çalışıyor.

oktay@snode3:~$ mysql -uroot -pmypassword -h172.30.1.32 -P3306 -e ‘CREATE DATABASE test’;
mysql: [Warning] Using a password on the command line interface can be insecure.

oktay@snode3:~$ mysql -uroot -pmypassword -h172.30.1.33 -P3306 -e ‘USE test; CREATE TABLE IF NOT EXISTS `filmler`( `film_adi` VARCHAR(150) NOT NULL , `kategori` VARCHAR(20) , `tarih` YEAR )’;
mysql: [Warning] Using a password on the command line interface can be insecure.

oktay@snode3:~$ mysql -uroot -pmypassword -h172.30.1.31 -P3306 -e ‘USE test;INSERT INTO filmler (film_adi,kategori,tarih) VALUES(“salako”,”komedi”,1974)’;
mysql: [Warning] Using a password on the command line interface can be insecure.

oktay@snode3:~$ mysql -uroot -pmypassword -h127.0.0.1 -P3306 -e ‘USE test;select * from filmler’;
mysql: [Warning] Using a password on the command line interface can be insecure.
+———-+———-+——-+
| film_adi | kategori | tarih |
+———-+———-+——-+
| salako | komedi | 1974 |
+———-+———-+——-+
oktay@snode3:~$

Şimdi testin ikinci aşamasına geçebiliriz. Bunun için önce snode2’yi Active hale getrireceğim, sonra da snode3’ü Drain hale getireceğim. Bu sayede Swarm Cluster snode3 üzerindeki Mysql Containeri snode2‘ye taşıyacak.

oktay@snode3:~$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
oqccatavyxxpiauj2zpa4olwv snode1 Ready Drain Reachable 19.03.5
fzu4bfa64e6gtmn9w6gw2rvyj snode2 Ready Drain Reachable 19.03.5
purwpk2xz25xoxeyutne09s6t * snode3 Ready Active Leader 19.03.5

oktay@snode3:~$ docker node update snode2 –availability active
snode2

oktay@snode3:~$ docker node update snode3 –availability drain
snode3

oktay@snode3:~$ docker node ls
ID                                              HOSTNAME STATUS  AVAILABILITY MANAGER STATUS ENGINE VERSION
oqccatavyxxpiauj2zpa4olwv     snode1          Ready      Drain    Reachable   19.03.5
fzu4bfa64e6gtmn9w6gw2rvyj   snode2         Ready      Active  Reachable   19.03.5
purwpk2xz25xoxeyutne09s6t * snode3         Ready      Drain    Leader        19.03.5
oktay@snode3:~$

oktay@snode3:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
yj5g33zjehkt oktaymysql replicated 1/1 mysql:latest *:3306->3306/tcp

Servisin hangi node üzerinde olduğuna bakalım: snode3 üzerindeki mysql kapandı ve snode2 üzerinde çalışmaya başladı. Yani istediğimiz node değişimi oldu.

oktay@snode3:~$ docker service ps oktaymysql
ID                     NAME              IMAGE         NODE     DESIRED STATE    CURRENT  STATE
scmm7kpt04lv oktaymysql.1    mysql:latest  snode2   Running                  Running 4 minutes ago
i8vv9kaybnlm \_ oktaymysql.1 mysql:latest  snode3   Shutdown               Shutdown 4 minutes ago

Aşağıdaki komut ile Mysql üzerinde bir önceki aşamada oluşturmuş olduğumuz kayıt görünüyormu kontrol edelim.

oktay@snode3:~$ mysql -uroot -pmypassword -h172.30.1.31 -P3306 -e ‘USE test;select * from filmler’;
mysql: [Warning] Using a password on the command line interface can be insecure.
+———-+———-+——-+
| film_adi | kategori | tarih |
+———-+———-+——-+
| salako | komedi | 1974 |
+———-+———-+——-+
oktay@snode3:~$

Yukarıdaki çıktıdan da görüyoruz ki,Mysql database kayıtları geliyor yani Docker Swarm Mysql Container’i başka bir fiziksel node üzerine taşısa da, dosyalar NFS Sunucu üzerinde olduğu için veri kaybı yaşanmadı.

Son Sözler

Daha önce yazdığım bir makalede, 12 Factor App konusunu yazarken, Container tabanlı uygulamalarda akılda tutulması gerekenlerden bahsetmiştim. 4ncü madde Destek Servisleri gibi çevirebileceğimiz bir maddeyi tarif eder. Bu da özellikle bir uygulamanın ihtiyaç duyacağı Database, Mail Sunucusu gibi uygulamaların bu sistemin dışında tutulmasıdır. Çünkü, Container mimarisinde hiçbir şey kalıcı değildir: Bir container çalışmaya başlar, işini yapar ve sonra iz bırakmadan yok olur gider.

Yukarıdaki grafikte ortalama Container yaşam süresi gösteriliyor. Tüm Containerlerin %10’dan fazlası 10 saniyeden daha az ömre sahip. Containerlerin %95’ten fazlasının ömrü 1 haftadan daha kısa (bu ve benzeri istatistiklere 2018 Docker usage report  web sayfasından ulaşabilirsiniz). Bu sebeple bu yazı dizisinin son üç bölümünde anlattığım Database gibi kalıcı uygulamalarının Container üzerinde çalıştırılması sadece test ortamında yapılmalı. Production ortamında database ve benzeri uygulamalara ihtiyaç varsa, bunların daha yüksek erişilebilir/güvenli bir ortamda tutulması en uygun çözümdür.

Ek Okuma:

https://docs.docker.com/storage/volumes/

 

Docker ile ilgili bu yazı dizisinde şimdiye kadar 6 yazı yazdım. Ubuntu sunucu üzerinde Docker Container çalıştırma ile ilgili bu yazı dizisinin ilk bölümünde kurulum konusuna başlamıştık. Yazının ikinci bölümünde kurulum sonrası yapılması gerekenler ve Apache httpd örneği ile devam etmiştik. Üçüncü bölümde örneklere devam ederken Docker İmajları konusuna basitçe değinmiştik Bu makalede ağırlıklı olarak Apache Httpd imajı üzerinden örnek vermiştim.  Yazının dördüncü bölümünde ağırlıklı olarak en çok çalıştırılan imaj olduğu için  nginx örnekleri vermiştim. Ayrıca çalışan Container’in komut satırına nasıl ulaşılacağı, containerin konfigürasyonunun nasıl değiştirileceğini ve  bind mount konusunu anlatmıştım.Yazının 5nci bölümünde ağırlıklı olarak nginx load balancer örneği üzerinden Docker Network konusunu anlatmıştım. Yazının altıncı bölümünde bu ana kadar anklattıklarımı toplamaya çalışmış ve Elasticsearch örneği vermiştim.

Docker üzerinde Nginx ile ilgili yazıda, ön tarafta Nginx sunucu load balancer olarak kullanılmış, kendisine gelen istekleri geride buluan 2 web sunucuya (ki bunlar da Nginx üzerinde çalışıyordu) yönlendirmişti. Bu topolojide Nginx in hem web sunucu hem de Load Balancer olarak kullanılabileceğini görmüştük. Load Balancer olarak kullanmak için aşağıda örneği bulunan konfigürasyon dosyasını nginx.conf adı ile Nginx Container altına /etc/nginx/nginx.conf yoluna bağlamıştık.

events {
worker_connections 1024; ## Default: 1024
}
http {
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
upstream backend {
server webserver1:80;
server webserver2:80;
}
}

Yukarıdaki örnek dosyayı üzerinde Ubuntu ve Docker Engine kurulu fiziksel sunucu üzerinde oluşturup aşağıdaki komutu kullanarak Docker Container’e bağlamıştık.

 docker container run -d -p 80:80 --network webnet \
    -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx

nginx.conf dosyası ayrıntıları

Yukarıdaki komut ile de bu konfig dosyasını kullanark Nginx sunucuyu Load Balancer olarak ayağa kaldırdık. Yukarıdaki konfig dosyasında da görülebileceği gibi en az 3 bölüm olmalı. Birinci bölüm events, ikici bölüm hangi port/servis dinlenecek belirtiğimiz http  bölümü ve son olarak yönlendirmenin yapılacağı sunucu grubu server bölümü.

Evemts bölümünde en azından worker_connections değeri ile her bir instance üzerinde kaç bağlantı olacağını belirtmeniz gerek (eğer bu bölüm olmazsa Nginx nginx: [emerg] no “events” section in configuration hatası verir ve durur). Bizim örneğimizde her worker/instance üzerinde 1024 bağlantıya izin veriliyor. Eğer konfig dosyasının başında bir worker_processes değeri atarsak, bu Nginx üzerinde kaç instance ve her instance üzerinde kaç bağlantı olacağını belirtmiş oluruz. Örnek olarak worker_processes 5 ve worker_connections 1024 yaparsak, bu Nginx üzerinde 5*1024=5120 adet bağlantıya izin verilmiş olur. Eğer worker_processes değeri belirtilmezse 1 (bir) olarak alınır. Bu değerleri seçerken sunucunun her bir core’u için maksimum 1 worker ve connections sayısı için bu işlemcinin kapasitesine göre de (core hızı, L1/2/3 cache bellek büyüklüğü) gibi parametrelere göre karar vermeli. 

Dosyanın http bölümünde servisin hangi porttan dinleyeceği ve bu porta gelen istekleri nereye yönlendireceği belirtilir, aslında burada gelen istekleri karşılayacak olan sanal bir sunucu tanımlanır. Bizim örneğimizde listen 80 ile 80 nolu portta dinleme yapmasını ve buraya gelen istekleri proxy_pass http://backend ile tanımlanan sunuculara göndermesini belirtiyor. Yani isteklerin gönderileceği sunucular backend değeri altında belirtiliyor. Daha önce hatırlarsanız 7 katmanlı OSI Modeline göre Layer3/4/7 de yük dengeleme yapılabilir. Bizim kendi örneğimizde Nginx, kendi IP Adresine 80 nolu porttan gelen istekleri dinlediği için L4/Transport Katmanında yük dengeleme yapıyor. L7/Apllication/Uygulama Katmanında da dengeleme yapabilir.

http { 
server { 
listen 80; 
location / 
{ proxy_pass http://backend;
}

Son bölüm olan upstream bölümü ile gelen isteklerin gönderileceği sunucuların isimleri belirtiliyor. Bu sunucular backend altında tanımlanmış olan  webserver1 ve webserve2 adlı sunucular. Daha sonra göreceğimiz gibi, yük dengeleme ile ilgili ayar bu bölümde yapılıyor.

upstream backend {
server webserver1:80;
server webserver2:80;

Load Balance/Yük Dengeleme Metodları

Buraya kadar konfig dosyasında mutlaka bulunması gereken bölümleri anlattım. Dikkat ederseniz bu dosya içinde Load Balance yöntemini belirtmedim. Load Balance yöntemi ile Nginx’e kendisine gelen istekleri hangi prensiplere göre gerideki sunuculara göndereceğini/dağıtacağını belirtmiş oluruz. Nginx aşağıdaki Load Balance yöntemlerini kullanabilir: Aslında burada anlatılan yöntemler, sadece Nginx için değil yük dengeleme yapan her yazılım/donanım için geçerli olup Nginx özelinde bir fark var ise bunu ayrıca belirteceğim. Nginx Round Robin, Weighted Round Robin, Least Connections, Least Time, Hash, IP Hash yöntemleri ile yük dengeleme yapabilir. Son iki yük dengeleme yöntemi olan Hash ve IP Hash’ tan, daha sonra bahsedeceğim 12 Factor Metodolojisine  göre Container tabanlı sistemlerde kullanılmaması gereken yöntemler olduğundan çok bahsetmeyeceğim.

Nginx config dosyası  içinde Upstream bölümünde yük dengeleme için kullanılacak olan yöntem belirtilir.

Round Robin

Sadece Load Balancer’lar tarafından değil DNS sunucuları tarafından da kullanılan en basit dengeleme metodudur. Eğer herhangi bir yük dengeleme metodu belirtilmez ise Nginx bu yöntemi kullanır. Temel olarak Round Robin yük dengelemede, gelen her istek sırası ile belirtilen sunuculara yönlendirilir.

 



Yukarıdaki resimde (bu resmi bu sitedeki makaleden aldım,  resim ve ilgili makaleye bu linkten erişebilirsiniz 6 ayrı istemciden gelen isteklerin Load Balancer arkasındaki 2 sunucuya nasıl yönlendirildiğini görebilirsiniz. İlk istek birinci sunucuya, 2nci istek ikinci sunucuya, üçüncü istek yine birinci sunucuya şeklinde. Bu yönlendirme yapılırken bu sunucular üzerinde kaç bağlantı var, üzerlerindeki yük nedir kontrol edilmez. Bu sebeple ilk başta sunucular üzerindeki yük dengeli olsa da bir süre sonra bu denge bozulabilir.

Ayrıca klasik Round Robin uygulamasında isteklerin yönlendirildiği sunucuların cevap verip verilmediğine bakılmaz, sunucu kapalı veya cevap veremeyecek durumda olsa bile istekler bu sunuculara gönderilmeye devam eder. Burada Nginx in çok önemli bir farkı vardır: Nginx gerideki sunucu(sunucularda) sorun olduğunda bunlara yönlendirme yapmaz.

Round Robin, sunucuların eşit güçte olduğu ortamlarda veya test ortamlarında kullanmak için uygundur. Tüm sunucuları cpu, bellek, bant genişliği gibi konularda eşit olarak değerlendirir. Ama gerçek dünyada durum bu olmayabilir. Bu açıdan her sunucuyu bir ağırlık vererek farklı performanstaki sunuculara farklı sayıda bağlantı yönlendirmek daha uygun olur. Bu açıdan   Server Weights/Sunucu Ağırlığı parametresi eklenerek sunuculara performanslarına göre bağlantı yönlendirilebilir.

Yukarıdaki resimde, config  dosyası içinde Sunucu1 için 5 ağırlık değeri kullanılmış olup sunucu 2 için değer belirtilmemiştir. Değer belirtilmeyen sunucu için bu değer 1 kabul edildiğinden ilk örnekteki gibi eğer 6 kullanıcı tarafından istek yollanır ise bu isteklerden 5 tanesi sunucu 1’e, 1 tanesi sunucu 2 ye yönlendirilecek.

upstream backend {
server webserver1:80 weight=5;
server webserver2:80;
}
}

Yukarıda config dosyasının upstream bölümünde webserver1 satırında weight=5 parametresi ile bu sunucuya 5 ağırlığı verdim. İkinci satırda webser2 için ağırlık değeri belirtilmediği için otomatik olarak 1 değeri aldı.

Round Robin için soz söz olarak şunu söyleyebiliriz: eğer yük dengeleme yöntemi belirtilmez ise Round Robin otomatik olarak devreye girer ve yine eğer sunucu ağırlığı belirtilmez ise her sunucu için otomatik olarak 1 değeri atanır.

Least Connections

Round Robin tekniğinden bahsederken, gerideki sunucular üzerindeki yük veya bağlantı sayısının kontrol edilmediğini ve bu yüzden bir süre sonra sunucular üzerinde dengesiz dağılım olabileceğini belirtmiştim. Sunuculara başta eşit sayıda bağlantı yönlendirilse bile, kullanıcıların bağlı kalma süreleri değişebileceğinden  bir süre sonra sunucular üzerindeki bağlantı sayıları farklılaşabilir. Şöyle düşünün: Sunucu 1’e bağlanan kullanıcılar, sunucu 2’ye bağlananlara göre daha az bağlı kalıyorlar, bu bir web sunucusu ise web sayfalarında daha az geziniyorlar.  Bunun sonucunda da bir süre sonra her iki sunucuya eşit bağlantı da gönderilmiş olsa Sunucu 1 üzerinde daha az bağlantı varken Sunucu 2 üzerinde daha çok bağlantı olacak. Bu yöntemde Nginx Load Balancer, yükü gerideki sunuculara gönderirken üzerinde en az bağlantı olan sunucuyu seçer.

Zaman içinde sunucularda bağlı bulunan kullanıcı sayısı farklılaşır ve bu da dengesizliğe sebep olur. Yukarıdaki resimde yine 6 ayrı istemciden gelen istekler var.Birinci sunucuda 1 ve 3 nolu istemciler bağlantıyı kesmişler, sadece 5 nolu bağlantı kalmış. İkinci sunucu üzerinde ise 3 bağlantı var ve hepsi aktif. Eğer sadece Round Robin kullanılır ise yeni gelen istek (bizim örneğimzde 6ncı istek)  üzerinde daha fazla bağlantı/yük olmasına rağmen 2nci sunucuya gidecek. Halbuki least_conn paranmetresi kullanılsa idi, aşağıdaki resimde olduğu gibi 6ncı istek 2 nolu sunucuya değil 1 nolu sunucuya gidecekti ve her iki sunucu üzerindeki aktif bağlantı sayısı eşit olacaktı.

Yukarıdaki resimde least connected parametresi ile yeni bağlantı yönlendirilirken sunucular üzerindeki aktif bağlantı sayıları dikkate alındığı için, 6ncı bağlantı Sunucu 2 yerine Sunucu 1’e yönlendirildi ve her iki sunucu üzerindeki bağlantı sayıları dengelenmiş oldu. Bu yöntem için kullanılan parametre least_conn; olup yine config dosyasının upstream bölümüne eklenmeli.

 

upstream backend {
least_conn;
server webserver1:80;
server webserver2:80;
}
}

 

Least Time

Bu özellik Nginx Plus versiyonunda bulunur. Bu ana kadar Nginx versiyonlarından bahsetmedim. Şu ana kadar hep Nginx Open Source (çok çok basite indirip kısaca ücrestiz diyelim) kullandık Ama Least Time gibi ek özellikler istenirse Nginx Plus (kısaca ücretli versiyon diyelim)  versiyonunu kullanmamız lazım. Versiyonlar arası farklar Internet üzerinde bulıunabilir. Tekrar konumuza dönersek, Least Time yönteminde yük dengelenirken hem sunucu üzerindeki bağlantı sayısına hem de Ortalama Cevap Dönüş Zamanına bakılır. Yani Nginx kullanıcıdan gelen isteği gerideki hangi sunucuya göndereceğini hesaplarken, aşağıda listelediğim 2 değerden birine göre karar verir:

Kısaca söylemek gerekir ise, bir sunucunun performansı hem kendi donanımına (yani ne kadar cpu, bellek ve hızlı diski var) hem de üzerindeki yüke (kaç aktif bağlantı var) göre değişir. Bu yöntemde Nginx her sunucu için aktif bağlantı sayısı  ve aşağıdaki değerlerden hangisi seçildi ise buna göre bir değer hesaplar ve bu değer hangi sunucuda en düşük ise (yani aktif bağlantı saysısı ve ortalama cevap süresi en düşük olan)  bağlantıyı ona gönderir.Burada 2 parametre seçilebilir:

header : bağlantı kurulması için geçen süre
last byte :bağlantı kurulduktan sonra isteğin tamamının gelmes için geçen süre

upstream backend {
least_time (header | last_byte);
server webserver1:80;
server webserver2:80;
}
}

Yukarıdaki örnekte bizim test ortamında kullandığımız config dosyasının ilgili bölümü var. Parametrelerden biri seçilmeli.

Ayrıca TCP/UDP yük dengelemede aşıdaki 3 parametreden biri kullanılabilir (config dosyasının stream bölümünde)

connect – Gerideki sunucuya bağlanmak için geçen süre
first_byte – Gerideki sunucudan gelen ilk Byte veri için geçen süre.
last_byte – Gerideki sunucudan gelen verinin son Byte’ının gelmesi için geçen süre

 

DOCKER Yazı  Serisinin  Sonu

Bu yazı dizisinin ilk yedi bölümünde Docker konusuna hızlıca uygulama ayağa kaldırma yöntemi olarak yaklaştım. Yazı dizisinin asıl amacı üretim ortamı için değil de test ve geliştirme ortamında bir veya birden fazla yazılımı hızlıca çalışır hale getirmek olduğu için Docker’in kurumsal özelliklerine çok girmedim. Docker konusunda yazacağım 2nci seride ağırlıklı olarak production ortamında kullanmak için gereken konuları anlatmaya çalışacağım. Şu ana kadarki örnekler hep tek bir fiziksel sunucu üzerinde çalışan Docker Engine üzerinde oldu, Production ortamları için  birden fazla fiziksel sunucu üzerinde çalışan ve Docker Cluster olarak değerlendirilebilecek Docker Swarm konusunu anlatacağım.

Son olarak Docker Container’lerin gücünü ve kolaylığını göstermek için aşağıda 5 farklı Containerden oluşan Oylama uygulamasını çalıştıralım. Uygulamaya bu linkten ulaşabilirsiniz.

Örnek topoloji aşağıdaki gibi: Bu uygulmada toplam 5 Container çalışacak. Bunlardan ilki bizim bağlanıp oy kullanacağımız Voting-App olup 80 nolu porttan dinleme yapan bir Python Web uygulaması. Burada kullanılan oy bir gerideki Redis DB üzerinde işlenecek ve Bir sonraki .NET ile yazılmış olan Worker uygulamasına gidecek. Worker uygulması Redis’ten gelen veriyi kalıcı olarak saklanmak üzere PostgreSQL Database üzerine yazacak. En sonunda da Result adlı Node.js tabanlı uygulama sonuçları 5001 nolu porttan dinleme yapan Web sunucuya yollayacak.

Bu yapı Docker Swarm üzerinde çalışacak. Normalde Docker Swarm’ birden fazla Docker Node  üzeinde çalışan ve node sayısına bağlı olarak bir veya birden fazla Node çökse de çalışmaya devam eden bir Docker Cluster teknolojisidir. Bu yapı beraberinde Overlay Networks, Manager Node, Worker Node gibi yeni kavramlar getirir. Bu kavramları ve diğer konuları bir sonraki  yazı dizisine bırakıp örneğe devam edelim. Normalde Docker Swarm birden fazla sunucu üzerinde kurulmalı ama bizim ortamımızda sadece bir sunucu olduğu için tek sunucu üzeinde çalıştıracağız.

Bunun için önce Docker Swarm servisi kurulumunu kontrol edelim. Bunun için aşağıdaki komut ile Swarm kurulup kurulmadığını kontrol edelim ve kurulu değilse kuralım:

oktay@ubuntu17docker:~$ docker info | grep "Swarm:"
Swarm: inactive

oktay@ubuntu17docker:~$ docker swarm init
Swarm initialized: current node (n9619fsplagzfwp5578s6vhpl) is 
   now a manager.

To add a worker to this swarm, run the following command:

docker swarm join --token 
   SWMTKN-1-2lrxk5jmlgflc6plym67q9dxmd4c69g1xpvo5lhlokjglihl9n-
     92borb4krsoh8fwywvs4v2abe 
       192.168.1.57:2377

To add a manager to this swarm, run 'docker swarm join-token manager' 
  and follow the instructions.

Yukarıdaki önce docker info komutunu  çalıştırıp Swarm satırına baktım. Komut çıkıtısı uzun olduğu için grep komutu ile sadece bu satırı aldım. Çıktı Swarm: inactive olduğu için kurulu olmadığını gördüm ve kurulum için docker swarm init komutunu çalıştırıp Docker Swarm kurulum yaptım. Gördüğünüz gibi kurulum bir satırlık bir komut ile yapıldı ama geri planda bir Swarm Cluster çalışması için pek çok şey gerçekleşti.  Bunları daha sonraki yazı dizisinde ayrıntılı anlatacağım.

Komutu çıktısına dikkate ederseniz bu Swarm Clusterına başka Node bağlamak istersem çalıştırmam gereken komutları da verdi. Sonradan bu Clustera Manager ve Worker Node eklemek gerekirse sadece bir komut ile bu işi halledebilirim.

Önce Docker üzerindeki tüm gereksiz imaj, container, network vs temizleyelim:

oktay@ubuntu1804:~$ docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all build cache
Are you sure you want to continue? [y/N] y

Sonrasında kullanılacak olan 2 network’ü oluşturalım: Dikkat ederseniz bu networklerin tipi olarak Overlay seçtim. Overlay netwok çok kısaca belirtmek gerekirse Swarm Cluster üzerindeki tüm Node’lar üzerinde oluşturulan ve farklı Node üzerindeki Containerlerin birbirleri ile konuşmasını sağlayan network tipidir. Geri planda VXLAN Tünel teknolojisini kullanır. Bizim test ortamımızda şimdilik tek Node olsa da daha sonra Node ekledikçe Containerleri bu Node’lara dağıttığımızda bunlar bu tip Network üzerinden konuşabilecekler.

docker network create -d overlay backend
docker network create -d overlay frontend

Bu networkleri oluşturduktan sonra şimdi de Containerleri çalıştıralım. Burada dikkat edeceğimiz fark Docker tarafında Container çalıştırmak için docker container run <parametreler> komutu kullanırken Swarm ortamında artık docker service create <parametreler> kullanacağız.

Bir başka ayrıntı ise artık Swarm ortamında bir servisin birden fazla kopysaını çalıştırabiliriz. Aşağıdaki komutalrdan vote adlı uygulamayı çalıştırırken  –replicas 2 diye bir parametre kullandık. Bunun anlamı bu Containerden 2 adet çalıştırılacak. Nitekim komut çalıştırıldıktan sonra aşağıdaki gibi bir çıktı göreceğiz:

overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged

Bunun anlamı bu servis için iki Container çalıştırıldı ve ikisi de çalışır hale geldi.  Swarm konusunu ayrıntılı anlatırken, Swarm ‘un Layer 3 çalışan bir Load Balancer servisi verdiğini ve bununla bizim örneğimizde olduğu gibi belli bir IP’ye gelen istekleri gerideki bir servisi oluşturan birden fazla Containerine yönlendirdiğini detaylandıracağım.

Bir diğer fark ise bu örnekte aşağıdaki komut parametresi ile PostgreSQL servisi için kalıcı Volume atadık.

--mount type=volume,source=db-data,target=/var/lib/postgresql/data

Artık aşağıdaki komutlar ile 2 Network, 5 servis için toplam 6 Container ve bir Volume oluşturabiliriz.

oktay@ubuntu17docker:~$ docker network create -d overlay backend
f0idukhwmasxdh4kxklkznnev
oktay@ubuntu17docker:~$ docker network create -d overlay frontend
j49hcilzn0xg0jvdbr9d0da7e

oktay@ubuntu17docker:~$ docker service create --name vote \
  -p 80:80 --network frontend --replicas 2 \
  dockersamples/examplevotingapp_vote:before
xw96fu6nwkjc3sc1kxiizecxb
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged

oktay@ubuntu17docker:~$ docker service create --name redis \
                         --network frontend redis:3.2
vfyha4www2fjja97hkfge69yf
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged

oktay@ubuntu17docker:~$ docker service create --name db \
  --network backend \
  --mount type=volume,source=db-data,target=/var/lib/postgresql/data \
    postgres:9.4
qcmzf527cqzrpfswz8m8czi9o
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged

oktay@ubuntu17docker:~$ docker service create --name result \
   --network backend -p 5001:80 \
   dockersamples/examplevotingapp_result:before
l7624wzisvqbdpewmuxoa4yvr
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged

oktay@ubuntu17docker:~$ docker service create --name worker \
     --network frontend --network backend \
     dockersamples/examplevotingapp_worker
l27ylpglswke9dumune3wx8xk
overall progress: 0 out of 1 tasks
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged

oktay@ubuntu17docker:~$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c7a19e27a34 dockersamples/examplevotingapp_worker:latest "/bin/sh -c 'dotnet …" 32 seconds ago Up 29 seconds worker.1.p6iuwrarj8szlpalcrjfo21me
70d773be720b dockersamples/examplevotingapp_result:before "node server.js" 4 minutes ago Up 4 minutes 80/tcp result.1.52q4lg5zwsw4chuq0i8tuy48t
637fdd9a0a72 postgres:9.4 "docker-entrypoint.s…" 6 minutes ago Up 6 minutes 5432/tcp db.1.ke8msdvdt0oj2uggjfth92948
69ea314eaa13 redis:3.2 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 6379/tcp redis.1.i075i281o9larw32fksci3qie
70b0c83a50a5 dockersamples/examplevotingapp_vote:before "gunicorn app:app -b…" 8 minutes ago Up 8 minutes 80/tcp vote.2.zkbvdqf2r1id4kqq25qgz1fn5
7ce0a5e71b9d dockersamples/examplevotingapp_vote:before "gunicorn app:app -b…" 8 minutes ago Up 8 minutes 80/tcp vote.1.rlcglgguxf2cvrak8wurvb6pp
oktay@ubuntu17docker:~$

Komutları girip tüm servisleri çalıştırdıktan sonra docker service ls komutu ile servisleri ve durumlarını görelim: Vote Servisinin 2 kopya çalıştığına dikkat edin.

oktay@ubuntu17docker:~$ docker service ls
ID           NAME   MODE       REPLICAS IMAGE PORTS
qcmzf527cqzr db     replicated 1/1 postgres:9.4
vfyha4www2fj redis  replicated 1/1 redis:3.2
l7624wzisvqb result replicated 1/1 dockersamples/examplevotingapp_result:before *:5001->80/tcp
xw96fu6nwkjc vote   replicated 2/2 dockersamples/examplevotingapp_vote:before *:80->80/tcp
l27ylpglswke worker replicated 1/1 dockersamples/examplevotingapp_worker:latest
oktay@ubuntu17docker:~$

Dikkat ederseniz artık Container değil Servis kontrol ediyoruz. Ama istersek docker container ps komutu ile containerleri de kontrol edebiliriz. Çıktıda vote için 2 ayrı Container görebilirsiniz.

oktay@ubuntu17docker:~$ docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c7a19e27a34 dockersamples/examplevotingapp_worker:latest “/bin/sh -c ‘dotnet …” 3 hours ago Up 3 hours worker.1.p6iuwrarj8szlpalcrjfo21me
70d773be720b dockersamples/examplevotingapp_result:before “node server.js” 3 hours ago Up 3 hours 80/tcp result.1.52q4lg5zwsw4chuq0i8tuy48t
637fdd9a0a72 postgres:9.4 “docker-entrypoint.s…” 3 hours ago Up 3 hours 5432/tcp db.1.ke8msdvdt0oj2uggjfth92948
69ea314eaa13 redis:3.2 “docker-entrypoint.s…” 3 hours ago Up 3 hours 6379/tcp redis.1.i075i281o9larw32fksci3qie
70b0c83a50a5 dockersamples/examplevotingapp_vote:before “gunicorn app:app -b…” 3 hours ago Up 3 hours 80/tcp vote.2.zkbvdqf2r1id4kqq25qgz1fn5
7ce0a5e71b9d dockersamples/examplevotingapp_vote:before “gunicorn app:app -b…” 3 hours ago Up 3 hours 80/tcp vote.1.rlcglgguxf2cvrak8wurvb6pp

Eğer buraya kadar herşey tamam ise artık uygulamaya bağlanabiliriz: İki ayrı Web tarayıcı açıp önce oy verip sonra sonuçlara bakabiliriz:

Benim test ortamımdaki Docker kurulu Ubuntu sunucumun IP Adresi

Ykarıdaki ilk resim oy vereceğimiz olan web sayfası, bu sayfa 80 nlolu porttan yayınlandığı için http://http://192.168.1.57/ adresi ile giriş yapıyorum. Siz kendi IP adresinizi kulanacaksınız. İkinci resimde ise aynı IP Adresinden ama bu sefer 5001 nou porttan yayınlanan sonuç ekranı var. Buna da http://192.168.1.57:5001/  URL’i ile bağlandım, siz yine kendi Ip Adresinizi ve 5001 nolu portu kullanacaksınız.

Bizi oy için karşılayan Vote Servisi 2 kopya olarak çlışıyordu. Bunu görmek için Web Sayfasının alt tarafında cevap veren Container ID’si mevcut.

Yukarıdaki resmin kırmızı ile çevirdiğim kısmındaki ID ‘yi kontrol ederseniz, açtığınız her bir web sayfasındaki ID değerinin değiştiğini, daha doğrusu iki Container ID’sini sıra ile gördüğünüzü fark edersiniz. Docker containerleri listelediğimizde bu iki ID’yi görürsünüz.

oktay@ubuntu17docker:~$ docker container ps
CONTAINER ID IMAGE PORTS 
70b0c83a50a5 dockersamples/examplevotingapp_vote:before 80/tcp 
7ce0a5e71b9d dockersamples/examplevotingapp_vote:before 80/tcp

Burada olan  daha önce belirttiğim Docker Swarm’ın oluşturduğu Layer 3 Load Balancer’ın Round Robin dengelemesidir.

Bu yazı serisinin 7nci yazısı ile Docker Container teknolojisinin  belli başlı konularını ele almış olduk. Bir sonraki yazı dizisinde Kurumsal ortamlarda kullanmak için Docker Swarm ve sonrasında Kubernetes konularında yazacağım.

 

 

Ek Okuma:

https://www.nginx.com/resources/wiki/start/topics/examples/full/

https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts

Choosing an NGINX Plus Load‑Balancing Technique

https://www.jscape.com/blog/load-balancing-algorithms

https://12factor.net/

https://github.com/dockersamples/example-voting-app

 

 

Docker ile ilgili bu yazı dizisinde şimdiye kadar 5 yazı yazdım. Ubuntu sunucu üzerinde Docker Container çalıştırma ile ilgili bu yazı dizisinin ilk bölümünde kurulum konusuna başlamıştık. Yazının ikinci bölümünde kurulum sonrası yapılması gerekenler ve Apache httpd örneği ile devam etmiştik. Üçüncü bölümde örneklere devam ederken Docker İmajları konusuna basitçe değinmiştik Bu makalede ağırlıklı olarak Apache Httpd imajı üzerinden örnek vermiştim.  Yazının dördüncü bölümünde ağırlıklı olarak en çok çalıştırılan imaj olduğu için  nginx örnekleri vermiştim. Ayrıca çalışan Container’in komut satırına nasıl ulaşılacağı, containerin konfigürasyonunun nasıl değiştirileceğini ve  bind mount konusunu anlatmıştım.Yazının 5nci bölümünde ağırlıklı olarak nginx load balancer örneği üzerinden Docker Network konusunu anlatmıştım.

Yazının 6ncı bölümünde şu ana kadar anlattıklarımı toparlamaya çalışacağım. Genelde de Elasticsearch üzerinden örnek vereceğim. Elasticsearch nedir derseniz açık kaynak kodlu, dağıtık yapıda çalışamaya hazır, ölçeklenebilir, RESTful API tabanlı çok hızlı bir indexleme aracıdır (daha önceki makalelerden hatrılarsanız, en çok kullanılan beşinci Docker Container imajıdır). NoSql Web Sayfasında yer aldığından NoSql Databse olarak değerlendirenler olsa da, bu sayfada Document Store başlığı altında yer alır ve benim de katıldığım bir fikre göre database özelliğinden çok hızlı indexleme özelliğine istinaden indexleme aracı olarak değerlendirmek daha doğru olacaktır. Örnek olarak Greylog2 gibi uygulamalar, NoSql database olarak Mongo gibi başka bir database  kullanırken Elasticsearch’ü çok hızlı olduğu için döküman indexleme aracı olarak kullanır.

En son network konusunu anlatmıştım. Networkten başlayıp şimdiye kadar gördüklerimizin püf noktalarını hatırlayalım veya unuttuklarımız varsa ekleyelim.

1. Default Bridge Network:

  • DNS adı çözümleme

Docker kurulduğu zaman otomatik olarak oluşturulan 3 networkten biri Default Brdige Network’tür. Aksi belirtilmedikçe  tüm Containerler bu networke bağlanır.  Bu network 172.17.0.0/16 IP adres aralığını alır olup bundan sonra oluşturulan networkler 172.18.0.0/16 ve sonrası ile devam eder. Bu networkte bulunan tüm Containerler -p parametresi olmadan birbirleri ile konuşabilirler. Yine bu Containerler bir NAT Router üzerinden fiziksel sunucunun fziksel ethernet kartı üzerinden dış networklere bağlanabilirler.

Kullanıcı tarafından da Bridge network oluşturulabilir. Kullanıcı tarafından oluşturulmuş olan Bridge Network’ün en büyük farkı, Docker bu  network üzerinde DNS servisi çalışıtırır, yani bu networkteki Containerler  DNS isimleri ile birbirlerine erişebilir. Container’e –name parametresi ile verdiğimiz isim veya bu parametreyi kullanmazsak Docker tarafından verilen isim DNS ismi olur.

Aşağıdaki komut ile mevcut Networkleri listeleyelim:

oktay@ubuntu1804:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a20de7839802 bridge bridge local
a38c11f94152 host host local
69b4acf65bc9 none null local

Şimdi kendimiz oktay-test-net  adında bir bridge network oluşturalım:

oktay@ubuntu1804:~$ docker network create oktay-test-net
dba04490b757dddc27b9e620baf7ff65681e8aee0bffc0ab2dd354534d9b28c2

oktay@ubuntu1804:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a20de7839802 bridge bridge local
a38c11f94152 host host local
69b4acf65bc9 none null local
dba04490b757 oktay-test-net bridge local

 

Şimdi bu network üzerinde 1 adet Elasticsearch Container çalıştıralım:

oktay@ubuntu1804:~$ docker container run -d –network oktay-test-net –name elas elasticsearch:2
a846a0c97194b18d0fbab88479ebcdfe4d9791004f6d73ac8f9fff698c30e5cc

Şimdi aşağıdaki komut ile bir Alpine Linux Container çalıştıralım, bu Container çalışınca nslookup komutu çalıştırsın ve elas DNS adını çözümlemeye çalışsın.  Bu komutta dikkat ederseniz -rm parametrresi var. Bu parametre Docker’a Container çalışıp gerekli işlemi yapıp bitirdikten sonra kendisini silmesini söyler. Bu komut aslında tam Container mantığına ait bir örnek oldu. Yani bir Container çok hızlı olarak ayağa kalkar ve çalışmaya başlar, işini yapar ve sonrasında hiçbir iz bırakmadan silinip gider. 

oktay@ubuntu1804:~$ docker container run –rm –network oktay-test-net alpine nslookup elas

nslookup: can’t resolve ‘(null)’: Name does not resolve
Name: elas
Address 1: 172.18.0.2 elas.oktay-test-net

Dikkat ederseniz komut çıktısında elas DNS adını 172.18.0.2 olarak çözdü. Container gerçekten bu IP Adresini mi aldı diye kontrol etmek istersek docker network inspect <network_adı> ile kontrol edebiliriz. 

oktay@ubuntu1804:~$ docker network inspect oktay-test-net
[
{
“Name”: “oktay-test-net”,
“Id”: “dba04490b757dddc27b9e620baf7ff65681e8aee0bffc0ab2dd354534d9b28c2”,
“Created”: “2018-11-29T07:40:52.380719487Z”,
“Scope”: “local”,
“Driver”: “bridge”,
“EnableIPv6”: false,
“IPAM”: {
“Driver”: “default”,
“Options”: {},
“Config”: [
{
“Subnet”: “172.18.0.0/16”,
“Gateway”: “172.18.0.1”
}
]
},
“Internal”: false,
“Attachable”: false,
“Ingress”: false,
“ConfigFrom”: {
“Network”: “”
},
“ConfigOnly”: false,
“Containers”: {
“a846a0c97194b18d0fbab88479ebcdfe4d9791004f6d73ac8f9fff698c30e5cc”: {
“Name”: “elas”,
“EndpointID”: “baab3d863968b564202a38c1fc0f1b8bb5976249b5f9c1e5cbff0c54ad57216f”,
“MacAddress”: “02:42:ac:12:00:02”,
“IPv4Address”: “172.18.0.2/16”,
“IPv6Address”: “”
}
},
“Options”: {},
“Labels”: {}
}
]

 

Yukarıdaki çıktıya dikkat ederseniz, çıktının Containers başlığı altındaki bölümde elas adlı Containerin 172.18.0.2/16 IP adresi aldığını görürsünüz.

 

  • –net-alias parametresi

Kendi işletttiğiniz DNS sunucu üzerinde aynı kaydı birden fazla sunucuya verebilirsiniz. Bu sayede bir grup sunucuya aynı DNS adını kullanarak erişmek mümkün olur. Örnek olarak birden fazla web sunucu için birden fazla www kaydı açıp farklı IP Adresleri girebilirsiniz.  Ama Docker üzerinde Container’e verilen ad ayn zamanda DNS adı olduğu için ve aynı adlı birden fazla Container olamıyacağı için bu özelliği kullanamazsınız. Bu sorunu aşmak için DNS içine net-alias diye bir özellik kazandırılmıştır. Bu komut parametresini kullanarak, bir Container’e DNS adı haricinde bir de grup adı atayabilirsiniz ve bu ismi kullanarak  bir grup Container’e aynı ismi kullanarak ulaşabilirsiniz. Yani birden fazla Container’iniz  var, bunlara srv1, srv2,…….. srv-N gibi tek tek DNS adları ile ulaşmak yerine hepsine bir grup adı verip bu ad üzerinden erişebilirsiniz.  Bunu anlatmak için aşağıdaki gibi elas1 ve elas2 adında 2 adet Elasticsearch sunucu kuracağım ve bunlara elasrv grup adı vereceğim. Sonra da bu sunuculara grup adı kullanarak erişeceğim. Bunun için –net-alias komutunu kullanacağım.

Örneğe geçmeden önce DNS Round-Robin özelliğinden bahsetmem gerek. DNS sunucuları bir DNS adına karşılık birden fazla Ip Adresi bulurlar ise, her bir sorguda bir sıra dahilinde farklı IP Adresi dönerler. Daha sonra Nginx Load Balance yöntemlerini anlatırken farklı Load Balance tekniklerinden bahsedeceğim ama Round Robin en basit yöntem olup basitçe her sorguda farklı IP Adresi dönüldüğü için istekler farklı sunuculara gönderilir ve basit bir Yük Dengeleme/Load Balance yapılır. Bu yöntemde Ip Adresi verilen sunucunun sağlık/performans durumu kontrol edilmediğinden çok tercih edilmemesi gerekir.

Bizim örneğimizde olduğu gibi elasrv için 2 farklı Container ve 2 farklı IP Adresi olacak. Bizim sorgularımızda her seferinde farklı bir Container IP gelecek.

Önce farklı adlı ama aynı grup adlı 2 Container çalıştıralım:

oktay@ubuntu1804:~$ docker container run -d –network oktay-test-net –net-alias elasrv –name srv1 elasticsearch:2
46167bf1023cab4a1facc77462668f6b4a1b4fddfec5bfe176a01825e4ba8b4b
oktay@ubuntu1804:~$ docker container run -d –network oktay-test-net –net-alias elasrv –name srv2 elasticsearch:2
8cc21ff023c56a00e28a3c1e81a4310bbbb6adec1bbb459fea46eed9d8ddcd81

Her iki Container da açlışyor, aşağıdaki gibi kontrol edelim: 

oktay@ubuntu1804:~$ docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8cc21ff023c5 elasticsearch:2 “/docker-entrypoint.…” 21 seconds ago Up 20 seconds 9200/tcp, 9300/tcp srv2
46167bf1023c elasticsearch:2 “/docker-entrypoint.…” 29 seconds ago Up 27 seconds 9200/tcp, 9300/tcp srv1

Daha önce yaptığımız gibi nslookup ile kontrol edelim: Çıktıya dikkate ederseniz her iki IP Adresi de görüleblir. 

oktay@ubuntu1804:~$ docker container run –network oktay-test-net alpine nslookup elasrv
nslookup: can’t resolve ‘(null)’: Name does not resolve

Name: elasrv
Address 1: 172.18.0.2 srv1.oktay-test-net
Address 2: 172.18.0.3 srv2.oktay-test-net

Şimdi daha farklı bir şey yapalım. Daha önce gördüğümüz gibi curl komutu ile web servislerini kontrol edebiliriz. Elasticsearch web sorularına 9200 nolu porttan cevap verebilir. Aşağıdaki komut ile bir Centos Linux Container ayağa kaldıracağız ve sonrasında Curl komutu ile 9200 nolu portta dinleyen Elasticsearch servisini kontrol edeceğiz. Komutta Elasticsearch  IP Adresini değil srv1 ve srv2 şeklindeki DNS adını kullanacağız.

oktay@ubuntu1804:~$ docker container run –rm –net oktay-test-net centos curl -s srv1:9200
{
“name” : “Big Bertha“,
“cluster_name” : “elasticsearch”,
“cluster_uuid” : “x6QnqBdpQ4W_ACwsyhjyDw”,
“version” : {
“number” : “2.4.6”,
“build_hash” : “5376dca9f70f3abef96a77f4bb22720ace8240fd”,
“build_timestamp” : “2017-07-18T12:17:44Z”,
“build_snapshot” : false,
“lucene_version” : “5.5.4”
},
“tagline” : “You Know, for Search”
}
oktay@ubuntu1804:~$ docker container run –rm –net oktay-test-net centos curl -s srv2:9200
{
“name” : “Y’Garon”,
“cluster_name” : “elasticsearch”,
“cluster_uuid” : “8ptLLweARXq4MJFguikDAg”,
“version” : {
“number” : “2.4.6”,
“build_hash” : “5376dca9f70f3abef96a77f4bb22720ace8240fd”,
“build_timestamp” : “2017-07-18T12:17:44Z”,
“build_snapshot” : false,
“lucene_version” : “5.5.4”
},
“tagline” : “You Know, for Search”
}

Yukarıdaki çıktıda gördüğünüz gibi srv1 ve srv2 olarak sorguladığımızda Big Bertha ve Y Garonadlı iki ayrı servisin ayrıntıları geldi. Bu aşamada aynı komutu bu sefer –net-alias komutunda kullandığımız elasrv ortak adı ile sorgulayalım ve sorgulamayı peş peşe defalarca yapalım.

oktay@ubuntu1804:~$ docker container run –rm –net oktay-test-net centos curl -s elasrv:9200
{
“name” : “Big Bertha”,
“cluster_name” : “elasticsearch”,
“cluster_uuid” : “x6QnqBdpQ4W_ACwsyhjyDw”,
“version” : {
“number” : “2.4.6”,
“build_hash” : “5376dca9f70f3abef96a77f4bb22720ace8240fd”,
“build_timestamp” : “2017-07-18T12:17:44Z”,
“build_snapshot” : false,
“lucene_version” : “5.5.4”
},
“tagline” : “You Know, for Search”
}

oktay@ubuntu1804:~$ docker container run –rm –net oktay-test-net centos curl -s elasrv:9200
{
“name” : “Y’Garon”,
“cluster_name” : “elasticsearch”,
“cluster_uuid” : “8ptLLweARXq4MJFguikDAg”,
“version” : {
“number” : “2.4.6”,
“build_hash” : “5376dca9f70f3abef96a77f4bb22720ace8240fd”,
“build_timestamp” : “2017-07-18T12:17:44Z”,
“build_snapshot” : false,
“lucene_version” : “5.5.4”
},
“tagline” : “You Know, for Search”

Yukarıda çıktıyı kısalttım, dikkat ederseniz bir sorguda “Big Bertha” ve diğer sorguda“Y’Garon” cevabı geldi.  Çünkü DNS Servisi her sorguda farklı bir IP döndü. 

 

2. Bind Mount

Daha önceki yazılarda da -v parametresi ile Docker Host üzerindeki bir klasör veya dosyayı çalışan Container’e bağlamayı görmüştük. Dosya ve klasör bağlama olarak her iki şekilde de kullanmıştık. Örneğin bir önceki yazıda aşağıdaki komut ile Docker Host üzerindeki /web1 klasörünü  /html klasörüne bağladık.

docker container run -d –name webserver1 –network webnet -v /$(pwd)/web1:/usr/share/nginx/html nginx:alpine

Aşağıdaki örnekte ise Docker Host üzerindeki nginx.conf dosyasını Container üzerinde /etc/nginx/nginx.conf dosyasına bağladık

docker container run -d -p 80:80 –network webnet -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx

Dosya değil Klasör bağlayın:

Yukarıdaki 2 örnekteki fark şu: Docker volumleri inode seviyesinde bağlar. Eğer siz klasör değil de dosya bağlarsanız, bu dosyayı edit edip kaydettiğinizde bu dosya yeni bir inode üzerine yazılır ve bu yüzden Docker bu değişikliği algılamaz. Eğer geliştirme ortamında devamlı dosyalar üzerinede değişiklik yapıyorsanız, değişikliklerin kaydettiğiniz anda Container üzerinde görünmesini istiyorsanız dosya değil klasör bağlayın.

Dosyaları :ro   (Read Only- Salt Okunur) bağlayın

Eğer bağladığınız dosyanın Container içinden değiştirilmesi istemiyorsanız, -v parametresinin en sonuna   :ro (Read-Only) Salt Okunur parametresi ekleyin. Bu parametre ile dosyanın Container içinden değiştirilmesini engellemiş olursunuz. Özellikle production ortamında bu parametreyi kullanmanız, eğer Container başkası tarafından ele geçirilir ise kritik konfigürasyon vs. dosyaların değiştirilmesini engeller.

3. Sistem Temizliği

Özellikle test ortamında bir süre sonra kullanılmayan İmajlar, Networkler ve Çalışmayan Containerler birikir. Bunları temizlemezseniz bir süre sonra karman çorman bir Docker ortamı oluşur. Bunları aşağıdaki komutlar ile silebilirsiniz.

docker container rm <container-adı/container-id>   Container silmek için bu komutu kullanabilirsiniz. Eğer Container çalışıyorsa hata verir, ya önce durdurun yada -f parametresi ile çalışan Containeri de silin.

docker network rm <network-adı/network-id> Networkleri sislemk için kullanılır.

docker image rm <imaj-adı> İmajları silmek için kullanılır

Prune Komutu

Bu işin daha kolayı yokmu derseniz prune komutu var. Ama bu komut çok güçlü bir komut olduğundan tam olarak ne yaptığını bilmeden kullanmayın.

Aşağıda 3 örnek vereceğim

docker image prune   Kullanılmayan imajları siler

docker container prune Çalışmayan Containerleri siler

docker system prune  En tehlikeli komut budur. Çalışmayan tüm Containerleri, kullanılmayan imajları, çalışan Container bağlı olmayan tüm networkleri, kısaca çalışmayan herseyini siler. Bu açıdan production ortamlarında çok ama çok dikkatli kullanın. Bu komutu kullandığınızda aşağıdaki gibi ek onay sorar

oktay@ubuntu1804:~$ docker system prune
WARNING! This will remove:
– all stopped containers
– all networks not used by at least one container
– all dangling images
– all build cache
Are you sure you want to continue? [y/N]

Ubuntu sunucu üzerinde Docker Container çalıştırma ile ilgili bu yazı dizisinin ilk bölümünde kurulum konusuna başlamıştık. Yazının ikinci bölümünde kurulum sonrası yapılması gerekenler ve Apache httpd örneği ile devam etmiştik. Üçüncü bölümde örneklere devam ederken Docker İmajları konusuna basitçe değinmiştik Bu makalede ağırlıklı olarak Apache Httpd imajı üzerinden örnek vermiştim.  Yazının dördüncü bölümünde ağırlıklı olarak en çok çalıştırılan imaj olduğu için  nginx örnekleri vermiştim. Ayrıca çalışan Container’in komut satırına nasıl ulaşılacağı, containerin konfigürasyonunun nasıl değiştirileceğini ve  bind mount konusunu anlatmıştım.Yazının bu 5nci bölümünde nginx load balancer örneği üzerinden Volume konusuna devam edeceğim. Bu makalenin anlaşılabilmesi için Network konusuna da girmemiz gerek. Bu yazıda ana örneğimiz bir nginx load balancer arkasında çalışan 2 web sunucusu olacak. Nginx load balancer olarak çalışırken,  kendisine gelen istekleri bu iki web sunucuya yönlendirecek.

Docker Network

Bu örneğin çalışacağı yapıyı kurmadan önce Docker Network konusunu  incelemeye çalışacağız. Docker networkü hakkında bilgi verirken bir altta yer alan ağ katmanlarını gösteren şemadan yaralanacağım. Şemada yeşil kısım Docker Networkünün fiziksel kısmını, turuncu ile çevrilmiş alan ise sanal networkü temsil ediyor. Sanal networkten kastım Docker tarafından oluşturulan switch, nat cihazı, sanal ethernet kartı ve diğer network komponentleri. Aslına bakarsanız, sanal yerine yazılım tabanlı network daha uygun bir tanımlama olur. Bu tanımlamayı açmak için network katmanlarını anlatmakta fayda var. Aşağıdaki resme bakarsanız network katmanlarını görürsünüz. TCP/IP networkü 4 katman olarak  açıklarken OSI Modeli  networkü 7 katman olarak tanımlar. Ben bu yazı boyunca 7 katmanlı OSI modelini esas alacağım.

 

Dikkat ederseniz 7 katmanın tamamı da elle tutulacak olan kablolar ve kartlar vs haricinde tamamen yazılımdır. 2nci katmanda (Layer/Katman 7 en üstte, Layer/Katman 1 en altta)  bulunan switcing, switch donanımı üzerinde çalışan bir yazılımdır. Layer3 te bulunan Routing te aynı şekilde bir router veya L3 destekleyen bir switch üzerinde çalışan bir yazılımdır. Bu açıdan tüm katmanlar donanımdan ayrıştırılmış olarak yazılım ortamında çalışabilir. Bu yazı boyunca Katman 2 DataLink’te çalışan Switcing, Katman 3 Network’te çalışan Routing, Katman 4 Transport’ta çalışan NAT , Katman 7 Application’da çalışan Proxy (ileri/forward ve geriye/reverse) ve kullanılan yönteme göre Katman 3/4/7de çalışan Load Balance (Yük dengeleme)  konularından bahsedeceğim.

Yukarıdaki şemada üzerinde Ubuntu kurulu sunucuya Docker kurduğunuz zaman, Docker hemen bir yazılım tabanlı network oluşturur (burada bahsettiğim Software Defined Network/SDN değil de daha çok Network Functions Virtualization/NVF  anlamında). Şemada bu network switchi Docker Tarafından Oluşturulan Nat/Brdige Sanal Switch olarak belirttim.  Oluşturulan her Container bu switche şemada Docker Container Sanal Ethernet Kart olarak belirttiğim bağlantıdan bağlanır. Aksi belirtilmedikçe oluşturulan tüm Containerler bu networke bağlanır. Bu networke bağlanan tüm Containerler kendi aralarında başka bir ayara gerek kalmadan konuşurlar. Bu network ile ilgili anlatmaya devam etmeden önce ilk network komutlarını görelim:

Komutlar docker network ile başlar, ilk komut mevcut networkleri listelemek için kullanılan docker network ls komutu. Bu komutun çıktısı aşağıdaki gibi olur, var olan networkleri listeler. Bu networklerden en önemlisi adı ve network tipi bridge olan networktür.

oktay@ubuntu1804:~$ docker network ls
NETWORK ID      NAME     DRIVER     SCOPE
a8ea3b6d3eec      bridge     bridge        local
a38c11f94152        host        host            local
69b4acf65bc9        none       null            local
oktay@ubuntu1804:~$

Daha önce de yazdığım gibi oluşturulan tüm containerler, eğer aksi belirtilmezse bu networke bağlanırlar. Tüm L2 Switching bu katmanda olur. Bu networkteki containerler dış network ile konuşmak isterler ise, fiziksel sunucunun ethernet bağlantısı üzerinden dış dünyaya bağlanır. Bu bağlantı, NAT şeklinde olur. Yani  Container’den çıkan paketler, L4 katmanında NAT edilerek fiziksel sunucunun IP adresi üzerinden dış networke bağlanır. Dışarıya doğru giden paketler için özel bir ayar yapmaya gerek yoktur. Dış networkte, bu Containerden gelen paketlerin IP adresi olarak fiziksel ethernet kartının IP adresini görünür.  Ayrıca dışardan bu networkte bulunan Containerlere bağlanmak gerekirse, yine içeriye doğru NAT ile bağlanılabilir. İçeriye doğru NAT yapılabilmesi için, kullanılacak olan portun Container çalışırken belirtilmesi gerekir. Bu port parametresini daha önce kullandık, -p <dış_port>:<iç_port>.  Bu networkü ayrıntılı incelemek istersek inspect parametresi ile birlikte network adını kullanırız.

oktay@ubuntu1804:~$ docker network inspect bridge
[
{
“Name”: “bridge”,
“Id”: “f4673e040e5806df2ffebcb9c13a1a0a5b4f6501f452f445afbc32607d0250b1”,
“Created”: “2018-10-30T12:56:49.607398888Z”,
“Scope”: “local”,
“Driver”: “bridge”,
“EnableIPv6”: false,
“IPAM”: {
“Driver”: “default”,
“Options”: null,
“Config”: [
{
“Subnet”: “172.17.0.0/16”,
“Gateway”: “172.17.0.1”
}
]
},
“Internal”: false,
“Attachable”: false,
“Ingress”: false,
“ConfigFrom”: {
“Network”: “”
},
“ConfigOnly”: false,
“Containers”: {},
“Options”: {
“com.docker.network.bridge.default_bridge”: “true”,
“com.docker.network.bridge.enable_icc”: “true”,
“com.docker.network.bridge.enable_ip_masquerade”: “true”,
“com.docker.network.bridge.host_binding_ipv4”: “0.0.0.0”,
“com.docker.network.bridge.name”: “docker0”,
“com.docker.network.driver.mtu”: “1500”
},
“Labels”: {}
}
]

Yukarıda docker network inspect bridge komutunun çıktısını gördük. Komut çıktısında “Name”: “bridge” kısmı network adını, “Driver”: “bridge” network tipini belirtiyor. “Subnet”: “172.17.0.0/16” bölümü bu networkün IP aralığını,  belirtiyor. İlk network 172.17.0.0/16 olup sonra oluşan networkler bir sonraki IP aralığından devam eder. Bu çıktıda daha Container oluturmadığımız için Container görmüyoruz. Şimdi bir Ubuntu Container oluşturalım ve ne olduğuna bakalım. Bunun için aşağıdaki komut ile bir Container çalıştıracağım ve doğrudan komut satırına bağlanacağım. Önce ilk satırdaki komut ile Ubuntu:16.04 Containeri çalıştırıyorum, -it parametresi ile interaktif olarak çalışmasını ve konsola bağlanmasını belirtiyorum, en sondaki bash ile de Bash kabuğuna bağlanmasını belirtiyorum. Ubuntu Container imajı oluşturulurken herhangi bir yazılım vs çalıştırılması söylenmediği için, eğer -it ve bash parametresi kullanmaz isek Container çalışmaya başlar ve çalışması için herhangi birşey belirtilmediği için durur.

oktay@ubuntu1804:~$ docker container run -it –name ubuntu1 ubuntu:16.04 bash

Aşağıdaki ifconfig komutu ile içinde bulunduğum Container IP adresini öğrenmek istiyorum ama imaj küçük olsun diye ifconfig ve ping komutları konulmadığı için komut hata veriyor.

root@a2b8c78a5457:/# ifconfig
bash: ifconfig: command not found

Bu komutlalrın olduğu paketleri yüklemem gerek. Aşağıdaki çıktıyı kısa olsun diye kısalttım.

root@a2b8c78a5457:/# apt-get update

Reading package lists… Done

root@a2b8c78a5457:/# apt-get install net-tools
root@a2b8c78a5457:/# apt-get install iputils-ping

ifconfig komutunu tekrar çalıştırınca eth0 interfaci ip adresini 172.17.0.2/16 olarak görüyorum. eth0, Containerde içinde oluşan sanal ethernet kartı.

root@a2b8c78a5457:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:4266 errors:0 dropped:0 overruns:0 frame:0
TX packets:2720 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:17233590 (17.2 MB) TX bytes:186880 (186.8 KB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

Bağlı olduğumuz network default gateway’ini pingleyelim.

root@a2b8c78a5457:/# ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.062 ms
— 172.17.0.1 ping statistics —
3 packets transmitted, 3 received, 0% packet loss, time 2042ms
rtt min/avg/max/mdev = 0.062/0.088/0.115/0.024 ms
root@a2b8c78a5457:/#

Şimdi de dış dünyadan bir web sitesini pingleyelim. Dikkat ederseniz Cisco için hem Dns adresini çözümledi hem de bu IP’yi pingledi.

root@a2b8c78a5457:/# ping http://www.cisco.com
PING e2867.dsca.akamaiedge.net (104.66.70.56) 56(84) bytes of data.
64 bytes from a104-66-70-56.deploy.static.akamaitechnologies.com (104.66.70.56): icmp_seq=1 ttl=246 time=44.1 ms
64 bytes from a104-66-70-56.deploy.static.akamaitechnologies.com (104.66.70.56): icmp_seq=2 ttl=246 time=44.2 ms

Bu aşamada docker network inspect bridge komutu ile tekrar bridge network ayrıntılarına bakalım. Şu an için tek container var, buna verilen IP adresi 172.17.0.2/16 ki bu da Ubuntu içinde gördüğümüz adres.

Çıktıyı kısalttım.

“Containers”: {
“a2b8c78a545782459b5f44eb6a9682d10d72e5488c94dfb67e49878a2a71eb65”: {
Name”: “ubuntu1”,
“EndpointID”: “3560256a7aabc946a15328760c3a33b745c9ae7c9816d6ca50e2996a88bbc092”,
“MacAddress”: “02:42:ac:11:00:02”,
“IPv4Address”: “172.17.0.2/16”,
“IPv6Address”: “”
}

Container IP adresini görmenin başka bir yolu da docker container inspect ubuntu1 komutu ile Container ayrıntılarına bakmak. Çıktı uzun olduğu için kısalttım, 

“NetworkSettings”:

“Gateway”: “172.17.0.1”,
“IPAddress”: “172.17.0.2”,
“IPPrefixLen”: 16,

Bu network ile ilgili bir sonraki aşamada bir nginx Container çalıştıralım ve IP adresini öğrenelim. Dikkat ederseniz bu nginx i -p ile dış dünyaya bağlamadım.

oktay@ubuntu1804:~$ docker container run -d nginx:alpine
84071ab48176283db0f724e684734dc4111ab4d136db4d5f6290d622adbab52b
oktay@ubuntu1804:~$ docker container ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84071ab48176 nginx:alpine “nginx -g ‘daemon of…” 23 seconds ago Up 21 seconds 80/tcp stoic_torvalds
a2b8c78a5457 ubuntu:16.04 “bash” 18 hours ago Up 18 hours ubuntu1
oktay@ubuntu1804:~$ docker container inspect stoic_torvalds

Çıktıyı kısalttım

“Networks”: {
“bridge”: {
“IPAddress”: “172.17.0.3”

Şimdi bu nginx e daha önce çalıştırdığımız Ubuntu Container üzerinden ulaşalım:

root@a2b8c78a5457:/# curl http://172.17.0.3
bash: curl: command not found
root@a2b8c78a5457:/# apt-get install curl

Curl kurulu olmadığı için kuralım. 

root@a2b8c78a5457:/# curl http://172.17.0.3
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href=”http://nginx.org/”>nginx.org</a&gt;.<br/>
Commercial support is available at
<a href=”http://nginx.com/”>nginx.com</a&gt;.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@a2b8c78a5457:/# ^C
root@a2b8c78a5457:/#

 

Yukarıda gördüğünüz gibi, -p parametresi ile bir Containeri dış networklerden erişime açmasak ta Containerler birbirleri ile konuşabiliyorlar. Bu özellikle birden çok Container ile kurduğumuz yapılarda, dış networkten erişime ihtiyacı olmayan Containerlerin -p ile dıştan gelen bağnatılara açılmadan çalışabilmesi açısında önemli. Örnek olarak bir web sunucu ve bir sql sunucudan oluşan bir uygulama düşünün. Dış dünyadan sadece web sunucuya bağlanılacak ve web sunucu sql sunucu ile konuşacaksa, sql sunucuyu dışa açmanın anlamı yok. Bu güvenlik açısından da önemli.

Kendi Network’ümüzü Oluşturalım

Bu aşamaya kadar otomatik oluşturulan network üzerinde çalıştık. Şimdi de kendimiz bir network oluşturalım. Bunun için docker network create <network_adı> komutunu kullanacağım. Bu komutun parametreleri bu kadar değil, kolay olsun diye bu şekilde başlayalım.

oktay@ubuntu1804:~$ docker network create oktay_test
1ab7d37da3a945ca5be376817cbcd1c773f714babe6df0b9462493e4a4f66d0d
oktay@ubuntu1804:~$ docker network inspect oktay_test
[
{
“Name”: “oktay_test”,
“Id”: “1ab7d37da3a945ca5be376817cbcd1c773f714babe6df0b9462493e4a4f66d0d”,
“Created”: “2018-10-31T08:14:03.507142944Z”,
“Scope”: “local”,
“Driver”: “bridge”,
“EnableIPv6”: false,
“IPAM”: {
“Driver”: “default”,
“Options”: {},
“Config”: [
{
“Subnet”: “172.18.0.0/16”,
“Gateway”: “172.18.0.1”
}
]
},
“Internal”: false,
“Attachable”: false,
“Ingress”: false,
“ConfigFrom”: {
“Network”: “”
},
“ConfigOnly”: false,
“Containers”: {},
“Options”: {},
“Labels”: {}
}
]

Dikkat ederseniz bu network Bridge tipinde oldu. Yani Fiziksel sunucu ethernet kartı üzerinden dış dünyaya bağlanabilir. Ayrıca Ip Adres Bloğu olarak 172.18.0.0/16 bloğunu aldı. Şimdi bu networkte bir nginx çalıştıralım. Bunun için komuta –network <network_adı> parametresi ekleyeceğiz. Daha önce söylediğim gibi eğer network belirtmezsek Containerler default networke bağlanır. Container çalıştıktan sonra network inspect komutu ile bu yeni networke bakalım, container ne IP aldı?

oktay@ubuntu1804:~$ docker container run -d –network oktay_test nginx:alpine

oktay@ubuntu1804:~$ docker network inspect oktay_test
[
{
“Name”: “oktay_test”,

“Subnet”: “172.18.0.0/16”,
“Gateway”: “172.18.0.1”
“Containers”: {
“e3be928f4a71c4df09f164cd2fc2052d37c77faf6e91684fcdfbef2ffeb2a67c”: {
“Name”: “peaceful_franklin”,
“EndpointID”: “a4e67d68a545420c276964a9a3d66e07d977e178d8f35d66ee9395399814bf82”,
“MacAddress”: “02:42:ac:12:00:02”,
“IPv4Address”: “172.18.0.2/16”,
“IPv6Address”: “”

Kısaltılmış çıktıda IP adresini 172.18.0.2 olarak gördüm.

Otomatik olarak oluşan bridge network ile kullanıcı tarafından oluşturulan bridge network arasındaki en büyük fark, kullanıcı tarafından oluşturulan bridge networkte Container isimlerini çözebilen bir DNS servisi otomatik olarak çalışmaya başlar. Bu sayede Containerler birbirlerine isimleri ile erişebilirler. Bunu aşağıdaki örnek ile görelim. Bu örnekte benim oluşturduğum brdige network üzerinde 2 adet nginx:alpine Container çalıştıracağım. Bunlara da srv1 ve srv2 isimlerini vereceğim.

oktay@ubuntu1804:~$ docker container run -d –network oktay_test –name srv1 nginx:alpine
641fcf0b73131ec5e2606570faa8e96ac1806c79029c5caad23740a7d6537b0d
oktay@ubuntu1804:~$ docker container run -d –network oktay_test –name srv2 nginx:alpine
a593e45229f0f078c79c9e8e41c694c26d25a14c6edfb9e27e3c1b754968b8a0

oktay@ubuntu1804:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a593e45229f0 nginx:alpine “nginx -g ‘daemon of…” 9 seconds ago Up 8 seconds 80/tcp srv2
641fcf0b7313 nginx:alpine “nginx -g ‘daemon of…” 18 seconds ago Up 17 seconds 80/tcp srv1
oktay@ubuntu1804:~$

Aşağıdaki komut ile srv1 nginx sunucunun komut satırına bağlanıp srv2 yi pingleyeceğim. 

oktay@ubuntu1804:~$ docker container exec -it srv1 sh
/ # hostname
641fcf0b7313
/ # ping srv2
PING srv2 (172.18.0.4): 56 data bytes
64 bytes from 172.18.0.4: seq=0 ttl=64 time=0.134 ms
64 bytes from 172.18.0.4: seq=1 ttl=64 time=0.175 ms
64 bytes from 172.18.0.4: seq=2 ttl=64 time=0.175 ms
^C
— srv2 ping statistics —
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.134/0.161/0.175 ms
/ #

Gördüğünüz gibi, kullanıcı tarafından oluşturulmuş olan networkte containerler birbirlerini isimleri ile pingleyebiliyorlar. Daha sonra kullanacağımız örneklerde, bu özellik sayesinde servisler birbirlerini ip vs bilmeden bulabilecek.

Nginx Load Balancer Örneği

Bir önceki yazıda hatrlarsanız ngix web sunucu olarak kullanılmıştı. Bu yazıda ise nginx load balancer olarak çalışacak. İnternet üzerinde load balancer veya reverse proxy nedir (her ikisi de benzer işler yapan ama çok temel farklılıkları olan teknolojiler) diye aratırsanız pek çok kaynak bulabilirsiniz. Kısaca belirtmek gerekirse, load balancer dengelenmek istenen hizmetin önüne koyulan ve yükü arkasındaki sunuculara bizim belirttiğimiz kurallara göre dağıtan teknolojidir (servis/sunucu/hizmet). Web sunucu olarak çalıştığı zaman html dosyaları  /usr/share/nginx/html klasörü altında idi. Eğer bu dosyada html kodu varsa nginx web sunucu olarak çalışıyordu.

Ama eğer /etc/nginx/nginx.conf yolunda bulunan dosyayı konfigüre edersek bu sefer nginx load balancer olarak çalışır. Bu makalede anlatacağım örmekte, 2 nginx container web sunucu olarak çalışırken bunların önünde çalışan başka bir nginx container load balancer olarak çalışacak. Bu örneğin çalışması için iki ayrı klasör içinde iki ayrı index.html dosyası oluşturup bunu 2 ayrı nginx web sunucu containerine bağlayacağım. Ayrıca üçüncü bir ngix sunucunun load balancer olarak çalışması için bir nginx.conf dosyası oluşturacağım. Load balancer olarak çalışması için aşağıdaki örnek dosya kullanılabilir.

events {
worker_connections 1024; ## Default: 1024
}

http {
server {
listen 80;

location / {
proxy_pass http://backend;
}
}

upstream backend {
server webserver1:80;
server webserver2:80;
}
}

Yukarıdaki dosya ayrıntılarını bir sonraki yazımda ayrıntılı anlatacağım ama kısaca söylemek gerekirse nginx’e 80 nolu porttan dinlemesini ve gelen istekleri webserver1 ve webserver2  arasında paylaştırmasını/yönlendirmesini söylüyor. Web sunucuları için ip yerine isim kullandığımdan bu network kullanıcı tarafından oluşturulmalı.

Şimdi gerekli altyapıyı kuralım. Önce webnet adında bir network oluşturdum. Sonra loadbaltest adında bir klasör ve bu klasör altında web1 ve web2 adında iki ayrı alt klasör oluşturdum. Her iki klasör içinde de 2 ayrı index. html dosyası. Bu dosya içerklerini aşağıda görebilirsiniz. Load balancer üzerinden gelen istekleri ayırabilmek için bir web sunucusu 1 diğer web sunucusu ise 2 diye cevap dönecek. Bu sayede hangi web sunucunun cevap verdiğini anlayabileceğim. Aşağıdaki yapıyı kurunca bir alttan devam edin.

 

oktay@ubuntu1804:~$ docker network create webnet
705d528dd7f620a9fae68d30ea751afff460750fb515d60819e2ccf96aea6b61
oktay@ubuntu1804:~$ pwd
/home/oktay
oktay@ubuntu1804:~$ mkdir loadbaltest

oktay@ubuntu1804:~$ cd loadbaltest/

oktay@ubuntu1804:~/loadbaltest$ mkdir web1

oktay@ubuntu1804:~/loadbaltest$ mkdir web2

oktay@ubuntu1804:~/loadbaltest$ cd web1
oktay@ubuntu1804:~/loadbaltest/web1$ nano index.html

<html>
<body bgcolor=”white” text=”blue”>
<h1> Oktay Web Sunucu 1 </h1>
</body>
</html>

oktay@ubuntu1804:~/loadbaltest/web1$ cd ..
oktay@ubuntu1804:~/loadbaltest$ cd web2

oktay@ubuntu1804:~/loadbaltest/web2$ nano index.html

<html>
<body bgcolor=”white” text=”blue”>
<h1> Oktay Web Sunucu 2 </h1>
</body>
</html>

oktay@ubuntu1804:~/loadbaltest/web2$ cd ..
oktay@ubuntu1804:~/loadbaltest$ tree
.
├── web1
│   └── index.html
└── web2
└── index.html

2 directories, 2 files

 

Şimdi gelelim Load Balancer konfig dosyasına. Bu dosya için yukarıdaki örnekteki içeriği kullanabiliriz. Bunun için loadbaltest klasörü içinde  nginx.conf dosyası oluşturun ve bir üstteki örnek içeriği yapıştırın.

oktay@ubuntu1804:~/loadbaltest$ nano nginx.conf

Herşey hazır ise önce web sunucuları çalıştıralım:

oktay@ubuntu1804:~/loadbaltest$ docker container run -d –name webserver1 –network webnet -v /$(pwd)/web1:/usr/share/nginx/html nginx:alpine
e09c811220700647d0bded60c12ff1ee06bc59d16675be8b2744d49fad2f4986
oktay@ubuntu1804:~/loadbaltest$ docker container run -d –name webserver2 –network webnet -v /$(pwd)/web2:/usr/share/nginx/html nginx:alpine
9a375001aa706f3345d9c350d87ced74a688e564e618492659136f7afe1442f6

Buraya kadar geldiysek çalışan 2 ayrı web sunucumuz var. Dikkat edin, komutları daha önceden oluşturduğumuz loadbaltest klasörü içinden çalıştırdım, bu sayade komut satırında index.html dosylarının tam yolunu yazmak yerine $(pwd) yazın, bunun anlamı bulunduğum klasör demek. Aşağıdaki komut ile devam edelim, nginx bu sefer web sunucu değil de Load Balancer olarak çalışacak. Dikkat ederseniz ngin’leri web sunucu olarak çalıştırırken -p parametresi ile port açmadım. Bu işlemi load balancer yapacak ve Layer/Katman 4 de 80 nolu porta gelen istekleri gerideki bu web sunucuların 80 nolu portuna yollayacak.  

oktay@ubuntu1804:~/loadbaltest$ docker container run -d -p 80:80 –network webnet -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx
cfce60c397e568cdd00ea3cf94a1b0f39dc78ba3be64a82b363231faad8b1aa5

Bu aşamada  web sayfaları geliyor mu diye kontrol edelim. Curl komutu ile web sayfalarını çağırdığımızda sırayla sunucu 1 ve sunucu 2 cevap verecek. Bir sonraki yazıda web sunuculardan biri çökerse LB nasıl davranır, LB için hangi yöntemler var vs. konularında ayrıntılı bilgi vereceğim. 

oktay@ubuntu1804:~/loadbaltest$ curl http://localhost
<html>
<body bgcolor=”white” text=”blue”>
<h1> Oktay Web Sunucu 1 </h1>
</body>

</html>
oktay@ubuntu1804:~/loadbaltest$ curl http://localhost
<html>
<body bgcolor=”white” text=”blue”>
<h1> Oktay Web Sunucu 2 </h1>
</body>
</html>

Eğer LB loglarına bakarsam istekleri görürüm:

oktay@ubuntu1804:~/loadbaltest$ docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cfce60c397e5 nginx “nginx -g ‘daemon of…” 8 minutes ago Up 8 minutes 0.0.0.0:80->80/tcp xenodochial_mcnulty
9a375001aa70 nginx:alpine “nginx -g ‘daemon of…” 9 minutes ago Up 9 minutes 80/tcp webserver2
e09c81122070 nginx:alpine “nginx -g ‘daemon of…” 9 minutes ago Up 9 minutes 80/tcp webserver1
oktay@ubuntu1804:~/loadbaltest$ docker container logs xenodochial_mcnulty
172.18.0.1 – – [31/Oct/2018:12:53:25 +0000] “GET / HTTP/1.1” 200 98 “-” “curl/7.58.0”
172.18.0.1 – – [31/Oct/2018:12:53:27 +0000] “GET / HTTP/1.1” 200 98 “-” “curl/7.58.0”
oktay@ubuntu1804:~/loadbaltest$

Bu yazıyı burada bitiriyorum. Önümüzdeki yazıda buraya kadar yaptıklarımızı toparlamaya çalışacağım.

 

Ek Okuma:

https://docs.docker.com/network/bridge/