Skip to content
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

Introduce DISABLE_LEAKED_ENI_CLEANUP to disable leaked ENI cleanup task #2370

Merged
merged 1 commit into from
May 11, 2023
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,15 @@ Default: empty

Specify the EC2 endpoint to use. This is useful if you are using a custom endpoint for EC2. For example, if you are using a proxy for EC2, you can set this to the proxy endpoint. Any kind of URL or IP address is valid such as `https://localhost:8080` or `http://ec2.us-west-2.customaws.com`. If this is not set, the default EC2 endpoint will be used.

#### `DISABLE_LEAKED_ENI_CLEANUP` (v1.13.0+)

Type: Boolean as a String

Default: `false`

On IPv4 clusters, IPAMD schedules an hourly background task per node that cleans up leaked ENIs. Setting this environment variable to `true` disables that job. The primary motivation to disable this task is to decrease the amount of EC2 API calls made from each node.
jdn5126 marked this conversation as resolved.
Show resolved Hide resolved
Note that disabling this task should be considered carefully, as it requires users to manually cleanup ENIs leaked in their account. See [#1223](https://github.com/aws/amazon-vpc-cni-k8s/issues/1223) for a related discussion.

### VPC CNI Feature Matrix

IP Mode | Secondary IP Mode | Prefix Delegation | Security Groups Per Pod | WARM & MIN IP/Prefix Targets | External SNAT
Expand Down
5 changes: 2 additions & 3 deletions pkg/awsutils/awsutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ func (i instrumentedIMDS) GetMetadataWithContext(ctx context.Context, p string)
}

