K8s kümesinde yük dengeleme sonrası istek kaynak IP'sini nasıl koruyabiliriz

Giriş

Uygulama dağıtımı her zaman basit bir yükleme ve çalıştırma olmayabilir, bazen sorunlarını da dikkate almak gerekir. Bu makale, k8s kümesinde hizmetin isteğin kaynak IP’sine erişmesini nasıl sağlayacağınızı anlatacaktır.

Uygulamalar hizmet sağlarken genellikle giriş bilgilerine dayanır, giriş bilgileri beşli gruba (kaynak IP, kaynak port, hedef IP, hedef port, protokol) bağlı değilse, bu hizmet ağ bağımlılığı düşük olur ve ağ detaylarını umursamaz.

Bu nedenle, çoğu kişi için bu makaleyi okumanıza gerek yoktur, eğer ağa ilgi duyuyorsanız veya ufuklarınızı genişletmek istiyorsanız, aşağıdaki metni okumaya devam edebilir, daha fazla hizmet senaryosu öğrenebilirsiniz.

Bu makale k8s v1.29.4 tabanlıdır, metinde pod ve endpoint karışık kullanılmıştır, bu senaryoda eşdeğer olarak kabul edilebilir.

Hata varsa, düzeltme için lütfen belirtin, zamanında düzelteceğim.

Neden kaynak IP bilgisi kaybolur?

Öncelikle kaynak IP’nin ne olduğunu netleştirelim, A’dan B’ye istek gönderildiğinde, B isteği C’ye yönlendirirse, C’nin gördüğü IP protokolünün kaynak IP’si B’nin IP’si olsa da, bu makalede A’nın IP’si kaynak IP olarak kabul edilir.

Kaynak bilgi kaybına yol açan iki ana davranış vardır:

  1. Ağ Adres Çevirisi (NAT), amacı IPv4 kamu IP’lerini tasarruflu kullanmak, yük dengeleme vb. Bu, sunucunun gördüğü kaynak IP’nin NAT cihazının IP’si olmasını sağlar, gerçek kaynak IP değil.
  2. Vekil (Proxy), Ters Vekil (RP, Reverse Proxy) ve Yük Dengeleyici (LB, Load Balancer) bu kategoriye girer, aşağıda vekil sunucu olarak adlandırılır. Bu tür vekil hizmetler isteği arka uç hizmete yönlendirir, ancak kaynak IP’yi kendi IP’si ile değiştirir.
  • NAT basitçe port alanı ile IP alanı değiştirme olarak özetlenebilir, IPv4 adresleri sınırlıdır, bir IP adresi 65535 portu eşleyebilir, çoğu zaman bu portlar tükenmez, bu nedenle birden fazla alt ağ IP’si bir kamu IP’sini paylaşabilir, portlarda farklı hizmetleri ayırt eder. Kullanım biçimi: kamu IP:kamu port -> özel IP_1:özel port, daha fazla içerik için lütfen Ağ Adres Çevirisi’ni kendiniz okuyun
  • Vekil hizmetler gizleme veya maruz bırakma içindir, vekil hizmet isteği arka uç hizmete yönlendirir, aynı zamanda kaynak IP’yi kendi IP’si ile değiştirerek arka uç hizmetin gerçek IP’sini gizler, arka uç hizmetin güvenliğini korur. Vekil hizmet kullanım biçimi: istemci IP -> vekil IP -> sunucu IP, daha fazla içerik için lütfen Vekil‘i kendiniz okuyun

NAT ve vekil sunucular çok yaygındır, çoğu hizmet isteğin kaynak IP’sini alamaz.

Bu, kaynak IP’yi değiştiren yaygın iki yol, başkaları varsa lütfen ekleyin.

Kaynak IP’yi nasıl koruyabiliriz?

Aşağıda bir HTTP isteği örneği:

AlanUzunluk (bayt)Bit OfsetiAçıklama
IP Başlığı
Kaynak IP40-31Gönderenin IP adresi
Hedef IP432-63Alıcının IP adresi
TCP Başlığı
Kaynak Port20-15Gönderen port numarası
Hedef Port216-31Alıcı port numarası
Sıra Numarası432-63Gönderenin gönderdiği veri bayt akışını tanımlamak için kullanılır
Onay Numarası464-95ACK bayrağı ayarlanmışsa, bir sonraki beklenen sıra numarası
Veri Ofseti496-103Veri başlangıç konumunun TCP başlığına göre bayt sayısı
Ayrılmış4104-111Ayrılmış alan, kullanılmaz, 0 olarak ayarlanır
Bayrak Bitleri2112-127Çeşitli kontrol bayrakları, SYN, ACK, FIN vb.
Pencere Boyutu2128-143Alıcının alabileceği veri miktarı
Kontrol Toplamı2144-159Verinin iletim sırasında hata olup olmadığını tespit etmek için
Acil İşaretçi2160-175Gönderenin alıcının en hızlı işleyeceği acil verinin konumu
SeçeneklerDeğişken176-…Zaman damgası, maksimum segment uzunluğu vb. içerebilir
HTTP Başlığı
İstek SatırıDeğişken…-…İstek yöntemi, URI ve HTTP sürümü içerir
Başlık AlanlarıDeğişken…-…Host, User-Agent vb. çeşitli başlık alanları içerir
Boş Satır2…-…Başlık ve gövde kısmını ayırmak için kullanılır
GövdeDeğişken…-…İsteğe bağlı istek veya yanıt gövdesi

Yukarıdaki HTTP istek yapısını inceleyin, TCP seçenekleri, istek satırı, başlık alanları, gövde değişkendir, TCP seçenekleri alanı sınırlıdır, genellikle kaynak IP taşımak için kullanılmaz, istek satırı sabit bilgi taşır genişletilemez, HTTP gövdesi şifrelendikten sonra değiştirilemez, sadece HTTP başlık alanları kaynak IP taşımak için genişletmeye uygundur.

HTTP header’ına X-REAL-IP alanı eklenebilir, kaynak IP taşımak için, bu işlem genellikle vekil sunucuda yapılır, sonra vekil sunucu isteği arka uç hizmete gönderir, arka uç hizmet bu alan üzerinden kaynak IP bilgisini alabilir.

Dikkat, vekil sunucunun NAT cihazından önce olduğundan emin olun, böylece gerçek isteğin kaynak kimliğini alabilir. Alibaba Cloud ürünlerinde Yük Dengeleyici adlı ayrı bir ürün kategorisi görebilirsiniz, ağdaki konumu sıradan uygulama sunucularından farklıdır.

K8S İşlem Kılavuzu

whoami projesi örneğiyle dağıtım yapalım.

Deployment Oluşturma

Önce hizmeti oluşturun:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: docker.io/traefik/whoami:latest
          ports:
            - containerPort: 8080

Bu adım bir Deployment oluşturur, içinde 3 Pod bulunur, her pod bir konteyner içerir, bu konteyner whoami hizmetini çalıştırır.

Service Oluşturma

NodePort veya LoadBalancer tipi hizmet oluşturabilirsiniz, dış erişimi destekler, veya ClusterIP tipi hizmet oluşturup yalnızca küme içi erişimi destekler, sonra Ingress hizmeti ekleyerek dış erişimi açığa çıkarır.

NodePort hem NodeIP:NodePort ile hem de Ingress hizmeti ile erişilebilir, test için uygundur, bu bölümde NodePort hizmeti kullanılır.

apiVersion: v1
kind: Service
metadata:
  name: whoami-service
spec:
  type: NodePort
  selector:
    app: whoami
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30002

Hizmet oluşturulduktan sonra, curl whoami.example.com:30002 ile erişin, dönen IP’nin NodeIP olduğunu göreceksiniz, isteğin kaynak whoami değil.

Dikkat, bu doğru istemci IP’si değildir, bunlar kümenin iç IP’leridir. Olan şu:

  • İstemci veri paketini node2:nodePort’a gönderir
  • node2 veri paketinin kaynak IP adresini kendi IP adresi ile değiştirir (SNAT)
  • node2 veri paketindeki hedef IP’yi Pod IP ile değiştirir
  • Veri paketi node1’e yönlendirilir, sonra uç noktaya
  • Pod’un yanıtı node2’ye geri yönlendirilir
  • Pod’un yanıtı istemciye geri gönderilir

Şekille ifade edelim:

externalTrafficPolicy: Local Yapılandırma

Bunu önlemek için, Kubernetes bir özellik sunar ki istemci kaynak IP’sini korur. service.spec.externalTrafficPolicy’yi Local olarak ayarlarsanız, kube-proxy yalnızca yerel uç noktaları proxy’ler, trafiği diğer düğümlere yönlendirmez.

apiVersion: v1
kind: Service
metadata:
  name: whoami-service
spec:
  type: NodePort
  externalTrafficPolicy: Local
  selector:
    app: whoami
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30002

curl whoami.example.com:30002 ile test edin, whoami.example.com kümedeki birden fazla node’un IP’sine eşlendiğinde, belirli bir oranda erişilemezlik olur. Alan adı kaydının yalnızca endpoint(pod) bulunduğu node( düğüm) IP’sini içerdiğinden emin olun.

Bu yapılandırmanın maliyeti vardır, küme içi yük dengeleme yeteneğini kaybedersiniz, istemci yalnızca endpoint dağıtılan node’a erişirse yanıt alır.

