Skip to content
This repository has been archived by the owner on Apr 27, 2021. It is now read-only.

Buffered backends fix #44

Closed
wants to merge 1 commit into from
Closed

Buffered backends fix #44

wants to merge 1 commit into from

Conversation

andrewloux
Copy link

@andrewloux andrewloux commented Apr 10, 2018

Superseded by kubernetes#2408

What this PR does / why we need it:

  • Read from temp file if the request body from the controller's POST to update backends is larger than client_body_buffer_size.
  • There was an to address this problem over here: https://github.com/kubernetes/ingress-nginx/pull/2309/files - but the updates will still silently fail by updating the lua shared table with nil if the controller POST body is larger than 10m.
  • This PR makes sure that the dictionary is not updated if there is either too large of a request body - or an empty request body.

Which issue this PR fixes

Special notes for your reviewer:

  • Could someone look at the test I've written? What I'm doing right now is updating the ingress-nginx controller's client-body-buffer-size to be set to 1k so I can reproduce the problem.
  • After the test, the attribute is reset.

r/ @ElvinEfendi @zrdaley @fmejia97

@@ -7,6 +7,21 @@ function _M.get_backends_data()
return configuration_data:get("backends")
end

function _M.new_backends()

Choose a reason for hiding this comment

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

I think the convention is to name these kind of functions _handler, no? i.e backends_handler in this case.

--

Also since you are using this only locally, you don't have to export it. You can do

local function backends_handler()
...

Choose a reason for hiding this comment

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

Oh wait this is just for extracting request body. IMO we should not make it backends specific - there's nothing in the function body that's backends specific - this should just be a helper function that returns request body and nil or in case of error nil and error message

ngx.req.read_body()
local backends = _M.new_backends()
if not backends then
ngx.log(ngx.ERR, "dynamic-configuration: POST carries empty response body")

Choose a reason for hiding this comment

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

dynamic-configuration

I'm not sure what value this adds. Can you describe a real use case?

Copy link
Author

Choose a reason for hiding this comment

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

Other users might see the error, and see that it originated from this line in configuration.lua, but still might not know what "component" of ingress-nginx is not working. Given the annotation is named dynamic-configuration, I figured that this is a good way of making error logs from this lua module more meaningful.

@@ -270,7 +270,7 @@ func UpdateDeployment(kubeClientSet kubernetes.Interface, namespace string, name
return err
}

