update-agent: wait for pods to terminate before rebooting#137
Conversation
|
Can one of the admins verify this patch? |
e6180d4 to
8376483
Compare
|
Just a friendly ping on this |
dghubble
left a comment
There was a problem hiding this comment.
Minor tweaks related to polling interval, timeout, and not terminating on transient errors.
|
|
||
| // waitForPodDeletion waits for a pod to be deleted | ||
| func (k *Klocksmith) waitForPodDeletion(pod v1.Pod) error { | ||
| return wait.PollInfinite(time.Second, func() (bool, error) { |
There was a problem hiding this comment.
Let's not poll every second. We already have issues filed about CLUO having too aggressive call rates. Other code in agent polls every 10 seconds.
const reapInterval = 10 * time.Second
...
wait.PollUntil(reapInterval, func() (bool, error) {...})
Forever is a long time to wait as well. We should probably set a sane maximum time we're willing to wait for a troublesome pod before proceeding with the shutdown anyway (e.g. 10 min). Otherwise one app developer with a misbehaving app and an unreasonable grace period will stall Container Linux updates for the cluster.
const (
reapInterval = 10 * time.Second
reapTimeout = 10 * time.Minute
)
...
wait.Poll(reapInterval, reapTimeout, func() (bool, error) {...})
There was a problem hiding this comment.
Any opposition to making the reap timeout configurable? 10 minutes seems reasonable for most use cases but there are some that require a much longer timeout. Specifically, some of our pods running in our cluster can take up to 24 hours to terminate gracefully in certain scenarios.
There was a problem hiding this comment.
I'm not greatly in favor of new flags, unless really needed. Apps that require 24 hours to terminate gracefully, on clusters designed around the idea that individual nodes can go down and workloads move flexibly?
There was a problem hiding this comment.
If you've been using the original PR on clusters (and we mentioned that on any transient call error, the wait would exit), it sounds like your apps are already subject to ungraceful shutdown?
There was a problem hiding this comment.
We haven't been using it on our clusters for the very reason this PR was opened. The fact that pods don't terminate gracefully before reboot showed up in our testing prior to rolling this out.
Yep, it's most definitely not ideal that these jobs can take so long (most of the time they don't) and it's something that is being worked on but we have to work around it for the time being.
24 hours is most definitely an outlier but I'd argue that 11 minutes isn't. 15 minutes still sounds pretty reasonable to me depending on what's running in the cluster. The point I'm trying to make is that the reap timeout is heavily dependent on the type of workload that runs on your cluster. Because it could be so variable, I'd say it's worth making into a flag.
There was a problem hiding this comment.
Ah sorry, misunderstood that last question. We’ve used this PR on our test cluster running a similar workload but with not nearly as long of running.
There was a problem hiding this comment.
Hm, I'm not excited to add config flags (flags cascade into higher-level components), but I can see a --grace-period flag on update-agent (mirroring the --grace-period flag of kubectl drain) being warranted here. It does also set expectations that there has to be some period of time beyond which we give up on waiting (i.e. its best-effort, we can't guarantee we'll satisfy every pod grace period).
| return false, nil | ||
| case errors.IsNotFound(err): | ||
| return true, nil | ||
| default: |
There was a problem hiding this comment.
Handling this more naively could improve logs and retry. Compared with logging at the top level where the wait return error is just a loose approximation of what happened.
_, err := k.kc.CoreV1().Pods(pod.Namespace).Get(pod.Name, v1meta.GetOptions{})
if errors.IsNotFound(err) {
glog.Infof("deleted pod %s", pod.Name) // success log
return true, nil
}
if err != nil {
glog.Errorf("failed to get pod %v", err) // some API call error, likely transient
}
return false, nil
So then success looks like:
Waiting for pod POD to terminate
failed to get pod: transient error
failed to get pod: transient error
deleted pod POD
And "failure" gets logged (by the caller) as skipping the wait.
Waiting for pod POD to terminate
failed to get pod: transient error
failed to get pod: transient error
Skipping wait on pod POD: timeout error
| for _, pod := range pods { | ||
| glog.Infof("Waiting for pod %q to terminate", pod.Name) | ||
| if err := k.waitForPodDeletion(pod); err != nil { | ||
| glog.Errorf("failed waiting for pod %q to terminate: %v", pod.Name, err) |
There was a problem hiding this comment.
In practice, many of these log lines will be about transient errors to make API calls and we shouldn't stop the wait for them (e.g. "failed waiting for pod xx to terminate: can't reach 10.3.0.1"). Other uses of wait in agent allow the anon function to log errors and return nil to continue trying.
There was a problem hiding this comment.
On timeout, we can say we're skipping waiting for the pod to terminate.
|
@dghubble feedback implemented. thanks! |
|
Just a comment on this. I am using Rook (a Ceph based cluster filesystem) and failed with locksmith and this PR to reboot updated nodes. |
|
@FaKod If graceful termination of a pod is dependent on the order in which other pods are terminated, this PR won't help much. That being said, the agent shouldn't hang with the changes I pushed ~6 hours ago. The agent should timeout after 10 minutes of waiting for a pod to terminate. Did you try with that version? If so, could you send me the agent logs? If not, could you try with that version? My testing proved successful in our test cluster. |
|
@hankjacobs In case of rook: Although pods are terminated eventually, the mounts are still valid on the node. That prevents a node from restarting. See rook/rook#1018 This may be a specific issue of Rook. What would help is to call "drain" before and wait for the non DaemonSet Pods to terminate. |
|
This is effectively a "drain" as implemented by kubectl (albeit a rather simplified version -- doesn't use the eviction APIs). The hanging you saw was an issue with my original implementation. That should be fixed with the current version of this PR. |
|
Additionally, DaemonSet pods are skipped. To me, this points to something with your setup. This is outside the scope of this PR but I'd double check that you're running rook as a DS. |
|
Right, its a ReplicaSet (created by an Operator) - sorry for the confusion. So I'll issue a feature request to exclude some Resources from deletion by labels or so. |
| nc v1core.NodeInterface | ||
| ue *updateengine.Client | ||
| lc *login1.Conn | ||
| ri time.Duration |
There was a problem hiding this comment.
remove, this isn't customized anywhere
| ue *updateengine.Client | ||
| lc *login1.Conn | ||
| ri time.Duration | ||
| rt time.Duration |
There was a problem hiding this comment.
Can you name this reapTimeout or gracePeriod? No need to emulate these (poor imo) existing field names
| rt time.Duration | ||
| } | ||
|
|
||
| const reapInterval = 10 * time.Second |
There was a problem hiding this comment.
Let's have this be defaultInterval or something more general to replace the other occurrences of 10 second intervals
| } | ||
|
|
||
| return &Klocksmith{node, kc, nc, ue, lc}, nil | ||
| return &Klocksmith{node, kc, nc, ue, lc, reapInterval, reapTimeout}, nil |
There was a problem hiding this comment.
No need to configure with the constant
|
adding the |
This change makes the agent wait until all pods are terminated before rebooting. Prior to this change, the agent would reboot immediately after the pod delete request was made and, consequently, pods were not given the time needed to terminate gracefully.
…en waiting for pod deletion
8f1b849 to
8015b10
Compare
|
nits picked and an image to go with it: quay.io/dollarshaveclub/container-linux-update-operator:8015b109a973f0e8497ae894b90f66b6e47e5455-dirty |
This change makes the agent wait until all pods are terminated before rebooting. Prior to this change, the agent would reboot immediately after the pod delete request was made and, consequently, pods were not given the time needed to terminate gracefully.