Skip to content

Traffic Flows

In interconnect mode each node is its own OVN zone (by default). Zones are stitched together via a transit switch using GENEVE tunnels over the physical underlay.

Scope: Diagrams and path narratives on this page use IPv4 addresses for readability. Dual-stack and IPv6-only clusters follow the same logical component paths (logical switches, ovn_cluster_router, gateway routers, transit switch, management port, and host masquerade); substitute the IPv6 defaults from the subnet table below. IPv6-specific service VIP, EgressIP, and host-masquerade address details are out of scope for these diagrams.

Topology Reference

The examples below use a two-node IPv4 cluster, each node in its own zone:

Component ovn-worker ovn-worker2
Node / Underlay IP 172.19.0.2 172.19.0.3
External Switch ext_ovn-worker ext_ovn-worker2
Gateway Router GR_ovn-worker GR_ovn-worker2
Join Switch IP 100.64.0.2/16 100.64.0.3/16
Transit Switch IP 100.88.0.3/16 100.88.0.4/16
Pod Subnet 10.244.0.0/24 10.244.2.0/24
Management Port (ovn-k8s-mp0) 10.244.0.2 10.244.2.2

Default Internal Subnets

Subnet IPv4 IPv6 Purpose
Join 100.64.0.0/16 fd98::/64 Connects ovn_cluster_router ↔ gateway routers
Transit 100.88.0.0/16 fd97::/64 Interconnects zones across the cluster
Masquerade 169.254.169.0/29 fd69::/125 Host ↔ OVN service traffic masquerading

Full Topology Diagram

