Caddy ile Go Uygulaması ve API Sunumu

Modern web geliştirme dünyasında Go dilinin popülaritesi giderek artıyor. Yüksek performanslı API’lar ve web uygulamaları geliştirmek için mükemmel bir seçenek olan Go, doğru bir reverse proxy ile birleşince gerçekten güçlü bir altyapı oluşturuyor. Caddy ise bu altyapının ön yüzünde durmak için belki de en ideal web sunucusu. Otomatik HTTPS, sade konfigürasyon sözdizimi ve Go ile yazılmış olması nedeniyle bu ikilinin birlikte çalışması hem teknik hem de pratik açıdan çok mantıklı.

Bu yazıda gerçek dünya senaryolarıyla Caddy’yi bir Go uygulamasının ve REST API’ının önüne nasıl koyacağımızı adım adım inceleyeceğiz. Basit bir uygulamadan başlayıp çoklu servis yönetimi, sağlık kontrolleri ve üretim ortamına hazır yapılandırmaya kadar her şeyi ele alacağız.

Neden Caddy ve Go Birlikte?

Go uygulamaları genellikle kendi içinde bir HTTP sunucusu çalıştırır. Örneğin net/http paketi veya Gin, Echo, Fiber gibi framework’ler kullanılarak yazılmış bir uygulama, doğrudan bir port dinleyebilir. Ancak bu uygulamayı doğrudan 80 veya 443 portuna koymak güvenlik ve yönetim açısından pek akıllıca değil.

İşte burada Caddy devreye giriyor:

  • Otomatik TLS: Let’s Encrypt sertifikası otomatik alır ve yeniler
  • Sıfır konfigürasyon karmaşıklığı: Nginx veya Apache’ye kıyasla çok daha sade
  • HTTP/2 ve HTTP/3 desteği: Kutudan çıkar çıkmaz aktif
  • Güvenlik başlıkları: Kolayca eklenebilir middleware’ler
  • Load balancing: Birden fazla Go instance’ını kolayca yönetebilirsin

Ortamı Hazırlama

Önce temel kurulumları yapalım. Ubuntu 22.04 veya Debian tabanlı bir sistem kullandığımızı varsayıyoruz.

Caddy Kurulumu

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | 
  sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | 
  sudo tee /etc/apt/sources.list.d/caddy-stable.list

sudo apt update && sudo apt install caddy

Kurulum sonrası Caddy’nin çalışıp çalışmadığını kontrol et:

systemctl status caddy
caddy version

Örnek Go Uygulaması Oluşturma

Gerçekçi bir senaryo için hem statik dosya sunan hem de REST API endpoint’leri içeren bir Go uygulaması yazalım. Bu uygulama bir ürün yönetim API’si olacak.

mkdir -p /opt/myapp
cd /opt/myapp
go mod init myapp

Ardından main.go dosyasını oluştur:

cat > /opt/myapp/main.go << 'EOF'
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    "time"
)

type Product struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Price     float64   `json:"price"`
    CreatedAt time.Time `json:"created_at"`
}

type HealthResponse struct {
    Status    string    `json:"status"`
    Timestamp time.Time `json:"timestamp"`
    Version   string    `json:"version"`
}

var products = []Product{
    {ID: 1, Name: "Laptop", Price: 1299.99, CreatedAt: time.Now()},
    {ID: 2, Name: "Mouse", Price: 29.99, CreatedAt: time.Now()},
    {ID: 3, Name: "Keyboard", Price: 79.99, CreatedAt: time.Now()},
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(HealthResponse{
        Status:    "healthy",
        Timestamp: time.Now(),
        Version:   "1.0.0",
    })
}

func productsHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(products)
}

func main() {
    port := os.Getenv("APP_PORT")
    if port == "" {
        port = "8080"
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/health", healthHandler)
    mux.HandleFunc("/api/products", productsHandler)

    log.Printf("Server starting on port %s", port)
    if err := http.ListenAndServe(":"+port, mux); err != nil {
        log.Fatal(err)
    }
}
EOF

go build -o myapp .

Uygulamayı systemd ile yönetelim ki sunucu yeniden başladığında otomatik devreye girsin:

cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
RestartSec=5
Environment=APP_PORT=8080
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp

Temel Caddyfile Yapılandırması

Go uygulamamız 8080 portunda çalışıyor. Şimdi Caddy’yi bu uygulamanın önüne koyalım.

cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
    reverse_proxy localhost:8080
}
EOF

