Skip to content

Uncomplicated L7 Load Balancer & Reverse Proxy with Admin API

License

Notifications You must be signed in to change notification settings

unkn0wn-root/terraster

Repository files navigation

Terraster

MIT License

A high-performance, feature-rich Layer 7 (L7) load balancer with a robust and user-friendly admin API.

Key Features

  • Multiple load balancing methods including Round Robin, Weighted Round Robin, and IP Hash
  • TLS termination with certificate management
  • Path rewriting and service-to-service redirection
  • Dynamic configuration via comprehensive Admin API
  • Multiple host support on the same port
  • HTTP compression
  • Certificate expiration notifications via email

Feature Status

Load Balancing Algorithms

  • âś… Round Robin
  • âś… Weighted Round Robin
  • âś… Least Connections
  • âś… Weighted Least Connections
  • âś… Response Time Based
  • âś… IP Hash
  • âś… Consistent Hashing
  • âś… Adaptive Load Balancing

Advanced Features

  • ⏳ WebSocket Support (WIP)
  • âś… SSL/TLS Support
  • ⏳ Automatic Certificate Management (WIP)
  • âś… Connection Pooling
  • âś… Circuit Breaker
  • âś… Rate Limiting
  • âś… Compression
  • ❌ Custom Request Headers - WIP
  • âś… Dynamic Middleware Plug-in
  • âś… Configurable Request Logging

Core Features

  • âś… Health Checking
  • âś… Dynamic Configuration via Admin API
  • âś… Graceful Shutdown

Quick Start

Building from Source

go build -o terraster cmd/terraster

Configuration Guide

Configuration Methods

Terraster offers three ways to manage your configuration:

  1. Single Config File

    • Create a config file anywhere and use the -config flag
    • Example: ./terraster -config /path/to/config.yaml
  2. Default Config

    • Place config.yaml in the root directory
    • Terraster will load it automatically at startup
  3. Multiple Services

    • Create a directory containing multiple service configs
    • Use the -services flag to point to the directory
    • Example: ./terraster -services /path/to/services/

Basic Configuration

The minimal configuration requires only three fields:

port: 8080
host: "lb.domain.com"
backends:
  - url: http://localhost:8081
  - url: http://localhost:8082

Basic Configuration with Middleware and TLS

This configuration demonstrates TLS termination and basic middleware setup:

port: 8080
algorithm: round-robin
host: "lb.domain.com"
backends:
  - url: http://localhost:8081
  - url: http://localhost:8082

# Middleware Configuration
middleware:
  - rate_limit:
      requests_per_second: 100
      burst: 30
  - security:
      hsts: true
      frame_options: DENY
      xss_protection: true

# TLS Configuration (optional)
tls:
  enabled: true
  cert_file: "./certificates/my_cert.pem"
  key_file: "./certificates/my_cert_privatekey.key"

Advanced Configuration

This example demonstrates a comprehensive setup with multiple services, health checks, and advanced features:

### GLOBAL CONFIG ###
port: 443

# Global Health Check Configuration
health_check:
  interval: 10s
  timeout: 2s
  path: /health

# Global Middleware Configuration
middleware:
  - rate_limit:
      requests_per_second: 100
      burst: 150
  - security:
      hsts: true
      hsts_max_age: 31536000
      frame_options: DENY
      content_type_options: true
      xss_protection: true
  - circuit_breaker:
      threshold: 5
      timeout: 60s

# Global Connection Pool Settings
connection_pool:
  max_idle: 100
  max_open: 1000
  idle_timeout: 90s

### SERVICES CONFIGURATION ###
services:
  # Backend API Service
  - name: backend-api
    host: internal-api1.local.com
    port: 8455
    log_name: backend-api  # Maps to logger configuration
    
    # Service-specific TLS
    tls:
      cert_file: "/path/to/api-cert.pem"
      key_file: "/path/to/api-key.pem"
    
    # Service-specific middleware (overrides global)
    middleware:
      - rate_limit:
          requests_per_second: 2500
          burst: 500
    
    # Service-specific health check
    health_check:
      type: "http"
      path: "/"
      interval: "5s"
      timeout: "3s"
      thresholds:
        healthy: 2
        unhealthy: 3
    
    # Path-based routing
    locations:
      - path: "/api/"
        lb_policy: round-robin
        redirect: "/"
        backends:
          - url: http://internal-api1.local.com:8455
            weight: 5
            max_connections: 1000
            # Backend-specific health check
            health_check:
              type: "http"
              path: "/api_health"
              interval: "4s"
              timeout: "3s"
              thresholds:
                healthy: 1
                unhealthy: 2
          - url: http://internal-api2.local.com:8455
            weight: 3
            max_connections: 800

  # Frontend Service
  - name: frontend
    host: frontend.local.com
    port: 443
    locations:
      - path: "/"
        lb_policy: least_connections
        rewrite: "/frontend/"
        backends:
          - url: http://frontend-1.local.com:3000
            weight: 5
            max_connections: 1000
          - url: http://frontend-2.local.com:3000
            weight: 3
            max_connections: 800

  # HTTP to HTTPS Redirect Service
  - name: frontend_redirect
    host: frontend.local.com
    port: 80
    http_redirect: true
    redirect_port: 443

  # Custom Port Redirect Service
  - name: backend_api_redirect
    host: internal-api1.local.com
    port: 80
    http_redirect: true
    redirect_port: 8455

