K8s क्लस्टर में लोड बैलेंसर के बाद के अनुरोध स्रोत IP को कैसे बनाए रखें

परिचय

एप्लिकेशन तैनाती हमेशा सरल स्थापना और चलाना नहीं होती, कभी-कभी नेटवर्क की समस्याओं पर भी विचार करना पड़ता है। यह लेख बताएगा कि K8s क्लस्टर में सेवा को अनुरोध का स्रोत IP कैसे प्राप्त करने में सक्षम बनाया जाए।

एप्लिकेशन सेवा प्रदान करने के लिए सामान्यतः इनपुट जानकारी पर निर्भर करता है, यदि इनपुट जानकारी पाँच टुपल (स्रोत IP, स्रोत पोर्ट, गंतव्य IP, गंतव्य पोर्ट, प्रोटोकॉल) पर निर्भर नहीं है, तो सेवा का नेटवर्क संयोजन क्षमता कम होती है, और नेटवर्क विवरणों की चिंता करने की आवश्यकता नहीं होती।

इसलिए, अधिकांश लोगों के लिए इस लेख को पढ़ने की आवश्यकता नहीं है, यदि आप नेटवर्क में रुचि रखते हैं, या दृष्टिकोण को थोड़ा व्यापक बनाना चाहते हैं, तो आप आगे पढ़ सकते हैं, और अधिक सेवा परिदृश्यों को समझ सकते हैं।

यह लेख K8s v1.29.4 पर आधारित है, लेख में कुछ वर्णन pod और endpoint को मिलाकर उपयोग करते हैं, इस लेख के परिदृश्य में इन्हें समकक्ष माना जा सकता है।

यदि कोई त्रुटि है, तो सुधार का स्वागत है, मैं तुरंत सुधार करूंगा।

स्रोत IP जानकारी क्यों खो जाती है?

सबसे पहले हम स्पष्ट करें कि स्रोत IP क्या है, जब A B को अनुरोध भेजता है, B अनुरोध को C को फॉरवर्ड करता है, हालांकि C द्वारा देखा गया IP प्रोटोकॉल का स्रोत IP B का IP है, लेकिन यह लेख A का IP को स्रोत IP मानता है।

मुख्य रूप से दो प्रकार के व्यवहार स्रोत जानकारी के खोने का कारण बनते हैं:

  1. नेटवर्क पता अनुवर्तन (NAT), उद्देश्य सार्वजनिक IPv4 की बचत, लोड बैलें싱 आदि। इससे सेवा द्वारा देखा गया स्रोत IP NAT डिवाइस का IP होगा, न कि वास्तविक स्रोत IP।
  2. प्रॉक्सी (Proxy), रिवर्स प्रॉक्सी (RP, Reverse Proxy) और लोड बैलेंसर (LB, Load Balancer) इस श्रेणी में आते हैं, नीचे सामूहिक रूप से प्रॉक्सी सर्वर कहा जाएगा। इस प्रकार के प्रॉक्सी सेवाएँ अनुरोध को बैकएंड सेवा को फॉरवर्ड करेंगी, लेकिन स्रोत IP को अपने IP से बदल देंगी।
  • NAT सरल शब्दों में पोर्ट स्पेस से IP स्पेस का आदान-प्रदान है, IPv4 पते सीमित हैं, एक IP पता 65535 पोर्ट मैप कर सकता है, अधिकांश समय ये पोर्ट समाप्त नहीं होते, इसलिए कई सबनेट IP एक सार्वजनिक IP साझा कर सकते हैं, पोर्ट पर विभिन्न सेवाओं को अलग किया जा सकता है। इसका उपयोग रूप है: public IP:public port -> private IP_1:private port, अधिक सामग्री के लिए कृपया देखेंनेटवर्क पता अनुवर्तन
  • प्रॉक्सी सेवा छिपाने या उजागर करने के लिए है, प्रॉक्सी सेवा अनुरोध को बैकएंड सेवा को फॉरवर्ड करेगी, साथ ही स्रोत IP को अपने IP से बदल देगी, ताकि बैकएंड सेवा का वास्तविक IP छिपा रहे, बैकएंड सेवा की सुरक्षा हो। प्रॉक्सी सेवा का उपयोग रूप है: client IP -> proxy IP -> server IP, अधिक सामग्री के लिए कृपया देखेंप्रॉक्सी

NAT और प्रॉक्सी सर्वर बहुत सामान्य हैं, अधिकांश सेवाएँ अनुरोध का स्रोत IP प्राप्त नहीं कर सकतीं।

ये स्रोत IP बदलने के सामान्य दो तरीके हैं, यदि अन्य हैं तो पूरक का स्वागत है।

स्रोत IP कैसे बनाए रखें?

यहाँ एक HTTP अनुरोध का उदाहरण है:

फ़ील्ड लंबाई (बाइट्स) बिट ऑफ़सेट विवरण
IP हेडर
स्रोत IP 4 0-31 प्रेषक का IP पता
गंतव्य IP 4 32-63 प्राप्तकर्ता का IP पता
TCP हेडर
स्रोत पोर्ट 2 0-15 प्रेषण पोर्ट नंबर
गंतव्य पोर्ट 2 16-31 प्राप्ति पोर्ट नंबर
अनुक्रम संख्या 4 32-63 प्रेषक द्वारा भेजे गए डेटा की बाइट स्ट्रीम की पहचान के लिए
पुष्टि संख्या 4 64-95 यदि ACK फ्लैग सेट है, तो अगली अपेक्षित प्राप्त अनुक्रम संख्या
डेटा ऑफ़सेट 4 96-103 डेटा प्रारंभिक स्थिति TCP हेडर के सापेक्ष बाइट्स की संख्या
आरक्षित 4 104-111 आरक्षित फ़ील्ड, अप्रयुक्त, 0 पर सेट करें
फ़्लैग बिट्स 2 112-127 विभिन्न नियंत्रण फ़्लैग, जैसे SYN, ACK, FIN आदि
विंडो आकार 2 128-143 प्राप्तकर्ता द्वारा प्राप्त की जा सकने वाली डेटा मात्रा
चेकसम 2 144-159 डेटा संचरण के दौरान त्रुटि का पता लगाने के लिए
आपातकालीन सूचक 2 160-175 प्रेषक द्वारा प्राप्तकर्ता द्वारा जल्दी संसाधित होने वाली आपातकालीन डेटा की स्थिति
विकल्प चर 176-… समय स्टैंप, अधिकतम सेगमेंट लंबाई आदि शामिल हो सकते हैं
HTTP हेडर
अनुरोध पंक्ति चर …-… अनुरोध विधि, URI और HTTP संस्करण शामिल
हेडर फ़ील्ड चर …-… विभिन्न हेडर फ़ील्ड जैसे Host, User-Agent आदि शामिल
खाली पंक्ति 2 …-… हेडर और बॉडी भाग को अलग करने के लिए
बॉडी चर …-… वैकल्पिक अनुरोध या प्रतिक्रिया पाठ

उपरोक्त HTTP अनुरोध संरचना को ब्राउज़ करें, पाया जा सकता है कि TCP विकल्प, अनुरोध पंक्ति, हेडर फ़ील्ड, बॉडी परिवर्तनीय हैं, जिनमें TCP विकल्प स्पेस सीमित है, सामान्यतः स्रोत IP पास करने के लिए उपयोग नहीं किया जाता, अनुरोध पंक्ति जानकारी निश्चित है विस्तार योग्य नहीं, HTTP बॉडी एन्क्रिप्टेड होने के बाद संशोधित नहीं की जा सकती, केवल HTTP हेडर फ़ील्ड स्रोत IP पास करने के लिए उपयुक्त विस्तार है।

HTTP हेडर में X-REAL-IP फ़ील्ड जोड़ी जा सकती है, स्रोत IP पास करने के लिए, यह संचालन सामान्यतः प्रॉक्सी सर्वर पर रखा जाता है, फिर प्रॉक्सी सर्वर अनुरोध को बैकएंड सेवा को भेजेगा, बैकएंड सेवा इस फ़ील्ड के माध्यम से स्रोत IP जानकारी प्राप्त कर सकेगी।

ध्यान दें, प्रॉक्सी सर्वर को NAT डिवाइस से पहले सुनिश्चित करना होगा, ताकि वास्तविक अनुरोध का स्रोत प्राप्त हो सके। हम अलीक्लाउड के उत्पादों मेंलोड बैलेंसर इस वस्तु को अलग श्रेणी में देख सकते हैं, इसका नेटवर्क में स्थान सामान्य एप्लिकेशन सर्वर से भिन्न है।

K8S संचालन मार्गदर्शन

whoami प्रोजेक्ट का उदाहरण लेकर तैनाती करें।

Deployment बनाएँ

सबसे पहले सेवा बनाएँ:

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

यह चरण एक Deployment बनाएगा, जिसमें 3 Pod शामिल हैं, प्रत्येक pod में एक कंटेनर है, जो whoami सेवा चलाएगा।

Service बनाएँ

NodePort या LoadBalancer प्रकार की सेवा बना सकते हैं, बाहरी पहुँच का समर्थन, या ClusterIP प्रकार की सेवा बनाएँ, केवल क्लस्टर आंतरिक पहुँच का समर्थन, फिर Ingress सेवा जोड़ें, Ingress सेवा के माध्यम से बाहरी पहुँच उजागर करें।

NodePort NodeIP:NodePort या Ingress सेवा के माध्यम से पहुँचा जा सकता है, परीक्षण के लिए सुविधाजनक, इस खंड में NodePort सेवा का उपयोग करें।

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

सेवा बनाने के बाद, curl whoami.example.com:30002 से पहुँचें, देखा जा सकता है कि लौटाया गया IP NodeIP है, न कि अनुरोध का स्रोत whoami।

कृपया ध्यान दें, यह सही क्लाइंट IP नहीं है, ये क्लस्टर के आंतरिक IP हैं। यही होता है:

  • क्लाइंट डेटा पैकेट node2:nodePort को भेजता है
  • node2 डेटा पैकेट के स्रोत IP को अपने IP से बदल देता है (SNAT)
  • node2 डेटा पैकेट पर गंतव्य IP को Pod IP से बदल देता है
  • डेटा पैकेट node1 को रूट किया जाता है, फिर एंडपॉइंट तक
  • Pod का जवाब node2 को रूट किया जाता है
  • Pod का जवाब क्लाइंट को भेजा जाता है

चित्र से दर्शाएँ:

externalTrafficPolicy: Local कॉन्कफ़िगर करें

इस स्थिति से बचने के लिए, Kubernetes में एक विशेषता है जो क्लाइंट स्रोत IP को बनाए रख सकती है। यदि service.spec.externalTrafficPolicy को Local पर सेट करें, तो kube-proxy केवल स्थानीय एंडपॉइंट्स को प्रॉक्सी करेगा, अन्य नोड्स को ट्रैफ़िक फॉरवर्ड नहीं करेगा।

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 से परीक्षण करें, जब whoami.example.com क्लस्टर के कई नोड IP पर मैप हो, तो कुछ अनुपात में पहुँच असफल होगी। डोमेन रिकॉर्ड में केवल endpoint(pod) वाले नोड(नोड) के IP की पुष्टि करें।

यह कॉन्कफ़िगरेशन की कीमत है, यानी क्लस्टर आंतरिक लोड बैलें싱 क्षमता खो दी जाती है, क्लाइंट केवल endpoint तैनात नोड पर पहुँचने पर ही प्रतिक्रिया प्राप्त करेगा।

