A complete, production-ready observability platform for monitoring DigitalOcean droplets with multi-tenant support. Built with OpenTelemetry, ClickHouse, Prometheus, and Grafana.
- Multi-Tenant Support: Each monitored droplet belongs to a separate tenant with isolated telemetry
- Complete Observability Stack:
- π Metrics: Prometheus for metrics storage and querying
- π Logs: ClickHouse columnar storage with full-text search
- π Traces: Distributed tracing with ClickHouse storage
- Persistent Storage: DigitalOcean volumes for data durability
- Production-Ready: VPC networking, firewall rules, and security best practices
- Auto-instrumentation: OpenTelemetry agents automatically collect host metrics
- Visualization: Pre-configured Grafana dashboards for all telemetry types
- Scalable: Designed for horizontal scaling
| Component | Size | Purpose | Volume |
|---|---|---|---|
| Monitored Droplets (Γ5) | 1GB, 1 vCPU | Applications being monitored | - |
| OTel Gateway | 2GB, 2 vCPU | Central telemetry routing | - |
| ClickHouse | 4GB, 2 vCPU | Traces & logs storage | 50GB |
| Prometheus | 2GB, 2 vCPU | Metrics storage | 30GB |
| Grafana | 2GB, 2 vCPU | Visualization & dashboards | - |
Estimated Cost: ~$100-120/month in Sydney region
- DigitalOcean Account with API access
- Terraform (>= 1.0) - Install Guide
- doctl (optional but recommended) - Install Guide
- SSH Key at
~/.ssh/id_rsa.pub(or specify custom path)
-
Clone/Navigate to the repository:
cd /Users/achandna/dev/hack-observability -
Set up DigitalOcean authentication:
# Option 1: Using doctl (recommended) doctl auth init # Option 2: Export token directly export DIGITALOCEAN_TOKEN="your_token_here"
-
Configure your deployment:
cd terraform cp terraform.tfvars.example terraform.tfvars # Edit terraform.tfvars with your preferences nano terraform.tfvars
-
Deploy the platform:
cd .. ./deploy.sh -
Access Grafana (after 5-10 minutes for cloud-init to complete):
# Get the Grafana URL from outputs cd terraform terraform output grafana_url
Edit terraform/terraform.tfvars to customize:
region = "syd1" # Sydney region
project_name = "observability" # Resource prefix
monitored_droplet_count = 5 # Number of monitored droplets
grafana_admin_password = "YourPassword123!" # Change this!
# Optional customizations
tenant_names = [
"acme-corp",
"widgets-inc",
"tech-startup",
"finance-co",
"retail-shop"
]
# Droplet sizes
clickhouse_size = "s-2vcpu-4gb"
prometheus_size = "s-2vcpu-2gb"
grafana_size = "s-2vcpu-2gb"
monitored_droplet_size = "s-1vcpu-1gb"
# Storage volumes
clickhouse_volume_size = 50 # GB
prometheus_volume_size = 30 # GBAfter deployment:
-
Grafana Dashboard:
http://<grafana-ip>:3000- Username:
admin - Password: (from your
terraform.tfvars)
- Username:
-
Prometheus:
http://<prometheus-ip>:9090- View metrics and targets
- Query using PromQL
-
ClickHouse:
<clickhouse-ip>:8123(HTTP) or:9000(Native)- Direct SQL queries for traces/logs
Grafana comes with several dashboards:
- Host Metrics - Multi-Tenant: CPU, memory, disk, network by tenant
- Traces Overview: Distributed tracing analysis
- Logs Overview: Log aggregation and search
ClickHouse Traces:
-- Get traces for a specific tenant
SELECT * FROM otel.otel_traces
WHERE TenantId = 'tenant-alpha'
AND Timestamp > now() - INTERVAL 1 HOUR
LIMIT 100;
-- Trace latency percentiles by service
SELECT
ServiceName,
quantile(0.5)(Duration) as p50,
quantile(0.95)(Duration) as p95,
quantile(0.99)(Duration) as p99
FROM otel.otel_traces
WHERE TenantId = 'tenant-alpha'
GROUP BY ServiceName;ClickHouse Logs:
-- Search logs by tenant
SELECT * FROM otel.otel_logs
WHERE TenantId = 'tenant-beta'
AND SeverityText = 'ERROR'
AND Timestamp > now() - INTERVAL 1 HOUR
ORDER BY Timestamp DESC;Prometheus Metrics:
# CPU usage by tenant
100 - (avg by (tenant, instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Memory usage by tenant
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
- β VPC with private networking for internal communication
- β Firewall rules restricting access
- β SSH key authentication
- β Grafana password protection
Before using in production, consider:
- Enable HTTPS: Add TLS certificates (Let's Encrypt)
- Restrict IPs: Limit Grafana/Prometheus access to your IPs
- Enable Authentication:
- ClickHouse: Add user authentication
- Prometheus: Enable basic auth
- Monitoring: Set up alerts for security events
- Backups: Schedule volume snapshots
- Secrets Management: Use DigitalOcean Secrets or Vault
- Update Strategy: Plan for security updates
# SSH to any droplet
ssh root@<droplet-ip>
# Watch cloud-init logs
tail -f /var/log/cloud-init-output.log
# Check service status
systemctl status otel-agent # On monitored droplets
systemctl status otel-collector # On gateway
systemctl status clickhouse-server # On ClickHouse
systemctl status prometheus # On Prometheus
systemctl status grafana-server # On GrafanaServices not starting:
# Check logs
journalctl -u otel-agent -f
journalctl -u clickhouse-server -fVolume not mounted:
# Check mounts
df -h
lsblk
# Remount if needed
mount /mnt/clickhouse_dataCan't connect to Grafana:
- Wait 10 minutes for cloud-init to complete
- Check firewall rules:
terraform state show digitalocean_firewall.grafana - Verify service:
systemctl status grafana-server
Add more monitored droplets:
# In terraform.tfvars
monitored_droplet_count = 10
# Add tenant names
tenant_names = ["tenant-1", "tenant-2", ..., "tenant-10"]Scale storage backend:
- Resize volumes:
doctl compute volume resize <volume-id> --size 100 - Add more ClickHouse nodes (requires manual ClickHouse cluster setup)
- Add Prometheus federation or remote storage (Thanos/Cortex)
Change droplet sizes in terraform.tfvars:
clickhouse_size = "s-4vcpu-8gb" # Upgrade to 8GB
prometheus_size = "s-4vcpu-8gb" # More RAM = longer retentionThen:
terraform applyTo destroy all resources:
./destroy.shWarning: This permanently deletes all data!
-
No High Availability: Single points of failure
- Mitigation: Add ClickHouse replication, Prometheus HA pairs
-
Limited Retention: 30-day TTL
- Mitigation: Increase TTL or add cold storage (S3/Spaces)
-
Basic Authentication: No SSO/SAML
- Mitigation: Configure Grafana OAuth with GitHub/Google
-
No Alerting: Alerts configured but not routed
- Mitigation: Add Alertmanager for Prometheus alerts
-
Manual Backups: No automated backup strategy
- Mitigation: Schedule volume snapshots via API
-
No Load Balancing: Single gateway could bottleneck
- Mitigation: Add multiple OTel gateways behind load balancer
- Add Tempo for native trace storage (alternative to ClickHouse)
- Implement Loki for log aggregation (alternative to ClickHouse)
- Add Alertmanager for alert routing
- Configure backup automation
- Add Kubernetes support for better orchestration
- Implement log sampling for cost optimization
- Add APM dashboards for application performance
- Create Terraform module for reusability
- Add CI/CD pipeline for infrastructure updates
-
Metrics:
Monitored Droplet β OTel Agent (hostmetrics) β OTel Gateway β Prometheus β Grafana -
Traces:
Application β OTel SDK β OTel Agent β OTel Gateway β ClickHouse β Grafana -
Logs:
Application β OTel SDK β OTel Agent β OTel Gateway β ClickHouse β Grafana
Tenant isolation is achieved through:
- Resource attributes:
tenant.idandservice.namespace - ClickHouse table partitioning by
TenantId - Prometheus labels:
tenant="tenant-name" - Grafana variable filtering by tenant
ClickHouse Tables:
- Partitioned by date for efficient TTL
- Compressed with ZSTD codec (~5-10x compression)
- Indexed on tenant, service, and trace IDs
- Materialized views for common queries
Prometheus:
- 30-day local retention
- Can enable remote write for long-term storage
- Supports federation for multi-cluster
Ideas for improvement:
- Add more pre-built dashboards
- Create alerting rule templates
- Add service discovery for dynamic droplets
- Implement automatic scaling policies
This project is provided as-is for educational and production use.
For issues:
- Check the troubleshooting section
- Review Terraform state:
terraform show - Check DigitalOcean console for resource status
- Review cloud-init logs on droplets
Ready to deploy?
./deploy.shπ Happy monitoring!