Caddy ile Kubernetes Ingress Controller Kurulumu ve Yapılandırması

Kubernetes ortamlarında ingress yönetimi genellikle NGINX ya da Traefik ile özdeşleşmiş durumda. Ama son yıllarda Caddy’nin bu alana girişi ve özellikle otomatik TLS yönetimi konusundaki üstünlükleri, pek çok ekibin dikkatini çekmeye başladı. Eğer Caddy’nin sadeliğini ve otomatik HTTPS özelliğini Kubernetes cluster’ınıza taşımak istiyorsanız, doğru yerdesiniz.

Caddy’nin Kubernetes’te Kullanım Mantığı

Caddy, geleneksel ingress controller’lardan farklı bir yaklaşım sergiliyor. NGINX’in karmaşık annotation yığınlarına ya da Traefik’in middleware zincirlerine alışkınsanız, Caddy’nin Caddyfile tabanlı konfigürasyon anlayışı başta biraz farklı gelecek. Ama bir kez oturttuğunuzda, özellikle küçük ve orta ölçekli cluster’larda son derece verimli çalıştığını göreceksiniz.

Kubernetes’te Caddy’yi ingress controller olarak kullanmanın iki temel yolu var:

  • caddy-ingress-controller: Resmi Caddy ekibi tarafından geliştirilen ve aktif olarak devam eden proje
  • Manuel Caddy deployment: Caddy’yi bir DaemonSet ya da Deployment olarak ayağa kaldırıp ConfigMap üzerinden yönetme

Bu yazıda her iki yaklaşıma da bakacağız, ama ağırlıklı olarak caddy-ingress-controller üzerinde duracağız çünkü production ortamlar için bu yol çok daha mantıklı.

Ön Gereksinimler

Başlamadan önce şunlara ihtiyacınız var:

  • Çalışan bir Kubernetes cluster’ı (v1.19+)
  • kubectl ve helm kurulu
  • Bir domain adı (otomatik TLS için)
  • Cluster’ınıza dışarıdan erişim sağlayan bir LoadBalancer ya da NodePort

Ortamınızı test etmek için:

kubectl version --client
helm version
kubectl get nodes

Cluster node’larının Ready durumunda olduğunu ve en az bir worker node bulunduğunu doğrulayın.

Caddy Ingress Controller Kurulumu

Helm ile Kurulum

En hızlı yol Helm chart kullanmak. Önce repo’yu ekleyelim:

helm repo add caddy https://caddyserver.github.io/ingress/
helm repo update

Basit bir kurulum için:

kubectl create namespace caddy-system

helm install caddy-ingress-controller caddy/caddy-ingress-controller 
  --namespace caddy-system 
  --set [email protected] 
  --set ingressController.config.acmeCA=https://acme-v02.api.letsencrypt.org/directory

Kurulum tamamlandıktan sonra pod’ların ayağa kalktığını kontrol edin:

kubectl get pods -n caddy-system
kubectl get svc -n caddy-system

LoadBalancer tipi servis oluşturulmuş olmalı ve external IP atanmış olmalı. Cloud ortamında bu IP cloud provider’ınızdan gelir. Bare-metal kurulumda MetalLB ya da benzeri bir çözüm gerekir.

Manuel YAML ile Kurulum

Helm kullanmak istemeyenler ya da konfigürasyonu daha ince ayarlamak isteyenler için YAML dosyaları üzerinden gidebiliriz. Önce namespace ve ServiceAccount oluşturalım:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: caddy-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: caddy-ingress-controller
  namespace: caddy-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: caddy-ingress-controller
