mirror of
https://github.com/apple/container.git
synced 2026-07-15 14:07:04 +00:00
16f2630126
We're working on making a pure swift container image build system that leverages containerization. This PR represents our initial design and initial work towards this goal. The native builder is still in active development and most of the implementation has not been started or completed. We will be opening a series of issues that represent various (but not necessarily all) pieces of work that need to be done here. There are docs included in this PR that describe the overall design of each component and outline some of our goals. The easiest way to view the docs by themselves (since this is a massive PR) is to look at the docs commit in the `Commits` tab. We'd love any feedback! @wlan0 --------- Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
5.0 KiB
5.0 KiB
ContainerBuildExecutor Architecture
Overview
The ContainerBuildExecutor implements a clean, production-ready execution layer for ContainerBuildIR using the Executor Pattern rather than a complex scheduler.
Architecture
Core Components
- BuildExecutor - The main orchestrator that executes complete build graphs
- OperationExecutor - Executes individual operations (pluggable for different operation types)
- ExecutionContext - Carries mutable state through the build process
- ExecutionDispatcher - Routes operations to appropriate executors based on capabilities
- BuildCache - Caches operation results to avoid redundant work
- Snapshotter - Manages filesystem snapshots for layer creation
Design Principles
- Simplicity - Clean interfaces with minimal complexity
- Extensibility - Easy to add new operation types and executors
- Performance - Parallel execution where possible, with efficient caching
- Type Safety - Leverages Swift's type system for correctness
Execution Flow
graph TD
A[BuildGraph] --> B[SimpleExecutor]
B --> C[Stage Ordering]
C --> D[For Each Stage]
D --> E[Topological Sort]
E --> F[For Each Node]
F --> G{Check Cache}
G -->|Hit| H[Return Cached Result]
G -->|Miss| I[ExecutionDispatcher]
I --> J[Find Suitable Executor]
J --> K[Execute Operation]
K --> L[Update Context]
L --> M[Cache Result]
M --> N[Next Node]
Capability-Based Routing
The dispatcher matches operations to executors based on:
- Operation Type - Does the executor support this operation kind?
- Platform - Can the executor handle the target platform?
- Privileges - Does the executor have required privileges?
- Resources - Are resource requirements satisfied?
Concurrency Model
Stage-Level Execution
Stages are executed sequentially to respect dependencies:
for stage in graph.stagesInDependencyOrder() {
let context = ExecutionContext(stage: stage, ...)
let snapshot = try await executeStage(stage, context: context)
stageSnapshots[stage.name] = snapshot
}
Node-Level Parallelism
Within a stage, independent nodes execute concurrently:
let levels = try GraphTraversal.topologicalLevels(stage)
for level in levels {
await withTaskGroup(of: ExecutionResult.self) { group in
for node in level {
group.addTask {
try await executeNode(node, context)
}
}
}
}
State Management
Snapshot Evolution
Each operation produces a new filesystem snapshot:
Initial Snapshot (S0)
↓
Operation 1 → Snapshot S1
↓
Operation 2 → Snapshot S2
↓
Operation 3 → Snapshot S3 (Final)
Environment Propagation
Environment changes cascade through operations:
context.updateEnvironment(["FOO": .literal("bar")])
// This update is visible to all subsequent operations in the stage
Caching Strategy
Cache Key Generation
Cache keys include:
- Operation digest (content hash)
- Input digests (from dependencies)
- Platform identifier
- Additional context
Cache Lookup Flow
- Compute cache key for operation
- Check cache for existing result
- If hit: skip execution, use cached result
- If miss: execute operation, store result
Error Handling
Errors are categorized and handled appropriately:
- Unsupported Operations - No executor can handle the operation
- Resource Constraints - Requirements cannot be satisfied
- Execution Failures - Operation failed during execution
- Cancellation - Build was cancelled by user
Extensibility Points
Adding New Operation Types
- Define the operation in ContainerBuildIR
- Create a specific executor implementing
OperationExecutor - Register the executor with the dispatcher
Custom Caching
Implement the BuildCache protocol:
public protocol BuildCache: Sendable {
func get(_ key: CacheKey, for operation: Operation) async -> CachedResult?
func put(_ result: ExecutionResult, key: CacheKey, for operation: Operation) async
}
Alternative Snapshotters
Implement the Snapshotter protocol for different backends:
public protocol Snapshotter: Sendable {
func createSnapshot(from parent: Snapshot?, applying changes: FilesystemChanges) async throws -> Snapshot
func prepare(_ snapshot: Snapshot) async throws -> SnapshotHandle
}
Performance Considerations
- Lazy Snapshot Creation - Only create snapshots when filesystem changes occur
- Parallel Execution - Maximize concurrency within dependency constraints
- Efficient Caching - Cache keys designed for fast lookup
- Resource Pooling - Reuse expensive resources like container instances
Future Enhancements
- Distributed Execution - Execute operations across multiple machines
- Incremental Builds - Skip unchanged portions of the graph
- Progress Reporting - Real-time feedback during execution
- Resource Monitoring - Track CPU, memory, and I/O usage