// New creates an EC2InstanceMetadataCache
func New(useCustomNetworking, disableENIProvisioning, v4Enabled, v6Enabled bool, eventRecorder *eventrecorder.EventRecorder) (*EC2InstanceMetadataCache, error) {
func New(useCustomNetworking, disableLeakedENICleanup, v4Enabled, v6Enabled bool, eventRecorder *eventrecorder.EventRecorder) (*EC2InstanceMetadataCache, error) {
//ctx is passed to initWithEC2Metadata func to cancel spawned go-routines when tests are run
ctx := context.Background()

Expand Down Expand Up @@ -437,7 +437,7 @@ func New(useCustomNetworking, disableENIProvisioning, v4Enabled, v6Enabled bool,
}

// Clean up leaked ENIs in the background
if !disableENIProvisioning {
if !disableLeakedENICleanup {
go wait.Forever(cache.cleanUpLeakedENIs, time.Hour)
}

Expand Down Expand Up @@ -1807,7 +1807,6 @@ func (cache *EC2InstanceMetadataCache) getLeakedENIs() ([]*ec2.NetworkInterface,
}

err := cache.getENIsFromPaginatedDescribeNetworkInterfaces(input, filterFn)

if err != nil {
return nil, errors.Wrap(err, "awsutils: unable to obtain filtered list of network interfaces")
}
Expand Down
41 changes: 21 additions & 20 deletions pkg/ipamd/ipamd.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ const (
// should not manage it in any form.
eniNoManageTagKey = "node.k8s.amazonaws.com/no_manage"

// disableENIProvisioning is used to specify that ENI doesn't need to be synced during initializing a pod.
// disableENIProvisioning is used to specify that ENIs do not need to be synced during initializing a pod.
envDisableENIProvisioning = "DISABLE_NETWORK_RESOURCE_PROVISIONING"

// disableLeakedENICleanup is used to specify that the task checking and cleaning up leaked ENIs should not be run.
envDisableLeakedENICleanup = "DISABLE_LEAKED_ENI_CLEANUP"

// Specify where ipam should persist its current IP<->container allocations.
envBackingStorePath = "AWS_VPC_K8S_CNI_BACKING_STORE"
defaultBackingStorePath = "/var/run/aws-node/ipam.json"
Expand Down Expand Up @@ -382,7 +385,6 @@ func (c *IPAMContext) inInsufficientCidrCoolingPeriod() bool {
func New(rawK8SClient client.Client, cachedK8SClient client.Client) (*IPAMContext, error) {
prometheusRegister()
c := &IPAMContext{}

c.rawK8SClient = rawK8SClient
c.cachedK8SClient = cachedK8SClient
c.networkClient = networkutils.New()
Expand All @@ -391,10 +393,9 @@ func New(rawK8SClient client.Client, cachedK8SClient client.Client) (*IPAMContex
c.enablePrefixDelegation = usePrefixDelegation()
c.enableIPv4 = isIPv4Enabled()
c.enableIPv6 = isIPv6Enabled()
c.disableENIProvisioning = disableENIProvisioning()

c.disableENIProvisioning = disablingENIProvisioning()

client, err := awsutils.New(c.useCustomNetworking, c.disableENIProvisioning, c.enableIPv4, c.enableIPv6, c.eventRecorder)
client, err := awsutils.New(c.useCustomNetworking, disableLeakedENICleanup(), c.enableIPv4, c.enableIPv6, c.eventRecorder)
if err != nil {
return nil, errors.Wrap(err, "ipamd: can not initialize with AWS SDK interface")
}
Expand Down Expand Up @@ -759,18 +760,16 @@ func (c *IPAMContext) tryFreeENI() {
// tryUnassignIPsorPrefixesFromAll determines if there are IPs to free when we have extra IPs beyond the target and warmIPTargetDefined
// is enabled, deallocate extra IP addresses
func (c *IPAMContext) tryUnassignCidrsFromAll() {

_, over, warmTargetDefined := c.datastoreTargetState()

//WARM IP targets not defined then check if WARM_PREFIX_TARGET is defined.
// If WARM IP targets are not defined, check if WARM_PREFIX_TARGET is defined.
if !warmTargetDefined {
over = c.computeExtraPrefixesOverWarmTarget()
}

if over > 0 {
eniInfos := c.dataStore.GetENIInfos()
for eniID := range eniInfos.ENIs {
//Either returns prefixes or IPs [Cidrs]
// Either returns prefixes or IPs [Cidrs]
cidrs := c.dataStore.FindFreeableCidrs(eniID)
if cidrs == nil {
log.Errorf("Error finding unassigned IPs for ENI %s", eniID)
Expand All @@ -781,15 +780,14 @@ func (c *IPAMContext) tryUnassignCidrsFromAll() {
// this ENI. In that case we should only free the number of available Cidrs.
numFreeable := min(over, len(cidrs))
cidrs = cidrs[:numFreeable]

if len(cidrs) == 0 {
continue
}

// Delete IPs from datastore
var deletedCidrs []datastore.CidrInfo
for _, toDelete := range cidrs {
// Don't force the delete, since a freeable Cidrs might have been assigned to a pod
// Do not force the delete, since a freeable Cidr might have been assigned to a pod
// before we get around to deleting it.
err := c.dataStore.DelIPv4CidrFromStore(eniID, toDelete.Cidr, false /* force */)
if err != nil {
Expand All @@ -801,7 +799,7 @@ func (c *IPAMContext) tryUnassignCidrsFromAll() {
}
}

// Deallocate Cidrs from the instance if they aren't used by pods.
// Deallocate Cidrs from the instance if they are not used by pods.
c.DeallocCidrs(eniID, deletedCidrs)
}
}
Expand Down Expand Up @@ -887,7 +885,6 @@ func (c *IPAMContext) tryAllocateENI(ctx context.Context) error {

if c.useCustomNetworking {
eniCfg, err := eniconfig.MyENIConfig(ctx, c.cachedK8SClient)

if err != nil {
log.Errorf("Failed to get pod ENI config")
return err
Expand All @@ -909,7 +906,6 @@ func (c *IPAMContext) tryAllocateENI(ctx context.Context) error {
}

resourcesToAllocate := c.GetENIResourcesToAllocate()

_, err = c.awsClient.AllocIPAddresses(eni, resourcesToAllocate)
if err != nil {
log.Warnf("Failed to allocate %d IP addresses on an ENI: %v", resourcesToAllocate, err)
Expand Down Expand Up @@ -1771,10 +1767,18 @@ func getMinimumIPTarget() int {
return noMinimumIPTarget
}

func disablingENIProvisioning() bool {
func disableENIProvisioning() bool {
return getEnvBoolWithDefault(envDisableENIProvisioning, false)
}

func disableLeakedENICleanup() bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this? I am not getting a good variable name but something like disableENIOperations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used exclusively for disabling the leaked ENI cleanup task, though. It doesn't disable other ENI operations. disableENIProvisioning is the flag we use for cases like not allocating or deallocating an ENI

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but the API is checking if v6 is enabled or disable ENI provisioning is set or disabled leaked eni cleanup is set..so it would be better to have some common name for readability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will sync up offline on this..

// Cases where leaked ENI cleanup is disabled:
// 1. IPv6 is enabled, so no ENIs are attached
// 2. ENI provisioning is disabled, so ENIs are not managed by IPAMD
// 3. Environment var explicitly disabling task is set
return isIPv6Enabled() || disableENIProvisioning() || getEnvBoolWithDefault(envDisableLeakedENICleanup, false)
}

func enablePodENI() bool {
return getEnvBoolWithDefault(envEnablePodENI, false)
}
Expand Down Expand Up @@ -1832,7 +1836,6 @@ func (c *IPAMContext) filterUnmanagedENIs(enis []awsutils.ENIMetadata) []awsutil
// accounting for the MINIMUM_IP_TARGET
// With prefix delegation this function determines the number of Prefixes `short` or `over`
func (c *IPAMContext) datastoreTargetState() (short int, over int, enabled bool) {

if c.warmIPTarget == noWarmIPTarget && c.minimumIPTarget == noMinimumIPTarget {
// there is no WARM_IP_TARGET defined and no MINIMUM_IP_TARGET, fallback to use all IP addresses on ENI
return 0, 0, false
Expand All @@ -1854,10 +1857,8 @@ func (c *IPAMContext) datastoreTargetState() (short int, over int, enabled bool)
over = max(min(over, stats.TotalIPs-c.minimumIPTarget), 0)

if c.enablePrefixDelegation {

//short : number of IPs short to reach warm targets
//over : number of IPs over the warm targets

// short : number of IPs short to reach warm targets
// over : number of IPs over the warm targets
_, numIPsPerPrefix, _ := datastore.GetPrefixDelegationDefaults()
// Number of prefixes IPAMD is short of to achieve warm targets
shortPrefix := datastore.DivCeil(short, numIPsPerPrefix)
Expand Down
4 changes: 2 additions & 2 deletions pkg/ipamd/ipamd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1503,11 +1503,11 @@ func TestDisablingENIProvisioning(t *testing.T) {
defer m.ctrl.Finish()

_ = os.Setenv(envDisableENIProvisioning, "true")
disabled := disablingENIProvisioning()
disabled := disableENIProvisioning()
assert.True(t, disabled)

_ = os.Unsetenv(envDisableENIProvisioning)
disabled = disablingENIProvisioning()
disabled = disableENIProvisioning()
assert.False(t, disabled)
}

Expand Down