-
Notifications
You must be signed in to change notification settings - Fork 5.4k
extension: Add stateful session extension #18207
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
Changes from all commits
320f403
9342111
a084c5d
ed30909
8968f3c
007e5c3
b5c28fd
fd2e635
b33b703
47aae23
0b6b70b
22519a1
4271423
85bffee
4671ea3
ea01e0a
2baf32b
2d47b26
ff77ee8
fb1583b
4c353f3
07ddaaa
d955dfb
7c92aad
cac65a0
8f1e514
087992e
e735b2b
c410b5f
9e98452
23a800e
958b209
5ab21ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. | ||
|
|
||
| load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| api_proto_package( | ||
| deps = [ | ||
| "//envoy/config/core/v3:pkg", | ||
| "@com_github_cncf_udpa//udpa/annotations:pkg", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package envoy.extensions.filters.http.stateful_session.v3; | ||
|
|
||
| import "envoy/config/core/v3/extension.proto"; | ||
|
|
||
| import "udpa/annotations/status.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option java_package = "io.envoyproxy.envoy.extensions.filters.http.stateful_session.v3"; | ||
| option java_outer_classname = "StatefulSessionProto"; | ||
| option java_multiple_files = true; | ||
| option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3;stateful_sessionv3"; | ||
| option (udpa.annotations.file_status).package_version_status = ACTIVE; | ||
|
|
||
| // [#protodoc-title: Stateful session filter] | ||
| // Stateful session :ref:`configuration overview <config_http_filters_stateful_session>`. | ||
| // [#extension: envoy.filters.http.stateful_session] | ||
|
|
||
| message StatefulSession { | ||
| // Specific implementation of session state. This session state will be used to store and | ||
| // get address of the upstream host to which the session is assigned. | ||
| // | ||
| // [#extension-category: envoy.http.stateful_session] | ||
| config.core.v3.TypedExtensionConfig session_state = 1 | ||
| [(validate.rules).message = {required: true}]; | ||
| } | ||
|
|
||
| message StatefulSessionPerRoute { | ||
| oneof override { | ||
| option (validate.required) = true; | ||
|
|
||
| // Disable the stateful session filter for this particular vhost or route. If disabled is | ||
| // specified in multiple per-filter-configs, the most specific one will be used. | ||
| bool disabled = 1 [(validate.rules).bool = {const: true}]; | ||
|
|
||
| // Per-route stateful session configuration that can be served by RDS or static route table. | ||
| StatefulSession stateful_session = 2; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. | ||
|
|
||
| load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| api_proto_package( | ||
| deps = [ | ||
| "//envoy/type/http/v3:pkg", | ||
| "@com_github_cncf_udpa//udpa/annotations:pkg", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package envoy.extensions.http.stateful_session.cookie.v3; | ||
|
|
||
| import "envoy/type/http/v3/cookie.proto"; | ||
|
|
||
| import "udpa/annotations/status.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option java_package = "io.envoyproxy.envoy.extensions.http.stateful_session.cookie.v3"; | ||
| option java_outer_classname = "CookieProto"; | ||
| option java_multiple_files = true; | ||
| option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3;cookiev3"; | ||
| option (udpa.annotations.file_status).package_version_status = ACTIVE; | ||
|
|
||
| // [#protodoc-title: Cookie based stateful session extension] | ||
|
|
||
| // This extension allows the session state to be tracked via cookies. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking a bit about multiple hops. For example, client -> external LB (envoy) -> internal LB (envoy) -> app. I am assuming this would be broken if both envoy's configure this, right? Since both Envoys would have the cookie set but they would be configured the value to the host's of Is that right? I don't think there is any code changes here, just something users should be aware of. If we have this we can set a different cookie at each hop I think
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Thanks for calling it out. I will add some new comments as note. 😄 |
||
| // | ||
| // This extension first encodes the address of the upstream host selected by the load balancer | ||
| // into a `set-cookie` response header with the :ref:`cookie configuration | ||
| // <envoy_v3_api_field_extensions.http.stateful_session.cookie.v3.CookieBasedSessionState.cookie>`. | ||
| // when new requests are incoming, this extension will try to parse the specific upstream host | ||
| // address by the cookie name. If the address parsed from the cookie corresponds to a valid | ||
| // upstream host, this upstream host will be selected first. See :ref:`stateful session filter | ||
| // <envoy_v3_api_msg_extensions.filters.http.stateful_session.v3.StatefulSession>`. | ||
| // | ||
| // For example, if the cookie name is set to `sticky-host`, envoy will prefer `1.2.3.4:80` | ||
| // as the upstream host when the request contains the following header: | ||
| // | ||
| // .. code-block:: none | ||
| // | ||
| // cookie: sticky-host="MS4yLjMuNDo4MA==" | ||
|
wbpcode marked this conversation as resolved.
|
||
| // | ||
| // When processing the upstream response, if `1.2.3.4:80` is indeed the final choice the extension | ||
| // does nothing. If `1.2.3.4:80` is not the final choice, the new selected host will be used to | ||
| // update the cookie (via the `set-cookie` response header). | ||
| // | ||
| // [#extension: envoy.http.stateful_session.cookie] | ||
| message CookieBasedSessionState { | ||
| // The cookie configuration used to track session state. | ||
| type.http.v3.Cookie cookie = 1 [(validate.rules).message = {required: true}]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package envoy.type.http.v3; | ||
|
|
||
| import "google/protobuf/duration.proto"; | ||
|
|
||
| import "udpa/annotations/status.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option java_package = "io.envoyproxy.envoy.type.http.v3"; | ||
| option java_outer_classname = "CookieProto"; | ||
| option java_multiple_files = true; | ||
| option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/http/v3;httpv3"; | ||
| option (udpa.annotations.file_status).package_version_status = ACTIVE; | ||
|
|
||
| // [#protodoc-title: Http cookie API] | ||
|
|
||
| // Cookie defines an API for obtaining or generating HTTP cookie. | ||
| message Cookie { | ||
| // The name that will be used to obtain cookie value from downstream HTTP request or generate | ||
| // new cookie for downstream. | ||
| string name = 1 [(validate.rules).string = {min_len: 1}]; | ||
|
|
||
| // Duration of cookie. This will be used to set the expiry time of a new cookie when it is | ||
| // generated. Set this to 0 to use a session cookie. | ||
| google.protobuf.Duration ttl = 2 [(validate.rules).duration = {gte {}}]; | ||
|
|
||
| // Path of cookie. This will be used to set the path of a new cookie when it is generated. | ||
| // If no path is specified here, no path will be set for the cookie. | ||
| string path = 3; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| Stateful Session | ||
| ===================== | ||
|
|
||
| .. toctree:: | ||
| :glob: | ||
| :maxdepth: 2 | ||
|
|
||
| ../../extensions/http/stateful_session/*/v3/* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,3 +46,4 @@ HTTP filters | |
| sxg_filter | ||
| tap_filter | ||
| wasm_filter | ||
| stateful_session_filter | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| .. _config_http_filters_stateful_session: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should include a note in this documentation that this type of sticky session is an anti-pattern and that operators should avoid using this feature when possible?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this function is not that special, and there are similar functions among other proxies. But I can add a note to let users use this feature with caution. Similar function in other proxy: https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#enabling-session-persistence
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add it. Something like: |
||
|
|
||
| Stateful session | ||
| ================ | ||
|
|
||
| Stateful session is an HTTP filter which overrides the upstream host based on extensible session state | ||
| and updates the session state based on the final selected upstream host. The override host will | ||
| eventually overwrites the load balancing result. This filter implements session stickiness without using | ||
| a hash-based load balancer. | ||
|
|
||
| And by extending the session state, this filter also allows more flexible control over the results of | ||
|
wbpcode marked this conversation as resolved.
|
||
| the load balancing. | ||
|
|
||
| .. note:: | ||
|
|
||
| Stateful sessions can result in imbalanced load across upstreams and allow external actors to direct | ||
| requests to specific upstream hosts. Operators should carefully consider the security and reliability | ||
| implications of stateful sessions before enabling this feature. | ||
|
|
||
| Overview | ||
| -------- | ||
|
|
||
| Session stickiness allows requests belonging to the same session to be consistently routed to a specific | ||
| upstream host. | ||
|
|
||
| HTTP session stickiness in Envoy is generally achieved through hash-based load balancing. | ||
| The stickiness of hash-based sessions can be regarded as 'weak' since the upstream host may change when the | ||
| host set changes. This filter implements 'strong' stickiness. It is intended to handle the following cases: | ||
|
|
||
| * The case where more stable session stickiness is required. For example, when a host is marked as degraded | ||
| but it is desirable to continue routing requests for existing sessions to that host. | ||
| * The case where a non hash-based load balancer (Random, Round Robin, etc.) is used and session stickiness | ||
| is still required. If stateful sessions are enabled in this case, requests for new sessions will be routed | ||
| to the corresponding upstream host based on the result of load balancing. Requests belonging to existing | ||
| sessions will be routed to the session's upstream host. | ||
|
|
||
| Configuration | ||
| ------------- | ||
|
|
||
| * :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.http.stateful_session.v3.StatefulSession>` | ||
| * This filter should be configured with the name *envoy.filters.http.stateful_session*. | ||
|
|
||
| How it works | ||
| ------------ | ||
|
|
||
| The most important configuration for this filter is an :ref:`extensible session state | ||
| <envoy_v3_api_field_extensions.filters.http.stateful_session.v3.StatefulSession.session_state>`. | ||
|
|
||
| While processing the request, the stateful session filter will search for the corresponding session and | ||
| host based on the request. The results of the search will be used to influence the final load balancing | ||
| results. | ||
|
|
||
| If no existing session is found, the filter will create a session to store the selected upstream host. | ||
| Please note that the session here is an abstract concept. The details of the storage are based on the | ||
| session state implementation. | ||
|
|
||
| One example | ||
| ___________ | ||
|
|
||
| Currently, only :ref:`cookie-based session state | ||
| <envoy_v3_api_msg_extensions.http.stateful_session.cookie.v3.CookieBasedSessionState>` is supported. | ||
| So let's take this as an example. | ||
|
|
||
| .. code-block:: yaml | ||
|
|
||
| name: envoy.filters.http.stateful_session | ||
| typed_config: | ||
| "@type": type.googleapis.com/envoy.extensions.filters.http.stateful_session.v3.StatefulSession | ||
| session_state: | ||
| name: envoy.http.stateful_session.cookie | ||
| typed_config: | ||
| "@type": type.googleapis.com/envoy.extensions.http.stateful_session.cookie.v3.CookieBasedSessionState | ||
| name: global-session-cookie | ||
| path: /path | ||
| ttl: 120s | ||
|
|
||
|
|
||
| In the above configuration, the cookie-based session state obtains the overridden host of the current session | ||
| from the cookie named `global-session-cookie` and if the corresponding host exists in the upstream cluster, the | ||
| request will be routed to that host. | ||
|
|
||
| If there is no valid cookie, the load balancer will choose a new upstream host. When responding, the address | ||
| of the selected upstream host will be stored in the cookie named `global-session-cookie`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| #include "envoy/router/router.h" | ||
| #include "envoy/ssl/connection.h" | ||
| #include "envoy/tracing/http_tracer.h" | ||
| #include "envoy/upstream/load_balancer.h" | ||
| #include "envoy/upstream/upstream.h" | ||
|
|
||
| #include "source/common/common/scope_tracked_object_stack.h" | ||
|
|
@@ -610,6 +611,19 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks { | |
| */ | ||
| virtual void | ||
| requestRouteConfigUpdate(RouteConfigUpdatedCallbackSharedPtr route_config_updated_cb) PURE; | ||
|
|
||
| /** | ||
| * Set override host to be used by the upstream load balancing. If the target host exists in the | ||
| * host list of the routed cluster, the host should be selected first. | ||
| * @param host The override host address. | ||
| */ | ||
| virtual void setUpstreamOverrideHost(absl::string_view host) PURE; | ||
|
|
||
| /** | ||
| * @return absl::optional<absl::string_view> optional overrride host for the upstream | ||
| * load balancing. | ||
| */ | ||
| virtual absl::optional<absl::string_view> upstreamOverrideHost() const PURE; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need this getter method on the decoder callbacks? Generally we want to keep this interface as minimal as possible as every filter implements it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what you mean. Only |
||
| }; | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.