Configuring Egress Gateway with Static Node IP

This guide shows how to route outbound traffic from pods through a specific Kubernetes node using Cilium’s Egress Gateway functionality. This is useful when you need predictable egress IPs, for example, for firewall allowlists or compliance.

warning

This guide assumes your egress gateway node has a static public IP assigned to the host. On Hetzner, cloud VMs will receive a different public IP after cluster upgrades or node reprovisioning. To avoid this, use bare-metal servers as your egress gateway nodes.

As an added benefit, Hetzner bare-metal machines offer significantly higher network bandwidth: up to 10 Gbit/s dedicated NIC, compared to max 1 Gbit/s on cloud instances.

Step 1: Label the Egress Gateway Node

The label acts as a selector that the Egress Gateway Policy will  use to identify which node should handle the outbound traffic. Without this label, the policy wouldn’t know which node is eligible to route egress traffic, and the setup would not function as intended.

For adding a persistent label to the node, add it to the metadata section of your machine deployment in the Cluster resource. This ensures that the label is applied to the node even after a node replacement or upgrade. Example cluster definition:

yaml
apiVersion: cluster.x-k8s.io/v1beta1 kind: Cluster metadata: name: egress-testing spec: clusterNetwork: services: cidrBlocks: ['10.128.0.0/12'] pods: cidrBlocks: ['192.168.0.0/16'] serviceDomain: 'cluster.local' topology: class: hetzner-apalla-1-32-v2 version: v1.32.5 controlPlane: replicas: 1 workers: machineDeployments: - class: workeramd64hcloud name: md-cloud replicas: 3 failureDomain: nbg1 variables: overrides: - name: workerMachineTypeHcloud value: cpx31 - class: workeramd64baremetal name: md-baremetal replicas: 1 metadata: // [!code ++] labels: // [!code ++] node-role.kubernetes.io/egress-gateway: "true" // [!code ++] variables: - name: region value: nbg1 - name: controlPlaneMachineTypeHcloud value: cpx31

For more information on node labeling, please refer to the Labelling and assign roles on nodes page.

Step 2: Create the Egress Gateway Policy

The Egress Gateway Policy is a Kubernetes resource that defines how outbound traffic should be routed. It specifies the node label to match and the static IP address to use for egress traffic.

Replace the egressIP value with the actual external IP of your egress gateway node. For finding the external IP of your node, you can use the following command:

bash
kubectl get nodes -l node-role.kubernetes.io/egress-gateway=true -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}'

Sample Egress Gateway Policy YAML:

egress-gateway-policy.yaml yaml
apiVersion: cilium.io/v2 kind: CiliumEgressGatewayPolicy metadata: name: force-egress-via-node spec: selectors: - podSelector: matchLabels: app: my-app destinationCIDRs: - 0.0.0.0/0 egressGateway: nodeSelector: matchLabels: node-role.kubernetes.io/egress-gateway: "true" // [!code tooltip:135.119.210.110:1:Replace with the actual external IP of your egress gateway node] egressIP: 135.119.210.110

You can save this YAML to a file named egress-gateway-policy.yaml and apply it using kubectl :

bash
kubectl apply -f egress-gateway-policy.yaml

In this example, the policy will route all outbound traffic from pods with the label app: my-app through the node labeled with node-role.kubernetes.io/egress-gateway: "true" . There are additional options available for an Egress Gateway Policy, such as specifying multiple node labels or CIDR ranges. For more details, refer to the Cilium documentation.

Step 3: Test the Egress Gateway (Optional)

To verify that the egress traffic is being routed through the specified node, you can deploy a test pod in the non-egress node and check its outbound IP address:

test-pod.yaml yaml
apiVersion: v1 kind: Pod metadata: name: egress-test labels: app: my-app spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/egress-gateway operator: DoesNotExist containers: - name: curl image: curlimages/curl:8.5.0 command: ["sleep", "3600"] restartPolicy: Never

Save this YAML to a file named test-pod.yaml and apply it using kubectl :

bash
kubectl apply -f test-pod.yaml

Once the pod is running, you can exec into it and check the outbound IP address. The following command uses ifconfig.me , a simple web service that returns the public IP address of the client making the request:

bash
kubectl exec -it egress-test -- curl -s ifconfig.me

The output will show the IP address as seen by external services.

If you see the static IP address you specified in the Egress Gateway Policy, it means the setup is working correctly.