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

feat: allow proxying readiness checks to the function #145

Conversation

LucasRoesler
Copy link
Member

@LucasRoesler LucasRoesler commented Oct 9, 2022

Allow setting an endpoint path for the function readiness check via an ENV variable function_ready_endpoint

When this value is set, the requests to /_/ready will execute an empty GET request with/to the configured endpoint. This allows the function authors to implement custom readiness check logic.

This custom request is checked after the the standard liveliness checks and the ConcurrencyLimiter check. For a completely custom readiness check, the function should be deployed with max_inflight == 0 and function_ready_endpoint to the custom path.

Motivation and Context

  • I have raised an issue to propose this change (required)

Resolved #144

How Has This Been Tested?

Additional unit tests have been added
Manually tested with a custom python function
image

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I've read the CONTRIBUTION guide
  • I have signed-off my commits with git commit -s
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@LucasRoesler LucasRoesler force-pushed the feat-allow-proxying-ready-check-to-function branch from 83d5ecf to 318d12d Compare October 10, 2022 12:44
Allow setting an endpoint path for the function readiness check via an
ENV variable `function_ready_endpoint`

When this value is set, the requests to `/_/ready` will execute an empty
GET request with/to the configured endpoint. This allows the function
authors to implement custom readiness check logic.

This custom request is checked _after_ the the standard liviness checks
and the ConcurrencyLimiter check. For a completely custom readiness
check, the function should be deployed with `max_inflight == 0` and
`function_ready_endpoint` to the custom path.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
@LucasRoesler LucasRoesler force-pushed the feat-allow-proxying-ready-check-to-function branch from 318d12d to fd43548 Compare October 10, 2022 12:51
@LucasRoesler LucasRoesler marked this pull request as ready for review October 10, 2022 12:51
@kevin-lindsay-1
Copy link

kevin-lindsay-1 commented Oct 10, 2022

I have tested this, and the functionality appears to work as expected.

I built a golang fn that had a readiness endpoint of /_/function/ready and simply serve it, with an init() function defined in my fn. Once the init fn completed, the readiness endpoint was made available, and began outputting 200s, and the function entered the ready state in k8s.

@kevin-lindsay-1
Copy link

kevin-lindsay-1 commented Oct 10, 2022

One note: the readiness endpoint seems to emit logs, which should probably be silent by default.

@kevin-lindsay-1
Copy link

kevin-lindsay-1 commented Oct 10, 2022

This is a priority to get merged for me, because if the gateway forwards traffic to a fn before it is initially ready, while the handler itself is not yet available in the fn to process the invocation, this would likely result in errors. This error case is similar to the Istio issue we previously solved for, where the gateway was sending traffic to the pod just a few milliseconds early.

@LucasRoesler
Copy link
Member Author

@kevin-lindsay-1 do you mean the error log or the proxy log line? I would have to add a bit more code to make that log line disappear because I am reusing the function proxy implementation, it just looks like any other request at the moment?

The error log line should never happen, if you are seeing that, then i am very curious for more details.

@LucasRoesler
Copy link
Member Author

On more thing, we already exclude the kube-probe from the logs

	// Exclude logging for health check probes from the kubelet which can spam
	// log collection systems.
	if !strings.HasPrefix(r.UserAgent(), "kube-probe") {
		log.Printf("%s %s - %s - ContentLength: %d", r.Method, r.RequestURI, res.Status, res.ContentLength)
	}

so ... it should be omitted if/when i pass the headers along

@kevin-lindsay-1
Copy link

kevin-lindsay-1 commented Oct 11, 2022

I'm pretty sure I was seeing the proxy log line. Looked like the one you in the code example above.

I'd be fine with a follow-up patch, if I can confirm the behavior. It didn't seem to do it after the initial start, so it might have been an "error" due to not passing initial readiness.

When proxying the readiness request to the upstream function, we should
copy the original request headers. This allows the handler to use
headers, if needed. It also allows the of-watchdog to suppress the noisy
request logging for readiness checks.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
@LucasRoesler
Copy link
Member Author

I'm pretty sure I was seeing the proxy log line. Looked like the one you in the code example above.

I'd be fine with a follow-up patch, if I can confirm the behavior. It didn't seem to do it after the initial start, so it might have been an "error" due to not passing initial readiness.

Pretty easy to pass the headers along, i just pushed a followup commit to copy them from the original request, this should now include the User-Agent and allow the proxy log to be suppressed (if the user agent contains "kube-probe")

README.md Outdated Show resolved Hide resolved
// otherwise it will just route to `/`, typically this shouldn't be used or set
readyReq.RequestURI = r.endpoint
readyReq.Header = req.Header.Clone()
r.functionHandler.ServeHTTP(w, readyReq)
Copy link
Member

Choose a reason for hiding this comment

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

Why wouldn't we http.DefaultClient.Do this?

Copy link
Member Author

Choose a reason for hiding this comment

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

That requires re-parsing all of the config options to build the correct URL etc. The existing function handler already exists and has all of the desired configuration and URL parsing/construction.

I think it also matches the semantics of the feature:

"during readiness, invoke the function in a certain way"

This code matches this semantic directly because we pass down the function invoker (i.e. handler) and then invoke it.

Copy link
Member Author

Choose a reason for hiding this comment

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

@alexellis the doc strings have been added and it also ensures the basic unwrapped handler is used in the readiness check.

User read_path instead of function_ready_endpoint. This should be
clearer that it needs to be a path on the function and match prior art
in other projects.

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
Document why we use the base function handler to provide custom ready
checks to all function modes. Additionally, make sure that the unwrapped
function handler is passed to the readiness check. This ensures that
readiness checks do not count toward the concurrency limit

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
@@ -65,7 +65,16 @@ func main() {
os.Exit(1)
}

requestHandler := buildRequestHandler(watchdogConfig, watchdogConfig.PrefixLogs)
if watchdogConfig.ReadyEndpoint != "" {
Copy link
Member

Choose a reason for hiding this comment

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

I think we can omit this because it will generate a lot of logs over the course of a year

@alexellis
Copy link
Member

Hi @LucasRoesler I did some more testing on this and wanted to share the results:

go build && mode=serializing ready_path=/ready  upstream_url="http://127.0.0.1:8888" fprocess="env" ./of-watchdog 

curl localhost:8080/_/ready

Screenshot from 2022-10-14 10-28-03

alexellis pushed a commit that referenced this pull request Oct 14, 2022
Document why we use the base function handler to provide custom ready
checks to all function modes. Additionally, make sure that the unwrapped
function handler is passed to the readiness check. This ensures that
readiness checks do not count toward the concurrency limit

Review by AE - reverts change to faas-middleware, by introducing an
interface.

Closes: #145

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
@alexellis
Copy link
Member

I have a fix for this, it wasn't a new issue in a new PR along with your commits (for you to get credit), but was possibly triggered by the changes.

Since you may want this PR to count towards hacktoberfest, feel free to open a PR to the README file to update how this feature works and I'll merge that after.

alexellis pushed a commit that referenced this pull request Oct 14, 2022
Document why we use the base function handler to provide custom ready
checks to all function modes. Additionally, make sure that the unwrapped
function handler is passed to the readiness check. This ensures that
readiness checks do not count toward the concurrency limit

Review by AE - reverts change to faas-middleware, by introducing an
interface.

Closes: #145

Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Function readiness check
3 participants