The host datapath is abstracted as eth0 → breth0 → External Switch. OVS integration details (br-int, patch ports) are omitted from all diagrams on this page.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 20}, 'themeVariables': {'fontSize': '20px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph TD
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold

    subgraph zone1["ovn-worker zone · 172.19.0.2"]
        eth1["eth0"]:::host --- br1["breth0"]:::host
        br1 --- es1["External Switch · ext_ovn-worker"]:::sw
        es1 --- gr1["GR_ovn-worker"]:::rtr
        gr1 ---|"100.64.0.2/16"| js1["Join Switch"]:::sw
        js1 --- cr1["ovn_cluster_router"]:::rtr
        cr1 ---|"10.244.0.1/24"| ls1["Logical Switch · 10.244.0.0/24"]:::sw
        ls1 --- mp1["ovn-k8s-mp0 · 10.244.0.2"]:::host
    end

    subgraph zone2["ovn-worker2 zone · 172.19.0.3"]
        eth2["eth0"]:::host --- br2["breth0"]:::host
        br2 --- es2["External Switch · ext_ovn-worker2"]:::sw
        es2 --- gr2["GR_ovn-worker2"]:::rtr
        gr2 ---|"100.64.0.3/16"| js2["Join Switch"]:::sw
        js2 --- cr2["ovn_cluster_router"]:::rtr
        cr2 ---|"10.244.2.1/24"| ls2["Logical Switch · 10.244.2.0/24"]:::sw
        ls2 --- mp2["ovn-k8s-mp0 · 10.244.2.2"]:::host
    end

    transit["Transit Switch · 100.88.0.0/16"]:::ts
    cr1 ---|"100.88.0.3/16"| transit
    cr2 ---|"100.88.0.4/16"| transit

East-West Traffic Flows

Pod to Pod – Same Node

Both pods are on ovn-worker and attach to the same node-local logical switch. Delivery uses an L2 lookup at the egress stage — traffic never reaches ovn_cluster_router and no tunnel is used.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 60, 'padding': 15}, 'themeVariables': {'fontSize': '30px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold

    A["Pod A · 10.244.0.5"]:::pod -->|"1 · egress"| LS["Logical Switch · L2 lookup"]:::sw
    LS -->|"2 · deliver"| B["Pod B · 10.244.0.8"]:::pod

Pod to Pod – Different Node

Pod A is on ovn-worker, Pod B is on ovn-worker2. Traffic crosses zones via the transit switch over a GENEVE tunnel on the underlay.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold

    A["Pod A · 10.244.0.5"]:::pod -->|"1"| LS1["Logical Switch"]:::sw
    LS1 -->|"2"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"3 · GENEVE"| TS["Transit Switch"]:::ts
    TS -->|"4"| CR2["ovn_cluster_router"]:::rtr
    CR2 -->|"5"| LS2["Logical Switch"]:::sw
    LS2 -->|"6"| B["Pod B · 10.244.2.5"]:::pod

Pod to ClusterIP (ovn pod backend)

The OVN load balancer on the logical switch DNATs the ClusterIP VIP to a backend pod endpoint. After DNAT, normal pod-to-pod routing applies.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 60, 'padding': 15}, 'themeVariables': {'fontSize': '40px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef decision fill:#ffca28,stroke:#F57F17,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod · 10.244.0.5"]:::pod -->|"1 · dst: ClusterIP"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR --> DECISIONEndpoint local?:::decision
    DECISION -->|"Same node"| LSL["Logical Switch → Local Pod"]:::pod
    DECISION -->|"Different node"| TS["Transit Switch → ovn_cluster_router → Logical Switch → Remote Pod"]:::ts

Pod to LoadBalancerIP/ExternalIP Service (ovn pod backend)

A pod inside the cluster targets a Kubernetes Service of type LoadBalancer (via its status.loadBalancer.ingress VIP) or a Service with an externalIPs entry. From the pod's perspective these VIPs are handled the same way as ClusterIP — the OVN load balancer on the logical switch DNATs the VIP to a backend pod endpoint.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 40, 'rankSpacing': 60, 'padding': 15}, 'themeVariables': {'fontSize': '40px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef decision fill:#ffca28,stroke:#F57F17,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod · 10.244.0.5"]:::pod -->|"1 · dst: LB/ExternalIP"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR --> DECISIONEndpoint local?:::decision
    DECISION -->|"Same node"| LSL["Logical Switch → Local Pod"]:::pod
    DECISION -->|"Different node"| TS["Transit Switch → ovn_cluster_router → Logical Switch → Remote Pod"]:::ts

Pod to NodePort (ovn pod backend) – Same Node NodePort Access

Pod accesses <nodeIP>:<nodePort> on the same node it runs on.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 60, 'padding': 15}, 'themeVariables': {'fontSize': '30px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod · 10.244.0.5"]:::pod -->|"1 · dst: NodePort"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| LS
    LS -->|"4 · deliver"| EP["Endpoint Pod · 10.244.0.8"]:::pod

Pod to NodePort (ovn pod backend) – Different Node NodePort Access

Pod accesses <remoteNodeIP>:<nodePort> on a different node. The destination is the remote node's IP, so traffic egresses the local node via the GR (SNAT to local node IP) and travels over the physical underlay — not via GENEVE / transit switch. On the remote node, the packet enters through breth0 → External Switch → GR, where the GR's load balancer performs the DNAT to the backend pod endpoint.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod · 10.244.0.5"]:::pod -->|"1"| LS1["Logical Switch"]:::sw
    LS1 -->|"2"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"3"| JS1["Join Switch"]:::sw
    JS1 -->|"4"| GR1["GR · SNAT"]:::rtr
    GR1 -->|"5"| ES1["External Switch"]:::sw
    ES1 -->|"6"| BR1["breth0 → eth0"]:::sw
    BR1 -->|"7 · underlay"| BR2["eth0 → breth0 (remote)"]:::sw
    BR2 -->|"8"| ES2["External Switch"]:::sw
    ES2 -->|"9"| GR2["GR · LB DNAT"]:::rtr
    GR2 -->|"10"| JS2["Join Switch"]:::sw
    JS2 -->|"11"| CR2["ovn_cluster_router"]:::rtr
    CR2 -->|"12"| LS2["Logical Switch"]:::sw
    LS2 -->|"13"| EP["Endpoint Pod · 10.244.2.5"]:::pod

Pod to ClusterIP/LoadBalancerIP/ExternalIP (host networked pod backend) – Same Node

The endpoint is a host-networked pod on the same node. After DNAT, ovn_cluster_router delivers the packet back to the logical switch and out the management port (ovn-k8s-mp0) to the host — it does not traverse the join switch or gateway router.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 40, 'rankSpacing': 40, 'padding': 20}, 'themeVariables': {'fontSize': '30px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1 · dst: ClusterIP"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| LS
    LS -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| HNP["Host Networked Pod"]:::host

Pod to ClusterIP/LoadBalancerIP/ExternalIP (host networked pod backend) – Different Node

The endpoint is a host-networked pod on a different node. Traffic exits through the local GR (with SNAT), crosses the underlay. On the remote node the destination is a host IP, so traffic is delivered directly to the host via the breth0 host gateway interface — it does not re-enter the OVN pipeline.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1 · dst: ClusterIP"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · SNAT"]:::rtr
    GR -->|"5"| ES["External Switch"]:::sw
    ES -->|"6"| BR["breth0 → eth0"]:::sw
    BR -->|"7 · underlay"| BR2["eth0 → breth0 (remote)"]:::sw
    BR2 -->|"8 · host gateway"| HNP["Host Networked Pod"]:::host

