Skip to content

Commit fe7b5e8

Browse files
committed
Static IP allocation via CNI config
1 parent 833ff8a commit fe7b5e8

File tree

15 files changed

+166
-2
lines changed

15 files changed

+166
-2
lines changed

pkg/alibabacloud/eni/node.go

+5
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ func (n *Node) AllocateIPs(ctx context.Context, a *ipam.AllocationAction) error
306306
return err
307307
}
308308

309+
func (n *Node) AllocateStaticIP(ctx context.Context, staticIPTags ipamTypes.Tags) (string, error) {
310+
// TODO
311+
return "", nil
312+
}
313+
309314
// PrepareIPRelease prepares the release of ENI IPs.
310315
func (n *Node) PrepareIPRelease(excessIPs int, scopedLog *logrus.Entry) *ipam.ReleaseAction {
311316
r := &ipam.ReleaseAction{}

pkg/aws/ec2/ec2.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
1515
"github.com/aws/aws-sdk-go-v2/service/ec2"
1616
ec2_types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
17-
log "github.com/sirupsen/logrus"
1817

1918
"github.com/cilium/cilium/pkg/api/helpers"
2019
"github.com/cilium/cilium/pkg/aws/endpoints"
@@ -24,6 +23,8 @@ import (
2423
ipPkg "github.com/cilium/cilium/pkg/ip"
2524
"github.com/cilium/cilium/pkg/ipam/option"
2625
ipamTypes "github.com/cilium/cilium/pkg/ipam/types"
26+
"github.com/cilium/cilium/pkg/logging"
27+
"github.com/cilium/cilium/pkg/logging/logfields"
2728
"github.com/cilium/cilium/pkg/spanstat"
2829
)
2930

@@ -39,6 +40,8 @@ const (
3940
InvalidParameterValueStr = "InvalidParameterValue"
4041
)
4142

43+
var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "ec2")
44+
4245
// Client represents an EC2 API client
4346
type Client struct {
4447
ec2Client *ec2.Client
@@ -691,6 +694,51 @@ func (c *Client) UnassignENIPrefixes(ctx context.Context, eniID string, prefixes
691694
return err
692695
}
693696

697+
func (c *Client) AssociateEIP(ctx context.Context, instanceID string, eipTags ipamTypes.Tags) (string, error) {
698+
filters := make([]ec2_types.Filter, 0, len(eipTags))
699+
for k, v := range eipTags {
700+
filters = append(filters, ec2_types.Filter{
701+
Name: aws.String(fmt.Sprintf("tag:%s", k)),
702+
Values: []string{v},
703+
})
704+
}
705+
706+
describeAddressesInput := &ec2.DescribeAddressesInput{
707+
Filters: filters,
708+
}
709+
c.limiter.Limit(ctx, "DescribeAddresses")
710+
sinceStart := spanstat.Start()
711+
addresses, err := c.ec2Client.DescribeAddresses(ctx, describeAddressesInput)
712+
c.metricsAPI.ObserveAPICall("DescribeAddresses", deriveStatus(err), sinceStart.Seconds())
713+
if err != nil {
714+
return "", err
715+
}
716+
log.Infof("Found %d EIPs corresponding to tags %v", len(addresses.Addresses), eipTags)
717+
718+
for _, address := range addresses.Addresses {
719+
// Only pick unassociated EIPs
720+
if address.AssociationId == nil {
721+
associateAddressInput := &ec2.AssociateAddressInput{
722+
AllocationId: address.AllocationId,
723+
AllowReassociation: aws.Bool(false),
724+
InstanceId: aws.String(instanceID),
725+
}
726+
c.limiter.Limit(ctx, "AssociateAddress")
727+
sinceStart = spanstat.Start()
728+
association, err := c.ec2Client.AssociateAddress(ctx, associateAddressInput)
729+
c.metricsAPI.ObserveAPICall("AssociateAddress", deriveStatus(err), sinceStart.Seconds())
730+
if err != nil {
731+
// TODO some errors can probably be skipped and next EIP can be tried
732+
return "", err
733+
}
734+
log.Infof("Associated EIP %s with instance %s (association ID: %s)", *address.PublicIp, instanceID, *association.AssociationId)
735+
return *address.PublicIp, nil
736+
}
737+
}
738+
739+
return "", fmt.Errorf("no unassociated EIPs found for tags %v", eipTags)
740+
}
741+
694742
func createAWSTagSlice(tags map[string]string) []ec2_types.Tag {
695743
awsTags := make([]ec2_types.Tag, 0, len(tags))
696744
for k, v := range tags {

pkg/aws/ec2/mock/mock.go

+5
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,11 @@ func (e *API) UnassignENIPrefixes(ctx context.Context, eniID string, prefixes []
531531
return fmt.Errorf("Unable to find ENI with ID %s", eniID)
532532
}
533533

534+
func (e *API) AssociateEIP(ctx context.Context, instanceID string, eipTags ipamTypes.Tags) (string, error) {
535+
// TODO
536+
return "", nil
537+
}
538+
534539
func (e *API) GetInstances(ctx context.Context, vpcs ipamTypes.VirtualNetworkMap, subnets ipamTypes.SubnetMap) (*ipamTypes.InstanceMap, error) {
535540
instances := ipamTypes.NewInstanceMap()
536541

pkg/aws/eni/instances.go

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type EC2API interface {
3434
UnassignPrivateIpAddresses(ctx context.Context, eniID string, addresses []string) error
3535
AssignENIPrefixes(ctx context.Context, eniID string, prefixes int32) error
3636
UnassignENIPrefixes(ctx context.Context, eniID string, prefixes []string) error
37+
AssociateEIP(ctx context.Context, instanceID string, eipTags ipamTypes.Tags) (string, error)
3738
}
3839

3940
// InstancesManager maintains the list of instances. It must be kept up to date

pkg/aws/eni/node.go

+4
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ func (n *Node) AllocateIPs(ctx context.Context, a *ipam.AllocationAction) error
295295
return n.manager.api.AssignPrivateIpAddresses(ctx, a.InterfaceID, int32(a.AvailableForAllocation))
296296
}
297297

298+
func (n *Node) AllocateStaticIP(ctx context.Context, staticIPTags ipamTypes.Tags) (string, error) {
299+
return n.manager.api.AssociateEIP(ctx, n.node.InstanceID(), staticIPTags)
300+
}
301+
298302
func (n *Node) getSecurityGroupIDs(ctx context.Context, eniSpec eniTypes.ENISpec) ([]string, error) {
299303
// 1. check explicit security groups associations via checking Spec.ENI.SecurityGroups
300304
// 2. check if Spec.ENI.SecurityGroupTags is passed and if so filter by those

pkg/azure/ipam/node.go

+5
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ func (n *Node) AllocateIPs(ctx context.Context, a *ipam.AllocationAction) error
130130
}
131131
}
132132

133+
func (n *Node) AllocateStaticIP(ctx context.Context, staticIPTags ipamTypes.Tags) (string, error) {
134+
// TODO
135+
return "", nil
136+
}
137+
133138
// CreateInterface is called to create a new interface. This operation is
134139
// currently not supported on Azure.
135140
func (n *Node) CreateInterface(ctx context.Context, allocation *ipam.AllocationAction, scopedLog *logrus.Entry) (int, string, error) {

pkg/ipam/node.go

+29
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ type Node struct {
131131
// IPAMReleased : IP released by the operator
132132
ipReleaseStatus map[string]string
133133

134+
assignedStaticIP string
135+
134136
// logLimiter rate limits potentially repeating warning logs
135137
logLimiter logging.Limiter
136138
}
@@ -264,6 +266,14 @@ func (n *Node) getMaxAllocate() int {
264266
return instanceMax
265267
}
266268

269+
func (n *Node) getStaticIPTags() ipamTypes.Tags {
270+
if n.resource.Spec.IPAM.StaticIPTags != nil {
271+
return n.resource.Spec.IPAM.StaticIPTags
272+
} else {
273+
return ipamTypes.Tags{}
274+
}
275+
}
276+
267277
// GetNeededAddresses returns the number of needed addresses that need to be
268278
// allocated or released. A positive number is returned to indicate allocation.
269279
// A negative number is returned to indicate release of addresses.
@@ -887,6 +897,16 @@ func (n *Node) maintainIPPool(ctx context.Context) (instanceMutated bool, err er
887897
n.removeStaleReleaseIPs()
888898
}
889899

900+
if len(n.getStaticIPTags()) > 1 {
901+
if n.assignedStaticIP == "" {
902+
ip, err := n.ops.AllocateStaticIP(ctx, n.getStaticIPTags())
903+
if err != nil {
904+
return false, err
905+
}
906+
n.assignedStaticIP = ip
907+
}
908+
}
909+
890910
a, err := n.determineMaintenanceAction()
891911
if err != nil {
892912
n.abortNoLongerExcessIPs(nil)
@@ -981,6 +1001,14 @@ func (n *Node) PopulateIPReleaseStatus(node *v2.CiliumNode) {
9811001
node.Status.IPAM.ReleaseIPs = releaseStatus
9821002
}
9831003

1004+
func (n *Node) PopulateStaticIPStatus(node *v2.CiliumNode) {
1005+
n.mutex.Lock()
1006+
defer n.mutex.Unlock()
1007+
if n.assignedStaticIP != "" {
1008+
node.Status.IPAM.AssignedStaticIP = n.assignedStaticIP
1009+
}
1010+
}
1011+
9841012
// syncToAPIServer synchronizes the contents of the CiliumNode resource
9851013
// [(*Node).resource)] with the K8s apiserver. This operation occurs on an
9861014
// interval to refresh the CiliumNode resource.
@@ -1029,6 +1057,7 @@ func (n *Node) syncToAPIServer() (err error) {
10291057

10301058
n.ops.PopulateStatusFields(node)
10311059
n.PopulateIPReleaseStatus(node)
1060+
n.PopulateStaticIPStatus(node)
10321061

10331062
err = n.update(origNode, node, retry, true)
10341063
if err == nil {

pkg/ipam/node_manager.go

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ type NodeOperations interface {
7575
// to perform the actual allocation.
7676
AllocateIPs(ctx context.Context, allocation *AllocationAction) error
7777

78+
AllocateStaticIP(ctx context.Context, staticIPTags ipamTypes.Tags) (string, error)
79+
7880
// PrepareIPRelease is called to calculate whether any IP excess needs
7981
// to be resolved. It behaves identical to PrepareIPAllocation but
8082
// indicates a need to release IPs.

pkg/ipam/node_manager_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ func (n *nodeOperationsMock) AllocateIPs(ctx context.Context, allocation *Alloca
123123
return nil
124124
}
125125

126+
func (n *nodeOperationsMock) AllocateStaticIP(ctx context.Context, staticIPTags ipamTypes.Tags) (string, error) {
127+
// TODO
128+
return "", nil
129+
}
130+
126131
func (n *nodeOperationsMock) PrepareIPRelease(excessIPs int, scopedLog *logrus.Entry) *ReleaseAction {
127132
n.mutex.RLock()
128133
excessIPs = math.IntMin(excessIPs, len(n.allocatedIPs))

pkg/ipam/types/types.go

+6
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ type IPAMSpec struct {
176176
//
177177
// +kubebuilder:validation:Minimum=0
178178
PodCIDRReleaseThreshold int `json:"pod-cidr-release-threshold,omitempty"`
179+
180+
// +optional
181+
StaticIPTags map[string]string `json:"static-ip-tags,omitempty"`
179182
}
180183

181184
// IPReleaseStatus defines the valid states in IP release handshake
@@ -212,6 +215,9 @@ type IPAMStatus struct {
212215
//
213216
// +optional
214217
ReleaseIPs map[string]IPReleaseStatus `json:"release-ips,omitempty"`
218+
219+
// +optional
220+
AssignedStaticIP string `json:"assigned-static-ip,omitempty"`
215221
}
216222

217223
// IPAMPoolRequest is a request from the agent to the operator, indicating how

pkg/ipam/types/zz_generated.deepcopy.go

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ipam/types/zz_generated.deepequal.go

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnodes.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ spec:
409409
cilium-operator to get involved.
410410
minimum: 0
411411
type: integer
412+
static-ip-tags:
413+
additionalProperties:
414+
type: string
415+
type: object
412416
type: object
413417
nodeidentity:
414418
description: NodeIdentity is the Cilium numeric identity allocated
@@ -651,6 +655,8 @@ spec:
651655
ipam:
652656
description: IPAM is the IPAM status of the node.
653657
properties:
658+
assigned-static-ip:
659+
type: string
654660
operator-status:
655661
description: Operator is the Operator status of the node
656662
properties:

pkg/k8s/apis/cilium.io/register.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ const (
1515
//
1616
// Maintainers: Run ./Documentation/check-crd-compat-table.sh for each release
1717
// Developers: Bump patch for each change in the CRD schema.
18-
CustomResourceDefinitionSchemaVersion = "1.27.1"
18+
CustomResourceDefinitionSchemaVersion = "1.27.5"
1919
)

pkg/nodediscovery/nodediscovery.go

+17
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,12 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ciliumv2.CiliumNode) er
571571
nodeResource.Spec.BootID = n.localNode.BootID
572572

573573
switch option.Config.IPAM {
574+
case ipamOption.IPAMKubernetes:
575+
if c := n.NetConf; c != nil {
576+
if c.IPAM.StaticIPTags != nil && len(c.IPAM.StaticIPTags) > 0 {
577+
nodeResource.Spec.IPAM.StaticIPTags = c.IPAM.StaticIPTags
578+
}
579+
}
574580
case ipamOption.IPAMClusterPoolV2:
575581
if c := n.NetConf; c != nil {
576582
nodeResource.Spec.IPAM.PodCIDRAllocationThreshold = c.IPAM.PodCIDRAllocationThreshold
@@ -609,6 +615,10 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ciliumv2.CiliumNode) er
609615
nodeResource.Spec.IPAM.PreAllocate = c.IPAM.PreAllocate
610616
}
611617

618+
if c.IPAM.StaticIPTags != nil && len(c.IPAM.StaticIPTags) > 0 {
619+
nodeResource.Spec.IPAM.StaticIPTags = c.IPAM.StaticIPTags
620+
}
621+
612622
if c.ENI.FirstInterfaceIndex != nil {
613623
nodeResource.Spec.ENI.FirstInterfaceIndex = c.ENI.FirstInterfaceIndex
614624
}
@@ -674,6 +684,9 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ciliumv2.CiliumNode) er
674684
if c.IPAM.PreAllocate != 0 {
675685
nodeResource.Spec.IPAM.PreAllocate = c.IPAM.PreAllocate
676686
}
687+
if c.IPAM.StaticIPTags != nil && len(c.IPAM.StaticIPTags) > 0 {
688+
nodeResource.Spec.IPAM.StaticIPTags = c.IPAM.StaticIPTags
689+
}
677690
if c.Azure.InterfaceName != "" {
678691
nodeResource.Spec.Azure.InterfaceName = c.Azure.InterfaceName
679692
}
@@ -740,6 +753,10 @@ func (n *NodeDiscovery) mutateNodeResource(nodeResource *ciliumv2.CiliumNode) er
740753
if c.IPAM.PreAllocate != 0 {
741754
nodeResource.Spec.IPAM.PreAllocate = c.IPAM.PreAllocate
742755
}
756+
757+
if c.IPAM.StaticIPTags != nil && len(c.IPAM.StaticIPTags) > 0 {
758+
nodeResource.Spec.IPAM.StaticIPTags = c.IPAM.StaticIPTags
759+
}
743760
}
744761
}
745762

0 commit comments

Comments
 (0)