Skip to content

Commit 5bf01c3

Browse files
added GPU requirement logic to docker compose flow using override files
1 parent 2d8caa8 commit 5bf01c3

File tree

2 files changed

+34
-8
lines changed

2 files changed

+34
-8
lines changed

src/spec-node/dockerCompose.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,25 @@ ${cacheFromOverrideContent}
230230
args.push('-f', composeOverrideFile);
231231
}
232232

233+
const hasGpuRequirement = config.hostRequirements?.gpu;
234+
const addGpuCapability = hasGpuRequirement && await checkDockerSupportForGPU(params);
235+
if (addGpuCapability) {
236+
const composeOverrideContent = `services:
237+
${config.service}:
238+
deploy:
239+
resources:
240+
reservations:
241+
devices:
242+
- capabilities: [gpu]
243+
`;
244+
const composeFolder = cliHost.path.join(overrideFilePath, 'docker-compose');
245+
await cliHost.mkdirp(composeFolder);
246+
const composeOverrideFile = cliHost.path.join(composeFolder, `${overrideFilePrefix}-gpu-${Date.now()}.yml`);
247+
await cliHost.writeFile(composeOverrideFile, Buffer.from(composeOverrideContent));
248+
additionalComposeOverrideFiles.push(composeOverrideFile);
249+
args.push('-f', composeOverrideFile);
250+
}
251+
233252
if (!noBuild) {
234253
args.push('build');
235254
if (noCache) {
@@ -291,6 +310,13 @@ async function checkForPersistedFile(cliHost: CLIHost, output: Log, files: strin
291310
foundLabel: false
292311
};
293312
}
313+
314+
async function checkDockerSupportForGPU(params: DockerCLIParameters | DockerResolverParameters): Promise<Boolean> {
315+
const result = await dockerCLI(params, 'info', '-f', '{{.Runtimes.nvidia}}');
316+
const runtimeFound = result.stdout.includes('nvidia-container-runtime');
317+
return runtimeFound;
318+
}
319+
294320
async function startContainer(params: DockerResolverParameters, buildParams: DockerCLIParameters, config: DevContainerFromDockerComposeConfig, projectName: string, composeFiles: string[], envFile: string | undefined, container: ContainerDetails | undefined, idLabels: string[]) {
295321
const { common } = params;
296322
const { persistedFolder, output } = common;
@@ -300,15 +326,14 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
300326

301327
common.progress(ResolverProgress.StartingContainer);
302328

303-
const localComposeFiles = composeFiles;
304329
// If dockerComposeFile is an array, add -f <file> in order. https://docs.docker.com/compose/extends/#multiple-compose-files
305-
const composeGlobalArgs = ([] as string[]).concat(...localComposeFiles.map(composeFile => ['-f', composeFile]));
330+
const composeGlobalArgs = ([] as string[]).concat(...composeFiles.map(composeFile => ['-f', composeFile]));
306331
if (envFile) {
307332
composeGlobalArgs.push('--env-file', envFile);
308333
}
309334

310335
const infoOutput = makeLog(buildParams.output, LogLevel.Info);
311-
const composeConfig = await readDockerComposeConfig(buildParams, localComposeFiles, envFile);
336+
const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile);
312337
const services = Object.keys(composeConfig.services || {});
313338
if (services.indexOf(config.service) === -1) {
314339
throw new ContainerError({ description: `Service '${config.service}' configured in devcontainer.json not found in Docker Compose configuration.`, data: { fileWithError: composeFiles[0] } });
@@ -355,7 +380,7 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
355380
const noBuild = !!container; //if we have an existing container, just recreate override files but skip the build
356381

357382
const infoParams = { ...params, common: { ...params.common, output: infoOutput } };
358-
const { collapsedFeaturesConfig, additionalComposeOverrideFiles } = await buildAndExtendDockerCompose(config, projectName, infoParams, localComposeFiles, envFile, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, persistedFolder, featuresBuildOverrideFilePrefix, params.additionalCacheFroms, noBuild);
383+
const { collapsedFeaturesConfig, additionalComposeOverrideFiles } = await buildAndExtendDockerCompose(config, projectName, infoParams, composeFiles, envFile, composeGlobalArgs, config.runServices ?? [], params.buildNoCache ?? false, persistedFolder, featuresBuildOverrideFilePrefix, params.additionalCacheFroms, noBuild);
359384
additionalComposeOverrideFiles.forEach(overrideFilePath => composeGlobalArgs.push('-f', overrideFilePath));
360385

361386
let cache: Promise<ImageDetails> | undefined;
@@ -391,7 +416,7 @@ async function startContainer(params: DockerResolverParameters, buildParams: Doc
391416
}
392417
} catch (err) {
393418
cancel!();
394-
throw new ContainerError({ description: 'An error occurred starting Docker Compose up.', originalError: err, data: { fileWithError: localComposeFiles[0] } });
419+
throw new ContainerError({ description: 'An error occurred starting Docker Compose up.', originalError: err, data: { fileWithError: composeFiles[0] } });
395420
}
396421

397422
await started;

src/spec-node/singleContainer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
import { createContainerProperties, startEventSeen, ResolverResult, getTunnelInformation, getDockerfilePath, getDockerContextPath, DockerResolverParameters, isDockerFileConfig, uriToWSLFsPath, WorkspaceConfiguration, getFolderImageName, inspectDockerImage, ensureDockerfileHasFinalStageName, getImageUser, logUMask } from './utils';
8-
import { ContainerProperties, setupInContainer, ResolverProgress } from '../spec-common/injectHeadless';
8+
import { ContainerProperties, setupInContainer, ResolverProgress, ResolverParameters } from '../spec-common/injectHeadless';
99
import { ContainerError, toErrorText } from '../spec-common/errors';
1010
import { ContainerDetails, listContainers, DockerCLIParameters, inspectContainers, dockerCLI, dockerPtyCLI, toPtyExecParameters, ImageDetails, toExecParameters } from '../spec-shutdown/dockerUtils';
1111
import { DevContainerConfig, DevContainerFromDockerfileConfig, DevContainerFromImageConfig } from '../spec-configuration/configuration';
@@ -291,12 +291,13 @@ export async function findDevContainer(params: DockerCLIParameters | DockerResol
291291
return details.filter(container => container.State.Status !== 'removing')[0];
292292
}
293293

294-
export async function extraRunArgs(params: DockerCLIParameters | DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig) {
294+
export async function extraRunArgs(common: ResolverParameters, params: DockerCLIParameters | DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig) {
295295
const extraArguments: string[] = [];
296296
if (config.hostRequirements?.gpu) {
297297
const result = await dockerCLI(params, 'info', '-f', '{{.Runtimes.nvidia}}');
298298
const runtimeFound = result.stdout.includes('nvidia-container-runtime');
299299
if (runtimeFound) {
300+
common.output.write(`GPU support found, add GPU flags to docker call.`);
300301
extraArguments.push('--gpus', 'all');
301302
}
302303
}
@@ -381,7 +382,7 @@ while sleep 1 & wait $!; do :; done`, '-']; // `wait $!` allows for the `trap` t
381382
...containerEnv,
382383
...containerUser,
383384
...(config.runArgs || []),
384-
...(await extraRunArgs(params, config) || []),
385+
...(await extraRunArgs(common, params, config) || []),
385386
...featureArgs,
386387
...entrypoint,
387388
imageName,

0 commit comments

Comments
 (0)