Skip to content

[feat] : enable nodeipamcontroller within CCM #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ A Cloud Controller Manager (CCM) is a Kubernetes control plane component that em
- Manages network policies
- Configures network routes for optimal communication

#### NodeIPAM Controller
- Manages and configures pod CIDRs to nodes

## Requirements

- Kubernetes 1.22+
Expand Down Expand Up @@ -75,6 +78,7 @@ A Cloud Controller Manager (CCM) is a Kubernetes control plane component that em
- [Firewall Setup](docs/configuration/firewall.md)
- [Route Configuration](docs/configuration/routes.md)
- [Session Affinity](docs/configuration/session-affinity.md)
- [NodeIPAM Configuration](docs/configuration/nodeipam.md)

### Examples and Development
- [Examples](docs/examples/README.md) - Real-world usage examples
Expand Down
8 changes: 8 additions & 0 deletions cloud/linode/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
NodeBalancerBackendIPv4Subnet string
GlobalStopChannel chan<- struct{}
EnableIPv6ForLoadBalancers bool
AllocateNodeCIDRs bool
ClusterCIDRIPv4 string
NodeCIDRMaskSizeIPv4 int
NodeCIDRMaskSizeIPv6 int
}

type linodeCloud struct {
Expand Down Expand Up @@ -187,6 +191,10 @@
serviceInformer := sharedInformer.Core().V1().Services()
nodeInformer := sharedInformer.Core().V1().Nodes()

if err := startNodeIpamController(stopCh, c, nodeInformer, kubeclient); err != nil {
klog.Fatal("starting of node ipam controller failed", err)
}

Check warning on line 196 in cloud/linode/cloud.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/cloud.go#L194-L196

Added lines #L194 - L196 were not covered by tests

if c.linodeTokenHealthChecker != nil {
go c.linodeTokenHealthChecker.Run(stopCh)
}
Expand Down
167 changes: 167 additions & 0 deletions cloud/linode/nodeipamcontroller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This file holds the code related with the sample nodeipamcontroller
// which demonstrates how cloud providers add external controllers to cloud-controller-manager

package linode

import (
"fmt"
"net"
"strings"

"k8s.io/apimachinery/pkg/util/wait"
v1 "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
cloudprovider "k8s.io/cloud-provider"
nodeipamcontroller "k8s.io/kubernetes/pkg/controller/nodeipam"
"k8s.io/kubernetes/pkg/controller/nodeipam/ipam"
netutils "k8s.io/utils/net"
)

const (
maxAllowedNodeCIDRs = 2
)

var (
// defaultNodeMaskCIDRIPv4 is default mask size for IPv4 node cidr
defaultNodeMaskCIDRIPv4 = 24
// defaultNodeMaskCIDRIPv6 is default mask size for IPv6 node cidr
defaultNodeMaskCIDRIPv6 = 64
)

func startNodeIpamController(stopCh <-chan struct{}, cloud cloudprovider.Interface, nodeInformer v1.NodeInformer, kubeclient kubernetes.Interface) error {
var serviceCIDR *net.IPNet
var secondaryServiceCIDR *net.IPNet

// should we start nodeIPAM
if !Options.AllocateNodeCIDRs {
return nil
}

// failure: bad cidrs in config
clusterCIDRs, dualStack, err := processCIDRs(Options.ClusterCIDRIPv4)
if err != nil {
return fmt.Errorf("processCIDRs failed: %w", err)
}

// failure: more than one cidr but they are not configured as dual stack
if len(clusterCIDRs) > 1 && !dualStack {
return fmt.Errorf("len of ClusterCIDRs==%v and they are not configured as dual stack (at least one from each IPFamily", len(clusterCIDRs))
}

// failure: more than cidrs is not allowed even with dual stack
if len(clusterCIDRs) > maxAllowedNodeCIDRs {
return fmt.Errorf("len of clusters is:%v > more than max allowed of %d", len(clusterCIDRs), maxAllowedNodeCIDRs)
}

/* TODO: uncomment and fix if we want to support service cidr overlap with nodecidr
// service cidr processing
if len(strings.TrimSpace(nodeIPAMConfig.ServiceCIDR)) != 0 {
_, serviceCIDR, err = netutils.ParseCIDRSloppy(nodeIPAMConfig.ServiceCIDR)
if err != nil {
klog.ErrorS(err, "Unsuccessful parsing of service CIDR", "CIDR", nodeIPAMConfig.ServiceCIDR)
}
}

if len(strings.TrimSpace(nodeIPAMConfig.SecondaryServiceCIDR)) != 0 {
_, secondaryServiceCIDR, err = netutils.ParseCIDRSloppy(nodeIPAMConfig.SecondaryServiceCIDR)
if err != nil {
klog.ErrorS(err, "Unsuccessful parsing of service CIDR", "CIDR", nodeIPAMConfig.SecondaryServiceCIDR)
}
}

// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
if serviceCIDR != nil && secondaryServiceCIDR != nil {
// should be dual stack (from different IPFamilies)
dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
if err != nil {
return nil, false, fmt.Errorf("failed to perform dualstack check on serviceCIDR and secondaryServiceCIDR error:%v", err)
}
if !dualstackServiceCIDR {
return nil, false, fmt.Errorf("serviceCIDR and secondaryServiceCIDR are not dualstack (from different IPfamiles)")
}
}
*/

nodeCIDRMaskSizes := setNodeCIDRMaskSizes(clusterCIDRs)

ctx := wait.ContextForChannel(stopCh)

nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
ctx,
nodeInformer,
cloud,
kubeclient,
clusterCIDRs,
serviceCIDR,
secondaryServiceCIDR,
nodeCIDRMaskSizes,
ipam.RangeAllocatorType,
)
if err != nil {
return err
}

Check warning on line 118 in cloud/linode/nodeipamcontroller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/nodeipamcontroller.go#L117-L118

Added lines #L117 - L118 were not covered by tests

go nodeIpamController.Run(ctx)
return nil
}