Pod to NodePort (host networked pod backend) – Same Node NodePort Access

This scenario occurs when a Kubernetes Service with a NodePort selects host-networked pods as backends (e.g. infrastructure DaemonSets exposed via a Service for uniform service discovery). The datapath is the same as the ClusterIP → host-networked case — delivery is via the management port.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 40, 'rankSpacing': 40, 'padding': 20}, 'themeVariables': {'fontSize': '30px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1 · dst: NodePort"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| LS
    LS -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| HNP["Host Networked Pod"]:::host

Pod to NodePort (host networked pod backend) – Different Node NodePort Access

Same scenario (Service with NodePort selecting host-networked pods), but the backend is on a remote node. The OVN load balancer DNATs locally to the host-networked pod IP. Since this is a host IP, traffic exits through the GR (with SNAT), crosses the physical underlay — not GENEVE. On the remote node, traffic is delivered directly to the host via the breth0 host gateway interface — it does not re-enter the OVN pipeline.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1 · dst: NodePort"| LS["Logical Switch · LB: DNAT"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · SNAT"]:::rtr
    GR -->|"5"| ES["External Switch"]:::sw
    ES -->|"6"| BR["breth0 → eth0"]:::sw
    BR -->|"7 · underlay"| BR2["eth0 → breth0 (remote)"]:::sw
    BR2 -->|"8 · host gateway"| HNP["Host Networked Pod"]:::host

Egress Traffic Flows

Pod to External: routingViaHost=false

Default shared gateway mode. Egress goes entirely through the OVN pipeline; the GR SNATs the pod IP to the node IP.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · SNAT"]:::rtr
    GR -->|"5"| ES["External Switch"]:::sw
    ES -->|"6"| BR["breth0"]:::sw
    BR -->|"7"| ETH["eth0"]:::sw
    ETH -->|"8"| EXT["External"]:::ext

Pod to External: routingViaHost=true

Local gateway mode. Egress leaves OVN via the management port (ovn-k8s-mp0) into the host networking stack. The host routing table selects the egress interface and iptables/nftables masquerade SNATs to the node IP. Traffic does not traverse the gateway router for SNAT.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 40, 'rankSpacing': 40, 'padding': 20}, 'themeVariables': {'fontSize': '30px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| LS
    LS -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| HOST["Host · masquerade"]:::host
    HOST -->|"6"| ETH["eth0"]:::sw
    ETH -->|"7"| EXT["External"]:::ext

EgressIP Pod to External – Same Node; EIP on primary br-ex shared network

The GR SNATs to the EgressIP instead of the node IP.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · SNAT → EgressIP"]:::rtr
    GR -->|"5"| ES["External Switch · breth0"]:::sw
    ES -->|"6"| ETH["eth0"]:::sw
    ETH -->|"7 · SNAT: EgressIP"| EXT["External"]:::ext

EgressIP Pod to External – Same Node; EIP on secondary network

When the EgressIP is on a secondary host interface, ovn_cluster_router reroutes the packet to the local management port. The host then applies an iptables SNAT (OVN-KUBE-EGRESS-IP-MULTI-NIC) to the EgressIP and an ip-rule steers the packet out the secondary NIC. No NAT is performed on the gateway router.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 80, 'rankSpacing': 50, 'padding': 65}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router · reroute"]:::rtr
    CR -->|"3"| LS
    LS -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| HOST["Host · iptables SNAT → EgressIP"]:::host
    HOST -->|"6"| ETH2["Secondary NIC"]:::sw
    ETH2 -->|"7 · SNAT: EgressIP"| EXT["External"]:::ext

EgressIP Pod to Node IP within the cluster

When an EgressIP pod sends traffic to another node IP within the cluster, the EgressIP is not used as the source IP. EgressIP only applies to destinations outside the cluster's node network.

Shared gateway mode (routingViaHost=false)

The GR performs a regular SNAT to the node IP (same as non-EgressIP egress).

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · SNAT → Node IP"]:::rtr
    GR -->|"5"| ES["External Switch"]:::sw
    ES -->|"6"| BR["breth0 → eth0"]:::sw
    BR -->|"7 · SNAT: Node IP"| DST["ovn-worker2"]:::mgmt

Local gateway mode (routingViaHost=true)

Egress leaves OVN via the management port. The host networking stack performs the SNAT to the node IP via iptables/nftables masquerade (same as non-EgressIP local gateway egress).

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '50px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| LS
    LS -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| HOST["Host · masquerade SNAT → Node IP"]:::host
    HOST -->|"6"| ETH["eth0"]:::sw
    ETH -->|"7 · SNAT: Node IP"| DST["ovn-worker2"]:::mgmt

EgressIP Pod to External – Different Node; EIP on primary br-ex shared network

Traffic is rerouted to the EgressIP node via the transit switch, then exits from that node's GR.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS1["Logical Switch"]:::sw
    LS1 -->|"2"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"3 · reroute"| TS["Transit Switch"]:::ts
    TS -->|"4"| CR2["ovn_cluster_router"]:::rtr
    CR2 -->|"5"| JS2["Join Switch"]:::sw
    JS2 -->|"6"| GR2["GR · SNAT → EgressIP"]:::rtr
    GR2 -->|"7"| ES2["External Switch"]:::sw
    ES2 -->|"8 · SNAT: EgressIP"| EXT["External"]:::ext

EgressIP Pod to External – Different Node; EIP on secondary network

Traffic is rerouted to the EgressIP node via the transit switch. On that node, ovn_cluster_router sends the packet out the management port; the host performs iptables SNAT to the EgressIP and steers it onto the secondary NIC.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod"]:::pod -->|"1"| LS1["Logical Switch"]:::sw
    LS1 -->|"2"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"3 · reroute"| TS["Transit Switch"]:::ts
    TS -->|"4"| CR2["ovn_cluster_router · reroute"]:::rtr
    CR2 -->|"5"| LS2["Logical Switch"]:::sw
    LS2 -->|"6"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"7"| HOST["Host · iptables SNAT → EgressIP"]:::host
    HOST -->|"8"| ETH2["Secondary NIC"]:::sw
    ETH2 -->|"9 · SNAT: EgressIP"| EXT["External"]:::ext

Ingress Traffic Flows

External to NodePort/ExternalIP/LoadBalancerIP (ovn pod backend) – Same Node: routingViaHost=false

External traffic enters the node where the backend pod is local.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External Client"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| ES["External Switch"]:::sw
    ES -->|"4"| GR["GR · LB DNAT"]:::rtr
    GR -->|"5"| JS["Join Switch"]:::sw
    JS -->|"6"| CR["ovn_cluster_router"]:::rtr
    CR -->|"7"| LS["Logical Switch"]:::sw
    LS -->|"8"| P["Pod · 10.244.0.5"]:::pod

External to NodePort/ExternalIP/LoadBalancerIP (ovn pod backend) – Different Node: routingViaHost=false

External traffic enters one node but the backend pod is on a different node. After DNAT, traffic crosses zones via the transit switch.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| ES["Ext Switch"]:::sw
    ES -->|"4"| GR["GR · DNAT"]:::rtr
    GR -->|"5"| JS["Join Switch"]:::sw
    JS -->|"6"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"7 · GENEVE"| TS["Transit Switch"]:::ts
    TS -->|"8"| CR2["ovn_cluster_router"]:::rtr
    CR2 -->|"9"| LS2["Logical Switch"]:::sw
    LS2 -->|"10"| P["Pod"]:::pod

External to NodePort/ExternalIP/LoadBalancerIP (ovn pod backend) – Same Node: routingViaHost=true

