Have you ever wished that container orchestration could be more flexible than static dependency chains, yet simpler than Kubernetes? Meet PnR (Prompt and Respond) – A configuration-driven approach that leverages Go’s powerful platform abstractions to orchestrate containers based on actual readiness status rather than simple dependencies.
The power of abstraction in the Go platform
Before we delve into PnR, let’s understand why Go is particularly suitable for cross-platform container orchestration:
-
Unified Docker API interface: Go’s Docker client library provides a consistent interface across Windows, Linux, and macOS via platform-specific socket connections:
- Unix system use
/var/run/docker.sock
- Windows uses named pipes
- this
client.NewClientWithOpts()
The function handles these differences automatically
- Unix system use
-
Native concurrency support: Go’s Goroutine and Channel can achieve efficient container monitoring:
- Health checks for each container run simultaneously
- Intent loop to coordinate multiple containers without blocking
- Mutex-protected state updates prevent race conditions
-
Cross-platform web processing: Go ahead
net
Packages abstract platform-specific networking details:- TCP health checks work the same way on different operating systems
- HTTP client handles platform-specific DNS resolution
- Port binding uses consistent syntax regardless of platform
Core Concept: Configuration is better than code
PnR orchestrates containers through three key components:
- Domain configuration (JSON)
- Platform-independent health checks
- Runtime state management
Let’s look at this through a typical web stack: MongoDB, API server, and web client.
Domain configuration structure
{
"name": "dev_stack",
"cpuxs": {
"stack_startup": {
"design_chunks": [
{
"name": "mongodb",
"gatekeeper": {
"system_ready": {
"prompt": "Is system ready?",
"response": ["yes"],
"tv": "Y"
}
},
"flowout": {
"mongodb_ready": {
"prompt": "Is MongoDB ready?",
"response": ["yes"],
"tv": "Y"
}
},
"health_check": {
"type": "tcp",
"port_key": "27017",
"timeout_seconds": 2,
"status_mapping": {
"success": {
"key": "mongodb_ready",
"response": ["yes"],
"tv": "Y"
},
"failure": {
"key": "mongodb_ready",
"response": ["no"],
"tv": "N"
}
}
},
"container": {
"name": "pnr_mongodb",
"image": "mongo:latest",
"ports": {
"27017": "27017"
}
}
}
]
}
}
}
Platform-agnostic container management
At the heart of PnR is its platform-agnostic container management. Here’s how it works:
func (il *ContainerIntentionLoop) Execute() error {
// Create platform-specific network
_, err := il.dockerClient.NetworkCreate(il.ctx, "pnr_network", types.NetworkCreate{})
if err != nil {
return fmt.Errorf("failed to create network: %v", err)
}
for {
// Update runtime state
if err := il.updateRTStateFromRuntime(); err != nil {
return err
}
allCompleted := true
anyExecuting := false
// Process each container
for i := range il.cpux.DesignChunks {
chunk := &il.cpux.DesignChunks[i]
// Container state machine
switch chunk.Status {
case "completed":
continue
case "executing":
anyExecuting = true
allCompleted = false
if il.checkChunkCompletion(chunk) {
chunk.Status = "completed"
}
case "", "ready":
allCompleted = false
if il.checkGatekeeper(chunk) {
if err := il.startContainer(chunk); err != nil {
return err
}
chunk.Status = "executing"
anyExecuting = true
}
}
}
// Check termination conditions
if allCompleted {
return nil
}
if !anyExecuting && !allCompleted {
return fmt.Errorf("no progress possible - execution stalled")
}
time.Sleep(5 * time.Second)
}
}
Cross-platform health checks
PnR uses Go’s standard library to implement platform-independent health checks:
func (il *ContainerIntentionLoop) checkChunkCompletion(chunk *DesignChunk) bool {
// Platform-agnostic container status check
isRunning, err := il.isContainerRunning(chunk.Container.Name)
if !isRunning {
il.updateChunkStatus(chunk, false)
return false
}
// Health check based on configuration
status := false
switch chunk.HealthCheck.Type {
case "tcp":
addr := fmt.Sprintf("localhost:%s", chunk.Container.Ports[chunk.HealthCheck.PortKey])
conn, err := net.DialTimeout("tcp", addr, timeout)
if err == nil {
conn.Close()
status = true
}
case "http":
url := fmt.Sprintf("http://localhost:%s%s",
chunk.Container.Ports[chunk.HealthCheck.PortKey],
chunk.HealthCheck.Path)
resp, err := client.Get(url)
if err == nil {
status = (resp.StatusCode == chunk.HealthCheck.ExpectedCode)
}
}
il.updateChunkStatus(chunk, status)
return status
}
Main advantages
- True cross-platform support: Works the same on Windows, Linux, and macOS
- Configure driver: All orchestration logic in domain.json
- Container-agnostic: No modifications required to PnR specific containers
- Flexible health checks:TCP, HTTP, and can be extended to other protocols
- status visibility: Clear status updates via runtime files
- Concurrent execution: Efficient parallel container management
getting Started
The complete code is available here: jitub
Prerequisites
-
Install Go (1.19 or higher):
-
Install Docker
Project structure
pnr-orchestrator/
├── main.go
├── containers.go
├── config/
│ └── domain.json
└── runtime/ # Created automatically
Install
# Create project directory
mkdir pnr-orchestrator
cd pnr-orchestrator
# Initialize Go module
go mod init pnr-orchestrator
# Install dependencies
go get github.com/docker/docker/client
go get github.com/docker/docker/api/types
go get github.com/docker/go-connections/nat
Build and run
# Option 1: Direct run
go run main.go containers.go
# Option 2: Build and run separately
go build
./pnr-orchestrator # Unix/Linux/Mac
pnr-orchestrator.exe # Windows
Beyond mere dependencies
Traditional Docker combination:
api:
depends_on:
- mongodb
PnR’s smart orchestration:
"gatekeeper": {
"mongodb_ready": {
"prompt": "Is MongoDB ready?",
"response": ["yes"],
"tv": "Y"
}
}
What’s the key difference? PnR ensures actual service readiness across any platform, not just container startup.
Next step
- Explore more complex orchestration patterns
- Add custom health check type
- Implement graceful shutdown and cleanup
- Create platform-specific optimization tips
PnR demonstrates how Go’s powerful platform abstractions can create powerful cross-platform container orchestration tools without sacrificing simplicity or functionality.
If you’d like to see more examples or have questions about platform-specific implementations, let me know in the comments!