Skip to content

Commit

Permalink
Add option to exclude end ips (#155)
Browse files Browse the repository at this point in the history
* Update doc to reflect using cidr will exclude network address and broadcast address by default

Signed-off-by: Lubron Zhan <[email protected]>
  • Loading branch information
lubronzhan authored Jun 24, 2024
1 parent 7262739 commit 11c3012
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 116 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The `kube-vip-cloud-provider` will only implement the `loadBalancer` functionali
- Support loadbalancerClass `kube-vip.io/kube-vip-class`
- Support assigning multiple services on single VIP (IPv4 only, optional)
- Support specifying service interface per namespace or at global level
- Support excluding first and last ip from cidr

## Installing the `kube-vip-cloud-provider`

Expand Down Expand Up @@ -146,7 +147,7 @@ If users only want kube-vip-cloud-provider to allocate ip for specific set of se

When enabled, kube-vip-cloud-provider tries to assign services to already used VIPs if the ports of the services
do not overlap.
If you want to enable VIP-sharing between services, you can set `allow-shared`-`namespace` to true. It follows the same rules as
If you want to enable VIP-sharing between services, you can set `allow-share`-`namespace` to true. It follows the same rules as
the configuration for global and namespace pools.

Example-object with sharing enabled for namespace `development`:
Expand All @@ -164,7 +165,7 @@ data:
allow-share-development: true
```

### Namespace pool
### Specify namespace scoped service interface

Kube-vip 0.8.0 supports `kube-vip.io/serviceInterface` annotation on service type LB. Now user can specify a ip range/cidr at namespace level, we would assume these ips within a namespace should share the same interface, then we support specifying interface per namespace level by

Expand All @@ -183,7 +184,30 @@ data:
interface-default: eth5
```
`interface-global` could be used to specify all ips would use this ip address. If there is no interface specified for a namespace, it will fall back to this `interface-global`. But this is usually not needed since kube-vip has `vip_servicesinterface` for user to define default interface for service type LB.
`interface-global` could be used to specify all services under all namespace would use this ip interface. If there is no interface specified for a namespace, it will fall back to this `interface-global`. But this is usually not needed since kube-vip has `vip_servicesinterface` for user to define default interface for service type LB.
## Exclude first and last ip from cidr
By default, when specifying cidr-<namespace>, all ips within that cidr will be allocated to service type lb. But in some case that
the first IP might be used for network ip and last ip might be used for broadcast ip, and user might no want to alloacte those to a service type LB. In
this case, the user could specify skip-end-ips-in-cidr: true in the configmap to skip the first and last ip for cidr.
Example object:
```
$ kubectl get configmap -n kube-system kubevip -o yaml

apiVersion: v1
kind: ConfigMap
metadata:
name: kubevip
namespace: kube-system
data:
cidr-default: 192.168.0.200/29
skip-end-ips-in-cidr: true
```
In this case, only ips `192.168.0.201-192.168.0.206` will be allocated to service, `192.168.0.200` and `192.168.0.207` are excluded.
## Debugging
Expand Down
37 changes: 37 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import v1 "k8s.io/api/core/v1"

const (
// ConfigMapSearchOrderKey is the key in the ConfigMap that defines whether IPs are allocated from the beginning or from the end.
ConfigMapSearchOrderKey = "search-order"

// ConfigMapSkipStartIPsKey is the key in the ConfigMap that has the IPs to skip at the start and end of the CIDR
ConfigMapSkipEndIPsKey = "skip-end-ips-in-cidr"

// ConfigMapServiceInterfacePrefix is prefix of the key in the ConfigMap for specifying the service interface for that namespace
ConfigMapServiceInterfacePrefix = "interface"
)

// KubevipLBConfig defines the configuration for the kube-vip load balancer in the kubevip configMap
// TODO: move all config into here so that it can be easily accessed and processed
type KubevipLBConfig struct {
ReturnIPInDescOrder bool
SkipEndIPsInCIDR bool
}

// GetKubevipLBConfig returns the KubevipLBConfig from the ConfigMap
func GetKubevipLBConfig(cm *v1.ConfigMap) *KubevipLBConfig {
c := &KubevipLBConfig{}
if searchOrder, ok := cm.Data[ConfigMapSearchOrderKey]; ok {
if searchOrder == "desc" {
c.ReturnIPInDescOrder = true
}
}
if skip, ok := cm.Data[ConfigMapSkipEndIPsKey]; ok {
if skip == "true" {
c.SkipEndIPsInCIDR = true
}
}
return c
}
22 changes: 16 additions & 6 deletions pkg/ipam/addressbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/netip"
"strings"

"github.com/kube-vip/kube-vip-cloud-provider/pkg/config"
"go4.org/netipx"
)

Expand All @@ -30,30 +31,39 @@ func parseCidrs(cidr string) (*netipx.IPSet, error) {

// buildHostsFromCidr - Builds a IPSet constructed from the cidr and filters out
// the broadcast IP and network IP for IPv4 networks
func buildHostsFromCidr(cidr string) (*netipx.IPSet, error) {
func buildHostsFromCidr(cidr string, kubevipLBConfig *config.KubevipLBConfig) (*netipx.IPSet, error) {
unfilteredSet, err := parseCidrs(cidr)
if err != nil {
return nil, err
}

builder := &netipx.IPSetBuilder{}
for _, prefix := range unfilteredSet.Prefixes() {
if prefix.IsSingleIP() {
builder.Add(prefix.Addr())
continue
}
// If the prefix is IPv6 address, add it to the builder directly
if !prefix.Addr().Is4() {
builder.AddPrefix(prefix)
continue
}

// Only skip the end IPs if skip-end-ips-in-cidr in configMap is set to true.
if prefix.IsSingleIP() && kubevipLBConfig != nil && kubevipLBConfig.SkipEndIPsInCIDR {
builder.Add(prefix.Addr())
continue
}

if r := netipx.RangeOfPrefix(prefix); r.IsValid() {
if prefix.Bits() == 31 {
// rfc3021 Using 31-Bit Prefixes on IPv4 Point-to-Point Links
builder.AddRange(netipx.IPRangeFrom(r.From(), r.To()))
continue
}

from, to := r.From(), r.To()
// For 192.168.0.200/23, 192.168.0.206 is the BroadcastIP, and 192.168.0.201 is the NetworkID
builder.AddRange(netipx.IPRangeFrom(r.From().Next(), r.To().Prev()))
if kubevipLBConfig != nil && kubevipLBConfig.SkipEndIPsInCIDR {
from, to = from.Next(), to.Prev()
}
builder.AddRange(netipx.IPRangeFrom(from, to))
}
}
return builder.IPSet()
Expand Down
23 changes: 11 additions & 12 deletions pkg/ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/netip"

"github.com/kube-vip/kube-vip-cloud-provider/pkg/config"
"go4.org/netipx"
"k8s.io/klog"
)
Expand Down Expand Up @@ -41,7 +42,7 @@ type ipManager struct {
}

// FindAvailableHostFromRange - will look through the cidr and the address Manager and find a free address (if possible)
func FindAvailableHostFromRange(namespace, ipRange string, inUseIPSet *netipx.IPSet, descOrder bool) (string, error) {
func FindAvailableHostFromRange(namespace, ipRange string, inUseIPSet *netipx.IPSet, kubevipLBConfig *config.KubevipLBConfig) (string, error) {
// Look through namespaces and update one if it exists
for x := range Manager {
if Manager[x].namespace == namespace {
Expand All @@ -58,7 +59,7 @@ func FindAvailableHostFromRange(namespace, ipRange string, inUseIPSet *netipx.IP
Manager[x].ipRange = ipRange
}

addr, err := FindFreeAddress(Manager[x].poolIPSet, inUseIPSet, descOrder)
addr, err := FindFreeAddress(Manager[x].poolIPSet, inUseIPSet, kubevipLBConfig)
if err != nil {
return "", &OutOfIPsError{namespace: namespace, pool: ipRange, isCidr: false}
}
Expand All @@ -79,38 +80,36 @@ func FindAvailableHostFromRange(namespace, ipRange string, inUseIPSet *netipx.IP

Manager = append(Manager, newManager)

addr, err := FindFreeAddress(poolIPSet, inUseIPSet, descOrder)
addr, err := FindFreeAddress(poolIPSet, inUseIPSet, kubevipLBConfig)
if err != nil {
return "", &OutOfIPsError{namespace: namespace, pool: ipRange, isCidr: false}
}
return addr.String(), nil
}

// FindAvailableHostFromCidr - will look through the cidr and the address Manager and find a free address (if possible)
func FindAvailableHostFromCidr(namespace, cidr string, inUseIPSet *netipx.IPSet, descOrder bool) (string, error) {
func FindAvailableHostFromCidr(namespace, cidr string, inUseIPSet *netipx.IPSet, kubevipLBConfig *config.KubevipLBConfig) (string, error) {
// Look through namespaces and update one if it exists
for x := range Manager {
if Manager[x].namespace == namespace {
// Check that the address range is the same
if Manager[x].cidr != cidr {
// If not rebuild the available hosts
poolIPSet, err := buildHostsFromCidr(cidr)
poolIPSet, err := buildHostsFromCidr(cidr, kubevipLBConfig)
if err != nil {
return "", err
}
Manager[x].poolIPSet = poolIPSet
Manager[x].cidr = cidr

}
addr, err := FindFreeAddress(Manager[x].poolIPSet, inUseIPSet, descOrder)
addr, err := FindFreeAddress(Manager[x].poolIPSet, inUseIPSet, kubevipLBConfig)
if err != nil {
return "", &OutOfIPsError{namespace: namespace, pool: cidr, isCidr: true}
}
return addr.String(), nil

}
}
poolIPSet, err := buildHostsFromCidr(cidr)
poolIPSet, err := buildHostsFromCidr(cidr, kubevipLBConfig)
if err != nil {
return "", err
}
Expand All @@ -122,7 +121,7 @@ func FindAvailableHostFromCidr(namespace, cidr string, inUseIPSet *netipx.IPSet,
}
Manager = append(Manager, newManager)

addr, err := FindFreeAddress(poolIPSet, inUseIPSet, descOrder)
addr, err := FindFreeAddress(poolIPSet, inUseIPSet, kubevipLBConfig)
if err != nil {
return "", &OutOfIPsError{namespace: namespace, pool: cidr, isCidr: true}
}
Expand Down Expand Up @@ -152,8 +151,8 @@ func FindAvailableHostFromCidr(namespace, cidr string, inUseIPSet *netipx.IPSet,

// FindFreeAddress returns the next free IP Address in a range based on a set of existing addresses.
// It will skip assumed gateway ip or broadcast ip for IPv4 address
func FindFreeAddress(poolIPSet *netipx.IPSet, inUseIPSet *netipx.IPSet, descOrder bool) (netip.Addr, error) {
if descOrder {
func FindFreeAddress(poolIPSet *netipx.IPSet, inUseIPSet *netipx.IPSet, kubevipLBConfig *config.KubevipLBConfig) (netip.Addr, error) {
if kubevipLBConfig != nil && kubevipLBConfig.ReturnIPInDescOrder {
ipranges := poolIPSet.Ranges()
for i := range len(ipranges) {
iprange := ipranges[len(ipranges)-1-i]
Expand Down
Loading

0 comments on commit 11c3012

Please sign in to comment.