पहुँच पथ प्रतिबंध

जब क्लाइंट Node 2 पर पहुँचता है, तो कोई प्रतिक्रिया नहीं होगी।

Ingress बनाएँ

अधिकांश सेवाएँ उपयोगकर्ताओं को प्रदान करते समय http/https का उपयोग करती हैं, https://ip:port रूप उपयोगकर्ताओं को अपरिचित लग सकता है। सामान्यतः Ingress का उपयोग ऊपर बनाई NodePort सेवा को एक डोमेन के 80/443 पोर्ट पर लोड बैलेंस करने के लिए किया जाता है।

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

लागू करने के बाद, curl whoami.example.com से परीक्षण पहुँचें, देखा जा सकता है कि ClientIP हमेशा endpoint वाले नोड पर Ingress Controller के Pod IP होता है।

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 रिवर्स प्रॉक्सी NodePort सेवा, यानी endpoint के सामने दो लेयर service लगाना, नीचे चित्र दोनों के अंतर को दर्शाता है।

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]

पथ 1 में, बाहरी Ingress पहुँचते समय, ट्रैफ़िक पहले Ingress Controller endpoint तक पहुँचता है, फिर whoami endpoint तक।
और Ingress Controller वास्तव में एक LoadBalancer सेवा है,

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

इसलिए, ऊपर उल्लिखित externalTrafficPolicy को Ingress Controller में सेट करके स्रोत IP बनाए रखा जा सकता है।

साथ ही ingress-nginx-controller के configmap में use-forwarded-headers को true पर सेट करना होगा, ताकि Ingress Controller X-Forwarded-For या X-REAL-IP फ़ील्ड को पहचान सके।

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 सेवा और ingress-nginx-controller सेवा का अंतर मुख्य रूप से यह है कि NodePort का बैकएंड सामान्यतः हर नोड पर तैनात नहीं होता, जबकि ingress-nginx-controller का बैकएंड सामान्यतः हर बाहरी उजागर नोड पर तैनात होता है।

NodePort सेवा में externalTrafficPolicy सेट करने से क्रॉस-नोड अनुरोध बिना प्रतिक्रिया के भिन्न, Ingress अनुरोध को पहले HEADER सेट कर प्रॉक्सी फॉरवर्ड कर सकता है, जिससे स्रोत IP बनाए रखना और लोड बैलें싱 दोनों क्षमताएँ प्राप्त होती हैं।

सारांश

  • पता अनुवर्तन (NAT), प्रॉक्सी (Proxy), रिवर्स प्रॉक्सी (Reverse Proxy), लोड बैलेंस (Load Balance) स्रोत IP खोने का कारण बनेंगे।
  • स्रोत IP खोने से रोकने के लिए, प्रॉक्सी सर्वर फॉरवर्ड करते समय वास्तविक IP को HTTP हेडर फ़ील्ड X-REAL-IP में सेट करें, प्रॉक्सी सेवा के माध्यम से पास करें। यदि मल्टी-लेयर प्रॉक्सी का उपयोग, तो X-Forwarded-For फ़ील्ड का उपयोग करें, यह फ़ील्ड स्टैक रूप में स्रोत IP और प्रॉक्सी पथ का IP सूची रिकॉर्ड करती है।
  • क्लस्टर NodePort सेवा externalTrafficPolicy: Local सेट करके स्रोत IP बनाए रख सकती है, लेकिन लोड बैलें싱 क्षमता खो देगी।
  • ingress-nginx-controller को daemonset रूप में सभी loadbalancer भूमिका नोड पर तैनात करने की शर्त पर, externalTrafficPolicy: Local सेट करके स्रोत IP बनाए रख सकती है, और लोड बैलें싱 क्षमता भी बनाए रख सकती है।

संदर्भ