Skip to content

[WIP] Docker in Docker support #484

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

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ServerlessPythonRequirements {
? 'python.exe'
: this.serverless.service.provider.runtime || 'python',
dockerizePip: false,
dockerInDockerPath: false,
dockerSsh: false,
dockerImage: null,
dockerFile: null,
Expand Down
61 changes: 58 additions & 3 deletions lib/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@ const { spawnSync } = require('child_process');
const isWsl = require('is-wsl');
const fse = require('fs-extra');
const path = require('path');
let cachedContainerId = false;

/**
* A helper function to determine if we are inside Docker
* @return {boolean} if we are inside docker or not
*/
function areInsideDocker() {
return fse.existsSync('/.dockerenv')
}

/**
* A helper function to determine the container id when inside docker, caching results
* @return {string} sha container id, or returns false on error/failure
*/
function getContainerIdFromWithinContainer() {
if (cachedContainerId){ return cachedContainerId; }
try {
// First, detect our container id by using /proc/self/cgroup trick
cgroup = fse.readFileSync('/proc/self/cgroup', { encoding: 'utf-8' });
// Parse the stdout and extract the systemd line, and the the last tokenized item in that line
pos1 = cgroup.toString('utf8').indexOf("systemd");
cachedContainerId = cgroup.toString('utf8').substr(pos1, cgroup.toString('utf8').indexOf("\n", pos1)-pos1).split('/').pop();
return cachedContainerId
} catch (e) {
console.error("Docker-in-Docker: Unable to get container id from within container, cgroup might not be present");
console.error(e);
return false;
}
}

/**
* Helper function to run a docker command
Expand Down Expand Up @@ -98,12 +127,38 @@ function tryBindPath(serverless, bindPath, testFile) {
* Get bind path depending on os platform
* @param {object} serverless
* @param {string} servicePath
* @param {Object} options serverless cli options to alter logic
* @return {string} The bind path.
*/
function getBindPath(serverless, servicePath) {
function getBindPath(serverless, servicePath, options) {
serverless.cli.log(`Docker-In-Docker: servicePath: ${servicePath}`);
// Determine bind path
if (process.platform !== 'win32' && !isWsl) {
return servicePath;
// Detect if we're trying to do docker-in-docker
if (options.dockerizePip && areInsideDocker()) {
serverless.cli.log(`Docker-In-Docker: We have detected an docker-in-docker configuration. NOTE: This feature is in beta for this plugin, verbose output for now`);
// Check if we want to specify our own path...
if (options.dockerInDockerPath) {
serverless.cli.log(`Docker-In-Docker: User-specified docker-in-docker current working directory path on host: ${options.dockerInDockerPath}`);
return options.dockerInDockerPath + "/.serverless"
}
// Get our container id from within docker-in-docker
let containerId = getContainerIdFromWithinContainer();
if (!containerId) {
console.log(`Docker-In-Docker: Unable to get container ID, falling back to local path. WARNING this will probably not work...`);
return servicePath;
}
serverless.cli.log(`Docker-In-Docker: Detected container: ${containerId}`);
// Inspect this container to get the root volume mount
data = dockerCommand(['inspect', containerId]).output[1];
result = JSON.parse(data)[0]['GraphDriver']['Data']['MergedDir'];
serverless.cli.log(`Docker-In-Docker: Found docker-in-docker root volume mounted from: ` + JSON.stringify(result))
serverless.cli.log(`Docker-In-Docker: Using: ` + result + servicePath);
return result + servicePath
} else {
// Check if we're inside docker
return servicePath;
}
}

// test docker is available
Expand Down Expand Up @@ -173,4 +228,4 @@ function getDockerUid(bindPath) {
return ps.stdout.trim();
}

module.exports = { buildImage, getBindPath, getDockerUid };
module.exports = { buildImage, getBindPath, getDockerUid, areInsideDocker };
5 changes: 2 additions & 3 deletions lib/pip.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ function installRequirements(targetFolder, serverless, options) {
serverless.cli.log(`Docker Image: ${dockerImage}`);

// Prepare bind path depending on os platform
const bindPath = dockerPathForWin(getBindPath(serverless, targetFolder));
let bindPath = dockerPathForWin(getBindPath(serverless, targetFolder, options));

dockerCmd.push('docker', 'run', '--rm', '-v', `${bindPath}:/var/task:z`);
if (options.dockerSsh) {
Expand Down Expand Up @@ -227,8 +227,7 @@ function installRequirements(targetFolder, serverless, options) {
fse.closeSync(
fse.openSync(path.join(downloadCacheDir, 'requirements.txt'), 'w')
);
const windowsized = getBindPath(serverless, downloadCacheDir);
// And now push it to a volume mount and to pip...
let windowsized = getBindPath(serverless, downloadCacheDir, options);
dockerCmd.push('-v', `${windowsized}:${dockerDownloadCacheDir}:z`);
pipCmd.push('--cache-dir', dockerDownloadCacheDir);
}
Expand Down
7 changes: 7 additions & 0 deletions lib/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const glob = require('glob-all');
const path = require('path');
const fse = require('fs-extra');
const sha256File = require('sha256-file');
const { areInsideDocker } = require('./docker');


/**
* This helper will check if we're using static cache and have max
Expand Down Expand Up @@ -72,6 +74,11 @@ function getRequirementsWorkingPath(
return path.join(getUserCachePath(options), subfolder);
}

// If we're in docker-in-docker we must use a path that can be mounted in a sub-docker container, so we'll put a folder at the root
if (options.dockerizePip && areInsideDocker()) {
return path.join('/', '_slspyreqs');
}

// If we don't want to use the static cache, then fallback to the way things used to work
return path.join(requirementsTxtDirectory, 'requirements');
}
Expand Down