-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 lifecycle hooks and readiness checks for smoother autoscaling #670
Conversation
/retest |
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. I am super excited to see some of this instability put to rest :)
cmd/ela-queue/main.go
Outdated
mutex sync.RWMutex | ||
} | ||
|
||
// isAlive() returns true iff a PreStop hook has not been called. |
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.
I think this is clearer: // isAlive() returns true until a PreStop hook has been called.
cmd/ela-queue/main.go
Outdated
func (h *healthServer) kill() { | ||
h.mutex.Lock() | ||
h.alive = false | ||
defer h.mutex.Unlock() |
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.
nit: Generally when defer
is used it's right after the Lock()
. If you leave it here, you may as well drop the defer
.
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.
Done
cmd/ela-queue/main.go
Outdated
// the same time the pod is marked for removal. | ||
func (h *healthServer) quitHandler(w http.ResponseWriter, r *http.Request) { | ||
h.kill() | ||
time.Sleep(quitSleepSecs * time.Second) |
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.
- You never write back a response?
- Can you explain the ordering for the kill / delay? Why does this work? Why do you hold the connection open for 20 seconds after you start responding to health checks with non-200?
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.
Done
cmd/ela-queue/main.go
Outdated
defer h.mutex.Unlock() | ||
} | ||
|
||
// healthHandler is used for readinessProbe of queue-proxy. |
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.
readiness vs. liveness?
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.
Done
cmd/ela-queue/main.go
Outdated
|
||
// Add a SIGTERM handler to gracefully shutdown the servers during | ||
// pod termination. | ||
var sigTermChan = make(chan os.Signal) |
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.
sigTermChan := make(...)
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.
Done
pkg/controller/revision/revision.go
Outdated
// Queue proxy admin port and paths provide health check and | ||
// lifecycle hooks. | ||
requestQueueAdminPortName string = "queueadm-port" | ||
requestQueueAdminPort = 8022 |
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.
Can we make this public and use this constant from //cmd/ela-queue/main.go
? Same for 8012
above.
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.
Done
pkg/controller/revision/ela_pod.go
Outdated
@@ -59,13 +87,41 @@ func MakeElaPodSpec(u *v1alpha1.Revision, queueSidecarImage string) *corev1.PodS | |||
}, | |||
}, | |||
Ports: []corev1.ContainerPort{ | |||
// TOOD: HTTPS connections from the Cloud LB require | |||
// TODO: HTTPS connections from the Cloud LB require |
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.
we no longer use nginx, so perhaps this comment should simply be removed?
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.
Done
pkg/controller/revision/ela_pod.go
Outdated
elaContainer.Lifecycle = &corev1.Lifecycle{} | ||
} | ||
if elaContainer.Lifecycle.PreStop == nil { | ||
elaContainer.Lifecycle.PreStop = &corev1.Handler{ |
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.
Can you explain what this is doing in a detailed comment?
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.
Your other comments are great, thanks!
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.
Done
pkg/controller/revision/ela_pod.go
Outdated
if elaContainer.Lifecycle == nil { | ||
elaContainer.Lifecycle = &corev1.Lifecycle{} | ||
} | ||
if elaContainer.Lifecycle.PreStop == nil { |
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.
Here and above, we should simply disallow user-provided lifecycle hooks.
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.
Done
if p.Handler.HTTPGet == nil { | ||
return false | ||
} | ||
return p.Handler.HTTPGet.Path != "" |
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.
Should we also check that it's on the expected port?
I'm wondering if we should simply disallow users from specifying port in the readiness check, since the port will be implied by our runtime contract.
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.
I think we should disallow setting the port here. I'm putting TODOs to follow up with webhook changes.
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.
Open issues for each please :)
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.
(1) Adding readinessProbes and PreStop hooks, and (2) Gracefully shutting down queue HTTP server.
/retest |
if p.Handler.HTTPGet == nil { | ||
return false | ||
} | ||
return p.Handler.HTTPGet.Path != "" |
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.
Open issues for each please :)
pkg/controller/revision/ela_pod.go
Outdated
Name: RequestQueuePortName, | ||
ContainerPort: int32(RequestQueuePort), | ||
}, | ||
// Provides health checks and life cycle hooks. |
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.
nit: lifecycle
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.
Done
/lgtm |
/hold for comments |
/hold |
/lgtm |
@josephburnett can you please give feedback/approval? thanks |
We have an issue to make sure sidecar injection works properly or else Pod creation would fail (#379). Such would insulate us in case our changes here (to Istio injector configuration) break sidecar injection. |
/approve |
/hold |
Sorry I thought that was asking me :) |
I'm still good with this, deferring to @josephburnett for final approval. Thanks for the improvements to availability :) |
io.WriteString(w, "alive: false") | ||
} | ||
|
||
// Sets up /health and /quitquitquit endpoints. |
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.
Does this mean that any IP on the Internet can send a bunch of requests to /quitquitquit and bring down a revision?
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.
No, this is not exposed like :8012. I believe only a local prober can access it.
@@ -143,15 +154,92 @@ func statReporter() { | |||
} | |||
} | |||
|
|||
func isProbe(r *http.Request) bool { | |||
// Since K8s 1.8, prober requests have | |||
// User-Agent = "kube-probe/{major-version}.{minor-version}". |
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.
Can this User-Agent be spoofed? Can any IP on the Internet send a bunch of requests to with this User-Agent and overwhelm pods without causing autoscaling to kick in?
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.
I think this is a valid concern. I'll open an issue to find out if we can avoid.
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.
Just a few questions. Looks good!
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mattmoor, tcnghia The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/retest |
* Reduce autoscaling-related 503s by: (1) Adding readinessProbes and PreStop hooks, and (2) Gracefully shutting down queue HTTP server. * Address PR feedback. * Exclude K8s prober requests from autoscaling consideration. * Clarifying why we added flag.Parse().
Fixes knative/pkg#2026 The actual issue is that the test context expires between individual stages run by the upgrade framework. This fix passes an external logger that survives the stages.
Fixes knative/pkg#2026 The actual issue is that the test context expires between individual stages run by the upgrade framework. This fix passes an external logger that survives the stages.
* Fix race condition with Prober logger in upgrade tests (knative#670) Fixes knative/pkg#2026 The actual issue is that the test context expires between individual stages run by the upgrade framework. This fix passes an external logger that survives the stages. * Only use exec probe at startup time (knative#10741) * Only use exec probe at startup time Now that StartupProbe is available, we can avoid using spawning the exec probe other than at startup time. For requests after startup this directly uses the same endpoint as the exec probe in the QP as the target of a HTTP readiness probe. Following on from this I think we may want to rework quite a bit of how our readiness probe stuff works (e.g. it'd be nice to keep the probes on the user container so failures are on the right object, and we currently ignore probes ~entirely after startup if periodSeconds>0), but this is a minimal change that should be entirely backwards-compatible and saves quite a few cpu cycles. * Use ProgressDeadline as failure timeout for startup probe - Also just drop exec probe entirely for periodSeconds > 1 since these can just use the readiness probe now. (Easier than figuring out how to do square ProgressDeadline with a custom period). * See if flag is what's making upgrades unhappy * reorganize comments * Default PeriodSeconds of the readiness probe to 1 if unset (knative#10992) Co-authored-by: Martin Gencur <[email protected]> Co-authored-by: Julian Friedman <[email protected]>
Fixes #429
Proposed Changes
(1) Adding readinessProbes and PreStop hooks and
(2) Gracefully shutting down queue HTTP server.
I tested this change on a cluster having 24 nodes, with 500 clients gradually ramping up in 100 seconds. That generated about 170K requests.
Before: 4K 503 errors out of 170K requests.
After: 0 error out of 170K requests.