Local gateway mode with default externalTrafficPolicy: Cluster. External traffic enters the host via breth0, where nftables DNATs the NodePort / ExternalIP / LoadBalancerIP to its ClusterIP. The packet is then routed back into breth0 and enters OVN via the GR, where the GR load balancer performs the final DNAT to a backend pod endpoint.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '50px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| HOST["Host · nftables DNAT → ClusterIP"]:::host
    HOST -->|"4"| BR2["breth0"]:::sw
    BR2 -->|"5"| GR["GR · LB DNAT"]:::rtr
    GR -->|"6"| JS["Join Switch"]:::sw
    JS -->|"7"| CR["ovn_cluster_router"]:::rtr
    CR -->|"8"| LS["Logical Switch"]:::sw
    LS -->|"9"| P["Pod · 10.244.0.5"]:::pod

External to NodePort/ExternalIP/LoadBalancerIP (ovn pod backend) – Different Node: routingViaHost=true

Local gateway mode with default externalTrafficPolicy: Cluster, backend on a remote node. The host nftables DNAT translates to ClusterIP, the packet is routed back into breth0, and the GR load balancer performs the final DNAT. Since the backend is remote, traffic crosses zones via the transit switch.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| HOST["Host · nftables DNAT → ClusterIP"]:::host
    HOST -->|"4"| BR2["breth0"]:::sw
    BR2 -->|"5"| GR["GR · LB DNAT"]:::rtr
    GR -->|"6"| JS["Join Switch"]:::sw
    JS -->|"7"| CR1["ovn_cluster_router"]:::rtr
    CR1 -->|"8 · GENEVE"| TS["Transit Switch"]:::ts
    TS -->|"9"| CR2["ovn_cluster_router"]:::rtr
    CR2 -->|"10"| LS2["Logical Switch"]:::sw
    LS2 -->|"11"| P["Pod"]:::pod

External to NodePort/ExternalIP/LoadBalancer (ovn pod backend) ETP=local

Shared gateway mode (routingViaHost=false)

With externalTrafficPolicy: Local, only local endpoints receive ingress traffic (preserving client source IP). A special skip_snat load balancer is used on the GR to preserve the client IP.

Ingress path:

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '60px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| ES["Ext Switch"]:::sw
    ES -->|"4"| GR["GR · DNAT · skip_snat · ETP=local"]:::rtr
    GR -->|"5 · client IP preserved"| JS["Join Switch"]:::sw
    JS -->|"6"| CR["ovn_cluster_router"]:::rtr
    CR -->|"7"| LS["Logical Switch"]:::sw
    LS -->|"8"| P["Local Pod"]:::pod

Reply path: The reply is not SNAT'd. Because skip_snat was used on ingress, the GR performs only a conntrack reverse DNAT (unDNAT): the pod's source address is translated back to the original service IP + service port. The client IP in the destination is unchanged throughout. EgressIP does not apply to service reply traffic — it only affects pod-initiated outbound connections.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '70px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    P["Pod reply"]:::pod -->|"1"| LS["Logical Switch"]:::sw
    LS -->|"2"| CR["ovn_cluster_router"]:::rtr
    CR -->|"3"| JS["Join Switch"]:::sw
    JS -->|"4"| GR["GR · unDNAT → svc IP:port"]:::rtr
    GR -->|"5"| ES["External Switch"]:::sw
    ES -->|"6"| BR["breth0 → eth0"]:::sw
    BR -->|"7"| EXT["External Client"]:::ext

Local gateway mode (routingViaHost=true)

In local gateway mode with ETP=Local, host nftables DNATs the NodePort to a special masquerade IP (169.254.169.3) and forwards into ovn-k8s-mp0. A dedicated load balancer on the logical switch translates the masquerade VIP to local-only backends. Client IP is preserved because iptables skips SNAT on ovn-k8s-mp0 for this flow.

Note: This path applies to services that allocate NodePorts (the default). A LoadBalancer Service with allocateLoadBalancerNodePorts: false bypasses the NodePort-based logical-switch load balancer; in that case host nftables DNATs directly to the local endpoint without the masquerade VIP detour.

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 60, 'rankSpacing': 50, 'padding': 35}, 'themeVariables': {'fontSize': '50px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph LR
    classDef pod fill:#66bb6a,stroke:#2E7D32,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef host fill:#ab47bc,stroke:#6A1B9A,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef mgmt fill:#78909c,stroke:#37474F,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ext fill:#ef5350,stroke:#B71C1C,stroke-width:1.5px,color:#000000,font-weight:bold

    EXT["External"]:::ext -->|"1"| ETH["eth0"]:::sw
    ETH -->|"2"| BR["breth0"]:::sw
    BR -->|"3"| HOST["Host · nftables DNAT → 169.254.169.3"]:::host
    HOST -->|"4"| MP["ovn-k8s-mp0"]:::mgmt
    MP -->|"5"| LS["Logical Switch · LB: masq VIP DNAT"]:::sw
    LS -->|"6"| P["Local Pod"]:::pod

Summary — Component Roles

%%{init: {'theme': 'base', 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 15}, 'themeVariables': {'fontSize': '16px', 'primaryTextColor': '#000000', 'secondaryTextColor': '#000000', 'tertiaryTextColor': '#000000', 'textColor': '#000000', 'nodeTextColor': '#000000'}}}%%
graph TD
    classDef sw fill:#42a5f5,stroke:#1565C0,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef rtr fill:#ffa726,stroke:#E65100,stroke-width:1.5px,color:#000000,font-weight:bold
    classDef ts fill:#26a69a,stroke:#00695C,stroke-width:1.5px,color:#000000,font-weight:bold

    LS["Logical Switch · per node"]:::sw -->|"routed pod traffic + LB DNAT"| CR["ovn_cluster_router · per zone"]:::rtr
    CR -->|"intra-zone routing"| LS
    CR -->|"cross-zone routing"| TS["Transit Switch · cluster-wide"]:::ts
    CR -->|"north-south routing"| JS["Join Switch"]:::sw
    JS --> GR["Gateway Router · per node · SNAT / DNAT (shared gw)"]:::rtr
    GR --> ES["External Switch · per node"]:::sw
    ES --> BRINT["breth0 ↔ eth0"]:::sw
Component Scope Role
Logical Switch Node-local An OVN logical switch emulates an L2 Ethernet switch. Each node has one logical switch that hosts all pod logical ports on that node. Same-node pod-to-pod traffic is delivered entirely within the logical switch via L2 lookup without reaching ovn_cluster_router. OVN load balancers are attached here to perform service DNAT (ClusterIP, NodePort, LoadBalancerIP, ExternalIP); after DNAT, routed traffic enters the routing pipeline
ovn_cluster_router Per zone An OVN logical router that performs L3 routing for east-west pod traffic within the zone. It connects to the node-local logical switch (pod subnet), the join switch (toward the gateway router), and the transit switch (for cross-zone traffic). Routing policies here implement EgressIP reroutes and management port delivery
Join Switch Per zone An internal logical switch that connects ovn_cluster_router to the per-node gateway routers within the same zone. Traffic traversing this switch is transitioning between east-west and north-south domains
Gateway Router Per node An OVN logical router (one per node) that handles north-south traffic — packets entering or leaving the cluster. In shared gateway mode it performs SNAT on egress (pod IP → node IP, or pod IP → EgressIP when the EgressIP is on the primary breth0 network) and DNAT on ingress (VIP → pod IP). For EgressIP on a secondary network, SNAT happens in the host iptables stack instead (traffic exits via ovn-k8s-mp0, bypassing the GR). In local gateway mode (routingViaHost=true) ordinary pod egress SNAT happens in the host network stack (iptables/nftables), but service traffic can still undergo load-balancer DNAT in OVN on the gateway router or logical switch depending on the path, and EgressIP SNAT also remains on the gateway router when the EgressIP is assigned to the primary network attached to the OVS gateway bridge
External Switch Per node An OVN logical switch that bridges the gateway router's external-facing port to the physical network via the OVS bridge breth0. It carries traffic between the gateway router and the node's physical NIC
Transit Switch Cluster-wide An OVN logical switch that interconnects zones (one per cluster in interconnect mode). Each zone's ovn_cluster_router has a port on this switch. Cross-zone traffic is encapsulated in GENEVE tunnels over the physical underlay between nodes
ovn-k8s-mp0 Per node The management port — a logical switch port whose peer is a Linux interface (ovn-k8s-mp0) in the host network namespace. It enables host ↔ OVN connectivity: host-networked pod delivery, local gateway egress, and health-check traffic between the host and OVN