Docker Compose Ag Yapilandirmasi ve Servis Iletisimi
Docker Compose varsayilan bridge agi, servis ismi ile DNS cozumu, host modu kullanimi ve UFW kurallarini bypass eden port yayinlama detaylarini bu rehberde bulabilirsiniz.
Uygulamanız başlamadan önce Compose neleri oluşturur
Docker Compose ağ iletişimi tek bir kural ile başlar: docker compose up proje için özel bir ağ oluşturur, her servisi bu ağa bağlar ve bu servislerin birbirlerine servis ismi üzerinden erişebilmelerini sağlar. Bunu sağlamak için tek bir networks: satırı yazmanız gerekmez. Compose ağ iletişimiyle ilgili kafa karışıklıklarının çoğu, bu varsayılan durumun zaten mevcut olduğunun bilinmemesinden kaynaklanır.
İşte küçük bir dosya. Bunu shop adlı bir dizinde compose.yaml olarak kaydedin.
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: exampleProjeyi ayağa kaldırın ve Docker'ın neler oluşturduğuna bakın:
docker compose up -d
docker network lsListe artık shop_default adında bir ağ içerir. Compose bu ağa <project>_default adını verir ve proje ismi varsayılan olarak dizin isminin küçük harfli hali olur. Bunu docker compose -p myproject up -d ile veya dosya içerisinde en üst seviyede bir name: myproject ile geçersiz kılabilirsiniz. Ağın sürücüsü, ana makine (host) içerisinde sanal bir anahtar görevi gören bridge'dir. Her container özel bir alt ağda (subnet) bir adres alır ve dışarı giden trafik, çıkış sırasında ana makinenin adresine dönüştürülür (NAT).
docker compose down bu ağı tekrar siler. Eski bir projeden kalan atıl bir container'ın ağı açık tutmasının nedeni budur: Docker error while removing network: network shop_default has active endpoints hatası vererek işlemi reddeder; çözüm, ağa hala bağlı olan container'ı durdurmak veya kaldırmaktır.
Eğer Compose sizin için yeniyse, Compose dosya yapısı ve yaşam döngüsü komutları bölümünü öncelikle okumanızda fayda vardır, çünkü aşağıdaki her şey bir projeyi başlatıp durdurabildiğiniz varsayımı üzerine kuruludur.
Servis ismiyle DNS çözümleme yeni başlayanların gözden kaçırdığı kısımdır
Kullanıcı tanımlı herhangi bir ağ üzerinde Docker, her container'ın 127.0.0.11 adresinde gördüğü gömülü bir DNS sunucusu çalıştırır. Bu sunucu, servis isimlerini mevcut container adreslerine çözümler. Bu sayede web, veritabanına herhangi bir yapılandırma gerektirmeden db ana makine ismi ve 5432 portu üzerinden erişebilir.
docker compose exec web getent hosts dbBu komut 172.18.0.2 db gibi bir satır çıktısı verir. Eğer hiçbir çıktı vermiyorsa, iki servis aynı ağ üzerinde değildir.
Neredeyse herkesin bir kez yaptığı hata, uygulama yapılandırmasında localhost kullanmaktır. Bir container içerisinde localhost, ana makineyi veya diğer servisi değil, o container'ın kendisini ifade eder. Postgres istemcileri bunu açıkça raporlar:
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP connections on port 5432?Bağlantı dizgisi postgresql://postgres:example@db:5432/postgres şeklinde olmalıdır. Ana makine kısmı servis ismidir.
Daha sonra zaman kazandıracak iki detay şunlardır: İsimler o an çalışan her ne ise ona çözümlenir, dolayısıyla docker compose up -d --scale web=3 üç farklı adrese sahip tek bir isim verir; DNS çözümlemesini kalıcı olarak önbelleğe alan bir istemci ise kendisini ölü bir container'a sabitleyecektir. Ayrıca, herhangi bir --network parametresi içermeyen düz bir docker run tarafından kullanılan eski bridge ağı, hiçbir isim çözümlemesi yapmaz; 2016 yılından kalma container bağlantılarıyla ilgili tavsiyelerin güncel durumla uyuşmamasının nedeni budur.
İki servisi birbirine bağlamak için ports: kullanmanıza gerek yoktur
ports:, bir container portunu ana makine üzerinde dışarıya açar. Bu, Docker dışından gelen trafik içindir. Proje ağındaki tüm port aralığında zaten çalışan servisler arası trafikle hiçbir ilgisi yoktur.
Bu nedenle, birçok kişinin veritabanı servisine eklediği ports: - "5432:5432" hiçbir işe yaramaz ve aksine zarar verir: Postgres'i sunucunun genel arayüzüne açık hale getirir. Bunu silin. Eğer bir taşıma işlemi için dizüstü bilgisayarınızdan erişmek istiyorsanız, "127.0.0.1:5432:5432" ile loopback arayüzüne bağlayın ve bir SSH tüneli üzerinden erişin. Dinleyen bir soket, yayınlanan bir port ve güvenlik duvarı kuralı arasındaki fark, Linux üzerinde portlar ve dinleyen servisler nasıl çalışır bölümünde açıklanmıştır.
expose:, Compose altında yalnızca dokümantasyon amaçlıdır. Hiçbir şeyi açmaz, çünkü aynı ağdaki container'lar arasında zaten hiçbir şey kapalı değildir.
network_mode host ne zaman uygundur ve maliyeti nedir
Host modu, container'ın kendi ağ ad alanını (network namespace) devre dışı bırakır ve sürecin doğrudan ana makinenin arayüzlerini kullanmasını sağlar.
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityBunu tercih etmek için geçerli nedenler vardır. Bir medya sunucusu veya ev otomasyonu merkezi için cihaz keşfi gibi yerel ağdaki broadcast veya multicast trafiğini görmesi gereken bir süreç, köprü (bridge) arkasından bunu göremez; çünkü köprü bu trafiği container'a iletmez. Ana makinenin arayüz sayaçlarını okuyan bir izleme ajanı, ana makinenin arayüzlerine ihtiyaç duyar. Ayrıca, yüksek paket hızlarında önemli olan adres çevrimi (NAT) adımını atlamış olursunuz.
Maliyetleri ise spesifiktir.
ports: çalışmayı durdurur. Docker, host ağ modu kullanıldığında yayınlanan portların (published ports) göz ardı edildiğini ve container'ın, sürecin bağlandığı her ne ise ona bağlandığını belirtir. 8080 numaralı portu kullanmak isteyen iki host modu container'ı çakışır ve ikincisi bind: address already in use hatasıyla sonlanır.
Servis ismiyle isim çözümleme her iki yönde de kaybolur. Container proje ağında olmadığından db adresini çözümleyemez ve diğer servisler de container'ı çözümleyemez. Container diğer servislere yalnızca ana makinede yayınlanan portlar üzerinden, genellikle 127.0.0.1 adresinden ulaşabilir.
İzolasyon kaybolur. Host modu container'ı içinde 0.0.0.0 adresine bağlanan bir süreç, tıpkı apt ile yüklenmiş bir paket gibi, sunucunuzun herkese açık olan dahil tüm arayüzlerinde dinleme yapar. Bunun tek bir avantajı vardır: bu trafik normal giriş yolunu izler, dolayısıyla yayınlanan portlar için geçerli olmayan UFW kuralları burada uygulanabilir.
Host modu bir Linux Docker Engine özelliğidir. Docker Desktop bunu yalnızca 4.34 ve sonraki sürümlerde, siz etkinleştirdikten sonra destekler; ayrıca container'ların ana makine IP adreslerine bağlanamaması ve yalnızca TCP ile UDP protokollerinin işlenmesi gibi ek kısıtlamalar mevcuttur. Ekibinizin yarısı Linux sunucularında, yarısı Docker Desktop üzerinde çalışıyorsa, aynı dosyanın farklı davranmasını beklemelisiniz.
Host moduna, ana makinenin arayüzlerine ihtiyaç duyduğunuzda başvurun. Bir bağlantı sorununu çözmek için bu yöntemi kullanmayın; çünkü genellikle bir sorunu daha zor bir başkasıyla değiştirmiş olursunuz.
İki Compose projesini harici bir ağ ile birbirine bağlama
Bir proje tarafından oluşturulan ağ, diğer bir proje tarafından görülemez. Bu nedenle, proxy/compose.yaml içindeki bir reverse proxy, aynı sunucuda olsa bile app/compose.yaml içindeki bir uygulamayı göremez. Çözüm, hiçbir projenin sahipliğinde olmayan bir ağ kullanmaktır.
Bu ağı manuel olarak bir kez oluşturun:
docker network create edgeArdından, her iki projede de bu ağı harici (external) olarak tanımlayın. Proxy tarafı:
services:
proxy:
image: traefik:v3.1
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- edge
networks:
edge:
name: edge
external: trueUygulama tarafı:
services:
app:
image: nginx:1.27
networks:
- edge
- internal
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- internal
networks:
edge:
name: edge
external: true
internal:external: true, Compose'a yeni bir ağ oluşturmak yerine mevcut bir ağa bağlanmasını ve docker compose down komutu çalıştırıldığında ağı yerinde bırakmasını söyler. Ayrı olan name: anahtarı göründüğünden daha önemlidir: bu anahtar olmadan Compose, tam olarak edge adında bir ağ arar; bu anahtar sayesinde ağa dosyanız içinde bir isim, ana makinede (host) ise başka bir isim verebilirsiniz.
Eğer ağ mevcut değilse, Compose başlatmayı reddeder ve ağın harici olarak tanımlandığını ancak bulunamadığını bildirir. Ağı önceden oluşturun.
Uygulama dosyasının internal ile ne yaptığına dikkat edin. Veritabanı yalnızca o projeye özel yerel ağda bulunur, bu nedenle proxy ona erişemez; sadece app erişebilir. Bir ağ altında internal: true eklemek, dış dünyaya olan rotasını tamamen kaldırarak daha ileri bir güvenlik sağlar. Bu, veritabanları için iyi bir varsayılan ayardır; ancak yapılandırmadan önce bilinmesi gereken bir maliyeti vardır: dahili bir ağdaki container hiçbir şey indiremez. Bu nedenle, başlangıçta apt-get update veya pip install çalıştıran bir giriş noktası (entrypoint) yanıt vermeyi durdurur ve zaman aşımı (timeout) hatasıyla başarısız olur.
Yönlendirme kuralları ve sertifikalar içeren tam bir kurulum örneği için bir Traefik örneğinin arkasında birden fazla uygulama çalıştırma bölümüne bakın.
Yayınlanan portlar UFW'yi atlar
Docker Compose ağ yapısının güvenlik ihlaline yol açan kısmı burasıdır. Bir portu yayınladığınızda, UFW'nin aktif olduğunu ve SSH dışında her şeyi engellediğini kontrol edersiniz, ancak servis internetten hala erişilebilirdir.
sudo ufw status
curl http://203.0.113.10:8080UFW portun engellendiğini belirtir. curl ise sayfayı yine de döndürür. Hiçbir şey bozuk değildir. Docker, kendi adres çevirisi ve yönlendirme kurallarını doğrudan iptables içine yazar. Yayınlanan bir container portuna gelen trafik, ana makineye teslim edilmek yerine doğrudan container'a yönlendirilir; bu nedenle UFW'nin yerel trafik için yönettiği zincirden asla geçmez. Ayrıca Docker'ın kuralları, UFW'ninkilerden önce eşleştirilir.
Kısa çözüm, portu yalnızca ihtiyaç duyduğunuz yerde yayınlamaktır:
ports:
- "127.0.0.1:8080:80"Bu, ana makine tarafını loopback adresine bağlar; böylece port sadece sunucunun kendisinden ve bir SSH tüneli üzerinden erişilebilir olur, başka hiçbir yerden erişilemez. Genel giriş noktasını, 80 ve 443 portlarını bilerek yayınlayan bir reverse proxy arkasına yerleştirin. Yayınlanan bir portu filtrelemeniz gereken durumlar için DOCKER-USER zinciri de dahil olmak üzere tam açıklama Docker'ın neden UFW'yi atlayarak yayın yaptığı ve bunun nasıl düzeltileceği başlığında yer almaktadır.
Dört komutla hata ayıklama
Öncelikle her container'ın gerçekte hangi ağ üzerinde olduğunu sorgulayarak başlayın:
docker network inspect shop_defaultContainers bloğu, bağlı olan her container'ı ve adresini listeler. Bu listede yer almayan bir servis farklı bir ağdadır, host modundadır veya çalışmıyordur.
Kendi imajlarınızın içinde herhangi bir araca ihtiyaç duymamanız için, aynı ağa bağlı geçici bir container üzerinden isim çözümlemeyi test edin:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432nslookup komutunun başarısız olması, isim çözümleme veya ağ üyeliği sorununa işaret eder. nslookup başarılıyken nc başarısız oluyorsa, servis çalışıyor ancak o portu dinlemiyordur ya da kendi container'ı içinde 0.0.0.0 yerine 127.0.0.1 adresini dinliyordur. Bu durum geliştirme sunucularında yaygındır ve çözüm Docker'da değil, uygulamanın bind adresindedir.
Docker hatası gibi görünen bir diğer hata durumu şudur: Eğer container'lar birbirleriyle konuşabiliyor ancak ofis veya VPN ağınızdaki bir makineye erişemiyorsa, Docker alt ağı muhtemelen o ağ ile çakışıyordur. Docker varsayılan olarak 172.17.0.0/16 adresinden itibaren atama yapar. Havuzu /etc/docker/daemon.json dosyasında taşıyın:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}Ardından sudo systemctl restart docker komutunu çalıştırın ve etkilenen ağları yeniden oluşturun; çünkü mevcut bir ağ, oluşturulduğu andaki alt ağ bilgisini korur.
FAQ
Konteynerlerim neden servis adıyla birbirine ulaşamıyor?
Aynı ağ üzerinde değillerdir. Compose, her servisi otomatik olarak <project>_default ağına dahil eder; ancak bir servise networks: listesi eklediğiniz anda, bu liste ilgili servis için ağların tamamını temsil eder ve varsayılan ağ artık dahil edilmez. docker network inspect <network> komutunu çalıştırın ve her iki konteynerin de Containers bloğunda göründüğünü doğrulayın. Ayrıca hiçbir servisin network_mode: host kullanmadığından emin olun; çünkü host modundaki bir konteyner hiçbir Docker ağında yer almaz ve servis adlarını çözümleyemez.
Bir servisin diğerine ulaşması için portları dışarı açmam gerekir mi?
Hayır. Bir Compose ağı üzerinde, her konteynerin tüm portlarına aynı ağdaki diğer konteynerler tarafından erişilebilir. ports: yalnızca bir konteyneri Docker dışından gelen trafiğe açmak için kullanılır, expose: ise yalnızca dokümantasyon amaçlıdır. Veritabanı portunu dışarı açmak yaygın ve riskli bir alışkanlıktır; çünkü bu işlem veritabanını sunucunuzun genel ağ arayüzüne yerleştirir.
Bridge ve host ağ yapılandırması arasındaki fark nedir?
Bridge, konteynere sanal bir anahtar üzerinde kendi ağ ad alanını ve adresini verir; konteynerler arasında otomatik isim çözümlemesi sağlar ve giden trafiği dönüştürür. Host ise konteynere doğrudan sunucunun ağ yığınını kullandırır: ayrı bir adres, servis adıyla çözümleme veya port yönlendirme yoktur; ayrıca sunucudaki diğer dinleyicilerden herhangi bir yalıtım sağlanmaz. Süreç sunucunun arayüzlerine doğrudan ihtiyaç duymadığı sürece, varsayılan ve doğru yöntem Bridge kullanmaktır.
İki farklı Compose dosyasındaki konteynerleri nasıl birbirine bağlarım?
docker network create edge ile paylaşımlı bir ağ oluşturun, ardından her iki dosyada da external: true ile bu ağı tanımlayın ve iletişim kurması gereken servisleri bu ağa dahil edin. Compose bu ağı ne oluşturur ne de siler. Oluşturma adımını atlarsanız, Compose başlatmayı reddeder ve ağın harici olarak tanımlandığını ancak bulunamadığını bildirir.
UFW portu engellediği halde konteynerime internetten neden erişilebiliyor?
Çünkü dışarı açılan bir port, Docker'ın iptables kurallarına eklediği yönlendirme kuralları tarafından yönetilir. Bu kurallar UFW kurallarından önce eşleşir ve yönlendirilen trafik zaten UFW'nin filtrelediği zincirden geçmez. Sunucu tarafındaki bağlantıyı "127.0.0.1:8080:80" ile loopback adresine bağlayın ve herkese açık olan her şeyi 80 ve 443 numaralı portlar üzerinden bir reverse proxy arkasına alın.