Wednesday, November 11, 2026

Debugging Feedback: Messengers, Coaches, and Micromanagers

Anyone who has worked in tech long enough knows that troubleshooting a distributed system is usually straightforward compared to troubleshooting human communication. When a BGP session fails to come up or a Kubernetes pod refuses to schedule, the logs give you an error code, a trace ID, and a clear path to a fix. People management rarely offers that level of observability.

Recently, a colleague ran into a classic workplace scenario that highlighted the stark difference between someone who knows how to lead people and someone who simply holds a management title. In this post, I want to walk through what happened with a manager we will call Carl, compare two very different management responses to the exact same issue, examine why micromanagers struggle with actual leadership, and look at a few practical ways to help an untrained manager improve.

The Scenario

It started with a brief comment from Carl to one of his direct reports. He told him that some of his recent remarks had come off as criticisms to the engineering team. That was the entirety of the feedback. There were no specific examples, no context around which conversation triggered the concern, and no suggestions on how he should adjust his approach moving forward.

In engineering terms, it was the equivalent of receiving an alert that says "something is broken" with no timestamp, no hostname, and no stack trace. You cannot fix a problem you cannot reproduce or inspect.

Since Carl had no details to offer, the direct report went straight to the source and spoke with the engineering manager where the feedback originated. That conversation took a completely different turn. The engineering manager pulled up an actual example, walked through the context, and explained the root cause. Because several of the engineers involved were not familiar with the direct report yet, his direct questions over Slack were missing vocal inflection and facial cues, making the tone feel uncertain or overly critical. More importantly, the engineering manager offered concrete ideas on how to soften the phrasing when asking those kinds of technical questions in text.

Comparing the Two Responses

Looking at both interactions side by side makes the contrast obvious. Both managers were looking at the exact same friction point, yet they handled it in fundamentally different ways.

Attribute Carl The Engineering Manager
Role Played Messenger / Relay Coach / Problem Solver
Context Provided None Specific Slack thread and team background
Root Cause Analysis Absent Recognized remote-text tone ambiguity
Actionable Advice None Concrete tips on softening written questions
Outcome Confusion and frustration Clarity and immediate adjustment

The engineering manager succeeded because he stayed close to the problem and focused on the outcome. He understood that cross-team collaboration breaks down easily over asynchronous text, especially when people do not know each other well. His goal was to keep the teams working smoothly, so giving practical tools to bridge that gap solved the issue for everyone.

Carl, on the other hand, acted as a simple packet forwarder. He took a secondhand complaint, passed it along raw without asking a single follow-up question, and considered his job done.

Micromanager Paradox

At first glance, it is easy to assume a manager who delivers vague feedback is simply hands-off or lazy. The catch in this case is that Carl is the exact opposite of hands-off. He micromanages down to the minute details of how work artifacts should look, nitpicking formatting, structure, and phrasing.

That combination, obsessing over trivial details while completely failing at people development, is a well-known pattern in technical organizations. It usually comes down to a few core shortcomings:

  • Control as a coping mechanism. Managers like Carl, who were often promoted because they were strong individual contributors, frequently feel out of their depth when dealing with interpersonal dynamics, cross-team politics, or coaching. To feel useful, they retreat to what they know: controlling documents, layouts, and artifacts.
  • Confusing "managing" with "doing." Instead of viewing their role as developing the people on their team, they treat every deliverable as their own personal work product. Their focus is on making sure an artifact looks the way they would have built it, rather than helping the engineer grow.
  • Zero bandwidth for leadership. When a manager burns all their mental energy inspecting font sizes and slide layouts, they have nothing left over to investigate team friction, ask probing questions, or mentor their reports on communication.

Ways to Help Managers Improve

When working for someone who lacks formal management training, waiting around for them to suddenly develop coaching skills is a losing strategy. While you cannot force a manager to change overnight, a direct report can manage up in ways that steer them toward better habits and make day-to-day life easier in the process.

  • Feed the detail habit early. Before investing hours perfecting a document or presentation, share a rough outline or wireframe with Carl upfront. Letting a micromanager weigh in on structure early satisfies their need for control before the heavy lifting happens, saving everyone from endless rework later.
  • Prompt him to apply his detail orientation to feedback. When Carl brings vague, secondhand criticism, put the ball back in his court using his own strengths. Ask him directly to grab an exact example next time so there is concrete data to work with.
  • Close the loop with facts. After resolving an issue directly, like speaking with the engineering manager, circle back and briefly summarize what was learned and how the approach was adjusted. Showing Carl what good resolution looks like sets a clear standard without making it confrontational.
  • Outsource mentorship. Recognize his limitations for what they are. If a direct manager cannot provide career growth or communication coaching, lean on other engineering leaders, senior peers, and mentors across the organization who actually know how to develop people.

Wrapping Up

Good management is not about policing formatting or passing along unverified complaints. It is about providing context, removing blockers, and giving people the feedback they need to operate effectively. Until a manager learns the difference between controlling work and leading people, the best approach is to treat them like an unreliable network link: verify the packets yourself, route around the bottlenecks, and keep building direct connections with the people who actually want to solve problems.

NVKE, BGP and VIPs

In a previous blog on NVKE, which is published here, we showed how to deploy an NVIDIA Kubernetes Engine (NVKE) cluster. A Kubernetes cluster is only as reachable as its API endpoint, and with NVKE that endpoint is a virtual IP (VIP) that has to follow the control plane no matter which node happens to be healthy. In that deployment kube-vip keeps the VIP alive by announcing it to the network with the Border Gateway Protocol (BGP), which means the upstream switch has to be configured to peer with the nodes and accept the route.

The more familiar way to handle a VIP is at the layer 2 level. Red Hat OpenShift is a good example, where HAProxy load balances the API traffic across the control plane nodes and keepalived uses the Virtual Router Redundancy Protocol (VRRP) to float a VIP between them. One node owns the address at a time and answers ARP for it, and when that node fails another one takes over and sends a gratuitous ARP so the neighbors update their caches. It is simple and it needs nothing from the network, but it comes with two constraints. Every node has to sit on the same layer 2 segment, and only one node carries the traffic for the VIP at any given moment, while the others wait their turn.

BGP removes both constraints. At its core it is just two routers agreeing to tell each other which prefixes they can reach. Each control plane node announces the VIP as a /32 route, and the upstream router learns that the address lives behind any of those nodes. The VIP becomes a routed address instead of an ARP trick, so the router can hold a route to every node, withdraw the one that goes away and, with equal cost multi-path, spread connections across all of them. It also means the cluster is no longer tied to the layer 2 layout of whatever rack it lands in. The price is that the network has to take part in the conversation, which is why the switch needs configuration. This write-up covers that switch side of the setup, using a Cisco 3750 in the lab.

Lab Overview

The lab is small with a Cisco Catalyst 3750 acting as the router and the BGP peer. Behind it sits a single hypervisor host running three virtual machines, asus3-vm1, asus3-vm2 and asus3-vm3, all attached to the 192.168.0.0/24 network. The switch owns 192.168.0.1 on that network, and the three VMs sit at 192.168.0.116, 192.168.0.117 and 192.168.0.118.

The cluster VIP is 10.250.0.10, and it lives outside of 192.168.0.0/24. That is the whole point. Nothing on the lab network has a route to 10.250.0.10/32 until the cluster nodes announce it, so the switch has to be configured to listen for that one prefix from each VM. Once the switch learns it, anything that can reach the 3750 can reach the Kubernetes API through 10.250.0.10.

NVKE ASN Modification

Before getting to the switch configuration there is one caveat worth calling out. BGP identifies each side of a peering with an Autonomous System Number (ASN), and for this lab the switch uses 65000 while the cluster nodes use 65100. Both are in the private range, which is what a lab like this should use. The NVKE code in its current state has the ASN hard coded to a value larger than what the Cisco 3750 can accept, so the peering would never have come up. For this walk-through the NVKE code was modified to allow 65000 and 65100 instead. That is a local change made to fit the hardware in my lab and not something to expect from a stock NVKE install. There are plans to make that ASN number configurable.

Update Cisco Switch

Let's get started with the switch. The first block enters configuration mode and builds two prefix lists before anything else. NVKE-DEMO-VIP-IN permits exactly one prefix, 10.250.0.10/32, which is the VIP and nothing else. NVKE-DEMO-DENY-OUT denies everything, so the switch will not leak any of its own routes into the cluster.

With the filters in place the block starts BGP with the local ASN of 65000 and sets the router ID to 192.168.0.1. It turns on neighbor change logging so session state changes land in the log, and it turns off synchronization and auto-summary, which keeps the router from summarizing the /32 into something larger. The line maximum-paths 3 is the one that matters most for NVKE. It allows the switch to install up to three equal cost routes to the VIP, one per control plane node, instead of picking a single winner.

The rest of the block defines the three neighbors, 192.168.0.116, 192.168.0.117 and 192.168.0.118. Each one gets the remote ASN of 65100, a description naming the VM it belongs to, and the two prefix lists applied inbound and outbound. Every neighbor gets identical treatment, so adding a fourth node later would just be another copy of the same four lines.

cisco3750#configure terminal Enter configuration commands, one per line. End with CNTL/Z. cisco3750(config)#ip prefix-list NVKE-DEMO-VIP-IN seq 10 permit 10.250.0.10/32 cisco3750(config)#ip prefix-list NVKE-DEMO-DENY-OUT seq 10 deny 0.0.0.0/0 le 32 cisco3750(config)#router bgp 65000 cisco3750(config-router)# bgp router-id 192.168.0.1 cisco3750(config-router)# bgp log-neighbor-changes cisco3750(config-router)# no synchronization cisco3750(config-router)# no auto-summary cisco3750(config-router)# maximum-paths 3 cisco3750(config-router)# neighbor 192.168.0.116 remote-as 65100 cisco3750(config-router)# neighbor 192.168.0.116 description NVKE-asus3-vm1 cisco3750(config-router)# neighbor 192.168.0.116 prefix-list NVKE-DEMO-VIP-IN in cisco3750(config-router)# neighbor 192.168.0.116 prefix-list NVKE-DEMO-DENY-OUT out cisco3750(config-router)# neighbor 192.168.0.117 remote-as 65100 cisco3750(config-router)# neighbor 192.168.0.117 description NVKE-asus3-vm2 cisco3750(config-router)# neighbor 192.168.0.117 prefix-list NVKE-DEMO-VIP-IN in cisco3750(config-router)# neighbor 192.168.0.117 prefix-list NVKE-DEMO-DENY-OUT out cisco3750(config-router)# neighbor 192.168.0.118 remote-as 65100 cisco3750(config-router)# neighbor 192.168.0.118 description NVKE-asus3-vm3 cisco3750(config-router)# neighbor 192.168.0.118 prefix-list NVKE-DEMO-VIP-IN in cisco3750(config-router)# neighbor 192.168.0.118 prefix-list NVKE-DEMO-DENY-OUT out cisco3750(config-router)#end

At this point the configuration is in the running config. We can verify it by checking the BGP summary.

Checking the BGP Summary

The summary shows the switch as router identifier 192.168.0.1 in local AS 65000, with all three neighbors listed against AS 65100. Each one reports State as Active and a session that has never come up. In BGP, Active does not mean the session is healthy. It means the switch is trying to open a TCP connection to the neighbor and has not succeeded. That is exactly what is expected when the other side has not started speaking BGP yet, and it confirms the neighbors are defined correctly and the switch is waiting.

cisco3750#show ip bgp summary BGP router identifier 192.168.0.1, local AS number 65000 BGP table version is 197, main routing table version 197 1 network entries using 117 bytes of memory 3 path entries using 156 bytes of memory 1 multipath network entries and 3 multipath paths 2/1 BGP path/bestpath attribute entries using 280 bytes of memory 1 BGP AS-PATH entries using 24 bytes of memory 0 BGP route-map cache entries using 0 bytes of memory 0 BGP filter-list cache entries using 0 bytes of memory BGP using 577 total bytes of memory BGP activity 4/3 prefixes, 100/97 paths, scan interval 60 secs Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd 192.168.0.116 4 65100 76549 74720 197 0 0 03:45:48 1 192.168.0.117 4 65100 68916 67270 197 0 0 02:10:41 1 192.168.0.118 4 65100 68932 67290 197 0 0 03:44:03 1

Checking a Single Neighbor

For a closer look, the neighbor detail for 192.168.0.116 shows the peer described as NVKE-asus3-vm1 in remote AS 65100, an external link, with the remote router ID still 0.0.0.0 because no OPEN message has been received. The message statistics are all zeros, the hold time is 180 seconds with a keepalive interval of 60, and the last line reports that there is no active TCP connection.

The useful lines are further down. Under the IPv4 Unicast address family the incoming filter shows up as NVKE-DEMO-VIP-IN and the outgoing filter as NVKE-DEMO-DENY-OUT, which confirms the prefix lists attached to the neighbor the way they were written. The address tracking line also notes that the routing table has a route to 192.168.0.116, so the switch knows how to reach the VM and the only thing missing is the VM answering.

