Servisler Arası İletişim: Docker Compose Ağ Yapılandırması

Docker Compose ile çalışırken en çok kafanın karıştığı konulardan biri servisler arası iletişimin nasıl kurulduğudur. “Nginx container’ım neden backend’e ulaşamıyor?”, “Veritabanı bağlantısı neden localhost değil?” gibi sorular her yeni Compose kullanıcısının önüne çıkar. Bu yazıda Compose ağ yapılandırmasını derinlemesine ele alacağız, gerçek dünya senaryolarıyla pekiştireceğiz.

Compose Ağ Modelini Anlamak

Docker Compose, varsayılan olarak her proje için otomatik bir bridge network oluşturur. Bu network’ün adı _default formatında olur. Yani myapp klasöründe çalıştırdığın Compose dosyası, arka planda myapp_default adında bir sanal ağ yaratır.

Bu modelin güzel tarafı şu: Aynı Compose dosyasındaki tüm servisler bu network’e otomatik olarak dahil edilir ve birbirlerine servis adıyla erişebilirler. 172.17.0.x gibi IP adreslerini ezberlemene gerek yok. nginx servisi, backend servisine doğrudan http://backend:3000 şeklinde ulaşabilir.

# Oluşturulan network'leri görmek için
docker network ls

# Belirli bir network'ün detaylarını incelemek için
docker network inspect myapp_default

Şimdi bu temeli daha ileriye taşıyalım.

Servis Adıyla İletişim: Temel Senaryo

En basit örneğimizle başlayalım. Bir Node.js backend’in, PostgreSQL veritabanına bağlandığı klasik bir yapı:

version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: gizlisifre
    volumes:
      - postgres_data:/var/lib/postgresql/data

  backend:
    build: ./backend
    environment:
      DATABASE_URL: postgresql://appuser:gizlisifre@db:5432/myapp
    depends_on:
      - db
    ports:
      - "3000:3000"

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
    depends_on:
      - backend

volumes:
  postgres_data:

Burada dikkat et: DATABASE_URL içinde localhost yerine db yazıyoruz. Çünkü Docker’ın DNS çözümlemesi, db servis adını otomatik olarak ilgili container’ın IP adresine çeviriyor. Bu mekanizma sayesinde IP adresi değişse bile bağlantı kopmaz.

nginx.conf içinde de aynı prensip geçerli:

upstream backend_pool {
    server backend:3000;
}

