Add support for BGP path prepending through node annotations (#476)

This commit is contained in:
Emil Stolarsky 2018-07-11 06:48:21 -04:00 committed by Andrew Sy Kim
parent 57f4eea2f4
commit 82410c14d0
4 changed files with 338 additions and 6 deletions

View File

@ -92,6 +92,20 @@ kubectl annotate node <kube-node> "kube-router.io/peer.ips=192.168.1.99,192.168.
kubectl annotate node <kube-node> "kube-router.io/peer.asns=65000,65000"
```
### AS Path Prepending
For traffic shaping purposes, you may want to prepend the AS path announced to peers.
This can be accomplished on a per-node basis with annotations:
- `kube-router.io/path-prepend.as`
- `kube-router.io/path-prepend.repeat-n`
If you wanted to prepend all routes from a particular node with the AS 65000 five times,
you would run the following commands:
```
kubectl annotate node <kube-node> "kube-router.io/path-prepend.as=65000"
kubectl annotate node <kube-node> "kube-router.io/path-prepend.repeat-n=5"
```
### BGP Peer Password Authentication
The examples above have assumed there is no password authentication with BGP

View File

@ -66,6 +66,16 @@ func (nrc *NetworkRoutingController) addExportPolicies() error {
statements := make([]config.Statement, 0)
var bgpActions config.BgpActions
if nrc.pathPrepend {
bgpActions = config.BgpActions{
SetAsPathPrepend: config.SetAsPathPrepend{
As: nrc.pathPrependAS,
RepeatN: nrc.pathPrependCount,
},
}
}
if nrc.bgpEnableInternal {
// Get the current list of the nodes from the local cache
nodes := nrc.nodeLister.List()
@ -136,6 +146,7 @@ func (nrc *NetworkRoutingController) addExportPolicies() error {
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
BgpActions: bgpActions,
},
})
if nrc.advertisePodCidr {

View File

@ -41,6 +41,8 @@ const (
nodeAddrsIPSetName = "kube-router-node-ips"
nodeASNAnnotation = "kube-router.io/node.asn"
pathPrependASNAnnotation = "kube-router.io/path-prepend.as"
pathPrependRepeatNAnnotation = "kube-router.io/path-prepend.repeat-n"
peerASNAnnotation = "kube-router.io/peer.asns"
peerIPAnnotation = "kube-router.io/peer.ips"
peerPasswordAnnotation = "kube-router.io/peer.passwords"
@ -87,6 +89,9 @@ type NetworkRoutingController struct {
cniConfFile string
initSrcDstCheckDone bool
ec2IamAuthorized bool
pathPrependAS string
pathPrependCount uint8
pathPrepend bool
nodeLister cache.Indexer
svcLister cache.Indexer
@ -610,6 +615,28 @@ func (nrc *NetworkRoutingController) startBgpServer() error {
nrc.bgpRRClient = true
}
if prependASN, okASN := node.ObjectMeta.Annotations[pathPrependASNAnnotation]; okASN {
prependRepeatN, okRepeatN := node.ObjectMeta.Annotations[pathPrependRepeatNAnnotation]
if !okRepeatN {
return fmt.Errorf("Both %s and %s must be set", pathPrependASNAnnotation, pathPrependRepeatNAnnotation)
}
_, err := strconv.ParseUint(prependASN, 0, 32)
if err != nil {
return errors.New("Failed to parse ASN number specified to prepend")
}
repeatN, err := strconv.ParseUint(prependRepeatN, 0, 8)
if err != nil {
return errors.New("Failed to parse number of times ASN should be repeated")
}
nrc.pathPrepend = true
nrc.pathPrependAS = prependASN
nrc.pathPrependCount = uint8(repeatN)
}
nrc.bgpServer = gobgp.NewBgpServer()
go nrc.bgpServer.Serve()
@ -787,7 +814,6 @@ func NewNetworkRoutingController(clientset kubernetes.Interface,
nrc.advertiseExternalIP = kubeRouterConfig.AdvertiseExternalIp
nrc.advertiseLoadBalancerIP = kubeRouterConfig.AdvertiseLoadBalancerIp
nrc.advertisePodCidr = kubeRouterConfig.AdvertiseNodePodCidr
nrc.enableOverlays = kubeRouterConfig.EnableOverlay
// Convert ints to uint32s

View File

@ -1503,6 +1503,287 @@ func Test_addExportPolicies(t *testing.T) {
},
nil,
},
{
"prepends AS with external peers",
&NetworkRoutingController{
clientset: fake.NewSimpleClientset(),
hostnameOverride: "node-1",
bgpEnableInternal: true,
bgpFullMeshMode: false,
pathPrepend: true,
pathPrependCount: 5,
pathPrependAS: "65100",
bgpServer: gobgp.NewBgpServer(),
activeNodes: make(map[string]bool),
globalPeerRouters: []*config.NeighborConfig{
{
NeighborAddress: "10.10.0.1",
},
{
NeighborAddress: "10.10.0.2",
},
},
nodeAsnNumber: 100,
},
[]*v1core.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
Annotations: map[string]string{
"kube-router.io/node.asn": "100",
},
},
Status: v1core.NodeStatus{
Addresses: []v1core.NodeAddress{
{
Type: v1core.NodeInternalIP,
Address: "10.0.0.1",
},
},
},
Spec: v1core.NodeSpec{
PodCIDR: "172.20.0.0/24",
},
},
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "podcidrprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "172.20.0.0/24",
MasklengthRange: "24..24",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "clusteripprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "1.1.1.1/32",
MasklengthRange: "32..32",
},
{
IpPrefix: "10.0.0.1/32",
MasklengthRange: "32..32",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{},
NeighborSets: []config.NeighborSet{
{
NeighborSetName: "externalpeerset",
NeighborInfoList: []string{"10.10.0.1/32", "10.10.0.2/32"},
},
},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
[]*config.Statement{
{
Name: "kube_router_stmt0",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "podcidrprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
MatchNeighborSet: config.MatchNeighborSet{
NeighborSet: "iBGPpeerset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
{
Name: "kube_router_stmt1",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "clusteripprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
MatchNeighborSet: config.MatchNeighborSet{
NeighborSet: "externalpeerset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
BgpActions: config.BgpActions{
SetAsPathPrepend: config.SetAsPathPrepend{
As: "65100",
RepeatN: 5,
},
},
},
},
},
nil,
},
{
"only prepends AS when both node annotations are present",
&NetworkRoutingController{
clientset: fake.NewSimpleClientset(),
hostnameOverride: "node-1",
bgpEnableInternal: true,
bgpFullMeshMode: false,
pathPrepend: false,
pathPrependAS: "65100",
bgpServer: gobgp.NewBgpServer(),
activeNodes: make(map[string]bool),
globalPeerRouters: []*config.NeighborConfig{
{
NeighborAddress: "10.10.0.1",
},
{
NeighborAddress: "10.10.0.2",
},
},
nodeAsnNumber: 100,
},
[]*v1core.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
Annotations: map[string]string{
"kube-router.io/node.asn": "100",
},
},
Status: v1core.NodeStatus{
Addresses: []v1core.NodeAddress{
{
Type: v1core.NodeInternalIP,
Address: "10.0.0.1",
},
},
},
Spec: v1core.NodeSpec{
PodCIDR: "172.20.0.0/24",
},
},
},
[]*v1core.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-1",
},
Spec: v1core.ServiceSpec{
Type: "ClusterIP",
ClusterIP: "10.0.0.1",
ExternalIPs: []string{"1.1.1.1"},
},
},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "podcidrprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "172.20.0.0/24",
MasklengthRange: "24..24",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{
{
PrefixSetName: "clusteripprefixset",
PrefixList: []config.Prefix{
{
IpPrefix: "1.1.1.1/32",
MasklengthRange: "32..32",
},
{
IpPrefix: "10.0.0.1/32",
MasklengthRange: "32..32",
},
},
},
},
NeighborSets: []config.NeighborSet{},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
&config.DefinedSets{
PrefixSets: []config.PrefixSet{},
NeighborSets: []config.NeighborSet{
{
NeighborSetName: "externalpeerset",
NeighborInfoList: []string{"10.10.0.1/32", "10.10.0.2/32"},
},
},
TagSets: []config.TagSet{},
BgpDefinedSets: config.BgpDefinedSets{},
},
[]*config.Statement{
{
Name: "kube_router_stmt0",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "podcidrprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
MatchNeighborSet: config.MatchNeighborSet{
NeighborSet: "iBGPpeerset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
{
Name: "kube_router_stmt1",
Conditions: config.Conditions{
MatchPrefixSet: config.MatchPrefixSet{
PrefixSet: "clusteripprefixset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
MatchNeighborSet: config.MatchNeighborSet{
NeighborSet: "externalpeerset",
MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY,
},
},
Actions: config.Actions{
RouteDisposition: config.ROUTE_DISPOSITION_ACCEPT_ROUTE,
},
},
},
nil,
},
}
for _, testcase := range testcases {
@ -1615,7 +1896,7 @@ func Test_addExportPolicies(t *testing.T) {
for _, expectedStatement := range testcase.policyStatements {
found := false
for _, statement := range statements {
if statement.Equal(expectedStatement) {
if reflect.DeepEqual(statement, expectedStatement) {
found = true
}
}