-
Notifications
You must be signed in to change notification settings - Fork 40
util: tools to drain and uncordon cluster #1060
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
Merged
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
bf57014
simple tool to cordon cluster
62c1328
minor changes
4c2f90d
add retry with backoff
62607c0
list CRPs instead of CRBs
8b8ec57
wait for evictions to reach terminal state
5279951
minor fix
de42202
minor change
8d7e4da
add uncordon file, readme
3a0d293
minor changes
6f047a0
minor fix
cb575db
refactor
41145c0
minor fix
88ba286
add drain retry
dd0a3e2
minor fix
6bda9cc
restructure files
fb3e002
rename directories
de67b26
add more checks
d6e9af3
fix lint
e7a687d
minor changes
eb63e4d
add comments
31d3557
use work object to get propagated resources
e0830d4
minor improvements
ef77c37
improve readme
d9cac20
minor changes to readme
89e7b3d
address minor comments
77f5615
address comments
996070f
minor fix
dbddbcc
address comment
ca4e6e3
address comment
1ca0348
minor changes
6470834
doc update
7fa0c3f
refactor code
729c015
minor changes
e657966
minor changes
d7eebb8
change struct names
7c34d23
address comments
c715acf
address comments
e4717e6
make reviewable
eb20eee
minor fixes
a369776
minor change
f00d3ad
minor change
1885458
address comments
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,42 @@ | ||
| /* | ||
| Copyright (c) Microsoft Corporation. | ||
| Licensed under the MIT license. | ||
| */ | ||
|
|
||
| package eviction | ||
|
|
||
| import ( | ||
| "k8s.io/klog/v2" | ||
|
|
||
| placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" | ||
| "go.goms.io/fleet/pkg/utils/condition" | ||
| ) | ||
|
|
||
| // IsEvictionInTerminalState checks to see if eviction is in a terminal state. | ||
| func IsEvictionInTerminalState(eviction *placementv1beta1.ClusterResourcePlacementEviction) bool { | ||
| if validCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeValid)); condition.IsConditionStatusFalse(validCondition, eviction.GetGeneration()) { | ||
| klog.V(2).InfoS("Invalid eviction, no need to reconcile", "clusterResourcePlacementEviction", eviction.Name) | ||
| return true | ||
| } | ||
|
|
||
| if executedCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeExecuted)); executedCondition != nil { | ||
| klog.V(2).InfoS("Eviction has executed condition specified, no need to reconcile", "clusterResourcePlacementEviction", eviction.Name) | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| // IsPlacementPresent checks to see if placement on target cluster could be present. | ||
| func IsPlacementPresent(binding *placementv1beta1.ClusterResourceBinding) bool { | ||
| if binding.Spec.State == placementv1beta1.BindingStateBound { | ||
| return true | ||
| } | ||
| if binding.Spec.State == placementv1beta1.BindingStateUnscheduled { | ||
| currentAnnotation := binding.GetAnnotations() | ||
| previousState, exist := currentAnnotation[placementv1beta1.PreviousBindingStateAnnotation] | ||
| if exist && placementv1beta1.BindingState(previousState) == placementv1beta1.BindingStateBound { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } |
This file contains hidden or 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,153 @@ | ||
| /* | ||
| Copyright (c) Microsoft Corporation. | ||
| Licensed under the MIT license. | ||
| */ | ||
|
|
||
| package eviction | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
|
||
| placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" | ||
| ) | ||
|
|
||
| func TestIsEvictionInTerminalState(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| eviction *placementv1beta1.ClusterResourcePlacementEviction | ||
| want bool | ||
| }{ | ||
| { | ||
| name: "Invalid eviction - terminal state", | ||
| eviction: &placementv1beta1.ClusterResourcePlacementEviction{ | ||
| Status: placementv1beta1.PlacementEvictionStatus{ | ||
| Conditions: []metav1.Condition{ | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeValid), | ||
| Status: metav1.ConditionFalse, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "Executed eviction set to true - terminal state", | ||
| eviction: &placementv1beta1.ClusterResourcePlacementEviction{ | ||
| Status: placementv1beta1.PlacementEvictionStatus{ | ||
| Conditions: []metav1.Condition{ | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeValid), | ||
| Status: metav1.ConditionTrue, | ||
| }, | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeExecuted), | ||
| Status: metav1.ConditionTrue, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "Executed eviction set to false - terminal state", | ||
| eviction: &placementv1beta1.ClusterResourcePlacementEviction{ | ||
| Status: placementv1beta1.PlacementEvictionStatus{ | ||
| Conditions: []metav1.Condition{ | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeValid), | ||
| Status: metav1.ConditionTrue, | ||
| }, | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeExecuted), | ||
| Status: metav1.ConditionFalse, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "Eviction with only valid condition set to true - non terminal state", | ||
| eviction: &placementv1beta1.ClusterResourcePlacementEviction{ | ||
| Status: placementv1beta1.PlacementEvictionStatus{ | ||
| Conditions: []metav1.Condition{ | ||
| { | ||
| Type: string(placementv1beta1.PlacementEvictionConditionTypeValid), | ||
| Status: metav1.ConditionTrue, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| want: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| if got := IsEvictionInTerminalState(tt.eviction); got != tt.want { | ||
| t.Errorf("IsEvictionInTerminalState test failed got = %v, want = %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestIsPlacementPresent(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| binding *placementv1beta1.ClusterResourceBinding | ||
| want bool | ||
| }{ | ||
| { | ||
| name: "Bound binding - placement present", | ||
| binding: &placementv1beta1.ClusterResourceBinding{ | ||
| Spec: placementv1beta1.ResourceBindingSpec{ | ||
| State: placementv1beta1.BindingStateBound, | ||
| }, | ||
| }, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "Unscheduled binding with previous state bound - placement present", | ||
| binding: &placementv1beta1.ClusterResourceBinding{ | ||
| Spec: placementv1beta1.ResourceBindingSpec{ | ||
| State: placementv1beta1.BindingStateUnscheduled, | ||
| }, | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Annotations: map[string]string{ | ||
| placementv1beta1.PreviousBindingStateAnnotation: string(placementv1beta1.BindingStateBound), | ||
| }, | ||
| }, | ||
| }, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "Unscheduled binding with no previous state - placement not present", | ||
| binding: &placementv1beta1.ClusterResourceBinding{ | ||
| Spec: placementv1beta1.ResourceBindingSpec{ | ||
| State: placementv1beta1.BindingStateUnscheduled, | ||
| }, | ||
| }, | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "Scheduled binding - placement not present", | ||
| binding: &placementv1beta1.ClusterResourceBinding{ | ||
| Spec: placementv1beta1.ResourceBindingSpec{ | ||
| State: placementv1beta1.BindingStateScheduled, | ||
| }, | ||
| }, | ||
| want: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| if got := IsPlacementPresent(tt.binding); got != tt.want { | ||
| t.Errorf("IsPlacementPresent test failed got = %v, want = %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
This file contains hidden or 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,77 @@ | ||
| # Steps to build draincluster as a kubectl plugin | ||
|
|
||
| 1. Build the binary for the `draincluster` tool by running the following command in the root directory of the fleet repo: | ||
|
|
||
| ```bash | ||
| go build -o ./hack/tools/bin/kubectl-draincluster ./tools/draincluster/main.go | ||
| ``` | ||
|
|
||
| 2. Copy the binary to a directory in your `PATH` so that it can be run as a kubectl plugin. For example, you can move it to | ||
| `/usr/local/bin`: | ||
|
|
||
| ```bash | ||
| sudo cp ./hack/tools/bin/kubectl-draincluster /usr/local/bin/ | ||
| ``` | ||
|
|
||
| 3. Make the binary executable by running the following command: | ||
|
|
||
| ```bash | ||
| chmod +x /usr/local/bin/kubectl-draincluster | ||
| ``` | ||
|
|
||
| 4. Verify that the plugin is recognized by kubectl by running the following command: | ||
|
|
||
| ```bash | ||
| kubectl plugin list | ||
| ``` | ||
|
|
||
| you should see the `draincluster` plugin listed in the output: | ||
|
|
||
| ``` | ||
| The following compatible plugins are available: | ||
|
|
||
| /usr/local/bin/kubectl-draincluster | ||
| ``` | ||
|
|
||
| please refer to the [kubectl plugin documentation](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) for | ||
| more information. | ||
|
|
||
| # Drain Member Cluster connected to a fleet | ||
|
|
||
| After following the steps above to build the `draincluster` tool as a kubectl plugin, you can use it to remove all | ||
| resources propagated to the member cluster from the hub cluster by any `Placement` resource. This is useful when you | ||
| want to temporarily move all workloads off a member cluster in preparation for an event like upgrade or reconfiguration. | ||
|
|
||
| The `draincluster` tool can be used to drain a member cluster by running the following command: | ||
|
|
||
| ``` | ||
| kubectl draincluster --hubClusterContext <hub-cluster-context> --clusterName <memberClusterName> | ||
| ``` | ||
|
|
||
| the tool currently is a go program that takes the hub cluster context and the member cluster name as arguments. | ||
|
|
||
| - The `--hubClusterContext` flag specifies the context of the hub cluster | ||
| - The `--clusterName` flag specifies the name of the member cluster to drain. | ||
|
|
||
| the user can run the following command to identify the context of the hub cluster: | ||
|
|
||
| ``` | ||
| kubectl config get-contexts | ||
| ``` | ||
|
|
||
| the output of the command will look like this: | ||
|
|
||
| ``` | ||
| CURRENT NAME CLUSTER AUTHINFO NAMESPACE | ||
| * hub hub clusterUser_clusterResourceGroup_hub | ||
| ``` | ||
|
|
||
|
Arvindthiru marked this conversation as resolved.
|
||
| Here you can see that the context of the hub cluster is called `hub` under the `NAME` column. | ||
|
|
||
| The command adds a `Taint` to the `MemberCluster` resource of the member cluster to prevent any new resources from being | ||
| propagated to the member cluster. Then it creates `Eviction` objects for all the `Placement` objects that have propagated | ||
| resources to the member cluster. | ||
|
|
||
| >> **Note**: The `draincluster` tool is a best-effort mechanism at the moment, so once the command is run successfully | ||
| > the user must verify if all resources propagated by `Placement` resources are removed from the member cluster. | ||
| > Re-running the command is safe and is recommended if the user notices any resources still present on the member cluster. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.