// processCIDRs is a helper function that works on a comma separated cidrs and returns
// a list of typed cidrs
// a flag if cidrs represents a dual stack
// error if failed to parse any of the cidrs
func processCIDRs(cidrsList string) ([]*net.IPNet, bool, error) {
cidrsSplit := strings.Split(strings.TrimSpace(cidrsList), ",")

cidrs, err := netutils.ParseCIDRs(cidrsSplit)
if err != nil {
return nil, false, err
}

// if cidrs has an error then the previous call will fail
// safe to ignore error checking on next call
dualstack, err := netutils.IsDualStackCIDRs(cidrs)
if err != nil {
return nil, false, fmt.Errorf("failed to perform dualstack check on cidrs: %w", err)
}

Check warning on line 141 in cloud/linode/nodeipamcontroller.go

View check run for this annotation

Codecov / codecov/patch

cloud/linode/nodeipamcontroller.go#L140-L141

Added lines #L140 - L141 were not covered by tests

return cidrs, dualstack, nil
}

func setNodeCIDRMaskSizes(clusterCIDRs []*net.IPNet) []int {
sortedSizes := func(maskSizeIPv4, maskSizeIPv6 int) []int {
nodeMaskCIDRs := make([]int, len(clusterCIDRs))

for idx, clusterCIDR := range clusterCIDRs {
if netutils.IsIPv6CIDR(clusterCIDR) {
nodeMaskCIDRs[idx] = maskSizeIPv6
} else {
nodeMaskCIDRs[idx] = maskSizeIPv4
}
}
return nodeMaskCIDRs
}

if Options.NodeCIDRMaskSizeIPv4 != 0 {
defaultNodeMaskCIDRIPv4 = Options.NodeCIDRMaskSizeIPv4
}
if Options.NodeCIDRMaskSizeIPv6 != 0 {
defaultNodeMaskCIDRIPv6 = Options.NodeCIDRMaskSizeIPv6
}
return sortedSizes(defaultNodeMaskCIDRIPv4, defaultNodeMaskCIDRIPv6)
}
Loading
Loading