server {
    listen 80;

    location /api {
        proxy_pass http://backend_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

Özel Network Tanımları

Varsayılan network yeterli olmayan durumlar var. Mesela frontend servisinin direkt olarak veritabanına erişememesini istiyorsun. Ya da microservice mimarisinde bazı servislerin izole olmasını istiyorsun. İşte bu noktada özel network tanımları devreye giriyor.

version: '3.8'

services:
  frontend:
    build: ./frontend
    networks:
      - frontend_net
    ports:
      - "3000:3000"

  backend:
    build: ./backend
    networks:
      - frontend_net
      - backend_net
    environment:
      DB_HOST: db
      REDIS_HOST: cache

  db:
    image: postgres:15
    networks:
      - backend_net
    environment:
      POSTGRES_PASSWORD: gizlisifre

  cache:
    image: redis:7-alpine
    networks:
      - backend_net

networks:
  frontend_net:
    driver: bridge
  backend_net:
    driver: bridge

Bu yapıda frontend servisi, db ve cache servislerine doğrudan erişemez. Sadece backend üzerinden ulaşabilir. backend ise her iki network’te de bulunduğu için köprü görevi görüyor. Bu tür segmentasyon, production ortamlarında güvenlik açısından kritik önem taşır.

Network Alias Kullanımı

Bazen aynı servise birden fazla isimle erişmen gerekebilir. Ya da mevcut bir uygulamayı container’a taşırken bağlantı adresini değiştirmek istemiyorsundur. aliases özelliği tam burada işe yarıyor:

version: '3.8'

services:
  db:
    image: postgres:15
    networks:
      backend_net:
        aliases:
          - database
          - postgres-primary
    environment:
      POSTGRES_PASSWORD: gizlisifre

  backend:
    build: ./backend
    networks:
      - backend_net
    environment:
      # Üç yöntemle de aynı servise ulaşırsın
      DB_HOST_1: db
      DB_HOST_2: database
      DB_HOST_3: postgres-primary

networks:
  backend_net:

Bu özelliği özellikle blue-green deployment ya da A/B test senaryolarında da kullanabilirsin. Eski ve yeni servis versiyonlarını aynı alias altında yönetmek mümkün.

Harici Network’lere Bağlanmak

Bazen birden fazla Compose projesi birbiriyle konuşması gerekir. Örneğin bir monitoring stack’in (Prometheus + Grafana) tüm uygulama container’larına erişmesi gerekiyordur. Bu durumda harici network’ler devreye giriyor.

Önce paylaşımlı network’ü oluştur:

docker network create shared_monitoring

Sonra her iki Compose dosyasında da bu network’ü external: true ile tanımla:

Uygulama Compose dosyası (myapp/docker-compose.yml):

version: '3.8'

services:
  backend:
    build: ./backend
    networks:
      - app_internal
      - monitoring_network

  db:
    image: postgres:15
    networks:
      - app_internal

networks:
  app_internal:
    driver: bridge
  monitoring_network:
    external: true
    name: shared_monitoring

Monitoring Compose dosyası (monitoring/docker-compose.yml):

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - monitoring_network
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    networks:
      - monitoring_network
    ports:
      - "3000:3000"

networks:
  monitoring_network:
    external: true
    name: shared_monitoring

Bu yapı sayesinde prometheus, myapp projesindeki backend servisine adıyla ulaşabilir. Ancak dikkat: Container adları çakışmamalı. Her Compose projesi container adlarını __ formatında oluşturur, bu yüzden genellikle sorun çıkmaz.

IP Adresi ve Subnet Yapılandırması

Bazı production senaryolarında, container network’lerinin IP aralığının kurumsal ağ politikalarıyla çakışmaması gerekir. Ya da belirli bir container’a statik IP ataman lazımdır. Bu durumda subnet yapılandırmasına ihtiyaç duyarsın:

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    networks:
      app_net:
        ipv4_address: 172.20.0.10
    ports:
      - "80:80"

  backend:
    build: ./backend
    networks:
      app_net:
        ipv4_address: 172.20.0.11

  db:
    image: postgres:15
    networks:
      app_net:
        ipv4_address: 172.20.0.12
    environment:
      POSTGRES_PASSWORD: gizlisifre

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1

Statik IP ataması güçlü bir araç ama dikkatli kullan. Büyük çoğunlukla servis adı üzerinden DNS çözümlemesi yeterli ve çok daha yönetilebilir oluyor. Statik IP, özellikle iptables kuralları, güvenlik duvarı politikaları ya da legacy uygulama entegrasyonları için gerekebilir.

Gerçek Dünya Senaryosu: Microservice Mimarisi

Daha kapsamlı bir örneğe bakalım. E-ticaret platformu gibi düşün: Kullanıcı yönetimi, ürün kataloğu, sipariş servisi, ve bunların önünde bir API gateway. Her servisin farklı veritabanı var ve birbirleriyle HTTP üzerinden konuşuyorlar.

version: '3.8'

services:
  api-gateway:
    build: ./api-gateway
    ports:
      - "80:80"
    networks:
      - gateway_net
    environment:
      USER_SERVICE_URL: http://user-service:4001
      PRODUCT_SERVICE_URL: http://product-service:4002
      ORDER_SERVICE_URL: http://order-service:4003

  user-service:
    build: ./user-service
    networks:
      - gateway_net
      - user_net
    environment:
      DB_HOST: user-db
      DB_PORT: 5432

  product-service:
    build: ./product-service
    networks:
      - gateway_net
      - product_net
    environment:
      DB_HOST: product-db
      CACHE_HOST: product-cache

  order-service:
    build: ./order-service
    networks:
      - gateway_net
      - order_net
    environment:
      DB_HOST: order-db
      USER_SERVICE_URL: http://user-service:4001
      PRODUCT_SERVICE_URL: http://product-service:4002

  user-db:
    image: postgres:15
    networks:
      - user_net
    volumes:
      - user_db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: users
      POSTGRES_PASSWORD: ${USER_DB_PASS}

  product-db:
    image: mongodb:6
    networks:
      - product_net
    volumes:
      - product_db_data:/data/db

  product-cache:
    image: redis:7-alpine
    networks:
      - product_net

  order-db:
    image: postgres:15
    networks:
      - order_net
    volumes:
      - order_db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: orders
      POSTGRES_PASSWORD: ${ORDER_DB_PASS}

networks:
  gateway_net:
    driver: bridge
  user_net:
    driver: bridge
  product_net:
    driver: bridge
  order_net:
    driver: bridge

volumes:
  user_db_data:
  product_db_data:
  order_db_data:

Bu mimaride api-gateway, tüm servislerle konuşabilirken veritabanları tamamen izole. Bir servisin veritabanına başka bir servis doğrudan ulaşamıyor. Bu, hem güvenlik hem de bağımsız ölçeklendirme açısından ideal bir yapı.

Network Sorunlarını Debug Etmek

En iyi yapılandırmayı yapsan bile zaman zaman bağlantı sorunları çıkar. İşte bu durumda kullanabileceğin araçlar ve komutlar:

# Servisin hangi network'lerde olduğunu görmek
docker inspect <container_adı> | grep -A 30 "Networks"

# Container içinden DNS çözümlemesini test etmek
docker exec -it <container_adı> nslookup <hedef_servis_adı>

# Container içinden bağlantı testi
docker exec -it <container_adı> curl -v http://backend:3000/health

# Network üzerindeki tüm container'ları listelemek
docker network inspect <network_adı> --format='{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"n"}}{{end}}'

# Geçici bir debug container başlatarak network'ü test etmek
docker run --rm -it --network myapp_default nicolaka/netshoot nmap -p 5432 db

nicolaka/netshoot imajı bu tür debug işlemleri için biçilmiş kaftan. İçinde nmap, tcpdump, dig, curl, netstat gibi onlarca araç hazır geliyor. Production’da bir bağlantı sorunu çıktığında bu imajı geçici container olarak aynı network’e bağlayıp sorunun nereden kaynaklandığını hızlıca anlayabilirsin.

depends_on ve Sağlık Kontrolü

Network yapılandırması doğru olsa bile, servisler başlangıç sırasına bağlı sorunlar yaşayabilir. depends_on tek başına yeterli değil, çünkü sadece container’ın başladığını bekler, hazır olduğunu değil.

version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: gizlisifre
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  backend:
    build: ./backend
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app_net
    environment:
      DATABASE_URL: postgresql://postgres:gizlisifre@db:5432/myapp

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

  worker:
    build: ./worker
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    networks:
      - app_net

networks:
  app_net:

condition: service_healthy kullanımı, bağımlı servisler tamamen hazır olmadan diğer servislerin başlamasını engelliyor. Bu yapı özellikle veritabanı migration’larının çalıştığı backend servisleri için hayat kurtarır.

Host Network Modu ve Kullanım Senaryoları

Bazı özel durumlar için container’ın doğrudan host network stack’ini kullanması gerekebilir. Performans açısından kritik uygulamalar ya da host üzerindeki servislere düşük gecikmeli erişim gerektiren durumlar bunların başında gelir.

version: '3.8'

services:
  high-perf-proxy:
    image: haproxy:2.8
    network_mode: host
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro

Ancak bu modda port çakışmalarına dikkat etmek gerekiyor. Container artık host’un portlarını doğrudan kullanıyor ve başka hiçbir servis aynı porta bind olamıyor. Ayrıca ports tanımı bu modda geçersiz oluyor, host’un tüm portları zaten erişilebilir durumda.

Host network modunu kullanman gereken durumlar:

  • Gerçek zamanlı uygulamalar: Oyun sunucuları, canlı yayın sistemleri
  • Ağ performansı kritik uygulamalar: Yüksek throughput gerektiren proxy’ler
  • Multicast veya broadcast gerektiren uygulamalar: Bazı discovery protokolleri
  • Host üzerindeki servislerle entegrasyon: Yerel MongoDB veya PostgreSQL’e bağlanma

Dikkat: Host network modu Linux’ta çalışır, Docker Desktop (Mac/Windows) üzerinde beklendiği gibi davranmaz.

.env ile Network Yapılandırmasını Yönetmek

Büyük projelerde network yapılandırmasını ortama göre değiştirmen gerekebilir. .env dosyaları ve değişkenler burada çok işe yarıyor:

# .env dosyası
COMPOSE_PROJECT_NAME=myapp
NETWORK_SUBNET=172.25.0.0/24
NETWORK_GATEWAY=172.25.0.1
DB_PORT=5432
REDIS_PORT=6379
version: '3.8'

services:
  backend:
    build: ./backend
    networks:
      - app_net
    environment:
      DB_HOST: db
      DB_PORT: ${DB_PORT:-5432}

  db:
    image: postgres:15
    networks:
      app_net:
        ipv4_address: 172.25.0.10
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

networks:
  app_net:
    driver: bridge
    ipam:
      config:
        - subnet: ${NETWORK_SUBNET:-172.25.0.0/24}
          gateway: ${NETWORK_GATEWAY:-172.25.0.1}

COMPOSE_PROJECT_NAME değişkenini ayarlamak, birden fazla ortamı (dev, staging) aynı host üzerinde çalıştırırken network ismi çakışmalarını önlüyor. Aksi takdirde aynı klasörden farklı branch’ler çalıştırmaya kalktığında işler karışabilir.

Sık Yapılan Hatalar ve Çözümleri

Localhost tuzağı: En klasik hata, container içinde localhost kullanmak. Container’ın kendi içini gösterir, başka bir servisi değil. Servis adını kullan.

Port expose etmek zorunda değilsin: Servisler arası iletişim için ports tanımına gerek yok. ports sadece host makineden erişim içindir. Compose network’ü üzerindeki tüm portlar zaten servisler arası erişime açık.

Network driver seçimi: Varsayılan bridge driver çoğu senaryo için yeterli. overlay driver Swarm için, macvlan container’ların fiziksel ağa doğrudan bağlanması için kullanılır. Yanlış driver seçimi performans ve bağlantı sorunlarına yol açar.

Ağ izolasyonu unutulmak: Bir servisi birden fazla network’e bağlamak gerektiğinde sadece networks listesine eklemek yetmiyor, o network’ün de networks bölümünde tanımlı olması lazım.

depends_on sıralaması yanıltıcı: Yukarıda belirttiğimiz gibi, depends_on container başlangıcını bekler ama uygulama hazırlığını değil. Sağlık kontrolü eklemek şart.

Sonuç

Docker Compose ağ yapılandırması ilk bakışta karmaşık görünse de temel mantığı kavradıktan sonra oldukça akıcı bir deneyime dönüşüyor. Servis adıyla DNS çözümlemesi, özel network’lerle izolasyon, harici network’lerle multi-project iletişim ve sağlık kontrolleriyle güvenilir başlangıç sırası… Bunların hepsini bir arada kullandığında hem geliştirme hem de production ortamında tutarlı, güvenli ve yönetilebilir bir yapı elde ediyorsun.

Pratikte şu yaklaşımı benimse: Küçük projeler için varsayılan network yeterli, sadece servis adlarını doğru kullan. Orta ölçekli projeler için frontend/backend/data katmanlarını ayrı network’lere ayır. Büyük microservice mimarileri için her servis grubunu kendi izole network’üne koy, sadece gerekli servisleri birden fazla network’e ekle.

Son olarak şunu söyleyelim: Network yapılandırmasını dokümante et. Altı ay sonra projeye geri döndüğünde ya da yeni bir ekip üyesi geldiğinde, hangi servisin hangi servisle neden konuşabildiğini açıklayan bir README hayat kurtarır. Compose dosyasındaki yorum satırları da bu konuda çok değerli.

Yorum yapın