Page MenuHomePhabricator

Optimize k8s same row traffic flows
Closed, ResolvedPublic

Description

Every single node in the old network design peers with the core routers. When traffic needs to leave a Kubernetes node and go to a Kubernetes node in the same rack/rack row, it needs to go via the core router. This will eventually needlessly saturate switch upstream links. Intra rack/rack row traffic should stay local to the rack/ rack row. This can be achieved easily by route reflectors, which calico supports. It should be pointed out that the new network design does not suffer from this issue and every ToR is effectively a route reflector. The generic industry compliant terminology for the above "deficit" would probably be that East-West traffic gets transformed to North-South.

The lack of optimal path happens for any traffic:

  • towards a prefix advertised by a BGP speaking host in one of the old (L2 only) rows (so advertised to the core routers directly), which means k8s but also LVS, DNS, etc)
  • from a host in the same row as that advertising host

In the new network design, as the router is the switch (so first device the host connects to), traffic will always flow in an optimal manner.

All the "regular" hosts in our DCs don't have any other choice than to talk to their default gateway. So not much can be realistically done here.
However all the BGP speaking hosts can potentially receive additional routing information from their peers (usually core routers or ToR switches). We currently have a policy to not send prefixes to BGP speaking servers. They exclusively use their default gateway for outbound traffic.
Mostly a tradeoff between simplicity (explicit traffic flows, lean server config, easier troubleshooting) and additional traffic on those uplinks (we can ignore the latency difference here). Especially in the past were we had little BGP speaking hosts. This is obviously changing with k8s.

Calico's Bird (BGP daemon) config is not as configurable as a regular Bird daemon, but allows any inbound prefixes by default. That means we can fully decide on what prefixes those hosts can received in the core routers export policies but if additional "mangling" is needed on the host it might make such change much more complex.

The change I want to experiment with here (on the staging clusters) is to have the core routers advertise some prefixes back to the k8s hosts. The easiest filtering would be based on source ASNs. There is no easy/clean way to filter on "same row only" source hosts.
This solution shouldn't have any additional downsides than the current status-quo (eg. BGP failure detection, etc) as the core routers are still their default gateway (worse case traffic will flow back through the routers).
Behaviors such as anycast would need to be looked at more closely when the time comes.

If there is lots of traffic from k8s hosts to LVS, we could also let this kind of traffic flow directly.

How to prioritize it depends on how much traffic we're talking about, as well as the complexity of the solution. Depending on those two factors it could be fine to wait for the new design to be progressively rolled out everywhere (but we're talking 2 years here).

EDIT, putting some more thoughts into it:
BGP is smart about it (see '"first party" NEXT_HOP' in section 5.1.3.2 of the RFC), so it should just work on the router side.
However using Calico's numAllowedLocalASNumbers config knob will be needed, as all the nodes from a given cluster use the same AS#.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

Change 885814 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/deployment-charts@master] codfw-stating: allow own AS path loop

https://gerrit.wikimedia.org/r/885814

Change 885814 merged by jenkins-bot:

[operations/deployment-charts@master] codfw-stating: allow own AS path loop

https://gerrit.wikimedia.org/r/885814

With the above patch, plus the following test router config:

[edit policy-options]
+   policy-statement kubestage_test_out {
+       term stage_as {
+           from as-path kubestage;
+           then accept;
+       }
+       then reject;
+   }
[edit policy-options]
    as-path from_remote_confed { ... }
+   as-path kubestage 64604;
[edit protocols bgp group Kubestage4]
+    advertise-peer-as;
-    export NONE;
+    export kubestage_test_out;

I was able to confirm that prefixes originating from the same AS# were properly advertised from the router to the node, and installed in the routing table.
10.192.75.0/26 is the prefix advertised by kubestage2002 only. Unfortunately it's in a different vlan, so the test is not fully completed.

advertise-peer-as config knob is required.

Next hop is the router 10.192.0.1. So it's redundant with the default route but will allow an easier deployment (no need to filter nodes/prefixes specifically).

kubestage2001:~$ ip route
default via 10.192.0.1 dev eno1 onlink 
10.192.0.0/22 dev eno1 proto kernel scope link src 10.192.0.195 
10.192.75.0/26 via 10.192.0.1 dev eno1 proto bird 
10.192.75.64 dev calib1bf823df58 scope link src 10.192.0.195 
blackhole 10.192.75.64/26 proto bird 
10.192.75.67 dev cali09f9d16b8bb scope link src 10.192.0.195 
10.192.75.73 dev cali0392293d123 scope link src 10.192.0.195 
[...]
kubestage2001:~$ ping 10.192.75.3
PING 10.192.75.3 (10.192.75.3) 56(84) bytes of data.
64 bytes from 10.192.75.3: icmp_seq=1 ttl=62 time=0.256 ms
64 bytes from 10.192.75.3: icmp_seq=2 ttl=62 time=0.278 ms
^C
--- 10.192.75.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1015ms

Pings still go through.

Next step is to try in a setup where the destination node is in the same vlan/row.

BGP is smart about it (see '"first party" NEXT_HOP' in section 5.1.3.2 of the RFC), so it should just work on the router side.

TIL didn't realise EBGP behaved that way, thought it needed no-nexthop-change. Nice!

However using Calico's numAllowedLocalASNumbers config knob will be needed, as all the nodes from a given cluster use the same AS#.

You could also possibly approach that on the JunOS side by adding "remove-private" to the peerings to K8s servers, or perhaps as-override, both of which I believe would remove the originating AS number from the path. Just mentioning, I'm not sure if there is any particular benefit either way.

Lastly I wonder if there is any point sending other routes to them that have been originated on the same subnets? Like say IPs announced by our LVS or Anycast servers on the same subnet? I guess depends on if there is traffic between those from K8s hosts, clearly won't be as big a win as the K8s internal stuff.

However using Calico's numAllowedLocalASNumbers config knob will be needed, as all the nodes from a given cluster use the same AS#.

You could also possibly approach that on the JunOS side by adding "remove-private" to the peerings to K8s servers, or perhaps as-override, both of which I believe would remove the originating AS number from the path. Just mentioning, I'm not sure if there is any particular benefit either way.

Those are good options as well, thanks! As the server side can be configured to accept loops, I think it' cleaner to do it that way as it doesn't tamper/hide the advertisements.

Lastly I wonder if there is any point sending other routes to them that have been originated on the same subnets? Like say IPs announced by our LVS or Anycast servers on the same subnet? I guess depends on if there is traffic between those from K8s hosts, clearly won't be as big a win as the K8s internal stuff.

It comes down to traffic levels vs. complexity of both the implementation and troubleshooting. While keeping in mind that it's only until the rows are refreshed.
I'm fine doing it only for k8s and we can revisit later on if the need arises for other services.

Change 886321 had a related patch set uploaded (by Alexandros Kosiaris; author: Alexandros Kosiaris):

[operations/deployment-charts@master] DNM: Showcase row-level mesh in codfw

https://gerrit.wikimedia.org/r/886321

Change 886328 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/deployment-charts@master] Allow AS loops in eqiad staging k8s cluster

https://gerrit.wikimedia.org/r/886328

Change 886329 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/deployment-charts@master] Add BGP community to all k8s advertisments

https://gerrit.wikimedia.org/r/886329

Change 886329 merged by jenkins-bot:

[operations/deployment-charts@master] Add BGP community to all k8s advertisments

https://gerrit.wikimedia.org/r/886329

kosiaris@re0.cr1-codfw> show route receive-protocol bgp 10.192.0.195 detail   

inet.0: 904595 destinations, 1757164 routes (904563 active, 11 holddown, 32 hidden)
Restart Complete
* 10.192.75.64/26 (2 entries, 1 announced)
     Accepted
     Nexthop: 10.192.0.195
     AS path: 64604 I 
     Communities: 14907:14

And show route community 14907:14 returns the expected results too.

Change 886328 abandoned by Ayounsi:

[operations/deployment-charts@master] Allow AS loops in eqiad staging k8s cluster

Reason:

This change is included in I9d12d026f647ff332a1ab01d6580919dfc70823f

https://gerrit.wikimedia.org/r/886328

Change 887945 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/deployment-charts@master] Refactor and centralize BGPpeer config

https://gerrit.wikimedia.org/r/887945

Change 887945 merged by jenkins-bot:

[operations/deployment-charts@master] Refactor and centralize BGPpeer config

https://gerrit.wikimedia.org/r/887945

Change 904150 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/homer/public@master] Add policy to export prefixes to k8s nodes

https://gerrit.wikimedia.org/r/904150

Change 904150 merged by jenkins-bot:

[operations/homer/public@master] Add policy to export prefixes to k8s nodes

https://gerrit.wikimedia.org/r/904150

Change 904486 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/homer/public@master] Move the as-path-regex out of the policy

https://gerrit.wikimedia.org/r/904486

Change 904486 merged by jenkins-bot:

[operations/homer/public@master] Move the as-path-regex out of the policy

https://gerrit.wikimedia.org/r/904486

Change 904544 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/homer/public@master] Kubestage: don't set next-hop self on exported prefixes

https://gerrit.wikimedia.org/r/904544

Change 904544 merged by jenkins-bot:

[operations/homer/public@master] Kubestage: don't set next-hop self on exported prefixes

https://gerrit.wikimedia.org/r/904544

Thanks @cmooney for the no-nexthop-change on the multihop sessions.

