mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(k8s/application): add the ability to set the auto-scale policy of an application (#4118)
* feat(application): add horizontalpodautoscaler creation * feat(application): Add the ability to set the auto-scale policy of an application * feat(k8s/application): minor UI update * fix(application): set api version and prevent to use hpa with global deployment type * feat(settings): add a switch to enable features based on server metrics * feat(k8s/applications): minor UI update Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
parent
909e1ef02c
commit
6756b04b67
16 changed files with 534 additions and 84 deletions
|
@ -1,4 +1,6 @@
|
|||
import * as JsonPatch from 'fast-json-patch';
|
||||
import { KubernetesHorizontalPodAutoScaler } from './models';
|
||||
import { KubernetesHorizontalPodAutoScalerCreatePayload } from './payload';
|
||||
|
||||
export class KubernetesHorizontalPodAutoScalerConverter {
|
||||
/**
|
||||
|
@ -11,7 +13,8 @@ export class KubernetesHorizontalPodAutoScalerConverter {
|
|||
res.Name = data.metadata.name;
|
||||
res.MinReplicas = data.spec.minReplicas;
|
||||
res.MaxReplicas = data.spec.maxReplicas;
|
||||
res.TargetCPUUtilizationPercentage = data.spec.targetCPUUtilizationPercentage;
|
||||
res.TargetCPUUtilization = data.spec.targetCPUUtilizationPercentage;
|
||||
|
||||
if (data.spec.scaleTargetRef) {
|
||||
res.TargetEntity.ApiVersion = data.spec.scaleTargetRef.apiVersion;
|
||||
res.TargetEntity.Kind = data.spec.scaleTargetRef.kind;
|
||||
|
@ -20,4 +23,111 @@ export class KubernetesHorizontalPodAutoScalerConverter {
|
|||
res.Yaml = yaml ? yaml.data : '';
|
||||
return res;
|
||||
}
|
||||
|
||||
static createPayload(data) {
|
||||
const payload = new KubernetesHorizontalPodAutoScalerCreatePayload();
|
||||
payload.metadata.namespace = data.Namespace;
|
||||
payload.metadata.name = data.TargetEntity.Name;
|
||||
payload.spec.minReplicas = data.MinReplicas;
|
||||
payload.spec.maxReplicas = data.MaxReplicas;
|
||||
payload.spec.targetCPUUtilizationPercentage = data.TargetCPUUtilization;
|
||||
payload.spec.scaleTargetRef.apiVersion = data.TargetEntity.ApiVersion;
|
||||
payload.spec.scaleTargetRef.kind = data.TargetEntity.Kind;
|
||||
payload.spec.scaleTargetRef.name = data.TargetEntity.Name;
|
||||
return payload;
|
||||
}
|
||||
|
||||
static patchPayload(oldScaler, newScaler) {
|
||||
const oldPayload = KubernetesHorizontalPodAutoScalerConverter.createPayload(oldScaler);
|
||||
const newPayload = KubernetesHorizontalPodAutoScalerConverter.createPayload(newScaler);
|
||||
const payload = JsonPatch.compare(oldPayload, newPayload);
|
||||
return payload;
|
||||
}
|
||||
|
||||
static applicationFormValuesToModel(formValues, kind) {
|
||||
const res = new KubernetesHorizontalPodAutoScaler();
|
||||
res.Name = formValues.Name;
|
||||
res.Namespace = formValues.ResourcePool.Namespace.Name;
|
||||
res.MinReplicas = formValues.AutoScaler.MinReplicas;
|
||||
res.MaxReplicas = formValues.AutoScaler.MaxReplicas;
|
||||
res.TargetCPUUtilization = formValues.AutoScaler.TargetCPUUtilization;
|
||||
res.TargetEntity.Name = formValues.Name;
|
||||
res.TargetEntity.Kind = kind;
|
||||
res.TargetEntity.ApiVersion = formValues.AutoScaler.ApiVersion;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertion functions to use with v2beta2 model
|
||||
*/
|
||||
|
||||
// static apiToModel(data, yaml) {
|
||||
// const res = new KubernetesHorizontalPodAutoScaler();
|
||||
// res.Id = data.metadata.uid;
|
||||
// res.Namespace = data.metadata.namespace;
|
||||
// res.Name = data.metadata.name;
|
||||
// res.MinReplicas = data.spec.minReplicas;
|
||||
// res.MaxReplicas = data.spec.maxReplicas;
|
||||
// res.TargetCPUUtilization = data.spec.targetCPUUtilization;
|
||||
|
||||
// _.forEach(data.spec.metrics, (metric) => {
|
||||
// if (metric.type === 'Resource') {
|
||||
// if (metric.resource.name === 'cpu') {
|
||||
// res.TargetCPUUtilization = metric.resource.target.averageUtilization;
|
||||
// }
|
||||
// if (metric.resource.name === 'memory') {
|
||||
// res.TargetMemoryValue = parseFloat(metric.resource.target.averageValue) / 1000;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (data.spec.scaleTargetRef) {
|
||||
// res.TargetEntity.ApiVersion = data.spec.scaleTargetRef.apiVersion;
|
||||
// res.TargetEntity.Kind = data.spec.scaleTargetRef.kind;
|
||||
// res.TargetEntity.Name = data.spec.scaleTargetRef.name;
|
||||
// }
|
||||
// res.Yaml = yaml ? yaml.data : '';
|
||||
// return res;
|
||||
// }
|
||||
|
||||
// static createPayload(data) {
|
||||
// const payload = new KubernetesHorizontalPodAutoScalerCreatePayload();
|
||||
// payload.metadata.namespace = data.Namespace;
|
||||
// payload.metadata.name = data.TargetEntity.Name;
|
||||
// payload.spec.minReplicas = data.MinReplicas;
|
||||
// payload.spec.maxReplicas = data.MaxReplicas;
|
||||
|
||||
// if (data.TargetMemoryValue) {
|
||||
// const memoryMetric = new KubernetesHorizontalPodAutoScalerMemoryMetric();
|
||||
// memoryMetric.resource.target.averageValue = data.TargetMemoryValue;
|
||||
// payload.spec.metrics.push(memoryMetric);
|
||||
// }
|
||||
|
||||
// if (data.TargetCPUUtilization) {
|
||||
// const cpuMetric = new KubernetesHorizontalPodAutoScalerCPUMetric();
|
||||
// cpuMetric.resource.target.averageUtilization = data.TargetCPUUtilization;
|
||||
// payload.spec.metrics.push(cpuMetric);
|
||||
// }
|
||||
|
||||
// payload.spec.scaleTargetRef.apiVersion = data.TargetEntity.ApiVersion;
|
||||
// payload.spec.scaleTargetRef.kind = data.TargetEntity.Kind;
|
||||
// payload.spec.scaleTargetRef.name = data.TargetEntity.Name;
|
||||
|
||||
// return payload;
|
||||
// }
|
||||
|
||||
// static applicationFormValuesToModel(formValues, kind) {
|
||||
// const res = new KubernetesHorizontalPodAutoScaler();
|
||||
// res.Name = formValues.Name;
|
||||
// res.Namespace = formValues.ResourcePool.Namespace.Name;
|
||||
// res.MinReplicas = formValues.AutoScaler.MinReplicas;
|
||||
// res.MaxReplicas = formValues.AutoScaler.MaxReplicas;
|
||||
// res.TargetCPUUtilization = formValues.AutoScaler.TargetCPUUtilization;
|
||||
// if (formValues.AutoScaler.TargetMemoryValue) {
|
||||
// res.TargetMemoryValue = formValues.AutoScaler.TargetMemoryValue + 'M';
|
||||
// }
|
||||
// res.TargetEntity.Name = formValues.Name;
|
||||
// res.TargetEntity.Kind = kind;
|
||||
// return res;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -5,22 +5,22 @@ import { KubernetesDeployment } from 'Kubernetes/models/deployment/models';
|
|||
import { KubernetesStatefulSet } from 'Kubernetes/models/stateful-set/models';
|
||||
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
|
||||
|
||||
function _getApplicationTypeString(app) {
|
||||
if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DEPLOYMENT) || app instanceof KubernetesDeployment) {
|
||||
return KubernetesApplicationTypeStrings.DEPLOYMENT;
|
||||
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DAEMONSET) || app instanceof KubernetesDaemonSet) {
|
||||
return KubernetesApplicationTypeStrings.DAEMONSET;
|
||||
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET) || app instanceof KubernetesStatefulSet) {
|
||||
return KubernetesApplicationTypeStrings.STATEFULSET;
|
||||
// } else if () { ---> TODO: refactor - handle bare pod type !
|
||||
} else {
|
||||
throw new PortainerError('Unable to determine application type');
|
||||
}
|
||||
}
|
||||
|
||||
export class KubernetesHorizontalPodAutoScalerHelper {
|
||||
static findApplicationBoundScaler(sList, app) {
|
||||
const kind = _getApplicationTypeString(app);
|
||||
const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
|
||||
return _.find(sList, (item) => item.TargetEntity.Kind === kind && item.TargetEntity.Name === app.Name);
|
||||
}
|
||||
|
||||
static getApplicationTypeString(app) {
|
||||
if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DEPLOYMENT) || app instanceof KubernetesDeployment) {
|
||||
return KubernetesApplicationTypeStrings.DEPLOYMENT;
|
||||
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.DAEMONSET) || app instanceof KubernetesDaemonSet) {
|
||||
return KubernetesApplicationTypeStrings.DAEMONSET;
|
||||
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET) || app instanceof KubernetesStatefulSet) {
|
||||
return KubernetesApplicationTypeStrings.STATEFULSET;
|
||||
// } else if () { ---> TODO: refactor - handle bare pod type !
|
||||
} else {
|
||||
throw new PortainerError('Unable to determine application type');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ const _KubernetesHorizontalPodAutoScaler = Object.freeze({
|
|||
Name: '',
|
||||
MinReplicas: 1,
|
||||
MaxReplicas: 1,
|
||||
TargetCPUUtilizationPercentage: undefined,
|
||||
TargetCPUUtilization: 0,
|
||||
TargetEntity: {
|
||||
ApiVersion: '',
|
||||
Kind: '',
|
||||
|
|
86
app/kubernetes/horizontal-pod-auto-scaler/payload.js
Normal file
86
app/kubernetes/horizontal-pod-auto-scaler/payload.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* KubernetesHorizontalPodAutoScaler Create Payload Model
|
||||
*/
|
||||
const _KubernetesHorizontalPodAutoScalerCreatePayload = Object.freeze({
|
||||
metadata: {
|
||||
namespace: '',
|
||||
name: '',
|
||||
},
|
||||
spec: {
|
||||
maxReplicas: 0,
|
||||
minReplicas: 0,
|
||||
targetCPUUtilizationPercentage: 0,
|
||||
scaleTargetRef: {
|
||||
kind: '',
|
||||
name: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export class KubernetesHorizontalPodAutoScalerCreatePayload {
|
||||
constructor() {
|
||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesHorizontalPodAutoScalerCreatePayload)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KubernetesHorizontalPodAutoScaler Create Payload Model for v2beta2
|
||||
* Include support of memory usage
|
||||
*/
|
||||
|
||||
// const _KubernetesHorizontalPodAutoScalerCreatePayload = Object.freeze({
|
||||
// metadata: {
|
||||
// namespace: '',
|
||||
// name: ''
|
||||
// },
|
||||
// spec: {
|
||||
// maxReplicas: 0,
|
||||
// minReplicas: 0,
|
||||
// targetCPUUtilizationPercentage: 0,
|
||||
// scaleTargetRef: {
|
||||
// kind: '',
|
||||
// name: ''
|
||||
// },
|
||||
// metrics: []
|
||||
// }
|
||||
// });
|
||||
|
||||
// export class KubernetesHorizontalPodAutoScalerCreatePayload {
|
||||
// constructor() {
|
||||
// Object.assign(this, JSON.parse(JSON.stringify(_KubernetesHorizontalPodAutoScalerCreatePayload)));
|
||||
// }
|
||||
// }
|
||||
|
||||
// const _KubernetesHorizontalPodAutoScalerCPUMetric = Object.freeze({
|
||||
// type: 'Resource',
|
||||
// resource: {
|
||||
// name: 'cpu',
|
||||
// target: {
|
||||
// type: 'Utilization',
|
||||
// averageUtilization: 0
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// export class KubernetesHorizontalPodAutoScalerCPUMetric {
|
||||
// constructor() {
|
||||
// Object.assign(this, JSON.parse(JSON.stringify(_KubernetesHorizontalPodAutoScalerCPUMetric)));
|
||||
// }
|
||||
// }
|
||||
|
||||
// const _KubernetesHorizontalPodAutoScalerMemoryMetric = Object.freeze({
|
||||
// type: 'Resource',
|
||||
// resource: {
|
||||
// name: 'memory',
|
||||
// target: {
|
||||
// type: 'AverageValue',
|
||||
// averageValue: ''
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// export class KubernetesHorizontalPodAutoScalerMemoryMetric {
|
||||
// constructor() {
|
||||
// Object.assign(this, JSON.parse(JSON.stringify(_KubernetesHorizontalPodAutoScalerMemoryMetric)));
|
||||
// }
|
||||
// }
|
|
@ -12,10 +12,10 @@ class KubernetesHorizontalPodAutoScalerService {
|
|||
|
||||
this.getAsync = this.getAsync.bind(this);
|
||||
this.getAllAsync = this.getAllAsync.bind(this);
|
||||
// this.createAsync = this.createAsync.bind(this);
|
||||
// this.patchAsync = this.patchAsync.bind(this);
|
||||
this.createAsync = this.createAsync.bind(this);
|
||||
this.patchAsync = this.patchAsync.bind(this);
|
||||
// this.rollbackAsync = this.rollbackAsync.bind(this);
|
||||
// this.deleteAsync = this.deleteAsync.bind(this);
|
||||
this.deleteAsync = this.deleteAsync.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,65 +53,65 @@ class KubernetesHorizontalPodAutoScalerService {
|
|||
return this.$async(this.getAllAsync, namespace);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * CREATE
|
||||
// */
|
||||
// async createAsync(horizontalPodAutoScaler) {
|
||||
// try {
|
||||
// const params = {};
|
||||
// const payload = KubernetesHorizontalPodAutoScalerConverter.createPayload(horizontalPodAutoScaler);
|
||||
// const namespace = payload.metadata.namespace;
|
||||
// const data = await this.KubernetesHorizontalPodAutoScalers(namespace).create(params, payload).$promise;
|
||||
// return data;
|
||||
// } catch (err) {
|
||||
// throw new PortainerError('Unable to create horizontalPodAutoScaler', err);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* CREATE
|
||||
*/
|
||||
async createAsync(horizontalPodAutoScaler) {
|
||||
try {
|
||||
const params = {};
|
||||
const payload = KubernetesHorizontalPodAutoScalerConverter.createPayload(horizontalPodAutoScaler);
|
||||
const namespace = payload.metadata.namespace;
|
||||
const data = await this.KubernetesHorizontalPodAutoScalers(namespace).create(params, payload).$promise;
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to create horizontalPodAutoScaler', err);
|
||||
}
|
||||
}
|
||||
|
||||
// create(horizontalPodAutoScaler) {
|
||||
// return this.$async(this.createAsync, horizontalPodAutoScaler);
|
||||
// }
|
||||
create(horizontalPodAutoScaler) {
|
||||
return this.$async(this.createAsync, horizontalPodAutoScaler);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * PATCH
|
||||
// */
|
||||
// async patchAsync(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||
// try {
|
||||
// const params = new KubernetesCommonParams();
|
||||
// params.id = newHorizontalPodAutoScaler.Name;
|
||||
// const namespace = newHorizontalPodAutoScaler.Namespace;
|
||||
// const payload = KubernetesHorizontalPodAutoScalerConverter.patchPayload(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||
// if (!payload.length) {
|
||||
// return;
|
||||
// }
|
||||
// const data = await this.KubernetesHorizontalPodAutoScalers(namespace).patch(params, payload).$promise;
|
||||
// return data;
|
||||
// } catch (err) {
|
||||
// throw new PortainerError('Unable to patch horizontalPodAutoScaler', err);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* PATCH
|
||||
*/
|
||||
async patchAsync(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||
try {
|
||||
const params = new KubernetesCommonParams();
|
||||
params.id = newHorizontalPodAutoScaler.Name;
|
||||
const namespace = newHorizontalPodAutoScaler.Namespace;
|
||||
const payload = KubernetesHorizontalPodAutoScalerConverter.patchPayload(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||
if (!payload.length) {
|
||||
return;
|
||||
}
|
||||
const data = await this.KubernetesHorizontalPodAutoScalers(namespace).patch(params, payload).$promise;
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to patch horizontalPodAutoScaler', err);
|
||||
}
|
||||
}
|
||||
|
||||
// patch(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||
// return this.$async(this.patchAsync, oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||
// }
|
||||
patch(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||
return this.$async(this.patchAsync, oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * DELETE
|
||||
// */
|
||||
// async deleteAsync(horizontalPodAutoScaler) {
|
||||
// try {
|
||||
// const params = new KubernetesCommonParams();
|
||||
// params.id = horizontalPodAutoScaler.Name;
|
||||
// const namespace = horizontalPodAutoScaler.Namespace;
|
||||
// await this.KubernetesHorizontalPodAutoScalers(namespace).delete(params).$promise;
|
||||
// } catch (err) {
|
||||
// throw new PortainerError('Unable to remove horizontalPodAutoScaler', err);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* DELETE
|
||||
*/
|
||||
async deleteAsync(horizontalPodAutoScaler) {
|
||||
try {
|
||||
const params = new KubernetesCommonParams();
|
||||
params.id = horizontalPodAutoScaler.Name;
|
||||
const namespace = horizontalPodAutoScaler.Namespace;
|
||||
await this.KubernetesHorizontalPodAutoScalers(namespace).delete(params).$promise;
|
||||
} catch (err) {
|
||||
throw new PortainerError('Unable to remove horizontalPodAutoScaler', err);
|
||||
}
|
||||
}
|
||||
|
||||
// delete(horizontalPodAutoScaler) {
|
||||
// return this.$async(this.deleteAsync, horizontalPodAutoScaler);
|
||||
// }
|
||||
delete(horizontalPodAutoScaler) {
|
||||
return this.$async(this.deleteAsync, horizontalPodAutoScaler);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * ROLLBACK
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue