Bu doküman kablosuzkedi Youtube Kanalı için hazırlanmıştır. Bu içerikte bulunan konular ve daha fazlası için video serisine de göz atabilirsiniz.
Docker Nedir Nasıl Kullanılır? | Part #1 | Image Nedir? Container Nedir? Docker Komutları
Docker Network Türleri | Dockerfile ile Image Nasıl Üretilir | Part #2
Docker Compose ile Servis Yönetimi Part #3 | Twitter, Asana, Wordpress, MongoDB NodeJS Uygulamaları
Linux'a Docker Kurulumu | Production | Nginx | Reverse Proxy ile 2 Domain 1 Host Yönetimi | Part #4
Klasik Yöntem ile Yazılımcılar geliştirmiş oldukları yazılımı Production ortamına almak için DevOps ekibiyle görüşür ve dosyaları iletir. Bu dosyalar yazılım ve environment'in nasıl ayarlanacağına ait bilgileri de içerir. Devops yazılımı kendisi yazmadığı için doğal olarak bunun sıkıntısını çok fazla yaşarlar ve uygulamayı başarılı bir şekilde aynı development ve test envlerinde olduğu gibi çalıştırmak için uğraşırlar.
Docker ile Yazılımcılar bu uygulamalarını Docker Image haline getirip DevOps takımıyla paylaşırlar ve Devops'un yapacağı tek işlem bu image'i;
docker run imageName
ile container haline getirmek olacaktır. Bu da tamı tamına yazılımcının istediği envye sahip tüm ayarlar yapılmış halde uygulamanın çalışabileceği en iyi ortamı kurmak demektir.
Tüm bu uygulamalar OS ile uyumlu olacak şekilde çalışır. Bizim bu uyumu OS sürümüne göre her bir servis için ayrı ayrı sağlamamız gerekir.
İşte burada problem ortaya çıkmaya başlıyor.
- Peki gerçekten bu uyum her bir servis için geçerli olabilir mi? Bazen mümkün bazen mümkün olmayabilir.
- Zamanla bu servisler güncellenir ya da servisler içerisinde kullanılan bağımlılıklar kullanmış olduğumuz servisler ile uyumlu olmayabilir.Tabi ki yapabileceğimiz en mantıklı çözüm bu servis için uygun bir işletim sistemini bulmak olacaktır. Peki bulacağımız uygun işletim sistemi diğer servisler için uyumlu olacak mıdır?
- Diğer problem ise bu bağımlılıkların birbirlerini etkilemesi.
- Uygulamanın çalışacağı ortamı, sürece dahil olan tüm ekibin sahip olması beklenemez (Development / Test / Production)
Doğal olarak bu şekilde uygulama geliştirmek bunları yönetmek ve sunucular arasında taşımak oldukça zor bir durumdur.
Tam olarak burada bu servisler arasında iletişimi en iyi şekilde yapacak ve birbirlerinden etkilenmeden bunu başarılı bir şekilde yönetebilecek bir araca ihtiyacımız oluyor.
Burada Docker devreye giriyor. Docker'ın yapmış olduğu işlem her bir servisi aynı işletim sistemi üzerinde kendilerine ait bir dünyada çalışmasını sağlayarak kendilerine ait kütüphaneleri ve bağımlılıkları olmasını sağlıyor.(Elbette çok daha fazlasını da bize sağlıyor fakat şimdilik burada kalalım :))
Böylece herhangi bir developer kendi uygulamasının çalışması için gereken Docker konfigürasyon dosyasını oluşturduktan sonra, bu servisin ayağa kalması/çalışması için docker run
demesi yetiyor. Bu sadece geliştirici için değil bu sürece dahil olan herkes için geçerli oluyor. Tek komut aynı environment (ortam).
Docker bunu yaparken container yapısından yararlanıyor. Peki Container Nedir?
Container, kendilerine ait prosesleri, servisleri, networkleri bulunan tamamen izole edilmiş ortamlardır (environment). Tıpkı VM gibi fakat her bir VM kendisine ait bir OS barındırırken her bir Container OS kernel'i paylaşmaktadır.
Bu ifade size oldukça devrimsel ve Docker ile yeni ortaya atılmış bir fikir gibi gelebilir fakat tam olarak öyle değil :) Container yaklaşık 10 yıldan beri kullanılmaktadır. Container'ların bir çok türü bulunmaktadır. Linux tarafında bunlardan bazıları;
- LXC
- LXD
- LXCFS
docker Linux'de bu container türlerinden LXC container türünü kullanmaktadır. Windows'da ise bu iş için kullanılan adı Windows Server Container Support*'dur. İşte bu container'ları yönetmek oldukça zor ve low level bir işlem olduğundan dolayı docker bize bu işleri kolaylaştırmak için high level birçok araç sunuyor. Böylece kullanıcılar container'ları istedikleri gibi kolaylıkla yönetebiliyor.
Docker'ın nasıl çalıştığını anlamak istiyorsanız ilk olarak İşletim sisteminin nasıl çalıştığını biraz anlamanız gerekmektedir. Örnek vermek gerekirse Linux İşletim sistemini ele alalım.
Tüm Linux tabanlı işletim sistemlerine bakacak olursanız bu işleletim sistemleri temelde 2 farklı bileşeni içerisinde barındırır.
- OS Kernel
- Bir çok yazılım seti
OS Kernel donanim ile etikleşimden sorumludur. OS Kernel aynı kalır. Fakat üzerindeki yazılım setleri işletim sistemleri arasındaki farkları belirler. Bundan dolayı sürekli bir Linux dağıtımı gibi cümleler duyarsınız. Bu yazılım setleri bir çok farklılıklar içerebilir. Grafik arayüzleri, sürücüler, geliştiriciler için komut setleri ve çok daha fazlası.
Yukarıda söylediğim gibi Docker Container'ları ortak OS Kernel kullanır. Peki bu gerçekten ne anlama gelir?
Diyelim ki siz Linux üzerine Docker yüklediniz. Docker aynı kernele sahip herhangi bir linux sürümünü container olarak çalıştırabilir. Bu durumda kernel ortak olarak paylaşıldığında yazılım seti container içerisinde kullandığımız linux sürümünden gelir.
Eğer siz Linux OS üzerine yüklemiş olduğunuz bir Docker ile Windows işletim sistemine sahip bir contianer çalıştırmak isterseniz bunu yapamazsınız. Çünkü container'ların ortak bir kernel paylaştığını söylemiştik. Doğal olarak Linux Kerneli ile Windows'un kerneli farklı olduğu için bu container'ı çalıştıramazsınız.
O zaman Windows üzerine Docker yükleyebilirim ve Docker üzerinden Windows base container'lar çalıştırabilirim diye düşünebilirsiniz. Fakat bu da mümkün değil.
Buradaki durum sizi aldatmasın çünkü windows işletim sistemine Docker yükleyebiliyorsunuz ve linux base bir container çalıştırmış da olabilirsiniz fakat burada çok önemli bir nokta Docker Host tarafından bizim bilmemize ihtiyacımız olmadan yapılıyor.
Siz Windows üzerine docker yüklediğinizde Docker kendisi Linux bir VM üzerinden tüm yönetimini yapmaktadır. Doğal olarak siz yine Linux üzerinde Docker host çalıştırmış oluyorsunuz. Doğal olarak ortak kernel paylaşımı yine linux üzerinden olmaktadır. Bundan dolayı Windows işletim sistemi üzerinden yönettiğiniz docker host ile yine windows base bir container çalıştıramazsınız.
Bunu yapabilmek için Windows Server üzerine kurmuş olduğunuz Docker üzerinden windows base bir container çalıştırabilirsiniz.
-
VM Hypervisor üzerinde her bir VM container'ını yönetir. Fakat bu her bir VM kendine ait bir işletim sistemine sahiptir.
-
Docker container'ları ise sadece bizim application dosyalarımız, kütüphaneler ve bağımlılıkları içerir. Sadece tek bir işletim sistemi olduğu için bu utilization adına oldukça performanslı bir çözümdür.
-
VM içerisinde İşletim sistemi barındırdığı için doğal olarak Docker'a göre daha fazla alan kaplar.
-
Uygulamayı ayağa kaldırmak istediğinizde VM OS içerdiğinden ilk olarak OS'in açılmasını beklemek gerekir. Docker'da ise direk uygulamanın gereksinimleri doğrultusunda ayağa kalkar. Yani çok daha hızlı boot süresine sahiptir.
-
Sanal Makineler kendilerine ait OS'e sahip olduğu için ve bunlar arasındaki iletişim Hypervisor tarafından yapıldığı için doğal olarak bir sunucu üzerinde farklı işletim sistemlerine sahip VM'ler üretebilirsiniz. Fakat docker aynı OS üzerinde çalışan yapılar olduğu için bu mümkün değildir.
İkisinin de kendisine göre avantaj ve dezavantajları bulunmaktadır. Fakat genel olarak Docker bu konuda bayağı bir önde.
Aynı zamanda bu iki teknolojiyi beraber kullanarak da oldukça güçlü alt yapılar inşaa edebiliriz. 1 hypervisor üzerinden farklı VM'ler ve onların içerisinde Docker üzerinden yönetilen container'lar şeklinde.
Nasıl yapılacağını öğrenmek için ilk olarak Image konusunun ne olduğunu bir açıklağa kavuşturmamız gerekiyor.
- İçerisinde bir çok farklı yapıyı barındıran yapılardır. (OS, Application, ve daha fazlası)
- Template, plan, package
- Akılda kalması için;
- Form tabanlı uygulamaların (WinForms vs) projesi image olarak düşünülebilir
- Eskilere gidecek olursak Ahead Nero ile ne image’ler ürettik :)
- Peki bu image’ler nerede?
- Docker Hub (Docker’un public ve private olarak bize sunduğu resmî repository hesabı)
- Image’i çalıştırdığımızda elde ettiğimiz proses olarak düşünülebilir
- Akılda kalması için;
- Form tabanlı uygulamanın, çalıştırılmış hali olarak düşünülebilir
- Proje > exe
- Form tabanlı uygulamanın, çalıştırılmış hali olarak düşünülebilir
Docker'ı bilgisayarımıza kurduk. Peki Docker ile container'ları nasıl kullanacağız? Bunun için ilk olarak ihtiyacımız container'ımızın base yani temelini oluşturacak bir image elde etmek. Peki bu image'ı nereden alacağız?
Bir çok şirket kendisine ait bir çok uygulamayı containerized edip DockerHub üzerinden bunu public ya da private olarak sunuyor. Siz bunlardan birini kullanmak istediğinizde bunun sadece
docker run imageName
şeklinde yazarak kendi ortamınıza bir kopyasını (instance) alabiliyorsunuz. Mesela;
docker run nodejs
docker run redis
docker mongodb
gibi.. farklı farklı instance'ları kendi ortamınıza rahatlıkla çekebilirsiniz.
Docker Image sizin projeniz gibi düşünebilirsiniz. Projelerinizin dosyaları tüm ayarlar yani bir paket, template, plan gibi.
Docker Container ise bu template'in çalıştırılmış hali bir instance'ı gibi. Bir Docker Image'den birden fazla Docker Container çalıştırabilirsiniz.
Ayrıca siz de kendinize ait image dosyalarınızı üretebilir ve bunu Docker Hub Repository'e gönderebilirsiniz. Böylece Public ya da Private olarak diğer geliştiriciler bundan yararlanabilirler.
## Docker Sürümleri
- Comminity Edition
- Ücretsiz sürüm ve belirli ücretsiz yönetim sistemleri mevcut. Community edition linux, mac, windows, cloud olarak erişebiliyor. Eğer Mac ve Windows sahibiyseniz bunun için Docker Desktop yüklemeniz ya da Virtualization özelliği bulunan Linux VM yüklemeniz gerekebilir.
- Enterprise Edition
- Ücretli
docker run
run komutu bir image'ı çalıştırmayı sağlar. Örneğin;
docker run node
dediğimizde node isimli container eğer bizim docker host'umuzda varsa çalıştırılır. Eğer yoksa Docker Hub Repository üzerinden bulunur indirilir ve çalıştırılır. Bir sonraki docker run node komutuyla beraber docker host üzerinden çalışmaya başlar.
docker ps
ps komutu çalışan tüm containerların listesini bilgileriyle beraber döker.
her bir container random isim ve id bilgisi alır. Eğer çalışan ya da çalışmayan tüm container'ların listesini görmek istersek. Bu durumda -a parametresini göndermemiz gerekir.
docker ps -a
aynı işlemi
docker container ls
docker container ls -a
ile de görebiliriz.
bu bize çalışan ve daha önce çalışmış ama durmuş tüm container'ların listesini verecektir.
docker stop containerName|containerID
çalışan bir container'ı durdurmak için kullanılır containerName ya da containerID bilgisini vermemiz yeterlidir.
Eğer çalışmayan docker container'larının boşuna yer kaplamasını istemiyorsak;
docker rm containerName|containerID
kullanabiliriz. Bu durumda sildiğimiz container'ları docker ps -a yaptığımızda listede görmeyeceğiz!
Aktif ve pasif olan tüm container'ları silmek için
docker container rm $(docker container ls -aq)
komutunu kullanabiliriz.
Peki indirdiğimiz ama kullanmadığımız image listesini nasıl görebiliriz? ya da image listesini nasıl görebiliriz?
docker images
bize docker hosts üzerinde bulunan tüm image listesini detayları ile getirir.
peki bu image listesinden bir image silmek istersek ne yapabiliriz?
docker rmi imageName|imageID
image'i silecektir.
Önemli Not Bir image'i silmek için, bu image'i kullanan herhangi bir container olmaması gerekir. Bundan dolayı ilk olarak bu image'e bağlı tüm container'ları silip sonrasında bu komutu çalıştırmalısınız.
Daha önceden docker run nginx ile beraber image'i bilgisayarımıza indirmiş ve bu sırada ubuntu image'ini yüklemesini beklemiştik. Tabi ki indirme ve çalıştırma esnasında bir bekleme süresi geçti. Bunu yapmak istemiyorsak? Yani sadece image'i indirip bırakmak istiyorsak bu durumda.
docker pull imageName (nginx)
diyebiliriz. Bu durumda sadece indirip bırakır. Image'i çalıştırmaz!
Normalde bir bir docker image run ettiğimizde bu container ile ilgili bilgileri ya da Logları ekrana basabilir. Burada container'ın gereksinimlerinden dolayı çalıştırdığımız terminal üzerinde bir çok loga rastlayabiliriz. Bu modun adına attach mode denilir. İstersek bunu arkaplanda yapabiliriz. Yani bir uygulamayı arkaplanda çalışmasını sağlayabilir ve bu bizim terminal üzerinde herhangi bir log görmemize engel olur bu modun adına da detach mode denilir. Bunu yapmak için;
docker run -d imageName
bu bize ayağa kaldırdığı container'in ID bilgisini verecektir.
Eğer detach yapılmış bir container'ı attach moda geri sokmak istersek bunu için attach komutunu kullanabilirz.
docker attach containerID
Not:
docker komutu ile tanımladığınız herhangi bir komutta eğer ID bilgisine ihtiyaç duyuyorsanız bu ID bilgisinin ilk bir kaç karakterini tanımlamanız yeterli. Yukarıdaki örnek üzerinden gidecek olursak;
docker attach a043d
yazdığımızda bu bizim için detach moda sokulan container'ın ID bilgisine eşittir.
docker run -d --name webapp nginx:1.14-alpine
## Docker Run Komutu ### run -tag Bir image pull ettiğimizde bu image belirli bir sürüme sahip olur. Bu sürümün adına tag denilir. Son sürüm indirildiğinde bu tag latest olacaktır.
### run -stdin (-it/interactive terminal)
Örneğin terminal üzerinde kullanıcıdan bir bilgi alan uygulamanız var ve bu uygulamayı dockerize ettiniz. Bu uygulamanın image adı testApp olsun;
docker run testApp
dediğinizde, çalışan uygulama sizden herhangi bir bilgi almadan sonlanacaktır. Çünkü Docker prompt default olarak kullanıcıdan bilgi almamaya programlıdır. Fakat bunu değiştirebiliriz.
docker run -i testApp
-it argümanını göndererek run ettiğiniz image'de bir user prompt varsa buna izin verir. -i = interactive terminal demektir.
### run -port mapping
Bir container ayaga kaldırdığımızda bu image bir porta sahip olur. Biz bu ayağa kaldırdığımız uygulamaya yani container'a dışarıdan erişmek istediğimizde bu portu kullanarak erişemeyiz. Bunun yerine port mapping yapmalıyız. Bunu yapmak oldukça basit.
docker run -p DIS_PORT:IC_PORT imageName
şeklinde düşünülebilir. yani
docker run -p 80:5000 webApp
şeklinde bir komut ile çalıştırdığımızda artık biz uygulamamıza (yani docker host/engine üzerindeki container'a) 80 portu üzerinden erişebiliriz. Fakat Docker Engine/Host içerisinde uygulamamız çalışırken 5000 üzerinden çalışmaya devam edecektir. Böylece portları maplemiş oluyoruz.
### run -volume mapping
Docker container içerisinde veriler herhangi bir şekilde kalıcı olamaz. Bundan dolayı dockerize ettiğiniz uygulamanız dosya sistemi üzerinde eğer veri saklamaya ihtiyaç duyuyorsa ya da container içerisinde veri tabanı barındıran bir sistem de olabilir (mysql, mongodb vs.). Bu durumda volume kullanarak persistency sağlıyoruz yani kalıcılık. Bunun için container dışında bir location seçerek run komutu ile beraber volume mapping yapıyoruz.
Bunu yapabilmek için;
docker run -v /opt/datadir:/var/lib/mysql mysql
dediğimizde mySQL'in kayıtları sakladığı dizin olan /var/lib/mysql'deki verileri docker host içerisinde /opt/datadir klasöründe tutacaktır. Bu /opt/datadir klasörü docker host/engine tarafından izin verilen klasörlerden olmalıdır. Verilen data klasörü Container silinse bile bilgiler docker engine üzerinde kalmaya devam eder.
### inspect
Docker ps komutu contariner'lar hakkinda birçok bilgiyi bizimle paylaşır. Fakat daha fazla bilgiye ihtiyaciniz olursa inspect komutu bu bilgileri bize sağlar.
docker inspect containerName
### Container logs Özellikle -d dettach mod ile çalışan bir container'a ait logları görmek için logs komutunu kullanabilirsiniz.
docker logs containerName
Bir image'e tag vermek istiyorsak bu oldukça kolay.
docker image tag imageID/imageName tagName
### Docker Images Kendimize ait bir docker image yaratmak istersek bunu yapabildiğimiz yollardan bir tanesi Dockerfile oluşturmak. Dockerfile docker tarafından bilinen image yaratırken bizim image'imizin içerisinde bulunmasını istediğimiz tüm yapıları barındıran bir dosyadır.
Genel olarak Dockerfile içerisinde yazdığımız komuların yapısı şu şekildedir.
KOMUT ARGUMAN
KOMUT docker tarafından bilinen Komut setlerinden bir tanesi olmalıdır. ARGUMAN ise, KOMUT un yapacağı işlevi belirtir. Örnek olarak bir Docker file dosyasına bakalım.
FROM ubuntu:18.04
RUN apt-get update
# RUN DEBIAN_FRONTEND="noninteractive" apt-get -y install tzdata
RUN apt-get install curl -y
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install nodejs -y
COPY . /opt/node-server/
WORKDIR /opt/node-server
RUN npm install
CMD ["node", "app.js"]
Bu dosyadaki komutlardan FROM, RUN, COPY, WORKDIR, CMD komutları docker engine'ine ne yapması gerektiğini söyler. Buradaki en önemli durum ise Her Dockerfile bir FROM komutu ile başlamalıdır.
FROM komutu o image'in hangi Image üzerinden Base alınacağını söyler. Yani Her bir image başka bir BaseImage üzerinden türetilir. Örneğimizdeki image ise ubuntu image'inin 18.04 isimli tag'i üzerinden türetilmiştir.
Dockerfile üzerinden bir image üretmek için;
docker build Dockerfile
ya da aynı dizindeysek
docker build .
komutu kullanılabilir. Docker Engine hemen Dockerfile üzerindeki direktifleri yapmaya başlayacaktır. Bu sırada Dockerfile içerisindeki her bir satır ayrı ayrı işleme tâbi tutulur. Bu yapının adına Layered denilir.
Dockerfile üzerindeki her bir satır Docker Host üzerinde Layer by layer çalışmaktadır. Yani
Layer 1
FROM ubuntu:18.04
Layer 2
RUN apt-get update
Layer 3
RUN apt-get install curl -y
Layer 4
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
Layer 5
RUN apt-get install nodejs -y
Layer 6
COPY . /opt/node-server/
Layer 7
WORKDIR /opt/node-server
Layer 8
RUN npm install
Layer 9
CMD ["node", "app.js"]
şeklinde 9 katmandan oluşan yapı şeklinde oluşturulur. Eğer herhangi bir step üzerinde hata alırsak, bundan önceki Layer'lar otomatik olarak geçilip hatanın bulunduğu Step üzerinden build işlemine devam edecektir.
Eğer buradaki Layer'lardan herhangi birine sahip başka bir image daha build edecek olursak Docker Engine o layer'ı build etmez onun yerine build edilmiş olanı tekrardan kullanır.
### Build Image With Tag Name (Bir image'e build anında isim vermek) Eğer bir image'e build edilirken bir isim (tag) vermek istiyorsak -t opsiyonu kullanılabilir. Tam olarak kullanımı ise şu şekilde.
docker build . -t tagName
#### Peki Dockerfile içerisindeki yapıları neye göre oluşturuyoruz?
Bu aslında tahmininizden çok kolay. Eğer docker komut setini aklınızda tutamıyorsanız ya da tam olarak nerede ne yapacağınızı bilmiyorsanız bunu herhangi OS üzerinde nasıl adım adım yapıyorsunuz ilk olarak onu düşünün. Daha sonrasında yaptığınız tüm adımları bir kenara not alın ve başlarına Docker Komut setlerini yerleştirin.
- Mesela sizin uygulamanız nerede çalışacak?
- Ubuntu üzerinde
- İlk başta İşletim sisteminin update edilmesi gerekir
apt-get update
komutu çalıştırılır
- Ardından Node.js'i indirmek için Curl'e ihtiyacımız olur
apt-get install curl -y
- Sonrasında curl vasıtasıyla Node.js indirilir
curl -sL https://deb.nodesource.com/setup_10.x | bash
- Node.js Yüklenir
apt-get install nodejs -y
- Kaynak dosyalarınızı bir klasör altında oluşturursunuz veya buraya kopyalarsınız
- Bağımlılıkları yüklersiniz
- Uygulamayı çalıştırırsınız
İşte bunlar sizin kendi projenizi çalıştırmak için izlediğiniz bir yol. Bunu Docker'ın da yapabilmesi için bu adımları docker'a söylemeniz gerekir. Buradaki en önemli işlem burada işletim sisteminin yaptığı işleri RUN komutuyla yaptırmanız ve sizin manuel olarak yaptığınız işlemleri ise diğer komutlarla yapmanız. Mesela apt-get update
işlemi işletim sistemi bazında olacak olan bir durum. Bunun için buna RUN apt-get update
diyoruz. Fakat klasörlerinizin oluşturulması veya bir yerden dosyalarınızın kopyalanması ise sizin manuel olarak yaptığınız işlemler. Bundan dolayı bunu COPY komutuyla yapıyoruz. WORKDIR ile altındaki satırların çalışacağı klasörü belirterek yine RUN ile bağımlılıkları yüklüyoruz. Son olarak da uygulamanın image oluşturulduktan sonra container olarak ayağa kaldırılması durumunda çalıştırılacak komutu belirtiyoruz. Yani sizin uygulamanızın çalışması için gereken komut neyse onu belirtiyoruz. Bu da CMD komutu ile yapılabilir.
Bir çok uygulama deploy edildiği yerde bulunan environment değişkenlerine göre uygulamada belirli başlı aksiyonları almaktadır. Elbette sizin uygulamanızın da kendisine has belirli başlı dışarıdan alabileceği environment değişkenlerine ihtiyacı olabilir. Bunu bir kaç şekilde Docker ile yapabiliyoruz.
- Container'ı ayağa kaldırma esnasında
- Dockerfile içerisinde image içinde
Bu işlem için -e DEGISKEN=DEGER
şeklinde tanımlama yapabiliyoruz. Burada istediğimiz kadar parametre gönderebiliriz.
docker run -r CHANNEL_NAME=kablosuzkedi
çalışan bir container'ın sahip olduğu ENV değişkenlerinin bilgilerine ulaşmak için;
docker inspect containerName
yapıldıktan sonra Config > Env altında bulunabilir.
Çalışan 2 container'ı birbirine bağlamak için bu iki container'ı birbirine linkleyebiliriz. Bunun için bağlanılmak istenen container'ın çalıştırılırken name almış olması gerekir.
Bunu iki farklı örnek ile açıklayalım. Bir MySQL ayağa kaldırdığımız bir container'ımız var. Aynı şekilde MySQL'e erişebilecek bir arayüz olan phpMyAdmin olsun. Bu iki container arasında bağlantı kurmamız gerekiyor ki phpMyAdmin MySQL'e bağlanabilsin. Bunun için;
docker run --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=test123456 mysql
docker run --name mysql-server -p 3306:3306 -v /opt/data:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=test123 mysql
docker run --name padmin -p 8000:80 --link mysql-server:db phpmyadmin/phpmyadmin
Diğer bir örneğimiz ise kendi yaptığımız bir proje üzerinden olsun;
Bir TODO App'imiz var. NodeJS ile MongoDB arasında bir bağlantı kurmamız gerekiyor. Burada NodeJS ile MongoDB'ye bağlantı kurduğumuz için MongoDB için ayağa kaldırdığımız container'ın name almış olması gerekiyor.
docker run --name mongo-container mongo
daha sonra ayağa kalkmış mongo-container isimli container'a NodeJS içerisinden bağlanmak için link yapıyoruz.
Not: NodeJS için kullanacağımız ImageName todo-app olsun.
docker run -p 3001:3001 --link baglanilacakContainerName:kodIcerisindeKullanilacakAlias todo-app
şeklinde yapilmalidir. Peki burada açıklamamız gereken 2 farkli nokta var.
baglanilacakContainerName : Adından da anlaşılabileceği gibi bağlanmak istediğimiz container'ın adını belirttiğimiz bölüm. Bizim durumumuz için bu mongo-container olmalıdır.
kodIcerisindeKullanilacakAlias : Bu bölüm biraz çetrefilli işte :) Mongoose ile mongoDB'ye bağlanmak için yazdığımız komut aşağıdaki gibidir;
await Mongoose.connect("mongodb://127.0.0.1:27017/todos", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
Fakat container'lar çalıştığında 127.0.0.1 üzerinden çalışmıyor. Docker Host içerisinde çalışıyorlar. Bu container'ları birbirlerine bağlamak için burada --link
argümanını kullandık. Bundan dolayı da bu bağlantı cümlesi değiştirilmelidir.
Peki nasıl?
yukarıda mongodb için --link baglanilacakContainerName:kodIcerisindeKullanilacakAlias
tanımını yapmıştık. Buradaki kodIcerisindeKullanilacakAlias bizim tamı tamına mongoDB'ye kod üzerinden bağlantıyı yaptığımız yerdeki değişikliği belirtiyor.
await Mongoose.connect("mongodb://kodIcerisindeKullanilacakAlias:27017/todos", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
CMD ile ENTRYPOINT temelde aynı komutlar yani Container ayağa kalktığı anda çalıştırılacak komutları belirlerler. Fakat ikisi arasındaki temel fark şudur. Ubuntuyu şu şeklide çalıştırdığınızı farzedelim.
docker run ubuntu
ubuntu image'inin içerisinde bulunan CMD komutundan dolayı burada container ayağa kalkar ve direk kapanır. Çünkü çalışabilecek bir prosesi bulunmamaktadır. Fakat;
docker run ubuntu sleep 2
gibi bir komut verdiğinizde bu arkadaş 2 saniye bekler. Peki neden? Eğer DockerHub üzerinden ubuntunun image'ine giderseniz orada CMD ["/bin/bash"]
yazdığını göreceksiniz.docker run imageName
ifadesinden sonra yazdığımız bir argüman bizim Image içerisinde kullandığımız CMD komutunu overwrite eder. Yani docker run imageName [CMD]
olarak düşünebilirsiniz bunu.
Burada CMD komutun içeriğini komple değiştirir yani. Fakat siz parametreyi bizim durumumuzda sleep 2
yani 2 değerini çalışma anında göndermek istiyorsanız işte işler burada değişiyor.
Bunun için ENTRYPOINT kullanabilirsiniz. ENTRYPOINT'in yapmış olduğu işlem; container ayağa kalktığında kendi içerisinde tanımlanmış olan executable bir uygulamaya sizin gönderdiğiniz parametreyi eklemek. Yani;
Image içerisinde şöyle bir yapımız olsun;
FROM ubuntu
ENTRYPOINT ["sleep"]
bu image'den bir instance alarak container ayağa kaldırmak istediğinizde;
docker run imageName 10
ifadesi gibi bir ifade ile çalıştırırsınız. Bu durumda ENTRYPOINT 10 argümanını alır ve sleep
in sonuna ekler yani sleep 10
olur.
Temel olarak farkı budur. CMD komple değiştirilir, ENTRYPOINT ekleme yapar.
CMD ["executable", "param1", "param2", "param3"]
şeklinde JSON formunda bunu yazabildiğiniz gibi shell formunda da yazabilirsiniz.
CMD executable param1 param2 param3 param4
Aynı yapı ENTRYPOINT için de geçerlidir.
ENTRYPOINT ["executable"]
şeklinde JSON formunda bunu yazabildiğiniz gibi shell formunda da yazabilirsiniz.
ENTRYPOINT executable
Ya sizin birden fazla servisiniz ve bunlarda kullandığınız birden farkli veritabanı, izleme araçları, cache servisi varsa? Bunlar sizin uygulamanız içerisinde aktif olarak kullanılıyorsa her defasında uygulamayı çalıştırmadan önce sürekli sürekli bu link işlemini mi yapmalıyız? Buna cevabımız HAYIR Diğer bir durum ise bu uygulamanızda kullanılan servislerin uygulamanızdan önce çalıştırılmış olmasıdır ki Uygulamanız başarılı bir şekilde bağlanabilsin.
Bunun yerine servislerimizi detaylı bir şekilde tanımlayabildiğimiz mesela;
- Kullanacağı image ya da Dockerfile
- Port numaraları
- Volume Mapping
- Bağlı olduğu servisler
ve çok daha fazlasını yapabildiğimiz bir yapıdır docker compose.
Docker compose bizim image'lerimizi üretmemize ve bu image'lerin container'lara dönüşmesine yardımcı olan bunları tamı tamına bizim istediğimiz sıra ile yapan yardımcı bir araçtır.
Peki bunu nasıl yapıyoruz. Bunun için en önemli nokta; nasıl kendimize ait bir image oluştururuken Dockerfile isimli bir özel dosyadan yararlanıyorsak. Burada da yine docker için bir anlam ifade eden docker-compose.yml isimli YML dosyasından yararlanacağız. Örnek bir YML file üzerinden incelememize devam edelim.
version: "3.4"
services:
webapp:
build: .
ports:
- 3001:3001
depends_on:
- mongodb
mongodb:
image: mongo:latest
ports:
- 27018:27018
volumes:
- data:/data/db
volumes:
data:
peki bunlar nedir? Hemen açıklayalım.
- services Bizim componentlerimizi servislerimizi belirtir. Mesela webapp, mongodb, redis vs vs.. --link ile yapmış olduğumuz bağlantıdaki gibi named container isimleri olarak düşünebilirsiniz.
- build Eğer custom bir image'e sahipseniz yani kendi kaynak kodunuzla bir image üretiyorsanız bunun için hazırlamış olduğunuz Dockerfile dosyasını kaynak olarak gösterebilirsiniz. Burada docker-compose.yml ile Dockerfile aynı yerde olduğu için "." kullandık.
- ports Bir container'ı ayağa kaldırırken kullandığımız
-p
flag' inden farklı bir şey değildir. - volumes eğer bu container içerisindeki bilgiler bir volume üzerine tutulacaksa bunu volumes altında tanımlıyoruz.
- depends_on ise bizim için oldukça heyecan verici diğer bir özellik. Birbirleriyle bağlantıyı aslında tam olarak burada yapıyoruz diyebilirim. depends_on içerisinde tanımladığımız isimler aslında bu yml file içerisindeki diğer servis isimleri. Docker bunu gördüğünde depends_on içindeki servisler tamamen ayağa kalkmadan önce kesinlikle ilgili servisi çalıştırmaz. Yani bizim örneğimizden yola çıkacak olursak. mongodb servisi ayağa kalkmadan önce webapp çalıştırılmaz.
- image Eğer dockerHub üzerinden bir image çekiyorsanız onu burada belirtmelisiniz. Aslında Container'ın BASE IMAGE olarak kullanacağı image'i belirtir. Yani Dockerfile üzerindeki FROM komutuna denk gelir.
- volumes Servisler içerisinde belirtmiş olduğumuz volumes tanımlamalarını burada da yazmamız gerekiyor.
Bu oldukça kolay. docker-compose.yml dosyasının bulunduğu dizine giderek;
docker-compose up
dememiz yeterlidir. Bu bizim için içerisinde bulunan image tanımlarını bir bundle olarak yapar ve container'ı ayağa kaldıracaktır.
Docker varsayılan olarak gelen 3 farklı network türü vardır.
- Bridge
- None
- Host
Hepsinin birbirinden farklı özellikleri vardır.
Docker host içerisinde birbirlerinden bağımsız IP'lerde çalışan farklı farklı container'ları belirtir. Bu container'lar kendi aralarında iletişim kurabilirler.
docker run --network=bridge mongo
Docker host içerisinde herhangi bir şekilde bağlantı kurulamayan izole edilmiş container'lar için kullanılan network türüdür.
docker run --network=none mongo
Docker host üzerinde yapabileceğimiz işlemler için kullanılan network türüdür.
docker run --network=host mongo
Peki biz 2 container sadece bir network üzerinde diğer 2 container'da farklı bir network üzerinde çalışmasını isteseydik(yani birbirlerinden izole etmek isteseydik) nasıl bir network türü oluşturacaktık?
docker network create --driver bridge --subnet 182.18.0.1/24 --gateway 182.18.0.1 wp-mysql-network
docker network ls
komutunu kullanarak, kullanılan tüm network'lerin listesini görebilirsiniz.
Bir network ile ilgili detayları öğrenmek istiyorsanız
docker network inspect bridge
komutunu kullanabilirsiniz.
Herhangi bir container'in kullanmış olduğu networke ait detayları görmek istersek
docker inspect containerName
komutu ile bunu rahatlıkla yapabiliriz.
Eğer bir container'da bulunan uygulamanız diğer bir container içerisinde çalışan bir MySQL, MongoDB vs gibi herhangi bir uygulamaya erişmek isterse örneğin mysql bağlantısı için;
mysql.connect(172.18.0.3)
şeklinde IP adresi üzerinden bağlanabiliriz fakat bu doğru bir yol değildir. Çünkü Docker host yeniden başlatıldığında Docker host bu IP adresinin hala mysql isimli image'e ait olup olamayacağının garantisini veremez.
Bunun yerine yapılacak en iyi çözüm containerName ile bağlanmaktır.
mysql.connect(mysql-db)
şeklinde bağlantı yaptığımızda container, diğer container içerisinde bulunan mysql instance'ına erişebilecektir.
### ÖRNEK
docker network create --driver bridge --subnet 182.18.0.1/24 --gateway 182.18.0.1 wp-mysql-network
docker run --name mysql-db -e MYSQL_ROOT_PASSWORD=db_pass123 --network=wp-mysql-network mysql:5.6
docker run --name webapp -e DB_Host=mysql-db --network=wp-mysql-network kodekloud/simple-webapp-mysql
docker network create todo-app-network
docker run --name mongodb --net todo-app-network -d mongo
docker run --name todo-app -p 3001:3001 --net todo-app-network node-mongo-todo-app
Aynı Network içerisinde bulunan bu iki uygulamayi birbirine bağlamak için "IP" adresi üzerinden değil container name üzerinden ilerliyoruz. Buna göre MongoDB ye bağlanmak için.
mongodb:27017/todos
dememiz yeterlidir.
Elimizde bir image var diyelim. Bunu public olarak dockerHub'a göndermek istiyoruz bunu nasıl yapabiliriz?
Bunun için ilk olarak DockerHub üzerinde bir hesabınızın olması gerekiyor. Bu hesap üzerinde kendinize ait public/private olarak saklayabileceğiniz image'lerinizi tutabilir ve bunlara erişebilir ve kendin Docker Host'unuz üzerine çekebilirsiniz.
Eğer dockerHub üzerinde bir account'unuz varsa o zaman terminal üzerinden docker ile sisteme giriş yapmanız gerekiyor.
docker login
ile giriş yaptıktan sonra artık kendi ürettiğimiz image'leri dockerHub'a gönderebiliriz demektir. Bu işlemi yapmak için yapacağımız tek komut image push
. Tam kullanımı ise;
docker image push imageName:tagName
Bir image'e ait tüm tag'leri dockerHub üzerine göndermek istiyorsanız.
docker image push --all-tags imageName
argümanını kullanabilirisiniz. Fakat burada dikkat etmemiz gereken çok önemli bir durum söz konusu.
Eğer Docker tarafından onaylanan bir DockerHub Pusher değilseniz (Mongo, Node, Ubuntu vs) Docker'a kendi imageName'inizle bir image upload edemezsiniz. Bu image'in önüne kullanıcı adınızı koyarak bu image'i göndermeniz lazım yani;
kendi oluşturmuş olduğunuz image'in ismi counter-app diyelim. Bunu DockerHub üzerine göndermek istiyorsanız;
docker image push --all-tags gkandemir/counter-app
şeklinde image'i gönderebilirsiniz. Fakat bu durumda gkandemir/counter-app
şeklinde bir image'iniz Docker Engine üzerinde olmayabilir. Bundan dolayı kullandığınız image'i yeni bir tag ekleyerek push yapılabilecek hale getirebilirsiniz.
docker image tag counter-app gkandemir/counter-app
dedikten sonra artık DockerHub'a gönderebiliriz.
docker image push --all-tags gkandemir/counter-app
Bu işlem için ilk olarak DigitalOcean üzerinden Ubuntu 20.04(LTS) bir sunucu aldım ve onunla yoluma devam ettim. Sunucuya ssh ile giriş yaptıktan sonra aşağıdaki gibi adımları teker teker uyguladım fakat bunlar için 2 farklı kaynak önerebilirim size;
Ubuntu'ya Docker Yüklemek Install Docker Engine on Ubuntu
Reverse Proxy Açıklaması Reverse Proxy NGINX
Yine ben buraya kendi adımlarımızı da yazıyorum çünkü biz 2 farklı domain'i tek bir host üzerine yönlendirdik. Yukarıdaki Reverse Proxy açıklaması tam olarak yaptıklarımızı karşılamıyor yine de okumanızda fayda vardır :)
İlk olarak paket listesini güncelleyip ihtiyacımız olan bazı paketleri yüklüyoruz.
sudo apt-get update
apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
Docker ve Docker Compose kurulumlarını yapıp versiyonlarını kontrol ediyoruz
apt install docker.io
docker --version
apt install docker-compose
docker-compose --version
Docker servisinin çalışıp çalışmadığına dikkat edelim. Eğer burada active(Running) gibi bir ifade yoksa servisi çalıştırmalıyız.
systemctl status docker
Çalıştırmak için;
systemctl start docker
Şimdi Docker yüklendi 2 farklı uygulamayı iki farklı domain tarafından görüntülenebilecek şekilde ayarlayacağız hostumuzu fakat öncesinde bu iki uygulamayı Docker üzerinen sunmamız gerekiyor. Yani Container'ları ayağa kaldırmamız lazım. Container için gerekli olanlar nedir? Image. O halde image'leri oluşturmaya başlayalım.
Bunun için /temp altında web-apps isimli bir klasör oluşturduk ve onun içerisinde wordpress ve asana-clone isimli 2 farklı klasör oluşturduk.
mkdir web-app/wordpress
mkdir web-app/asana-clone
WordPress kurulumu için wordpress klasörü içerisinde docker-compose.yml dosyasını oluşturup içerisine aşağıdaki kodları yazabilirsiniz.
version: "3.4"
services:
wordpress:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: bloguser
WORDPRESS_DB_PASSWORD: test123
WORDPRESS_DB_NAME: blogdb
volumes:
- wordpress:/var/www/html
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: blogdb
MYSQL_USER: bloguser
MYSQL_PASSWORD: test123
MYSQL_RANDOM_ROOT_PASSWORD: "1"
volumes:
- db:/var/lib/mysql
volumes:
wordpress:
db:
daha sonrasında yapacağımız tek işlem;
docker-compose up
ÖNEMLİ NOT: bunu yaparken /tmp/web-apps/wordpress
dizininde olduğunuzdan emin olun.
Asana Clone App kurulumu için ise Local üzerinden dosyayı aktardık. Onun içerisinde zaten bir app/
ve bir de Dockerfile
olduğu için onu direk sunucuya scp
yardımı ile gönderdik.
Örnek Dockerfile içeriği;
FROM node:14-slim
WORKDIR /vue-app
COPY app/ .
RUN npm install
RUN npm install -g live-server
RUN npm run build
EXPOSE 8080
CMD ["live-server", "dist"]
Bu Dockerfile üzerinden image üretmek için de;
docker build . -t asana-clone-app
dememiz yeterlidir. Bu image'i çalıştırmak için ise;
docker run -p 8090:8080 -d asana-clone-app
dememiz yeterli olacaktır.
ÖNEMLİ NOT: bunun içinde /temp/web-apps/asana-clone/
dizininde olduğunuzdan emin olun.
Artık Sunucu üzerinde 8090 portundan Asana uygulamasını, 8080 portu üzerinden ise wordpress uygulamasını ayağa kaldırdık.
Elimizde 2 farklı domain var;
- videomeet.app
- talkinghead.app
bu iki domaini ilk başta DigitalOcean üzerinden almış olduğumuz Sunucunun IP Adresine CloudFlare üzerinden yönlendirdik. Yani kullanıcı bu iki domainden hangisine girerse girsin aynı makineye yönlendirilecek.
Şimdi yapmamız gereken ise buradaki domainin ismine göre (talkinghead ya da videomeet) ilgili container'a yönlendirmek olacak. İşte bu duruma reverse proxy deniliyor. Bunu yapmak için sunucuda yoksa nginx yüklemeliyiz. Ardından da sites-available
dizinine girip orada reverse-proxy.conf
dosyası üreteceğiz. Yazacağımız Reverse Proxy bilgisini bu konfigürasyon dosyasında saklayacağız.
apt install nginx
unlink /etc/nginx/sites-enabled/default
cd /etc/nginx/sites-available
vi reverse-proxy.conf
Konfigürasyon ise;
server {
listen 80;
listen [::]:80;
server_name videomeet.app;
server_name_in_redirect off;
access_log /var/log/nginx/reverse-access.log;
error_log /var/log/nginx/reverse-error.log;
location / {
proxy_set_header Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
}
}
server {
listen 80;
listen [::]:80;
server_name talkinghead.app;
server_name_in_redirect off;
access_log /var/log/nginx/reverse-access.log;
error_log /var/log/nginx/reverse-error.log;
location / {
proxy_set_header Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8090;
}
}
daha sonrasında şu adımları yapmalıyız;
ln -s /etc/nginx/sites-available/reverse-proxy.conf /etc/nginx/sites-enabled/reverse-proxy.conf
nginx -t
systemctl restart nginx
systemctl status nginx
burayı özet geçecek olursak sites-available
içerisindeki reverse-proxy.conf dosyasını sites-enabled
içerisine aktarıyoruz ve nginx'i test ediyoruz. Burada eğer success görüyorsanız her şey yolunda demektir :)
Bundan sonraki tek adım ise nginx servisini yeniden başlatmak demektir :)
Böylece 2 farklı domain tek sunucu üzerinen docker host'un farklı uçlarına bakıyor hale geldiler.