Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjustments of NVIDIA GPU support in Docker containers #130

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ help:
@echo " Daemon register(Linux, socket):"
@echo " dock-sock create docker daemon for Nextcloud 28, 27 (/var/run/docker.sock)"
@echo " dock-sock28 create docker daemon for Nextcloud 28 (/var/run/docker.sock)"
@echo " dock-sock28-gpu create docker daemon with GPU for Nextcloud 28 (/var/run/docker.sock)"
@echo " dock-sock27 create docker daemon for Nextcloud 27 (/var/run/docker.sock)"
@echo " dock-sock27-gpu create docker daemon with GPU for Nextcloud 27 (/var/run/docker.sock)"
@echo " "
@echo " Daemon register(any OS, host:port)"
@echo " dock2port will map docker socket to port. first use this!"
Expand All @@ -37,13 +39,27 @@ dock-sock28:
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev Docker docker-install unix-socket /var/run/docker.sock http://nextcloud.local/index.php --net=master_default

.PHONY: dock-sock28-gpu
dock-sock28-gpu:
@echo "creating daemon with NVIDIA gpu for nextcloud 'master' container"
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:daemon:unregister docker_dev_nvidia || true
docker exec master-nextcloud-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev_gpu "Docker with GPU" docker-install unix-socket /var/run/docker.sock http://nextcloud.local/index.php --net=master_default --gpu --set-default

.PHONY: dock-sock27
dock-sock27:
@echo "creating daemon for nextcloud 'stable27' container"
docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:unregister docker_dev || true
docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev Docker docker-install unix-socket /var/run/docker.sock http://stable27.local/index.php --net=master_default

.PHONY: dock-sock27-gpu
dock-sock27-gpu:
@echo "creating daemon with NVIDIA gpu for nextcloud 'stable27' container"
docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:unregister docker_dev_nvidia || true
docker exec master-stable27-1 sudo -u www-data php occ app_api:daemon:register \
docker_dev_gpu "Docker with GPU" docker-install unix-socket /var/run/docker.sock http://stable27.local/index.php --net=master_default --gpu --set-default

.PHONY: dock2port
dock2port:
@echo "deploying kekru/docker-remote-api-tls..."
Expand Down
4 changes: 2 additions & 2 deletions lib/Command/Daemon/RegisterDaemon.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected function configure(): void {
$this->addOption('ssl_cert', null, InputOption::VALUE_REQUIRED, 'SSL cert for daemon connection (local absolute path)');
$this->addOption('ssl_cert_password', null, InputOption::VALUE_REQUIRED, 'SSL cert password for daemon connection(optional)');

$this->addOption('gpu', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Docker containers gpu device mapping (will be forwarded to all created containers)', []);
$this->addOption('gpu', null, InputOption::VALUE_NONE, 'Enable support of GPUs for containers');

$this->addOption('set-default', null, InputOption::VALUE_NONE, 'Set DaemonConfig as default');

Expand All @@ -67,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'ssl_key_password' => $input->getOption('ssl_key_password'),
'ssl_cert' => $input->getOption('ssl_cert'),
'ssl_cert_password' => $input->getOption('ssl_cert_password'),
'gpus' => $input->getOption('gpu'),
'gpu' => $input->getOption('gpu') ?? false,
];

$daemonConfig = $this->daemonConfigService->registerDaemonConfig([
Expand Down
41 changes: 33 additions & 8 deletions lib/DeployActions/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ public function createContainer(string $dockerUrl, array $imageParams, array $pa
$containerParams['NetworkingConfig'] = $networkingConfig;
}

if (isset($params['devices'])) {
$containerParams['HostConfig']['Devices'] = $this->buildDevicesParams($params['devices']);
if (isset($params['gpu']) && $params['gpu']) {
$containerParams['HostConfig']['DeviceRequests'] = $this->buildDefaultGPUDeviceRequests();
}

$url = $this->buildApiUrl($dockerUrl, sprintf('containers/create?name=%s', urlencode($this->buildExAppContainerName($params['name']))));
Expand Down Expand Up @@ -339,13 +339,15 @@ public function buildDeployParams(DaemonConfig $daemonConfig, SimpleXMLElement $
$port = $oldEnvs['APP_PORT'] ?? $this->service->getExAppRandomPort();
$secret = $oldEnvs['APP_SECRET'];
$storage = $oldEnvs['APP_PERSISTENT_STORAGE'];
// Preserve previous devices or use from params (if any)
$devices = array_map(function (array $device) {
return $device['PathOnHost'];
}, (array) $containerInfo['HostConfig']['Devices']);
// Preserve previous device requests (GPU)
$deviceRequests = $containerInfo['HostConfig']['DeviceRequests'];
} else {
$port = $this->service->getExAppRandomPort();
$devices = $deployConfig['gpus'];
if ($deployConfig['gpu']) {
$deviceRequests = $this->buildDefaultGPUDeviceRequests();
} else {
$deviceRequests = [];
}
$storage = $this->buildDefaultExAppVolume($appId)[0]['Target'];
}

Expand Down Expand Up @@ -373,7 +375,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, SimpleXMLElement $
'port' => $port,
'net' => $deployConfig['net'] ?? 'host',
'env' => $envs,
'devices' => $devices,
'deviceRequests' => $deviceRequests,
];

return [
Expand Down Expand Up @@ -408,6 +410,12 @@ public function buildDeployEnvs(array $params, array $envOptions, array $deployC
sprintf('NEXTCLOUD_URL=%s', $deployConfig['nextcloud_url'] ?? str_replace('https', 'http', $this->urlGenerator->getAbsoluteURL(''))),
];

// Add required GPU runtime envs if daemon configured to use GPU
if (filter_var($deployConfig['gpu'], FILTER_VALIDATE_BOOLEAN)) {
$autoEnvs[] = sprintf('NVIDIA_VISIBLE_DEVICES=%s', 'all');
$autoEnvs[] = sprintf('NVIDIA_DRIVER_CAPABILITIES=%s', 'compute,utility');
}

foreach ($envOptions as $envOption) {
[$key, $value] = explode('=', $envOption, 2);
// Do not overwrite required auto generated envs
Expand Down Expand Up @@ -621,4 +629,21 @@ private function isGPUAvailable(): bool {
}
return false;
}

/**
* Return default GPU device requests for container.
* For now only NVIDIA GPUs supported.
* TODO: Add support for other GPU vendors
*
* @return array[]
*/
private function buildDefaultGPUDeviceRequests() {
return [
[
'Driver' => 'nvidia', // Currently only NVIDIA GPU vendor
'Count' => -1, // All available GPUs
'Capabilities' => [['compute', 'utility']], // Compute and utility capabilities
],
];
}
}
1 change: 1 addition & 0 deletions src/components/Apps/DaemonDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<p><b>{{ t('app_api', 'Type') }}</b>: {{ daemon.accepts_deploy_id }}</p>
<p><b>{{ t('app_api', 'Name') }}</b>: {{ daemon.name }}</p>
<p><b>{{ t('app_api', 'Display Name') }}</b>: {{ daemon.display_name }}</p>
<p><b>{{ t('app_api', 'GPUs support') }}</b>: {{ daemon.deploy_config?.gpu || 'false' }}</p>
<div class="exappinfo">
<div class="exappinfo-row">
<b>{{ t('app_api', 'External App URL: ') }}</b>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DaemonConfig/DaemonConfigDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<p><b>{{ t('app_api', 'SSL key pass: ') }}</b>{{ daemon.deploy_config.ssl_key_password || 'null' }}</p>
<p><b>{{ t('app_api', 'SSL cert: ') }}</b>{{ daemon.deploy_config.ssl_cert || 'null' }}</p>
<p><b>{{ t('app_api', 'SSL cert pass: ') }}</b>{{ daemon.deploy_config.ssl_cert_password || 'null' }}</p>
<p><b>{{ t('app_api', 'GPUs support: ') }}</b>{{ daemon.deploy_config.gpus.length > 0 }}</p>
<p><b>{{ t('app_api', 'GPUs support: ') }}</b>{{ daemon.deploy_config?.gpu || 'false' }}</p>

<div class="actions">
<NcButton v-if="daemon.accepts_deploy_id !== 'manual-install'" @click="verifyConnection">
Expand Down
4 changes: 2 additions & 2 deletions src/components/DaemonConfig/RegisterDaemonConfigModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
{{ t('app_api', 'GPUs support') }}
</NcCheckboxRadioSwitch>
<p v-if="deployConfig.gpu" class="hint">
{{ t('app_api', 'Default "/dev/dri" device will be attached to ExApp containers') }}
{{ t('app_api', 'All GPU devices will be requested to be enabled in ExApp containers') }}
</p>
</div>
</template>
Expand Down Expand Up @@ -204,7 +204,7 @@ export default {
ssl_key_password: this.deployConfig.ssl_key_password,
ssl_cert: this.deployConfig.ssl_cert,
ssl_cert_password: this.deployConfig.ssl_cert_password,
gpus: this.deployConfig.gpu ? ['/dev/dri'] : [],
gpu: this.deployConfig.gpu,
},
},
defaultDaemon: this.acceptsDeployId === 'docker-install' ? this.defaultDaemon : false,
Expand Down
Loading