mirror of https://github.com/portainer/portainer
feat(docker): allow docker container resource settings without restart EE-1942 (#6065)
Co-authored-by: sam <sam@allofword> Co-authored-by: sam@gemibook <huapox@126.com> Co-authored-by: Prabhat Khera <prabhat.khera@gmail.com>pull/6079/head
parent
c267355759
commit
1e80061186
|
@ -90,11 +90,26 @@ angular.module('portainer.docker').factory('ContainerService', [
|
||||||
};
|
};
|
||||||
|
|
||||||
service.updateRestartPolicy = updateRestartPolicy;
|
service.updateRestartPolicy = updateRestartPolicy;
|
||||||
|
service.updateLimits = updateLimits;
|
||||||
|
|
||||||
function updateRestartPolicy(id, restartPolicy, maximumRetryCounts) {
|
function updateRestartPolicy(id, restartPolicy, maximumRetryCounts) {
|
||||||
return Container.update({ id: id }, { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } }).$promise;
|
return Container.update({ id: id }, { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } }).$promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateLimits(id, config) {
|
||||||
|
return Container.update(
|
||||||
|
{ id: id },
|
||||||
|
{
|
||||||
|
// MemorySwap: must be set
|
||||||
|
// -1: non limits, 0: treated as unset(cause update error).
|
||||||
|
MemoryReservation: config.HostConfig.MemoryReservation,
|
||||||
|
Memory: config.HostConfig.Memory,
|
||||||
|
MemorySwap: -1,
|
||||||
|
NanoCpus: config.HostConfig.NanoCpus,
|
||||||
|
}
|
||||||
|
).$promise;
|
||||||
|
}
|
||||||
|
|
||||||
service.createContainer = function (configuration) {
|
service.createContainer = function (configuration) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
Container.create(configuration)
|
Container.create(configuration)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { ContainerCapabilities, ContainerCapability } from '../../../models/cont
|
||||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||||
|
|
||||||
|
import './createcontainer.css';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('CreateContainerController', [
|
angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
'$q',
|
'$q',
|
||||||
'$scope',
|
'$scope',
|
||||||
|
@ -61,6 +63,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
endpoint
|
endpoint
|
||||||
) {
|
) {
|
||||||
$scope.create = create;
|
$scope.create = create;
|
||||||
|
$scope.update = update;
|
||||||
$scope.endpoint = endpoint;
|
$scope.endpoint = endpoint;
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
|
@ -97,6 +100,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
mode: '',
|
mode: '',
|
||||||
pullImageValidity: true,
|
pullImageValidity: true,
|
||||||
|
settingUnlimitedResources: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.handleEnvVarChange = handleEnvVarChange;
|
$scope.handleEnvVarChange = handleEnvVarChange;
|
||||||
|
@ -758,6 +762,40 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.handleResourceChange = handleResourceChange;
|
||||||
|
function handleResourceChange() {
|
||||||
|
$scope.state.settingUnlimitedResources = false;
|
||||||
|
if (
|
||||||
|
($scope.config.HostConfig.Memory > 0 && $scope.formValues.MemoryLimit === 0) ||
|
||||||
|
($scope.config.HostConfig.MemoryReservation > 0 && $scope.formValues.MemoryReservation === 0) ||
|
||||||
|
($scope.config.HostConfig.NanoCpus > 0 && $scope.formValues.CpuLimit === 0)
|
||||||
|
) {
|
||||||
|
$scope.state.settingUnlimitedResources = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLimits(config) {
|
||||||
|
try {
|
||||||
|
if ($scope.state.settingUnlimitedResources) {
|
||||||
|
create();
|
||||||
|
} else {
|
||||||
|
await ContainerService.updateLimits($transition$.params().from, config);
|
||||||
|
$scope.config = config;
|
||||||
|
Notifications.success('Limits updated');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error('Failure', err, 'Update Limits fail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
$scope.state.actionInProgress = true;
|
||||||
|
var config = angular.copy($scope.config);
|
||||||
|
prepareResources(config);
|
||||||
|
await updateLimits(config);
|
||||||
|
$scope.state.actionInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
var oldContainer = null;
|
var oldContainer = null;
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName);
|
HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
.edit-resources {
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--border-widget-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget .edit-resources button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-7 {
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-10 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
|
@ -708,60 +708,106 @@
|
||||||
<!-- !sysctls-input-list -->
|
<!-- !sysctls-input-list -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !sysctls -->
|
<!-- !sysctls -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div ng-class="{ 'edit-resources': state.mode == 'duplicate' }">
|
||||||
Resources
|
<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 mt-20">
|
||||||
|
Memory reservation
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<slider
|
||||||
|
on-change="(handleResourceChange)"
|
||||||
|
model="formValues.MemoryReservation"
|
||||||
|
floor="0"
|
||||||
|
ceil="state.sliderMaxMemory"
|
||||||
|
step="256"
|
||||||
|
ng-if="state.sliderMaxMemory"
|
||||||
|
></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 mt-7">
|
||||||
|
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 mt-20">
|
||||||
|
Memory limit
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<slider
|
||||||
|
on-change="(handleResourceChange)"
|
||||||
|
model="formValues.MemoryLimit"
|
||||||
|
floor="0"
|
||||||
|
ceil="state.sliderMaxMemory"
|
||||||
|
step="256"
|
||||||
|
ng-if="state.sliderMaxMemory"
|
||||||
|
></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 mt-7">
|
||||||
|
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 mt-20">
|
||||||
|
CPU limit
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<slider
|
||||||
|
on-change="(handleResourceChange)"
|
||||||
|
model="formValues.CpuLimit"
|
||||||
|
floor="0"
|
||||||
|
ceil="state.sliderMaxCpu"
|
||||||
|
step="0.25"
|
||||||
|
precision="2"
|
||||||
|
ng-if="state.sliderMaxCpu"
|
||||||
|
></slider>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 mt-20">
|
||||||
|
<p class="small text-muted">
|
||||||
|
Maximum CPU usage
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !cpu-limit-input -->
|
||||||
|
|
||||||
|
<!-- update-limit-btn -->
|
||||||
|
<div class="form-group" ng-if="state.mode == 'duplicate'">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
ng-disabled="state.actionInProgress || !formValues.RegistryModel.Image || (!formValues.RegistryModel.Registry && fromContainer)"
|
||||||
|
ng-click="update()"
|
||||||
|
button-spinner="state.actionInProgress"
|
||||||
|
>
|
||||||
|
<span ng-hide="state.actionInProgress">Update Limits</span>
|
||||||
|
<span ng-show="state.actionInProgress">Update in progress...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12" ng-if="state.settingUnlimitedResources">
|
||||||
|
<p class="text-muted mr-4">
|
||||||
|
<i class="fa fa-exclamation-circle text-warning mt-10" aria-hidden="true"></i> Updating any resource value to ‘unlimited' will redeploy this container.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !update-limit-btn -->
|
||||||
</div>
|
</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">
|
|
||||||
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></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">
|
|
||||||
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></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">
|
|
||||||
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></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-resources -->
|
<!-- !tab-runtime-resources -->
|
||||||
|
|
|
@ -3,7 +3,7 @@ angular.module('portainer.app').component('slider', {
|
||||||
controller: 'SliderController',
|
controller: 'SliderController',
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '=',
|
model: '=',
|
||||||
onChange: '&',
|
onChange: '<',
|
||||||
floor: '<',
|
floor: '<',
|
||||||
ceil: '<',
|
ceil: '<',
|
||||||
step: '<',
|
step: '<',
|
||||||
|
|
Loading…
Reference in New Issue