Build a production-ready Deno microservices architecture with Consul service discovery, HAProxy load balancing, and comprehensive monitoring using Prometheus. This tutorial covers container orchestration, health checks, and automated failover for scalable applications.
Prerequisites
- Linux server with sudo access
- 4GB+ RAM for running multiple services
- Basic understanding of microservices architecture
- Familiarity with TypeScript/JavaScript
What this solves
Modern applications need to scale horizontally by breaking into smaller services, but managing multiple Deno microservices becomes complex without proper service discovery and load balancing. This tutorial builds a production-ready architecture where Deno services automatically register themselves with Consul, HAProxy distributes traffic based on health checks, and Prometheus monitors the entire stack.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all components.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget unzip software-properties-common
Install Deno runtime
Install Deno using the official installation script, then verify the installation.
curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
deno --version
Install Consul for service discovery
Download and install HashiCorp Consul for service registration and health checking.
CONSUL_VERSION="1.17.0"
wget https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip
unzip consul_${CONSUL_VERSION}_linux_amd64.zip
sudo mv consul /usr/local/bin/
sudo chmod +x /usr/local/bin/consul
consul version
Configure Consul server
Create Consul configuration directory and setup the main configuration file.
sudo mkdir -p /etc/consul.d /opt/consul
sudo useradd --system --home /etc/consul.d --shell /bin/false consul
sudo chown -R consul:consul /etc/consul.d /opt/consul
datacenter = "dc1"
data_dir = "/opt/consul"
log_level = "INFO"
server = true
bootstrap_expect = 1
bind_addr = "0.0.0.0"
client_addr = "0.0.0.0"
ui_config {
enabled = true
}
connect {
enabled = true
}
ports {
grpc = 8502
}
acl = {
enabled = false
default_policy = "allow"
}
Create Consul systemd service
Setup Consul to run as a systemd service with automatic restarts.
[Unit]
Description=Consul
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable consul
sudo systemctl start consul
sudo systemctl status consul
Install HAProxy load balancer
Install HAProxy for distributing traffic across Deno microservices with health checks.
sudo apt install -y haproxy
Configure HAProxy with Consul integration
Setup HAProxy configuration with service discovery integration and health checks.
global
daemon
maxconn 4096
log stdout local0
stats socket /var/run/haproxy.sock mode 660 level admin
stats timeout 30s
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
option dontlognull
option redispatch
retries 3
frontend api_gateway
bind *:80
bind *:443 ssl crt /etc/ssl/certs/haproxy.pem
redirect scheme https if !{ ssl_fc }
# Route based on URL path
acl is_users_service path_beg /api/users
acl is_orders_service path_beg /api/orders
acl is_health path /health
use_backend users_service if is_users_service
use_backend orders_service if is_orders_service
use_backend health_check if is_health
default_backend api_default
backend users_service
balance roundrobin
option httpchk GET /health
http-check expect status 200
# Dynamic backend discovery via Consul
server-template users 3 _users._tcp.service.consul:8080 check resolvers consul
backend orders_service
balance roundrobin
option httpchk GET /health
http-check expect status 200
server-template orders 3 _orders._tcp.service.consul:8081 check resolvers consul
backend api_default
balance roundrobin
server default 127.0.0.1:8000 check
backend health_check
http-request return status 200 content-type text/plain string "HAProxy healthy"
resolvers consul
nameserver consul 127.0.0.1:8600
accepted_payload_size 8192
hold valid 5s
listen stats
bind *:8404
stats enable
stats uri /
stats refresh 5s
stats admin if TRUE
Install Prometheus for monitoring
Download and install Prometheus to monitor your microservices architecture.
PROMETHEUS_VERSION="2.48.0"
wget https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz
tar xvf prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz
sudo mv prometheus-${PROMETHEUS_VERSION}.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-${PROMETHEUS_VERSION}.linux-amd64/promtool /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo useradd --system --home /var/lib/prometheus --shell /bin/false prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus
Configure Prometheus with service discovery
Setup Prometheus to automatically discover services registered in Consul.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'consul'
static_configs:
- targets: ['localhost:8500']
metrics_path: /v1/agent/metrics
params:
format: ['prometheus']
- job_name: 'haproxy'
static_configs:
- targets: ['localhost:8404']
metrics_path: /stats/prometheus
- job_name: 'consul-services'
consul_sd_configs:
- server: 'localhost:8500'
services: ['users', 'orders']
relabel_configs:
- source_labels: [__meta_consul_service]
target_label: job
- source_labels: [__meta_consul_node]
target_label: instance
- source_labels: [__meta_consul_service_address]
target_label: __address__
- source_labels: [__meta_consul_service_port]
target_label: __address__
regex: '(.*)'
replacement: '${1}:${__meta_consul_service_port}'
- job_name: 'deno-services'
consul_sd_configs:
- server: 'localhost:8500'
tags: ['deno', 'microservice']
relabel_configs:
- source_labels: [__meta_consul_service]
target_label: service
- source_labels: [__address__]
target_label: __address__
regex: '([^:]+):(\d+)'
replacement: '${1}:${2}'
metrics_path: '/metrics'
Create Prometheus systemd service
Setup Prometheus to run as a systemd service with proper permissions.
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable prometheus
sudo systemctl start prometheus
Create Deno microservice template
Create a reusable template for Deno microservices with service registration and metrics.
mkdir -p ~/deno-microservices
cd ~/deno-microservices
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
export interface ServiceConfig {
name: string;
port: number;
version: string;
consulUrl?: string;
}
export class MicroService {
private config: ServiceConfig;
private routes: Map Promise> = new Map();
private metrics = {
requests_total: 0,
requests_duration: [] as number[],
errors_total: 0,
};
constructor(config: ServiceConfig) {
this.config = {
consulUrl: "http://localhost:8500",
...config,
};
this.setupDefaultRoutes();
}
private setupDefaultRoutes() {
this.routes.set("/health", async () => {
return new Response(JSON.stringify({ status: "healthy", service: this.config.name }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
this.routes.set("/metrics", async () => {
const metrics = [
# HELP http_requests_total Total number of HTTP requests,
# TYPE http_requests_total counter,
http_requests_total{service="${this.config.name}"} ${this.metrics.requests_total},
# HELP http_request_duration_seconds HTTP request duration in seconds,
# TYPE http_request_duration_seconds histogram,
http_request_duration_seconds_sum{service="${this.config.name}"} ${this.metrics.requests_duration.reduce((a, b) => a + b, 0)},
http_request_duration_seconds_count{service="${this.config.name}"} ${this.metrics.requests_duration.length},
# HELP http_errors_total Total number of HTTP errors,
# TYPE http_errors_total counter,
http_errors_total{service="${this.config.name}"} ${this.metrics.errors_total},
].join("\n");
return new Response(metrics, {
status: 200,
headers: { "Content-Type": "text/plain" },
});
});
}
addRoute(path: string, handler: (req: Request) => Promise) {
this.routes.set(path, handler);
}
private async handleRequest(req: Request): Promise {
const start = Date.now();
this.metrics.requests_total++;
try {
const url = new URL(req.url);
const handler = this.routes.get(url.pathname);
if (!handler) {
this.metrics.errors_total++;
return new Response("Not Found", { status: 404 });
}
const response = await handler(req);
const duration = (Date.now() - start) / 1000;
this.metrics.requests_duration.push(duration);
if (response.status >= 400) {
this.metrics.errors_total++;
}
return response;
} catch (error) {
this.metrics.errors_total++;
console.error(Error handling request:, error);
return new Response("Internal Server Error", { status: 500 });
}
}
private async registerWithConsul() {
const serviceDefinition = {
ID: ${this.config.name}-${Deno.env.get("HOSTNAME") || "localhost"}-${this.config.port},
Name: this.config.name,
Tags: ["deno", "microservice", this.config.version],
Address: Deno.env.get("SERVICE_ADDRESS") || "localhost",
Port: this.config.port,
Check: {
HTTP: http://localhost:${this.config.port}/health,
Interval: "10s",
Timeout: "3s",
},
Meta: {
version: this.config.version,
runtime: "deno",
},
};
try {
const response = await fetch(${this.config.consulUrl}/v1/agent/service/register, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(serviceDefinition),
});
if (response.ok) {
console.log(Service ${this.config.name} registered with Consul);
} else {
console.error("Failed to register with Consul:", await response.text());
}
} catch (error) {
console.error("Error registering with Consul:", error);
}
}
async start() {
await this.registerWithConsul();
console.log(Starting ${this.config.name} service on port ${this.config.port});
await serve(this.handleRequest.bind(this), {
port: this.config.port,
onListen: ({ port, hostname }) => {
console.log(${this.config.name} service listening on http://${hostname}:${port});
},
});
}
}
Create users microservice
Build the first microservice for user management with Consul registration.
import { MicroService } from "./service-base.ts";
const service = new MicroService({
name: "users",
port: 8080,
version: "1.0.0",
});
// Mock user data
const users = new Map([
["1", { id: "1", name: "Alice Johnson", email: "alice@example.com" }],
["2", { id: "2", name: "Bob Smith", email: "bob@example.com" }],
["3", { id: "3", name: "Carol Wilson", email: "carol@example.com" }],
]);
// GET /api/users
service.addRoute("/api/users", async (req: Request) => {
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
return new Response(JSON.stringify(Array.from(users.values())), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
// GET /api/users/:id
service.addRoute("/api/users/", async (req: Request) => {
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
const url = new URL(req.url);
const id = url.pathname.split("/").pop();
if (!id || !users.has(id)) {
return new Response("User not found", { status: 404 });
}
return new Response(JSON.stringify(users.get(id)), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
if (import.meta.main) {
service.start();
}
Create orders microservice
Build the second microservice for order management with service discovery.
import { MicroService } from "./service-base.ts";
const service = new MicroService({
name: "orders",
port: 8081,
version: "1.0.0",
});
// Mock order data
const orders = new Map([
["1", { id: "1", userId: "1", items: ["laptop", "mouse"], total: 1299.99, status: "shipped" }],
["2", { id: "2", userId: "2", items: ["phone"], total: 899.99, status: "processing" }],
["3", { id: "3", userId: "1", items: ["keyboard"], total: 129.99, status: "delivered" }],
]);
// GET /api/orders
service.addRoute("/api/orders", async (req: Request) => {
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
const url = new URL(req.url);
const userId = url.searchParams.get("userId");
let result = Array.from(orders.values());
if (userId) {
result = result.filter(order => order.userId === userId);
}
return new Response(JSON.stringify(result), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
// GET /api/orders/:id
service.addRoute("/api/orders/", async (req: Request) => {
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
const url = new URL(req.url);
const id = url.pathname.split("/").pop();
if (!id || !orders.has(id)) {
return new Response("Order not found", { status: 404 });
}
return new Response(JSON.stringify(orders.get(id)), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
if (import.meta.main) {
service.start();
}
Create systemd services for Deno microservices
Setup systemd services to manage the Deno microservices lifecycle with automatic restarts.
[Unit]
Description=Deno Users Microservice
After=network.target consul.service
Requires=consul.service
[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/home/deno/microservices
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-env users-service.ts
Restart=always
RestartSec=5
Environment=SERVICE_ADDRESS=localhost
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
[Unit]
Description=Deno Orders Microservice
After=network.target consul.service
Requires=consul.service
[Service]
Type=simple
User=deno
Group=deno
WorkingDirectory=/home/deno/microservices
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-env orders-service.ts
Restart=always
RestartSec=5
Environment=SERVICE_ADDRESS=localhost
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Create dedicated user and setup permissions
Create a dedicated user for running Deno services securely with minimal permissions.
sudo useradd --system --home /home/deno --create-home --shell /bin/bash deno
sudo mkdir -p /home/deno/microservices
sudo cp ~/deno-microservices/* /home/deno/microservices/
sudo chown -R deno:deno /home/deno
sudo chmod 755 /home/deno/microservices
sudo chmod 644 /home/deno/microservices/*.ts
Start all services
Enable and start all the services in the correct order with dependency checking.
# Start Consul first
sudo systemctl status consul
Start HAProxy
sudo systemctl enable haproxy
sudo systemctl start haproxy
sudo systemctl status haproxy
Start Prometheus
sudo systemctl status prometheus
Start Deno microservices
sudo systemctl daemon-reload
sudo systemctl enable deno-users deno-orders
sudo systemctl start deno-users deno-orders
sudo systemctl status deno-users
sudo systemctl status deno-orders
Verify your setup
Test the complete microservices architecture with service discovery and load balancing.
# Check Consul UI and registered services
curl -s http://localhost:8500/v1/agent/services | jq
curl -s http://localhost:8500/v1/health/service/users | jq
curl -s http://localhost:8500/v1/health/service/orders | jq
Test direct service access
curl -s http://localhost:8080/health | jq
curl -s http://localhost:8081/health | jq
Test load balancer endpoints
curl -s http://localhost:80/api/users | jq
curl -s http://localhost:80/api/orders | jq
Check HAProxy stats
curl -s http://localhost:8404/
Verify Prometheus targets
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job | contains("consul"))'
Test service metrics
curl -s http://localhost:8080/metrics
curl -s http://localhost:8081/metrics
Verify service registration in Consul
consul catalog services
consul catalog nodes -service=users
consul catalog nodes -service=orders
Configure inter-service communication
Setup secure communication between microservices using service discovery.
import { MicroService } from "./service-base.ts";
const gateway = new MicroService({
name: "api-gateway",
port: 8000,
version: "1.0.0",
});
class ServiceDiscovery {
private consulUrl: string;
constructor(consulUrl = "http://localhost:8500") {
this.consulUrl = consulUrl;
}
async getServiceEndpoint(serviceName: string): Promise {
try {
const response = await fetch(${this.consulUrl}/v1/health/service/${serviceName}?passing=true);
const services = await response.json();
if (services.length === 0) {
return null;
}
// Simple round-robin selection
const service = services[Math.floor(Math.random() * services.length)];
return http://${service.Service.Address}:${service.Service.Port};
} catch (error) {
console.error(Failed to discover service ${serviceName}:, error);
return null;
}
}
async proxyRequest(serviceName: string, path: string, req: Request): Promise {
const endpoint = await this.getServiceEndpoint(serviceName);
if (!endpoint) {
return new Response(JSON.stringify({ error: Service ${serviceName} unavailable }), {
status: 503,
headers: { "Content-Type": "application/json" },
});
}
try {
const targetUrl = ${endpoint}${path};
const response = await fetch(targetUrl, {
method: req.method,
headers: req.headers,
body: req.body,
});
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
} catch (error) {
console.error(Error proxying to ${serviceName}:, error);
return new Response(JSON.stringify({ error: "Service communication failed" }), {
status: 502,
headers: { "Content-Type": "application/json" },
});
}
}
}
const discovery = new ServiceDiscovery();
// Route aggregation endpoint
gateway.addRoute("/api/users", async (req: Request) => {
return await discovery.proxyRequest("users", "/api/users", req);
});
gateway.addRoute("/api/orders", async (req: Request) => {
return await discovery.proxyRequest("orders", "/api/orders", req);
});
// Aggregated user profile endpoint
gateway.addRoute("/api/profile", async (req: Request) => {
const url = new URL(req.url);
const userId = url.searchParams.get("userId");
if (!userId) {
return new Response(JSON.stringify({ error: "userId parameter required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
try {
// Fetch user data
const userEndpoint = await discovery.getServiceEndpoint("users");
const userResponse = await fetch(${userEndpoint}/api/users/${userId});
const user = await userResponse.json();
// Fetch user orders
const ordersEndpoint = await discovery.getServiceEndpoint("orders");
const ordersResponse = await fetch(${ordersEndpoint}/api/orders?userId=${userId});
const orders = await ordersResponse.json();
const profile = {
user,
orders,
orderCount: orders.length,
totalSpent: orders.reduce((sum: number, order: any) => sum + order.total, 0),
};
return new Response(JSON.stringify(profile), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ error: "Failed to aggregate profile data" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
});
if (import.meta.main) {
gateway.start();
}
Setup monitoring and alerting
Configure Prometheus alerting rules for microservices health and performance monitoring.
sudo mkdir -p /etc/prometheus/rules
groups:
- name: microservices
rules:
- alert: ServiceDown
expr: up{job=~"consul-services"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Microservice {{ $labels.service }} is down"
description: "{{ $labels.service }} has been down for more than 1 minute"
- alert: HighErrorRate
expr: rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} for {{ $labels.service }}"
- alert: HighLatency
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "High latency on {{ $labels.service }}"
description: "95th percentile latency is {{ $value }}s for {{ $labels.service }}"
- alert: ConsulServiceUnhealthy
expr: consul_health_service_status{status!="passing"} == 1
for: 30s
labels:
severity: critical
annotations:
summary: "Consul health check failing for {{ $labels.service_name }}"
description: "Health check {{ $labels.check_name }} is failing for {{ $labels.service_name }}"
- alert: HAProxyBackendDown
expr: haproxy_server_status{state!="UP"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "HAProxy backend {{ $labels.server }} is down"
description: "Backend server {{ $labels.server }} in {{ $labels.proxy }} is not responding"
sudo systemctl restart prometheus
sudo systemctl status prometheus
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service registration fails | Consul agent not running or network issue | sudo systemctl status consul, check /var/log/consul.log |
| HAProxy shows backend as down | Health check URL returns non-200 status | Test curl http://localhost:8080/health, verify service is running |
| Deno service fails to start | Permission issues or missing dependencies | Check journalctl -u deno-users, verify file ownership |
| Prometheus can't scrape services | Service discovery not finding targets | Check Consul API: curl http://localhost:8500/v1/catalog/services |
| Load balancer returns 503 | No healthy backends available | Check HAProxy stats at http://localhost:8404 |
| Inter-service communication fails | Network policies or service not registered | Test direct service URLs, check Consul service catalog |
Next steps
- Configure OpenTelemetry custom metrics for application monitoring with Prometheus and Grafana
- Implement Kubernetes RBAC with service accounts and role-based access control
- Setup Deno WebSocket clustering for real-time applications
- Implement CI/CD pipeline for Deno microservices with automated testing
- Configure Consul ACL security and encryption for production deployments
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
CONSUL_VERSION="1.17.0"
BIND_IP="${1:-0.0.0.0}"
# Usage message
usage() {
echo "Usage: $0 [bind_ip]"
echo " bind_ip: IP address to bind services (default: 0.0.0.0)"
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function for rollback
cleanup() {
log_error "Installation failed. Cleaning up..."
systemctl stop consul 2>/dev/null || true
systemctl stop haproxy 2>/dev/null || true
systemctl disable consul 2>/dev/null || true
systemctl disable haproxy 2>/dev/null || true
rm -f /usr/local/bin/consul /usr/local/bin/deno
rm -rf /etc/consul.d /opt/consul
userdel consul 2>/dev/null || true
exit 1
}
trap cleanup ERR
# Check prerequisites
check_prerequisites() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
if ! command -v curl >/dev/null 2>&1; then
log_error "curl is required but not installed"
exit 1
fi
}
# Auto-detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
log_success "Detected distribution: $ID using $PKG_MGR"
}
# Validate arguments
if [[ $# -gt 1 ]]; then
usage
fi
check_prerequisites
detect_distro
log_info "[1/6] Updating system packages..."
eval $PKG_UPDATE
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL curl wget unzip software-properties-common
else
$PKG_INSTALL curl wget unzip
fi
log_success "System packages updated"
log_info "[2/6] Installing Deno runtime..."
DENO_INSTALL="/usr/local" curl -fsSL https://deno.land/install.sh | sh
mv ~/.deno/bin/deno /usr/local/bin/
chmod 755 /usr/local/bin/deno
if deno --version >/dev/null 2>&1; then
log_success "Deno installed successfully"
else
log_error "Deno installation failed"
exit 1
fi
log_info "[3/6] Installing Consul for service discovery..."
cd /tmp
wget -q https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip
unzip -q consul_${CONSUL_VERSION}_linux_amd64.zip
mv consul /usr/local/bin/
chmod 755 /usr/local/bin/consul
rm -f consul_${CONSUL_VERSION}_linux_amd64.zip
if consul version >/dev/null 2>&1; then
log_success "Consul installed successfully"
else
log_error "Consul installation failed"
exit 1
fi
log_info "[4/6] Configuring Consul server..."
mkdir -p /etc/consul.d /opt/consul
useradd --system --home /etc/consul.d --shell /bin/false consul 2>/dev/null || true
chown -R consul:consul /etc/consul.d /opt/consul
cat > /etc/consul.d/consul.hcl << EOF
datacenter = "dc1"
data_dir = "/opt/consul"
log_level = "INFO"
server = true
bootstrap_expect = 1
bind_addr = "${BIND_IP}"
client_addr = "${BIND_IP}"
ui_config {
enabled = true
}
connect {
enabled = true
}
ports {
grpc = 8502
}
acl = {
enabled = false
default_policy = "allow"
}
EOF
chmod 644 /etc/consul.d/consul.hcl
chown consul:consul /etc/consul.d/consul.hcl
cat > /etc/systemd/system/consul.service << EOF
[Unit]
Description=Consul
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/consul.d/consul.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable consul
systemctl start consul
log_success "Consul configured and started"
log_info "[5/6] Installing and configuring HAProxy..."
$PKG_INSTALL haproxy
# Backup original config
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.backup
cat > /etc/haproxy/haproxy.cfg << EOF
global
daemon
maxconn 4096
log stdout local0
stats socket /var/run/haproxy.sock mode 660 level admin
stats timeout 30s
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
option dontlognull
option redispatch
retries 3
resolvers consul
nameserver consul1 127.0.0.1:8600
accepted_payload_size 8192
hold valid 5s
frontend api_gateway
bind *:8080
# Route based on URL path
acl is_users_service path_beg /api/users
acl is_orders_service path_beg /api/orders
acl is_health path /health
use_backend users_service if is_users_service
use_backend orders_service if is_orders_service
use_backend health_check if is_health
default_backend api_default
backend users_service
balance roundrobin
option httpchk GET /health
http-check expect status 200
backend orders_service
balance roundrobin
option httpchk GET /health
http-check expect status 200
backend health_check
option httpchk GET /health
http-check expect status 200
backend api_default
option httpchk GET /health
http-check expect status 200
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
EOF
chmod 644 /etc/haproxy/haproxy.cfg
systemctl enable haproxy
systemctl start haproxy
log_success "HAProxy configured and started"
log_info "[6/6] Configuring firewall and final verification..."
if command -v ufw >/dev/null 2>&1; then
ufw allow 8080/tcp
ufw allow 8500/tcp
ufw allow 8404/tcp
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=8500/tcp
firewall-cmd --permanent --add-port=8404/tcp
firewall-cmd --reload
fi
# Verification checks
log_info "Performing verification checks..."
sleep 5
if systemctl is-active --quiet consul; then
log_success "✓ Consul service is running"
else
log_error "✗ Consul service failed to start"
fi
if systemctl is-active --quiet haproxy; then
log_success "✓ HAProxy service is running"
else
log_error "✗ HAProxy service failed to start"
fi
if curl -s http://localhost:8500/ui/ >/dev/null; then
log_success "✓ Consul UI accessible at http://localhost:8500"
else
log_warning "⚠ Consul UI may not be accessible"
fi
if curl -s http://localhost:8404/stats >/dev/null; then
log_success "✓ HAProxy stats accessible at http://localhost:8404/stats"
else
log_warning "⚠ HAProxy stats may not be accessible"
fi
log_success "Installation completed successfully!"
log_info "Services available:"
log_info " - Consul UI: http://localhost:8500"
log_info " - HAProxy Stats: http://localhost:8404/stats"
log_info " - Load Balancer: http://localhost:8080"
Review the script before running. Execute with: bash install.sh