mirror of https://github.com/portainer/portainer
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>pull/4156/head
parent
909e1ef02c
commit
6756b04b67
|
@ -3,8 +3,9 @@ package portainer
|
||||||
func KubernetesDefault() KubernetesData {
|
func KubernetesDefault() KubernetesData {
|
||||||
return KubernetesData{
|
return KubernetesData{
|
||||||
Configuration: KubernetesConfiguration{
|
Configuration: KubernetesConfiguration{
|
||||||
UseLoadBalancer: false,
|
UseLoadBalancer: false,
|
||||||
StorageClasses: []KubernetesStorageClassConfig{},
|
UseServerMetrics: false,
|
||||||
|
StorageClasses: []KubernetesStorageClassConfig{},
|
||||||
},
|
},
|
||||||
Snapshots: []KubernetesSnapshot{},
|
Snapshots: []KubernetesSnapshot{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,8 +337,9 @@ type (
|
||||||
|
|
||||||
// KubernetesConfiguration represents the configuration of a Kubernetes endpoint
|
// KubernetesConfiguration represents the configuration of a Kubernetes endpoint
|
||||||
KubernetesConfiguration struct {
|
KubernetesConfiguration struct {
|
||||||
UseLoadBalancer bool `json:"UseLoadBalancer"`
|
UseLoadBalancer bool `json:"UseLoadBalancer"`
|
||||||
StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"`
|
UseServerMetrics bool `json:"UseServerMetrics"`
|
||||||
|
StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration
|
// KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration
|
||||||
|
|
|
@ -260,6 +260,7 @@ class KubernetesApplicationConverter {
|
||||||
res.EnvironmentVariables = KubernetesApplicationHelper.generateEnvVariablesFromEnv(app.Env);
|
res.EnvironmentVariables = KubernetesApplicationHelper.generateEnvVariablesFromEnv(app.Env);
|
||||||
res.PersistedFolders = KubernetesApplicationHelper.generatePersistedFoldersFormValuesFromPersistedFolders(app.PersistedFolders, persistentVolumeClaims); // generate from PVC and app.PersistedFolders
|
res.PersistedFolders = KubernetesApplicationHelper.generatePersistedFoldersFormValuesFromPersistedFolders(app.PersistedFolders, persistentVolumeClaims); // generate from PVC and app.PersistedFolders
|
||||||
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
|
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
|
||||||
|
res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler);
|
||||||
|
|
||||||
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
|
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
|
||||||
res.PublishingType = KubernetesApplicationPublishingTypes.LOAD_BALANCER;
|
res.PublishingType = KubernetesApplicationPublishingTypes.LOAD_BALANCER;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
KubernetesApplicationConfigurationFormValueOverridenKey,
|
KubernetesApplicationConfigurationFormValueOverridenKey,
|
||||||
KubernetesApplicationPersistedFolderFormValue,
|
KubernetesApplicationPersistedFolderFormValue,
|
||||||
KubernetesApplicationPublishedPortFormValue,
|
KubernetesApplicationPublishedPortFormValue,
|
||||||
|
KubernetesApplicationAutoScalerFormValue,
|
||||||
} from 'Kubernetes/models/application/formValues';
|
} from 'Kubernetes/models/application/formValues';
|
||||||
import {
|
import {
|
||||||
KubernetesApplicationEnvConfigMapPayload,
|
KubernetesApplicationEnvConfigMapPayload,
|
||||||
|
@ -263,6 +264,20 @@ class KubernetesApplicationHelper {
|
||||||
return finalRes;
|
return finalRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static generateAutoScalerFormValueFromHorizontalPodAutoScaler(autoScaler) {
|
||||||
|
const res = new KubernetesApplicationAutoScalerFormValue();
|
||||||
|
if (autoScaler) {
|
||||||
|
res.IsUsed = true;
|
||||||
|
res.MinReplicas = autoScaler.MinReplicas;
|
||||||
|
res.MaxReplicas = autoScaler.MaxReplicas;
|
||||||
|
res.TargetCPUUtilization = autoScaler.TargetCPUUtilization;
|
||||||
|
res.ApiVersion = autoScaler.ApiVersion;
|
||||||
|
} else {
|
||||||
|
res.ApiVersion = 'apps/v1';
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* !APPLICATION TO FORMVALUES FUNCTIONS
|
* !APPLICATION TO FORMVALUES FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
import { KubernetesHorizontalPodAutoScaler } from './models';
|
import { KubernetesHorizontalPodAutoScaler } from './models';
|
||||||
|
import { KubernetesHorizontalPodAutoScalerCreatePayload } from './payload';
|
||||||
|
|
||||||
export class KubernetesHorizontalPodAutoScalerConverter {
|
export class KubernetesHorizontalPodAutoScalerConverter {
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +13,8 @@ export class KubernetesHorizontalPodAutoScalerConverter {
|
||||||
res.Name = data.metadata.name;
|
res.Name = data.metadata.name;
|
||||||
res.MinReplicas = data.spec.minReplicas;
|
res.MinReplicas = data.spec.minReplicas;
|
||||||
res.MaxReplicas = data.spec.maxReplicas;
|
res.MaxReplicas = data.spec.maxReplicas;
|
||||||
res.TargetCPUUtilizationPercentage = data.spec.targetCPUUtilizationPercentage;
|
res.TargetCPUUtilization = data.spec.targetCPUUtilizationPercentage;
|
||||||
|
|
||||||
if (data.spec.scaleTargetRef) {
|
if (data.spec.scaleTargetRef) {
|
||||||
res.TargetEntity.ApiVersion = data.spec.scaleTargetRef.apiVersion;
|
res.TargetEntity.ApiVersion = data.spec.scaleTargetRef.apiVersion;
|
||||||
res.TargetEntity.Kind = data.spec.scaleTargetRef.kind;
|
res.TargetEntity.Kind = data.spec.scaleTargetRef.kind;
|
||||||
|
@ -20,4 +23,111 @@ export class KubernetesHorizontalPodAutoScalerConverter {
|
||||||
res.Yaml = yaml ? yaml.data : '';
|
res.Yaml = yaml ? yaml.data : '';
|
||||||
return res;
|
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 { KubernetesStatefulSet } from 'Kubernetes/models/stateful-set/models';
|
||||||
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-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 {
|
export class KubernetesHorizontalPodAutoScalerHelper {
|
||||||
static findApplicationBoundScaler(sList, app) {
|
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);
|
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: '',
|
Name: '',
|
||||||
MinReplicas: 1,
|
MinReplicas: 1,
|
||||||
MaxReplicas: 1,
|
MaxReplicas: 1,
|
||||||
TargetCPUUtilizationPercentage: undefined,
|
TargetCPUUtilization: 0,
|
||||||
TargetEntity: {
|
TargetEntity: {
|
||||||
ApiVersion: '',
|
ApiVersion: '',
|
||||||
Kind: '',
|
Kind: '',
|
||||||
|
|
|
@ -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.getAsync = this.getAsync.bind(this);
|
||||||
this.getAllAsync = this.getAllAsync.bind(this);
|
this.getAllAsync = this.getAllAsync.bind(this);
|
||||||
// this.createAsync = this.createAsync.bind(this);
|
this.createAsync = this.createAsync.bind(this);
|
||||||
// this.patchAsync = this.patchAsync.bind(this);
|
this.patchAsync = this.patchAsync.bind(this);
|
||||||
// this.rollbackAsync = this.rollbackAsync.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);
|
return this.$async(this.getAllAsync, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * CREATE
|
* CREATE
|
||||||
// */
|
*/
|
||||||
// async createAsync(horizontalPodAutoScaler) {
|
async createAsync(horizontalPodAutoScaler) {
|
||||||
// try {
|
try {
|
||||||
// const params = {};
|
const params = {};
|
||||||
// const payload = KubernetesHorizontalPodAutoScalerConverter.createPayload(horizontalPodAutoScaler);
|
const payload = KubernetesHorizontalPodAutoScalerConverter.createPayload(horizontalPodAutoScaler);
|
||||||
// const namespace = payload.metadata.namespace;
|
const namespace = payload.metadata.namespace;
|
||||||
// const data = await this.KubernetesHorizontalPodAutoScalers(namespace).create(params, payload).$promise;
|
const data = await this.KubernetesHorizontalPodAutoScalers(namespace).create(params, payload).$promise;
|
||||||
// return data;
|
return data;
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// throw new PortainerError('Unable to create horizontalPodAutoScaler', err);
|
throw new PortainerError('Unable to create horizontalPodAutoScaler', err);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// create(horizontalPodAutoScaler) {
|
create(horizontalPodAutoScaler) {
|
||||||
// return this.$async(this.createAsync, horizontalPodAutoScaler);
|
return this.$async(this.createAsync, horizontalPodAutoScaler);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * PATCH
|
* PATCH
|
||||||
// */
|
*/
|
||||||
// async patchAsync(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
async patchAsync(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||||
// try {
|
try {
|
||||||
// const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
// params.id = newHorizontalPodAutoScaler.Name;
|
params.id = newHorizontalPodAutoScaler.Name;
|
||||||
// const namespace = newHorizontalPodAutoScaler.Namespace;
|
const namespace = newHorizontalPodAutoScaler.Namespace;
|
||||||
// const payload = KubernetesHorizontalPodAutoScalerConverter.patchPayload(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
const payload = KubernetesHorizontalPodAutoScalerConverter.patchPayload(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||||
// if (!payload.length) {
|
if (!payload.length) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// const data = await this.KubernetesHorizontalPodAutoScalers(namespace).patch(params, payload).$promise;
|
const data = await this.KubernetesHorizontalPodAutoScalers(namespace).patch(params, payload).$promise;
|
||||||
// return data;
|
return data;
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// throw new PortainerError('Unable to patch horizontalPodAutoScaler', err);
|
throw new PortainerError('Unable to patch horizontalPodAutoScaler', err);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// patch(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
patch(oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler) {
|
||||||
// return this.$async(this.patchAsync, oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
return this.$async(this.patchAsync, oldHorizontalPodAutoScaler, newHorizontalPodAutoScaler);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * DELETE
|
* DELETE
|
||||||
// */
|
*/
|
||||||
// async deleteAsync(horizontalPodAutoScaler) {
|
async deleteAsync(horizontalPodAutoScaler) {
|
||||||
// try {
|
try {
|
||||||
// const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
// params.id = horizontalPodAutoScaler.Name;
|
params.id = horizontalPodAutoScaler.Name;
|
||||||
// const namespace = horizontalPodAutoScaler.Namespace;
|
const namespace = horizontalPodAutoScaler.Namespace;
|
||||||
// await this.KubernetesHorizontalPodAutoScalers(namespace).delete(params).$promise;
|
await this.KubernetesHorizontalPodAutoScalers(namespace).delete(params).$promise;
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// throw new PortainerError('Unable to remove horizontalPodAutoScaler', err);
|
throw new PortainerError('Unable to remove horizontalPodAutoScaler', err);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// delete(horizontalPodAutoScaler) {
|
delete(horizontalPodAutoScaler) {
|
||||||
// return this.$async(this.deleteAsync, horizontalPodAutoScaler);
|
return this.$async(this.deleteAsync, horizontalPodAutoScaler);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * ROLLBACK
|
// * ROLLBACK
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationPublishingTypes, KubernetesApplicationDataAccessPolicies } from './models';
|
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationPublishingTypes } from './models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KubernetesApplicationFormValues Model
|
* KubernetesApplicationFormValues Model
|
||||||
|
@ -21,6 +21,7 @@ const _KubernetesApplicationFormValues = Object.freeze({
|
||||||
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
||||||
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
||||||
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
||||||
|
AutoScaler: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export class KubernetesApplicationFormValues {
|
export class KubernetesApplicationFormValues {
|
||||||
|
@ -116,3 +117,20 @@ export class KubernetesApplicationPublishedPortFormValue {
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationPublishedPortFormValue)));
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationPublishedPortFormValue)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KubernetesApplicationAutoScalerFormValue Model
|
||||||
|
*/
|
||||||
|
const _KubernetesApplicationAutoScalerFormValue = Object.freeze({
|
||||||
|
MinReplicas: 0,
|
||||||
|
MaxReplicas: 0,
|
||||||
|
TargetCPUUtilization: 50,
|
||||||
|
ApiVersion: '',
|
||||||
|
IsUsed: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class KubernetesApplicationAutoScalerFormValue {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationAutoScalerFormValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
|
||||||
import { KubernetesApplication } from 'Kubernetes/models/application/models';
|
import { KubernetesApplication } from 'Kubernetes/models/application/models';
|
||||||
import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
||||||
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
||||||
|
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||||
|
|
||||||
class KubernetesApplicationService {
|
class KubernetesApplicationService {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -141,13 +142,14 @@ class KubernetesApplicationService {
|
||||||
|
|
||||||
const res = await Promise.all(
|
const res = await Promise.all(
|
||||||
_.map(namespaces, async (ns) => {
|
_.map(namespaces, async (ns) => {
|
||||||
const [deployments, daemonSets, statefulSets, services, pods, ingresses] = await Promise.all([
|
const [deployments, daemonSets, statefulSets, services, pods, ingresses, autoScalers] = await Promise.all([
|
||||||
this.KubernetesDeploymentService.get(ns),
|
this.KubernetesDeploymentService.get(ns),
|
||||||
this.KubernetesDaemonSetService.get(ns),
|
this.KubernetesDaemonSetService.get(ns),
|
||||||
this.KubernetesStatefulSetService.get(ns),
|
this.KubernetesStatefulSetService.get(ns),
|
||||||
this.KubernetesServiceService.get(ns),
|
this.KubernetesServiceService.get(ns),
|
||||||
this.KubernetesPodService.get(ns),
|
this.KubernetesPodService.get(ns),
|
||||||
this.KubernetesIngressService.get(ns),
|
this.KubernetesIngressService.get(ns),
|
||||||
|
this.KubernetesHorizontalPodAutoScalerService.get(ns),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const deploymentApplications = _.map(deployments, (item) =>
|
const deploymentApplications = _.map(deployments, (item) =>
|
||||||
|
@ -160,7 +162,15 @@ class KubernetesApplicationService {
|
||||||
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
|
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
|
||||||
);
|
);
|
||||||
|
|
||||||
return _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications);
|
const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications);
|
||||||
|
await Promise.all(
|
||||||
|
_.forEach(applications, async (application) => {
|
||||||
|
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
|
||||||
|
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(ns, boundScaler.Name) : undefined;
|
||||||
|
application.AutoScaler = scaler;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return applications;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return _.flatten(res);
|
return _.flatten(res);
|
||||||
|
@ -206,6 +216,12 @@ class KubernetesApplicationService {
|
||||||
await Promise.all(_.without(claimPromises, undefined));
|
await Promise.all(_.without(claimPromises, undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formValues.AutoScaler.IsUsed) {
|
||||||
|
const kind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(app);
|
||||||
|
const autoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(formValues, kind);
|
||||||
|
await this.KubernetesHorizontalPodAutoScalerService.create(autoScaler);
|
||||||
|
}
|
||||||
|
|
||||||
await apiService.create(app);
|
await apiService.create(app);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -257,6 +273,20 @@ class KubernetesApplicationService {
|
||||||
} else if (oldService && !newService) {
|
} else if (oldService && !newService) {
|
||||||
await this.KubernetesServiceService.delete(oldService);
|
await this.KubernetesServiceService.delete(oldService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(newApp);
|
||||||
|
const newAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(newFormValues, newKind);
|
||||||
|
if (_.isEmpty(oldFormValues.AutoScaler)) {
|
||||||
|
await this.KubernetesHorizontalPodAutoScalerService.create(newAutoScaler);
|
||||||
|
} else {
|
||||||
|
const oldKind = KubernetesHorizontalPodAutoScalerHelper.getApplicationTypeString(oldApp);
|
||||||
|
const oldAutoScaler = KubernetesHorizontalPodAutoScalerConverter.applicationFormValuesToModel(oldFormValues, oldKind);
|
||||||
|
if (newFormValues.AutoScaler.IsUsed) {
|
||||||
|
await this.KubernetesHorizontalPodAutoScalerService.patch(oldAutoScaler, newAutoScaler);
|
||||||
|
} else {
|
||||||
|
await this.KubernetesHorizontalPodAutoScalerService.delete(oldAutoScaler);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -319,6 +349,10 @@ class KubernetesApplicationService {
|
||||||
if (application.ServiceType) {
|
if (application.ServiceType) {
|
||||||
await this.KubernetesServiceService.delete(servicePayload);
|
await this.KubernetesServiceService.delete(servicePayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_.isEmpty(application.AutoScaler)) {
|
||||||
|
await this.KubernetesHorizontalPodAutoScalerService.delete(application.AutoScaler);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -635,7 +635,13 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="ctrl.supportGlobalDeployment()">
|
<div ng-if="ctrl.supportGlobalDeployment()">
|
||||||
<input type="radio" id="deployment_global" ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL" ng-model="ctrl.formValues.DeploymentType" />
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="deployment_global"
|
||||||
|
ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL"
|
||||||
|
ng-model="ctrl.formValues.DeploymentType"
|
||||||
|
ng-click="ctrl.unselectAutoScaler()"
|
||||||
|
/>
|
||||||
<label for="deployment_global">
|
<label for="deployment_global">
|
||||||
<div class="boxselector_header">
|
<div class="boxselector_header">
|
||||||
<i class="fa fa-cubes" aria-hidden="true" style="margin-right: 2px;"></i>
|
<i class="fa fa-cubes" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
@ -695,6 +701,116 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- auto scaling -->
|
||||||
|
<div class="col-sm-12 form-section-title" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL">
|
||||||
|
Auto-scaling
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.state.useServerMetrics">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label for="enable_auto_scaling" class="control-label text-left">
|
||||||
|
Enable auto scaling for this application
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" class="form-control" name="enable_auto_scaling" ng-model="ctrl.formValues.AutoScaler.IsUsed" />
|
||||||
|
<i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && !ctrl.state.useServerMetrics">
|
||||||
|
<div class="col-sm-12 small text-muted">
|
||||||
|
<p ng-if="!ctrl.isAdmin">
|
||||||
|
This feature is currently disabled and must be enabled by an administrator user.
|
||||||
|
</p>
|
||||||
|
<p ng-if="ctrl.isAdmin">
|
||||||
|
Server metrics features must be enabled in the
|
||||||
|
<a ui-sref="portainer.endpoints.endpoint.kubernetesConfig({id: ctrl.endpoint.Id})" class="ctrl.isAdmin">endpoint configuration view</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-inline" ng-if="ctrl.formValues.AutoScaler.IsUsed">
|
||||||
|
<table class="table" style="margin-bottom: 0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr class="small">
|
||||||
|
<td style="width: 33%; border: none; padding: 2px 0 2px 0;">Minimum instances</td>
|
||||||
|
<td style="width: 33%; border: none; padding: 2px 0 2px 0;">Maximum instances</td>
|
||||||
|
<td style="width: 33%; border: none; padding: 2px 0 2px 0;">
|
||||||
|
Target CPU usage (<b>%</b>)
|
||||||
|
<portainer-tooltip position="bottom" message="The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.">
|
||||||
|
</portainer-tooltip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px 5px 5px 0; border: none;">
|
||||||
|
<div class="input-group input-group-sm" style="width: 100%;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
name="auto_scaler_min"
|
||||||
|
min="0"
|
||||||
|
ng-max="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||||
|
ng-model="ctrl.formValues.AutoScaler.MinReplicas"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_min'].$invalid">
|
||||||
|
<div class="small text-warning" style="margin-top: 5px;">
|
||||||
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_min'].$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum instances is required.</p>
|
||||||
|
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum instances must be greater than 0.</p>
|
||||||
|
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Minimum instances must be smaller than maximum instances.</p>
|
||||||
|
</ng-messages>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 5px 5px 0; border: none;">
|
||||||
|
<div class="input-group input-group-sm" style="width: 100%;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
name="auto_scaler_max"
|
||||||
|
ng-min="ctrl.formValues.AutoScaler.MinReplicas"
|
||||||
|
ng-model="ctrl.formValues.AutoScaler.MaxReplicas"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_max'].$invalid || ctrl.autoScalerOverflow()">
|
||||||
|
<div class="small text-warning" style="margin-top: 5px;">
|
||||||
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_max'].$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum instances is required.</p>
|
||||||
|
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Maximum instances must be greater than minimum instances.</p>
|
||||||
|
</ng-messages>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px 5px 5px 0; border: none;">
|
||||||
|
<div class="input-group input-group-sm" style="width: 100%;">
|
||||||
|
<input type="number" class="form-control" name="auto_scaler_cpu" ng-model="ctrl.formValues.AutoScaler.TargetCPUUtilization" min="1" max="100" required />
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm" ng-show="kubernetesApplicationCreationForm['auto_scaler_cpu'].$invalid">
|
||||||
|
<div class="small text-warning" style="margin-top: 5px;">
|
||||||
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_cpu'].$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Target CPU usage is required.</p>
|
||||||
|
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Target CPU usage must be greater than 0.</p>
|
||||||
|
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Target CPU usage must be smaller than 100.</p>
|
||||||
|
</ng-messages>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="form-group" ng-if="ctrl.autoScalerOverflow()" style="margin-bottom: 10px;">
|
||||||
|
<div class="col-sm-12 small text-muted">
|
||||||
|
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !auto scaling -->
|
||||||
|
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Publishing the application
|
Publishing the application
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHel
|
||||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||||
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application/index';
|
||||||
|
|
||||||
class KubernetesCreateApplicationController {
|
class KubernetesCreateApplicationController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -80,6 +81,16 @@ class KubernetesCreateApplicationController {
|
||||||
this.state.alreadyExists = (this.state.isEdit && existingApplication && this.application.Id !== existingApplication.Id) || (!this.state.isEdit && existingApplication);
|
this.state.alreadyExists = (this.state.isEdit && existingApplication && this.application.Id !== existingApplication.Id) || (!this.state.isEdit && existingApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTO SCALER UI MANAGEMENT
|
||||||
|
*/
|
||||||
|
|
||||||
|
unselectAutoScaler() {
|
||||||
|
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL) {
|
||||||
|
this.formValues.AutoScaler.IsUsed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CONFIGURATION UI MANAGEMENT
|
* CONFIGURATION UI MANAGEMENT
|
||||||
*/
|
*/
|
||||||
|
@ -319,6 +330,24 @@ class KubernetesCreateApplicationController {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoScalerOverflow() {
|
||||||
|
const instances = this.formValues.AutoScaler.MaxReplicas;
|
||||||
|
const cpu = this.formValues.CpuLimit;
|
||||||
|
const maxCpu = this.state.sliders.cpu.max;
|
||||||
|
const memory = this.formValues.MemoryLimit;
|
||||||
|
const maxMemory = this.state.sliders.memory.max;
|
||||||
|
|
||||||
|
if (cpu * instances > maxCpu) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memory * instances > maxMemory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
publishViaLoadBalancerEnabled() {
|
publishViaLoadBalancerEnabled() {
|
||||||
return this.state.useLoadBalancer;
|
return this.state.useLoadBalancer;
|
||||||
}
|
}
|
||||||
|
@ -345,11 +374,12 @@ class KubernetesCreateApplicationController {
|
||||||
|
|
||||||
isDeployUpdateButtonDisabled() {
|
isDeployUpdateButtonDisabled() {
|
||||||
const overflow = this.resourceReservationsOverflow();
|
const overflow = this.resourceReservationsOverflow();
|
||||||
|
const autoScalerOverflow = this.autoScalerOverflow();
|
||||||
const inProgress = this.state.actionInProgress;
|
const inProgress = this.state.actionInProgress;
|
||||||
const invalid = !this.isValid();
|
const invalid = !this.isValid();
|
||||||
const hasNoChanges = this.isEditAndNoChangesMade();
|
const hasNoChanges = this.isEditAndNoChangesMade();
|
||||||
const nonScalable = this.isNonScalable();
|
const nonScalable = this.isNonScalable();
|
||||||
const res = overflow || inProgress || invalid || hasNoChanges || nonScalable;
|
const res = overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +579,7 @@ class KubernetesCreateApplicationController {
|
||||||
this.state = {
|
this.state = {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
useLoadBalancer: false,
|
useLoadBalancer: false,
|
||||||
|
useServerMetrics: false,
|
||||||
sliders: {
|
sliders: {
|
||||||
cpu: {
|
cpu: {
|
||||||
min: 0,
|
min: 0,
|
||||||
|
@ -580,6 +611,8 @@ class KubernetesCreateApplicationController {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.isAdmin = this.Authentication.isAdmin();
|
||||||
|
|
||||||
this.editChanges = [];
|
this.editChanges = [];
|
||||||
|
|
||||||
if (this.$transition$.params().namespace && this.$transition$.params().name) {
|
if (this.$transition$.params().namespace && this.$transition$.params().name) {
|
||||||
|
@ -587,8 +620,10 @@ class KubernetesCreateApplicationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = this.EndpointProvider.currentEndpoint();
|
const endpoint = this.EndpointProvider.currentEndpoint();
|
||||||
|
this.endpoint = endpoint;
|
||||||
this.storageClasses = endpoint.Kubernetes.Configuration.StorageClasses;
|
this.storageClasses = endpoint.Kubernetes.Configuration.StorageClasses;
|
||||||
this.state.useLoadBalancer = endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
this.state.useLoadBalancer = endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
||||||
|
this.state.useServerMetrics = endpoint.Kubernetes.Configuration.UseServerMetrics;
|
||||||
|
|
||||||
this.formValues = new KubernetesApplicationFormValues();
|
this.formValues = new KubernetesApplicationFormValues();
|
||||||
|
|
||||||
|
@ -611,6 +646,10 @@ class KubernetesCreateApplicationController {
|
||||||
this.formValues = KubernetesApplicationConverter.applicationToFormValues(this.application, this.resourcePools, this.configurations, this.persistentVolumeClaims);
|
this.formValues = KubernetesApplicationConverter.applicationToFormValues(this.application, this.resourcePools, this.configurations, this.persistentVolumeClaims);
|
||||||
this.savedFormValues = angular.copy(this.formValues);
|
this.savedFormValues = angular.copy(this.formValues);
|
||||||
delete this.formValues.ApplicationType;
|
delete this.formValues.ApplicationType;
|
||||||
|
} else {
|
||||||
|
this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler();
|
||||||
|
this.formValues.AutoScaler.MinReplicas = this.formValues.ReplicaCount;
|
||||||
|
this.formValues.AutoScaler.MaxReplicas = this.formValues.ReplicaCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateSliders();
|
await this.updateSliders();
|
||||||
|
|
|
@ -355,7 +355,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ ctrl.application.AutoScaler.MinReplicas }}</td>
|
<td>{{ ctrl.application.AutoScaler.MinReplicas }}</td>
|
||||||
<td>{{ ctrl.application.AutoScaler.MaxReplicas }}</td>
|
<td>{{ ctrl.application.AutoScaler.MaxReplicas }}</td>
|
||||||
<td>{{ ctrl.application.AutoScaler.TargetCPUUtilizationPercentage }}%</td>
|
<td>{{ ctrl.application.AutoScaler.TargetCPUUtilization }}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -13,10 +13,11 @@
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Expose applications over external IP addresses
|
Expose applications over external IP addresses
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span class="col-sm-12 text-muted small">
|
<span class="col-sm-12 text-muted small">
|
||||||
Enabling this feature will allow users to expose application they deploy over an external IP address assigned by cloud provider.
|
Enabling this feature will allow users to expose application they deploy over an external IP address assigned by cloud provider.
|
||||||
<p>
|
<p style="margin-top: 2px;">
|
||||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
Ensure that your cloud provider allows you to create load balancers if you want to use this feature. Might incur costs.
|
Ensure that your cloud provider allows you to create load balancers if you want to use this feature. Might incur costs.
|
||||||
</p>
|
</p>
|
||||||
|
@ -31,6 +32,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Metrics
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<span class="col-sm-12 text-muted small">
|
||||||
|
Enabling this feature will allow users to use specific features that leverage the server metrics component.
|
||||||
|
<p style="margin-top: 2px;">
|
||||||
|
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Ensure that <a href="https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/#metrics-server" target="_blank">server metrics</a> is
|
||||||
|
running inside your cluster.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label class="control-label text-left">
|
||||||
|
Enable features using server metrics
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="ctrl.formValues.UseServerMetrics" /><i></i> </label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Available storage options
|
Available storage options
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -50,12 +50,14 @@ class KubernetesConfigureController {
|
||||||
|
|
||||||
this.endpoint.Kubernetes.Configuration.StorageClasses = classes;
|
this.endpoint.Kubernetes.Configuration.StorageClasses = classes;
|
||||||
this.endpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
this.endpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
||||||
|
this.endpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
|
||||||
await this.EndpointService.updateEndpoint(this.endpoint.Id, this.endpoint);
|
await this.EndpointService.updateEndpoint(this.endpoint.Id, this.endpoint);
|
||||||
const endpoints = this.EndpointProvider.endpoints();
|
const endpoints = this.EndpointProvider.endpoints();
|
||||||
const modifiedEndpoint = _.find(endpoints, (item) => item.Id === this.endpoint.Id);
|
const modifiedEndpoint = _.find(endpoints, (item) => item.Id === this.endpoint.Id);
|
||||||
if (modifiedEndpoint) {
|
if (modifiedEndpoint) {
|
||||||
modifiedEndpoint.Kubernetes.Configuration.StorageClasses = classes;
|
modifiedEndpoint.Kubernetes.Configuration.StorageClasses = classes;
|
||||||
modifiedEndpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
modifiedEndpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
|
||||||
|
modifiedEndpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
|
||||||
this.EndpointProvider.setEndpoints(endpoints);
|
this.EndpointProvider.setEndpoints(endpoints);
|
||||||
}
|
}
|
||||||
this.Notifications.success('Configuration successfully applied');
|
this.Notifications.success('Configuration successfully applied');
|
||||||
|
@ -80,6 +82,7 @@ class KubernetesConfigureController {
|
||||||
|
|
||||||
this.formValues = {
|
this.formValues = {
|
||||||
UseLoadBalancer: false,
|
UseLoadBalancer: false,
|
||||||
|
UseServerMetrics: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -100,6 +103,7 @@ class KubernetesConfigureController {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.formValues.UseLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
this.formValues.UseLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
|
||||||
|
this.formValues.UseServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve storage classes');
|
this.Notifications.error('Failure', err, 'Unable to retrieve storage classes');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in New Issue