mirror of
https://github.com/Stone-Red-Code/RemoteExec.git
synced 2026-09-04 09:06:20 +02:00
Add docker execution environment
This commit is contained in:
@@ -14,6 +14,45 @@ public class ExecutionConfiguration
|
||||
|
||||
/// <summary>
|
||||
/// Type of execution environment to use. Default is "AssemblyLoadContext".
|
||||
/// Options: "AssemblyLoadContext", "DockerContainer"
|
||||
/// </summary>
|
||||
public string ExecutionEnvironment { get; set; } = "AssemblyLoadContext";
|
||||
}
|
||||
|
||||
public class DockerExecutionConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Docker host URL. Default is unix:///var/run/docker.sock (Linux) or npipe://./pipe/docker_engine (Windows).
|
||||
/// </summary>
|
||||
public string DockerHost { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Docker worker image name. Default is remoteexec-worker:latest.
|
||||
/// </summary>
|
||||
public string WorkerImageName { get; set; } = "remoteexec-worker:latest";
|
||||
|
||||
/// <summary>
|
||||
/// Container timeout in seconds. Default is 300 (5 minutes).
|
||||
/// </summary>
|
||||
public int ContainerTimeoutSeconds { get; set; } = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Memory limit per container in MB. Default is 512 MB.
|
||||
/// </summary>
|
||||
public long ContainerMemoryLimitMb { get; set; } = 512;
|
||||
|
||||
/// <summary>
|
||||
/// CPU shares per container. Default is 1024.
|
||||
/// </summary>
|
||||
public long ContainerCpuShares { get; set; } = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Disable network access in containers. Default is true.
|
||||
/// </summary>
|
||||
public bool DisableNetwork { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Use read-only filesystem in containers. Default is true.
|
||||
/// </summary>
|
||||
public bool ReadOnlyFilesystem { get; set; } = true;
|
||||
}
|
||||
@@ -8,3 +8,5 @@ using System.Diagnostics.CodeAnalysis;
|
||||
[assembly: SuppressMessage("Minor Code Smell", "S2325:Methods and properties that don't access instance data should be static", Justification = "<Pending>", Scope = "type", Target = "~T:RemoteExec.Server.Hubs.RemoteExecutionHub")]
|
||||
[assembly: SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "<Pending>", Scope = "member", Target = "~M:RemoteExec.Server.Hubs.RemoteExecutionHub.Execute(RemoteExec.Shared.RemoteExecutionRequest)~System.Threading.Tasks.Task{RemoteExec.Shared.RemoteExecutionResult}")]
|
||||
[assembly: SuppressMessage("Major Code Smell", "S3010:Static fields should not be updated in constructors", Justification = "<Pending>")]
|
||||
[assembly: SuppressMessage("Performance", "CA1873:Avoid potentially expensive logging", Justification = "Annoying")]
|
||||
[assembly: SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "<Pending>", Scope = "member", Target = "~M:RemoteExec.Server.Services.AssemblyLoadContextExecutionEnvironment.ExecuteTaskAsync(RemoteExec.Shared.Models.RemoteExecutionRequest)~System.Threading.Tasks.Task{RemoteExec.Shared.Models.RemoteExecutionResult}")]
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
using RemoteExec.Server.Configuration;
|
||||
using RemoteExec.Server.Services;
|
||||
using RemoteExec.Shared;
|
||||
using RemoteExec.Shared.Models;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
@@ -126,6 +126,10 @@ public class RemoteExecutionHub : Hub
|
||||
RemoteExecutionResult result = await ExecuteTask(taskItem.Request);
|
||||
await Clients.Caller.SendAsync("TaskResult", taskItem.TaskId, result);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Connection {ConnectionId} disposed while processing task {TaskId}", Context.ConnectionId, taskItem.TaskId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing task {TaskId}", taskItem.TaskId);
|
||||
@@ -144,7 +148,7 @@ public class RemoteExecutionHub : Hub
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogError(ex, "Task stream for connection {ConnectionId} was canceled", Context.ConnectionId);
|
||||
logger.LogInformation(ex, "Task stream for connection {ConnectionId} was canceled", Context.ConnectionId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -300,7 +304,8 @@ public class RemoteExecutionHub : Hub
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error loading assembly {Assembly}", assemblyName);
|
||||
throw;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,10 @@ builder.Services.AddHealthChecks();
|
||||
|
||||
builder.Services.Configure<AuthenticationConfiguration>(builder.Configuration.GetSection("Authentication"));
|
||||
builder.Services.Configure<ExecutionConfiguration>(builder.Configuration.GetSection("Execution"));
|
||||
builder.Services.Configure<DockerExecutionConfiguration>(builder.Configuration.GetSection("DockerExecution"));
|
||||
builder.Services.Configure<MetricsConfiguration>(builder.Configuration.GetSection("Metrics"));
|
||||
|
||||
builder.Services.AddScoped<ExecutionEnvironment, DockerContainerExecutionEnvironment>();
|
||||
builder.Services.AddScoped<ExecutionEnvironment, AssemblyLoadContextExecutionEnvironment>();
|
||||
|
||||
builder.Services.AddHostedService<MetricsBroadcastService>();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using RemoteExec.Server.Utilities;
|
||||
using RemoteExec.Shared;
|
||||
using RemoteExec.Shared.Models;
|
||||
using RemoteExec.Shared.Utilities;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using RemoteExec.Server.Configuration;
|
||||
using RemoteExec.Shared.Models;
|
||||
using RemoteExec.Shared.Models.Docker;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Formats.Tar;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RemoteExec.Server.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Executes tasks in ephemeral Docker containers for maximum isolation.
|
||||
/// </summary>
|
||||
public class DockerContainerExecutionEnvironment : ExecutionEnvironment
|
||||
{
|
||||
public override string Name => "DockerContainer";
|
||||
|
||||
private readonly DockerClient dockerClient;
|
||||
private readonly ILogger<DockerContainerExecutionEnvironment> logger;
|
||||
private readonly string workerImageName;
|
||||
private readonly TimeSpan containerTimeout;
|
||||
private readonly long memoryLimit;
|
||||
private readonly long cpuLimit;
|
||||
private readonly bool networkDisabled;
|
||||
private readonly bool readOnlyFilesystem;
|
||||
|
||||
private readonly ConcurrentDictionary<string, byte[]> assemblyCache = [];
|
||||
private readonly ConcurrentDictionary<string, string> runningContainers = [];
|
||||
|
||||
public DockerContainerExecutionEnvironment(ILogger<DockerContainerExecutionEnvironment> logger, IOptions<DockerExecutionConfiguration> dockerConfig)
|
||||
{
|
||||
this.logger = logger;
|
||||
|
||||
DockerExecutionConfiguration config = dockerConfig.Value;
|
||||
|
||||
workerImageName = config.WorkerImageName;
|
||||
containerTimeout = TimeSpan.FromSeconds(config.ContainerTimeoutSeconds);
|
||||
memoryLimit = config.ContainerMemoryLimitMb * 1024 * 1024;
|
||||
cpuLimit = config.ContainerCpuShares;
|
||||
networkDisabled = config.DisableNetwork;
|
||||
readOnlyFilesystem = config.ReadOnlyFilesystem;
|
||||
|
||||
DockerClientConfiguration dockerClientConfig;
|
||||
|
||||
if (string.IsNullOrEmpty(config.DockerHost))
|
||||
{
|
||||
dockerClientConfig = new DockerClientConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
dockerClientConfig = new DockerClientConfiguration(new Uri(config.DockerHost));
|
||||
}
|
||||
|
||||
dockerClient = dockerClientConfig.CreateClient();
|
||||
}
|
||||
|
||||
public override Task PrepareEnvironmentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task<RemoteExecutionResult> ExecuteTaskAsync(RemoteExecutionRequest request)
|
||||
{
|
||||
string containerId = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// Get assembly bytes from cache
|
||||
if (!assemblyCache.TryGetValue(request.AssemblyName, out byte[]? assemblyBytes))
|
||||
{
|
||||
assemblyBytes = await RequestAssemblyAsync(request.AssemblyName);
|
||||
assemblyCache[request.AssemblyName] = assemblyBytes;
|
||||
}
|
||||
|
||||
// Prepare execution request
|
||||
ContainerExecutionRequest containerRequest = new()
|
||||
{
|
||||
AssemblyBytes = Convert.ToBase64String(assemblyBytes),
|
||||
TypeName = request.TypeName,
|
||||
MethodName = request.MethodName,
|
||||
ArgumentTypes = request.ArgumentTypes,
|
||||
Arguments = request.Arguments
|
||||
};
|
||||
|
||||
string requestJson = JsonSerializer.Serialize(containerRequest);
|
||||
|
||||
// Create and start container
|
||||
containerId = await CreateAndStartContainerAsync(requestJson, CancellationToken.None);
|
||||
|
||||
// Start monitoring logs for assembly requests in background
|
||||
using CancellationTokenSource timeoutCts = new(containerTimeout);
|
||||
Task logMonitorTask = MonitorContainerLogsAsync(containerId, timeoutCts.Token);
|
||||
|
||||
// Wait for container to complete with timeout
|
||||
ContainerWaitResponse waitResponse = await dockerClient.Containers.WaitContainerAsync(containerId, timeoutCts.Token);
|
||||
|
||||
// Cancel log monitoring
|
||||
await timeoutCts.CancelAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await logMonitorTask;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Get container logs (stdout contains JSON result)
|
||||
string stdout = await GetContainerLogsAsync(containerId);
|
||||
|
||||
if (waitResponse.StatusCode != 0)
|
||||
{
|
||||
logger.LogError("Container {ContainerId} exited with code {ExitCode}", containerId, waitResponse.StatusCode);
|
||||
return new RemoteExecutionResult
|
||||
{
|
||||
Exception = $"Container exited with code {waitResponse.StatusCode}\nOutput: {stdout}"
|
||||
};
|
||||
}
|
||||
|
||||
// Parse result from stdout - get last JSON line (filter out assembly protocol lines)
|
||||
string[] lines = stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
string? resultLine = lines.LastOrDefault(l =>
|
||||
{
|
||||
string trimmed = l.TrimStart();
|
||||
return trimmed.StartsWith('{') && !trimmed.Contains("#REQUEST_ASSEMBLY") && !trimmed.Contains("#PROVIDE_ASSEMBLY");
|
||||
});
|
||||
|
||||
if (resultLine == null)
|
||||
{
|
||||
return new RemoteExecutionResult
|
||||
{
|
||||
Exception = $"No valid JSON result found in output: {stdout}"
|
||||
};
|
||||
}
|
||||
|
||||
ContainerExecutionResponse? response = JsonSerializer.Deserialize<ContainerExecutionResponse>(resultLine);
|
||||
|
||||
return new RemoteExecutionResult
|
||||
{
|
||||
Result = response?.Result,
|
||||
Exception = response?.Exception
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing task in container {ContainerId}", containerId);
|
||||
return new RemoteExecutionResult
|
||||
{
|
||||
Exception = ex.ToString()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!string.IsNullOrEmpty(containerId))
|
||||
{
|
||||
await CleanupContainerAsync(containerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task CleanupEnvironmentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (string containerId in runningContainers.Keys)
|
||||
{
|
||||
await CleanupContainerAsync(containerId);
|
||||
}
|
||||
|
||||
assemblyCache.Clear();
|
||||
|
||||
logger.LogInformation("Docker container execution environment cleaned up");
|
||||
}
|
||||
|
||||
private async Task MonitorContainerLogsAsync(string containerId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
MultiplexedStream logStream = await dockerClient.Containers.GetContainerLogsAsync(
|
||||
containerId,
|
||||
false,
|
||||
new ContainerLogsParameters
|
||||
{
|
||||
ShowStdout = true,
|
||||
ShowStderr = false,
|
||||
Follow = true
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
StringBuilder lineBuffer = new();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
MultiplexedStream.ReadResult result = await logStream.ReadOutputAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string text = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
_ = lineBuffer.Append(text);
|
||||
|
||||
// Process complete lines
|
||||
string bufferContent = lineBuffer.ToString();
|
||||
int lastNewline = bufferContent.LastIndexOf('\n');
|
||||
|
||||
if (lastNewline == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string completeLines = bufferContent[..lastNewline];
|
||||
string remaining = bufferContent[(lastNewline + 1)..];
|
||||
|
||||
_ = lineBuffer.Clear();
|
||||
_ = lineBuffer.Append(remaining);
|
||||
|
||||
string[] lines = completeLines.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string trimmedLine = line.Trim();
|
||||
|
||||
if (trimmedLine.StartsWith("#REQUEST_ASSEMBLY ") && trimmedLine.EndsWith('#'))
|
||||
{
|
||||
string assemblyName = trimmedLine.Substring("#REQUEST_ASSEMBLY ".Length, trimmedLine.Length - "#REQUEST_ASSEMBLY ".Length - 1);
|
||||
_ = Task.Run(() => HandleAssemblyRequestAsync(containerId, assemblyName, cancellationToken), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when container completes or timeout occurs
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error monitoring container {ContainerId} logs", containerId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleAssemblyRequestAsync(string containerId, string assemblyName, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!assemblyCache.TryGetValue(assemblyName, out byte[]? assemblyBytes))
|
||||
{
|
||||
assemblyBytes = await RequestAssemblyAsync(assemblyName);
|
||||
assemblyCache[assemblyName] = assemblyBytes;
|
||||
}
|
||||
|
||||
using MemoryStream tarStream = new();
|
||||
using (TarWriter tarWriter = new(tarStream, TarEntryFormat.Ustar, leaveOpen: true))
|
||||
{
|
||||
string fileName = $"{assemblyName}.dll";
|
||||
|
||||
UstarTarEntry dllEntry = new(TarEntryType.RegularFile, fileName)
|
||||
{
|
||||
DataStream = new MemoryStream(assemblyBytes)
|
||||
};
|
||||
await tarWriter.WriteEntryAsync(dllEntry, cancellationToken);
|
||||
|
||||
UstarTarEntry sentinelEntry = new(TarEntryType.RegularFile, fileName + ".ready")
|
||||
{
|
||||
DataStream = new MemoryStream()
|
||||
};
|
||||
|
||||
await tarWriter.WriteEntryAsync(sentinelEntry, cancellationToken);
|
||||
}
|
||||
|
||||
tarStream.Position = 0;
|
||||
|
||||
ContainerPathStatParameters pathParams = new()
|
||||
{
|
||||
Path = "/tmp",
|
||||
AllowOverwriteDirWithFile = false,
|
||||
};
|
||||
|
||||
await dockerClient.Containers.ExtractArchiveToContainerAsync(containerId, pathParams, tarStream, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error transferring assembly {AssemblyName} to container {ContainerId}", assemblyName, containerId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> CreateAndStartContainerAsync(string requestJson, CancellationToken cancellationToken)
|
||||
{
|
||||
CreateContainerParameters parameters = new()
|
||||
{
|
||||
Image = workerImageName,
|
||||
Name = $"remoteexec-task-{Guid.NewGuid()}",
|
||||
HostConfig = new HostConfig
|
||||
{
|
||||
Memory = memoryLimit,
|
||||
CPUShares = cpuLimit,
|
||||
NetworkMode = networkDisabled ? "none" : "bridge",
|
||||
ReadonlyRootfs = readOnlyFilesystem,
|
||||
AutoRemove = false,
|
||||
CapDrop = ["ALL"],
|
||||
SecurityOpt = ["no-new-privileges"],
|
||||
Tmpfs = new Dictionary<string, string>
|
||||
{
|
||||
["/tmp/assemblies"] = "rw,noexec,nosuid,size=100m"
|
||||
}
|
||||
},
|
||||
Env =
|
||||
[
|
||||
$"EXECUTION_REQUEST={Convert.ToBase64String(Encoding.UTF8.GetBytes(requestJson))}"
|
||||
],
|
||||
WorkingDir = "/app",
|
||||
AttachStdout = true,
|
||||
AttachStderr = true
|
||||
};
|
||||
|
||||
CreateContainerResponse container = await dockerClient.Containers.CreateContainerAsync(parameters, cancellationToken);
|
||||
|
||||
bool started = await dockerClient.Containers.StartContainerAsync(container.ID, new ContainerStartParameters(), cancellationToken);
|
||||
|
||||
if (!started)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to start container {container.ID}");
|
||||
}
|
||||
|
||||
runningContainers[container.ID] = string.Empty;
|
||||
|
||||
return container.ID;
|
||||
}
|
||||
|
||||
private async Task<string> GetContainerLogsAsync(string containerId)
|
||||
{
|
||||
MultiplexedStream logStream = await dockerClient.Containers.GetContainerLogsAsync(
|
||||
containerId,
|
||||
false,
|
||||
new ContainerLogsParameters
|
||||
{
|
||||
ShowStdout = true,
|
||||
ShowStderr = true
|
||||
});
|
||||
|
||||
StringBuilder output = new();
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
MultiplexedStream.ReadResult result = await logStream.ReadOutputAsync(buffer, 0, buffer.Length, CancellationToken.None);
|
||||
|
||||
while (result.Count > 0)
|
||||
{
|
||||
logger.LogDebug("Read {ByteCount} bytes from container {ContainerId} logs", result.Count, containerId);
|
||||
|
||||
_ = output.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
|
||||
result = await logStream.ReadOutputAsync(buffer, 0, buffer.Length, CancellationToken.None);
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
private async Task CleanupContainerAsync(string containerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Stop container if still running
|
||||
_ = await dockerClient.Containers.StopContainerAsync(
|
||||
containerId,
|
||||
new ContainerStopParameters { WaitBeforeKillSeconds = 5 });
|
||||
|
||||
// Remove container
|
||||
await dockerClient.Containers.RemoveContainerAsync(
|
||||
containerId,
|
||||
new ContainerRemoveParameters { Force = true, RemoveVolumes = true });
|
||||
|
||||
_ = runningContainers.TryRemove(containerId, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to cleanup container {ContainerId}", containerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using RemoteExec.Server.Utilities;
|
||||
using RemoteExec.Shared;
|
||||
using RemoteExec.Shared.Models;
|
||||
|
||||
namespace RemoteExec.Server.Services;
|
||||
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Execution": {
|
||||
"ExecutionEnvironment": "DockerContainer"
|
||||
},
|
||||
"DockerExecution": {
|
||||
"DockerHost": null,
|
||||
"WorkerImageName": "remoteexec-worker:latest",
|
||||
"ContainerTimeoutSeconds": 300,
|
||||
"ContainerMemoryLimitMb": 512,
|
||||
"ContainerCpuShares": 1024,
|
||||
"DisableNetwork": true,
|
||||
"ReadOnlyFilesystem": false
|
||||
},
|
||||
"Authentication": {
|
||||
"ApiKeys": [
|
||||
{
|
||||
|
||||
@@ -31,7 +31,52 @@
|
||||
// Timeout in seconds for loading assemblies from clients
|
||||
// If a client doesn't respond with assembly bytes within this time, the request fails
|
||||
// Default: 30 seconds
|
||||
"AssemblyLoadTimeoutSeconds": 30
|
||||
"AssemblyLoadTimeoutSeconds": 30,
|
||||
|
||||
// Type of execution environment to use
|
||||
// Options: "AssemblyLoadContext", "DockerContainer"
|
||||
// - AssemblyLoadContext: Lighter weight, per-connection assembly isolation
|
||||
// - DockerContainer: Maximum isolation, ephemeral containers per task
|
||||
// Default: "AssemblyLoadContext"
|
||||
"ExecutionEnvironment": "AssemblyLoadContext"
|
||||
},
|
||||
|
||||
// Docker execution configuration (only used when ExecutionEnvironment = "DockerContainer")
|
||||
"DockerExecution": {
|
||||
// Docker host URL
|
||||
// Linux: "unix:///var/run/docker.sock"
|
||||
// Windows: "npipe://./pipe/docker_engine"
|
||||
"DockerHost": "unix:///var/run/docker.sock",
|
||||
|
||||
// Docker worker image name to use for task execution
|
||||
// Must be built and available on the Docker host
|
||||
// Default: "remoteexec-worker:latest"
|
||||
"WorkerImageName": "remoteexec-worker:latest",
|
||||
|
||||
// Maximum execution time per container in seconds
|
||||
// Containers exceeding this time will be forcefully terminated
|
||||
// Default: 300 (5 minutes)
|
||||
"ContainerTimeoutSeconds": 300,
|
||||
|
||||
// Memory limit per container in MB
|
||||
// Prevents containers from consuming excessive memory
|
||||
// Default: 512 MB
|
||||
"ContainerMemoryLimitMb": 512,
|
||||
|
||||
// CPU shares allocated to each container (relative weight)
|
||||
// Higher values = more CPU priority
|
||||
// Default: 1024
|
||||
"ContainerCpuShares": 1024,
|
||||
|
||||
// Disable network access in containers for security
|
||||
// Set to false if tasks require network connectivity
|
||||
// Default: true
|
||||
"DisableNetwork": true,
|
||||
|
||||
// Make container filesystem read-only
|
||||
// Enhances security by preventing file modifications
|
||||
// Default: true
|
||||
"ReadOnlyFilesystem": true
|
||||
},
|
||||
|
||||
// Server metrics broadcasting configuration
|
||||
|
||||
Reference in New Issue
Block a user