rules:
- apiGroups: [""]
  resources: ["endpoints", "services", "secrets", "configmaps", "namespaces"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses", "ingressclasses"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses/status"]
  verbs: ["update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: caddy-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: caddy-ingress-controller
subjects:
- kind: ServiceAccount
  name: caddy-ingress-controller
  namespace: caddy-system
EOF

Bu RBAC tanımları Caddy’nin cluster içindeki kaynakları okuyabilmesi için gerekli. Özellikle ingresses kaynağını izleyebilmesi kritik.

IngressClass Tanımı

Kubernetes 1.18’den itibaren IngressClass kavramı zorunlu hale geldi. Caddy için bunu tanımlıyoruz:

kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: caddy
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: caddy.ingress.kubernetes.io/ingress-controller
EOF

is-default-class: "true" annotation’ını dikkatli kullanın. Bu, ingressClassName belirtilmemiş tüm Ingress kaynaklarının otomatik olarak Caddy’ye yönlendirilmesi anlamına gelir. Eğer cluster’ınızda birden fazla ingress controller varsa bu annotation’ı kaldırın.

İlk Uygulama Deployment’ı

Teoriyi bir kenara bırakıp gerçek bir senaryoya geçelim. Diyelim ki bir web uygulamanızı ve bir API servisinizi aynı cluster’da çalıştırıyorsunuz. Her ikisini de farklı subdomain’ler üzerinden dışarıya açmak istiyorsunuz.

Önce örnek uygulamaları ayağa kaldıralım:

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web-app
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-app-svc
  namespace: default
spec:
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
      - name: api-service
        image: hashicorp/http-echo:alpine
        args:
        - "-text=Merhaba API!"
        ports:
        - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: api-service-svc
  namespace: default
spec:
  selector:
    app: api-service
  ports:
  - port: 80
    targetPort: 5678
EOF

Şimdi bu servisleri dışarıya açan Ingress kaynağını oluşturalım:

kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: production-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: caddy
spec:
  ingressClassName: caddy
  rules:
  - host: app.sirketiniz.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-app-svc
            port:
              number: 80
  - host: api.sirketiniz.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service-svc
            port:
              number: 80
EOF

Ingress oluştuktan birkaç dakika içinde Caddy Let’s Encrypt’ten otomatik olarak sertifika alır. Bu süreci takip etmek için:

kubectl logs -n caddy-system -l app.kubernetes.io/name=caddy-ingress-controller -f

Logları izlerken certificate obtained successfully mesajını görmelisiniz.

Otomatik TLS ve Sertifika Yönetimi

Caddy’nin en büyük avantajı olan otomatik TLS konusuna biraz daha derinlemesine bakalım. Caddy, Let’s Encrypt ile iletişim kurarken iki farklı challenge mekanizması kullanabilir:

  • HTTP-01 Challenge: 80 numaralı port üzerinden. Cluster’ınızın 80 portuna dışarıdan erişilebilmesi gerekiyor.
  • DNS-01 Challenge: DNS sağlayıcınız üzerinden. Port açmaya gerek yok, wildcard sertifikalar için ideal.

DNS-01 challenge için Caddy’yi özelleştirmek biraz daha fazla çaba gerektiriyor. Cloudflare kullananlar için değerleri Helm values dosyasına ekleyelim:

cat > caddy-values.yaml <<EOF
ingressController:
  config:
    email: [email protected]
    acmeCA: https://acme-v02.api.letsencrypt.org/directory
    debug: false
  image:
    repository: ghcr.io/caddyserver/ingress
    tag: latest

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

replicaCount: 2

service:
  type: LoadBalancer
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
EOF

helm upgrade caddy-ingress-controller caddy/caddy-ingress-controller 
  --namespace caddy-system 
  -f caddy-values.yaml

Path-Based Routing ve Yük Dağılımı

Tek bir domain altında birden fazla servis sunan bir senaryo oldukça yaygın. Örneğin bir monorepo’dan çıkan microservice’leri tek domain altında topluyorsunuz:

kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: microservices-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: caddy
spec:
  ingressClassName: caddy
  rules:
  - host: platform.sirketiniz.com
    http:
      paths:
      - path: /users
        pathType: Prefix
        backend:
          service:
            name: users-service
            port:
              number: 80
      - path: /orders
        pathType: Prefix
        backend:
          service:
            name: orders-service
            port:
              number: 80
      - path: /notifications
        pathType: Prefix
        backend:
          service:
            name: notifications-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
EOF

Burada dikkat edilmesi gereken nokta path sıralaması. Caddy, daha spesifik path’lere öncelik veriyor, dolayısıyla / en sona gelmelidir, aksi halde diğer path’ler bu genel kural tarafından yakalanır.

TLS Sertifikaları İçin Secret Kullanımı

Kendi sertifikanızı kullanmak istediğiniz durumlar da olabilir. Kurumsal ortamlarda internal CA’dan alınmış sertifikalar veya wildcard sertifikalar bu kategoriye giriyor. Secret oluşturma ve Ingress’e bağlama:

# Sertifika ve key'i Secret olarak kaydet
kubectl create secret tls sirket-tls-secret 
  --cert=sirket.crt 
  --key=sirket.key 
  --namespace=default

# Ingress'te bu secret'ı kullan
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-ingress
  namespace: default
spec:
  ingressClassName: caddy
  tls:
  - hosts:
    - app.sirketiniz.com
    - www.sirketiniz.com
    secretName: sirket-tls-secret
  rules:
  - host: app.sirketiniz.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-app-svc
            port:
              number: 80
EOF

Caddy bu durumda Let’s Encrypt’e gitmez, doğrudan Secret içindeki sertifikayı kullanır.

Monitoring ve Gözlemlenebilirlik

Production ortamda Caddy ingress controller’ınızın ne yaptığını bilmeniz gerekiyor. Caddy, Prometheus formatında metrik sunabiliyor. Bunun için pod’un annotation’larına bakalım:

# Mevcut pod annotation'larını kontrol et
kubectl get pods -n caddy-system -o jsonpath='{.items[0].metadata.annotations}'

# Caddy metrics endpoint'ini kontrol et
kubectl port-forward -n caddy-system svc/caddy-ingress-controller 2019:2019
curl http://localhost:2019/metrics

Eğer Prometheus Operator kullanıyorsanız, ServiceMonitor kaynağı oluşturabilirsiniz:

kubectl apply -f - <<EOF
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: caddy-ingress-monitor
  namespace: caddy-system
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: caddy-ingress-controller
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
EOF

Log seviyesini production’da warn ya da error olarak tutmanızı öneririm. Debug modunda log hacmi ciddi boyutlara ulaşabiliyor, özellikle trafik yoğunsa.

Gerçek Dünya Senaryosu: Blue-Green Deployment

Caddy ingress controller’ı blue-green deployment senaryolarında oldukça işe yarıyor. Diyelim ki uygulamanızın yeni versiyonunu sıfır downtime ile yayına almak istiyorsunuz:

# Green ortam (yeni versiyon) deploy et
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-green
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      version: green
  template:
    metadata:
      labels:
        app: web-app
        version: green
    spec:
      containers:
      - name: web-app
        image: web-app:v2.0.0
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-app-green-svc
  namespace: default
spec:
  selector:
    app: web-app
    version: green
  ports:
  - port: 80
    targetPort: 80
EOF

# Green ortam test edildikten sonra Ingress'i güncelle
kubectl patch ingress production-ingress -n default --type='json' 
  -p='[{"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/service/name", "value":"web-app-green-svc"}]'

Bu yaklaşımla eski versiyon hala çalışmaya devam ediyor, bir sorun çıkarsa anında geri dönebiliyorsunuz.

Yaygın Sorunlar ve Çözümleri

Caddy ingress controller kullanırken sık karşılaşılan sorunları ve çözümlerini ele alalım.

Sertifika alamıyor: Let’s Encrypt rate limit’e takıldıysanız staging ortamını kullanın.

helm upgrade caddy-ingress-controller caddy/caddy-ingress-controller 
  --namespace caddy-system 
  --set ingressController.config.acmeCA=https://acme-staging-v02.api.letsencrypt.org/directory

503 Service Unavailable hatası: Backend servisin endpoint’lerini kontrol edin.

kubectl get endpoints web-app-svc -n default
kubectl describe ingress production-ingress -n default

Ingress kuralı uygulanmıyor: IngressClass ayarlarını ve controller annotation’larını doğrulayın.

kubectl get ingressclass caddy -o yaml
kubectl describe ingress production-ingress

Pod restart döngüsü: Resource limit’leri gözden geçirin. Caddy görece az kaynak tüketse de cluster’ınızdaki trafik yoğunluğuna göre limit’leri ayarlamanız gerekebilir.

kubectl top pods -n caddy-system
kubectl describe pod -n caddy-system -l app.kubernetes.io/name=caddy-ingress-controller

Çoklu Namespace Desteği

Büyük cluster’larda farklı ekiplerin farklı namespace’lerde çalıştığı durumlar yaygın. Caddy ingress controller varsayılan olarak tüm namespace’lerdeki Ingress kaynaklarını izliyor. Bunu belirli namespace’lerle kısıtlamak isteyebilirsiniz:

helm upgrade caddy-ingress-controller caddy/caddy-ingress-controller 
  --namespace caddy-system 
  --set ingressController.watchNamespaces="default,staging,production"

Her ekibin kendi Ingress annotation’larını yönetmesi için namespace başına ayrı Caddy instance’ı çalıştırmak da bir seçenek. Bu yaklaşım izolasyon açısından daha güvenli ama kaynak kullanımı açısından daha maliyetli.

NGINX’ten Caddy’ye Geçiş

Halihazırda NGINX ingress kullanıyorsanız ve Caddy’ye geçmeyi düşünüyorsanız, geçiş süreci dikkatli planlanmalı. Her iki controller’ı aynı anda çalıştırıp trafiği kademeli aktarabilirsiniz:

# Mevcut NGINX ingress'leri listele
kubectl get ingress --all-namespaces -o wide

# IngressClass'ı kontrol et
kubectl get ingressclass

# Yeni Ingress'lerde Caddy'yi belirt, eskilerde NGINX kalsın
# Önce kritik olmayan servislerle başla

Test ortamında aynı uygulamayı hem NGINX hem Caddy üzerinden yayımlayıp karşılaştırma yapmanızı şiddetle tavsiye ederim. Özellikle NGINX’e özgü annotation’lar (nginx.ingress.kubernetes.io/*) Caddy’de çalışmayacak, bunların Caddy muadillerini bulmanız gerekiyor.

Performans Optimizasyonu

Caddy’nin Go tabanlı yapısı ve async mimarisi performans açısından avantaj sağlıyor. Ama birkaç ek ayarla bunu daha da ileriye taşıyabilirsiniz:

# HPA ile otomatik ölçeklendirme
kubectl apply -f - <<EOF
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: caddy-ingress-hpa
  namespace: caddy-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: caddy-ingress-controller
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
EOF

Pod’ları birden fazla node’a dağıtmak için PodAntiAffinity kuralı ekleyin. Tüm Caddy pod’larının aynı node’a düşmesi, o node’un çökmesi halinde ciddi sorun yaratır.

Sonuç

Caddy ingress controller, özellikle otomatik TLS yönetimi, sade konfigürasyon yapısı ve düşük kaynak tüketimi açısından NGINX ve Traefik’e gerçek bir alternatif sunuyor. Küçük ve orta ölçekli cluster’larda, özellikle de TLS sertifika yönetiminin baş ağrısı haline geldiği ortamlarda Caddy ciddi bir zaman kazandırıyor.

Bununla birlikte bazı sınırlamaları da göz önünde bulundurun. NGINX’in annotation ekosistemi kadar geniş bir özellik seti henüz mevcut değil. Rewrite, rate limiting gibi gelişmiş senaryolarda Caddy’nin Kubernetes entegrasyonu hala olgunlaşıyor. Proje aktif geliştirme altında, GitHub reposunu düzenli takip etmenizi öneririm.

Production’a geçmeden önce staging cluster’ında yeterince test edin, özellikle TLS yenileme döngülerini ve yük altında davranışı gözlemleyin. Ama sonuçta Caddy’yi bir kez düzgün kurduğunuzda, “sertifikam neden yenilenmedi” sorusunu sormaktan kurtuluyorsunuz ki bu başlı başına büyük bir kazanım.

Yorum yapın