kubectl-us-net
kubectl-us-net is a kubectl plugin for troubleshooting UnderStack's
Neutron/OVN data plane. It wraps kubectl exec into the OVN NB/SB pods
(and, for ovs-vsctl/ovs-appctl, whichever pod is running on a given
node) alongside OpenStack API calls, so you don't have to remember pod
names, container names, or the neutron-<uuid> naming convention OVN uses
for objects it syncs from Neutron.
It's a companion to the manual debugging steps in
OVN / Open vSwitch -- for example, the walkthrough in
Verifying a router port is bound to an HA_Chassis_Group
is exactly what router show (below) automates in one command.
This is intentionally a plain Python CLI for now (no compiled binary, no
krew packaging) so the command surface and output can be iterated on
quickly. Distributing it via krew is planned once the behavior settles.
Installation
cd python/kubectl-us-net
uv sync
export PATH="$PWD/.venv/bin:$PATH" # so `kubectl us-net ...` finds it
Prerequisites
kubectl, pointed at the target cluster (via--contextor your current
context)- OpenStack credentials available on your machine --
OS_CLOUDenv var /
clouds.yaml, or pass--os-cloudexplicitly
This tool doesn't manage either of those; it only reads them.
Global options
| Option | Default | Description |
|---|---|---|
--context |
current kubectl context | kubectl context to target |
--namespace, -n |
openstack |
Namespace hosting the OVN NB/SB pods |
--nb-pod |
ovn-ovsdb-nb-0 |
Northbound OVSDB pod name |
--sb-pod |
ovn-ovsdb-sb-0 |
Southbound OVSDB pod name |
--os-cloud |
OS_CLOUD env / clouds.yaml default |
OpenStack cloud name |
Every command starts by printing a banner showing the kube context, OVN
namespace/pod names, and (for OpenStack-backed commands) the OpenStack cloud
target, so it's always clear what you're actually talking to.
Commands
Raw passthrough
kubectl us-net nbctl -- show
kubectl us-net sbctl -- list Chassis
kubectl us-net vsctl --node <nodename> -- show
kubectl us-net appctl --node <nodename> -- version
Thin wrappers around ovn-nbctl, ovn-sbctl, ovs-vsctl, and
ovs-appctl that resolve the right pod for you and stream native output
straight through -- use these for anything not covered by a higher-level
command below.
vsctl/appctl resolve the target pod by node name. On UnderStack's OVN
deployment, OVS is co-located inside the ovn-controller DaemonSet pod (no
separate openvswitch pod), so both commands default to the
ovn-controller name-prefix. If your cluster's pod naming differs, pass
--pod <name> (and, if needed, --container <name>) to target it
directly, or --target <prefix> on appctl to change the discovery
prefix.
router list
A table of every router seen in OpenStack and/or OVN, so you can spot
mismatches (present on only one side) before drilling into one with
router show. Flavored routers (e.g. VRF) are handled by a different L3
backend and never get an OVN Logical_Router, so they're marked
n/a (flavored) in the OVN column rather than a false "NO".
$ kubectl us-net router list
NAME ID OPENSTACK OVN
---------------------------------------------------------------------
tenant-router 769be712-d084-4846-bd21-a85f6494f3b6 yes yes
patch-router 94c6e0ee-3959-49e2-a9f2-e5f3a304bb77 yes yes
vrf-router df9746b3-d1da-484c-bf4c-86248279dddb yes n/a (flavored)
vrf-router-2 8f3492e8-d4d9-4f27-817c-b53a19b91f67 yes n/a (flavored)
router show
kubectl us-net router show <router-name-or-id>
kubectl us-net router show <router-name-or-id> --flows # also dump SB logical flows
Resolves the router in OpenStack, maps it to its OVN Logical_Router
(neutron-<router_id>), and prints:
- Router ports -- each
Logical_Router_Port(gateway vs. internal), its
Neutron port ID, OVN-side networks and Neutron-side fixed IPs, the VLAN
tag(s) of its network's localnet/uplink port(s), and its chassis binding:- a linked
HA_Chassis_Group, with each chassis's liveness and physical
networks (viaovn-bridge-mappings), highest priority first; - or, for a VLAN/FLAT distributed gateway, its
Gateway_Chassisbinding
(OVN's own L3-scheduler mechanism, distinct fromha_chassis_group); - or, for a centralized router, the chassis it's pinned to via
options:chassis; - and only flags a port as "likely bug" when none of the above apply --
the exact bug classscripts/cleanup_dead_ovn_ha_chassis.pyrepairs.
- a linked
- NAT rules -- each rule's type/external IP/logical IP, and the
OpenStack port it resolves to (if any). - Ports -- for each NAT-resolved port: its fixed IPs, owner (server name
for compute-owned ports), a cross-check against its OVNLogical_Switch_Port
(type/up/addresses), and that port's ownHA_Chassis_Groupbinding (the
per-network unified HCG referenced by external/baremetal ports, distinct
from the router-port-level HCG shown above). - Optionally, southbound logical flows (
ovn-sbctl lflow-list), with
--flows.
Note
As of this writing, every router in UnderStack is centralized --
there are no VLAN/FLAT distributed-gateway routers deployed, so the
Gateway_Chassis path above is exercised only by unit tests, not live
traffic. A router port should show up as "Pinned chassis"
(options:chassis), not a router-port-level HA_Chassis_Group. The
per-network HA_Chassis_Group is expected on external ports bound to
physical baremetal servers instead (the Ports section below) -- that
is a different, per-network HCG used to steer traffic to the chassis
wired to that server's physical network, and is unrelated to router
centralization. If a router port ever shows a linked HCG of its own, or
a baremetal external port has none, treat it as a red flag worth
investigating with router show's "likely bug" diagnostics.
Example output (IPs and hostnames below are illustrative, not real):
$ kubectl us-net --context my-cluster-dev --os-cloud example-cloud router show patch-router
================================================================
kubectl-us-net -- target environment
================================================================
Kubernetes context : my-cluster-dev
OVN namespace/pods : openstack (nb=ovn-ovsdb-nb-0, sb=ovn-ovsdb-sb-0)
OpenStack cloud : example-cloud
auth URL : https://keystone.dev.undercloud.example.com/v3
project : baremetal
================================================================
Router patch-router (94c6e0ee-3959-49e2-a9f2-e5f3a304bb77)
OVN Logical_Router: neutron-94c6e0ee-3959-49e2-a9f2-e5f3a304bb77
Type: centralized (options:chassis=a2172b59-5558-4018-858d-88947e2d9adf)
Router ports:
- lrp-6aeee225-e410-4a7a-891d-edc2a3f3831f [gateway]
Neutron port : 6aeee225-e410-4a7a-891d-edc2a3f3831f
OVN networks : 203.0.113.41/26
Neutron fixed IPs : 203.0.113.41
Network VLAN tag : 1804, 1800
Pinned chassis : a2172b59-5558-4018-858d-88947e2d9adf (alive, physnets=physnet1)
- lrp-c0a41495-4cb5-4913-a7e7-9a2047f79e7d [internal]
Neutron port : c0a41495-4cb5-4913-a7e7-9a2047f79e7d
OVN networks : 192.0.2.1/24
Neutron fixed IPs : 192.0.2.1
Network VLAN tag : 1802
Pinned chassis : a2172b59-5558-4018-858d-88947e2d9adf (alive, physnets=physnet1)
NAT rules:
dnat_and_snat external=203.0.113.19 logical=192.0.2.156 -> port d0075095-6232-43a6-b0c0-7fd7a07716a3
snat external=203.0.113.41 logical=192.0.2.0/24
Ports:
d0075095-6232-43a6-b0c0-7fd7a07716a3 ((unnamed))
Fixed IPs : 192.0.2.156
Owner : server 14d4e13f-fef5-4676-a8e2-a5bdf63c2b6f (web-server-01)
OVN LSP : type=external, up, addresses=d4:04:e6:4f:7e:cc 192.0.2.156
HA_Chassis_Group : neutron-5af67e57-7052-49fc-9bad-253275b39986 -> a2172b59-5558-4018-858d-88947e2d9adf (priority=32767, alive, physnets=physnet1)
Here the router port shows a pinned chassis (expected for a centralized
router), while the baremetal server's external port shows its own
HA_Chassis_Group -- exactly the split described in the note above.
Development
Each command is a self-contained module under us_net/commands/ that
registers into the top-level typer app (us_net/cli.py). Low-level OVSDB
access lives in us_net/ovn.py (JSON unwrap ported from
scripts/cleanup_dead_ovn_ha_chassis.py), kubectl exec plumbing in
us_net/kube.py, and OpenStack SDK connection setup in
us_net/osclient.py. Run tests and linting from python/kubectl-us-net/:
Contributing
If you find any issues or have suggestions for improvements, please open an
issue on the GitHub repository.