-
Notifications
You must be signed in to change notification settings - Fork 269
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
Add better handling for cookie affinity with preserve option #667
Add better handling for cookie affinity with preserve option #667
Conversation
Thank you for the kind words and for supporting this humble controller. Btw thank you also for such a detailed description and a carefully built pr. Sorry about the long delay, I'll need a few more days to have a look in the changes. In the mean time there is some suggestions I'd like to share:
Regarding a contributions guideline it's a concept being built along the years writing the code but never translated to words. Just to point an example, the git commit title and message has about one week - this is both a common practice and I observed that gitlab doesn't break lines in the commit preview, so we need to do it ourselves. I need to stop a few days to revise some docs for new users and new contributors, this should help in the visibility of the project. |
Thanks for the guidelines! I can definitely follow those. Did you want me to reconstruct the commits of this pull request directly by rewriting the history, or do you want me to make a new branch and PR with the rewritten commits? Some projects are ok with rewriting pull request branches but I just wanted to check with you first. |
No, lets make it simple. Just a |
To access fields other than the name in an empty endpoint, temporarily store the object itself, rather than just the name string.
Currently, the CookieValue will just be set to Name when cookie affinity is enabled, which is how it already works. Separating CookieValue out to a different field allows us to add future logic which will compute the CookieValue differently from the name.
The 'session-cookie-preserve` annotation is able to be used when cookie affinity is enabled. The purpose is to be able to intelligently control dynamic updating in such a way where it avoids causing an update that would make the cookie value for a server no longer match an Enabled server (mismatch on disabling a server is fine). The reason cookie values matching their assigned backend is important, is because the "preserve" mode of HAProxy allows servers to emit a "Set-Cookie" header to effectively assign their own server. Because of this, the specific cookie value assigned to each backend server must be consistent and known by both HAProxy as well as the backend server itself. This change makes usage of "preserve" mode in "session-cookie-keywords" no longer (or at least, less) desirable, but it should still be allowed for backward compatibility and maximum customizability, therefore a warning will simply be logged when it is used in that way.
This new backend annotation allows the way that a cookie value for a backend server is calculated to be controlled more precisely. Prior to this, the value of a cookie for a backend server was always set to the Name of an endpoint, which was in turn controlled by 'backend-server-naming'. While that gives a few options for changing the cookie value, the downside was that it required changing the name of the server. Additionally, there are possible cookie values which would make poor backend server names (such as a guid/uuid) but may make good cookie values. The new annotation defaults to setting the naming strategy to the Name of the server, which is the existing behaviour. In addition, it adds the ability to set the value to a Pod UID.
f3c149d
to
379a859
Compare
Done. When recreating the commits, to make things simple, the following changed from the original submission:
|
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.
Thanks again for the contribution and sorry about the long delay. I needed to familiarize myself again with sticky session related options. Below has a few minor suggestions and doubts.
|
||
if strings.Contains(d.backend.Cookie.Keywords, "preserve") { | ||
// just warn, no error, for keeping backwards compatibility where "preserve" may have been used in the "keywords" section | ||
c.logger.Warn("session-cookie-keywords contains 'preserve'; consider using 'session-cookie-preserve' instead for better dynamic update cookie persistence") |
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.
Add the source of the annotation here, something like "session-cookie-keywords on %s contains 'preserve'...". d.mapper.Get(...).Source
has the source in string format and %s
does not need to be quoted. This way one can easily find its ingress or service on a large k8s cluster. It'd suggest also to extract to a local var and use .Bool()
and Source
from there.
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.
Implemented in 474d3c5
By .Bool()
I am thinking you meant just .Value
as it's a string value. Let me know if I misinterpreted that part.
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.
Sure, yes, .Value
. I did a little mess with session-cookie-preserve
which would use .Bool()
.
case "server-name": | ||
d.backend.EpCookieStrategy = hatypes.EpCookieName | ||
default: | ||
c.logger.Warn("invalid session-cookie-value-strategy '%s', using 'server-name' instead", cookieStrategy) |
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.
Add source here as well.
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.
Implemented in e7cf7f5
pkg/converters/ingress/ingress.go
Outdated
if err == nil { | ||
ep.CookieValue = fmt.Sprintf("%v", pod.UID) | ||
} else { | ||
c.logger.Error("error calculating cookie value for pod %s: %v", ep.TargetRef, err) |
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.
backend.AcquireEndpoint()
doesn't fill CookieValue, shouldn't be assigned to ep.Name in the case of an error reading the pod UID?
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.
Implemented in 0d58edb
ep.CookieValue = ep.Name | ||
case hatypes.EpCookiePodUid: | ||
if ep.TargetRef != "" { | ||
pod, err := c.cache.GetPod(ep.TargetRef) |
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.
Update --disable-pod-list
command-line doc that the pod list is also being used by session-cookie-preserve as well.
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.
Implemented in 099b1f6
Thank you for the review. I'll check it out and address the comments in a couple days. |
LGTM, thanks! Merging. |
First off, wanted to say, great project and I appreciate you making this open source and all your hard work maintaining it. I didn't see a contributors file but I've attempted to make this submission as complete as possible. If you are interested in including these changes but want me to make any modifications, please let me know and I'd be happy to accommodate.
The use case that I had for adding these is when using the "preserve" option of cookie affinity, I want to allow the servers to assign their own cookie using Set-Cookie, and have haproxy simply route it to the server specified by a Set-Cookie header. An example may be something like a MMO server where there is a load balancer in front of many servers, but the users character should always be routed to the server their character exists on after they log in. This requires the servers to specify what cookie value is set, and it requires that cookie value to always be valid; it will break if a dynamic update was to reassign things in such a way that the cookie matched with a server changes.
This is supported by haproxy via the "preserve" option, which preserves the cookie set by the server.
Obviously for this to work properly, the servers as well as the ingress both need to agree on what the cookie values for a particular server are, which is easy to do as long as you use something accessible via the kubernetes Downward API, such as a pod name, or in the case I wanted, a pod UID.
balance
mode.Set-Cookie
to change the server, eg. say if it was a MMO server that wanted to redirect the client to which server their character is on after they log in.Without any changes, this doesn't work when dynamic updating is on because the cookie will just be something like
srv001
until reloading. By making the dynamic updater understand when "preserve" cookie mode is enabled, we can tell it to not do a dynamic update (and do a reload instead) if it would cause a cookie value mismatch, fixing this, while allowing dynamic updates to still be used if the cookie matches.Changes:
session-cookie-value-strategy
which allows changing how the cookie value is calculated. Right now it supports choosing between naming the cookie value as the endpoint Name (the default, and current behavior), and a pod UID.session-cookie-preserve
which setspreserve
option on the cookie. This was already achievable by settingpreserve
as part ofsession-cookie-keywords
, however there wasn't a good way to make special behavior for it such as having different logic for dynamic updates without disabling dynamic updating altogether, and the existing documentation wasn't totally clear that dynamic updating breaks cookies when used like this. With this option, when dynamic updating, it knows to reload if a cookie value change is inevitable, making sure that servers are always assigned the correct cookie.The changes in here don't affect backwards compatibility and aren't breaking:
Tested:
make test
all pass