A lightweight, efficient init process manager (PID 1) for container environments, written in Go.
- PID 1 Process Manager: Designed to run as the init process in containers
- Process Supervision: Automatically monitors and manages child processes
- Configuration Hot-Reload: Watches for configuration file changes and gracefully restarts the managed process
- Graceful Shutdown: Handles SIGTERM/SIGINT signals and performs clean shutdown
- Lightweight: Minimal resource usage and simple design
- Well-Tested: Comprehensive test coverage with unit tests
go build -o manager ./cmd/managermake build# Run with default settings (redis-exporter)
./manager
# Run with custom command
./manager -command /path/to/your/app
# Run with custom command and arguments
./manager -command /usr/bin/myapp arg1 arg2
# Specify config file to watch
./manager -command /usr/bin/myapp -config /etc/myapp/config.conf-command: Command to execute (default:/usr/local/bin/redis-exporter)-config: Configuration file to watch for changes (default:/usr/local/bin/conf/exporter.conf)-version: Print version information
FROM alpine:latest
COPY manager /usr/local/bin/manager
COPY your-app /usr/local/bin/your-app
COPY config.conf /usr/local/bin/conf/config.conf
# Use manager as PID 1
ENTRYPOINT ["/usr/local/bin/manager"]
CMD ["-command", "/usr/local/bin/your-app"]- Process Management: The manager starts the specified child process and monitors its lifecycle
- Configuration Watching: If a configuration file path is provided and the file exists, the manager watches for file modifications
- Uses fsnotify for real-time file system events
- Includes polling fallback (every 5 seconds) for reliable detection
- Handles Kubernetes ConfigMap updates via symlink/inode tracking
- Automatic Restart: When the configuration file changes, the manager gracefully restarts the child process
- Exit Handling:
- If the child process exits abnormally, the manager also exits
- If the manager restarts the child process, it continues running
- Signal Handling: The manager catches SIGTERM/SIGINT and performs graceful shutdown
All log messages are prefixed with [flush-manager] to make them easy to identify in combined logs. The manager logs at different levels:
- INFO: Important operational messages (startup, shutdown, config changes, process lifecycle)
- ERROR: Error conditions
- DEBUG: Detailed diagnostic information (file system events, internal state)
Example log output:
[flush-manager] INFO: === Flush Manager v1.0.0 starting ===
[flush-manager] INFO: PID: 1
[flush-manager] INFO: Configuration: command=/usr/local/bin/redis-exporter, config_file=/usr/local/bin/conf/exporter.conf
[flush-manager] INFO: Config file /usr/local/bin/conf/exporter.conf is a symlink pointing to /usr/local/bin/conf/..data/exporter.conf
[flush-manager] INFO: Watching directory: /usr/local/bin/conf
[flush-manager] INFO: Starting child process: /usr/local/bin/redis-exporter []
[flush-manager] INFO: Child process started with PID: 123
[flush-manager] INFO: File change detected: old_inode=456, new_inode=789
[flush-manager] INFO: Config file change detected, restarting child process...
The manager is specifically designed to work with Kubernetes ConfigMap mounts:
Kubernetes mounts ConfigMaps using symlinks:
/usr/local/bin/conf/
├── exporter.conf -> ..data/exporter.conf (symlink to file)
├── ..data -> ..2023_11_02_12_00_00.123456789 (symlink to directory)
└── ..2023_11_02_12_00_00.123456789/
└── exporter.conf (actual file)
When you update a ConfigMap, Kubernetes:
- Creates a new timestamped directory with updated files
- Atomically updates the
..datasymlink - Eventually removes old directories
- Symlink Detection: On startup, detects if the config file is a symlink
- Multi-Level Watching: Watches both the file and parent directories
- Inode Tracking: Detects when symlink target changes (inode changes)
- Polling Fallback: Checks every 5 seconds to ensure changes aren't missed
- Debouncing: Waits 500ms after last change to avoid multiple restarts
apiVersion: v1
kind: Pod
metadata:
name: redis-exporter
spec:
containers:
- name: exporter
image: your-registry/redis-exporter:latest
command: ["/usr/local/bin/manager"]
volumeMounts:
- name: config
mountPath: /usr/local/bin/conf
volumes:
- name: config
configMap:
name: redis-exporter-configWhen you run kubectl edit configmap redis-exporter-config, the manager will:
- Detect the ConfigMap update within 5 seconds (or instantly via fsnotify)
- Log the change with old and new inode numbers
- Gracefully restart the redis-exporter process
- Continue running normally
If the manager is not detecting config file changes, check:
-
Verify file watcher started:
kubectl logs <pod-name> | grep "Starting file watcher"
-
Check for change detection:
kubectl logs <pod-name> | grep "File change detected"
-
Monitor fsnotify events (debug):
kubectl logs <pod-name> | grep "Fsnotify event"
-
Verify polling is active:
kubectl logs <pod-name> | grep "polling"
For detailed troubleshooting, see TROUBLESHOOTING.md
The project is structured into three main components:
- Manages child process lifecycle (start, stop, restart)
- Tracks process exit reasons (abnormal vs. restart)
- Handles graceful termination with timeout
- Monitors configuration file changes using fsnotify
- Implements debouncing to avoid multiple rapid restarts
- Handles file recreation and modification events
- Coordinates process management and file watching
- Handles signal processing (SIGTERM, SIGINT)
- Implements the main event loop
- Go 1.13 or later (tested with Go 1.13 - 1.23)
# Run all tests
make test
# Run tests with coverage
make test-coverage
# Run tests with verbose output
go test -v ./...flush-manager/
├── cmd/
│ └── manager/ # Main application entry point
│ └── main.go
├── internal/
│ ├── logger/ # Logging utilities
│ │ └── logger.go
│ ├── manager/ # Core manager logic
│ │ ├── manager.go
│ │ └── manager_test.go
│ ├── process/ # Process management
│ │ ├── process.go
│ │ └── process_test.go
│ └── watcher/ # File watching
│ ├── watcher.go
│ └── watcher_test.go
├── go.mod
├── go.sum
├── Makefile
├── LICENSE
├── README.md
└── TROUBLESHOOTING.md
The project includes comprehensive tests for all components:
- Process Manager Tests: Process lifecycle, restart behavior, signal handling
- File Watcher Tests: File change detection, debouncing, edge cases
- Manager Tests: Integration tests, shutdown behavior, configuration changes
All external interactions are properly mocked to ensure reliable and fast tests.
- Minimal dependencies (only fsnotify for file watching)
- Simple, focused functionality
- Efficient resource usage
- 10-second timeout for graceful process termination
- Automatic fallback to SIGKILL if needed
- Proper cleanup of all resources
- File watcher returns a no-op implementation when file doesn't exist
- Avoids nil pointer checks throughout the codebase
- Cleaner, more maintainable code
- Child process runs in its own process group
- Prevents signal propagation issues
- Better isolation
See LICENSE file for details.
Contributions are welcome! Please ensure:
- All tests pass:
make test - Code is properly formatted:
go fmt ./... - New features include tests
Current version: 1.0.0