err = WaitForPodsReady(kubeClientSet, 60*time.Second, replicas, namespace, metav1.ListOptions{
err = WaitForPodsReady(kubeClientSet, 90*time.Second, replicas, namespace, metav1.ListOptions{

Choose a reason for hiding this comment

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

was flaky?

Copy link
Author

Choose a reason for hiding this comment

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

yep :/

Choose a reason for hiding this comment

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

probably started to be an issue with 60s because you're spinning up 30 replicas

Copy link
Author

Choose a reason for hiding this comment

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

haha yeah, I'm pretty sure that's it.


cm.Data["client-body-buffer-size"] = "1k"
_, updateErr := f.KubeClientSet.CoreV1().ConfigMaps("ingress-nginx").Update(cm)
Expect(updateErr).ToNot(HaveOccurred())

Choose a reason for hiding this comment

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

There's no helper function in the test framework for updating configmap? If not I suggest we extract it out into the framework code so that it can be reused.

Copy link
Author

Choose a reason for hiding this comment

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

I'll take a closer look. I notice that Zenara made a similar updateConfigmap method in her session affinity PR as well, so definitely should be made generic if there isn't already a way.

Expect(getErr).ToNot(HaveOccurred())
oldClientBodyBufferSize, oldClientBodyBufferSizeExists := cm.Data["client-body-buffer-size"];

cm.Data["client-body-buffer-size"] = "1k"

Choose a reason for hiding this comment

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

is this the lowest possible value?

Copy link
Author

Choose a reason for hiding this comment

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

From here: http://nginx.org/en/docs/syntax.html - I think so. Could also always make it 0 to make sure it such that it's always writing out to the temp file. What do you think?

Choose a reason for hiding this comment

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

if possible I'd go lower than this so that we don't have to spin up 30 replicas in test

Choose a reason for hiding this comment

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

I think 0 means never write to temp file

--

reading http://nginx.org/en/docs/syntax.html I think you can set in bytes

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense. Re-read that link, so without suffix denotes bytes.

Will do, thanks Elvin

@@ -7,6 +7,21 @@ function _M.get_backends_data()
return configuration_data:get("backends")
end

local function fetch_request_body()
ngx.req.read_body()
local backends = ngx.req.get_body_data()

Choose a reason for hiding this comment

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

just body? since this is not backends specific

Set("Host", "foo.com").
End()
Expect(len(errs)).Should(BeNumerically("==", 0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))

Choose a reason for hiding this comment

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

what's this for? we already assert that backend i s available at https://github.com/Shopify/ingress/pull/44/files#diff-8700c46a8379c603b5e41ccbaff6b71eR60

Copy link

@ElvinEfendi ElvinEfendi Apr 10, 2018

Choose a reason for hiding this comment

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

I think you wanna send this request after updating the client max body size, to make sure we parsed request body correctly

_, updateErr := f.KubeClientSet.CoreV1().ConfigMaps("ingress-nginx").Update(cm)
Expect(updateErr).ToNot(HaveOccurred())

replicas := 4
Copy link

@ElvinEfendi ElvinEfendi Apr 10, 2018

Choose a reason for hiding this comment

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

1 replica is not enough to generate a payload with sie of over 1 byte?

Copy link
Author

Choose a reason for hiding this comment

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

Not enough. I'm not entirely certain why.

Did some looking into upstream, might be possibly happening because Nginx might be pre-reading part of the request body. If it's able to pre-read the entire request body in its pre-read buffer - it might not even check the client_body_buffer_size.

See: https://github.com/nginx/nginx/blob/e66073c4d3a3d25b001dee617ee22d2a969ceab2/src/event/ngx_event_pipe.c#L149

Copy link
Author

Choose a reason for hiding this comment

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

But 2 replicas does look like it is enough :)

if not backends then
-- request body might've been wrote to tmp file if body > client_body_buffer_size
local file_name = ngx.req.get_body_file()
local file = assert(io.open(file_name, "rb"))

Choose a reason for hiding this comment

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

what does assert do? what happens if io.open fails?

local file_name = ngx.req.get_body_file()
local file = assert(io.open(file_name, "rb"))
backends = file:read("*all")
file:close()

Choose a reason for hiding this comment

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

what happens any of this function calls fail?

Copy link
Author

Choose a reason for hiding this comment

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

lines 18, 19 should be safe.

file:read & file:close will return a nil if the read or close operations are problematic.

And then since backends is nil, this will be handled appropriately here: https://github.com/Shopify/ingress/pull/44/files#diff-27d52aa3bacfc9feff2d5eb208af0a17R52

ngx.log(ngx.ERR, "dynamic-configuration: POST carries empty response body")
ngx.status = ngx.HTTP_BAD_REQUEST
return
end

Choose a reason for hiding this comment

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

This is a change of behaviour - i can not think of anything bad with it, but also don't see why it it necessary. Did you add it merely so that you can assert for absence of this logs in the e2e test?

Copy link
Author

Choose a reason for hiding this comment

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

The intention with this is to not update the config if the request_body is nil, as we were doing previously. This could be problematic, and is essentially a silent failure mode - since there are no symptoms i.e. 200 was being returned.

Understandably, if the balancer module cannot parse the backends data, then it doesn't sync the update - so the failure is somewhat limited, but I still think that the logging and 5xx response increases visibility here.

Actually read from the file

Logs probably shouldn't assume knowledge of implementation detail

Typos

Added integration test, and dynamic update config refactor

Don't force the 8k default

Minimal test case to make the configuration/backends request body write to temp file

Leverage new safe config updating methods, and use 2 replicas instead of 4
@andrewloux
Copy link
Author

Closing in favour of kubernetes#2408

@andrewloux andrewloux closed this Apr 25, 2018
@andrewloux andrewloux deleted the buffered-backends-fix branch April 25, 2018 05:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants