Takip et

Full-Stack Projelerde Docker Ağ İletişimi: Kapsayıcı Bağlantıları

Modern yazılım geliştirme dünyasında, full-stack uygulamaları dağıtmak ve yönetmek, özellikle mikroservis mimarileri yaygınlaştıkça, karmaşık bir hale gelebiliyor. Bu karmaşıklığın merkezinde ise farklı uygulama bileşenlerinin (ön yüz, arka yüz, veritabanı, önbellek gibi) birbiriyle nasıl iletişim kuracağı sorunu yatıyor. Docker, kapsayıcılaştırma teknolojisiyle bu süreci büyük ölçüde basitleştirse de, kapsayıcılar arası ağ iletişimi birçok geliştirici için hâlâ bir soru işareti olabiliyor. Peki, Docker ile bir full-stack projesinin tüm parçalarını sorunsuz bir şekilde nasıl bağlarız ve uygulamalarımızın birbiriyle konuşmasını nasıl sağlarız?

Docker, uygulamalarınızı izole edilmiş, taşınabilir ve tutarlı ortamlarda çalıştırmanızı sağlayan devrim niteliğinde bir platformdur. Her bir uygulama bileşeni (örneğin, bir Node.js API’si, bir React ön yüzü veya bir PostgreSQL veritabanı) kendi bağımsız kapsayıcısı içinde çalışır. Bu kapsayıcılar, işletim sistemi seviyesinde izole edilmiştir; yani, bir kapsayıcıdaki değişiklikler veya bağımlılıklar, diğerini doğrudan etkilemez. Ancak, bir full-stack uygulamanın parçaları birbirinden tamamen izole yaşayamaz; tam tersine, sürekli olarak bilgi alışverişinde bulunmaları gerekir. Örneğin, bir ön yüz uygulamasının arka yüz API’sine istek göndermesi, arka yüzün ise bir veritabanına veri yazması veya okuması gerekir. Bu noktada devreye Docker’ın güçlü ağ yetenekleri girer.

Docker, her kapsayıcıya sanal bir ağ arayüzü atar ve bu sayede kapsayıcıların birbirleriyle ve dış dünya ile iletişim kurmasını sağlar. Varsayılan olarak, Docker bir “bridge” ağı oluşturur ve yeni oluşturulan kapsayıcıların çoğu bu ağa bağlanır. Bu köprü ağı, kapsayıcılar için özel bir iç ağ görevi görür. Her kapsayıcı bu ağda benzersiz bir IP adresi alır ve birbiriyle IP adresleri veya kapsayıcı isimleri üzerinden iletişim kurabilir. Ancak, sadece IP adresleriyle çalışmak pratik değildir, özellikle de kapsayıcılar yeniden başlatıldığında IP adreslerinin değişebileceği düşünülürse. İşte bu yüzden Docker, DNS servis keşfi mekanizmalarıyla kapsayıcı isimlerini IP adreslerine çevirme yeteneği sunar, bu da uygulamalar arası iletişimi çok daha yönetilebilir hale getirir. Kapsayıcıların izole edilmiş ortamları, güvenlik açısından da önemli avantajlar sunar; her servis yalnızca ihtiyaç duyduğu ağ kaynaklarına erişebilir ve potansiyel güvenlik açıklarının yayılmasını engeller. Bu temel prensipler, Docker ağının karmaşık full-stack projelerde nasıl bir omurga görevi gördüğünü anlamak için kritik öneme sahiptir.

Docker Ağ Modları Nelerdir?

Docker, farklı ihtiyaçlara yönelik çeşitli ağ sürücüleri (network drivers) sunar. Bu sürücüler, kapsayıcılarınızın ağ davranışını ve izolasyon seviyesini belirler. Doğru ağ modunu seçmek, uygulamanızın performansı, güvenliği ve ölçeklenebilirliği açısından büyük fark yaratabilir. Gelin, en yaygın kullanılan ağ modlarına daha yakından bakalım:

  • Bridge Ağı (Varsayılan): Çoğu tek sunuculu Docker uygulaması için varsayılan ve en sık kullanılan ağ modudur. Docker, host üzerinde sanal bir köprü (docker0) oluşturur ve kapsayıcılara bu köprü üzerinden IP adresleri atar. Kapsayıcılar, birbirleriyle isimleri (DNS üzerinden) veya IP adresleri aracılığıyla iletişim kurabilir. Dış dünyaya erişim için port yönlendirme (port mapping) kullanılır. Kolay kurulumu ve iyi izolasyonu sayesinde küçük ve orta ölçekli full-stack projeler için idealdir.
  • Host Ağı: Bu modda kapsayıcı, kendi ağ yığını yerine Docker host’unun ağ yığınını doğrudan kullanır. Bu, kapsayıcının host ile aynı ağ arayüzlerini ve IP adresini paylaşması anlamına gelir. En yüksek ağ performansını sunar çünkü sanallaştırma katmanı atlanır. Ancak, izolasyon seviyesi en düşüktür ve aynı port numaralarını kullanan birden fazla kapsayıcı çalıştıramazsınız. Genellikle çok yüksek performans gerektiren veya ağ yapılandırmasının çok basit olduğu durumlarda tercih edilir.
  • None Ağı: Kapsayıcıya hiçbir ağ arayüzü atanmaz. Kapsayıcı tamamen ağdan izole olur ve dış dünya ile veya diğer kapsayıcılarla iletişim kuramaz. Bu, genellikle güvenlik testleri veya belirli bir görevi internet erişimi olmadan yapması gereken kapsayıcılar için kullanılır.
  • Overlay Ağı: Birden fazla Docker host’u (makine) arasında çalışan kapsayıcıların birbiriyle iletişim kurmasını sağlayan bir ağ sürücüsüdür. Docker Swarm veya Kubernetes gibi orkestrasyon araçlarıyla dağıtılmış uygulamalar için vazgeçilmezdir. Overlay ağı, birden fazla sunucu üzerindeki kapsayıcıları tek bir sanal ağda birleştirir, böylece servis keşfi ve iletişim sorunsuz bir şekilde gerçekleşir. Büyük ölçekli, mikroservis tabanlı projelerde bu ağ modu hayati bir rol oynar.
  • Macvlan Ağı: Kapsayıcılara doğrudan fiziksel ağ arayüzü üzerinden bir MAC adresi atar. Bu, kapsayıcıların fiziksel ağ üzerinde bağımsız bir cihaz gibi görünmesini sağlar. Genellikle eski sistemlerle uyumluluk veya ağ yöneticilerinin belirli ağ yapılandırmaları istediği senaryolarda kullanılır. Her kapsayıcı kendi benzersiz IP adresine sahip olur ve ağdaki diğer fiziksel cihazlar tarafından görülebilir.