As an example, the routers advertise all the k8s prefixes learned locally (form direct peers, AS path length == 1) to kubestage2002 with their next-hops as known by the core routers (they don't try to replace the next hop with self).

cr1-codfw> show route advertising-protocol bgp 10.192.16.137 

inet.0: 907177 destinations, 1763402 routes (907151 active, 0 holddown, 40 hidden)
Restart Complete
  Prefix		  Nexthop	       MED     Lclpref    AS path
* 10.192.75.64/26         10.192.0.195                            64604 I
* 10.194.16.64/26         10.192.0.21                             64607 I
* 10.194.18.0/26          10.192.48.175                           64607 I
* 10.194.18.192/26        10.192.32.29                            64607 I
* 10.194.19.64/26         10.192.0.202                            64607 I
* 10.194.20.128/26        10.192.48.11                            64607 I
* 10.194.20.192/26        10.192.16.115                           64607 I
* 10.194.22.192/26        10.192.16.43                            64607 I
* 10.194.23.128/26        10.192.32.78                            64607 I
* 10.194.61.128/26        10.192.48.174                           64608 I
* 10.194.61.192/26        10.192.0.201                            64608 I
* 10.194.134.0/26         10.192.16.102                           64602 I
* 10.194.134.192/26       10.192.0.117                            64602 I
* 10.194.135.64/26        10.192.16.138                           64602 I
* 10.194.137.128/26       10.192.48.180                           64602 I
* 10.194.139.64/26        10.192.32.109                           64602 I
* 10.194.147.128/26       10.192.16.135                           64602 I
* 10.194.149.128/26       10.192.0.125                            64602 I
* 10.194.150.0/26         10.192.16.212                           64602 I
* 10.194.153.128/26       10.192.0.56                             64602 I
* 10.194.157.64/26        10.192.16.39                            64602 I
* 10.194.161.128/26       10.192.0.197                            64602 I
* 10.194.167.0/26         10.192.48.29                            64602 I
* 10.194.171.192/26       10.192.48.87                            64602 I
* 10.194.173.64/26        10.192.0.207                            64602 I
* 10.194.173.192/26       10.192.32.110                           64602 I
* 10.194.179.128/26       10.192.48.28                            64602 I
* 10.194.181.0/26         10.192.32.89                            64602 I
* 10.194.183.128/26       10.192.32.41                            64602 I
* 10.194.184.192/26       10.192.16.48                            64602 I
* 10.194.185.128/26       10.192.48.30                            64602 I
* 10.194.187.0/26         10.192.0.196                            64602 I
* 10.194.191.128/26       10.192.32.21                            64602 I

On the server side, bellow, we can see that the prefixes from the same vlan don't have the core routers VIP as next-hop but their same Vlan IPs.

For all the other (different vlan) hosts, bird resolves the remote IPs to the directly connected gateway "10.192.16.1".

kubestage2002:~$ ip route
default via 10.192.16.1 dev eno1 onlink 
[...]
10.192.75.64/26 via 10.192.16.1 dev eno1 proto bird 
10.194.16.64/26 via 10.192.16.1 dev eno1 proto bird 
10.194.18.0/26 via 10.192.16.1 dev eno1 proto bird 
10.194.18.192/26 via 10.192.16.1 dev eno1 proto bird 
10.194.19.64/26 via 10.192.16.1 dev eno1 proto bird 
10.194.20.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.20.192/26 via 10.192.16.115 dev eno1 proto bird 
10.194.22.192/26 via 10.192.16.43 dev eno1 proto bird 
10.194.23.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.61.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.61.192/26 via 10.192.16.1 dev eno1 proto bird 
10.194.134.0/26 via 10.192.16.102 dev eno1 proto bird 
10.194.134.192/26 via 10.192.16.1 dev eno1 proto bird 
10.194.135.64/26 via 10.192.16.138 dev eno1 proto bird 
10.194.137.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.139.64/26 via 10.192.16.1 dev eno1 proto bird 
10.194.147.128/26 via 10.192.16.135 dev eno1 proto bird 
10.194.149.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.150.0/26 via 10.192.16.212 dev eno1 proto bird 
10.194.153.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.157.64/26 via 10.192.16.39 dev eno1 proto bird 
10.194.161.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.167.0/26 via 10.192.16.1 dev eno1 proto bird 
10.194.171.192/26 via 10.192.16.1 dev eno1 proto bird 
10.194.173.64/26 via 10.192.16.1 dev eno1 proto bird 
10.194.173.192/26 via 10.192.16.1 dev eno1 proto bird 
10.194.179.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.181.0/26 via 10.192.16.1 dev eno1 proto bird 
10.194.183.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.184.192/26 via 10.192.16.48 dev eno1 proto bird 
10.194.185.128/26 via 10.192.16.1 dev eno1 proto bird 
10.194.187.0/26 via 10.192.16.1 dev eno1 proto bird 
10.194.191.128/26 via 10.192.16.1 dev eno1 proto bird

Ping works and we can see that the TTL match the one lesser hop.

kubestage2002:~$ ping 10.194.157.64 
PING 10.194.157.64 (10.194.157.64) 56(84) bytes of data.
64 bytes from 10.194.157.64: icmp_seq=1 ttl=63 time=0.301 ms
64 bytes from 10.194.157.64: icmp_seq=2 ttl=63 time=0.186 ms
kubestage2002:~$ ping 10.194.161.130
PING 10.194.161.130 (10.194.161.130) 56(84) bytes of data.
64 bytes from 10.194.161.130: icmp_seq=1 ttl=62 time=0.221 ms
64 bytes from 10.194.161.130: icmp_seq=2 ttl=62 time=0.241 ms

Change 905170 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/homer/public@master] [k8s mlstage/aux] Add policy to export prefixes to notes

https://gerrit.wikimedia.org/r/905170

Change 905171 had a related patch set uploaded (by Ayounsi; author: Ayounsi):

[operations/homer/public@master] [k8s ml/dse/wiki] Add policy to export prefixes to nodes

https://gerrit.wikimedia.org/r/905171

Change 905170 merged by Ayounsi:

[operations/homer/public@master] [k8s mlstage/aux] Add policy to export prefixes to nodes

https://gerrit.wikimedia.org/r/905170

Change 905171 merged by jenkins-bot:

[operations/homer/public@master] [k8s ml/dse/wiki] Add policy to export prefixes to nodes

https://gerrit.wikimedia.org/r/905171

ayounsi claimed this task.

This has been rolled to all k8s clusters.

Change 886321 abandoned by Alexandros Kosiaris:

[operations/deployment-charts@master] DNM: Showcase row-level mesh

Reason:

Not going down that route after all

https://gerrit.wikimedia.org/r/886321