-
Notifications
You must be signed in to change notification settings - Fork 3.4k
aws/eni: Don't use subnet tags to filter ENIs for GC #40656
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
Conversation
/test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. there are some questions but not related to this PR
Thanks for the breakdown. Typically the way you have formatted the PR description, we would encode directly into the git commits as three separate commits. This is valuable as well when referring to the code, because GitHub PR descriptions are often not available locally and that context is harder to come by. Would you mind pushing that content into the commits and split them up? Additionally as a bugfix I'll nominate this for v1.18 backport. |
`GetDetachedNetworkInterfaces` is used to find ENIs that should be GCed. For that to work, it's important to use the same tags to filter ENIs here as tags that are used to create ENIs. This is done [here](https://github.com/cilium/cilium/blob/65c63cd6febecde7eb307a1d776fa63ca61c0cc6/pkg/ipam/allocator/aws/aws.go#L95-L97). The issue is that in `GetDetachedNetworkInterfaces`, we merge `c.subnetsFilters` to those existing tags filters. `c.subnetsFilters` is created by [`NewSubnetsFilters`](https://github.com/cilium/cilium/blob/65c63cd6febecde7eb307a1d776fa63ca61c0cc6/pkg/aws/ec2/ec2.go#L130) based on [`operatorOption.Config.IPAMSubnetsTags`](https://github.com/cilium/cilium/blob/65c63cd6febecde7eb307a1d776fa63ca61c0cc6/operator/option/config.go#L267-L268) and [`operatorOption.Config.IPAMSubnetsIDs`](https://github.com/cilium/cilium/blob/65c63cd6febecde7eb307a1d776fa63ca61c0cc6/operator/option/config.go#L264-L265). The subnet IDs filter is fine as ENIs can be filtered by the ID of their subnet ([ref](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html#API_DescribeNetworkInterfaces_RequestParameters)), but filtering ENIs based on `IPAMSubnetsTags` is problematic, because at no point in the ENI creation process are those tags added to the ENIs, and they are not intended to be added to the ENIs. [`ENITags`](https://github.com/cilium/cilium/blob/65c63cd6febecde7eb307a1d776fa63ca61c0cc6/operator/option/config.go#L112) is the flag for tags that should be added to all ENIs, and these tags are properly passed down to `GetDetachedNetworkInterfaces` to ensure the GC logic also filters based on those tags. So here the fix is to keep the subnet-ID based filtering of ENIs but remove the subnet-tag one as it currently prevents the operator from GC-ing ENIs if the operator is not configured with `ENITags` as a subset of `IPAMSubnetsTags`. Signed-off-by: Hadrien Patte <hadrien.patte@datadoghq.com>
When constructing the `input` [`DescribeNetworkInterfacesInput`](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ec2#DescribeNetworkInterfacesInput), its `MaxResults` field is set to the `maxResults` parameter of `GetDetachedNetworkInterfaces`. But despite having the same name, those two parameters are very different: * `maxResults` is the [maximum number of ENIs](https://github.com/cilium/cilium/blob/6d1aa98670fe046fe3ac5a2b4167cfece416603e/pkg/aws/eni/eni_gc.go#L29-L31) we want returned by `GetDetachedNetworkInterfaces` * `input.MaxResults` is actually the (max) page size of paginated AWS API calls, see [pagination doc](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Query-Requests.html#api-pagination) The way the AWS API constructs a `DescribeNetworkInterfaces` call response is: 1. First gather all ENIs, 2. Then break them into pages based on the requested max page size 3. Then apply filters to remove ENIs that don't match the filters 4. Finally send all pages with whatever is left in them. This means that for example in an account with 1000 ENIs, with 5 ENIs that have a specific tag, if we make a `DescribeNetworkInterfaces` with a filter on this tag and request a page size of 25, the AWS API will start by gathering all 1000 ENIs. Then group them in 40 pages of 25 ENIs each. Then apply the filter which will leave most pages empty, only at most five pages will have at least one ENI depending on whether the 5 ENIs matching the tag filter were in the same pages or not. Then the API will send all 40 pages one by one even if most of those are empty. this means that we make 40 individual API calls to get 5 ENIs. To avoid this situation, we set the AWS API response max page size to [its maximum value of 1000](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html#API_DescribeNetworkInterfaces_RequestParameters). using the existing `defaults.ENIMaxResultsPerApiCall`. Signed-off-by: Hadrien Patte <hadrien.patte@datadoghq.com>
b1e2a94
to
14d4584
Compare
`GetDetachedNetworkInterfaces` has a `maxResults` parameter which is set from [`GarbageCollectionParams.MaxPerInterval`](https://github.com/cilium/cilium/blob/6d1aa98670fe046fe3ac5a2b4167cfece416603e/pkg/aws/eni/eni_gc.go#L29-L31). Which in turn is set from the [`ENIGarbageCollectionMaxPerInterval`](https://github.com/cilium/cilium/blob/6d1aa98670fe046fe3ac5a2b4167cfece416603e/pkg/defaults/defaults.go#L391-L393) const to 25. The goal of this parameter is to cap the length of the returned `result`. But with the way it is currently used, `GetDetachedNetworkInterfaces` can actually return a `result` of length up to 49. This is because for each page, we append all ENIs from `output.NetworkInterfaces` to `result` and only after that check for the length of `result`. so if at the beginning of a page iteration `result` is a slice of length 24 and the new page has 25 ENIs, then they will all be appended to `result` before the length check will `break` out of the paginator loop as `result` will be of length 24+25=49. Signed-off-by: Hadrien Patte <hadrien.patte@datadoghq.com>
14d4584
to
2da9819
Compare
/test |
This PRs contains three fixes for the
GetDetachedNetworkInterfaces
function:GC tags
GetDetachedNetworkInterfaces
is used to find ENIs that should be GCed. For that to work, it's important to use the same tags to filter ENIs here as tags that are used to create ENIs. This is done here. The issue is that inGetDetachedNetworkInterfaces
, we mergec.subnetsFilters
to those existing tags filters.c.subnetsFilters
is created byNewSubnetsFilters
based onoperatorOption.Config.IPAMSubnetsTags
andoperatorOption.Config.IPAMSubnetsIDs
. The subnet IDs filter is fine as ENIs can be filtered by the ID of their subnet (ref), but filtering ENIs based onIPAMSubnetsTags
is problematic, because at no point in the ENI creation process are those tags added to the ENIs, and they are not intended to be added to the ENIs.ENITags
is the flag for tags that should be added to all ENIs, and these tags are properly passed down toGetDetachedNetworkInterfaces
to ensure the GC logic also filters based on those tags.So here the fix is to keep the subnet-ID based filtering of ENIs but remove the subnet-tag one as it currently prevents the operator from GC-ing ENIs if the operator is not configured with
ENITags
as a subset ofIPAMSubnetsTags
.Relevant diff:
AWS API calls page size
When constructing the
input
DescribeNetworkInterfacesInput
, itsMaxResults
field is set to themaxResults
parameter ofGetDetachedNetworkInterfaces
. But despite having the same name, those two parameters are very different:maxResults
is the maximum number of ENIs we want returned byGetDetachedNetworkInterfaces
input.MaxResults
is actually the (max) page size of paginated AWS API calls, see pagination docThe way the AWS API constructs a
DescribeNetworkInterfaces
call response is:This means that for example in an account with 1000 ENIs, with 5 ENIs that have a specific tag, if we make a
DescribeNetworkInterfaces
with a filter on this tag and request a page size of 25, the AWS API will start by gathering all 1000 ENIs. Then group them in 40 pages of 25 ENIs each. Then apply the filter which will leave most pages empty, only at most five pages will have at least one ENI depending on whether the 5 ENIs matching the tag filter were in the same pages or not. Then the API will send all 40 pages one by one even if most of those are empty. this means that we make 40 individual API calls to get 5 ENIs.To avoid this situation, we set the AWS API response max page size to its maximum value of 1000. using the existing
defaults.ENIMaxResultsPerApiCall
.Relevant diff:
Max results cutoff
GetDetachedNetworkInterfaces
has amaxResults
parameter which is set fromGarbageCollectionParams.MaxPerInterval
. Which in turn is set from theENIGarbageCollectionMaxPerInterval
const to 25.The goal of this parameter is to cap the length of the returned
result
. But with the way it is currently used,GetDetachedNetworkInterfaces
can actually return aresult
of length up to 49. This is because for each page, we append all ENIs fromoutput.NetworkInterfaces
toresult
and only after that check for the length ofresult
. so if at the begining of a page iterationresult
is a slice of length 24 and the new page has 25 ENIs, then they will all be appended toresult
before the length check willbreak
out of the paginator loop asresult
will be of length 24+25=49.Relevant diff:
Note: this was already reported in #37983:
Please ensure your pull request adheres to the following guidelines:
description and a
Fixes: #XXX
line if the commit addresses a particularGitHub issue.
Fixes: <commit-id>
tag, thenplease add the commit author[s] as reviewer[s] to this issue.