Setting up fixed IP address for egress traffic with Linkerd

Hello Linkerd community,

I’m trying to implement a solution for using fixed IP addresses for egress traffic from specific applications in my Kubernetes cluster. I would appreciate any guidance on how to achieve this using Linkerd.

My setup

  • Self-hosted RKE2 Kubernetes cluster (v1.31.3+rke2r1)
  • 6 nodes
  • Linkerd version: edge-25.5.1
  • CNI: Canal (Calico+Flannel)
  • LoadBalancer: MetalLB
  • OPNsense firewall for external traffic

What I’m trying to achieve

I want specific applications to use a fixed, predefined IP address (10.x.y.100) for their outbound/egress traffic to the internet. Specifically:

  1. Regular pods in the cluster should use their default node IP for egress traffic
  2. Some specific applications need to use a predetermined IP (10.x.y.100)
  3. The solution should work with HTTP and HTTPS traffic

Current approach

I’ve set up an Envoy-based egress gateway with Linkerd:

apiVersion: v1
kind: Namespace
metadata:
  name: linkerd-egress
  annotations:
    linkerd.io/inject: enabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: egress-gateway
  namespace: linkerd-egress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: egress-gateway
  template:
    metadata:
      labels:
        app: egress-gateway
      annotations:
        config.linkerd.io/proxy-outbound-port: "8080"
        config.linkerd.io/skip-outbound-ports: "443,80"
    spec:
      containers:
      - name: proxy
        image: envoyproxy/envoy:v1.22.0
        # ... [configuration details]
---
apiVersion: v1
kind: Service
metadata:
  name: egress-gateway
  namespace: linkerd-egress
spec:
  type: LoadBalancer
  loadBalancerIP: 10.x.y.100
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    app: egress-gateway

Then I created an app that tries to use this gateway:

apiVersion: v1
kind: Pod
metadata:
  name: egress-debug-pod
  namespace: app-fixed-ip-linkerd
  annotations:
    linkerd.io/inject: enabled
spec:
  containers:
  - name: debug
    image: nicolaka/netshoot:latest
    # ... [configuration details]

The issue

When I run tests from inside the pod, I can successfully reach external services like httpbin.org, but my tcpdump monitoring on the node shows that the traffic is leaving with the node’s IP (10.x.y.12) instead of the gateway’s fixed IP (10.x.y.100):

09:27:18.917124 cali115e2e38908 In  IP 172.16.a.b.55964 > 18.205.c.d.80: Flags [S], seq 399294597, win 64860, options [mss 1410,sackOK,TS val 213956220 ecr 0,nop,wscale 7], length 0
09:27:18.917161 eth0  Out IP 10.x.y.12.17975 > 18.205.c.d.80: Flags [S], seq 399294597, win 64860, options [mss 1410,sackOK,TS val 213956220 ecr 0,nop,wscale 7], length 0

Even when accessing the egress gateway service directly:

curl http://egress-gateway.linkerd-egress.svc.cluster.local:8080/ip

The traffic still shows my public IP (a.b.c.d) rather than the fixed IP.

Questions

  1. What is the recommended approach to achieve fixed egress IPs with Linkerd?
  2. Is there a way to configure Linkerd to route egress traffic from specific namespaces through a dedicated gateway with a fixed IP?
  3. Should I be using a different Linkerd configuration or pattern for this use case?
  4. Do I need specific firewall rules to make this work properly?

I understand that I might need to set up NAT rules in my OPNsense firewall, but I’d like to understand the proper Linkerd configuration first, to ensure the traffic is correctly routed through the egress gateway.

Any help or pointers to relevant documentation or examples would be greatly appreciated!

Thanks for your time!
Daniel