Dynamic HAProxy load balancer that creates separate ACLs and backends for each controller. Deploys Guacamole VMs in GCP using Terraform with automatic HAProxy configuration.
┌─────────────────────────────────────┐
│ HAProxy │ :80 - HTTP traffic
│ │ :9999 - Stats
│ Frontend: console_frontend │
│ ├─ ACL: guac_access_controller1 │──► Backend: backend_controller1
│ ├─ ACL: guac_access_controller2 │──► Backend: backend_controller2
│ └─ ACL: guac_access_controllerN │──► Backend: backend_controllerN
└─────────────────────────────────────┘
│
├─► /console/controller1/* ──► backend_controller1 ──► VM1 (35.1.2.3:8080)
├─► /console/controller2/* ──► backend_controller2 ──► VM2 (35.1.2.4:8080)
└─► /console/controllerN/* ──► backend_controllerN ──► VMN (...)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Deployer 1 │ │ Deployer 2 │ │ Deployer N │
│ (Terraform) │ │ (Terraform) │ │ (Terraform) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
├─► Creates GCP VMs with Guacamole
├─► Generates controller config snippet
├─► Rebuilds HAProxy config
└─► Graceful reload (near-zero downtime)
- Separate ACL + Backend per Controller: Each controller gets its own dedicated backend
- Dynamic Configuration: Config generated from snippets, validated, and gracefully reloaded
- GCP-Optimized: Ready-to-use Google Cloud Platform examples
- Multiple Deployers: Run concurrent deployers safely
- URL Routing: Access each VM at
/console/<controller_id>/ - Near-Zero Downtime: Graceful reload when adding/removing controllers
- WebSocket Support: Optimized for Guacamole connections
When Terraform deploys a VM:
# 1. Create controller config snippet
/scripts/manage-controller.sh add controller123 35.1.2.100 8080
# This creates: /etc/haproxy/controllers/controller123.conf
# Containing:
# - ACL definition: guac_access_controller123 path_beg /console/controller123
# - Backend definition: backend_controller123 with server 35.1.2.100:8080
# 2. Rebuild HAProxy config
docker exec haproxy /usr/local/bin/manage-controller.sh rebuild
# This:
# - Reads all controller configs from /etc/haproxy/controllers/
# - Injects ACLs into frontend section
# - Injects backends into backends section
# - Validates the new config
# - Gracefully reloads HAProxy (no dropped connections)For each controller, HAProxy will have:
frontend console_frontend
# ... other config ...
acl guac_access_controller123 path_beg /console/controller123
use_backend backend_controller123 if guac_access_controller123
acl guac_access_prod01 path_beg /console/prod01
use_backend backend_prod01 if guac_access_prod01
backend backend_controller123
balance roundrobin
option httpchk GET /
server vm_backend_controller123 35.1.2.100:8080 check
backend backend_prod01
balance roundrobin
option httpchk GET /
server vm_backend_prod01 10.0.1.50:8080 check
- GCP account with project created
- Docker and docker-compose installed
- GCP credentials configured (
gcloud auth login)
cd haproxy
# Create the network first
docker network create haproxy-network
# Start HAProxy
docker-compose up -d
# Verify it's running
docker logs haproxy
curl http://localhost:9999/statscd deployer
# Copy and configure variables
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars
# Set your GCP project
# gcp_project = "my-project-123"
# haproxy_public_url = "http://35.1.2.3" # Your HAProxy public IP
# Copy provider configuration
cp providers.tf.example providers.tf
# GCP provider is already configured - ready to use!# Set environment variables for GCP
export GOOGLE_PROJECT="your-gcp-project-id"
export HAPROXY_PUBLIC_URL="http://your-haproxy-ip"
# Start deployer container
DEPLOYER_ID=1 docker-compose up -d
# Enter deployer
docker exec -it deployer_1 sh
# Inside container:
terraform init
terraform plan
terraform applyTerraform will:
- Create a GCP VM named
safa4-cont-<random-id>.my.domain.com - Install Guacamole on the VM
- Generate controller config snippet
- Rebuild HAProxy config with new ACL + backend
- Gracefully reload HAProxy
- Output console URL
# Get the console URL from Terraform output
terraform output console_url
# Example: http://35.1.2.3/console/a1b2c3d4/Default Guacamole credentials: guacadmin / guacadmin
# View the generated HAProxy config
docker exec haproxy cat /usr/local/etc/haproxy/haproxy.cfg
# You should see your controller's ACL and backendRun multiple deployers simultaneously:
cd deployer
# Terminal 1
DEPLOYER_ID=1 GOOGLE_PROJECT=my-project docker-compose up -d
docker exec -it deployer_1 sh
# terraform apply
# Terminal 2
DEPLOYER_ID=2 GOOGLE_PROJECT=my-project docker-compose up -d
docker exec -it deployer_2 sh
# terraform apply
# Terminal 3
DEPLOYER_ID=3 GOOGLE_PROJECT=my-project docker-compose up -d
docker exec -it deployer_3 sh
# terraform applyAll deployers share the same controllers directory and can register backends concurrently.
Use the management script directly:
# From deployer container:
/scripts/manage-controller.sh add controller123 35.1.2.100 8080
# Rebuild HAProxy config:
docker exec haproxy /usr/local/bin/manage-controller.sh rebuild/scripts/manage-controller.sh remove controller123
docker exec haproxy /usr/local/bin/manage-controller.sh rebuildls -la /etc/haproxy/controllers/cat /etc/haproxy/controllers/controller123.confLocated in haproxy/haproxy.cfg:
frontend console_frontend
bind *:80
### CONTROLLERS_START ###
# ACLs and use_backend rules inserted here
### CONTROLLERS_END ###
default_backend no_backend
### BACKENDS_START ###
# Backend definitions inserted here
### BACKENDS_END ###Key settings:
- Timeout tunnel: 3600s for WebSocket connections
- Stats port: 9999
- Graceful reload: No connection drops when updating config
Edit deployer/terraform.tfvars:
# GCP settings
gcp_project = "my-gcp-project"
gcp_region = "us-central1"
gcp_zone = "us-central1-a"
# VM configuration
vm_machine_type = "e2-medium"
vm_image = "ubuntu-os-cloud/ubuntu-2204-lts"
# HAProxy public URL
haproxy_public_url = "http://35.1.2.3"Specify a custom controller ID instead of random:
# In terraform.tfvars
controller_id = "prod-us-central1"This will create:
- ACL:
guac_access_prod-us-central1 - Backend:
backend_prod-us-central1 - URL:
/console/prod-us-central1/
http://your-haproxy:9999/stats
docker exec haproxy cat /usr/local/etc/haproxy/haproxy.cfgdocker exec haproxy ls -la /etc/haproxy/controllers/docker exec haproxy cat /etc/haproxy/controllers/controller123.confdocker logs -f haproxyCheck controller config was created:
docker exec haproxy ls /etc/haproxy/controllers/Check if config was rebuilt:
docker exec haproxy cat /usr/local/etc/haproxy/haproxy.cfg | grep controller123Manually trigger rebuild:
docker exec haproxy /usr/local/bin/manage-controller.sh rebuildVerify ACL exists in config:
docker exec haproxy grep "guac_access_controller123" /usr/local/etc/haproxy/haproxy.cfgVerify backend exists:
docker exec haproxy grep "backend_controller123" /usr/local/etc/haproxy/haproxy.cfgCheck HAProxy logs:
docker logs -f haproxyTest VM directly:
curl http://<vm-ip>:8080/guacamoleCheck GCP firewall:
gcloud compute firewall-rules list --filter="targetTags:guacamole"SSH to VM and check Guacamole:
gcloud compute ssh <vm-name> --zone=us-central1-a
docker ps
curl localhost:8080/guacamoleCheck syntax:
docker exec haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfgView controller config:
cat /etc/haproxy/controllers/controller123.confCheck for duplicate controllers:
docker exec haproxy ls /etc/haproxy/controllers/ | sort | uniq -d.
├── haproxy/
│ ├── docker-compose.yml # HAProxy container
│ ├── haproxy.cfg # HAProxy config template
│ └── manage-controller.sh # Controller management script
│
├── deployer/
│ ├── docker-compose.yml # Deployer container
│ ├── main.tf # Terraform main
│ ├── variables.tf # Terraform variables (GCP-focused)
│ ├── providers.tf.example # GCP provider config
│ ├── terraform.tfvars.example # Example GCP values
│ └── install-guacamole.sh # Guacamole installation script
│
└── README.md
Add SSL to haproxy/haproxy.cfg:
frontend console_frontend
bind *:443 ssl crt /etc/haproxy/ssl/cert.pem
bind *:80
redirect scheme https if !{ ssl_fc }
Mount certificate in docker-compose.yml:
volumes:
- ./ssl/cert.pem:/etc/haproxy/ssl/cert.pem:roEdit haproxy/manage-controller.sh to customize health checks:
# In the rebuild_config() function, modify:
echo " option httpchk GET /guacamole" >> "$TEMP_BACKENDS"
echo " server vm_${value1} ${value2}:${value3} check inter 5s fall 3 rise 2" >> "$TEMP_BACKENDS"Add path rewriting in backend section:
backend backend_controller123
http-request set-path %[path,regsub(^/console/controller123,/guacamole)]
server vm_backend_controller123 35.1.2.100:8080 check
- Config Validation: All configs are validated before reload
- GCP Firewall: Configure firewall rules for Guacamole VMs
- Guacamole Auth: Change default credentials immediately
- HAProxy Stats: Restrict access to stats page in production
- SSL/TLS: Use HTTPS for production deployments
- Network Isolation: Use VPCs and private networking
✅ Separate Backend per Controller: Exactly what you wanted! ✅ Dynamic ACLs: Each controller gets its own ACL ✅ Isolated Backends: Each VM has its own dedicated backend ✅ Near-Zero Downtime: Graceful reload keeps connections alive ✅ Simple: No API authentication needed ✅ Validated: Config is checked before reload ✅ GCP-Ready: Optimized for Google Cloud Platform
When manage-controller.sh rebuild is called:
- Generate new config from all controller snippets
- Validate config with
haproxy -c - Backup current config
- Write new config
- Send USR2 signal to HAProxy (graceful reload)
- HAProxy:
- Starts new process with new config
- New connections go to new process
- Old connections finish on old process
- Old process exits when all connections done
Result: No dropped connections!
This configuration is provided as-is for educational and operational purposes.
- Check HAProxy logs:
docker logs haproxy - View Terraform state:
terraform show - Validate config:
docker exec haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg - GCP documentation: https://cloud.google.com/compute/docs