Skip to content

Commit a865a8f

Browse files
committed
Update networking/egressip
1 parent 5c47146 commit a865a8f

File tree

5 files changed

+221
-51
lines changed

5 files changed

+221
-51
lines changed

content/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and benefit from the expertise shared in this repository.
2424

2525
|Date|Headline|
2626
|---|---|
27+
|2026-02-12|[Update networking/egressip](networking/egress-ip/)|
2728
|2026-02-06|[Added domain.xml adjustment example via sidecar hook](kubevirt/adjust-domain-xml/)|
2829
|2026-02-06|[Updated IBM Fusion Access for SAN with air-gapped/disconnected details](storage/ibm-fusion-access-san/)|
2930
|2026-01-29|[Added how to setup a rhel 10 router](my-lab/rhel-router/)|
266 KB
Loading
Lines changed: 192 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,212 @@
11
---
22
title: Egress IP
3-
linktitle: Egress IP
4-
description: Egress IP demo
5-
tags: ['Egress','v4.19']
3+
linktitle: Egress IP
4+
description: Configure egress IP addresses so traffic from selected namespaces leaves the cluster with a fixed, predictable source IP.
5+
tags: ['Egress','v4.20']
66
---
7-
# Some information
7+
# Egress IP
88

9-
Official documentation: <https://docs.redhat.com/en/documentation/openshift_container_platform/4.19/html/ovn-kubernetes_network_plugin/configuring-egress-ips-ovn>
9+
This example shows how to configure **egress IP addresses** on OpenShift so that traffic from selected namespaces uses a fixed source IP. That makes it easy to whitelist cluster workloads in firewalls or identify traffic in logs (e.g., from a specific app or team).
1010

11-
Tested with:
11+
**Official documentation:** [Chapter 9. Configuring an egress IP address](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/ovn-kubernetes_network_plugin/configuring-egress-ips-ovn)
1212

13-
|Component|Version|
14-
|---|---|
15-
|OpenShift|v4.19.14|
13+
**Tested with:**
1614

17-
## Prepare cluster
15+
| Component | Version |
16+
|-----------|---------|
17+
| OpenShift | v4.20.1 |
18+
19+
## Overview
20+
21+
![](overview.drawio)
22+
23+
Two OpenShift clusters are used:
24+
25+
### Cluster: ocp6
26+
27+
**Source cluster** — here we configure egress IPs and run a pod that sends HTTP requests (via `curl`) to the web server on **ocp7**.
28+
29+
=== "Worker Nodes"
30+
31+
| NAME | IP |
32+
|-----------------|--------------|
33+
| ocp6-worker-0 | 10.32.105.95 |
34+
| ocp6-worker-1 | 10.32.105.96 |
35+
| ocp6-worker-2 | 10.32.105.97 |
36+
37+
=== "oc output"
38+
39+
```shell
40+
% oc get nodes -l node-role.kubernetes.io/worker -o custom-columns='NAME:.metadata.name,IP:.status.addresses[0].address'
41+
NAME IP
42+
ocp6-worker-0 10.32.105.95
43+
ocp6-worker-1 10.32.105.96
44+
ocp6-worker-2 10.32.105.97
45+
```
46+
47+
### Cluster: ocp7
48+
49+
**Target cluster** — hosts a simple web server so we can see the **sender IP** in the server logs and verify egress IP behavior.
50+
51+
=== "Worker Nodes"
52+
53+
| NAME | IP |
54+
|-----------------|---------------|
55+
| ocp7-worker-0 | 10.32.105.103 |
56+
| ocp7-worker-1 | 10.32.105.104 |
57+
| ocp7-worker-2 | 10.32.105.105 |
58+
59+
=== "oc output"
60+
61+
```shell
62+
% oc get nodes -l node-role.kubernetes.io/worker -o custom-columns='NAME:.metadata.name,IP:.status.addresses[0].address'
63+
NAME IP
64+
ocp7-worker-0 10.32.105.103
65+
ocp7-worker-1 10.32.105.104
66+
ocp7-worker-2 10.32.105.105
67+
```
68+
69+
## Deploy simple-nginx on ocp7
70+
71+
Deploy the target web server on **ocp7** (target cluster):
1872

1973
```shell
20-
% oc get nodes -l node-role.kubernetes.io/worker
21-
NAME STATUS ROLES AGE VERSION
22-
ocp1-worker-0 Ready worker 79d v1.32.5
23-
ocp1-worker-1 Ready worker 79d v1.32.5
24-
ocp1-worker-2 Ready worker 79d v1.32.5
25-
26-
% oc label node/ocp1-worker-0 k8s.ovn.org/egress-assignable=""
27-
node/ocp1-worker-0 labeled
28-
% oc label node/ocp1-worker-1 k8s.ovn.org/egress-assignable=""
29-
node/ocp1-worker-1 labeled
30-
% oc label node/ocp1-worker-2 k8s.ovn.org/egress-assignable=""
31-
node/ocp1-worker-2 labeled
32-
33-
oc apply -f - <<EOF
34-
heredoc> apiVersion: k8s.ovn.org/v1
35-
kind: EgressIP
36-
metadata:
37-
name: egress-coe
38-
spec:
39-
egressIPs:
40-
- 10.32.105.72
41-
- 10.32.105.73
42-
namespaceSelector:
43-
matchLabels:
44-
egress: coe
45-
heredoc> EOF
46-
egressip.k8s.ovn.org/egress-coe created
74+
oc new-project egress-target
75+
oc apply -k 'git@github.com:openshift-examples/kustomize/components/simple-nginx?ref=2026-02-12'
4776
```
4877

49-
## Deployment
78+
Get the route hostname:
5079

5180
```shell
52-
oc new-project rbohne-egress
53-
oc deploy -k simple-nginx...
54-
oc rsh deployment/simple-nginx
55-
curl $WEBSERVER
56-
57-
oc label namespace/rbohne-egress egress=coe
81+
% oc get route simple-nginx -o jsonpath='{.spec.host}'
82+
simple-nginx-egress-target.apps.ocp7.stormshift.coe.muc.redhat.com
5883
```
5984

60-
```log
61-
10.32.96.44 - - [31/Aug/2025:11:54:00 +0200] "GET / HTTP/1.1" 301 247 "-" "curl/7.76.1"
62-
10.32.105.72 - - [31/Aug/2025:11:56:31 +0200] "GET / HTTP/1.1" 301 247 "-" "curl/7.76.1"
85+
Call the web server from your machine (or any client):
86+
87+
```shell
88+
% curl -I https://simple-nginx-egress-target.apps.ocp7.stormshift.coe.muc.redhat.com
89+
90+
HTTP/1.1 200 OK
91+
server: nginx/1.26.3
92+
date: Thu, 12 Feb 2026 15:19:17 GMT
93+
content-type: text/html
94+
content-length: 45
95+
last-modified: Thu, 12 Feb 2026 15:17:31 GMT
96+
etag: "698def0b-2d"
97+
accept-ranges: bytes
98+
set-cookie: 5386f97f58871214bc079b41bd88c1cd=43357b3d3312239cb10b42b1c3d17138; path=/; HttpOnly; Secure; SameSite=None
99+
cache-control: private
63100
```
64101

65-
## Check IP on the node
102+
Check the pod logs to see the client IP that nginx recorded:
66103

67104
```shell
68-
sh-5.1# ip -br a show dev br-ex
69-
br-ex UNKNOWN 10.32.105.69/20 169.254.0.2/17 10.32.105.72/32
70-
sh-5.1#
105+
% oc logs -f deployment/simple-nginx | grep curl
106+
10.130.0.2 - - [12/Feb/2026:15:26:45 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.12.1" "10.32.96.62"
71107
```
108+
109+
- `10.130.0.2` — cluster-internal IP of the ingress/router pod
110+
- `10.32.96.62`**real client IP** (your machine or the pod making the request)
111+
112+
## Configure egress IP on ocp6
113+
114+
1. **Label worker nodes** so they can be assigned egress IPs:
115+
116+
```shell
117+
% oc label node -l node-role.kubernetes.io/worker k8s.ovn.org/egress-assignable=""
118+
node/ocp6-worker-0 labeled
119+
node/ocp6-worker-1 labeled
120+
node/ocp6-worker-2 labeled
121+
```
122+
123+
2. **Create an `EgressIP` object** with the IPs to use and which namespaces (via label) will use them. Use IPs that are valid on your cluster’s egress network and not in use by nodes or other services:
124+
125+
```shell
126+
oc apply -f - <<EOF
127+
apiVersion: k8s.ovn.org/v1
128+
kind: EgressIP
129+
metadata:
130+
name: egress-coe
131+
spec:
132+
egressIPs:
133+
- 10.32.105.72
134+
- 10.32.105.73
135+
namespaceSelector:
136+
matchLabels:
137+
egress: coe
138+
EOF
139+
```
140+
141+
3. **Deploy a curl pod** in a new project — first **without** the egress label so traffic uses the node IP:
142+
143+
```shell
144+
oc new-project curl
145+
oc apply -k 'git@github.com:openshift-examples/kustomize/components/rhel-support-tools?rev=2026-02-12'
146+
```
147+
148+
From inside the pod, run `curl` against the ocp7 route. The pod is on `ocp6-worker-2` (node IP `10.32.105.97`):
149+
150+
```shell
151+
% oc rsh deployment.apps/rhel-support-tools
152+
sh-5.1$ echo $NODE_NAME $NODE_IP
153+
ocp6-worker-2 10.32.105.97
154+
sh-5.1$ curl -Ik https://simple-nginx-egress-target.apps.ocp7.stormshift.coe.muc.redhat.com
155+
HTTP/1.1 200 OK
156+
server: nginx/1.26.3
157+
date: Thu, 12 Feb 2026 15:42:30 GMT
158+
content-type: text/html
159+
content-length: 45
160+
last-modified: Thu, 12 Feb 2026 15:31:00 GMT
161+
etag: "698df234-2d"
162+
accept-ranges: bytes
163+
set-cookie: 5386f97f58871214bc079b41bd88c1cd=7fbe3d3d6235cc5431656b0f57361872; path=/; HttpOnly; Secure; SameSite=None
164+
cache-control: private
165+
166+
sh-5.1$
167+
```
168+
169+
On **ocp7**, check the `simple-nginx` logs. The client IP should be the **node IP** (`10.32.105.97`), because the namespace does not use egress IP yet:
170+
171+
```shell
172+
10.130.0.2 - - [12/Feb/2026:15:42:30 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.76.1" "10.32.105.97"
173+
```
174+
175+
![](nodeip-screenshot.png)
176+
177+
4. **Enable egress IP** for the `curl` namespace by adding the label that matches the `EgressIP` `namespaceSelector`:
178+
179+
```shell
180+
oc label namespace/curl egress=coe
181+
```
182+
183+
Run `curl` again from the same pod (still on the same node):
184+
185+
```shell
186+
% oc rsh deployment.apps/rhel-support-tools
187+
sh-5.1$ echo $NODE_NAME $NODE_IP
188+
ocp6-worker-2 10.32.105.97
189+
sh-5.1$ curl -Ik https://simple-nginx-egress-target.apps.ocp7.stormshift.coe.muc.redhat.com
190+
HTTP/1.1 200 OK
191+
server: nginx/1.26.3
192+
date: Thu, 12 Feb 2026 15:48:38 GMT
193+
content-type: text/html
194+
content-length: 45
195+
last-modified: Thu, 12 Feb 2026 15:31:00 GMT
196+
etag: "698df234-2d"
197+
accept-ranges: bytes
198+
set-cookie: 5386f97f58871214bc079b41bd88c1cd=7fbe3d3d6235cc5431656b0f57361872; path=/; HttpOnly; Secure; SameSite=None
199+
cache-control: private
200+
201+
sh-5.1$
202+
```
203+
204+
On **ocp7**, check the `simple-nginx` logs again. The client IP should now be **10.32.105.72** — one of the egress IPs — instead of the node IP:
205+
206+
```shell
207+
10.130.0.2 - - [12/Feb/2026:15:48:38 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.76.1" "10.32.105.72"
208+
```
209+
210+
**Source IP is now the egress IP.**
211+
212+
![](egressip-screenshot.png)
225 KB
Loading
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.6 Chrome/140.0.7339.249 Electron/38.8.0 Safari/537.36" version="29.3.6">
2+
<diagram name="Page-1" id="CfVRJ24CIUdbo2xG4-Xr">
3+
<mxGraphModel dx="1823" dy="486" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="EBQ1oc4t_hqgZCyZlvfD-4" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;align=center;verticalAlign=top;fontStyle=1;fontSize=15;" value="OpenShift Cluster - ocp6" vertex="1">
8+
<mxGeometry height="160" width="270" x="40" y="40" as="geometry" />
9+
</mxCell>
10+
<mxCell id="EBQ1oc4t_hqgZCyZlvfD-5" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;align=center;verticalAlign=top;fontStyle=1;fontSize=15;" value="OpenShift Cluster - ocp7" vertex="1">
11+
<mxGeometry height="160" width="270" x="480" y="40" as="geometry" />
12+
</mxCell>
13+
<mxCell id="EBQ1oc4t_hqgZCyZlvfD-6" parent="1" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" value="&lt;b&gt;simple-http-server&lt;/b&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div style=&quot;&quot;&gt;Web server to determine the sender&#39;s IP address.&lt;/div&gt;" vertex="1">
14+
<mxGeometry height="70" width="230" x="500" y="90" as="geometry" />
15+
</mxCell>
16+
<mxCell id="EBQ1oc4t_hqgZCyZlvfD-7" parent="1" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" value="Configured egress IPs&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;CURL as Client to connect to simple-http-server running on ocp7&lt;br&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/div&gt;" vertex="1">
17+
<mxGeometry height="90" width="240" x="60" y="80" as="geometry" />
18+
</mxCell>
19+
<mxCell id="EBQ1oc4t_hqgZCyZlvfD-8" edge="1" parent="1" source="EBQ1oc4t_hqgZCyZlvfD-7" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" target="EBQ1oc4t_hqgZCyZlvfD-6" value="">
20+
<mxGeometry height="50" relative="1" width="50" as="geometry">
21+
<mxPoint x="560" y="220" as="sourcePoint" />
22+
<mxPoint x="410" y="250" as="targetPoint" />
23+
</mxGeometry>
24+
</mxCell>
25+
</root>
26+
</mxGraphModel>
27+
</diagram>
28+
</mxfile>

0 commit comments

Comments
 (0)