systemctl reload caddy

Bu kadar. Evet, gerçekten bu kadar. Caddy otomatik olarak Let’s Encrypt’ten sertifika alacak ve HTTPS üzerinden trafiği Go uygulamanıza yönlendirecek. Nginx’te bu işlem için en az 20-30 satır konfigürasyon gerekirdi.

Gelişmiş Reverse Proxy Yapılandırması

Temel yapılandırma işe yarasa da üretim ortamı için daha fazlasına ihtiyacımız var. Header yönetimi, timeout değerleri ve sağlık kontrolleri ekleyelim.

cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
    # Reverse proxy ana yapılandırması
    reverse_proxy localhost:8080 {
        # Timeout değerleri
        transport http {
            dial_timeout 5s
            response_header_timeout 30s
            read_timeout 60s
            write_timeout 60s
        }

        # Sağlık kontrolü
        health_uri /health
        health_interval 10s
        health_timeout 5s
        health_status 200

        # Header yönetimi - upstream'e gönderilecekler
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
        header_up X-Request-ID {uuid}
    }

    # Güvenlik başlıkları
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        X-XSS-Protection "1; mode=block"
        Referrer-Policy strict-origin-when-cross-origin
        -Server
    }

    # Rate limiting - brute force koruması
    rate_limit {remote_host} 100r/m

    # Erişim logları
    log {
        output file /var/log/caddy/api.sirketim.com.log {
            roll_size 50mb
            roll_keep 10
        }
        format json
    }
}
EOF

systemctl reload caddy

Buradaki health_uri direktifi çok önemli. Caddy düzenli olarak /health endpoint’ini kontrol eder ve uygulama yanıt vermiyorsa 502 döndürmek yerine beklemeye geçer.

Çoklu Go Servisi Yönetimi

Gerçek dünya senaryolarında genellikle birden fazla mikro servisiniz olur. Diyelim ki bir e-ticaret platformu kuruyorsunuz: ürün servisi, kullanıcı servisi ve sipariş servisi ayrı Go uygulamaları olarak çalışıyor.

Önce her servis için systemd unit dosyaları oluşturalım:

# Kullanıcı servisi için
cat > /etc/systemd/system/user-service.service << 'EOF'
[Unit]
Description=User Microservice
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/user-service
ExecStart=/opt/user-service/user-service
Restart=on-failure
Environment=APP_PORT=8081
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

# Sipariş servisi için
cat > /etc/systemd/system/order-service.service << 'EOF'
[Unit]
Description=Order Microservice
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/order-service
ExecStart=/opt/order-service/order-service
Restart=on-failure
Environment=APP_PORT=8082
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now user-service order-service

Şimdi Caddyfile’ı path bazlı yönlendirme yapacak şekilde güncelleyelim:

cat > /etc/caddy/Caddyfile << 'EOF'
# Ana API gateway
api.sirketim.com {

    # Ürün servisi
    handle /api/products* {
        reverse_proxy localhost:8080 {
            health_uri /health
            health_interval 15s
            header_up X-Service "product"
        }
    }

    # Kullanıcı servisi
    handle /api/users* {
        reverse_proxy localhost:8081 {
            health_uri /health
            health_interval 15s
            header_up X-Service "user"
        }
    }

    # Sipariş servisi
    handle /api/orders* {
        reverse_proxy localhost:8082 {
            health_uri /health
            health_interval 15s
            header_up X-Service "order"
        }
    }

    # API dokümantasyonu için statik dosyalar
    handle /docs* {
        root * /opt/api-docs
        file_server
    }

    # Tanımsız path'ler için 404
    handle {
        respond "Not Found" 404
    }

    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        -Server
    }

    log {
        output file /var/log/caddy/api.log
        format json
    }
}
EOF