Logging Configuration

1. Default Logger

If no custom logging configuration is provided, Terraster will use the default logger configuration from log.config.json. Your services will use the service_default logger automatically.

{
  "loggers": {
    "terraster": {
      "level": "debug",
      "outputPaths": ["terraster.log"],
      "errorOutputPaths": ["stderr"],
      "development": false,
      "logToConsole": true
    },
    "service_default": {
      "level": "info",
      "outputPaths": ["service_default.log"],
      "errorOutputPaths": ["service_default_error.log"],
      "development": false,
      "logToConsole": false
    }
  }
}

2. Single Custom Logger Configuration

Create one custom logging configuration file for all services. Each service can reference a specific logger by name in its configuration.

{
  "loggers": {
    "api-services": {
      "level": "info",
      "outputPaths": ["api-services.log"],
      "errorOutputPaths": ["api-errors.log"],
      "development": false,
      "logToConsole": false,
      "logRotation": {
        "enabled": true,
        "maxSizeMB": 50,
        "maxBackups": 10,
        "maxAgeDays": 30,
        "compress": true
      }
    },
    "frontend-services": {
      "level": "debug",
      "outputPaths": ["frontend.log"],
      "errorOutputPaths": ["frontend-errors.log"],
      "development": true,
      "logToConsole": true
    }
  }
}

Use in service configuration:

services:
  - name: backend-api
    log_name: api-services  # References logger name from config
    # ... rest of service config

  - name: frontend
    log_name: frontend-services  # References logger name from config
    # ... rest of service config

3. Separate Logger Configuration Per Service

Create individual log configuration files for each service. Each file must start with the loggers key.

backend-api.log.json:

{
  "loggers": {
    "backend-api": {
      "level": "info",
      "outputPaths": ["backend-api.log"],
      "errorOutputPaths": ["backend-api-error.log"],
      "development": false,
      "logToConsole": false
    }
  }
}

frontend.log.json:

{
  "loggers": {
    "frontend": {
      "level": "debug",
      "outputPaths": ["frontend.log"],
      "errorOutputPaths": ["frontend-error.log"],
      "development": true,
      "logToConsole": true
    }
  }
}

Running Terraster with Different Logging Configurations

# Using default logger
./terraster --config config.yaml

# Using single custom log config
./terraster --config config.yaml --log_configs custom.log.json

# Using separate log configs for each service
./terraster --config config.yaml --log_configs backend-api.log.json,frontend.log.json

# Using default logger and appending additional loggers
./terraster --config config.yaml --log_configs additional.log.json

Important Notes:

  • All log config files must start with the loggers key
  • When using multiple config files, make sure logger names are unique
  • If no log_name is specified in service configuration, the service will use the service_default logger
  • You can append additional loggers to the default configuration by providing them via --log_configs

Admin API Setup

Database Configuration

  1. Create or use the provided API configuration file:
api:
  enabled: true
  host: lb-api.domain.com
  port: 8081
  tls:
    cert_file: "./certs/admin.pem"
    key_file: "./certs/admin_key.key"

database:
  path: "./api.db"

auth:
  jwt_secret: "YourSecretKey"
  token_cleanup_interval: "7h"
  password_expiry_days: 3
  1. Create an admin user:
go run scripts/database/api_util.go --config ./api.config.yaml \
  -username "lb_admin" \
  -password "SecurePassword123" \
  -role "admin"

API Examples

Get Backend Status

curl http://localhost:8081/api/backends \
    -H "Authorization: Bearer ${JWT_TOKEN}" \
    -H "Content-Type: application/json"

Add Backend

curl -X POST http://localhost:8081/api/backends?service_name=backend-api \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -d '{
    "url": "http://newbackend:8080",
    "weight": 5
  }'

Docker Deployment

Dockerfile

FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o terraster cmd/main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/terraster .
COPY config.yaml .

EXPOSE 8080 8081 9090
CMD ["./terraster", "--config", "config.yaml"]

Docker Compose

version: '3.8'

services:
  terraster:
    build: .
    ports:
      - "8080:8080"
      - "8081:8081"
      - "9090:9090"
    volumes:
      - ./config.yaml:/root/config.yaml
      - ./certs:/etc/certs
    restart: unless-stopped

Benchmarking

A benchmarking script is included in the tools/benchmark directory. Run it with:

go run tools/benchmark/main.go -url http://localhost:8080 -c 10 -n 1000

Available flags:

  • -url: Target URL (default: "http://localhost:8080")
  • -c: Number of concurrent requests (default: 10)
  • -n: Total number of requests (default: 1000)
  • -d: Duration of the test (e.g., "30s", "5m")