diff --git a/pkg/ingress/kube/annotations/loadbalance.go b/pkg/ingress/kube/annotations/loadbalance.go index a0aec1fb6..93a4e3cc5 100644 --- a/pkg/ingress/kube/annotations/loadbalance.go +++ b/pkg/ingress/kube/annotations/loadbalance.go @@ -50,13 +50,13 @@ var ( headersMapping = map[string]string{ "$request_uri": ":path", "$host": ":authority", - "$remote_addr": "x-envoy-external-address", } ) type consistentHashByOther struct { - header string - queryParam string + header string + queryParam string + useSourceIp bool } type consistentHashByCookie struct { @@ -110,19 +110,26 @@ func (l loadBalance) Parse(annotations Annotations, config *Ingress, _ *GlobalCo } else if isOtherAffinity(annotations) { if key, err := annotations.ParseStringASAP(upstreamHashBy); err == nil && strings.HasPrefix(key, varIndicator) { - value, exist := headersMapping[key] - if exist { + // Special case for $remote_addr: use useSourceIp instead of header mapping + if key == "$remote_addr" { loadBalanceConfig.other = &consistentHashByOther{ - header: value, + useSourceIp: true, } } else { - if strings.HasPrefix(key, headerIndicator) { + value, exist := headersMapping[key] + if exist { loadBalanceConfig.other = &consistentHashByOther{ - header: strings.TrimPrefix(key, headerIndicator), + header: value, } - } else if strings.HasPrefix(key, queryParamIndicator) { - loadBalanceConfig.other = &consistentHashByOther{ - queryParam: strings.TrimPrefix(key, queryParamIndicator), + } else { + if strings.HasPrefix(key, headerIndicator) { + loadBalanceConfig.other = &consistentHashByOther{ + header: strings.TrimPrefix(key, headerIndicator), + } + } else if strings.HasPrefix(key, queryParamIndicator) { + loadBalanceConfig.other = &consistentHashByOther{ + queryParam: strings.TrimPrefix(key, queryParamIndicator), + } } } } @@ -165,7 +172,13 @@ func (l loadBalance) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, } } else if loadBalanceConfig.other != nil { var consistentHash *networking.LoadBalancerSettings_ConsistentHashLB - if loadBalanceConfig.other.header != "" { + if loadBalanceConfig.other.useSourceIp { + consistentHash = &networking.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{ + UseSourceIp: true, + }, + } + } else if loadBalanceConfig.other.header != "" { consistentHash = &networking.LoadBalancerSettings_ConsistentHashLB{ HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpHeaderName{ HttpHeaderName: loadBalanceConfig.other.header, diff --git a/pkg/ingress/kube/annotations/loadbalance_test.go b/pkg/ingress/kube/annotations/loadbalance_test.go index 691540ba5..a8f4ebdc6 100644 --- a/pkg/ingress/kube/annotations/loadbalance_test.go +++ b/pkg/ingress/kube/annotations/loadbalance_test.go @@ -109,7 +109,7 @@ func TestLoadBalanceParse(t *testing.T) { expect: &LoadBalanceConfig{ simple: networking.LoadBalancerSettings_ROUND_ROBIN, other: &consistentHashByOther{ - header: "x-envoy-external-address", + useSourceIp: true, }, }, }, @@ -233,6 +233,27 @@ func TestLoadBalanceApplyTrafficPolicy(t *testing.T) { }, }, }, + { + config: &Ingress{ + LoadBalance: &LoadBalanceConfig{ + other: &consistentHashByOther{ + useSourceIp: true, + }, + }, + }, + input: &networking.TrafficPolicy_PortTrafficPolicy{}, + expect: &networking.TrafficPolicy_PortTrafficPolicy{ + LoadBalancer: &networking.LoadBalancerSettings{ + LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{ + ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{ + UseSourceIp: true, + }, + }, + }, + }, + }, + }, } for _, inputCase := range inputCases { diff --git a/samples/loadbalance/useSourceIp-example.yaml b/samples/loadbalance/useSourceIp-example.yaml new file mode 100644 index 000000000..37674265e --- /dev/null +++ b/samples/loadbalance/useSourceIp-example.yaml @@ -0,0 +1,155 @@ +# Example: Load Balancing with useSourceIp Support +# This example demonstrates the enhanced consistent hashing feature +# that uses source IP for load balancing instead of headers. +# +# Issue: https://github.com/alibaba/higress/issues/2790 +# PR: https://github.com/alibaba/higress/pull/2844 +# +# The key improvement is that $remote_addr now uses useSourceIp field +# instead of x-envoy-external-address header, which works better for +# private IP addresses. + +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test-remote-addr +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: echo-server-1 + namespace: test-remote-addr +spec: + replicas: 1 + selector: + matchLabels: + app: echo-server-1 + template: + metadata: + labels: + app: echo-server-1 + spec: + containers: + - name: echo-server + image: hashicorp/http-echo:0.2.3 + args: + - "-text=Server 1 - IP: $(hostname -i)" + ports: + - containerPort: 5678 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: echo-server-2 + namespace: test-remote-addr +spec: + replicas: 1 + selector: + matchLabels: + app: echo-server-2 + template: + metadata: + labels: + app: echo-server-2 + spec: + containers: + - name: echo-server + image: hashicorp/http-echo:0.2.3 + args: + - "-text=Server 2 - IP: $(hostname -i)" + ports: + - containerPort: 5678 +--- +apiVersion: v1 +kind: Service +metadata: + name: echo-service + namespace: test-remote-addr +spec: + selector: + app: echo-server-1 + ports: + - port: 80 + targetPort: 5678 + name: http +--- +apiVersion: v1 +kind: Service +metadata: + name: echo-service-2 + namespace: test-remote-addr +spec: + selector: + app: echo-server-2 + ports: + - port: 80 + targetPort: 5678 + name: http +--- +# Example 1: Using $remote_addr with useSourceIp (NEW FEATURE) +# This configuration now uses source IP directly for consistent hashing +# instead of relying on x-envoy-external-address header +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: test-remote-addr-source-ip + namespace: test-remote-addr + annotations: + higress.io/upstream-hash-by: "$remote_addr" +spec: + ingressClassName: higress + rules: + - host: test-source-ip.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: echo-service + port: + number: 80 +--- +# Example 2: Using traditional header-based hashing for comparison +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: test-remote-addr-header + namespace: test-remote-addr + annotations: + higress.io/upstream-hash-by: "$http_x_real_ip" +spec: + ingressClassName: higress + rules: + - host: test-header.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: echo-service-2 + port: + number: 80 +--- +# Test client for sending requests +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-client + namespace: test-remote-addr +spec: + replicas: 1 + selector: + matchLabels: + app: test-client + template: + metadata: + labels: + app: test-client + spec: + containers: + - name: curl + image: busybox:1.28 + command: ["sleep", "3600"]