1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-20 22:09:41 +02:00

Enable the ability to cordon/uncordon/drain nodes (#4723)

* feat(node): Enable the ability to cordon/uncordon/drain nodes

* feat(cluster): check if there is a drain operation somewhere

* feat(kubernetes): allow to cordon, uncordon, drain nodes

* refacto(kubernetes): set a constant for drain label name

* fix(node): Relocate the warning message next to the dropdown and change the information message
This commit is contained in:
Maxime Bajeux 2021-03-15 22:36:14 +01:00 committed by GitHub
parent 660bc2dadf
commit 32a9a2e46b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 182 additions and 13 deletions

View file

@ -5,7 +5,7 @@ import { KubernetesResourceReservation } from 'Kubernetes/models/resource-reserv
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
import KubernetesNodeConverter from 'Kubernetes/node/converter';
import { KubernetesNodeLabelFormValues, KubernetesNodeTaintFormValues } from 'Kubernetes/node/formValues';
import { KubernetesNodeTaintEffects } from 'Kubernetes/node/models';
import { KubernetesNodeTaintEffects, KubernetesNodeAvailabilities } from 'Kubernetes/node/models';
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
import { KubernetesNodeHelper } from 'Kubernetes/node/helper';
@ -35,12 +35,13 @@ class KubernetesNodeController {
this.KubernetesEndpointService = KubernetesEndpointService;
this.onInit = this.onInit.bind(this);
this.getNodeAsync = this.getNodeAsync.bind(this);
this.getNodesAsync = this.getNodesAsync.bind(this);
this.getEvents = this.getEvents.bind(this);
this.getEventsAsync = this.getEventsAsync.bind(this);
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
this.getEndpointsAsync = this.getEndpointsAsync.bind(this);
this.updateNodeAsync = this.updateNodeAsync.bind(this);
this.drainNodeAsync = this.drainNodeAsync.bind(this);
}
selectTab(index) {
@ -152,6 +153,47 @@ class KubernetesNodeController {
/* #endregion */
/* #region cordon */
computeCordonWarning() {
return this.formValues.Availability === this.availabilities.PAUSE;
}
/* #endregion */
/* #region drain */
computeDrainWarning() {
return this.formValues.Availability === this.availabilities.DRAIN;
}
async drainNodeAsync() {
const pods = _.flatten(_.map(this.applications, (app) => app.Pods));
let actionCount = pods.length;
for (const pod of pods) {
try {
await this.KubernetesPodService.eviction(pod);
this.Notifications.success('Pod successfully evicted', pod.Name);
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to evict pod');
this.formValues.Availability = this.availabilities.PAUSE;
await this.KubernetesNodeService.patch(this.node, this.formValues);
} finally {
--actionCount;
if (actionCount === 0) {
this.formValues.Availability = this.availabilities.PAUSE;
await this.KubernetesNodeService.patch(this.node, this.formValues);
}
}
}
}
drainNode() {
return this.$async(this.drainNodeAsync);
}
/* #endregion */
/* #region actions */
isNoChangesMade() {
@ -160,8 +202,12 @@ class KubernetesNodeController {
return !payload.length;
}
isDrainError() {
return (this.state.isDrainOperation || this.state.isContainPortainer) && this.formValues.Availability === this.availabilities.DRAIN;
}
isFormValid() {
return !this.state.hasDuplicateTaintKeys && !this.state.hasDuplicateLabelKeys && !this.isNoChangesMade();
return !this.state.hasDuplicateTaintKeys && !this.state.hasDuplicateLabelKeys && !this.isNoChangesMade() && !this.isDrainError();
}
resetFormValues() {
@ -196,7 +242,10 @@ class KubernetesNodeController {
async updateNodeAsync() {
try {
await this.KubernetesNodeService.patch(this.node, this.formValues);
this.node = await this.KubernetesNodeService.patch(this.node, this.formValues);
if (this.formValues.Availability === 'Drain') {
await this.drainNode();
}
this.Notifications.success('Node updated successfully');
this.$state.reload();
} catch (err) {
@ -207,6 +256,8 @@ class KubernetesNodeController {
updateNode() {
const taintsWarning = this.computeTaintsWarning();
const labelsWarning = this.computeLabelsWarning();
const cordonWarning = this.computeCordonWarning();
const drainWarning = this.computeDrainWarning();
if (taintsWarning && !labelsWarning) {
this.ModalService.confirmUpdate(
@ -235,16 +286,36 @@ class KubernetesNodeController {
}
}
);
} else if (cordonWarning) {
this.ModalService.confirmUpdate(
'Marking this node as unschedulable will effectively cordon the node and prevent any new workload from being scheduled on that node. Are you sure?',
(confirmed) => {
if (confirmed) {
return this.$async(this.updateNodeAsync);
}
}
);
} else if (drainWarning) {
this.ModalService.confirmUpdate(
'Draining this node will cause all workloads to be evicted from that node. This might lead to some service interruption. Are you sure?',
(confirmed) => {
if (confirmed) {
return this.$async(this.updateNodeAsync);
}
}
);
} else {
return this.$async(this.updateNodeAsync);
}
}
async getNodeAsync() {
async getNodesAsync() {
try {
this.state.dataLoading = true;
const nodeName = this.$transition$.params().name;
this.node = await this.KubernetesNodeService.get(nodeName);
this.nodes = await this.KubernetesNodeService.get();
this.node = _.find(this.nodes, { Name: nodeName });
this.state.isDrainOperation = _.find(this.nodes, { Availability: this.availabilities.DRAIN });
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve node');
} finally {
@ -252,8 +323,8 @@ class KubernetesNodeController {
}
}
getNode() {
return this.$async(this.getNodeAsync);
getNodes() {
return this.$async(this.getNodesAsync);
}
hasEventWarnings() {
@ -303,6 +374,7 @@ class KubernetesNodeController {
});
this.resourceReservation.Memory = KubernetesResourceReservationHelper.megaBytesValue(this.resourceReservation.Memory);
this.memoryLimit = KubernetesResourceReservationHelper.megaBytesValue(this.node.Memory);
this.state.isContainPortainer = _.find(this.applications, { ApplicationName: 'portainer' });
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve applications');
} finally {
@ -328,11 +400,15 @@ class KubernetesNodeController {
hasDuplicateTaintKeys: false,
duplicateLabelKeys: [],
hasDuplicateLabelKeys: false,
isDrainOperation: false,
isContainPortainer: false,
};
this.availabilities = KubernetesNodeAvailabilities;
this.state.activeTab = this.LocalStorage.getActiveTab('node');
await this.getNode();
await this.getNodes();
await this.getEvents();
await this.getApplications();
await this.getEndpoints();