mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
465 lines
12 KiB
Terraform
465 lines
12 KiB
Terraform
# Hanzo Platform - Digital Ocean Infrastructure
|
|
# Terraform configuration for DO resources
|
|
#
|
|
# Architecture: Embedded sled storage + Lux consensus (no external databases)
|
|
#
|
|
# This creates:
|
|
# - VPC with private networking
|
|
# - Droplets for compute nodes
|
|
# - Load balancer
|
|
# - Firewall rules
|
|
# - DNS records
|
|
|
|
terraform {
|
|
required_version = ">= 1.5.0"
|
|
|
|
required_providers {
|
|
digitalocean = {
|
|
source = "digitalocean/digitalocean"
|
|
version = "~> 2.34"
|
|
}
|
|
}
|
|
|
|
# Remote state storage (optional - configure for your setup)
|
|
# backend "s3" {
|
|
# endpoint = "nyc3.digitaloceanspaces.com"
|
|
# bucket = "hanzo-terraform-state"
|
|
# key = "platform/terraform.tfstate"
|
|
# region = "us-east-1" # Required but ignored for DO Spaces
|
|
# skip_credentials_validation = true
|
|
# skip_metadata_api_check = true
|
|
# skip_region_validation = true
|
|
# }
|
|
}
|
|
|
|
provider "digitalocean" {
|
|
token = var.do_token
|
|
}
|
|
|
|
# =============================================================================
|
|
# Variables
|
|
# =============================================================================
|
|
|
|
variable "do_token" {
|
|
description = "Digital Ocean API token"
|
|
type = string
|
|
sensitive = true
|
|
}
|
|
|
|
variable "environment" {
|
|
description = "Environment name (production, staging, development)"
|
|
type = string
|
|
default = "production"
|
|
}
|
|
|
|
variable "region" {
|
|
description = "Digital Ocean region"
|
|
type = string
|
|
default = "nyc3"
|
|
}
|
|
|
|
variable "domain" {
|
|
description = "Primary domain for the platform"
|
|
type = string
|
|
default = "hanzo.network"
|
|
}
|
|
|
|
variable "ssh_keys" {
|
|
description = "List of SSH key fingerprints for droplet access"
|
|
type = list(string)
|
|
default = []
|
|
}
|
|
|
|
variable "node_count" {
|
|
description = "Number of compute node droplets"
|
|
type = number
|
|
default = 3
|
|
}
|
|
|
|
variable "node_size" {
|
|
description = "Droplet size for compute nodes"
|
|
type = string
|
|
default = "s-4vcpu-8gb" # 4 vCPU, 8 GB RAM
|
|
}
|
|
|
|
# =============================================================================
|
|
# Local Values
|
|
# =============================================================================
|
|
|
|
locals {
|
|
prefix = "hanzo-${var.environment}"
|
|
tags = ["hanzo", var.environment, "terraform-managed"]
|
|
|
|
common_labels = {
|
|
environment = var.environment
|
|
managed_by = "terraform"
|
|
project = "hanzo-platform"
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# VPC Configuration
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_vpc" "main" {
|
|
name = "${local.prefix}-vpc"
|
|
region = var.region
|
|
ip_range = "10.10.0.0/16"
|
|
description = "Hanzo Platform VPC for ${var.environment}"
|
|
}
|
|
|
|
# =============================================================================
|
|
# SSH Key Data Sources
|
|
# =============================================================================
|
|
|
|
data "digitalocean_ssh_keys" "main" {
|
|
filter {
|
|
key = "name"
|
|
values = var.ssh_keys
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Compute Node Droplets
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_droplet" "node" {
|
|
count = var.node_count
|
|
|
|
name = "${local.prefix}-node-${count.index + 1}"
|
|
region = var.region
|
|
size = var.node_size
|
|
image = "docker-20-04" # Docker on Ubuntu 20.04
|
|
vpc_uuid = digitalocean_vpc.main.id
|
|
|
|
ssh_keys = data.digitalocean_ssh_keys.main.ssh_keys[*].id
|
|
|
|
tags = concat(local.tags, ["node", "compute"])
|
|
|
|
user_data = templatefile("${path.module}/templates/node-cloud-init.yaml", {
|
|
node_index = count.index + 1
|
|
environment = var.environment
|
|
network_id = var.environment == "production" ? "43114" : "43113"
|
|
bootstrap_peers = ""
|
|
data_dir = "/app/data"
|
|
})
|
|
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
ignore_changes = [user_data]
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Load Balancer
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_loadbalancer" "main" {
|
|
name = "${local.prefix}-lb"
|
|
region = var.region
|
|
vpc_uuid = digitalocean_vpc.main.id
|
|
|
|
# HTTP -> HTTPS redirect
|
|
redirect_http_to_https = true
|
|
|
|
# Health checks
|
|
healthcheck {
|
|
port = 8080
|
|
protocol = "http"
|
|
path = "/health"
|
|
check_interval_seconds = 10
|
|
response_timeout_seconds = 5
|
|
unhealthy_threshold = 3
|
|
healthy_threshold = 2
|
|
}
|
|
|
|
# HTTP forwarding rule (redirects to HTTPS)
|
|
forwarding_rule {
|
|
entry_port = 80
|
|
entry_protocol = "http"
|
|
|
|
target_port = 8080
|
|
target_protocol = "http"
|
|
}
|
|
|
|
# HTTPS forwarding rule
|
|
forwarding_rule {
|
|
entry_port = 443
|
|
entry_protocol = "https"
|
|
|
|
target_port = 8080
|
|
target_protocol = "http"
|
|
|
|
certificate_name = digitalocean_certificate.main.name
|
|
}
|
|
|
|
# gRPC forwarding (port 9090)
|
|
forwarding_rule {
|
|
entry_port = 9090
|
|
entry_protocol = "https"
|
|
|
|
target_port = 9090
|
|
target_protocol = "http"
|
|
|
|
certificate_name = digitalocean_certificate.main.name
|
|
}
|
|
|
|
droplet_ids = digitalocean_droplet.node[*].id
|
|
|
|
sticky_sessions {
|
|
type = "cookies"
|
|
cookie_name = "hanzo_session"
|
|
cookie_ttl_seconds = 300
|
|
}
|
|
|
|
# Enable PROXY protocol for client IP preservation
|
|
enable_proxy_protocol = true
|
|
|
|
depends_on = [digitalocean_certificate.main]
|
|
}
|
|
|
|
# =============================================================================
|
|
# SSL Certificate
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_certificate" "main" {
|
|
name = "${local.prefix}-cert"
|
|
type = "lets_encrypt"
|
|
domains = [
|
|
var.domain,
|
|
"*.${var.domain}",
|
|
"api.${var.domain}",
|
|
"console.${var.domain}",
|
|
"node.${var.domain}",
|
|
]
|
|
|
|
lifecycle {
|
|
create_before_destroy = true
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Firewall Rules
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_firewall" "nodes" {
|
|
name = "${local.prefix}-node-firewall"
|
|
|
|
droplet_ids = digitalocean_droplet.node[*].id
|
|
|
|
# Inbound rules
|
|
inbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "22"
|
|
source_addresses = ["0.0.0.0/0", "::/0"] # SSH - restrict in production
|
|
}
|
|
|
|
inbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "8080"
|
|
source_load_balancer_uids = [digitalocean_loadbalancer.main.id]
|
|
}
|
|
|
|
inbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "9090"
|
|
source_load_balancer_uids = [digitalocean_loadbalancer.main.id]
|
|
}
|
|
|
|
# P2P networking from anywhere
|
|
inbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "9000"
|
|
source_addresses = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
inbound_rule {
|
|
protocol = "udp"
|
|
port_range = "9000"
|
|
source_addresses = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
# VPC internal traffic
|
|
inbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "all"
|
|
source_addresses = [digitalocean_vpc.main.ip_range]
|
|
}
|
|
|
|
inbound_rule {
|
|
protocol = "udp"
|
|
port_range = "all"
|
|
source_addresses = [digitalocean_vpc.main.ip_range]
|
|
}
|
|
|
|
inbound_rule {
|
|
protocol = "icmp"
|
|
source_addresses = [digitalocean_vpc.main.ip_range]
|
|
}
|
|
|
|
# Outbound rules - allow all
|
|
outbound_rule {
|
|
protocol = "tcp"
|
|
port_range = "all"
|
|
destination_addresses = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
outbound_rule {
|
|
protocol = "udp"
|
|
port_range = "all"
|
|
destination_addresses = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
outbound_rule {
|
|
protocol = "icmp"
|
|
destination_addresses = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# DNS Records
|
|
# =============================================================================
|
|
|
|
data "digitalocean_domain" "main" {
|
|
name = var.domain
|
|
}
|
|
|
|
resource "digitalocean_record" "api" {
|
|
domain = data.digitalocean_domain.main.id
|
|
type = "A"
|
|
name = "api"
|
|
value = digitalocean_loadbalancer.main.ip
|
|
ttl = 300
|
|
}
|
|
|
|
resource "digitalocean_record" "console" {
|
|
domain = data.digitalocean_domain.main.id
|
|
type = "A"
|
|
name = "console"
|
|
value = digitalocean_loadbalancer.main.ip
|
|
ttl = 300
|
|
}
|
|
|
|
resource "digitalocean_record" "node" {
|
|
domain = data.digitalocean_domain.main.id
|
|
type = "A"
|
|
name = "node"
|
|
value = digitalocean_loadbalancer.main.ip
|
|
ttl = 300
|
|
}
|
|
|
|
# Individual node records for P2P
|
|
resource "digitalocean_record" "node_direct" {
|
|
count = var.node_count
|
|
|
|
domain = data.digitalocean_domain.main.id
|
|
type = "A"
|
|
name = "node${count.index + 1}"
|
|
value = digitalocean_droplet.node[count.index].ipv4_address
|
|
ttl = 300
|
|
}
|
|
|
|
# =============================================================================
|
|
# Project Organization
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_project" "main" {
|
|
name = "Hanzo Platform - ${title(var.environment)}"
|
|
description = "Hanzo AI compute platform infrastructure"
|
|
purpose = "Web Application"
|
|
environment = var.environment == "production" ? "Production" : "Development"
|
|
|
|
resources = concat(
|
|
[digitalocean_loadbalancer.main.urn],
|
|
[for d in digitalocean_droplet.node : d.urn],
|
|
)
|
|
}
|
|
|
|
# =============================================================================
|
|
# Monitoring and Alerts
|
|
# =============================================================================
|
|
|
|
resource "digitalocean_monitor_alert" "cpu_alert" {
|
|
alerts {
|
|
email = ["[email protected]"]
|
|
}
|
|
|
|
window = "5m"
|
|
type = "v1/insights/droplet/cpu"
|
|
compare = "GreaterThan"
|
|
value = 80
|
|
enabled = true
|
|
description = "CPU utilization above 80% for 5 minutes"
|
|
|
|
entities = digitalocean_droplet.node[*].id
|
|
}
|
|
|
|
resource "digitalocean_monitor_alert" "memory_alert" {
|
|
alerts {
|
|
email = ["[email protected]"]
|
|
}
|
|
|
|
window = "5m"
|
|
type = "v1/insights/droplet/memory_utilization_percent"
|
|
compare = "GreaterThan"
|
|
value = 85
|
|
enabled = true
|
|
description = "Memory utilization above 85% for 5 minutes"
|
|
|
|
entities = digitalocean_droplet.node[*].id
|
|
}
|
|
|
|
resource "digitalocean_monitor_alert" "disk_alert" {
|
|
alerts {
|
|
email = ["[email protected]"]
|
|
}
|
|
|
|
window = "5m"
|
|
type = "v1/insights/droplet/disk_utilization_percent"
|
|
compare = "GreaterThan"
|
|
value = 85
|
|
enabled = true
|
|
description = "Disk utilization above 85% for 5 minutes"
|
|
|
|
entities = digitalocean_droplet.node[*].id
|
|
}
|
|
|
|
# =============================================================================
|
|
# Outputs
|
|
# =============================================================================
|
|
|
|
output "vpc_id" {
|
|
description = "VPC ID"
|
|
value = digitalocean_vpc.main.id
|
|
}
|
|
|
|
output "vpc_ip_range" {
|
|
description = "VPC IP range"
|
|
value = digitalocean_vpc.main.ip_range
|
|
}
|
|
|
|
output "load_balancer_ip" {
|
|
description = "Load balancer IP address"
|
|
value = digitalocean_loadbalancer.main.ip
|
|
}
|
|
|
|
output "node_ips" {
|
|
description = "Compute node IP addresses"
|
|
value = digitalocean_droplet.node[*].ipv4_address
|
|
}
|
|
|
|
output "node_private_ips" {
|
|
description = "Compute node private IP addresses"
|
|
value = digitalocean_droplet.node[*].ipv4_address_private
|
|
}
|
|
|
|
output "api_url" {
|
|
description = "API URL"
|
|
value = "https://api.${var.domain}"
|
|
}
|
|
|
|
output "console_url" {
|
|
description = "Console URL"
|
|
value = "https://console.${var.domain}"
|
|
}
|