Erişim Yolu Kısıtlaması

İstemci Node 2’ye eriştiğinde yanıt olmaz.

Ingress Oluşturma

Çoğu hizmet kullanıcılara http/https olarak sunulur, https://ip:port biçimi kullanıcılara yabancı gelebilir. Genellikle Ingress ile yukarıdaki NodePort hizmetini bir alan adının 80/443 portuna yük dengeleyin.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami-ingress
  namespace: default
spec:
  ingressClassName: external-lb-default
  rules:
    - host: whoami.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: whoami-service
                port:
                  number: 80

Uygulandıktan sonra, curl whoami.example.com ile test edin, ClientIP’nin her zaman endpoint bulunduğu düğümdeki Ingress Controller Pod IP’si olduğunu göreceksiniz.

root@client:~# curl whoami.example.com
...
RemoteAddr: 10.42.1.10:56482
...

root@worker:~# kubectl get -n ingress-nginx pod -o wide
NAME                                       READY   STATUS    RESTARTS   AGE    IP           NODE          NOMINATED NODE   READINESS GATES
ingress-nginx-controller-c8f499cfc-xdrg7   1/1     Running   0          3d2h   10.42.1.10   k3s-agent-1   <none>           <none>

Ingress ile NodePort hizmetini ters vekil olarak kullanmak, endpoint önünde iki katman service eklemek gibidir, aşağıdaki şekil ikisinin farkını gösterir.

graph LR
    A[Client] -->|whoami.example.com:80| B(Ingress)
    B -->|10.43.38.129:32123| C[Service]
    C -->|10.42.1.1:8080| D[Endpoint]
graph LR
    A[Client] -->|whoami.example.com:30001| B(Service)
    B -->|10.42.1.1:8080| C[Endpoint]

Yol 1’de, dış erişim Ingress’e geldiğinde, trafiğin ilk ulaştığı endpoint Ingress Controller olur, sonra whoami endpoint’ine ulaşır.
Ingress Controller esasen bir LoadBalancer hizmetidir,

kubectl -n ingress-nginx get svc

NAMESPACE   NAME             CLASS   HOSTS                       ADDRESS                                              PORTS   AGE
default     echoip-ingress   nginx   ip.example.com       172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707   80      18h
default     whoami-ingress   nginx   whoami.example.com   172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707   80      16h

Bu nedenle, yukarıda bahsedilen externalTrafficPolicy‘yi Ingress Controller’a ayarlayarak kaynak IP korunabilir.

Aynı zamanda ingress-nginx-controller‘ın configmap‘indeki use-forwarded-headers‘ı true olarak ayarlamak gerekir, böylece Ingress Controller X-Forwarded-For veya X-REAL-IP alanlarını tanıyabilir.

apiVersion: v1
data:
  allow-snippet-annotations: "false"
  compute-full-forwarded-for: "true"
  use-forwarded-headers: "true"
  enable-real-ip: "true"
  forwarded-for-header: "X-Real-IP" # X-Real-IP or X-Forwarded-For
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.10.1
  name: ingress-nginx-controller
  namespace: ingress-nginx

NodePort hizmeti ile ingress-nginx-controller hizmetinin farkı esasen, NodePort‘un arkasının genellikle her node’da dağıtılmaması, ingress-nginx-controller‘ın arkasının ise genellikle her dışa açık node’da dağıtılmasıdır.

NodePort hizmetinde externalTrafficPolicy ayarlanması düğümler arası isteklerin yanıtsız kalmasına yol açarken, Ingress istekleri önce HEADER ayarlayıp sonra vekil olarak yönlendirebilir, kaynak IP koruma ve yük dengeleme yeteneklerini gerçekleştirir.

Özet

  • Adres Çevirisi (NAT), Vekil (Proxy), Ters Vekil (Reverse Proxy), Yük Dengeleme (Load Balance) kaynak IP kaybına yol açar.
  • Kaynak IP kaybını önlemek için, vekil sunucu yönlendirirken gerçek IP’yi HTTP başlık alanı X-REAL-IP‘de ayarlayabilir, vekil hizmet üzerinden iletir. Çok katmanlı vekil kullanırsanız, X-Forwarded-For alanını kullanabilirsiniz, bu alan yığın şeklinde kaynak IP ve vekil yolunun IP listesini kaydeder.
  • Küme NodePort hizmetinde externalTrafficPolicy: Local ayarlayarak kaynak IP korunabilir, ancak yük dengeleme yeteneği kaybolur.
  • ingress-nginx-controller daemonset şeklinde tüm loadbalancer rolü node’lara dağıtıldığında, externalTrafficPolicy: Local ayarlayarak kaynak IP korunabilir ve yük dengeleme yeteneği korunur.

Kaynaklar