-
Notifications
You must be signed in to change notification settings - Fork 323
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Set leave_on_terminate=true for servers and hardcode maxUnavailable=1
When leave_on_terminate=false (default), rolling the statefulset is disruptive because the new servers come up with the same node IDs but different IP addresses. They can't join the server cluster until the old server's node ID is marked as failed by serf. During this time, they continually start leader elections because they don't know there's a leader. When they eventually join the cluster, their election term is higher, and so they trigger a leadership swap. The leadership swap happens at the same time as the next node to be rolled is being stopped, and so the cluster can end up without a leader. With leave_on_terminate=true, the stopping server cleanly leaves the cluster, so the new server can join smoothly, even though it has the same node ID as the old server. This increases the speed of the rollout and in my testing eliminates the period without a leader. The downside of this change is that when a server leaves gracefully, it also reduces the number of raft peers. The number of peers is used to calculate the quorum size, so this can unexpectedly change the fault tolerance of the cluster. When running with an odd number of servers, 1 server leaving the cluster does not affect quorum size. E.g. 5 servers => quorum 3, 4 servers => quorum still 3. During a rollout, Kubernetes only stops 1 server at a time, so the quorum won't change. During a voluntary disruption event, e.g. a node being drained, Kubernetes uses the pod disruption budget to determine how many pods in a statefulset can be made unavailable at a time. That's why this change hardcodes this number to 1 now.
- Loading branch information
Showing
9 changed files
with
151 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
```releast-note:feature | ||
```release-note:feature | ||
api-gateway: (Consul Enterprise) Add JWT authentication and authorization for API Gateway and HTTPRoutes. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:breaking-change | ||
server: set `leave_on_terminate` to `true` and set the server pod disruption budget `maxUnavailable` to `1`. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package server | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" | ||
) | ||
|
||
var suite testsuite.Suite | ||
|
||
func TestMain(m *testing.M) { | ||
suite = testsuite.NewSuite(m) | ||
os.Exit(suite.Run()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package server | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/consul-k8s/acceptance/framework/consul" | ||
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers" | ||
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s" | ||
"github.com/hashicorp/consul-k8s/acceptance/framework/logger" | ||
"github.com/hashicorp/go-multierror" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// Test that when servers are restarted, they don't lose quorum. | ||
func TestServerRestart(t *testing.T) { | ||
ctx := suite.Environment().DefaultContext(t) | ||
replicas := 3 | ||
releaseName := helpers.RandomName() | ||
helmValues := map[string]string{ | ||
"global.enabled": "false", | ||
"connectInject.enabled": "false", | ||
"server.enabled": "true", | ||
"server.replicas": fmt.Sprintf("%d", replicas), | ||
} | ||
consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) | ||
|
||
consulCluster.Create(t) | ||
|
||
// Start a separate goroutine to check if at any point quorum is lost. | ||
// Use number of ready replicas as proxy for whether there is quorum because | ||
// replicas are marked unready if they don't think there's a leader so if | ||
// >n/2 replicas are unready then there's no quorum. | ||
noQuorumCount := 0 | ||
var unmarshallErrs error | ||
done := make(chan bool) | ||
go func() { | ||
for { | ||
select { | ||
case <-done: | ||
return | ||
default: | ||
out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "get", fmt.Sprintf("statefulset/%s-consul-server", releaseName), | ||
"-o", "jsonpath={.status}") | ||
if err != nil { | ||
// Not failing the test on this error to reduce flakiness. | ||
logger.Logf(t, "kubectl err: %s: %s", err, out) | ||
break | ||
} | ||
type statefulsetOut struct { | ||
ReadyReplicas *int `json:"readyReplicas,omitempty"` | ||
} | ||
var jsonOut statefulsetOut | ||
if err = json.Unmarshal([]byte(out), &jsonOut); err != nil { | ||
unmarshallErrs = multierror.Append(err) | ||
} else if jsonOut.ReadyReplicas == nil || *jsonOut.ReadyReplicas < replicas-1 { | ||
// note: for some k8s api reason when readyReplicas is 0 it's not included in the json output so | ||
// that's why we're checking if it's nil. | ||
noQuorumCount++ | ||
} | ||
time.Sleep(1 * time.Second) | ||
} | ||
} | ||
}() | ||
|
||
// Restart servers | ||
out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "restart", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) | ||
require.NoError(t, err, out) | ||
|
||
// Wait for restart to finish. | ||
start := time.Now() | ||
out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "rollout", "status", "--timeout", "5m", "--watch", fmt.Sprintf("statefulset/%s-consul-server", releaseName)) | ||
require.NoError(t, err, out, "rollout status command errored, this likely means the rollout didn't complete in time") | ||
close(done) | ||
|
||
require.NoError(t, unmarshallErrs, "there were some json unmarshall errors, this is likely a bug") | ||
|
||
logger.Logf(t, "restart took %s, there were %d seconds without quorum", time.Now().Sub(start), noQuorumCount) | ||
require.Equal(t, 0, noQuorumCount, "there was %d seconds without quorum", noQuorumCount) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters