mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-21 08:23:26 +02:00
Retry moveMasterPodsOffNode on failure instead of aborting (#3179)
attemptToMoveMasterPodsOffNode's transient errors (e.g. no synced standby available yet) were treated as terminal, so the retry loop gave up after a single ~4-5s attempt instead of retrying over master_pod_move_timeout. This left masters stuck on draining nodes indefinitely once a PodDisruptionBudget started rejecting evictions. Log the failure and return (false, nil) so the retry loop keeps polling every minute until the timeout expires. Fixes #3176
This commit is contained in:
@@ -556,9 +556,10 @@ configuration they are grouped under the `kubernetes` key.
|
|||||||
* **master_pod_move_timeout**
|
* **master_pod_move_timeout**
|
||||||
The period of time to wait for the success of migration of master pods from
|
The period of time to wait for the success of migration of master pods from
|
||||||
an unschedulable node. The migration includes Patroni switchovers to
|
an unschedulable node. The migration includes Patroni switchovers to
|
||||||
respective replicas on healthy nodes. The situation where master pods still
|
respective replicas on healthy nodes. A failed migration attempt is retried
|
||||||
exist on the old node after this timeout expires has to be fixed manually.
|
every minute until this timeout expires. The situation where master pods
|
||||||
The default is 20 minutes.
|
still exist on the old node after this timeout expires has to be fixed
|
||||||
|
manually. The default is 20 minutes.
|
||||||
|
|
||||||
* **enable_pod_antiaffinity**
|
* **enable_pod_antiaffinity**
|
||||||
toggles [pod anti affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/)
|
toggles [pod anti affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/)
|
||||||
|
|||||||
@@ -156,7 +156,8 @@ func (c *Controller) moveMasterPodsOffNode(node *v1.Node) {
|
|||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
err := c.attemptToMoveMasterPodsOffNode(node)
|
err := c.attemptToMoveMasterPodsOffNode(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
c.logger.Warningf("attempt to move master pods off node %q failed, will retry: %v", node.Name, err)
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
logrustest "github.com/sirupsen/logrus/hooks/test"
|
||||||
"github.com/zalando/postgres-operator/v2/pkg/spec"
|
"github.com/zalando/postgres-operator/v2/pkg/spec"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
k8stesting "k8s.io/client-go/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -93,3 +100,32 @@ func TestNodeIsReady(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMoveMasterPodsOffNodeRetriesOnError ensures a failed attempt to move
|
||||||
|
// master pods off a node is retried rather than aborting the whole retry
|
||||||
|
// loop on the first error.
|
||||||
|
func TestMoveMasterPodsOffNodeRetriesOnError(t *testing.T) {
|
||||||
|
clientSet := fake.NewSimpleClientset()
|
||||||
|
clientSet.PrependReactor("list", "pods", func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, nil, fmt.Errorf("could not list pods")
|
||||||
|
})
|
||||||
|
|
||||||
|
controller := newNodeTestController()
|
||||||
|
controller.KubeClient.PodsGetter = clientSet.CoreV1()
|
||||||
|
// timeout == the retry interval hardcoded in moveMasterPodsOffNode, so
|
||||||
|
// the single retry attempt resolves synchronously without a real sleep.
|
||||||
|
controller.opConfig.MasterPodMoveTimeout = &metav1.Duration{Duration: 1 * time.Minute}
|
||||||
|
|
||||||
|
logger, hook := logrustest.NewNullLogger()
|
||||||
|
controller.logger = logger.WithField("pkg", "controller")
|
||||||
|
|
||||||
|
controller.moveMasterPodsOffNode(makeNode(map[string]string{}, false))
|
||||||
|
|
||||||
|
lastEntry := hook.LastEntry()
|
||||||
|
if lastEntry == nil {
|
||||||
|
t.Fatal("expected moveMasterPodsOffNode to log a warning")
|
||||||
|
}
|
||||||
|
if !strings.Contains(lastEntry.Message, "still failing after") {
|
||||||
|
t.Errorf("expected the retry loop to run out of attempts instead of aborting on the first error, got log message: %q", lastEntry.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user