Doğru ağ sürücüsünü seçmek, uygulamanızın gereksinimlerine bağlıdır. Tek bir host üzerinde çalışan çoğu full-stack proje için bridge ağı yeterli ve önerilen çözümdür. Ancak dağıtık sistemlere geçildiğinde veya özel ağ gereksinimleri olduğunda overlay veya macvlan gibi diğer sürücüler devreye girer. Her bir modun kendi avantajları ve dezavantajları vardır, bu yüzden projenizin ölçeğini, güvenlik ihtiyaçlarını ve performans beklentilerini göz önünde bulundurarak akıllıca bir seçim yapmak önemlidir.

Bridge Ağı ve Port Yönlendirme: Geleneksel Yaklaşım

Docker’da kapsayıcılar arası iletişimin en temel ve yaygın yollarından biri, varsayılan bridge ağını kullanmak ve dış dünyaya açılması gereken servisler için port yönlendirme yapmaktır. Bu yaklaşım, özellikle tek bir sunucu üzerinde çalışan full-stack uygulamalar için oldukça etkilidir ve kurulumu kolaydır. bridge ağı, adından da anlaşılacağı gibi, host işletim sistemi ile kapsayıcılar arasında bir köprü görevi görür. Docker, her yeni kapsayıcıya bu köprü ağı içinde benzersiz bir IP adresi atar. Kapsayıcılar bu IP adresleri üzerinden birbirleriyle doğrudan iletişim kurabilirler. Ancak, genellikle kapsayıcıların IP adreslerini ezberlemek veya yönetmek yerine, kapsayıcı isimleri üzerinden iletişim kurmak çok daha pratiktir. Docker’ın dahili DNS mekanizması sayesinde, aynı bridge ağına bağlı kapsayıcılar birbirlerini isimleriyle çözümleyebilirler. Örneğin, bir “veritabanı” kapsayıcısına “veritabanı” ismiyle bağlanabilirsiniz, IP adresini bilmenize gerek kalmaz.

Dış dünyadan kapsayıcılara erişim sağlamak için ise “port yönlendirme” (port mapping) kullanılır. Bu, host makinedeki belirli bir portun, kapsayıcı içindeki belirli bir porta yönlendirilmesi işlemidir. Örneğin, web sunucusu olarak çalışan bir Nginx kapsayıcısının 80 portunu, host’un 8080 portuna yönlendirebilirsiniz. Böylece dışarıdan http://host-ip:8080 adresine yapılan istekler, aslında kapsayıcı içindeki Nginx’in 80 portuna ulaşır. Bu, uygulamanızın ön yüzüne kullanıcıların erişmesini sağlarken, arka yüz ve veritabanı gibi diğer servislerin portlarını doğrudan dış dünyaya açmadan güvenli bir iç iletişim ağı kurmanıza olanak tanır. Bridge ağı, kapsayıcıların birbirinden izole çalışmasını sağlarken, aynı zamanda kontrollü ve güvenli bir iletişim kanalı oluşturur. Bu mekanizma, bir full-stack projesinin farklı bileşenlerini bir araya getirirken hem esneklik hem de güvenlik sağlar.

Pratik Senaryo: Nginx, Node.js ve MongoDB Bağlantısı

Şimdi bu kavramları basit bir full-stack uygulaması üzerinden uygulamalı olarak görelim. Senaryomuzda, bir Nginx web sunucusu (ön yüz statik dosyalarını sunacak), bir Node.js API’si (arka yüz) ve bir MongoDB veritabanı bulunuyor. Bu üç bileşeni ayrı Docker kapsayıcılarında çalıştırıp, birbirleriyle iletişim kurmalarını sağlayacağız.

Adım 1: MongoDB Kapsayıcısını Başlatma

Öncelikle veritabanı kapsayıcımızı başlatalım. Bu kapsayıcıya mongodb adını vereceğiz. Arka yüz uygulamamız bu isimle ona erişebilecek.


docker run -d --name mongodb -p 27017:27017 mongo:latest

  • -d: Kapsayıcıyı arka planda (detached mode) çalıştırır.
  • --name mongodb: Kapsayıcıya mongodb adını verir.
  • -p 27017:27017: Host'un 27017 portunu, kapsayıcının 27017 portuna yönlendirir. Bu, dışarıdan (örneğin bir GUI aracıyla) veritabanına erişmek isterseniz kullanışlıdır. Node.js uygulamamız içeriden erişeceği için bu yönlendirme doğrudan zorunlu değildir, ancak genellikle geliştirme ortamlarında faydalıdır.
  • mongo:latest: Kullanılacak Docker imajının adıdır.

Adım 2: Node.js API Kapsayıcısını Başlatma

Şimdi Node.js arka yüz uygulamamızı çalıştıralım. Bu uygulama MongoDB'ye bağlanacak ve dış dünyaya (Nginx'e) hizmet verecek. Kapsayıcımıza backend adını verelim ve mongodb kapsayıcısıyla aynı ağda olduğundan emin olalım.


docker run -d --name backend --network bridge -p 3000:3000 my-node-app:latest

Node.js uygulamanızın kodunda MongoDB'ye bağlanırken mongodb://mongodb:27017/my-database gibi bir bağlantı dizesi kullanmanız gerekecektir. Docker, mongodb adını otomatik olarak MongoDB kapsayıcısının IP adresine çözümleyecektir. Burada my-node-app:latest yerine kendi Node.js uygulamanızın Docker imaj adını kullanmalısınız. İmajı daha önce build ettiğinizi varsayıyoruz.


// Örnek Node.js bağlantı kodu (MongoDB için)
const mongoose = require('mongoose');

mongoose.connect('mongodb://mongodb:27017/my-database', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB\'ye başarıyla bağlandı!'))
.catch(err => console.error('MongoDB bağlantı hatası:', err));

  • --network bridge: Kapsayıcıyı varsayılan bridge ağına bağlar. (Zaten varsayılan olduğu için bu parametreyi kullanmasanız da aynı etkiyi yaratır.)
  • -p 3000:3000: Host'un 3000 portunu, kapsayıcının 3000 portuna yönlendirir. Bu sayede Nginx veya doğrudan tarayıcı üzerinden Node.js API'sine erişilebilir.

Adım 3: Nginx Kapsayıcısını Başlatma (Ön Yüz İçin)

Son olarak, Nginx web sunucumuzu çalıştırıp statik React ön yüz dosyalarımızı sunalım. Nginx, Node.js API'sine istekleri yönlendirmek için bir proxy görevi görecektir.


docker run -d --name frontend -p 80:80 -v /path/to/your/react/build:/usr/share/nginx/html:ro nginx:latest

Nginx yapılandırma dosyanızda (nginx.conf), Node.js arka yüzüne istekleri yönlendirmek için aşağıdaki gibi bir proxy ayarı yapmanız gerekecektir:


# nginx.conf içinden bir bölüm
server {
    listen 80;

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

    location /api/ {
        proxy_pass http://backend:3000; # Node.js kapsayıcısının ismi
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

  • -v /path/to/your/react/build:/usr/share/nginx/html:ro: Kendi React uygulamanızın build edilmiş dosyalarının bulunduğu host dizinini Nginx kapsayıcısının web kök dizinine bağlar. ro (read-only) sadece okuma izni verir.
  • -p 80:80: Host'un 80 portunu, kapsayıcının 80 portuna yönlendirir. Böylece tarayıcınızdan doğrudan host IP adresinize erişerek ön yüz uygulamanızı görebilirsiniz.

Bu adımlarla, her bir uygulamanızı ayrı kapsayıcılarda çalıştırdınız ve Docker'ın varsayılan bridge ağı ile port yönlendirmeyi kullanarak başarılı bir şekilde birbirleriyle iletişim kurmalarını sağladınız. Bu yöntem, Docker ağ iletişiminin temelini oluşturur ve birçok basit full-stack proje için yeterlidir. Ancak daha karmaşık veya çok bileşenli projelerde docker-compose gibi araçlar devreye girerek bu süreci çok daha kolay ve yönetilebilir hale getirir.

Docker Compose ile Full-Stack Ağ Yapılandırması: Orkestrasyonun Gücü

Önceki bölümde docker run komutlarıyla tek tek kapsayıcıları nasıl başlatıp bağladığımızı gördük. Bu yöntem, birkaç kapsayıcı için işe yarasa da, daha karmaşık full-stack projelerde onlarca kapsayıcıyı yönetmek, her birinin port yönlendirmesini, ağ ayarlarını ve bağımlılıklarını manuel olarak belirtmek oldukça yorucu ve hataya açık bir süreçtir. İşte tam da bu noktada Docker Compose devreye giriyor. Docker Compose, çok kapsayıcılı Docker uygulamalarını tanımlamak ve çalıştırmak için bir araçtır. Tek bir YAML dosyası kullanarak uygulamanızın tüm servislerini, ağlarını ve birimlerini (volumes) yapılandırabilir, ardından tek bir komutla (docker-compose up) tüm uygulamayı başlatabilirsiniz. Bu, bir full-stack projenin tüm bileşenlerini bir orkestra gibi yönetmenizi sağlar.

docker-compose.yml dosyası, uygulamanızın blueprint'i gibidir. Bu dosya içinde her bir servis için kullanılacak Docker imajını, hangi portların açılacağını, hangi ağlara bağlanacağını, hangi ortam değişkenlerinin ayarlanacağını ve diğer servislerle olan bağımlılıklarını tanımlarsınız. Docker Compose'un en büyük avantajlarından biri, varsayılan olarak tüm servisleri özel bir bridge ağına bağlamasıdır. Bu sayede servisler, birbirlerini isimleriyle çözümleyebilirler. Örneğin, bir "web" servisi, "api" servisine http://api:3000 adresi üzerinden doğrudan erişebilir. Bu DNS tabanlı servis keşfi, manuel IP yönetimi sorununu tamamen ortadan kaldırır ve uygulamanızın bileşenleri arasında esnek bir iletişim ortamı yaratır.

docker-compose ayrıca servislerin başlangıç sıralamasını yönetmek için depends_on gibi mekanizmalar sunar (ancak bu sadece başlangıç sırasını garanti eder, servisin tamamen hazır olmasını değil). Daha sağlam bağımlılık yönetimi için uygulamaların servislerin hazır olmasını beklemesi gereken "sağlık kontrolleri" (health checks) tanımlamak önemlidir. Kısacası, Docker Compose, full-stack projeler için kapsayıcılaştırmayı bir sonraki seviyeye taşıyan, geliştirme ve dağıtım süreçlerini büyük ölçüde basitleştiren güçlü bir araçtır. Onunla, karmaşık uygulamalarınızı tek bir çatı altında tanımlayıp, tutarlı ve tekrarlanabilir bir şekilde yönetebilirsiniz.

Uygulamalı Örnek: Docker Compose ile Full-Stack Uygulama

Önceki örneğimizdeki Nginx, Node.js ve MongoDB uygulamasını şimdi docker-compose.yml kullanarak yapılandıralım. Bu, uygulamanızın tüm parçalarını tek bir yerde tanımlamanın ve yönetmenin ne kadar kolay olduğunu gösterecek.

Proje dizininizde bir docker-compose.yml dosyası oluşturun:


version: '3.8'

services:
  mongodb:
    image: mongo:latest
    container_name: mongodb_container
    ports:
      - "27017:27017" # Sadece dışarıdan erişim için, Node.js içeriden erişecek
    volumes:
      - mongodb_data:/data/db
    networks:
      - my_app_network

  backend:
    build: ./backend # Node.js uygulamanızın Dockerfile'ının olduğu dizin
    container_name: backend_container
    ports:
      - "3000:3000"
    environment:
      MONGO_URI: "mongodb://mongodb:27017/my-database" # Hostname olarak servis adı kullanılır
    depends_on:
      - mongodb # mongodb servisi başlamadan backend başlamaz
    networks:
      - my_app_network

  frontend:
    build: ./frontend # Nginx ve React uygulamanızın Dockerfile'ının olduğu dizin
    container_name: frontend_container
    ports:
      - "80:80"
    depends_on:
      - backend # backend servisi başlamadan frontend başlamaz (proxy için)
    networks:
      - my_app_network

networks:
  my_app_network:
    driver: bridge # Özel bir bridge ağı tanımlarız

volumes:
  mongodb_data: # MongoDB verilerinin kalıcı depolanması için volume

Açıklamalar:

  • version: '3.8': Compose dosyasının versiyonunu belirtir.
  • services: Uygulamanızdaki her bir bileşeni (kapsayıcıyı) tanımladığınız ana bölümdür.
  • mongodb:
    • image: mongo:latest: MongoDB için Docker Hub'daki resmi imajı kullanır.
    • container_name: mongodb_container: Kapsayıcıya okunabilir bir isim verir.
    • ports: - "27017:27017": Host'un 27017 portunu kapsayıcının 27017 portuna yönlendirir.
    • volumes: - mongodb_data:/data/db: MongoDB verilerini kalıcı olarak saklamak için bir Docker volume'u kullanır. Bu sayede kapsayıcı silinse bile verileriniz kaybolmaz.
    • networks: - my_app_network: Bu servisi özel olarak tanımladığımız my_app_network ağına bağlar.
  • backend:
    • build: ./backend: Mevcut dizindeki backend klasöründe bulunan Dockerfile'ı kullanarak bir imaj oluşturur. Bu, Node.js uygulamanızın Docker imajını sizin oluşturacağınız anlamına gelir.
    • environment: MONGO_URI: "mongodb://mongodb:27017/my-database": Ortam değişkeni ayarlar. Node.js uygulamanızda bu değişkeni okuyarak MongoDB'ye bağlanabilirsiniz. Dikkat edin, host olarak mongodb servis adını kullanıyoruz. Docker Compose, bu adı otomatik olarak MongoDB kapsayıcısının IP adresine çözümleyecektir.
    • depends_on: - mongodb: backend servisi başlamadan önce mongodb servisinin başlatılmasını sağlar. Bu sadece başlangıç sırasını garanti eder.
    • networks: - my_app_network: Backend servisini de aynı özel ağa bağlar.
  • frontend:
    • build: ./frontend: frontend klasöründeki Dockerfile'ı kullanarak bir Nginx/React imajı oluşturur.
    • ports: - "80:80": Ön yüz uygulamanızın dışarıdan erişilebilir olmasını sağlar.
    • depends_on: - backend: Frontend'in backend'e bağımlı olduğunu belirtir.
    • networks: - my_app_network: Frontend servisini de aynı özel ağa bağlar.
  • networks: my_app_network: driver: bridge: Uygulamamız için özel bir bridge ağı tanımlar. Bu, tüm servislerin birbiriyle izole ve isimleriyle iletişim kurmasını sağlar.
  • volumes: mongodb_data:: Kullanılacak Docker volume'unu tanımlar.
Uzman İpucu: depends_on sadece kapsayıcıların başlatılma sırasını garanti eder, servisin gerçekten "hazır" olduğu anlamına gelmez. Özellikle veritabanları veya API'ler için, uygulamanızın içerisinde servislerin hazır olup olmadığını kontrol eden mekanizmalar (örneğin, bir bağlantı döngüsü veya sağlık kontrolü) bulundurmak, daha sağlam bir başlangıç süreci sağlar. Docker Compose ayrıca healthcheck direktifi ile servislerin sağlık durumunu izlemenize olanak tanır.

Çalıştırma:

docker-compose.yml dosyasını kaydettikten sonra, aynı dizinde terminali açın ve aşağıdaki komutu çalıştırın:


docker-compose up -d

  • up: docker-compose.yml dosyasında tanımlanan tüm servisleri oluşturur ve başlatır.
  • -d: Servisleri arka planda çalıştırır.

Uygulamanız artık çalışıyor olmalı! Tarayıcınızdan http://localhost adresine giderek ön yüz uygulamanızı görebilirsiniz. Tüm servisler kendi özel ağlarında birbiriyle konuşuyor olacak.

Durdurma ve Temizleme:

Uygulamayı durdurmak ve oluşturulan tüm kapsayıcıları, ağları ve (varsayılan olarak) volume'leri kaldırmak için:


docker-compose down

Bu komut, full-stack uygulamanızın tüm bileşenlerini temiz bir şekilde kapatır. Docker Compose ile bu kadar kolaylıkla karmaşık uygulamaları yönetebilirsiniz. Bu, geliştirme, test ve hatta küçük ölçekli üretim ortamlarında inanılmaz bir kolaylık sağlar.

İleri Düzey Ağ Konfigürasyonları ve Güvenlik İpuçları

Docker ağ iletişimi sadece varsayılan bridge ağı ve docker-compose ile sınırlı değildir. Daha karmaşık senaryolar, daha fazla kontrol ve güvenlik gerektiren durumlar için Docker gelişmiş konfigürasyon seçenekleri sunar. Bu bölümde, özel ağların oluşturulmasından, ağ güvenliğine ve performans ipuçlarına kadar bazı ileri düzey konulara değineceğiz.

Özel Bridge Ağları Oluşturma ve Yönetme

Yukarıdaki docker-compose örneğinde gördüğümüz gibi, kendi özel bridge ağlarınızı oluşturmak, kapsayıcılarınız için daha iyi bir izolasyon ve organizasyon sağlar. Her bir uygulama yığını (örneğin, bir microservice grubu) için ayrı bir ağ tanımlayabilirsiniz. Bu, bir ağdaki trafik ve olası güvenlik sorunlarının diğer ağlara sıçramasını engeller. Manuel olarak bir özel ağ oluşturmak için:


docker network create --driver bridge my_custom_network

Ardından, kapsayıcıları bu ağa bağlayabilirsiniz:


docker run -d --name my_app_container --network my_custom_network my_app_image:latest

Bu, docker-compose dosyasında networks anahtarını kullanarak yaptığımız işlemin manuel karşılığıdır. Büyük projelerde, farklı microservice gruplarını farklı özel ağlara yerleştirmek, ağ trafiğini daha iyi anlamanıza ve yönetmenize yardımcı olur.

Ağ Güvenliği: Kapsayıcı İzolasyonundan Fazlası

Docker'ın kapsayıcı izolasyonu, varsayılan olarak bir güvenlik katmanı sağlar. Ancak, özellikle dış dünyaya açık portlar söz konusu olduğunda ek güvenlik önlemleri almak önemlidir. Host üzerindeki güvenlik duvarı (firewall) kurallarını kullanarak sadece belirli IP adreslerinden veya belirli portlara erişime izin vermek kritik bir adımdır. Örneğin, sadece belirli bir IP adresinden uygulamanızın 80 portuna erişime izin verebilirsiniz.

Ayrıca, kapsayıcıların sadece ihtiyaç duydukları portları dış dünyaya açtığından emin olun. Geliştirme sırasında kullandığınız veritabanı portlarını (örneğin 27017 veya 5432) üretimde dış dünyaya açmaktan kaçının. Bu portlar genellikle sadece arka yüz servisleriniz tarafından iç ağdan erişilmelidir. Uygulama içi kimlik doğrulama, API anahtarları ve HTTPS kullanımı gibi standart web güvenlik uygulamaları, Docker ağ yapılandırmasından bağımsız olarak her zaman uygulanmalıdır.

Ağ segmentasyonu, özellikle mikroservis mimarilerinde, farklı servis gruplarını farklı ağlara yerleştirerek olası saldırı yüzeyini küçültmenin bir yoludur. Örneğin, veritabanı kapsayıcılarını sadece backend servislerinin erişebileceği ayrı bir "db-network" ağına koyabilirsiniz. Bu, frontend veya gateway servislerinin doğrudan veritabanına erişmesini engeller, böylece potansiyel güvenlik açıklarını azaltır.

Performans İpuçları ve Mobil Uyumluluk

Docker ağ performansını optimize etmek için bazı ipuçları bulunmaktadır. Örneğin, eğer geliştirme yapıyorsanız ve kapsayıcılar arasındaki gecikmeler sizi etkiliyorsa, Mac veya Windows üzerinde host.docker.internal DNS adını kullanarak host makinesindeki servislere erişebilirsiniz. Bu, host'unuzun IP adresini otomatik olarak çözümleyerek host'taki bir veritabanına veya başka bir servise bağlanmanızı kolaylaştırır.


// Host'taki bir servise bağlanırken
const DB_HOST = process.env.DB_HOST || 'host.docker.internal';
// Örneğin, 'host.docker.internal:5432'

Özellikle Windows ve macOS'ta Docker Desktop sanal bir makine üzerinde çalıştığı için ağ performansı Linux host'larına göre farklılık gösterebilir. Büyük dosya transferleri veya yoğun ağ trafiği olan uygulamalar için bu durum göz önünde bulundurulmalıdır.

Mobil uyumluluk ise genellikle front-end geliştirme alanına girse de, Docker kapsayıcınızda çalışan bir web uygulamasının mobil cihazlarda düzgün görüntülenmesi için responsive tasarım prensiplerini uygulamanız gerekir. Bu, CSS medya sorguları (media queries) kullanılarak farklı ekran boyutlarına göre stil değişiklikleri yapmayı içerir. Docker burada sadece uygulamanızı sunan bir platform görevi görürken, uygulamanın kendisi mobil uyumlu olmalıdır.


/* CSS dosyasında medya sorgusu örneği */
/* Mobil cihazlar için özel stil kuralları */
@media (max-width: 768px) {
    .navbar {
        flex-direction: column; /* Menü öğelerini alt alta sırala */
        align-items: flex-start;
    }
    .main-content {
        padding: 10px;
    }
}

/* Küçük tabletler ve masaüstü bilgisayarlar için */
@media (min-width: 769px) and (max-width: 1024px) {
    .sidebar {
        width: 200px;
    }
}

/* Büyük ekranlar için */
@media (min-width: 1025px) {
    .container {
        max-width: 1200px;
    }
}

Yukarıdaki CSS medya sorgusu örneği, bir web uygulamasının farklı cihaz ekran boyutlarına nasıl uyum sağlayabileceğini gösterir. Bu CSS, Nginx kapsayıcınız (veya hangi kapsayıcının statik dosyalarınızı sunduğu) tarafından sunulur ve tarayıcı tarafından yorumlanarak uygulamanızın mobil uyumlu görünmesini sağlar. Docker, bu responsive uygulamanın kapsayıcı içinde sorunsuz bir şekilde çalışmasını ve kullanıcılara sunulmasını sağlayan temel altyapıdır. Gelişmiş Docker ağ konfigürasyonları, projenizin özel ihtiyaçlarına göre esneklik ve güvenlik sağlarken, performans ipuçları ve mobil uyumluluk gibi diğer faktörler de tam bir geliştirme deneyimi için göz ardı edilmemelidir.

Vaka Analizi: Büyük Ölçekli Bir Mikroservis Uygulamasında Docker Ağ İletişimi

Şimdi daha karmaşık, gerçek dünya bir senaryoya odaklanalım: büyük ölçekli bir e-ticaret platformu. Bu platform, monolitik bir yapı yerine mikroservis mimarisini benimsiyor ve her biri kendi Docker kapsayıcısında çalışan çeşitli bağımsız servislerden oluşuyor. Amacımız, bu servislerin birbiriyle güvenli ve verimli bir şekilde iletişim kurmasını sağlamak, aynı zamanda dış dünyadan gelen isteklere yanıt verebilmesini temin etmek. Bu senaryo, Docker'ın ileri düzey ağ yeteneklerinin ne kadar kritik olduğunu net bir şekilde ortaya koyacaktır.

Uygulama Mimarisi:

E-ticaret platformumuzun temel bileşenleri şunlar:

  • API Gateway: Dış dünyadan gelen tüm istekleri karşılar ve ilgili mikroservislere yönlendirir.
  • Kullanıcı Servisi: Kullanıcı kaydı, girişi ve profil yönetimi gibi işlemlerden sorumlu.
  • Ürün Servisi: Ürün katalogu, stok yönetimi, ürün detayları gibi işlevleri sağlar.
  • Sipariş Servisi: Sipariş oluşturma, takibi ve yönetimi.
  • Ödeme Servisi: Kredi kartı işlemleri, ödeme entegrasyonları.
  • Veritabanları (MongoDB, PostgreSQL): Her servis kendi veritabanını kullanabilir veya paylaşımlı veritabanları olabilir. Bu senaryoda her servis kendi veritabanına sahip.
  • Önbellek (Redis): Hızlı veri erişimi için kullanılır.

Ağ Tasarımı Kararları:

Bu karmaşık yapıda tüm servisleri tek bir varsayılan bridge ağına atmak, hem güvenlik hem de yönetilebilirlik açısından uygun değildir. Bunun yerine, özel Docker ağları oluşturarak segmentasyon uygulayacağız:

  1. public-gateway-network:
    • Bu ağa sadece API Gateway kapsayıcısı bağlanır.
    • Dış dünyaya açık tek port (genellikle 80/443) bu ağ üzerinden API Gateway'e yönlendirilir.
    • Bu, uygulamanın dışarıya açık yüzeyini minimumda tutar ve olası saldırı noktalarını azaltır.
  2. backend-services-network:
    • Kullanıcı, Ürün, Sipariş ve Ödeme servisleri gibi tüm uygulama mikroservisleri bu ağa bağlanır.
    • API Gateway de bu ağa bağlanır, böylece mikroservislere isimleri üzerinden erişebilir (örn. http://user-service:3001/users).
    • Bu ağdaki servisler birbirleriyle doğrudan konuşabilir ancak dış dünyaya açık değildirler.
    • Önbellek servisi (Redis) de performans için bu ağa bağlanır.
  3. database-network:
    • MongoDB ve PostgreSQL gibi tüm veritabanı servisleri bu ağa bağlanır.
    • Sadece ilgili mikroservisler (örneğin, Kullanıcı Servisi -> Kullanıcı Veritabanı) bu ağa bağlanabilir. Diğer servislerin bu ağa erişimi yoktur.
    • Bu, veritabanı katmanına ekstra bir güvenlik ve izolasyon katmanı ekler.

docker-compose.yml Taslağı (Basit Örnekleme):

Bu senaryoyu tam olarak yansıtacak bir docker-compose.yml dosyası oldukça uzun olacaktır. Ancak temel yapıyı aşağıdaki gibi hayal edebiliriz:


version: '3.8'

services:
  api-gateway:
    build: ./api-gateway
    ports:
      - "80:80"
    networks:
      - public-gateway-network
      - backend-services-network # Backend servislerine erişmek için
    depends_on:
      - user-service
      - product-service
      # ... diğer servisler

  user-service:
    build: ./user-service
    environment:
      DB_HOST: user-db
    networks:
      - backend-services-network
      - database-network # Kendi veritabanına erişmek için
    depends_on:
      - user-db

  user-db:
    image: postgres:13
    volumes:
      - user_db_data:/var/lib/postgresql/data
    networks:
      - database-network

  product-service:
    build: ./product-service
    environment:
      DB_HOST: product-db
    networks:
      - backend-services-network
      - database-network
    depends_on:
      - product-db

  product-db:
    image: mongo:latest
    volumes:
      - product_db_data:/data/db
    networks:
      - database-network

  redis-cache:
    image: redis:latest
    networks:
      - backend-services-network

networks:
  public-gateway-network:
    driver: bridge
  backend-services-network:
    driver: bridge
  database-network:
    driver: bridge

volumes:
  user_db_data:
  product_db_data:

Bu Yapının Avantajları:

  • Güvenlik: Veritabanları ve iç servisler dış dünyadan tamamen izole edilmiştir. Sadece API Gateway, belirli bir port üzerinden dışarıya açıktır. Ağ segmentasyonu sayesinde, bir serviste yaşanabilecek güvenlik açığı diğer ağlara daha az yayılır.
  • İzolasyon ve Yönetilebilirlik: Her servis kendi bağımsız ağında ve kapsayıcısında çalıştığı için, bir serviste yapılan değişiklikler diğerini etkilemez. Her ağın kendi trafik akışı vardır, bu da sorun gidermeyi kolaylaştırır.
  • Ölçeklenebilirlik: Servisler birbirinden bağımsız çalıştığı için, yoğunluğa göre sadece ihtiyaç duyulan servisler ölçeklendirilebilir (örneğin, daha fazla ürün servisi kapsayıcısı eklenebilir). Docker Swarm veya Kubernetes gibi orkestrasyon araçları ile bu yapı daha da ileri taşınabilir.
  • Hizmet Keşfi: Docker'ın DNS tabanlı hizmet keşfi sayesinde, servisler birbirlerini doğrudan isimleriyle (örn. product-service, user-db) çözümleyebilirler. Bu, IP adresleri yerine anlamlı isimler kullanmayı mümkün kılar ve konfigürasyon yönetimini basitleştirir.

Bu vaka analizi, Docker ağının sadece basit kapsayıcı bağlantılarından ibaret olmadığını, aynı zamanda karmaşık, büyük ölçekli ve güvenli mikroservis mimarileri inşa etmek için nasıl güçlü bir temel oluşturduğunu göstermektedir. Doğru ağ tasarımı, bir full-stack projenin başarısı için kritik öneme sahiptir.

Sonuç ve Sıkça Sorulan Sorular (SSS)

Docker, modern full-stack uygulama geliştirmenin ve dağıtımının temel taşlarından biri haline gelmiştir. Bu makale boyunca, Docker kapsayıcılarının birbiriyle nasıl iletişim kurduğunu, temel ağ modlarından başlayarak docker run ile manuel bağlantıya, ardından Docker Compose ile çok kapsayıcılı uygulamaların kolayca yönetilmesine kadar birçok konuyu ele aldık. Gördük ki, ister basit bir üç katmanlı uygulama ister karmaşık bir mikroservis mimarisi olsun, Docker'ın ağ yetenekleri, uygulamanın farklı bileşenlerini izole edilmiş ancak iletişim kurabilir bir ortamda çalıştırmak için vazgeçilmezdir. Özel ağlar oluşturarak güvenlik, performans ve yönetilebilirliği artırabilir, docker-compose gibi araçlarla dağıtım süreçlerini otomatikleştirebilirsiniz. Unutmayın, Docker ağını iyi anlamak, full-stack projelerinizin sorunsuz, ölçeklenebilir ve güvenli bir şekilde çalışmasını sağlamanın anahtarıdır.

Sıkça Sorulan Sorular (SSS)

1. Docker Compose kullanmadan kapsayıcıları nasıl bağlarım?

Manuel olarak kapsayıcıları bağlamak için docker run komutunu kullanabilirsiniz. Aynı özel bridge ağına --network parametresiyle bağlayabilir ve docker run --link (eski ve önerilmeyen) veya daha iyisi, kapsayıcı isimleri aracılığıyla DNS çözümlemesini kullanabilirsiniz. Örneğin, docker run --name db postgres ve sonra docker run --name app --network container:db my-app (aynı ağ yığını) ya da docker run --name app --network bridge --env DB_HOST=db my-app (aynı bridge ağında isimle çözümleme) şeklinde kullanılabilir. Ancak, çoklu kapsayıcılar için docker-compose çok daha pratiktir.

2. Farklı sunuculardaki Docker kapsayıcılarını nasıl bağlarım?

Farklı sunuculardaki kapsayıcıları bağlamak için overlay ağ sürücüsünü kullanmanız gerekir. Bu genellikle Docker Swarm veya Kubernetes gibi konteyner orkestrasyon araçları ile birlikte kullanılır. Bu araçlar, birden fazla sunucu (node) üzerinde çalışan kapsayıcılar arasında sorunsuz bir sanal ağ oluşturmanıza olanak tanır.

3. Docker ağında güvenlik ipuçları nelerdir?

Önemli ipuçları şunlardır: Yalnızca kesinlikle gerekli olan portları dış dünyaya açın. Uygulama katmanında güçlü kimlik doğrulama ve yetkilendirme mekanizmaları kullanın. Host makinenizde bir güvenlik duvarı (firewall) yapılandırarak Docker tarafından açılan portları kısıtlayın. Veritabanı ve iç servisler için özel, izole ağlar oluşturarak ağ segmentasyonu uygulayın. En düşük yetki prensibini benimseyerek kapsayıcılara sadece ihtiyaç duydukları kaynaklara erişim izni verin.

4. docker-compose restart veya up komutu kapsayıcıların IP adreslerini değiştirir mi?

Evet, bir kapsayıcı yeniden oluşturulduğunda veya docker-compose up komutuyla güncellendiğinde, genellikle yeni bir IP adresi alır. Ancak bu durum, docker-compose'un sunduğu DNS tabanlı servis keşfi sayesinde bir sorun teşkil etmez. Servisler, birbirlerine IP adresleri yerine docker-compose.yml dosyasında tanımlanan servis adlarıyla (örn. backend veya mongodb) eriştiği için, IP adresindeki değişiklikler uygulamayı etkilemez.

5. Uygulama kapsayıcım veritabanı kapsayıcısına neden bağlanamıyor?

Bu yaygın bir sorundur ve birkaç nedeni olabilir:

  1. Aynı ağda değiller: Her iki kapsayıcının da aynı Docker ağına bağlı olduğundan emin olun. docker network ls ve docker inspect komutlarıyla kontrol edebilirsiniz.
  2. Yanlış hostname/IP: Uygulama kodunuzda veritabanının hostname'ini doğru yazdığınızdan emin olun. docker-compose kullanıyorsanız servis adını (db, mongodb vb.) kullanmalısınız. Manuel çalıştırıyorsanız, --link veya DNS adlarını (eğer aynı özel ağda iseler) kullanın.
  3. Port açık değil: Veritabanı kapsayıcısının doğru portu dinlediğinden ve o portun ağ içinde erişilebilir olduğundan emin olun.
  4. Veritabanı hazır değil: Veritabanı kapsayıcısı başlamış olsa bile, veritabanı servisinin tam olarak başlatılması ve bağlantıları kabul etmeye hazır olması biraz zaman alabilir. Uygulama kodunuzda veya docker-compose dosyasında (örneğin healthcheck veya uygulama içi yeniden deneme mantığı ile) bu durumu ele almanız gerekebilir.
  5. Güvenlik Duvarı: Host makinenizdeki bir güvenlik duvarı, kapsayıcılar arası iletişimi engelliyor olabilir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.