systemctl reload caddy

Bu yapılandırma tek bir domain üzerinden birden fazla mikro servisi yönetmeni sağlıyor. Path bazlı yönlendirme sayesinde istemciler farklı portları bilmek zorunda kalmıyor.

Load Balancing ile Yüksek Erişilebilirlik

Kritik uygulamalarda tek instance yetmez. Caddy’nin load balancing özelliğini kullanarak aynı Go uygulamasının birden fazla instance’ını çalıştırabilirsin.

# İkinci ve üçüncü instance için servis dosyaları
for port in 8083 8084; do
    cat > /etc/systemd/system/myapp-${port}.service << EOF
[Unit]
Description=My App Instance ${port}
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
Environment=APP_PORT=${port}
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF
done

systemctl daemon-reload
systemctl enable --now myapp-8083 myapp-8084

Caddyfile’ı load balancing için güncelleyelim:

cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
    reverse_proxy localhost:8080 localhost:8083 localhost:8084 {
        # Load balancing stratejisi
        lb_policy least_conn

        # Sağlık kontrolü
        health_uri /health
        health_interval 10s
        health_timeout 3s
        health_status 200

        # Başarısız upstream'i devre dışı bırakma süresi
        fail_duration 30s
        unhealthy_request_count 3

        # Aktif pasif failover
        max_fails 3

        transport http {
            dial_timeout 3s
            response_header_timeout 25s
        }
    }

    log {
        output file /var/log/caddy/api.log
        format json
    }
}
EOF

systemctl reload caddy

lb_policy direktifi için seçenekler şunlar:

  • round_robin: Sırayla dağıt, varsayılan seçenek
  • least_conn: En az bağlantısı olan upstream’e yönlendir
  • ip_hash: Aynı IP her zaman aynı backend’e gitsin, session affinity için kullanışlı
  • random: Rastgele seç
  • first: Sağlıklı olduğu sürece hep ilk backend’i kullan, aktif-pasif senaryosu için ideal

CORS Yapılandırması

Modern Single Page Application’lar için CORS başlıklarını doğru ayarlamak şart. Caddy’de bunu birkaç satırla halledebilirsin.

cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
    @cors_preflight {
        method OPTIONS
        header Origin *
    }

    @cors {
        header Origin *
    }

    handle @cors_preflight {
        header Access-Control-Allow-Origin "{header.origin}"
        header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID"
        header Access-Control-Max-Age "3600"
        respond "" 204
    }

    handle @cors {
        header Access-Control-Allow-Origin "{header.origin}"
        header Access-Control-Allow-Credentials "true"
        reverse_proxy localhost:8080
    }

    handle {
        reverse_proxy localhost:8080
    }
}
EOF

Eğer sadece belirli domain’lerden gelen isteklere izin vermek istiyorsan daha kısıtlayıcı bir yaklaşım kullanabilirsin:

@allowed_origins {
    header Origin "https://app.sirketim.com"
    header Origin "https://admin.sirketim.com"
}

Authentication Middleware

API’larını JWT ile korumak yaygın bir senaryo. Caddy’de forward_auth direktifi ile harici bir auth servisi kullanabilirsin.

cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
    # Public endpoint'ler auth gerektirmiyor
    handle /api/auth* {
        reverse_proxy localhost:8080
    }

    handle /health {
        reverse_proxy localhost:8080
    }

    # Korumalı endpoint'ler
    handle /api/* {
        forward_auth localhost:9000 {
            uri /verify
            copy_headers X-User-ID X-User-Role X-Tenant-ID
            header_up Authorization {http.request.header.Authorization}
        }
        reverse_proxy localhost:8080
    }
}
EOF

Bu yapılandırmada 9000 portundaki bir auth servis /verify endpoint’ini kontrol eder. 200 dönerse istek Go uygulamasına geçer, başka bir kod dönerse istemciye iletilir. copy_headers ile auth servisin set ettiği başlıklar downstream uygulamaya iletilir.

Statik Dosya Sunumu ile Hibrit Yapı

Pek çok Go uygulaması hem API hem de statik dosya sunar. Tek sayfalık uygulamalarda React veya Vue ile derlenen dosyaları Caddy üzerinden sunmak, Go uygulamasının yükünü azaltır.

cat > /etc/caddy/Caddyfile << 'EOF'
sirketim.com {
    # API isteklerini Go'ya yönlendir
    handle /api/* {
        reverse_proxy localhost:8080 {
            health_uri /health
            health_interval 10s
            transport http {
                dial_timeout 3s
            }
        }
    }

    # WebSocket desteği
    handle /ws/* {
        reverse_proxy localhost:8080 {
            header_up Upgrade {http.request.header.Upgrade}
            header_up Connection "Upgrade"
        }
    }

    # Statik dosyalar - React/Vue build output
    handle {
        root * /var/www/sirketim.com
        try_files {path} /index.html
        file_server
    }

    # Sıkıştırma
    encode gzip zstd

    # Cache başlıkları
    @static {
        path *.js *.css *.png *.jpg *.svg *.woff2
    }
    header @static Cache-Control "public, max-age=31536000, immutable"

    log {
        output file /var/log/caddy/sirketim.com.log
        format json
    }
}
EOF

systemctl reload caddy

try_files {path} /index.html satırı SPA’lar için kritik. Client-side routing kullandığında, örneğin /dashboard/settings gibi bir path’e direkt erişildiğinde dosya bulunamaz. Bu direktif sayesinde Caddy bulunamayan path’ler için index.html‘i döndürür ve React/Vue router devralır.

Caddy API ile Dinamik Yapılandırma

Caddy’nin en güçlü özelliklerinden biri çalışırken konfigürasyon değişikliği yapabilmesi. Bunun için Caddy Admin API’sini kullanabilirsin.

# Mevcut yapılandırmayı JSON olarak al
curl -s localhost:2019/config/ | python3 -m json.tool

# Caddy'nin durumunu kontrol et
curl -s localhost:2019/load

# Bir route'u dinamik olarak ekle
curl -X POST localhost:2019/config/apps/http/servers/srv0/routes 
  -H "Content-Type: application/json" 
  -d '{
    "match": [{"host": ["yeni.sirketim.com"]}],
    "handle": [{
      "handler": "reverse_proxy",
      "upstreams": [{"dial": "localhost:9090"}]
    }]
  }'

Bu özellik özellikle otomatik deployment pipeline’larında çok işe yarıyor. Yeni bir servis deploy ettiğinde CI/CD sisteminiz Caddy API’sini çağırarak routing kurallarını güncelleyebilir, herhangi bir reload yapmadan.

Loglama ve Monitoring

Üretimde neler döndüğünü anlamak için iyi bir loglama stratejisi şart.

cat > /etc/caddy/Caddyfile << 'EOF'
{
    # Global log seviyesi
    log {
        level INFO
    }
}

api.sirketim.com {
    reverse_proxy localhost:8080

    log {
        output file /var/log/caddy/access.log {
            roll_size 100mb
            roll_keep 14
            roll_keep_for 720h
        }
        format json {
            time_format iso8601
        }
        # Sağlık kontrolü loglarını filtrele
        except_fields request>uri
    }
}
EOF

JSON formatındaki logları analiz etmek için birkaç pratik komut:

# Son 100 isteği görüntüle
tail -n 100 /var/log/caddy/access.log | python3 -m json.tool

# 5xx hatalarını filtrele
cat /var/log/caddy/access.log | 
  jq 'select(.status >= 500)' | 
  jq '{time: .ts, status: .status, uri: .request.uri, duration: .duration}'

# Yavaş istekleri bul (1 saniyeden uzun)
cat /var/log/caddy/access.log | 
  jq 'select(.duration > 1.0)' | 
  jq '{uri: .request.uri, duration: .duration, status: .status}'

# IP bazlı istek sayısı
cat /var/log/caddy/access.log | 
  jq -r '.request.remote_addr' | 
  cut -d: -f1 | sort | uniq -c | sort -rn | head -20

Güvenlik Sertleştirme

Üretim ortamına geçmeden önce birkaç önemli güvenlik ayarı daha yapalım:

cat > /etc/caddy/Caddyfile << 'EOF'
{
    # Admin API'yi sadece localhost'ta aç
    admin localhost:2019

    # Email adresi Let's Encrypt için
    email [email protected]

    # Global timeout
    servers {
        timeouts {
            read_body 10s
            read_header 10s
            write 30s
            idle 120s
        }
    }
}

api.sirketim.com {
    reverse_proxy localhost:8080

    # İstek boyutu limiti (10MB)
    request_body {
        max_size 10MB
    }

    # Belirli IP'leri engelle
    @blocked_ips {
        remote_ip 192.0.2.0/24 203.0.113.50
    }
    abort @blocked_ips

    # Sadece belirli metodlara izin ver
    @allowed_methods {
        not method GET POST PUT DELETE PATCH OPTIONS HEAD
    }
    respond @allowed_methods "Method Not Allowed" 405

    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        X-XSS-Protection "1; mode=block"
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        Content-Security-Policy "default-src 'self'"
        Referrer-Policy strict-origin-when-cross-origin
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server
        -X-Powered-By
    }
}
EOF

systemctl reload caddy

Troubleshooting İpuçları

Bir şeyler ters gittiğinde ilk başvuru noktaların bunlar olmalı:

# Caddyfile sözdizimini kontrol et
caddy validate --config /etc/caddy/Caddyfile

# Caddy loglarını canlı izle
journalctl -u caddy -f

# Caddy'nin aldığı konfigürasyonu kontrol et
caddy adapt --config /etc/caddy/Caddyfile --pretty

# TLS sertifika durumunu kontrol et
curl -s localhost:2019/config/apps/tls | python3 -m json.tool

# Upstream bağlantısını test et
curl -v http://localhost:8080/health

# Caddy üzerinden test et
curl -v https://api.sirketim.com/health

Sık karşılaşılan sorunlar ve çözümleri:

  • 502 Bad Gateway: Go uygulaması çalışmıyor veya yanlış portta. systemctl status myapp ve ss -tlnp | grep 8080 ile kontrol et.
  • Sertifika hatası: DNS kaydı doğru mu? dig api.sirketim.com ile kontrol et. Caddy sertifikayı alabilmek için 80 ve 443 portlarının açık olması gerekiyor.
  • Rate limit sorunları: Geliştirme sırasında kendi IP’nin engellenip engellenmediğini caddy logs ile kontrol et.
  • WebSocket bağlantı kopması: keep_alive ve timeout değerlerini artır, header_up Connection "Upgrade" direktifinin var olduğundan emin ol.

Sonuç

Caddy ve Go kombinasyonu modern bir web altyapısı için gerçekten güçlü bir seçenek. Bu yazıda ele aldığımız konuları özetleyecek olursak: temel reverse proxy kurulumundan başlayıp çoklu mikro servis yönetimi, load balancing, CORS, authentication middleware, statik dosya sunumu ve güvenlik sertleştirmeye kadar pek çok konuyu işledik.

Nginx veya Apache’den geçiş yapmayı düşünüyorsan Caddy’nin en büyük avantajının konfigürasyon sadeliği olduğunu unutma. 200 satır Nginx konfigürasyonuyla yapacağın şeyi Caddy’de 20 satırda yapabiliyorsun. Otomatik TLS yönetimi ise tek başına büyük bir zaman tasarrufu sağlıyor.

Go uygulamalarıyla ilgili önemli bir hatırlatma: uygulamanın /health endpoint’ini mutlaka implement et. Caddy’nin sağlık kontrolü mekanizması hem load balancing hem de monitoring için bu endpoint’e güveniyor. Graceful shutdown için de Go’nun sinyal yakalama mekanizmasını doğru kullanmak, sıfır downtime deployment’lar için kritik öneme sahip.

Üretim ortamına geçmeden önce mutlaka caddy validate ile konfigürasyonunu test et ve tüm timeout değerlerini uygulamanın gerçek performans karakteristiklerine göre ayarla. İyi sunucular dalgın yöneticileri affeder ama dalgın sysadminleri iyi yapılandırma kurtarır.

Bir yanıt yazın

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