mirror of
https://github.com/portainer/portainer.git
synced 2025-07-22 14:59:41 +02:00
feat(container-creation) - Add container resource management (#1224)
This commit is contained in:
parent
79121f9977
commit
f3a1250b27
5 changed files with 157 additions and 36 deletions
|
@ -1,8 +1,8 @@
|
||||||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||||
// See app/components/templates/templatesController.js as a reference.
|
// See app/components/templates/templatesController.js as a reference.
|
||||||
angular.module('createContainer', [])
|
angular.module('createContainer', [])
|
||||||
.controller('CreateContainerController', ['$q', '$scope', '$state', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SettingsService',
|
.controller('CreateContainerController', ['$q', '$scope', '$state', '$timeout', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SystemService', 'SettingsService',
|
||||||
function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SettingsService) {
|
function ($q, $scope, $state, $timeout, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService) {
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
alwaysPull: true,
|
alwaysPull: true,
|
||||||
|
@ -13,13 +13,22 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
ExtraHosts: [],
|
ExtraHosts: [],
|
||||||
IPv4: '',
|
IPv4: '',
|
||||||
IPv6: '',
|
IPv6: '',
|
||||||
AccessControlData: new AccessControlFormData()
|
AccessControlData: new AccessControlFormData(),
|
||||||
|
CpuLimit: 0,
|
||||||
|
MemoryLimit: 0,
|
||||||
|
MemoryReservation: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
formValidationError: ''
|
formValidationError: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.refreshSlider = function () {
|
||||||
|
$timeout(function () {
|
||||||
|
$scope.$broadcast('rzSliderForceRender');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
Image: '',
|
Image: '',
|
||||||
Env: [],
|
Env: [],
|
||||||
|
@ -221,6 +230,25 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
config.HostConfig.Devices = path;
|
config.HostConfig.Devices = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareResources(config) {
|
||||||
|
// Memory Limit - Round to 0.125
|
||||||
|
var memoryLimit = (Math.round($scope.formValues.MemoryLimit * 8) / 8).toFixed(3);
|
||||||
|
memoryLimit *= 1024 * 1024;
|
||||||
|
if (memoryLimit > 0) {
|
||||||
|
config.HostConfig.Memory = memoryLimit;
|
||||||
|
}
|
||||||
|
// Memory Resevation - Round to 0.125
|
||||||
|
var memoryReservation = (Math.round($scope.formValues.MemoryReservation * 8) / 8).toFixed(3);
|
||||||
|
memoryReservation *= 1024 * 1024;
|
||||||
|
if (memoryReservation > 0) {
|
||||||
|
config.HostConfig.MemoryReservation = memoryReservation;
|
||||||
|
}
|
||||||
|
// CPU Limit
|
||||||
|
if ($scope.formValues.CpuLimit > 0) {
|
||||||
|
config.HostConfig.NanoCpus = $scope.formValues.CpuLimit * 1000000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function prepareConfiguration() {
|
function prepareConfiguration() {
|
||||||
var config = angular.copy($scope.config);
|
var config = angular.copy($scope.config);
|
||||||
config.Cmd = ContainerHelper.commandStringToArray(config.Cmd);
|
config.Cmd = ContainerHelper.commandStringToArray(config.Cmd);
|
||||||
|
@ -232,6 +260,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
prepareVolumes(config);
|
prepareVolumes(config);
|
||||||
prepareLabels(config);
|
prepareLabels(config);
|
||||||
prepareDevices(config);
|
prepareDevices(config);
|
||||||
|
prepareResources(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +445,18 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadFromContainerResources(d) {
|
||||||
|
if (d.HostConfig.NanoCpus) {
|
||||||
|
$scope.formValues.CpuLimit = d.HostConfig.NanoCpus / 1000000000;
|
||||||
|
}
|
||||||
|
if (d.HostConfig.Memory) {
|
||||||
|
$scope.formValues.MemoryLimit = d.HostConfig.Memory / 1024 / 1024;
|
||||||
|
}
|
||||||
|
if (d.HostConfig.MemoryReservation) {
|
||||||
|
$scope.formValues.MemoryReservation = d.HostConfig.MemoryReservation / 1024 / 1024;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadFromContainerSpec() {
|
function loadFromContainerSpec() {
|
||||||
// Get container
|
// Get container
|
||||||
Container.get({ id: $transition$.params().from }).$promise
|
Container.get({ id: $transition$.params().from }).$promise
|
||||||
|
@ -435,6 +476,7 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
loadFromContainerConsole(d);
|
loadFromContainerConsole(d);
|
||||||
loadFromContainerDevices(d);
|
loadFromContainerDevices(d);
|
||||||
loadFromContainerImageConfig(d);
|
loadFromContainerImageConfig(d);
|
||||||
|
loadFromContainerResources(d);
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve container');
|
Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||||
|
@ -482,6 +524,21 @@ function ($q, $scope, $state, $transition$, $filter, Container, ContainerHelper,
|
||||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SystemService.info()
|
||||||
|
.then(function success(data) {
|
||||||
|
$scope.state.sliderMaxCpu = 32;
|
||||||
|
if (data.NCPU) {
|
||||||
|
$scope.state.sliderMaxCpu = data.NCPU;
|
||||||
|
}
|
||||||
|
$scope.state.sliderMaxMemory = 32768;
|
||||||
|
if (data.MemTotal) {
|
||||||
|
$scope.state.sliderMaxMemory = Math.floor(data.MemTotal / 1000 / 1000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve engine details');
|
||||||
|
});
|
||||||
|
|
||||||
SettingsService.publicSettings()
|
SettingsService.publicSettings()
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
$scope.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
<li class="interactive"><a data-target="#env" data-toggle="tab">Env</a></li>
|
<li class="interactive"><a data-target="#env" data-toggle="tab">Env</a></li>
|
||||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||||
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
||||||
<li class="interactive"><a data-target="#runtime" data-toggle="tab">Runtime</a></li>
|
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- tab-content -->
|
<!-- tab-content -->
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
@ -466,9 +466,12 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tab-restart-policy -->
|
<!-- !tab-restart-policy -->
|
||||||
<!-- tab-runtime -->
|
<!-- tab-runtime-resources -->
|
||||||
<div class="tab-pane" id="runtime">
|
<div class="tab-pane" id="runtime-resources">
|
||||||
<form class="form-horizontal" style="margin-top: 15px;">
|
<form class="form-horizontal" style="margin-top: 15px;">
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Runtime
|
||||||
|
</div>
|
||||||
<!-- privileged-mode -->
|
<!-- privileged-mode -->
|
||||||
<div class="form-group" ng-if="isAdmin || allowPrivilegedMode">
|
<div class="form-group" ng-if="isAdmin || allowPrivilegedMode">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
@ -510,10 +513,63 @@
|
||||||
<!-- !devices-input-list -->
|
<!-- !devices-input-list -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !devices-->
|
<!-- !devices-->
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Resources
|
||||||
|
</div>
|
||||||
|
<!-- memory-reservation-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="memory-reservation" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
|
Memory reservation
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<p class="small text-muted" style="margin-top: 7px;">
|
||||||
|
Memory soft limit (<b>MB</b>)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !memory-reservation-input -->
|
||||||
|
<!-- memory-limit-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
|
Memory limit
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<p class="small text-muted" style="margin-top: 7px;">
|
||||||
|
Memory limit (<b>MB</b>)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !memory-limit-input -->
|
||||||
|
<!-- cpu-limit-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
|
CPU limit
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<por-slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></por-slider>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4" style="margin-top: 20px;">
|
||||||
|
<p class="small text-muted">
|
||||||
|
Maximum CPU usage
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !cpu-limit-input -->
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- !tab-runtime -->
|
<!-- !tab-runtime-resources -->
|
||||||
</div>
|
</div>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
</rd-widget>
|
</rd-widget>
|
||||||
|
|
|
@ -352,6 +352,29 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, SecretHelper, Se
|
||||||
createNewService(config, accessControlData);
|
createNewService(config, accessControlData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function initSlidersMaxValuesBasedOnNodeData(nodes) {
|
||||||
|
var maxCpus = 0;
|
||||||
|
var maxMemory = 0;
|
||||||
|
for (var n in nodes) {
|
||||||
|
if (nodes[n].CPUs && nodes[n].CPUs > maxCpus) {
|
||||||
|
maxCpus = nodes[n].CPUs;
|
||||||
|
}
|
||||||
|
if (nodes[n].Memory && nodes[n].Memory > maxMemory) {
|
||||||
|
maxMemory = nodes[n].Memory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxCpus > 0) {
|
||||||
|
$scope.state.sliderMaxCpu = maxCpus / 1000000000;
|
||||||
|
} else {
|
||||||
|
$scope.state.sliderMaxCpu = 32;
|
||||||
|
}
|
||||||
|
if (maxMemory > 0) {
|
||||||
|
$scope.state.sliderMaxMemory = Math.floor(maxMemory / 1000 / 1000);
|
||||||
|
} else {
|
||||||
|
$scope.state.sliderMaxMemory = 32768;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
$('#loadingViewSpinner').show();
|
$('#loadingViewSpinner').show();
|
||||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||||
|
@ -368,18 +391,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, SecretHelper, Se
|
||||||
$scope.availableVolumes = data.volumes;
|
$scope.availableVolumes = data.volumes;
|
||||||
$scope.availableNetworks = data.networks;
|
$scope.availableNetworks = data.networks;
|
||||||
$scope.availableSecrets = data.secrets;
|
$scope.availableSecrets = data.secrets;
|
||||||
// Set max cpu value
|
var nodes = data.nodes;
|
||||||
var maxCpus = 0;
|
initSlidersMaxValuesBasedOnNodeData(nodes);
|
||||||
for (var n in data.nodes) {
|
|
||||||
if (data.nodes[n].CPUs && data.nodes[n].CPUs > maxCpus) {
|
|
||||||
maxCpus = data.nodes[n].CPUs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxCpus > 0) {
|
|
||||||
$scope.state.sliderMaxCpu = maxCpus / 1000000000;
|
|
||||||
} else {
|
|
||||||
$scope.state.sliderMaxCpu = 32;
|
|
||||||
}
|
|
||||||
var settings = data.settings;
|
var settings = data.settings;
|
||||||
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
||||||
var userDetails = Authentication.getUserDetails();
|
var userDetails = Authentication.getUserDetails();
|
||||||
|
|
|
@ -4,42 +4,36 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- memory-reservation-input -->
|
<!-- memory-reservation-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="memory-reservation" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="memory-reservation" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
Memory reservation
|
Memory reservation
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<input type="number" step="0.125" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation" placeholder="e.g. 64">
|
<por-slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<select class="form-control" ng-model="formValues.MemoryReservationUnit">
|
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation">
|
||||||
<option value="MB">MB</option>
|
|
||||||
<option value="GB">GB</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<p class="small text-muted">
|
<p class="small text-muted" style="margin-top: 7px;">
|
||||||
Minimum memory available on a node to run a task
|
Minimum memory available on a node to run a task (<b>MB</b>)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !memory-reservation-input -->
|
<!-- !memory-reservation-input -->
|
||||||
<!-- memory-limit-input -->
|
<!-- memory-limit-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
Memory limit
|
Memory limit
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<input type="number" step="0.125" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit" placeholder="e.g. 128">
|
<por-slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></por-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<select class="form-control" ng-model="formValues.MemoryLimitUnit">
|
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit">
|
||||||
<option value="MB">MB</option>
|
|
||||||
<option value="GB">GB</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4" style="margin-top: 7px;">
|
||||||
<p class="small text-muted">
|
<p class="small text-muted">
|
||||||
Maximum memory usage per task (set to 0 for unlimited)
|
Maximum memory usage per task (<b>MB</b>)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,6 +8,7 @@ angular.module('portainer')
|
||||||
step: ctrl.step,
|
step: ctrl.step,
|
||||||
precision: ctrl.precision,
|
precision: ctrl.precision,
|
||||||
showSelectionBar: true,
|
showSelectionBar: true,
|
||||||
|
enforceStep: false,
|
||||||
translate: function(value, sliderId, label) {
|
translate: function(value, sliderId, label) {
|
||||||
if (label === 'floor' || value === 0) {
|
if (label === 'floor' || value === 0) {
|
||||||
return 'unlimited';
|
return 'unlimited';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue