1
0
Fork 0
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:
Thomas Krzero 2017-10-04 08:39:59 +02:00 committed by Anthony Lapenna
parent 79121f9977
commit f3a1250b27
5 changed files with 157 additions and 36 deletions

View file

@ -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;

View file

@ -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>

View file

@ -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();

View file

@ -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>

View file

@ -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';