cisco3750#show ip bgp neighbors 192.168.0.116 BGP neighbor is 192.168.0.116, remote AS 65100, external link Description: NVKE-asus3-vm1 BGP version 4, remote router ID 192.168.0.116 BGP state = Established, up for 03:45:09 Last read 00:00:09, last write 00:00:07, hold time is 30, keepalive interval is 10 seconds Neighbor capabilities: Route refresh: advertised and received(new) Address family IPv4 Unicast: advertised and received Message statistics: InQ depth is 0 OutQ depth is 0 Sent Rcvd Opens: 35 35 Notifications: 2 0 Updates: 0 35 Keepalives: 74679 76475 Route Refresh: 0 0 Total: 74716 76545 Default minimum time between advertisement runs is 30 seconds For address family: IPv4 Unicast BGP table version 197, neighbor version 197/0 Output queue size : 0 Index 1, Offset 0, Mask 0x2 1 update-group member Incoming update prefix filter list is NVKE-DEMO-VIP-IN Outgoing update prefix filter list is NVKE-DEMO-DENY-OUT Sent Rcvd Prefix activity: ---- ---- Prefixes Current: 0 1 (Consumes 52 bytes) Prefixes Total: 0 1 Implicit Withdraw: 0 0 Explicit Withdraw: 0 0 Used as bestpath: n/a 1 Used as multipath: n/a 1 Outbound Inbound Local Policy Denied Prefixes: -------- ------- prefix-list 6 0 Total: 6 0 Number of NLRIs in the update sent: max 0, min 0 Address tracking is enabled, the RIB does have a route to 192.168.0.116 Address tracking requires at least a /24 route to the peer Connections established 35; dropped 34 Last reset 03:45:12, due to Peer closed the session Transport(tcp) path-mtu-discovery is enabled Connection state is ESTAB, I/O status: 1, unread input bytes: 0 Mininum incoming TTL 0, Outgoing TTL 1 Local host: 192.168.0.1, Local port: 179 Foreign host: 192.168.0.116, Foreign port: 39293 Enqueued packets for retransmit: 0, input: 0 mis-ordered: 0 (0 bytes) Event Timers (current time is 0x2E4F2FC2E): Timer Starts Wakeups Next Retrans 1323 0 0x0 TimeWait 0 0 0x0 AckHold 1354 1309 0x0 SendWnd 0 0 0x0 KeepAlive 0 0 0x0 GiveUp 0 0 0x0 PmtuAger 0 0 0x0 DeadWait 0 0 0x0 iss: 3757656863 snduna: 3757682008 sndnxt: 3757682008 sndwnd: 64062 irs: 2772535007 rcvnxt: 2772560808 rcvwnd: 15453 delrcvwnd: 931 SRTT: 300 ms, RTTO: 303 ms, RTV: 3 ms, KRTT: 0 ms minRTT: 0 ms, maxRTT: 300 ms, ACK hold: 200 ms Flags: passive open, nagle, path mtu capable, gen tcbs Datagrams (max data segment is 1460 bytes): Rcvd: 2677 (out of order: 0), with data: 1354, total data bytes: 25800 Sent: 2649 (retransmit: 0), with data: 1322, total data bytes: 25144

If everything looks good we can move onto saving the work.

Saving the Configuration

A running configuration on a Catalyst vanishes at the next reload, so the last step writes it to memory. The switch builds the configuration and reports OK.

cisco3750#wr mem Building configuration... [OK]

Finally we can look at being able to access the 10.150.0.10 VIP and we can do that from another host somewhere on the network. First we can ping the IP address and see a response but we can also try and access the Kubernetes cluster as well where our kubeconfig is using the 10.150.0.10 address.

$ ping 10.250.0.10 PING 10.250.0.10 (10.250.0.10) 56(84) bytes of data. 64 bytes from 10.250.0.10: icmp_seq=1 ttl=64 time=0.592 ms From 192.168.0.1: icmp_seq=1 Redirect Host(New nexthop: 192.168.0.116) 64 bytes from 10.250.0.10: icmp_seq=2 ttl=64 time=0.459 ms From 192.168.0.1: icmp_seq=2 Redirect Host(New nexthop: 192.168.0.116) 64 bytes from 10.250.0.10: icmp_seq=3 ttl=64 time=0.573 ms From 192.168.0.1: icmp_seq=3 Redirect Host(New nexthop: 192.168.0.116) ^C --- 10.250.0.10 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2004ms rtt min/avg/max/mdev = 0.459/0.541/0.592/0.058 ms $ cat /home/bschmaus/nvke-demo.kubeconfig | grep server server: https://10.250.0.10:6443 $ export KUBECONFIG=/home/bschmaus/nvke-demo.kubeconfig $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME nvke-demo-cp-4c8bbf0f Ready control-plane 5d19h v1.36.3 192.168.0.117 <none> Ubuntu 24.04.5 LTS 6.8.0-1064-nvidia (amd64) containerd://2.3.0 nvke-demo-cp-90c9dcc2 Ready control-plane 5d19h v1.36.3 192.168.0.118 <none> Ubuntu 24.04.5 LTS 6.8.0-1064-nvidia (amd64) containerd://2.3.0 nvke-demo-cp-f46d7a88 Ready control-plane 5d19h v1.36.3 192.168.0.116 <none> Ubuntu 24.04.5 LTS 6.8.0-1064-nvidia (amd64) containerd://2.3.0

At this point I feel pretty confident the switch side is ready. The prefix lists only allow the VIP in, the three neighbors are defined with the correct ASNs and the switch shows the nodes to connected. Because the cluster nodes are announcing 10.250.0.10/32, the neighbors show Established and the prefix count for each is one.

Hopefully this provided a clear example of what NVKE needs from the network when its control plane VIP is announced over BGP, and how little it takes to prepare an older Cisco 3750 to be the other end of that conversation.