Docker blog serimizin ilk iki bölümünde Docker’ı günlük hayatta kullanmaya başlamak için gerekli bilgi seviyesini oluşturmak için Docker ve sunduğu olanakları yakından tanımaya çalıştık. Bu blog’da ise Docker’ı gerek geliştirme, gerek test ve gerekse de üretim ortamında nasıl kullanabileceğimiz ile ilgili çok pratik ve genellikle demo’lardan oluşan bilgileri elde edeceğiz. Eminim verilen örnekler sizin kafanızda da farklı çağrışımlar uyandıracak ve siz de kendinizi her gün uğraştığınız işlerde Docker kullanarak nasıl daha verimli olabileceğinize dair düşünceler içinde bulacaksınız.
Docker blog serisinin ilk ikisi aşağıda verilmiştir. Eğer daha önce okumadıysanız bu blog’ları da okumanız tavsiye edilir.
Docker Bölüm 1: Nedir, Nasıl Çalışır, Nerede Kullanılır?
Docker Bölüm 2: Yeni bir Docker Image’ı Nasıl Hazırlanır?
Çalışma Özeti
Öncelikle bu blog’da anlatılacak özellikler Docker-Compose’un 1.6 versiyonu ve sonrasında desteklenmektedir. Komut satırında docker-compose version
komutunu vererek kullandığınız versiyon ile ilgili bilgi alabilirsiniz. Eğer bu komutun çıktısında görülen versiyon 1.6 altındaysa bu blog’da yazılanları takip edebilmek için kullandığınız versiyonu yükseltmeniz gereklidir.
Bu blog’da aşağıdaki adımları tamamlayarak Docker’ın günlük hayatta kullanımı ve getirdiği kolaylıkları daha yakından tanıma fırsatı bulacağız.
- Öncelikle Docker Compose’un ne işe yaradığı ve hangi problemi çözdüğü üzerinde duracağız.
- Motivasyon olması açısından en basit haliyle bir docker-compose.yml dosyası oluşturarak basit bir sistemi ayağa kaldıracağız.
- Docker Compose CLI (docker-compose) tanıtımı ile devam edeceğiz.
- docker-compose.yml dosya yapısını ve konfigürasyon opsiyonlarını detaylı bir şekilde inceleyeceğiz.
- Docker Compose ile kurgulanmış görece karmaşık bir yapıyı çalıştıracak, satır satır açıklayacak ve bu blog’u kapatacağız.
Ön Koşullar
Bu blog’da yer verilen adımları takip edebilmeniz için aşağıdaki koşulları sağlamanız beklenmektedir.
- İlgili platformda (Windows, Linux, Mac) komut satırı (command window veya terminal) kullanabiliyor olmak.
- Docker CLI ile ilgili genel bilgi sahibi olmak.
- YAML (Yamıl diye okunur) data serializasyon diline aşinalık.
- Docker ile ilgili blog serisinin ilk blogunda’da anlatılan düzeyde bilgi sahibi olmak.
- Dockerfile hazırlama ile ilgili blog serisinin ikinci blog’unda anlatılan düzeyde bilgi sahibi olmak.
Docker Compose - Giriş
Önceki iki blog’da üzerinde durduğumuz konular hep tek bir Docker Image oluşturma veya tek bir Container çalıştırma ile ilgiliydi. Günlük olarak kullandığımız veya kullanılmak üzere müşterilerimize sunduğumuz ürünler ve sistemler birden fazla servisin (genellikle web sunucular, uygulama sunucuları, veri tabanı sunucuları, cache sunucuları, message queue sunucuları, vb) bir araya gelmesiyle oluşmaktadırlar. Orta ve büyük ölçekteki sistemleri Docker CLI kullanarak kullanıma sunmak ve bakımını yapmak pek makul değildir. Bu tip sistemlerde Container’ların çalıştırılması, durdurulması, birbirleri ile olan ilişkilerinin belirtilmesi yani basit bir şekilde yönetilebilmesi görevlerini yerine getirecek ayrı bir aracın varlığı gereklidir.
Development (geliştirme), kullanıcı kabul (UAT) ve test ortamlarının kolay bir şekilde yönetilebilmesi için Docker Compose kullanılmaktadır. Docker Compose bu ortamlarda kolay kurulum, bakım ve genel olarak kullanım sağlamaktadır fakat Docker Inc
tarafından yapılan yeni geliştirmelerle sistem kararlılığı, performansı, tutarlılığı, yedekliliği ve yüksek erişilebilirliğinin önemli olduğu production (üretim) ortamlarında da kullanılmaya doğru gitmektedir. Özellikle Docker 1.12 versiyonu ile birlikte gelen Docker Compose ve Swarm entegrasyonu bu konuda yakın gelecekte daha da sağlam adımlar atılacağının bir habercisi niteliğindedir. Docker Compose, Docker Swarm ve Kubernetes gibi Clustering (kümeleme) araçları ile karıştırılmamalıdır. Swarm ve Kubernetes, Production sistemlerin yukarıda sıralanan özelliklerini yerine getirmeye çabalamaktadır.
docker-compose.yml dosyasında detayları verilen ve birbirleri ile ilişkileri tanımlanan servisler (Container’lar) tek bir komut ile ayağa kaldırılıp tek bir komut ile durdurulur ve yine tek bir komut ile kaldırılabilirler (silinebilirler).
Fig‘i referans alan docker-compose
aracı güncel olarak Docker CLI ve Docker Daemon’ı da sağlayan Docker Inc
tarafından sunulmakta ve genellikle bütün platformlarda Docker bundle’ı ile birlikte gelmektedir. Docker Compose’un ne işe yaradığını anladıktan sonra şimdi basit bir sistem ve senaryo ile kavramları ve kullanımını örneklemeye çalışalım.
Basit Bir Sistemi Docker Compose ile Ayağa Kaldırma
Bu blog serisinin ilk blog’unda Docker’ın Multitenancy mantığını uygulama içinden alarak platforma taşıma konusunda yardımcı olacağını söylemiştik. Bu bölümde yapacağımız çalışmada bu konuyu örneklemeye çalışarak bir taşla iki kuş vurmaya çalışalım. A, B ve C şirketlerinden kurulu bir holdingde, şirketleri statik sayfalardan oluşan web sitelerini sunmak için Docker Container’lardan oluşan bir yapı kurmak istediğimizi düşünelim. Bu yapı için kuracağımız test ortamını ele alacağız. Statik web sitelerini Nginx ile sunalım fakat bir önceki blog’da kendi hazırladığımız Nginx Docker Image’ı yerine DockerHub‘da bulunan official Nginx Docker Image’ını kullanacağız.
Hemen işe koyulalım.
-
Bu proje için yeni bir klasör oluşturarak, aşağıdaki içeriğe sahip bir
docker-compose.yml
dosyası oluşturun.version: '2' services: company_a_web_server: image: nginx:latest ports: - "8001:80" company_b_web_server: image: nginx:latest ports: - "8002:80" company_c_web_server: image: nginx:latest ports: - "8003:80"
Docker Compose’un 1.6 ve sonraki versiyonlarında
docker-compose.yml
dosyasının formatı farklı özellikleri de destekleyecek şekilde geliştirilmiştir. Yukarıda verilen dosyada ilk satırınversion: '2'
olduğuna dikkat edin. Bu satır Docker Compose’a içeriğin 1.6 ile gelen versiyon yaniversion: '2'
için değerlendirilmesini salık verir.YAML dosyasının
services
düğümünde Docker Compose ile yönetmek istediğimiz servisleri teker teker sıralarız. Bizim örneğimizde A, B ve C şirketleri için sırasıylacompany_a_web_server
,company_b_web_server
vecompany_c_web_server
servisleri yaratılmıştır. Compose file’da servisler örneğimizde yapıldığı gibi hazır bir Image’dan (nginx:latest
) türetilebileceği gibi bir Dockerfile sağlanarak da yaratılabilir.image
keyword’ü servisin hangi Image ile başlatılacağını belirtir.Docker Compose dosyası ile oluşturacağımız Nginx Container’larının (servislerinin) kullandığı official Nginx Image’ı HTTP (80) numaralı portu dinlemekte, bu portu
EXPOSE
etmekte ve bu porttan web sayfalarını sunmaktadır. Host üzerinden Container’lar tarafından 80 portundan sunulan web sayfalara ulaşabilmek için 80’li portların Host’ta farklı portlara forward edilmesi gereklidir. Port forwarding (Map’leme) amacı ile Compose file’da her bir servis içinports
keyword’ü ile 8001, 8002 ve 8003 numaralı portlar sırasıyla A, B ve C şirketlerinin web sayfaları için Map’lenir. -
Terminal veya komut satırı açarak
docker-compose.yml
dosyasının bulunduğu klasöre gidin vedocker-compose up
komutunu verin. Docker Compose, YAML dosyasında bulunan bütün Image’ları önce pull edip (eğer daha önce pull edilmediyse) sonra da çalıştıracaktır. Siz deNginx
Image’ını DockerHub’dan daha önce indirdiyseniz aşağıdakine benzer bir çıktı görmelisiniz.Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up Creating network "dcblog_default" with the default driver Creating dcblog_company_a_web_server_1 Creating dcblog_company_b_web_server_1 Creating dcblog_company_c_web_server_1 Attaching to dcblog_company_a_web_server_1, dcblog_company_c_web_server_1, dcblog_company_b_web_server_1
Çıktıdan görebileceğiniz gibi Docker Compose öncelikle
dcblog_default
adında bir network yarattı sonra kullanılan klasör ismi olanDCBlog
ve servis isimlerinicompany_x_web_server
birleştirip Container’lara vererek onları teker teker çalıştırdı. En son satırda ise terminali çalıştırılan bu Container’lara attach ettiğini görüyoruz. Bu çıktı ile ilgili farklı açılardan bakarak detaylı analizler yapacağız fakat öncelikle demo’yu tamamlayalım. -
Web tarayıcıyı açarak
http://localhost:8001
,http://localhost:8002
vehttp://localhost:8003
adreslerinden Nginx’in default sayfasının gelip gelmediğini kontrol edin. Sizde de aşağıdakine benzer bir görüntü oluşmalı. Burada aşağıya sadece A şirketinin web sitesi kabul ettiğimiz Container’ın sunduğu ana sayfanın ekran çıktısı alınmıştır. -
Şimdi Docker Compose’un arka planda ne yaptığını anlamaya çalışalım. Docker Compose’u başlattığınız terminal’den başka bir terminal açarak
docker ps
komutunu verin ve çalışan Container’ların hangileri olduğunu görün. Aşağıdaki çıktıda görebileceğiniz gibi Compose üç adet Container’ı onları ayarladığımız gibi başlatarak onları isimlendirmiş.CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 894cf2d86b46 nginx:latest "nginx -g 'daemon off" 14 minutes ago Up 14 minutes 443/tcp, 0.0.0.0:8003->80/tcp dcblog_company_c_web_server_1 ed8bdaa7514c nginx:latest "nginx -g 'daemon off" 14 minutes ago Up 14 minutes 443/tcp, 0.0.0.0:8002->80/tcp dcblog_company_b_web_server_1 99e685c06e2d nginx:latest "nginx -g 'daemon off" 14 minutes ago Up 14 minutes 443/tcp, 0.0.0.0:8001->80/tcp dcblog_company_a_web_server_1
-
docker-compose up
komutunu çalıştırdığınız terminal’e tekrar giderekCtrl + C
tuş kombinasyonuna basarak Container’ların çalışmalarını durdurun. Sonra da terminallerin herhangi birindendocker ps
komutunu verdiğinizde çalışan hiçbir Container olmadığını göreceksiniz.docker ps -a
komutunu verdiğinizde çalıştırılan Container’ların durdurulduğunu görebilirsiniz.Gokhans-MacBook-Pro:DCBlog gsengun$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 894cf2d86b46 nginx:latest "nginx -g 'daemon off" 17 minutes ago Exited (0) About a minute ago dcblog_company_c_web_server_1 ed8bdaa7514c nginx:latest "nginx -g 'daemon off" 17 minutes ago Exited (0) About a minute ago dcblog_company_b_web_server_1 99e685c06e2d nginx:latest "nginx -g 'daemon off" 17 minutes ago Exited (0) About a minute ago dcblog_company_a_web_server_1
-
Herhangi bir terminalde
docker-compose.yml
dosyasının bulunduğu klasöre giderekdocker-compose up
komutunu çalıştırın. Aşağıdakine benzer bir çıktı elde edeceksiniz.Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up Starting dcblog_company_a_web_server_1 Starting dcblog_company_b_web_server_1 Starting dcblog_company_c_web_server_1 Attaching to dcblog_company_b_web_server_1, dcblog_company_c_web_server_1, dcblog_company_a_web_server_1
Çıktıdan görebileceğiniz gibi bu kez ikinci adımdaki gibi Container’lar yaratılmak yerine sadece başlatıldı çünkü bu Container’lar daha önce başlatılmış ve durdurulmuştu. Muhtemelen burada Compose Docker CLI’ın
docker start
komutunu koşturarak Container’ları tekrar ayağa kaldırmıştır. -
docker-compose
Docker CLI’a benzer komutlar sağlamaktadır. Bildiğiniz gibidocker ps
Docker Daemon’da çalıştırılan bütün Container’ları listelemektedir. Docker Compose tarafından sağlanandocker-compose ps
,docker-compose.yml
dosyasının bulunduğu klasörde koşturulunca sadece Compose tarafından başlatılan Container’ları listeler.İkinci terminalde ilgili klasörde olduğunuza emin olarak
docker-compose ps
komutunu koşturun. Aşağıdakine benzer bir çıktı elde etmelisiniz.Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------------- dcblog_company_a_web_server_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:8001->80/tcp dcblog_company_b_web_server_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:8002->80/tcp dcblog_company_c_web_server_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:8003->80/tcp
-
Docker Compose CLI’ı, Docker CLI tarafından sunulan birçok komutu Compose mantığına göre tekrar uygulamıştır. Bu komutların bir listesine
docker-compose help
komutu ile bakılabilir. Bu komutlardandocker-compose logs
komutunu vererek Compose kapsamında çalıştırılan bütün Container’ların terminal çıktılarına ulaşabilirsiniz. -
docker-compose up
komutunu verdiğimiz terminale giderekCtrl + C
ile Container’ları Stop edin. Sonra da bütün Container’ları kaldırmak içindocker-compose down
komutunu verin. Sizde de aşağıdakine benzer bir çıktı oluşmalı.Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose down Removing dcblog_company_c_web_server_1 ... done Removing dcblog_company_b_web_server_1 ... done Removing dcblog_company_a_web_server_1 ... done Removing network dcblog_default
Çıktıdan Composer’ın yarattığı bütün Container’ları
docker-compose down
komutu ile kaldırdığını görebilirsiniz.docker-compose ps
veyadocker ps -a
komutları ile Container’ların gerçekten kaldırıldığını görebilirsiniz. Burada da muhtemelen Compose,docker rm
komutunu kullanarak Container’ları kaldırmıştır.ÖNEMLİ NOT:
docker-compose down
komutuna-v
parametresi eklemek Compose kapsamında başlatılan herhangi bir Container ile ilişkilendirmiş Volume’ları da silmektedir. Henüz Volume’ların ne olduğundan bahsetmedik ancak Container’da oluşturulan bilgiyi kaybetmek istemiyorsanız-v
parametresi ile çalıştırmamanız gerektiğini aklınızın bir kenarında tutmanız gerekir.
Docker Compose CLI
İlk blog’da Docker CLI ile ilgili detaylı bilgiler vermiştik. Bir önceki bölümde de Docker Compose CLI’ın Docker CLI’daki birçok komutu Docker Compose işlevleri doğrultusunda tekrar uyguladığından bahsettik. Bu bölümde bir önceki bölümdeki örnek üzerinden Docker Compose CLI’ı (docker-compose
) daha yakından tanıyalım.
docker-compose build
Docker Compose CLI’ın sunduğu docker-compose build
ve docker-compose build <service_name>
komutları ile Compose dosyasında tanımladığınız Container’ları (servisleri) teker teker veya toplu halde build edebilirsiniz. Önceki bölümde docker-compose up
komutunu verdiğimizde bu komutun, servisler eğer daha önce build edilmemişse onları build ettiğini söylemiştik. Servislerden birinin Host’dan kopyaladığı dosyaları Host’ta değiştirdiğimizi ve ilgili servisin Image’ını yeniden build etmek istediğimizi düşünelim. Bu durumda docker-compose up
komutunu verirsek Docker Compose servisin içeriğinin değiştiğini anlayamadığı için sadece stop durumdaki Container’ları tekrar ayağa kaldıracaktır, dolayısıyla yapılan değişiklikler görülemeyecektir. İçeriği değiştirilen servis için docker-compose build <service_name>
komutu koşturulduktan sonra docker-compose up
komutu koşturulursa ilgili servis’in Container’ı kaldırılarak yeni Image’dan yeni bir Container oluşturulur.
docker-compose up
Bir önceki bölümdeki örneklerde görüldüğü gibi Compose dosyasında tanımlı bütün servisleri ayağa kaldırmak için kullanılır.
docker-compose build
için verilen yukarıdaki örnekte eğer değiştirilen şey Host’tan kopyalanan dosyalar olmayıp, Dockerfile veya servis konfigürasyonu olsaydı docker-compose up
komutu verildiğinde Compose değişiklikleri tanıyacak, Image’ı rebuild edecek, önceki Container’ı kaldıracak ve yeni bir Container yaratacaktır. Bütün bunların olması istenmiyorsa --no-recreate
parametresi ile Compose’un değişiklikleri algılaması ve yeni bir build oluşturması engellenir. Tam tersine bir önceki örnekteki gibi Host’tan kopyalanan dosyalar gibi Compose’un anlayamayacağı değişikliklerde --force-recreate
paramtresi verilerek Image’ın her seferinde tekrar build edilmesi, önceki Container’ın sistemden kaldırılması ve yeni bir Container yaratılması sağlanabilir.
Bütün durumlarda Compose dosyasında servislerin tamamının ayağa kaldırılması istenmeyebilir. Sadece belirli servis veya servislerin ayağa kaldırılması için servis isimleri parametre olarak verilebilir. Kullanım docker-compose up <service_name_1> <service_name_2>
. Bir önceki bölümdeki örnekde A, B ve C şirketlerinin sitelerinin hepsini değil sadece A sitesini çalıştırmaya başlamak istersek docker-compose up company_a_web_server
komutunu verebiliriz. docker-compose.yml
dosyasının buluduğu klasöre giderek aşağıdaki komutu verin.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up company_a_web_server
Creating network "dcblog_default" with the default driver
Creating dcblog_company_a_web_server_1
Attaching to dcblog_company_a_web_server_1
Gördüğünüz gibi sadece A servisi için bir Container yaratıldı.
Docker CLI’da olduğu gibi -d
parametresi Compose’da da Container’ların detached
modda yani terminale bağlı olmadan arka planda (background) çalıştırılmasını sağlar. Bu kez A ve B şirketlerine ait sunucuları detached
modda çalıştıralım.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up -d company_a_web_server company_b_web_server
Creating network "dcblog_default" with the default driver
Creating dcblog_company_a_web_server_1
Creating dcblog_company_b_web_server_1
Bu kez -d
parametresi ile başlattığımız iki servisi görüyoruz. Bu servislerin log’larına bakmak istersek, aynı terminali kullanarak docker-compose logs <container_name>
komutunu verebiliriz.
docker-compose down
Önceki bölümdeki örnekten de gördüğümüz gibi docker-compose down
Compose tarafından yaratılmış Container’ları (servisleri) öncelikle durdurarak sonra da sistemden kaldırmaktadır. Burada -v
parametresine dikkat etmek gereklidir. Compose tarafından oluşturulan Volume’ların (sonraki bölümlerde değineceğiz) da kaldırılması isteniyorsa -v
parametresinin sağlanması gerekmektedir.
Eğer Compose dosyasında Compose tarafından yeni Image’lar build edilmesi konfigüre edilmişse docker-compose down
bu Image’ların silinmesi için de kullanılabilir. Oluşturulan Image’ların silinmesi için --rmi all
parametresinin sağlanması gereklidir.
docker-compose ps
Aynı Host üzerinde birden fazla Docker sistemi çalıştırılıyorsa docker ps
komutunun verdiği çıktı başka sistemlerden çalıştırılan Container’ları da gösterdiği için çok fazla anlamlı olmayabilir veya karmaşık gelebilir. Docker Compose CLI’ın sunduğu docker-compose ps
, docker-compose.yml
dosyasının olduğu klasörden verilirse sadece ilgili Compose dosyasında olan Container’ları listeleyecek, daha yönetilebilir ve az yoran bir çıktı sunacaktır.
docker-compose ps
Docker CLI’daki -a
parametresine benzer bir parametre sunmamaktadır. Hatırlarsanız docker ps -a
daha önce çalıştırılmış fakat sonradan çıkış (Exit) yapmış process’leri göstermekteydi. Compose sadece ve sadece kendi başlattığı Container’larla ilgilendiği için Exit olan Container’ları ayrı bir parametre ile göstermek yerine parametre almayan komutun çıktısına bir kolon olarak eklemiştir. Olayı örneklemek için öncelikle bütün Container’ları başlatalım ve sonra Ctrl + C
tuş kombinasyonu ile durdurup docker-compose ps
ile gösterilen çıktıya bakalım.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up
Creating network "dcblog_default" with the default driver
Creating dcblog_company_b_web_server_1
Creating dcblog_company_a_web_server_1
Creating dcblog_company_c_web_server_1
Attaching to dcblog_company_c_web_server_1, dcblog_company_a_web_server_1, dcblog_company_b_web_server_1
^CGracefully stopping... (press Ctrl+C again to force)
Stopping dcblog_company_c_web_server_1 ... done
Stopping dcblog_company_a_web_server_1 ... done
Stopping dcblog_company_b_web_server_1 ... done
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------
dcblog_company_a_web_server_1 nginx -g daemon off; Exit 0
dcblog_company_b_web_server_1 nginx -g daemon off; Exit 0
dcblog_company_c_web_server_1 nginx -g daemon off; Exit 0
docker-compose run
Docker Compose CLI’daki en enteresan komutlardan biridir. Bu komutu kullanmaya başladığınızda biliniz ki ya Docker ve Compose ile ilgili gerçekten çok ileri seviyeye geldiniz ya da bir şeyleri Best Practices çerçevesinde yapmıyorsunuz. docker-compose run
komutunun temel olarak yaptığı Compose dosyasında tanımlanan bir servis için Compose dosyasında tanımlanmayan bir komut koşturmaktır. Örnek olarak yine bir önceki bölümdeki senaryodan yola çıkalım. Öncelikle senaryoyu yaratmaya yardımcı olması açısından docker-compose up
ile bütün servisleri başlatalım. Sonra da docker-compose run
ile A şirketi için tanımlanan servisi kullanarak yeni bir Container çalıştırıp bu Container ile B şirketinin Container’ına ping -c 4 company_b_web_server
komutu ile 4 adet ping atalım. Burada docker-compose up
komutunu vermemizin tek sebebinin B şirketinin Container’ını ayağa kaldırmak olduğunu tekrar hatırlatayım. Aşağıdaki komutların aynısını siz de vererek benzer çıktılar elde etmelisiniz.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up -d
Creating dcblog_company_b_web_server_1
Creating dcblog_company_a_web_server_1
Creating dcblog_company_c_web_server_1
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose run company_a_web_server ping -c 4 company_b_web_server
PING company_b_web_server (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: icmp_seq=0 ttl=64 time=0.132 ms
64 bytes from 172.20.0.2: icmp_seq=1 ttl=64 time=0.117 ms
64 bytes from 172.20.0.2: icmp_seq=2 ttl=64 time=0.102 ms
64 bytes from 172.20.0.2: icmp_seq=3 ttl=64 time=0.108 ms
--- company_b_web_server ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.102/0.115/0.132/0.000 ms
Dikkatli okuyucularımız hemen iki şeyi farkettiler. İlki her nasıl olduysa company_a_web_server
servisini çalıştıran Container company_b_web_server
servisini çalıştıran Container’a ismi ile ping atabilmesiydi. İkincisi ise company_b_web_server
servisini çalıştıran Container’ın IP’sinin 172.20.0.2
olmasıydı.
ÇOK ÖNEMLİ NOT:
Docker Compose aynı dosyada tanımlanan bütün servislerin birbirlerine servis isimleri üzerinden network bağlantısı sağlayabilmeleri için gerekli düzenlemeleri yapar. Yaptığı düzenleme Networking modulünde ilgili servis isimleri ve bu servisleri çalıştıran Container’lara verdiği IP’leri mini bir DNS sunucuya karşılık olarak yazmasıdır.
Son olarak docker-compose run
ile çalıştırılan Container’lar genellikle tek seferlik bir işi yapıp çıkmak için kullanıldığından bu Container’ın Exit yaptıktan sonra sistemden kaldırılmasını isteyebiliriz. Bu durumda komutu --rm
parametresi ile çalıştırırsak Container Exit yaptıktan sonra sistemden kaldırılır.
docker-compose start
Önceden docker-compose up
veya docker-compose run
ile başlatılan Container’lardan birini veya birkaçını yeniden başlatmak için kullanılır. Açık konuşmak gerekirse bugüne kadar çok fazla kullandığımız bir komut olmadı bu komut. Sağladığı fonksiyon docker-compose up <service_name>
tarafından bire bir karşılandığı için docker-compose start
muhtemelen Docker CLI ile uyum amacıyla konmuş veya Fig‘e geriye dönük uyumluluk amacıyla bırakılmıştır.
docker-compose stop
Compose dosyasında verilen ve daha önceden docker-compose up
veya docker-compose run
komutları ile başlatılan bir veya daha fazla servisi durdurmak için kullanılır. Örneklemek için bir önceki bölümdeki kullanım senaryosunda A ve B şirketlerinin web sitelerini docker-compose up
komutu ile başlatalım sonra da A şirketinin web sayfasının sunan servisi docker-compose stop
komutu ile durduralım. Aşağıda verilen komutları takip ederseniz sizin de benzer çıktıları almanız gerekir.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up -d company_a_web_server company_b_web_server
Creating network "dcblog_default" with the default driver
Creating dcblog_company_a_web_server_1
Creating dcblog_company_b_web_server_1
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose stop company_a_web_server
Stopping dcblog_company_a_web_server_1 ... done
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------------------------
dcblog_company_a_web_server_1 nginx -g daemon off; Exit 0
dcblog_company_b_web_server_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:8002->80/tcp
Çıktıdan görebildiğiniz gibi durdurulan company_a_web_server
servisini sunan dcblog_company_a_web_server_1
Exit durumdadır.
docker-compose exec
Docker CLI’daki docker exec
komutunun bire bir aynısıdır. Çalışmakta olan Container’da bir komut koşturmak için kullanılır. Yine bir önceki bölümdeki örnekten devam ederek, A şirketinin web sitesini sunan Container’a terminal erişimi sağlayarak terminalde uname -a
komutunu koşturalım.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose up -d
Creating network "dcblog_default" with the default driver
Creating dcblog_company_a_web_server_1
Creating dcblog_company_b_web_server_1
Creating dcblog_company_c_web_server_1
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose exec company_a_web_server /bin/bash
root@3bb989b36e01:/# uname -a
Linux 3bb989b36e01 4.4.15-moby #1 SMP Thu Jul 28 21:30:50 UTC 2016 x86_64 GNU/Linux
docker-compose exec
komutu terminal erişimi sağlamanın yanında başka komutlar koşturmak için de kullanılabilir. Aşağıdaki örnekte A şirketinin web sitesini sunan Container’dan B’ninkine 4 adet ping atılmıştır.
Gokhans-MacBook-Pro:DCBlog gsengun$ docker-compose exec company_a_web_server ping -c 4 company_b_web_server
PING company_b_web_server (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: icmp_seq=0 ttl=64 time=0.078 ms
64 bytes from 172.20.0.2: icmp_seq=1 ttl=64 time=0.107 ms
64 bytes from 172.20.0.2: icmp_seq=2 ttl=64 time=0.113 ms
64 bytes from 172.20.0.2: icmp_seq=3 ttl=64 time=0.108 ms
--- company_b_web_server ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.078/0.101/0.113/0.000 ms
Docker Compose CLI’ı detaylı bir şekilde inceledik. Şimdi de docker-compose.yml
dosya yapısına ve komfigürasyon opsiyonlarına bir göz atalım.
docker-compose.yml Dosyasının Yapısı ve Konfigürasyon Opsiyonlarını
Daha önce de söylediğimiz ve uzantısından zaten anlaşılabileceği gibi docker-compose.yml dosyası YAML (yamıl diye okunur) formatındadır. YAML, JSON ve XML gibi bir data serializasyon formatıdır. YAML, endüstride basit konfigürasyonlar için JSON’dan çok daha okunur bulunmakta ve severek kullanılmaktadır.
Compose dosyasının yapısını üç ana bölümde inceleyebiliriz. Bu bölümler Service, Volume ve Network konfigürasyonu olarak sıralanabilir. Bu bölümde sıraladığımız bütün özelliklerin versiyon 2’ye ait olduğunu bir kez daha tekrarlayarak başlayalım.
Service Konfigürasyonu
Compose dosyasında service:
Tag’i ile belirtilir. Her bir servis tanımı, kendisinden çalıştırılan Container’ların detaylarını belirler. Şimdi service:
Tag’i altında verilen konfigürasyon opsiyonlarını teker teker sıralayarak örnekleyelim.
build
İlgili Service’in hangi Dockerfile’a göre build edileceği bu Tag ile konfigüre edilir. İki şekilde kullanılabilir. Birincisinde sadece build’de kullanılacak Dockerfile
‘ın relatif path’inin verilmesi yeterlidir. Aşağıdaki gibi kullanılır. Verilen örnekte MyWebApp
adlı klasörde Dockerfile
‘ın bulunması gereklidir.
build: ./MyWebApp
İkinci kullanımda ise build context (build’in Dockerfile’a relatif olarak hangi klasörde yapılacağı) ve kullanılacak Dockerfile’ın ismi verilir. Aşağıdaki gibi kullanılır.
build:
context: ./MyWebApp
dockerfile: Dockerfile-MyWebApp
command
İlgili servis için kullanılan Image’ın sağladığı komuttan farklı bir komut kullanılmak istendiğinde bu konfigürasyon opsiyonundan faydalanılır. Aşağıdaki gibi kullanılır.
command: ping -c 4 www.google.com
container_name
Basit senaryolarda kullanılmasına gerek olmayan fakat bazı karmaşık senaryolarda çok işe yarayan bir opsiyondur. Önceki bölümlerde anlatıldığı gibi Compose Container isimlerini içinde bulunulan klasör, servis ismi ve önceden bu servis tanımından çalıştırılan Container sayısı gibi faktörlerden otomatik olarak oluşturur. Bu konfigürasyon opsiyonu kullanılarak Service tanımından oluşturulacak Container’ın isimleri otomatik oluşturmak yerine böylece belirlenebilir.
Kullanım senaryosunu örneklemeye çalışalım. Docker Compose ile oluşturduğumuz sistemde herhangi bir nedenden dolayı Container’lardan birinde oluşan bir dosyanın docker cp
ile Host’a taşınmasının gerektiğini düşünelim. docker cp
komutuna Container’ın isminin sağlanması gerekmektedir. Servis tanımında eğer container_name
özel bir isim seçilirse istenen dosya docker cp
ile rahatlıkla kopyalanabilir. Aşağıdaki gibi kullanılabilir.
container_name: db-seeder-container
depends_on
docker-compose up
birden fazla Service tanımının olduğu Compose dosyalarında servisleri birbirlerine olan bağımlılıklarına göre sırayla başlatır. Eğer Service’ler arasında bir ilişki tanımı yoksa tanım sırasına göre başlatır. Servislerin birbirleri ile olan ilişkileri depends_on
opsiyonu ile belirlenir. docker-compose up
‘a parametre ile bir servis ismi verildiğinde Compose öncelikle ilgili Service’in bağımlı olduğu Service’leri ve sonra ilgili Service’i başlatır. nginx-service servisinin ruby-service’e ruby-service’in de redis-service’e bağımlı olduğunu düşünelim. Bu durumda aşağıdaki gibi bir kullanım uygun olur.
version: '2'
services:
nginx-service:
build: WebSite
depends_on:
- ruby-service
ruby-service:
build: WebApp
depends_on:
- redis-service
redis-service:
image: redis
Başlangıçta bu opsiyon hep yanlış anlaşıldığı için ve muhtemelen siz de yanlış anlayacağınız için bu yanlış anlaşılmayı en baştan düzeltelim isterseniz. depends_on
Service’leri başlatırken Service’lerin çalışmaya hazır hale gelip gelmediğini beklememektedir. Yukarıdaki örnekte, ruby-service
‘in redis-service
‘e bağımlılığı vardır. Burada redis-service
Container’ı başlatıldıktan hemen sonra ruby-service
Container’ı başlatılacaktır. Eğer Ruby uygulaması ilk açılışta Redis sunucunun hazır olup olmadığını kontrol ediyorsa ve hazır olmadığında hata veriyorsa Compose ile başlatıldıktan sonra muhtemelen hata verecektir çünkü Redis Container’ı ile Ruby App’inin Container’ı ile neredeyse aynı anlarda başlatılmış olmaktadır, Ruby App’inin Redis’in ayağa kalkması beklenmemektedir. Ruby App’inin Redis’in ayağa kalkmasının beklenmesi için bazı 3rd party mekanizmalar vardır ve bu hazırlayacağımız başka bir blog’un konusunu olacaktır fakat açık bir şekilde depends_on
bu işe yaramamaktadır.
Bu durumda depends_on
ne işe yaramaktır sorusunun cevabını yukarıdaki örneğe göre tekrar verelim. docker-compose up ruby-service
komutu verilerek ruby-service
ve bağımlılıklarının kaldırılması istendiğinde eğer depends_on
opsiyonu ile nginx-service
‘e olan bağımlılık belirlenmeseydi bu servis başlatılmamış olacaktı.
dns
Service’ten oluşturulacak Container’lar tarafından kullanılacak DNS sunucu veya sunucuları bu komut ile değiştirilebilir. Aşağıdaki gibi kullanılır.
dns: 8.8.8.8
veya
dns:
- 8.8.8.8
- 8.8.4.4
environment
Service’ten oluşturulacak Container’lara yeni Environment Variable’lar bu opsiyon ile eklenebilir. Aşağıdaki gibi kullanılır.
environment:
- MODE=PROD
- DEBUG=true
- PASSWORD=secret
expose
Host’a açmadan Container’lar arasında port’ları açmak için kullanılır. Dockerfile’daki EXPOSE
komutu ile aynı işe yarar.
expose:
- "5432"
extra_hosts
DNS sunucularda tanımlı olmayan fakat Container içerisiden isimleri ile erişilebilmesini istediğimiz IP’lerin Container’lardaki /etc/hosts
dosyasına yazılması için kullanılan bir opsiyondur. Aşağıdaki gibi kullanılır.
extra_hosts:
- "local.server:192.168.0.22"
image
Service’ten oluşturulacak Container’ların başlatılacağı Image’ı belirler. Eğer Service tanımında build
opsiyonu varsa yani Container’lar hazır bir Image’dan yaratılmak yerine Dockerfile
ile build edilecek bir Image’dan yaratılacaksa bu opsiyonda verilen isim oluşturulacak Image’a verilir. Aşağıdaki gibi kullanıldığında DockerHub’dan çekilecek Image’ın ismini belirler.
image: nginx
Aşağıdaki gibi kullanıldığında ise MyWebApp Image’ı build edilerek gsengun/mywebapp:1.0 olarak tag’lenir.
image: gsengun/mywebapp:1.0
build:
context: ./MyWebApp
dockerfile: Dockerfile-MyWebApp
networks
Service’ten oluşturulacak Container’ların dahil olacağı Network’leri (Network konfigürasyonu bölümünde detaylı olarak inceleyeceğiz) belirler. Aşağıdaki gibi kullanılır.
my-service:
networks:
- nosql-network
ports
Service’ten oluşturulacak Container’lardan Host’a map’lenecek (forward edilecek) portları belirler. En çok kullanılan opsiyonlardan biridir. Aşağıdaki örnekte ilk satırda Host’taki 5432 portu Container’daki aynı port’a map’lenmektedir. İkinci satırda ise 16000 ile 17000 arasındaki bütün port’lar Host’tan Container’e doğru forward edilmektedir.
ports:
- "5432:5432"
- "16000-17000:16000-17000"
volumes
volumes
opsiyonu üç farklı şekilde kullanılır. Aşağıda bu üç kullanım şekli verilmiş ve örneklenmiştir.
-
Host üzerindeki bir klasörün Container’a mount edilmesi:
Aşağıda Host üzerindeki
/Users/gsengun/Desktop/App
klasörü Container üzerindeki /var/lib/app klasörüne mount edilmiştir.services: my-service: volumes: - /Users/gsengun/Desktop/App:/var/lib/app
Aşağıda Host dosyasında Dockerfile’a göre relatif olan
./App
klasörü Container üzerindeki /var/lib/app klasörüne mount edilmiştir.services: my-service: volumes: - ./App:/var/lib/app
-
Container üzerinde bir volume yaratılması:
Container durdurulduktan sonra ve hatta eğer kasıtlı olarak silinmez ise sistemden kaldırıldıktan sonra bile ulaşılabilecek bir Volume yaratmak için opsiyon aşağıdaki gibi kullanılabilir.
services: my-service: volumes: - /var/lib/app
-
Volumes konfigürasyonunda tanımlanan bir Volume’un Container tarafında görünür hale gelebilmesi:
Compose, tanımlanan bütün servisler’den oluşturulan Container’lar tarafından erişilebilecek ve yaşam döngüsü Container’larınkinden bağımsız olarak yönetilebilecek bir Volume (
Named Volumes
) tanımlanmasına olanak tanımaktadır. Bu şekilde oluşturulan Volume’un nasıl mount edileceği aşağıda örneklenmiştir.services: my-service: volumes: - my-volume:/var/lib/app volumes: my-volume: driver: local
Volume Konfigürasyonu
Volume’ları Container bazlı olarak tanımlayabileceğimizi zaten biliyoruz. Compose dosyası içinde bulunan bütün servisler tarafından erişilebilir durumda olan ve ayrıca Docker CLI tarafından sağlanan docker volume
komutu ile incelenebilecek Volume’lar Named Volumes
olarak adlandırılmaktadır.
İnce işler yapmak için detaylı konfigürasyon yapılabilmekle birlikte bizim kullanacağımız çerçeve aşağıdaki gibidir.
volumes:
my-named-volume:
driver: local
Network Konfigürasyonu
Compose kompleks sistemlerde kullanılmak üzere kapsamlı Network konfigürasyon opsiyonları sağlamaktadır fakat bizim ihtiyacımız olan çerçevede bu detaya ihtiyaç bulunmadığı için burada bunlara yer vermeyeceğiz.
Docker Compose ile Oluşturulmuş Karmaşık Bir Sistemi İncelemek
Uygulama Tanıtımı
Bütün bu öğrendiklerimizi yapısı karmaşık (dolayısıyla Compose’un birçok özelliğini görebileceğimiz) ancak fonksiyonalitesi sade (dolayısıyla uygulamanın mantığından çok Compose’un mantığına odaklanabileceğimiz) bir uygulamada test ederek öğrendiklerimizi iyice pekiştirelim.
Bu uygulamanın orjinal hali bu linkten, bu blog için Github’dan fork edilen hali de bu linkten indirilebilir. Uygulamayı bir klasöre indirdikten sonra ilgili klasöre giderek docker-compose up
komutunu çalıştırırsanız uygulamayı kendi bilgisayarınızdan da test edebilirsiniz. Sanırım bu kolaylık Docker Compose’u tek cümlede açıklamaya yetti :)
Örnek uygulama olarak Docker ile ilgili birçok demoda kullanılan Voting App
‘i kullanacağız. Uygulamanın sunduğu fonksiyon, bir anketle kullanıcılara kedileri mi yoksa köpekleri mi daha çok sevdiklerini sormak ve bütün kullanıcılardan gelen cevapları kümüle bir şekilde yüzde olarak göstermektir.
Uygulamanın kullanım şekli basitçe aşağıdaki hareketli resimde gösterilmiştir.
Uygulama yukarıda verilen bu basit fonksiyon için temel olarak 5 farklı servis kullanıyor böylece Docker Compose’un gücünü uygulama mantığını işin işine çok fazla sokmadan güzel bir şekilde örnekliyor. Aşağıdaki şekilde ve sonrasındaki açıklamalarda bu uygulamanın mimari yapısı özetlenmiştir.
voting-app
adlı servis Python web uygulaması kullanıcılardan kedi veya köpekleri seçebilmesini sağlamaktadır.voting-app
servisi 5000 numaralı port’tan hizmet vermektedir.redis
adlı servis klasik Redis memory cache uygulaması ile yeni oyları dağıtık memory’e kaydetmektedir.worker
adlı servis bir .NET uygulamasıdır ve Redis’ten okuduğu yeni oyları alarakdb
servisine kaydetmektedir.db
adlı servis bir Postgres veri tabanını kullanıma sunmaktadır.result-app
adlı servis bir Node.js web uygulaması sunarak kullanıcılardan gelen oylardan oluşan anket sonucunu ekranda göstermektedir.result-app
servisi 5001 numaralı port’tan hizmet vermektedir.
docker-compose.yml Dosyasının İncelenmesi
Şimdi fazla detaya girmeden uygulamanın docker-compose.yml
dosyasını ve Service’leri oluşturan Image’ların nasıl oluşturulduğuna göz atacağız. İsterseniz siz de bu linkten uygulamanın kodunu indirerek kodları kendi bilgisayarınızdan inceleyebilirsiniz.
docker-compose.yml
dosyasının içeriği aşağıda verilmiştir. Şimdi burada tanımlanan servislerin üzerinden teker teker geçelim.
version: "2"
services:
voting-app:
build: ./vote
command: python app.py
volumes:
- ./vote:/app
ports:
- "5000:80"
redis:
image: redis:alpine
ports: ["6379"]
worker:
build: ./worker
db:
image: postgres:9.4
result-app:
build: ./result
command: nodemon --debug server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
voting-app Service’i
Bu servis aşağıdaki gibi tanımlanmıştır.
voting-app:
build: ./vote
command: python app.py
volumes:
- ./vote:/app
ports:
- "5000:80"
build: ./vote
ile docker-compose.yml
dosyasının bulunduğu klasöre göre relatif olarak vote
klasöründe bulunan Dockerfile
‘a göre Image’ın build edilmesi istenmiştir. İsterseniz burada bulunan Dockerfile
‘ı bu blog serisinin ikinci blog‘unda öğrendiğiniz bilgiler çerçevesinde inceleyebilirsiniz. command: python app.py
ile Image build edildikten sonra Container oluşturulurken Image tarafından verilen default CMD’nin ezilmesi ve python app.py
komutunun verilmesi konfigüre edilmiştir.
volumes:
düğümünde verilen - ./vote:/app
Host dosya sistemindeki ./vote
klasörünün, Service’ten oluşturulacak Container’ların /app
klasörüne Mount edilmesi istenmiştir. ports:
düğümünde verilen - "5000:80"
ile de Host üzerindeki 5000 port’una gelen isteklerin Container’ın 80 numaralı port’una yönlendirilmesi konfigüre edilmiştir.
redis Service’i
Bu servis aşağıdaki gibi tanımlanmıştır.
redis:
image: redis:alpine
ports: ["6379"]
image: redis:alpine
Service tanımından yola çıkılarak oluşturulan Container’ların baz alacağı Image’ın redis:alphine
olduğunu tanımlamıştır. İkinci satırdaki ports: ["6379"]
ile Container içerisindeki 6379 port’u (Redis’in default portu) rastgele bir Host portu üzerinden Host’a açılmıştır. Bu konfigürasyon olası problemlerde Container’a Host üzerinden erişebilmek ve Redis’i kontrol edebilmek amacı ile yapılan bir harekettir. Host dosyasında hangi port’a map’lendiğini öğrenmek için docker-compose ps
veya docker-compose ps redis
komutlarını koşturabilirsiniz. Bu komutların çıktısından görebileceğimiz gibi Container içerisindeki 6379 portu Host üzerinde 32772 port’una map’lenmiştir. Host üzerinden bu port’u kullanarak Redis’e ulaşabiliriz.
Gokhans-MacBook-Pro:DCVoteApp gsengun$ docker-compose ps redis
Name Command State Ports
------------------------------------------------------------------------------------
dcvoteapp_redis_1 docker-entrypoint.sh redis ... Up 0.0.0.0:32772->6379/tcp
worker Service’i
Bu servis aşağıdaki gibi basit bir şekilde tanımlanmıştır.
worker:
build: ./worker
Burada Compose’a docker-compose.yml
dosyasına göre relatif olarak worker
klasörüne gitmesi ve oradaki Dockerfile’ı build etmesi sonra da oluşan Image’ı bu servis için yeni bir Container yaratırken kullanması salık verilmiştir. Yine bu klasördeki Dockerfile’ı ikinci blog‘unda öğrendiğiniz bilgiler çerçevesinde inceleyebilirsiniz.
db Service’i
Bu servis aşağıdaki gibi çok basit bir şekilde tanımlanmıştır ve bu servis tanımından oluşturulacak Container’ların direkt olarak postgres:9.4
Image’ından oluşturulabileceğini konfigüre etmiştir.
db:
image: postgres:9.4
result-app Service’i
Bu servisin tanımı aşağıda verilmiştir. Şimdi teker teker ilgili satırları inceleyelim.
result-app:
build: ./result
command: nodemon --debug server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
Önceki servislerde de tekrar ettiğimiz gibi build: ./result
satırı docker-compose.yml
dosyasına göre relatif olarak ./result
klasöründe bulunan Dockerfile
‘ın build edilerek bu Service için oluşturulacak Container’larda kullanılması salık verilmiştir. command: nodemon --debug server.js
satırı ile kullanılacak Image’ın default CMD
‘si ezilerek ilgili komutun koşturulması sağlanmıştır.
voting-app
servisine benzer şekilde volumes:
düğümünde verilen - ./result:/app
Host dosya sistemindeki ./result
klasörünün, Service’ten oluşturulacak Container’ların /app
klasörüne Mount edilmesi istenmiştir. ports:
düğümünde verilen - "5001:80"
ile de Host üzerindeki 5001 port’una gelen isteklerin Container’ın 80 numaralı port’una yönlendirilmesi - "5858:5858"
ile de Host üzerindeki 5858 Container’ın aynı port’una yönlendirilmesi konfigüre edilmiştir. 5001 port’u uygulamanın sunulabilmesi için 5858 portu ise Node.js uygulamasının debug edilebilmesi için Host’a sunulmuştur.
Sonuç
Bu blog’da Docker’ı günlük hayat kullanımında çok daha etkili kılan Docker Compose’u çok detaylı bir biçimde inceleyerek Docker’la ilgili üç blog’luk blog serimizi tamamlamış olduk.
Bundan sonraki blog yazılarındaki bütün örnek uygulamaları (demo’ları) Docker kullanarak vereceğiz ve bu blog serisinde öğrendiğimiz bilgiler emin olun çok işimize yarayacak.
Teşekkür
Bu blog yazısını gözden geçiren ve düzeltmelerini yapan Burak Sarp’a (@Sarp_burak) teşekkür ederiz.