Add Terraform configuration for OpenVidu deployment on GCP

google-cloud-platform
Piwccle 2025-08-27 17:38:50 +02:00
parent 8a236e1b67
commit 4b37bf4304
1 changed files with 467 additions and 0 deletions

View File

@ -0,0 +1,467 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.0"
}
}
}
provider "google" {
project = var.projectId
region = var.region
zone = var.zone
}
# Enable APIs the deployment needs
resource "google_project_service" "compute_api" { service = "compute.googleapis.com" }
resource "google_project_service" "secretmanager_api" { service = "secretmanager.googleapis.com" }
resource "google_project_service" "storage_api" { service = "storage.googleapis.com" }
resource "random_id" "bucket_suffix" { byte_length = 3 }
# GCS bucket (conditional)
resource "google_storage_bucket" "bucket" {
count = 1
name = local.isEmpty ? "openvidu-appdata" : var.bucketName
location = var.region
force_destroy = false
uniform_bucket_level_access = true
}
# Secret Manager secret that stores deployment info and seed secrets
resource "google_secret_manager_secret" "openvidu" {
secret_id = "openvidu-${var.region}-${var.stackName}"
replication {
auto {}
}
}
resource "google_secret_manager_secret_version" "openvidu_version" {
secret = google_secret_manager_secret.openvidu.id
secret_data = jsonencode({
domainName = "none",
LIVEKIT_turnDomainName = "none",
LETSENCRYPT_EMAIL = "none",
REDIS_PASSWORD = "none",
MONGO_ADMIN_USERNAME = "none",
MONGO_ADMIN_PASSWORD = "none",
MONGO_REPLICA_SET_KEY = "none",
MINIO_URL = "none",
MINIO_ACCESS_KEY = "none",
MINIO_SECRET_KEY = "none",
DASHBOARD_URL = "none",
DASHBOARD_ADMIN_USERNAME = "none",
DASHBOARD_ADMIN_PASSWORD = "none",
GRAFANA_URL = "none",
GRAFANA_ADMIN_USERNAME = "none",
GRAFANA_ADMIN_PASSWORD = "none",
LIVEKIT_API_KEY = "none",
LIVEKIT_API_SECRET = "none",
MEET_ADMIN_USER = "none",
MEET_ADMIN_SECRET = "none",
MEET_API_KEY = "none",
ENABLED_MODULES = "none"
})
}
# Service account for the instance
resource "google_service_account" "openvidu_sa" {
account_id = "openvidu-sa-${substr(var.stackName, 0, 12)}"
display_name = "OpenVidu instance service account"
}
# IAM bindings for the service account so the instance can access Secret Manager and GCS
resource "google_project_iam_member" "sa_secret_accessor" {
project = var.projectId
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.openvidu_sa.email}"
}
resource "google_project_iam_member" "sa_storage_object_admin" {
project = var.projectId
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.openvidu_sa.email}"
}
# Firewall (similar ports to your AWS SG)
resource "google_compute_firewall" "openvidu_fw" {
name = "openvidu-fw-${var.stackName}"
network = "default"
allow {
protocol = "tcp"
ports = ["22", "80", "443", "1935", "7881"]
}
allow {
protocol = "udp"
ports = ["443", "7885", "50000-60000"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["openvidu-server-${var.stackName}"]
}
# Static external IP (optional)
resource "google_compute_address" "openvidu_ip" {
count = var.publicStaticIp == "" ? 0 : 1
name = "openvidu-eip-${var.stackName}"
address = var.publicStaticIp
}
# Compute instance for OpenVidu
resource "google_compute_instance" "openvidu" {
name = "openvidu-${var.stackName}"
machine_type = var.instanceType
zone = var.zone
tags = ["openvidu-server-${var.stackName}"]
boot_disk {
initialize_params {
image = var.boot_image
size = 200
type = "pd-standard"
}
}
network_interface {
network = "default"
access_config {
nat_ip = length(google_compute_address.openvidu_ip) > 0 ? google_compute_address.openvidu_ip[0].address : null
}
}
metadata = {
# metadata values are accessible from the instance
secret_name = google_secret_manager_secret.openvidu.secret_id
region = var.region
stackName = var.stackName
certificateType = var.certificateType
domainName = var.domainName
letsEncryptEmail = var.letsEncryptEmail
ownPublicCertificate = var.ownPublicCertificate
ownPrivateCertificate = var.ownPrivateCertificate
additional_install_flags = var.additional_install_flags
turnDomainName = var.turnDomainName
turnOwnPublicCertificate = var.turnOwnPublicCertificate
turnOwnPrivateCertificate = var.turnOwnPrivateCertificate
s3_bucket_name = var.bucketName == "" ? google_storage_bucket.appdata[0].name : var.bucketName
}
service_account {
email = google_service_account.openvidu_sa.email
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
}
metadata_startup_script = <<EOF
#!/bin/bash -x
set -euo pipefail
# Metadata helper
METADATA_URL="http://metadata.google.internal/computeMetadata/v1"
get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; }
projevar.projectId=$(get_meta "project/project-id")
REGION=$(get_meta "instance/attributes/region")
stackName=$(get_meta "instance/attributes/stackName")
SECRET_NAME=$(get_meta "instance/attributes/secret_name")
CERT_TYPE=$(get_meta "instance/attributes/certificateType")
domainName=$(get_meta "instance/attributes/domainName")
LE_EMAIL=$(get_meta "instance/attributes/letsEncryptEmail")
ADDITIONAL_FLAGS=$(get_meta "instance/attributes/additional_install_flags")
turnDomainName=$(get_meta "instance/attributes/turnDomainName")
OWN_CERT_URL=$(get_meta "instance/attributes/ownPublicCertificate")
OWN_KEY_URL=$(get_meta "instance/attributes/ownPrivateCertificate")
S3_BUCKET_NAME=$(get_meta "instance/attributes/s3_bucket_name")
# Install deps
apt-get update
apt-get install -y curl unzip jq wget ca-certificates gnupg lsb-release openssl
# Install google-cloud-sdk (to read secrets)
if ! command -v gcloud >/dev/null 2>&1; then
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
apt-get update && apt-get install -y google-cloud-sdk
fi
# Install yq
YQ_VERSION=v4.44.5
wget https://github.com/mikefarah/yq/releases/download/$${YQ_VERSION}/yq_linux_amd64.tar.gz -O - | tar xz && mv yq_linux_amd64 /usr/bin/yq
# Fetch secret (the secret contains a JSON string as in Terraform)
SHARED_SECRET_JSON=$(gcloud secrets versions access latest --secret="$${SECRET_NAME}" --project="$${projevar.projectId}") || SHARED_SECRET_JSON='{}'
# Helper to update secret using gcloud (we will use it to save values)
save_secret() {
KEY=$1
VALUE=$2
# read current, update key, and write a new version
TMP=$(mktemp)
echo "$SHARED_SECRET_JSON" | jq ". + { \"$${KEY}\": \"$${VALUE}\" }" > "$TMP" || echo '{ }' > "$TMP"
gcloud secrets versions add "$${SECRET_NAME}" --data-file="$TMP" --project="$${projevar.projectId}" >/dev/null
SHARED_SECRET_JSON=$(cat "$TMP")
rm -f "$TMP"
}
# Generate randoms and save to secret when needed (similar to CFN store_secret.sh)
generate_and_save() {
KEY=$1
PREFIX=$${2:-}
LENGTH=$${3:-44}
RAND=$(openssl rand -base64 64 | tr -d '+/=\n' | cut -c -$${LENGTH})
VALUE="$${PREFIX}$${RAND}"
save_secret "$KEY" "$VALUE"
echo "$VALUE"
}
# Configure domain
if [[ -z "$domainName" || "$domainName" == "none" ]]; then
# Use external IP
EXTERNAL_IP=$(curl -s ifconfig.co || true)
DOMAIN="$EXTERNAL_IP"
else
DOMAIN="$domainName"
fi
save_secret domainName "$DOMAIN"
# Generate/store secrets used by OpenVidu
REDIS_PASSWORD=$(generate_and_save REDIS_PASSWORD)
MONGO_ADMIN_USERNAME=$(save_secret MONGO_ADMIN_USERNAME "mongoadmin")
MONGO_ADMIN_PASSWORD=$(generate_and_save MONGO_ADMIN_PASSWORD)
MONGO_REPLICA_SET_KEY=$(generate_and_save MONGO_REPLICA_SET_KEY)
MINIO_ACCESS_KEY=$(save_secret MINIO_ACCESS_KEY "minioadmin")
MINIO_SECRET_KEY=$(generate_and_save MINIO_SECRET_KEY)
DASHBOARD_ADMIN_USERNAME=$(save_secret DASHBOARD_ADMIN_USERNAME "dashboardadmin")
DASHBOARD_ADMIN_PASSWORD=$(generate_and_save DASHBOARD_ADMIN_PASSWORD)
GRAFANA_ADMIN_USERNAME=$(save_secret GRAFANA_ADMIN_USERNAME "grafanaadmin")
GRAFANA_ADMIN_PASSWORD=$(generate_and_save GRAFANA_ADMIN_PASSWORD)
MEET_ADMIN_USER=$(save_secret MEET_ADMIN_USER "meetadmin")
MEET_ADMIN_SECRET=$(generate_and_save MEET_ADMIN_SECRET)
MEET_API_KEY=$(generate_and_save MEET_API_KEY)
ENABLED_MODULES=$(save_secret ENABLED_MODULES "observability,openviduMeet")
LIVEKIT_API_KEY=$(generate_and_save LIVEKIT_API_KEY "API" 12)
LIVEKIT_API_SECRET=$(generate_and_save LIVEKIT_API_SECRET)
# Build install command and args
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/community/singlenode/main/install.sh)"
COMMON_ARGS=(
"--no-tty"
"--install"
"--environment=gcp"
"--deployment-type=single_node"
"--domain-name=$DOMAIN"
"--enabled-modules='$ENABLED_MODULES'"
"--redis-password=$REDIS_PASSWORD"
"--mongo-admin-user=$MONGO_ADMIN_USERNAME"
"--mongo-admin-password=$MONGO_ADMIN_PASSWORD"
"--mongo-replica-set-key=$MONGO_REPLICA_SET_KEY"
"--minio-access-key=$MINIO_ACCESS_KEY"
"--minio-secret-key=$MINIO_SECRET_KEY"
"--dashboard-admin-user=$DASHBOARD_ADMIN_USERNAME"
"--dashboard-admin-password=$DASHBOARD_ADMIN_PASSWORD"
"--grafana-admin-user=$GRAFANA_ADMIN_USERNAME"
"--grafana-admin-password=$GRAFANA_ADMIN_PASSWORD"
"--meet-admin-user=$MEET_ADMIN_USER"
"--meet-admin-password=$MEET_ADMIN_SECRET"
"--meet-api-key=$MEET_API_KEY"
"--livekit-api-key=$LIVEKIT_API_KEY"
"--livekit-api-secret=$LIVEKIT_API_SECRET"
)
# Include additional installer flags (trimmed)
if [[ -n "$ADDITIONAL_FLAGS" && "$ADDITIONAL_FLAGS" != "none" ]]; then
IFS=',' read -ra EXTRA_FLAGS <<< "$ADDITIONAL_FLAGS"
for extra_flag in "$${EXTRA_FLAGS[@]}"; do
extra_flag="$(echo -e "$extra_flag" | sed -e 's/^\s*//' -e 's/\s*$//')"
if [[ -n "$extra_flag" ]]; then
COMMON_ARGS+=("$extra_flag")
fi
done
fi
# TURN domain
if [[ -n "$turnDomainName" && "$turnDomainName" != "none" ]]; then
save_secret LIVEKIT_turnDomainName "$turnDomainName"
COMMON_ARGS+=("--turn-domain-name=$turnDomainName")
fi
# Certificate handling
if [[ "$CERT_TYPE" == "selfsigned" ]] ; then
CERT_ARGS=("--certificate-type=selfsigned")
elif [[ "$CERT_TYPE" == "letsencrypt" ]] ; then
save_secret LETSENCRYPT_EMAIL "$LE_EMAIL"
CERT_ARGS=("--certificate-type=letsencrypt" "--letsencrypt-email=$LE_EMAIL")
else
# owncert: download from provided URLs and convert to base64
mkdir -p /tmp/owncert
if [[ -n "$OWN_CERT_URL" && -n "$OWN_KEY_URL" ]]; then
wget -O /tmp/owncert/fullchain.pem "$OWN_CERT_URL"
wget -O /tmp/owncert/privkey.pem "$OWN_KEY_URL"
OWN_CERT_CRT=$(base64 -w 0 /tmp/owncert/fullchain.pem)
OWN_CERT_KEY=$(base64 -w 0 /tmp/owncert/privkey.pem)
CERT_ARGS=("--certificate-type=owncert" "--owncert-public-key=$OWN_CERT_CRT" "--owncert-private-key=$OWN_CERT_KEY")
else
echo "owncert selected but cert URLs not provided"
exit 1
fi
fi
# Final command
FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "$${COMMON_ARGS[@]}") $(printf "%s " "$${CERT_ARGS[@]}")"
# Execute installation
bash -c "$FINAL_COMMAND"
# Configure GCS bucket in OpenVidu config if needed
if [[ -n "$S3_BUCKET_NAME" && "$S3_BUCKET_NAME" != "none" ]]; then
# Wait for openvidu config dir
CONFIG_DIR="/opt/openvidu/config"
if [[ -f "$${CONFIG_DIR}/openvidu.env" ]]; then
sed -i "s|EXTERNAL_S3_BUCKET_APP_DATA=.*|EXTERNAL_S3_BUCKET_APP_DATA=$${S3_BUCKET_NAME}|" "$${CONFIG_DIR}/openvidu.env" || true
fi
fi
EOF
labels = {
stack = var.stackName
}
}
# ------------------------- outputs.tf -------------------------
output "openvidu_instance_name" {
value = google_compute_instance.openvidu.name
}
output "openvidu_public_ip" {
value = length(google_compute_address.openvidu_ip) > 0 ? google_compute_address.openvidu_ip[0].address : google_compute_instance.openvidu.network_interface[0].access_config[0].nat_ip
}
output "services_and_credentials_secret_id" {
value = google_secret_manager_secret.openvidu.secret_id
}
output "appdata_bucket" {
value = var.bucketName == "" ? google_storage_bucket.appdata[0].name : var.bucketName
}
# ------------------------- local values -------------------------
locals {
isEmpty = var.bucketName == ""
}
# ------------------------- variables -------------------------
# Variables used by the configuration
variable "projectId" {
description = "GCP project id"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west1"
}
variable "zone" {
description = "GCP zone"
type = string
default = "europe-west1-b"
}
variable "stackName" {
description = "Stack name for OpenVidu deployment"
type = string
}
variable "certificateType" {
description = "[selfsigned] Not recommended for production use. If you don't have a FQDN, (DomainName parameter) you can use this option to generate a self-signed certificate. [owncert] Valid for productions environments. If you have a FQDN, (DomainName parameter) and an Elastic IP, you can use this option to use your own certificate. [letsencrypt] Valid for production environments. If you have a FQDN, (DomainName parameter) and an Elastic IP, you can use this option to generate a Let's Encrypt certificate."
type = string
default = "selfsigned"
validation {
condition = contains(["selfsigned", "owncert", "letsencrypt"], var.certificateType)
error_message = "certificateType must be one of: selfsigned, owncert, letsencrypt"
}
}
variable "publicStaticIp" {
description = "Previously created Public IP address for the OpenVidu Deployment. Blank will generate a public IP"
type = string
default = ""
}
variable "domainName" {
description = "Optional domain name for the deployment"
type = string
default = ""
}
variable "ownPublicCertificate" {
description = "If owncert: URL to fullchain.pem"
type = string
default = ""
}
variable "ownPrivateCertificate" {
description = "If owncert: URL to privkey.pem"
type = string
default = ""
}
variable "letsEncryptEmail" {
description = "If letsencrypt: email for LE"
type = string
default = ""
}
variable "additional_install_flags" {
description = "Comma-separated additional flags passed to the OpenVidu installer"
type = string
default = ""
}
variable "turnDomainName" {
description = "Optional TURN server TLS domain"
type = string
default = ""
}
variable "turnOwnPublicCertificate" {
description = "Optional TURN public cert URL for owncert"
type = string
default = ""
}
variable "turnOwnPrivateCertificate" {
description = "Optional TURN private key URL for owncert"
type = string
default = ""
}
variable "instanceType" {
description = "GCE machine type"
type = string
default = "e2-standard-8"
}
variable "boot_image" {
description = "Boot image for the instance (family or specific image)"
type = string
default = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts"
}
variable "bucketName" {
description = "If empty, a GCS bucket will be created for app data and recordings"
type = string
default = ""
}