I'm having trouble getting a kubernetes IPv6 SingleStack
LoadBalancer
service passing through the correct source IP address to pods. It works fine on a sister IPv4 SingleStack
LoadBalancer
that passes traffic to the same pods.
The cluster is a bare-metal v1.21.1 dual-stack cluster created with kubeadm
and uses Calico v3.18 as the cni and MetalLB to allocate loadbalancer IPs to services configured with type: LoadBalancer
. Calico is then configured to announce the loadbalancer IPs to the local router over BGP. Taking a toy example of a single nginx
deployment with two services (one for IPv4, one for IPv6), if I curl the IP via the IPv4 address, the nginx access log prints the correct client IP in 192.168.2.0/24
:
192.168.2.128 - - [01/Jun/2021:19:32:37 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" "-"
But curling the IPv6 address from the same client in 2001:8b0:c8f:e8b0::/64
, nginx shows a client IP address of fd5a:1111:1111::f31f
fd5a:1111:1111::f31f - - [01/Jun/2021:19:34:23 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" "-"
This address is from the cluster's serviceSubnet
of fd5a:1111:1111::/112
and happens to be the clusterIP
address of the IPv6 service. It seems like that something is actually doing some TCP proxying here (ipvs?), but it's not clear why it's behaving this way. I'd expect this if externalTrafficPolicy
were Cluster
- in fact, if I change the services from Local
to Cluster
, I get the local IP address of the cluster node forwarding the request on IPv4 (as expected), and the same clusterIP address on IPv6. externalTrafficPolicy
appears to have no effect in the IPv6 case.
Am I missing something obvious, or should these services behave in the same way as each other?
Manifest of the test:
---
apiVersion: v1
kind: Service
metadata:
name: test-service-source-ip-v4
namespace: default
labels:
k8s-app: test-service-source-ip
spec:
selector:
k8s-app: test-service-source-ip
type: LoadBalancer
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
loadBalancerIP: 192.168.254.11
externalTrafficPolicy: "Local"
ports:
- name: http-tcp
protocol: TCP
port: 80
---
apiVersion: v1
kind: Service
metadata:
name: test-service-source-ip-v6
namespace: default
labels:
k8s-app: test-service-source-ip
spec:
selector:
k8s-app: test-service-source-ip
type: LoadBalancer
ipFamilies:
- IPv6
ipFamilyPolicy: SingleStack
loadBalancerIP: 2001:8b0:c8f:e8b1:beef:f00d::11
externalTrafficPolicy: "Local"
ports:
- name: http-tcp
protocol: TCP
port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: test-service-source-ip
labels:
k8s-app: test-service-source-ip
spec:
replicas: 1
selector:
matchLabels:
k8s-app: test-service-source-ip
template:
metadata:
labels:
k8s-app: test-service-source-ip
spec:
containers:
- name: test-service-source-ip
image: nginx:1
ports:
- containerPort: 80
protocol: TCP