-
Notifications
You must be signed in to change notification settings - Fork 423
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
Adding Rate limiting ec2:DescribeInstances API along with Batching for high TPS #292
Conversation
Welcome @bhagwat070919! |
@bhagwat070919: GitHub didn't allow me to assign the following users: mhausler. Note that only kubernetes-sigs members, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
66a59ef
to
3b9735b
Compare
go test -v -coverprofile=coverage.out -race $(GITHUB_REPO)/... | ||
go tool cover -html=coverage.out -o coverage.html |
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.
Nice, but this change should probably have been a separate PR.
cmd/aws-iam-authenticator/server.go
Outdated
DefaultPort = 21362 | ||
// Default Ec2 TPS Variables |
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.
Nit: Add a newline before the comment so that gofmt
will intent this a bit nicer.
pkg/ec2provider/ec2provider.go
Outdated
|
||
const ( | ||
// max limit of k8s nodes support | ||
max_channel_size = 5000 |
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.
Nit: This could be set higher, then it won't have to be changed later on.
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.
How about 10K ?
pkg/ec2provider/ec2provider.go
Outdated
// Making sure the single instance calls waits max till 5 seconds 100* (50 * time.Millisecond) | ||
total_iteration_for_wait_interval = 100 |
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.
Isn't 5 seconds quite a long time?
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.
This seems OK, given that its just for calls from nodes not in the cache
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.
5 seconds is the timeout for any given request and upper limit.
pkg/ec2provider/ec2provider.go
Outdated
instanceIdsChannel chan string | ||
} | ||
|
||
func NewEC2Provider(roleARN string, qps int, burst int) EC2Provider { |
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.
In go, this func would usually be named just New
, since then it will get called using ec2provider.New()
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.
ack, yeah it does not hurt given I already have extracted it out from the server.go and in this package there are no other New()
pkg/ec2provider/ec2provider.go
Outdated
dnsCache map[string]string | ||
dnsCacheLock sync.RWMutex |
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.
Rename these cache
it lock
, since they will always be referenced using dnsCache.cache
and dnsCache.lock
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.
ack
pkg/ec2provider/ec2provider.go
Outdated
type ec2RequestQueue struct { | ||
requestQueueMap map[string]bool | ||
requestQueueLock sync.RWMutex | ||
} |
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.
Hmm, this is not even really a queue, is it? The pattern of a map to book is commonly used when you want a Set. Also, maps in go return the keys in random order, so you won't even get them in the order they were added when you iterate over it.
Can this just be
type ec2Requests struct {
set map[string]bool
lock sync.RWMutex
}
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.
Good point.
- my Idea was:
its a logical concept to understand easily as its a request queue where we have inflight requests saved to have lookup.
I will make this change.
pkg/ec2provider/ec2provider.go
Outdated
p.privateDNSCache.dnsCacheLock.Lock() | ||
defer p.privateDNSCache.dnsCacheLock.Unlock() |
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.
This is good! Change to use this "lock / defer unlock"-pattern in all the functions attached to ec2ProviderImpl
.
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.
Yeah , I like your idea as I was trying to be performance agnostic about these code in terms of nanoseconds while loosing the readability.
I will make all the unlock with defer.
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.
This is a great improvement! Mostly stylistic changes, and a reduction in the amount of logging
cmd/aws-iam-authenticator/server.go
Outdated
// DefaultPort is the default localhost port (chosen randomly). | ||
DefaultPort = 21362 | ||
// Default Ec2 TPS Variables | ||
Default_Ec2_DescribeInstances_Qps = 15 |
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.
Nit: Go generally doesn't use underscores, maybe DefaultEC2DescribeInstancesQPS
. See Effective Go
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.
ack
cmd/aws-iam-authenticator/server.go
Outdated
serverCmd.Flags().Int( | ||
"ec2-describeInstances-qps", | ||
Default_Ec2_DescribeInstances_Qps, | ||
"AWS Ec2 rate limiting with qps") |
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.
Nit: Ec2
-> EC2
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.
ack
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/aws/aws-sdk-go/aws" |
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.
nit: could you separate stdlib imports from third party imports?
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.
I hope one day my Goland will learn this and don't make this mistakes.
pkg/ec2provider/ec2provider.go
Outdated
// max limit of k8s nodes support | ||
max_channel_size = 5000 | ||
// max number of in flight non batched ec2:DescribeInstances request to flow | ||
max_allowed_single_request = 5 |
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.
nit: can you update these variable names to not include underscores?
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.
ack
pkg/ec2provider/ec2provider_test.go
Outdated
} | ||
var instances []*ec2.Instance | ||
instances = append(instances, instance) | ||
res1 := &ec2.Reservation{ |
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.
instead of all the variables and appends, what do you think about just returning a single variable with the nested fields set?
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.
good idea let me check.
pkg/ec2provider/ec2provider_test.go
Outdated
}, nil | ||
} | ||
|
||
func setup() *ec2ProviderImpl { |
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.
Can you name this something more specific like newMockedEC2ProviderImpl
?
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.
Done , good name suggestions
pkg/server/server.go
Outdated
"fmt" | ||
"log" | ||
"net/http" | ||
"regexp" | ||
"sigs.k8s.io/aws-iam-authenticator/pkg/ec2provider" |
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.
Can you move this to third party imports?
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.
ack
pkg/server/server_test.go
Outdated
func (p *testEC2Provider) StartEc2DescribeBatchProcessing() { | ||
|
||
} |
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.
tiniest of nit: this could be onelined
func (p *testEC2Provider) StartEc2DescribeBatchProcessing() {}
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.
ack
pkg/ec2provider/ec2provider.go
Outdated
var instanceId string | ||
select { | ||
case instanceId = <-p.instanceIdsChannel: | ||
logrus.Infof("Received the Instance Id := %s from buffered Channel for batch processing ", instanceId) |
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.
Do you need to log this since you have a log call in the batch process? Can this be debug?
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.
Added as Debug log
Thanks !!! I somehow knew that there would be an idea of removing the loggings but given the fact for every instance the whole workflow prints the messages once except few which just roam around for cache lookup. |
Thank you so much @mogren and @micahhausler for helping improve the Go code quality. |
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.
/lgtm
/approve
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: bhagwat070919, micahhausler The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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.
Nice work Bhagwat, thanks for fixing this!
/lgtm
* Adding workers_launch_template ebs encryption * Update CHANGELOG.md
Prerequisite:
The AWS ec2:DescribeInstances API is a low TPS batching capable API.
Summary :
As of Today a K8s cluster can support up-to 5000 nodes. every nodes has to run kubelet process to join to cluster, while joining the cluster kubelet makes multiple calls to API Server and if it fails it does a retry with backoff where it can make 8-9 calls per object type per second.
Given the Authenticator is a webhook and API Server makes calls to Authenticator for every API call being made to verify the users or nodes role.
While verifying for nodes it needs to call to ec:DescribeInstances to get the PrivateDNSName for the node. When we make call to verify token and get-caller-identity we know about the instanceId. PrivateDNSName and whether the session is valid but we don't know whether this node belongs to the same aws account.
Problem:
For example lets assume we have 500 nodes , and a cluster have 20 object types.
Thinking of two situations:
For the master restart , every connection to the old master from every kubelet running on nodes will close like watches and all hence kubelet will try to re-connect and get the updated list and do watch connection while doing so it has to pass through Authenticator and authenticator does verification of instance with PrivateDNSNames belonging to the same aws account.
As of today not having any rate limiting for the ec2:DescribeInstances make service unavailable as after 20-30 calls the aws ec2 API starts throttling because every node calls goes to API in the same interval and at multiple fold.
The number of calls per second for the failed to get authenticated would be
Number of nodes * 20 * 8 = 500 * 20 * 8 = ~ 8000 TPS
In a minute it can go upto ~ 200 K - ~400K
As this throttling would not get resolved at the same time it takes almost 2-5 minutes to get resolve
So total number of throttling goes to a very high number which impacts other services running in the aws account or CNI or Kube-Controller-manager.
While the Authenticator tries to cache the PrivateDNSNames but that does not help in this case as the kubelet calls very fast and the master has restarted so it does not have every node in cache.
Solution:
Testing:
Next: