[ci skip] Add coturn TURN/STUN server for WebRTC relay

- Deploy coturn on k8s with MetalLB shared IP (10.0.20.200)
- Normal pod networking (no hostNetwork), runs on any node
- 100 relay ports (49152-49252), port 3478 for STUN/TURN signaling
- Shared secret auth for time-limited TURN credentials
- For F1 streaming WebRTC NAT traversal
This commit is contained in:
Viktor Barzin 2026-02-21 18:08:01 +00:00
parent 5fe288a4e4
commit de1a43a3c7
3 changed files with 208 additions and 3 deletions

View file

@ -161,6 +161,7 @@ variable "gemini_api_key" { type = string }
variable "llama_api_key" { type = string }
variable "brave_api_key" { type = string }
variable "modal_api_key" { type = string }
variable "coturn_turn_secret" { type = string }
variable "k8s_users" {
type = map(any)
@ -703,12 +704,13 @@ module "kubernetes_cluster" {
affine_postgresql_password = var.affine_postgresql_password
health_postgresql_password = var.health_postgresql_password
health_secret_key = var.health_secret_key
openclaw_ssh_key = var.openclaw_ssh_key
openclaw_skill_secrets = var.openclaw_skill_secrets
openclaw_ssh_key = var.openclaw_ssh_key
openclaw_skill_secrets = var.openclaw_skill_secrets
gemini_api_key = var.gemini_api_key
llama_api_key = var.llama_api_key
brave_api_key = var.brave_api_key
modal_api_key = var.modal_api_key
coturn_turn_secret = var.coturn_turn_secret
k8s_users = var.k8s_users
ssh_private_key = var.ssh_private_key

View file

@ -0,0 +1,192 @@
variable "tls_secret_name" {}
variable "tier" { type = string }
variable "turn_secret" { type = string }
locals {
turn_realm = "viktorbarzin.me"
turn_port = 3478
# Small relay range 100 ports is plenty for a home lab (~50 concurrent streams)
min_port = 49152
max_port = 49252
}
resource "kubernetes_namespace" "coturn" {
metadata {
name = "coturn"
labels = {
tier = var.tier
}
}
}
module "tls_secret" {
source = "../setup_tls_secret"
namespace = kubernetes_namespace.coturn.metadata[0].name
tls_secret_name = var.tls_secret_name
}
resource "kubernetes_config_map" "coturn_config" {
metadata {
name = "coturn-config"
namespace = kubernetes_namespace.coturn.metadata[0].name
}
data = {
"turnserver.conf" = <<-EOF
# TURN server configuration
listening-port=${local.turn_port}
fingerprint
lt-cred-mech
use-auth-secret
static-auth-secret=${var.turn_secret}
realm=${local.turn_realm}
server-name=turn.${local.turn_realm}
# Network use 0.0.0.0, coturn auto-detects pod IP
listening-ip=0.0.0.0
# Media relay port range (narrow 100 ports)
min-port=${local.min_port}
max-port=${local.max_port}
# Logging
verbose
no-stdout-log
syslog
# Security
no-multicast-peers
no-cli
no-tlsv1
no-tlsv1_1
# Performance
total-quota=100
stale-nonce=600
max-bps=0
EOF
}
}
resource "kubernetes_deployment" "coturn" {
metadata {
name = "coturn"
namespace = kubernetes_namespace.coturn.metadata[0].name
labels = {
app = "coturn"
tier = var.tier
}
}
spec {
replicas = 1
strategy {
type = "RollingUpdate"
rolling_update {
max_unavailable = 0
max_surge = 1
}
}
selector {
match_labels = {
app = "coturn"
}
}
template {
metadata {
labels = {
app = "coturn"
}
}
spec {
container {
name = "coturn"
image = "coturn/coturn:latest"
args = ["-c", "/etc/turnserver/turnserver.conf"]
# STUN/TURN signaling port
port {
name = "turn-udp"
container_port = local.turn_port
protocol = "UDP"
}
port {
name = "turn-tcp"
container_port = local.turn_port
protocol = "TCP"
}
volume_mount {
name = "config"
mount_path = "/etc/turnserver"
read_only = true
}
resources {
requests = {
cpu = "100m"
memory = "128Mi"
}
limits = {
cpu = "1"
memory = "512Mi"
}
}
}
volume {
name = "config"
config_map {
name = kubernetes_config_map.coturn_config.metadata[0].name
}
}
}
}
}
}
# LoadBalancer service with MetalLB exposes STUN/TURN signaling + relay ports
resource "kubernetes_service" "coturn" {
metadata {
name = "coturn"
namespace = kubernetes_namespace.coturn.metadata[0].name
annotations = {
"metallb.universe.tf/loadBalancerIPs" = "10.0.20.200"
"metallb.universe.tf/allow-shared-ip" = "shared"
}
}
spec {
type = "LoadBalancer"
selector = {
app = "coturn"
}
# STUN/TURN signaling
port {
name = "turn-udp"
port = local.turn_port
target_port = local.turn_port
protocol = "UDP"
}
port {
name = "turn-tcp"
port = local.turn_port
target_port = local.turn_port
protocol = "TCP"
}
# Relay port range (49152-49252)
dynamic "port" {
for_each = range(local.min_port, local.max_port + 1)
content {
name = "relay-${port.value}"
port = port.value
target_port = port.value
protocol = "UDP"
}
}
}
}

View file

@ -132,6 +132,7 @@ variable "modal_api_key" { type = string }
variable "gemini_api_key" { type = string }
variable "llama_api_key" { type = string }
variable "brave_api_key" { type = string }
variable "coturn_turn_secret" { type = string }
variable "k8s_users" {
type = map(any)
@ -159,7 +160,7 @@ locals {
3 : ["reverse-proxy"], # Cluster admin services (k8s-dashboard chart repo still 404)
4 : [
"mailserver", "shadowsocks", "webhook_handler", "tuya-bridge", "dawarich", "owntracks", "nextcloud",
"calibre", "onlyoffice", "f1-stream", "rybbit", "isponsorblocktv", "actualbudget"
"calibre", "onlyoffice", "f1-stream", "rybbit", "isponsorblocktv", "actualbudget", "coturn"
], # Activel used services
# Optional services
5 : [
@ -256,6 +257,16 @@ module "f1-stream" {
depends_on = [null_resource.core_services]
}
module "coturn" {
source = "./coturn"
for_each = contains(local.active_modules, "coturn") ? { coturn = true } : {}
tls_secret_name = var.tls_secret_name
tier = local.tiers.edge
turn_secret = var.coturn_turn_secret
depends_on = [null_resource.core_services]
}
module "hackmd" {
source = "./hackmd"
for_each = contains(local.active_modules, "hackmd") ? { hackmd = true } : {}