Aegis is a high-performance network proxy that combines Go's control plane with Rust's data plane for optimal performance and maintainability.
The control plane handles configuration, health checks, and load balancing logic in Go, while the data plane processes packets in Rust for minimal latency overhead. The two communicate via gRPC, allowing independent development and deployment of each component.
Aegis is designed for production use in microservice architectures and backend infrastructure, and also serves as a reference implementation for building high-performance networked systems.
Aegis Proxy Dashboard:
Prometheus Target Health:
- Quick Start
- Features
- Architecture
- Design Decisions
- Installation
- Running Aegis
- Configuration
- Testing
- Monitoring
- Project Structure
- Development
- Troubleshooting
- Roadmap
Runs the full stack (proxy, test backends, Prometheus, Grafana) in one command. No local Go/Rust install needed.
git clone https://github.com/lazzerex/aegis.git
cd aegis
cp .env.example .env # configure credentials (defaults work out of the box)
docker-compose up --build -d
# Test the proxy
curl http://localhost:8080/api/test
# Admin API
curl http://localhost:9090/health
# Grafana: http://localhost:3030 (admin / admin by default)
# Prometheus: http://localhost:9092Requires Go 1.25+, Rust 1.88+, and protoc.
git clone https://github.com/lazzerex/aegis.git
cd aegis
make all # builds proto + control plane + data plane
# Terminal 1: data plane
make run-data
# Terminal 2: control plane
make run-control
# Terminal 3: test
./scripts/test-proxy.sh start # start test backends
./scripts/test-proxy.sh test-proxy
./scripts/test-proxy.sh stopFor the complete Docker Compose service list and port reference, see the Docker Compose section below.
- TCP Proxy: High-performance TCP forwarding with async I/O (Tokio)
- UDP Proxy: Session-based UDP forwarding with bidirectional NAT mapping and connection tracking
- Round-robin: Equal distribution across backends
- Weighted round-robin: Proportional distribution based on backend capacity
- Least connections: Routes to backend with fewest active connections
- Consistent hashing: Session affinity using client IP
- Circuit Breaking: Automatic failure detection and backend recovery with configurable thresholds
- Rate Limiting: Token bucket algorithm with global and per-connection limits
- Health Checking: Periodic backend health monitoring with automatic failover
- Connection Pooling: Pre-warmed idle backend connections skip the TCP handshake on the hot path — protocol-safe (not request-level reuse; each connection still serves exactly one client's session)
- Config Validation: Bad config is rejected at load/reload time, never partially applied
- Graceful Shutdown: Connection draining and cleanup
- Dual Prometheus Endpoints: Control plane (
:9091/metrics) and data plane (:9100/metrics) scraped independently — data plane metrics stay up even if the control plane is down - Structured Access Logs: One JSON line per connection (client IP, backend, bytes, latency, error) for both TCP and UDP
- Read-only Dashboard:
GET /dashboardon the Admin API — backend health, weight, and live circuit breaker state, no auth, no build step - Structured Logging: Detailed tracing with configurable log levels
- gRPC Communication: Clean separation between control and data planes
aegis-tui: Live read-only terminal dashboard — backend health, circuit breaker transitions, and load-balancing distribution as they happen, polling the Admin API and the data plane's own metrics endpoint independently so it keeps showing traffic even if the control plane goes downaegis-ctlCLI: Built-in operator tool for live backend management- Admin API authentication: Bearer token via
AEGIS_API_TOKENenv var - Dynamic backend API: Add/remove backends at runtime without config reload
- Helm Chart:
charts/aegis/for Kubernetes deployment (see Helm Chart) - TLS on gRPC: Optional TLS between control and data planes via
AEGIS_TLS_CERT_FILE/AEGIS_TLS_KEY_FILE
- Distributed tracing with OpenTelemetry
- HTTP/2 support and WebSocket proxying
- Zero-downtime config reload (preserve existing connections)
- Kubernetes service discovery (auto-register backends from a Service's endpoints)
- Redis-backed multi-instance state coordination (circuit breaker state only — see TASK.md for why rate limiting is intentionally excluded)
┌─────────────────────────────────────────────────────────────┐
│ Go Control Plane │
│ ┌────────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Config Mgmt│ │ Health │ │ Admin API │ │
│ │ (YAML) │ │ Checker │ │ (Chi Router)│ │
│ └────────────┘ └──────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────┴───────────────┘ │
│ │ │
│ gRPC Client │
└───────────────────────┼──────────────────────────────────────┘
│
gRPC (50051)
│
┌───────────────────────┼──────────────────────────────────────┐
│ gRPC Server │
│ │ │
│ Rust Data Plane │
│ ┌────────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ TCP Proxy │ │ UDP Proxy│ │ Load │ │
│ │ (Tokio) │ │ (Tokio) │ │ Balancer │ │
│ └────────────┘ └──────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────┴───────────────┘ │
│ │ │
└───────────────────────┼──────────────────────────────────────┘
│
Backends
(NestJS/Any HTTP Service)
Why Go + Rust instead of one language, why gRPC instead of shared memory, why this concurrency model, why these load balancing algorithms, why v1 doesn't horizontally scale yet — each with alternatives considered and the trade-offs actually accepted, not just what shipped: docs/decisions/.
- Go 1.25+
- Rust 1.88+
- Protocol Buffers compiler (protoc)
- Make
- Docker (optional, for containerized deployment)
# Install protobuf compiler
sudo apt update
sudo apt install -y protobuf-compiler
# Install Go (if not installed)
cd /tmp
wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz
rm go1.25.0.linux-amd64.tar.gz
# Add to PATH
echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.bashrc
source ~/.bashrc# Install protobuf compiler
brew install protobuf
# Install Go
brew install gogo install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Verify installation
protoc --version
go versionThe Protocol Buffer plugins must be in your PATH for protoc to find them. If you get errors like protoc-gen-go: program not found or is not executable, follow these steps:
1. Check if plugins are installed:
ls $(go env GOPATH)/binYou should see protoc-gen-go and protoc-gen-go-grpc.
2. Add Go bin directory to PATH:
For bash users:
echo 'export GOPATH="$HOME/go"' >> ~/.bashrc
echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.bashrc
source ~/.bashrcFor zsh users:
echo 'export GOPATH="$HOME/go"' >> ~/.zshrc
echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.zshrc
source ~/.zshrc3. Verify PATH is correct:
which protoc-gen-go
which protoc-gen-go-grpc
protoc-gen-go --versionExpected output should show paths like /home/yourusername/go/bin/protoc-gen-go.
# Clone the repository
git clone https://github.com/lazzerex/aegis.git
cd aegis
# Generate protobuf code
make proto
# Build both control and data planes
make all
# Or build individually
make build-go # Build Go control plane
make build-rust # Build Rust data planeThe binaries will be created at:
control-plane/aegis-control- Go control planedata-plane/target/release/aegis-data- Rust data plane
Aegis is a CLI-based infrastructure tool (like nginx or HAProxy), designed for deployment in production environments.
Common Use Cases:
-
Reverse Proxy - Place Aegis in front of your application servers
Internet → Aegis (port 80/443) → Your Backend Servers -
Load Balancer - Distribute traffic across multiple instances
Clients → Aegis → [Server 1, Server 2, Server 3, ...] -
API Gateway - Route requests with rate limiting and circuit breaking
Mobile/Web Apps → Aegis → Microservices
Deployment Methods:
# Option 1: Systemd Service (Linux)
sudo systemctl start aegis
sudo systemctl enable aegis
# Option 2: Docker Compose (Containers)
docker-compose up -d
# Option 3: Kubernetes (Cloud-native)
kubectl apply -f aegis-deployment.yaml
# Option 4: Direct Binary (Development)
# Use make commands (recommended)
make run-data
make run-control
# Or run binaries directly
./data-plane/target/release/aegis-data --config config.yaml
./control-plane/aegis-control --config config.yamlManagement:
- Configure via YAML files (
config.yaml) - Monitor via Prometheus metrics (
:9091/metrics) - Control via Admin API (
:9090) oraegis-ctlCLI - View dashboards with Grafana (connects to Prometheus)
No GUI Required - Aegis is infrastructure software managed through:
- Configuration files (YAML)
- Command-line interface
- HTTP Admin API
- Monitoring dashboards (Grafana/Prometheus)
Edit config.yaml to configure the proxy:
proxy:
listen:
tcp: "0.0.0.0:8080"
udp: "0.0.0.0:8081"
backends:
- address: "localhost:3000"
weight: 100
health_check:
interval: 5s
timeout: 2s
path: "/health"
- address: "localhost:3001"
weight: 100
health_check:
interval: 5s
timeout: 2s
path: "/health"
load_balancing:
algorithm: "round_robin" # round_robin, weighted, least_connections
session_affinity: false
traffic:
rate_limit:
requests_per_second: 1000
burst: 100
timeout:
connect: 5s
idle: 60s
read: 30s
circuit_breaker:
error_threshold: 5
timeout: 30s
admin:
api_address: "127.0.0.1:9090"
metrics_address: "0.0.0.0:9091"
grpc:
control_plane_address: "127.0.0.1:50051"This is the recommended method for development and testing.
Prerequisites:
- Built binaries (run
make all) - Config file:
config.yaml(useslocalhostfor backends)
Start Aegis:
# Terminal 1: Start Rust data plane
make run-data
# Terminal 2: Start Go control plane
make run-controlThe binaries are located at:
- Data plane:
data-plane/target/release/aegis-data - Control plane:
control-plane/aegis-control
Test it:
# Terminal 3: Test the proxy
curl http://localhost:8080
# Check health status
curl http://localhost:9090/health
# Check metrics
curl http://localhost:9091/metrics
# Check proxy status
curl http://localhost:9090/statusThis method runs the complete stack including monitoring tools.
Prerequisites:
- Docker and Docker Compose installed
- Config file:
config.docker.yaml(uses Docker service names for backends)
Start the stack:
# Option 1: Using Make commands (wrapper around docker-compose)
make docker-build # Build images
make docker-up # Start all services
make docker-logs # View logs
make docker-down # Stop all services
# Option 2: Using docker-compose directly (recommended)
docker-compose up --build -d # Build and start in detached mode
docker-compose ps # Check status
docker-compose logs -f # Follow logs
docker-compose logs -f grafana # Follow specific service
docker-compose down # Stop all services
docker-compose down -v # Stop and remove volumesServices started:
- Aegis data plane - TCP/UDP proxy (ports 8080/8081)
- Aegis control plane - Admin API (9090), Metrics (9091)
- Test backends - HTTP servers (ports 3000-3002)
- UDP backends - UDP echo servers (ports 5001-5003/udp)
- Prometheus - Metrics collection (port 9092)
- Grafana - Visualization (port 3030, login: admin/admin)
Access services:
- TCP Proxy: http://localhost:8080
- UDP Proxy: localhost:8081 (UDP)
- Admin API: http://localhost:9090
- Metrics: http://localhost:9091/metrics
- Prometheus: http://localhost:9092
- Grafana: http://localhost:3030
charts/aegis/ deploys both components as one release — see charts/aegis/README.md for full details.
helm install aegis charts/aegis \
--set controlPlane.config.proxy.backends[0].address=db1.internal:5432 \
--set dataPlane.image.repository=<your-registry>/aegis-data \
--set controlPlane.image.repository=<your-registry>/aegis-controlNotes:
- No public images are published yet — build
Dockerfile.data/Dockerfile.controland push to a registry your cluster can pull from. replicaCountdefaults to1for both components and the bundled HPA ships disabled — circuit breaker/rate limiter state is per-instance, not yet safe to run >1 replica (see TASK.md Phase 3).- Not yet run through
helm lint/a live cluster in this environment — review rendered output withhelm templatebefore deploying.
Essential Commands:
# Build
make all # Build everything
make build-go # Build control plane only
make build-rust # Build data plane only
make build-tui # Build the live terminal dashboard
# Run (development)
make run-data # Start data plane
make run-control # Start control plane
make run-tui # Start the live terminal dashboard
# Testing
./scripts/test-proxy.sh start # Start test backends
./scripts/test-proxy.sh test-proxy # Test load balancing
./scripts/test-proxy.sh status # Check services
./scripts/test-proxy.sh stop # Stop backends
# Docker
make docker-build # Build images
make docker-up # Start stack
make docker-down # Stop stack
make docker-logs # View logs
# Or use docker-compose directly
docker-compose up --build -d # Build and start
docker-compose ps # Check status
docker-compose logs -f # View logs
docker-compose down # Stop stack
# Development
make test # Run tests
make fmt # Format code
make lint # Lint code
make clean # Clean build artifacts
# aegis-ctl (set AEGIS_URL and AEGIS_API_TOKEN in env)
aegis-ctl status # list backends + health
aegis-ctl backends add db4.internal:5432 # add backend
aegis-ctl backends add db4.internal:5432 -w 80 # add with weight
aegis-ctl backends remove db4.internal:5432 # remove backend
aegis-ctl reload # reload config from disk
aegis-ctl drain # drain connectionsDefault Ports:
| Service | Port | Purpose |
|---|---|---|
| TCP Proxy | 8080 | Main proxy entry point |
| UDP Proxy | 8081 | UDP proxy entry point |
| Admin API | 9090 | Control & management (/health, /status, /backends, /dashboard) |
| Metrics | 9091 | Prometheus metrics (control plane, aggregated) |
| Data Plane Metrics | 9100 | Prometheus metrics (data plane, direct — stays up if control plane is down) |
| gRPC (Internal) | 50051 | Control/data plane comms |
| Prometheus | 9092 | Metrics scraping |
| Grafana | 3030 | Dashboard visualization |
| Backend 1-3 | 3000-3002 | Test HTTP servers |
| UDP Backend 1-3 | 5001-5003 | Test UDP servers |
Aegis includes test backend servers and convenient scripts to verify functionality.
Automated Testing (Recommended):
# 1. Start test backend servers
./test-proxy.sh start
# 2. Build and start Aegis (in separate terminals)
# Terminal 1: Data plane
make run-data
# Terminal 2: Control plane
make run-control
# 3. Test the proxy
./test-proxy.sh test-proxy
# 4. Check status of all services
./test-proxy.sh status
# 5. Stop test backends when done
./test-proxy.sh stopStep 1: Start Backend Servers
# Terminal 1
python3 examples/simple-http-server.py --port 3000 --name backend1
# Terminal 2
python3 examples/simple-http-server.py --port 3001 --name backend2
# Terminal 3
python3 examples/simple-http-server.py --port 3002 --name backend3Step 2: Start Aegis
# Terminal 4: Build and start data plane
make build-rust
make run-data
# Terminal 5: Build and start control plane
make build-go
make run-controlStep 3: Test Load Balancing
# Send multiple requests to see round-robin distribution
for i in {1..9}; do
echo "Request $i:"
curl -s http://localhost:8080/api/test
echo ""
doneExpected output: Requests should rotate through backend1 → backend2 → backend3 → backend1...
Step 4: Test Health Checks
# Check overall health
curl http://localhost:9090/health
# Expected: {"status":"ok","backends":{"localhost:3000":true,"localhost:3001":true,"localhost:3002":true}}Step 5: Test Backend Failover
# Stop one backend (Ctrl+C in its terminal)
# Then test again
curl http://localhost:8080/api/test
# Verify only healthy backends receive traffic
curl http://localhost:9090/healthStep 6: Monitor Metrics
# View Prometheus metrics
curl http://localhost:9091/metrics
# Metrics include:
# - go_goroutines
# - go_memstats_*
# - process_*
# And more...# Test raw TCP connection
printf "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n" | nc localhost 8080
# Test UDP proxy
echo "test_message" | nc -u -w1 localhost 8081Aegis includes a comprehensive UDP test suite:
# Start UDP backends
make udp-backends-start
# or
./scripts/test-udp-proxy.sh start
# Run comprehensive UDP tests
make test-udp
# or
./scripts/test-udp-proxy.sh test
# Tests include:
# - Basic packet forwarding
# - Load balancing across backends
# - NAT session tracking
# - Rate limiting
# - Circuit breaker
# - Metrics collection
# - Stress testing (1000 packets)
# Stop UDP backends
make udp-backends-stop
# or
./scripts/test-udp-proxy.sh stop# Test Go control plane
cd control-plane && go test ./...
# Test Rust data plane
cd data-plane && cargo test
# Run all tests
make test# Run comprehensive test suite for advanced features
./scripts/test-advanced-features.sh
# Tests include:
# - Load balancing algorithms (round-robin, least-connections, weighted, consistent-hash)
# - Rate limiting and burst handling
# - Circuit breaker failure detection
# - UDP proxy session management
# - Metrics collection# Install Apache Bench (if not installed)
# Ubuntu: sudo apt install apache2-utils
# macOS: brew install httpd
# Send 1000 requests with 10 concurrent connections
ab -n 1000 -c 10 http://localhost:8080/api/test# 1. Monitor backend health in real-time
watch -n 1 'curl -s http://localhost:9090/health | jq'
# 2. Stop a backend (Ctrl+C in backend terminal)
# 3. Observe automatic traffic redirection to healthy backends
# 4. Restart the backend and watch it rejoin the pool# Edit config.yaml to add/modify backends
vim config.yaml
# Restart control plane to apply changes
# New configuration will be pushed to data plane automatically# Drain connections
curl -X POST http://localhost:9090/drain
# Verify no active connections
curl http://localhost:9090/status# Install wrk
git clone https://github.com/wg/wrk.git
cd wrk && make
# Run load test
./wrk -t12 -c400 -d30s http://localhost:8080/# Install hey
go install github.com/rakyll/hey@latest
# Run load test
hey -n 10000 -c 100 http://localhost:8080/ab -n 10000 -c 100 http://localhost:8080/bench/run-bench.sh benchmarks Aegis against a raw nginx baseline: the
same nginx instance backs both arms, so the only variable is the overhead
Aegis itself adds. It builds an isolated stack and runs wrk (via the
williamyeh/wrk Docker image — no local install needed) directly against
nginx, then again through the Aegis TCP proxy.
./bench/run-bench.sh [duration] [connections] [threads]
# defaults: 15s 100 4Measured (15s / 100 connections / 4 threads, single-vCPU sandbox — see caveat below):
| direct → nginx | through Aegis | delta | |
|---|---|---|---|
| Requests/sec | 4513.18 | 3270.96 | -27.5% |
| Latency p50 | 19.80ms | 23.38ms | +18% |
| Latency p90 | 33.54ms | 60.10ms | +79% |
| Latency p99 | 61.95ms | 73.29ms | +18% |
This ran on a 1-vCPU sandbox shared across nginx, the load generator, and both Aegis processes — treat the absolute numbers as sandbox-limited and the direct-vs-Aegis ratio as the meaningful, directional signal, not a formal SLA.
Per-function profiling (pprof/flamegraph/criterion, Go + Rust) — what
actually costs time inside Aegis, not just the end-to-end number above:
docs/profiling/PROFILING.md.
A live backend crash against the real docker-compose stack — circuit
breaker, health checker, and a real cross-component gap the two surfaced
together: bench/FAILURE_TESTING.md. Reproduce
with ./scripts/failure-demo.sh.
Aegis provides comprehensive observability through Prometheus metrics and Grafana dashboards.
When using Docker Compose, monitoring is automatically configured:
# Start the full stack with monitoring
docker-compose up -d
# Access monitoring tools
open http://localhost:3030 # Grafana (admin/admin)
open http://localhost:9092 # PrometheusTwo independent endpoints:
http://localhost:9091/metrics— control plane, aggregated from the data plane over gRPC (also carries Go runtime metrics)http://localhost:9100/metrics— data plane, direct, rebuilt from live counters on every scrape. Keeps working even if the control plane is down, since it doesn't route through it.
Metrics Endpoint: http://localhost:9091/metrics
Key Metrics:
Request Metrics:
proxy_requests_total{backend="..."}- Total requests per backendproxy_errors_total{backend="..."}- Total errors per backendproxy_request_duration_seconds- Request latency histogram
Connection Metrics:
proxy_active_connections- Current active connectionsproxy_total_connections- Total connections handledproxy_bytes_sent_total- Total bytes sentproxy_bytes_received_total- Total bytes received
Circuit Breaker Metrics:
proxy_circuit_breaker_state{backend="..."}- State (0=closed, 1=half-open, 2=open)proxy_circuit_breaker_trips_total{backend="..."}- Number of trips
Rate Limiter Metrics:
proxy_rate_limit_rejected_total- Rejected requests due to rate limiting
Connection Pool Metrics (data plane only, :9100/metrics):
proxy_pool_hits_total- Backend connections served from the pre-warmed poolproxy_pool_misses_total- Backend connections that required a fresh dial
Backend Health:
proxy_backend_healthy{backend="..."}- Health status (0=unhealthy, 1=healthy)proxy_backend_connections{backend="..."}- Per-backend connection countproxy_backend_requests_total{backend="..."}- Per-backend request countproxy_backend_failures_total{backend="..."}- Per-backend failure count
Example Queries:
# View all metrics
curl http://localhost:9091/metrics
# View only proxy metrics
curl http://localhost:9091/metrics | grep proxy_
# Check request rate
curl -s http://localhost:9091/metrics | grep proxy_requests_totalThe data plane emits one structured JSON line per connection (both TCP and UDP) at target=access_log, covering every exit path — rate limited, no healthy backend, circuit breaker open, connect failure/timeout, and normal close:
docker-compose logs data-plane | grep access_log
# {"protocol":"tcp","client_ip":"127.0.0.1","backend":"localhost:3000","bytes_sent":77,"bytes_received":783,"duration_ms":0.8,"error":null}Pre-configured Dashboard includes:
- Request rate and error rate
- Request latency (p50, p95, p99)
- Active connections
- Circuit breaker states per backend
- Rate limit rejections
- Backend health status
- Traffic distribution across backends
Using Grafana:
- Navigate to http://localhost:3030
- Login with
admin/admin(change password on first login) - Go to Dashboards → Browse
- Select Aegis Proxy Dashboard
- Dashboard auto-refreshes and shows last 15 minutes by default
Customizing Dashboards:
- Open the Aegis Proxy Dashboard
- Click the gear icon (⚙️) to edit
- Add new panels or modify existing ones
- Click "Save dashboard" to persist changes
Prometheus is configured to scrape metrics every 10 seconds. Configuration in prometheus.yml:
global:
scrape_interval: 10s
scrape_configs:
- job_name: 'aegis'
static_configs:
- targets: ['control-plane:9091']Alerting Rules:
The following alerts are configured in prometheus-alerts.yml:
| Alert | Severity | Condition | Duration |
|---|---|---|---|
| HighProxyErrorRate | warning | Error rate > 5% | 5 minutes |
| CircuitBreakerOpen | warning | Circuit breaker open | 2 minutes |
| HighRateLimitRejections | info | > 10 rejections/sec | 5 minutes |
| BackendUnhealthy | critical | Backend down | 1 minute |
| HighConnectionCount | warning | > 1000 connections | 10 minutes |
Check Alerts:
# View active alerts
open http://localhost:9092/alerts
# View alert rules
open http://localhost:9092/rulesGenerate test traffic to see metrics in action:
# Generate TCP traffic
for i in {1..100}; do
curl -s http://localhost:8080/ > /dev/null
sleep 0.1
done
# Check metrics update
curl http://localhost:9091/metrics | grep proxy_requests_total
# View in Grafana
open http://localhost:3030Monitor and control Aegis via the Admin API (port 9090):
# Health status with backend states (no auth required)
curl http://localhost:9090/health
# Read-only dashboard — backend health, weight, circuit state (no auth required)
open http://localhost:9090/dashboard
# List backends with health state + circuit breaker state (no auth required)
curl http://localhost:9090/backends
# Proxy configuration and status (no auth required)
curl http://localhost:9090/status
# Add a backend at runtime (auth required)
curl -X POST http://localhost:9090/backends \
-H "Authorization: Bearer $AEGIS_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"address":"db4.internal:5432","weight":100}'
# Remove a backend at runtime (auth required)
curl -X DELETE "http://localhost:9090/backends/db4.internal:5432" \
-H "Authorization: Bearer $AEGIS_API_TOKEN"
# Reload configuration from disk (auth required)
curl -X POST http://localhost:9090/reload \
-H "Authorization: Bearer $AEGIS_API_TOKEN"
# Drain connections for graceful shutdown (auth required)
curl -X POST http://localhost:9090/drain \
-H "Authorization: Bearer $AEGIS_API_TOKEN"Authentication: Set AEGIS_API_TOKEN in your .env file or environment. When empty, auth is disabled (default for local dev). Read-only endpoints (/health, /status, /backends GET) never require auth.
aegis-tui is a live-refreshing terminal dashboard — the same read-only data as GET /dashboard, but with more of it, TCP and UDP, and a set of demo actions to trigger and watch react live. It's meant for demos and at-a-glance operator visibility, not backend management (that's still aegis-ctl — the TUI never mutates Aegis's own config, only external things like a demo backend container).
make build-tui # or: cd control-plane && go build -o aegis-tui ./cmd/aegis-tui
make run-tui # or: AEGIS_URL=http://localhost:9090 ./control-plane/aegis-tuiOn launch it shows an ASCII splash while the first poll is in flight, then switches to the dashboard. On a wide terminal (roughly 165+ columns) it renders as a two-column layout — backend tables + a stats sidebar on top, event feed + actions list below; narrower terminals fall back to the original single-column stack automatically, no broken layout either way.
What it shows, refreshed every second:
- Separate TCP and UDP backend panels — health, circuit breaker state, weight, connected count, request count (as a proportional bar, so load-balancing distribution is visible at a glance), failures, and per-backend latency
- The algorithm currently in effect and whether session affinity is on, read live from the Admin API
- Global stats with rolling sparklines for request rate and latency, pulled directly from the data plane's own
:9100/metrics— plus connection-pool hit rate, rate-limit rejections, circuit breaker trips - A live event feed, generated by diffing successive polls (not log tailing) — health flips, circuit breaker transitions, control-plane/data-plane reachability changes, and your own triggered actions all show up as timestamped lines, cause next to effect
- Two independent reachability indicators for the Admin API and the data plane's metrics endpoint. Killing the control plane doesn't blank the dashboard — the backend table freezes at its last known state while data-plane-sourced stats keep updating, a live demonstration of the control/data-plane split
Actions (same trust level as aegis-ctl's drain/reload — no confirmation prompts, run from repo root so docker compose/docker-compose can find docker-compose.yml):
| Key | Action |
|---|---|
1/2/3 |
Kill backend1/backend2/backend3 (docker compose kill) |
4/5/6 |
Restart backend1/backend2/backend3 (docker compose start) |
7 |
Fire a concurrent TCP burst — enough to trip rate limiting on a normal multi-core machine (see data-plane/src/rate_limiter.rs config); this project's own 1-vCPU sandbox can't reliably exceed the configured rate no matter the burst size, so don't be surprised if it doesn't trip there |
8 |
Fire a UDP burst at the UDP proxy |
p |
Pause/resume polling — freezes the display for a screenshot without quitting |
? |
Toggle a help screen listing all of this |
q |
Quit |
Env vars: AEGIS_URL (Admin API, default http://localhost:9090), AEGIS_DATA_METRICS_URL (data plane metrics, default http://localhost:9100/metrics), AEGIS_PROXY_URL (TCP burst target, default http://localhost:8080/api/test), AEGIS_UDP_ADDR (UDP burst target, default localhost:8081).
A full recordable walkthrough — proving all 4 load-balancing algorithms live, session affinity, dynamic backend management via aegis-ctl, and the resilience beat, not just kill/restart/burst — is in bench/DEMO_SCRIPT.md.
Both Prometheus and Grafana use Docker volumes for data persistence:
prometheus-data- Stores time-series datagrafana-storage- Stores dashboards and settings
Reset monitoring data:
docker-compose down -v
docker-compose up -dGrafana shows "No data":
# Check Prometheus is running
docker-compose ps prometheus
# Verify Prometheus is scraping
open http://localhost:9092/targets
# Ensure control-plane exposes metrics
curl http://localhost:9091/metricsPrometheus can't scrape control-plane:
# Check control-plane health
docker-compose ps control-plane
# Test network connectivity
docker-compose exec prometheus wget -O- http://control-plane:9091/metricsAlerts not firing:
# Generate load to trigger conditions
ab -n 1000 -c 50 http://localhost:8080/
# Check alert status
open http://localhost:9092/alerts
# Verify rules are loaded
open http://localhost:9092/rulesFor production deployments:
- Configure AlertManager for notifications (email, Slack, PagerDuty)
- Increase retention in Prometheus (default: 15 days)
- Enable authentication on Prometheus
- Use HTTPS with proper certificates
- Set up Grafana SSO for team access
- Configure backups for dashboards and Prometheus data
- Tune scrape intervals based on your needs
aegis/
├── proto/ # Shared protobuf definitions
│ └── proxy.proto # gRPC service definitions
│
├── control-plane/ # Go control plane
│ ├── cmd/
│ │ ├── main.go # Control plane entry point
│ │ ├── aegis-ctl/ # Operator CLI tool
│ │ │ └── main.go
│ │ └── aegis-tui/ # Live terminal dashboard
│ ├── internal/
│ │ ├── api/ # REST API handlers + tests
│ │ │ └── dashboard.html # Read-only dashboard (go:embed)
│ │ ├── config/ # Configuration management + validation + tests
│ │ ├── grpc/ # gRPC client to data plane
│ │ ├── health/ # Health checker + tests
│ │ └── metrics/ # Prometheus metrics + circuit state tracking
│ ├── proto/ # Generated protobuf code
│ ├── aegis-control # Binary (after build)
│ ├── aegis-ctl # CLI binary (after build)
│ ├── aegis-tui # TUI binary (after build)
│ └── go.mod
│
├── data-plane/ # Rust data plane
│ ├── src/
│ │ ├── main.rs # Entry point
│ │ ├── tcp_proxy.rs # TCP forwarding logic
│ │ ├── udp_proxy.rs # UDP forwarding logic
│ │ ├── grpc_server.rs # gRPC service implementation
│ │ ├── load_balancer.rs # Load balancing algorithms
│ │ ├── rate_limiter.rs # Rate limiting
│ │ ├── circuit_breaker.rs # Circuit breaker
│ │ ├── connection.rs # Pre-warmed backend connection pool
│ │ ├── access_log.rs # Structured JSON per-connection logging
│ │ ├── config.rs # Configuration structures
│ │ ├── metrics.rs # Metrics collection
│ │ └── metrics_server.rs # Direct Prometheus /metrics endpoint (9100)
│ ├── target/release/
│ │ └── aegis-data # Binary (after build)
│ ├── Cargo.toml
│ └── build.rs
│
├── examples/ # Test utilities
│ ├── simple-http-server.py # Test HTTP backend server
│ ├── udp-echo-server.py # Test UDP backend server
│ └── examples.md # Examples documentation
│
├── scripts/ # Testing and automation scripts
│ ├── test-proxy.sh # TCP proxy testing
│ ├── test-udp-proxy.sh # UDP proxy testing
│ └── test-advanced-features.sh # Advanced features testing
│
├── grafana/ # Grafana configuration
│ └── provisioning/
│ ├── dashboards/ # Pre-configured dashboards
│ └── datasources/ # Datasource configuration
│
├── charts/aegis/ # Helm chart for Kubernetes deployment
│ ├── values.yaml
│ └── templates/
│
├── .env.example # Environment variable template (copy to .env)
├── config.yaml # Local development config (localhost)
├── config.docker.yaml # Docker config (service names)
├── docker-compose.yml # Full stack deployment
├── Dockerfile.control # Control plane image
├── Dockerfile.data # Data plane image
├── prometheus.yml # Prometheus configuration
├── prometheus-alerts.yml # Alert rules
├── Makefile # Build automation
├── README.md # This file
└── TODO.md # Project tasks and roadmap
# Format Go code
cd control-plane && go fmt ./...
# Format Rust code
cd data-plane && cargo fmt# Lint Go code
cd control-plane && go vet ./...
# Lint Rust code
cd data-plane && cargo clippy# Format, lint, test, and build
make dev1. gRPC Connection Failed
- Ensure data plane starts before control plane
- Check port 50051 is not in use:
lsof -i :50051
2. Port Already in Use
- Change ports in
config.yaml - Check what's using the port:
lsof -i :8080
3. Protobuf Generation Fails
- Verify protoc is installed:
protoc --version - Install Go plugins:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest - Important: Ensure Go's bin directory is in your PATH (see Configure Go PATH section)
4. protoc-gen-go: program not found or is not executable
This error means protoc can't find the Go plugins. Fix it by adding Go's bin directory to your PATH:
# Check where Go installs binaries
go env GOPATH
# Add to your shell config (~/.bashrc or ~/.zshrc)
export GOPATH="$HOME/go"
export PATH="$PATH:$GOPATH/bin"
# Reload your shell
source ~/.bashrc # or source ~/.zshrc
# Verify it works
which protoc-gen-go
protoc-gen-go --version5. Backend Connection Refused
- Ensure backend services are running
- Check backend addresses in
config.yaml - Verify health check paths are correct
- Basic TCP proxy
- Health checking
- Metrics pipeline
- Load balancing (round-robin, weighted, least-connections, consistent-hash)
- UDP proxy with NAT
- Rate limiting
- Circuit breaking
- Configuration hot reload (
POST /reload) - Session affinity via consistent hashing
- Read timeout enforcement
- Admin API authentication (Bearer token via
AEGIS_API_TOKEN) - Dynamic backend management API (
GET/POST/DELETE /backends) -
aegis-ctlCLI for live backend management - SIGTERM handling for container/Kubernetes graceful shutdown
- TLS on the control-plane ↔ data-plane gRPC channel
- Helm chart for Kubernetes
- Pre-warmed backend connection pooling
- Structured JSON access logging (TCP + UDP, all exit paths)
- Config validation on load/reload
- Direct Prometheus scrape on the data plane (independent of control plane)
- Read-only admin dashboard (
GET /dashboard) - Circuit breaker state persistence across restarts/reloads
- Per-function profiling evidence (pprof, criterion, flamegraphs)
- Live failure-injection demo (backend crash, health checker + circuit breaker)
- Zero-downtime reload (preserve active connections)
- HTTP/2 support
- Distributed tracing
- Kubernetes service discovery (auto-register backends from Service endpoints)
- Redis-backed multi-instance coordination (circuit breaker state)
- Circuit breaker state reset on unrelated backend health-check reloads (see
bench/FAILURE_TESTING.mdFinding 2, fixed —TASK.md) - Live terminal dashboard (
aegis-tui) — backend health, circuit breaker transitions, and load-balancing distribution in real time
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE file for details
Built with:
- Go - Control plane and orchestration
- Rust - High-performance data plane
- gRPC - Inter-process communication
- Tokio - Async runtime for Rust
- Prometheus - Metrics collection
Author: lazzerex
Project: Aegis Network Proxy
