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

Initial implementation for ingress rate limiting #148

Merged
merged 4 commits into from
Mar 16, 2018

Conversation

rramkumar1
Copy link
Contributor

@rramkumar1 rramkumar1 commented Mar 13, 2018

This PR adds support for rate limiting certain GCE API call's that are made by the controller. Currently, the cloud provider layer does not rate limit any standard controller calls (such as BackendService Get). As a result, in some cases when API calls are frequent, we are blowing through project quota and getting rate limited on the GCE side. This could also cause the controller to slow down. By allowing for customization of what calls can be rate limited, we can prevent blowing through project quota and we can intelligently rate limit so that we do not experience slowdown.

Will add unit tests once the overall approach is LGTM'd.

/assign @nicksardo @bowei

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 13, 2018
func constructRateLimitImpl(params []string) flowcontrol.RateLimiter, error {
// For now, only the "qps" type is supported.
rlType := params[0]
implArgs := params[1:]
Copy link
Contributor

Choose a reason for hiding this comment

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

Are implArgs required? You assert the length of params >= 2, then send [1:] to this func.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the length of the array passed in to constructRateLimitImpl is 1, then params[1:] will return empty. Is this what you are referring to?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea, that's fine.

Can you add the expected format as a comment above the func - same thing you did for validateOperation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -85,6 +86,10 @@ the default backend.`)
external cloud resources as it's shutting down. Mostly used for testing. In
normal environments the controller should only delete a loadbalancer if the
associated Ingress is deleted.`)
flag.StringVar(&F.GCERateLimit, "gce-ratelimit", "",
`Optional, can be used to rate limit certain GCE API calls. Example usage:
--gce-ratelimit=ga.Addresses.Get,qps,30 (limit ga.Addresses.Get to maximum
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't show an example using the semi-colon delimiter.
Doesn't show the burst.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's also set the example to something logical: 1.25 or 1.5 QPS.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

if l.customRateLimiters != nil {
for _, c := range l.customRateLimiters {
if c.useLimiter(key) {
return c.rateLimiter.Accept(ctx, key)
Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed offline, I'm not seeing the immediate need of having a list over here and a map in ratelimit.go. Could it be simplified to a single map here?

Copy link
Contributor Author

@rramkumar1 rramkumar1 Mar 13, 2018

Choose a reason for hiding this comment

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

Done. Removed the usage of a map in ingress code, but left the list in vendored code. Still thinking about whether a map or list is better.

We can stick with the current list or we can use a map. In that case, AddRateLimiter would take in the cloud.RateLimiter and also the operation, version, service triple so that it can create a RateLimitKey from that which it would then use as a key in the map.

@rramkumar1 rramkumar1 force-pushed the rate-limit branch 4 times, most recently from 012e0e2 to f9be41f Compare March 13, 2018 17:54
@@ -510,6 +519,7 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) {
nodeInstancePrefix: config.NodeInstancePrefix,
useMetadataServer: config.UseMetadataServer,
operationPollRateLimiter: operationPollRateLimiter,
customRateLimiters: make([]CustomRateLimiter, 0),
Copy link
Contributor

@nicksardo nicksardo Mar 13, 2018

Choose a reason for hiding this comment

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

Nit: the , 0 is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

// AddRateLimiter adds a custom cloud.RateLimiter implementation and a
// function that is used to check whether the custom rate limiter can be
// used on a given RateLimitKey.
func (g *GCECloud) AddRateLimiter(rl cloud.RateLimiter, useLimiter func(key *cloud.RateLimitKey) bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason to make this an Add... instead of SetRateLimiters? I'm not suggesting a change, just wondering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I figured that since this could be called multiple times, that Add... makes more sense.

cmd/glbc/main.go Outdated
@@ -69,6 +70,10 @@ func main() {
}

cloud := app.NewGCEClient()
if err := ratelimit.ConfigureGCERateLimiting(cloud, flags.F.GCERateLimit); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be called within app.NewGCEClient()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@rramkumar1 rramkumar1 force-pushed the rate-limit branch 2 times, most recently from 4b9d48b to 0ad0a69 Compare March 13, 2018 20:11
@k8s-ci-robot
Copy link
Contributor

New changes are detected. LGTM label has been removed.

@k8s-ci-robot
Copy link
Contributor

New changes are detected. LGTM label has been removed.

@k8s-ci-robot
Copy link
Contributor

New changes are detected. LGTM label has been removed.

@k8s-ci-robot
Copy link
Contributor

New changes are detected. LGTM label has been removed.

@k8s-ci-robot
Copy link
Contributor

New changes are detected. LGTM label has been removed.

Copy link
Contributor

@G-Harmon G-Harmon left a comment

Choose a reason for hiding this comment

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

Is there an issue associated with this PR? (If so, I might've missed it.)

You should have a higher level description of what you're doing somewhere, either in this PR or in an Issue. It's not clear from the title what is being rate-limited. Or what happens to the system when the limit is reached?

@@ -102,6 +103,11 @@ func NewGCEClient() *gce.GCECloud {
// manually to re-create the client.
// TODO: why do we bail with success out if there is a permission error???
if _, err = cloud.ListGlobalBackendServices(); err == nil || utils.IsHTTPErrorCode(err, http.StatusForbidden) {
rl, err := ratelimit.ConfigureGCERateLimiting(flags.F.GCERateLimit)
if err != nil {
glog.Infof("Error in configuring rate limiting: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

not an error? (either glog.Errorf() or returning nil)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding your other point, I'll add some context to the PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

okay, thanks!

ch := make(chan struct{})
go func() {
// Call flowcontrol.RateLimiter implementation.
if l.rateLimitImpls[key] != nil {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't work -- the project ID will not be equal. You will have to write a routine that does the matching (can't use struct equality)

Copy link
Contributor Author

@rramkumar1 rramkumar1 Mar 14, 2018

Choose a reason for hiding this comment

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

Done. In order to the keep the rateLimitImpls field as a map, the way I did it was kind of ugly

If I change that to a slice, it can be a lot cleaner so let me know which way you prefer.

@@ -85,6 +86,11 @@ the default backend.`)
external cloud resources as it's shutting down. Mostly used for testing. In
normal environments the controller should only delete a loadbalancer if the
associated Ingress is deleted.`)
flag.StringVar(&F.GCERateLimit, "gce-ratelimit", "",
Copy link
Member

Choose a reason for hiding this comment

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

There is a way to handle multiple instances of the same flag and have them append to a list. Easier than having to parse the semicolon ';'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -102,6 +103,11 @@ func NewGCEClient() *gce.GCECloud {
// manually to re-create the client.
// TODO: why do we bail with success out if there is a permission error???
if _, err = cloud.ListGlobalBackendServices(); err == nil || utils.IsHTTPErrorCode(err, http.StatusForbidden) {
rl, err := ratelimit.ConfigureGCERateLimiting(flags.F.GCERateLimit.Values())
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 reverse the conditions above move the ConfigureGCERateLimiting() call to the outer level. In other words, let's avoid nesting them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


// ConfigureGCERateLimiting parses the value for the --gce-ratelimit
// command line flag and returns a properly configured cloud.RateLimiter implementation.
func ConfigureGCERateLimiting(specs []string) (*ingressRateLimiter, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be more idiomatic to name it NewGCERateLimiter and have the struct be GCERateLimiter?

While this implementation exists in ingress, there's nothing ingress-specific about it, right? We could potentially move it to a more global library and use it in different places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

// ConfigureGCERateLimiting parses the value for the --gce-ratelimit
// command line flag and returns a properly configured cloud.RateLimiter implementation.
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't mention the values coming in via flag. As far as this package knows, it's just a list of strings. You can give an example of the string format.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@rramkumar1 rramkumar1 force-pushed the rate-limit branch 4 times, most recently from b5b5afa to 7c8e3ef Compare March 16, 2018 18:07
@nicksardo nicksardo added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 16, 2018
@nicksardo nicksardo merged commit 765707e into kubernetes:master Mar 16, 2018
@MrHohn MrHohn mentioned this pull request Mar 19, 2018
MrHohn added a commit to MrHohn/ingress-gce that referenced this pull request Mar 21, 2018
MrHohn added a commit to MrHohn/ingress-gce that referenced this pull request Mar 26, 2018
k8s-github-robot pushed a commit to kubernetes/kubernetes that referenced this pull request Mar 27, 2018
Automatic merge from submit-queue (batch tested with PRs 61644, 61624, 61743, 61019, 61287). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Add support for setting a custom rate limiter in gce cloud provider

**What this PR does / why we need it**:
This PR makes it possible to use a custom rate limiter for the GCE cloud provider layer. This is a copy of the raw vendor patch made in kubernetes/ingress-gce#148.

Fixes: kubernetes/ingress-gce#154

**Special notes for your reviewer**:

**Release note**:
```release-note
None
```

/assign @bowei
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants