feat(resourcequotas): reduce resource quota requests [EE-4757] (#8420)

pull/8445/head
Ali 2023-02-10 18:28:53 +13:00 committed by GitHub
parent 44d69f3a3f
commit 9f6702d0b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 48 deletions

View File

@ -1,5 +1,3 @@
import _ from 'lodash-es';
import angular from 'angular'; import angular from 'angular';
import PortainerError from 'Portainer/error'; import PortainerError from 'Portainer/error';
import { KubernetesCommonParams } from 'Kubernetes/models/common/params'; import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
@ -65,10 +63,13 @@ class KubernetesNamespaceService {
async getAllAsync() { async getAllAsync() {
try { try {
// get the list of all namespaces (RBAC allows users to see the list of namespaces)
const data = await this.KubernetesNamespaces().get().$promise; const data = await this.KubernetesNamespaces().get().$promise;
const promises = _.map(data.items, (item) => this.KubernetesNamespaces().status({ id: item.metadata.name }).$promise); // get the status of each namespace (RBAC will give permission denied for status of unauthorised namespaces)
const promises = data.items.map((item) => this.KubernetesNamespaces().status({ id: item.metadata.name }).$promise);
const namespaces = await $allSettled(promises); const namespaces = await $allSettled(promises);
const allNamespaces = _.map(namespaces.fulfilled, (item) => { // only return namespaces if the user has access to namespaces
const allNamespaces = namespaces.fulfilled.map((item) => {
return KubernetesNamespaceConverter.apiToNamespace(item); return KubernetesNamespaceConverter.apiToNamespace(item);
}); });
updateNamespaces(allNamespaces); updateNamespaces(allNamespaces);

View File

@ -21,27 +21,33 @@ export function KubernetesResourcePoolService(
toggleSystem, toggleSystem,
}; };
async function getOne(name) { // getting quota isn't a costly operation for one namespace, so we can get it by default
async function getOne(name, { getQuota = true }) {
const namespace = await KubernetesNamespaceService.get(name); const namespace = await KubernetesNamespaceService.get(name);
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace); const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
if (quotaAttempt.status === 'fulfilled') { if (getQuota) {
pool.Quota = quotaAttempt.value; const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
pool.Yaml += '---\n' + quotaAttempt.value.Yaml; if (quotaAttempt.status === 'fulfilled') {
pool.Quota = quotaAttempt.value;
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
}
} }
return pool; return pool;
} }
async function getAll() { // getting the quota for all namespaces is costly by default, so disable getting it by default
async function getAll({ getQuota = false }) {
const namespaces = await KubernetesNamespaceService.get(); const namespaces = await KubernetesNamespaceService.get();
const pools = await Promise.all( const pools = await Promise.all(
_.map(namespaces, async (namespace) => { _.map(namespaces, async (namespace) => {
const name = namespace.Name; const name = namespace.Name;
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace); const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
if (quotaAttempt.status === 'fulfilled') { if (getQuota) {
pool.Quota = quotaAttempt.value; const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
pool.Yaml += '---\n' + quotaAttempt.value.Yaml; if (quotaAttempt.status === 'fulfilled') {
pool.Quota = quotaAttempt.value;
pool.Yaml += '---\n' + quotaAttempt.value.Yaml;
}
} }
return pool; return pool;
}) })
@ -49,11 +55,11 @@ export function KubernetesResourcePoolService(
return pools; return pools;
} }
function get(name) { function get(name, options = {}) {
if (name) { if (name) {
return $async(getOne, name); return $async(getOne, name, options);
} }
return $async(getAll); return $async(getAll, options);
} }
function create(formValues) { function create(formValues) {

View File

@ -872,8 +872,8 @@ class KubernetesCreateApplicationController {
/* #endregion */ /* #endregion */
/* #region DATA AUTO REFRESH */ /* #region DATA AUTO REFRESH */
updateSliders() { updateSliders(namespaceWithQuota) {
const quota = this.formValues.ResourcePool.Quota; const quota = namespaceWithQuota.Quota;
let minCpu = 0, let minCpu = 0,
minMemory = 0, minMemory = 0,
maxCpu = this.state.namespaceLimits.cpu, maxCpu = this.state.namespaceLimits.cpu,
@ -906,33 +906,36 @@ class KubernetesCreateApplicationController {
} }
} }
updateNamespaceLimits() { updateNamespaceLimits(namespaceWithQuota) {
let maxCpu = this.state.nodes.cpu; return this.$async(async () => {
let maxMemory = this.state.nodes.memory; let maxCpu = this.state.nodes.cpu;
const quota = this.formValues.ResourcePool.Quota; let maxMemory = this.state.nodes.memory;
this.state.resourcePoolHasQuota = false; const quota = namespaceWithQuota.Quota;
if (quota) { this.state.resourcePoolHasQuota = false;
if (quota.CpuLimit) {
this.state.resourcePoolHasQuota = true; if (quota) {
maxCpu = quota.CpuLimit - quota.CpuLimitUsed; if (quota.CpuLimit) {
if (this.state.isEdit && this.savedFormValues.CpuLimit) { this.state.resourcePoolHasQuota = true;
maxCpu += this.savedFormValues.CpuLimit * this.effectiveInstances(); maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
maxCpu += this.savedFormValues.CpuLimit * this.effectiveInstances();
}
}
if (quota.MemoryLimit) {
this.state.resourcePoolHasQuota = true;
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.effectiveInstances();
}
} }
} }
if (quota.MemoryLimit) { this.state.namespaceLimits.cpu = maxCpu;
this.state.resourcePoolHasQuota = true; this.state.namespaceLimits.memory = maxMemory;
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed; });
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
maxMemory += KubernetesResourceReservationHelper.bytesValue(this.savedFormValues.MemoryLimit) * this.effectiveInstances();
}
}
}
this.state.namespaceLimits.cpu = maxCpu;
this.state.namespaceLimits.memory = maxMemory;
} }
refreshStacks(namespace) { refreshStacks(namespace) {
@ -1026,9 +1029,10 @@ class KubernetesCreateApplicationController {
onResourcePoolSelectionChange() { onResourcePoolSelectionChange() {
return this.$async(async () => { return this.$async(async () => {
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.formValues.ResourcePool.Namespace.Name);
const namespace = this.formValues.ResourcePool.Namespace.Name; const namespace = this.formValues.ResourcePool.Namespace.Name;
this.updateNamespaceLimits(); this.updateNamespaceLimits(namespaceWithQuota);
this.updateSliders(); this.updateSliders(namespaceWithQuota);
await this.refreshNamespaceData(namespace); await this.refreshNamespaceData(namespace);
this.resetFormValues(); this.resetFormValues();
}); });
@ -1222,7 +1226,9 @@ class KubernetesCreateApplicationController {
this.allNamespaces = resourcePools.map(({ Namespace }) => Namespace.Name); this.allNamespaces = resourcePools.map(({ Namespace }) => Namespace.Name);
this.resourcePools = _.sortBy(nonSystemNamespaces, ({ Namespace }) => (Namespace.Name === 'default' ? 0 : 1)); this.resourcePools = _.sortBy(nonSystemNamespaces, ({ Namespace }) => (Namespace.Name === 'default' ? 0 : 1));
const namespaceWithQuota = await this.KubernetesResourcePoolService.get(this.resourcePools[0].Namespace.Name);
this.formValues.ResourcePool = this.resourcePools[0]; this.formValues.ResourcePool = this.resourcePools[0];
this.formValues.ResourcePool.Quota = namespaceWithQuota.Quota;
if (!this.formValues.ResourcePool) { if (!this.formValues.ResourcePool) {
return; return;
} }
@ -1289,8 +1295,8 @@ class KubernetesCreateApplicationController {
this.oldFormValues = angular.copy(this.formValues); this.oldFormValues = angular.copy(this.formValues);
this.updateNamespaceLimits(); this.updateNamespaceLimits(namespaceWithQuota);
this.updateSliders(); this.updateSliders(namespaceWithQuota);
} catch (err) { } catch (err) {
this.Notifications.error('Failure', err, 'Unable to load view data'); this.Notifications.error('Failure', err, 'Unable to load view data');
} finally { } finally {

View File

@ -151,7 +151,7 @@ class KubernetesCreateResourcePoolController {
getResourcePools() { getResourcePools() {
return this.$async(async () => { return this.$async(async () => {
try { try {
this.resourcePools = await this.KubernetesResourcePoolService.get(); this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
} catch (err) { } catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve namespaces'); this.Notifications.error('Failure', err, 'Unable to retrieve namespaces');
} }

View File

@ -378,7 +378,7 @@ class KubernetesResourcePoolController {
const name = this.$state.params.id; const name = this.$state.params.id;
const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get()]); const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get('', { getQuota: true })]);
this.ingressControllers = []; this.ingressControllers = [];
if (this.state.ingressAvailabilityPerNamespace) { if (this.state.ingressAvailabilityPerNamespace) {

View File

@ -62,7 +62,7 @@ class KubernetesResourcePoolsController {
async getResourcePoolsAsync() { async getResourcePoolsAsync() {
try { try {
this.resourcePools = await this.KubernetesResourcePoolService.get(); this.resourcePools = await this.KubernetesResourcePoolService.get('', { getQuota: true });
} catch (err) { } catch (err) {
this.Notifications.error('Failure', err, 'Unable to retreive namespaces'); this.Notifications.error('Failure', err, 'Unable to retreive namespaces');
} }