openvidu/openvidu-deployment/pro/ha/aws/cf-openvidu-ha.yaml

3451 lines
127 KiB
YAML
Raw Normal View History

2025-04-22 11:46:24 +02:00
AWSTemplateFormatVersion: 2010-09-09
Description: OpenVidu Pro - High Availability
Parameters:
DomainName:
Type: String
Description: Domain name for the OpenVidu High Availability cluster
AllowedPattern: ^$|^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
ConstraintDescription: The domain name does not have a valid domain name format
OpenViduCertificateARN:
Description: 'Amazon certificate arn resource to load into the LoadBalancer'
Type: String
AllowedPattern: '.+'
ConstraintDescription: The Load Balancer domain name must be defined
OpenViduLicense:
Description: "Visit https://openvidu.io/account"
Type: String
AllowedPattern: ^(?!\s*$).+$
NoEcho: true
ConstraintDescription: OpenVidu Pro License is mandatory
RTCEngine:
Description: "RTCEngine media engine to use"
Type: String
AllowedValues:
- pion
- mediasoup
Default: pion
MasterNodeInstanceType:
Description: "Specifies the EC2 instance type for your OpenVidu Master Node"
Type: String
Default: c6a.xlarge
AllowedValues:
- t3.nano
- t3.micro
- t3.small
2025-04-22 11:46:24 +02:00
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
- t3a.nano
- t3a.micro
- t3a.small
- t3a.medium
- t3a.large
- t3a.xlarge
- t3a.2xlarge
- t4g.nano
- t4g.micro
- t4g.small
- t4g.medium
- t4g.large
- t4g.xlarge
- t4g.2xlarge
2025-04-22 11:46:24 +02:00
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
- c5.metal
- c5a.large
- c5a.xlarge
- c5a.2xlarge
- c5a.4xlarge
- c5a.8xlarge
- c5a.12xlarge
- c5a.16xlarge
- c5a.24xlarge
- c5ad.large
- c5ad.xlarge
- c5ad.2xlarge
- c5ad.4xlarge
- c5ad.8xlarge
- c5ad.12xlarge
- c5ad.16xlarge
- c5ad.24xlarge
- c5d.large
- c5d.xlarge
- c5d.2xlarge
- c5d.4xlarge
- c5d.9xlarge
- c5d.12xlarge
- c5d.18xlarge
- c5d.24xlarge
- c5d.metal
- c5n.large
- c5n.xlarge
- c5n.2xlarge
- c5n.4xlarge
- c5n.9xlarge
- c5n.18xlarge
- c5n.metal
2025-04-22 11:46:24 +02:00
- c6a.large
- c6a.xlarge
- c6a.2xlarge
- c6a.4xlarge
- c6a.8xlarge
- c6a.12xlarge
- c6a.16xlarge
- c6a.24xlarge
- c6a.32xlarge
- c6a.48xlarge
- c6a.metal
- c6g.medium
- c6g.large
- c6g.xlarge
- c6g.2xlarge
- c6g.4xlarge
- c6g.8xlarge
- c6g.12xlarge
- c6g.16xlarge
- c6g.metal
- c6gd.medium
- c6gd.large
- c6gd.xlarge
- c6gd.2xlarge
- c6gd.4xlarge
- c6gd.8xlarge
- c6gd.12xlarge
- c6gd.16xlarge
- c6gd.metal
- c6gn.medium
- c6gn.large
- c6gn.xlarge
- c6gn.2xlarge
- c6gn.4xlarge
- c6gn.8xlarge
- c6gn.12xlarge
- c6gn.16xlarge
2025-04-22 11:46:24 +02:00
- c6i.large
- c6i.xlarge
- c6i.2xlarge
- c6i.4xlarge
- c6i.8xlarge
- c6i.12xlarge
- c6i.16xlarge
- c6i.24xlarge
- c6i.32xlarge
- c6i.metal
- c6id.large
- c6id.xlarge
- c6id.2xlarge
- c6id.4xlarge
- c6id.8xlarge
- c6id.12xlarge
- c6id.16xlarge
- c6id.24xlarge
- c6id.32xlarge
- c6id.metal
- c6in.large
- c6in.xlarge
- c6in.2xlarge
- c6in.4xlarge
- c6in.8xlarge
- c6in.12xlarge
- c6in.16xlarge
- c6in.24xlarge
- c6in.32xlarge
- c6in.metal
2025-04-22 11:46:24 +02:00
- c7a.medium
- c7a.large
- c7a.xlarge
- c7a.2xlarge
- c7a.4xlarge
- c7a.8xlarge
- c7a.12xlarge
- c7a.16xlarge
- c7a.24xlarge
- c7a.32xlarge
- c7a.48xlarge
- c7a.metal-48xl
- c7g.medium
- c7g.large
- c7g.xlarge
- c7g.2xlarge
- c7g.4xlarge
- c7g.8xlarge
- c7g.12xlarge
- c7g.16xlarge
- c7g.metal
- c7gd.medium
- c7gd.large
- c7gd.xlarge
- c7gd.2xlarge
- c7gd.4xlarge
- c7gd.8xlarge
- c7gd.12xlarge
- c7gd.16xlarge
- c7gd.metal
- c7gn.medium
- c7gn.large
- c7gn.xlarge
- c7gn.2xlarge
- c7gn.4xlarge
- c7gn.8xlarge
- c7gn.12xlarge
- c7gn.16xlarge
- c7gn.metal
- c7i-flex.large
- c7i-flex.xlarge
- c7i-flex.2xlarge
- c7i-flex.4xlarge
- c7i-flex.8xlarge
- c7i-flex.12xlarge
- c7i-flex.16xlarge
2025-04-22 11:46:24 +02:00
- c7i.large
- c7i.xlarge
- c7i.2xlarge
- c7i.4xlarge
- c7i.8xlarge
- c7i.12xlarge
- c7i.16xlarge
- c7i.24xlarge
- c7i.48xlarge
- c7i.metal-24xl
- c7i.metal-48xl
- c8g.medium
- c8g.large
- c8g.xlarge
- c8g.2xlarge
- c8g.4xlarge
- c8g.8xlarge
- c8g.12xlarge
- c8g.16xlarge
- c8g.24xlarge
- c8g.48xlarge
- c8g.metal-24xl
- c8g.metal-48xl
- c8gd.medium
- c8gd.large
- c8gd.xlarge
- c8gd.2xlarge
- c8gd.4xlarge
- c8gd.8xlarge
- c8gd.12xlarge
- c8gd.16xlarge
- c8gd.24xlarge
- c8gd.48xlarge
- c8gd.metal-24xl
- c8gd.metal-48xl
- g4dn.xlarge
- g4dn.2xlarge
- g4dn.4xlarge
- g4dn.8xlarge
- g4dn.12xlarge
- g4dn.16xlarge
- g4dn.metal
- g5.xlarge
- g5.2xlarge
- g5.4xlarge
- g5.8xlarge
- g5.12xlarge
- g5.16xlarge
- g5.24xlarge
- g5.48xlarge
- g5g.xlarge
- g5g.2xlarge
- g5g.4xlarge
- g5g.8xlarge
- g5g.16xlarge
- g5g.metal
- g6.xlarge
- g6.2xlarge
- g6.4xlarge
- g6.8xlarge
- g6.12xlarge
- g6.16xlarge
- g6.24xlarge
- g6.48xlarge
- g6e.xlarge
- g6e.2xlarge
- g6e.4xlarge
- g6e.8xlarge
- g6e.12xlarge
- g6e.16xlarge
- g6e.24xlarge
- g6e.48xlarge
- g6f.large
- g6f.xlarge
- g6f.2xlarge
- g6f.4xlarge
- g7e.2xlarge
- g7e.4xlarge
- g7e.8xlarge
- g7e.12xlarge
- g7e.24xlarge
- g7e.48xlarge
- gr6.4xlarge
- gr6.8xlarge
- gr6f.4xlarge
- m6a.large
- m6a.xlarge
- m6a.2xlarge
- m6a.4xlarge
- m6a.8xlarge
- m6a.12xlarge
- m6a.16xlarge
- m6a.24xlarge
- m6a.32xlarge
- m6a.48xlarge
- m6a.metal
- m6g.medium
- m6g.large
- m6g.xlarge
- m6g.2xlarge
- m6g.4xlarge
- m6g.8xlarge
- m6g.12xlarge
- m6g.16xlarge
- m6g.metal
- m6gd.medium
- m6gd.large
- m6gd.xlarge
- m6gd.2xlarge
- m6gd.4xlarge
- m6gd.8xlarge
- m6gd.12xlarge
- m6gd.16xlarge
- m6gd.metal
- m6i.large
- m6i.xlarge
- m6i.2xlarge
- m6i.4xlarge
- m6i.8xlarge
- m6i.12xlarge
- m6i.16xlarge
- m6i.24xlarge
- m6i.32xlarge
- m6i.metal
- m6id.large
- m6id.xlarge
- m6id.2xlarge
- m6id.4xlarge
- m6id.8xlarge
- m6id.12xlarge
- m6id.16xlarge
- m6id.24xlarge
- m6id.32xlarge
- m6id.metal
- m6idn.large
- m6idn.xlarge
- m6idn.2xlarge
- m6idn.4xlarge
- m6idn.8xlarge
- m6idn.12xlarge
- m6idn.16xlarge
- m6idn.24xlarge
- m6idn.32xlarge
- m6idn.metal
2025-04-22 11:46:24 +02:00
- m6in.large
- m6in.xlarge
- m6in.2xlarge
- m6in.4xlarge
- m6in.8xlarge
- m6in.12xlarge
- m6in.16xlarge
- m6in.24xlarge
- m6in.32xlarge
- m6in.metal
- m7a.medium
- m7a.large
- m7a.xlarge
- m7a.2xlarge
- m7a.4xlarge
- m7a.8xlarge
- m7a.12xlarge
- m7a.16xlarge
- m7a.24xlarge
- m7a.32xlarge
- m7a.48xlarge
- m7a.metal-48xl
- m7g.medium
- m7g.large
- m7g.xlarge
- m7g.2xlarge
- m7g.4xlarge
- m7g.8xlarge
- m7g.12xlarge
- m7g.16xlarge
- m7g.metal
- m7gd.medium
- m7gd.large
- m7gd.xlarge
- m7gd.2xlarge
- m7gd.4xlarge
- m7gd.8xlarge
- m7gd.12xlarge
- m7gd.16xlarge
- m7gd.metal
- m7i-flex.large
- m7i-flex.xlarge
- m7i-flex.2xlarge
- m7i-flex.4xlarge
- m7i-flex.8xlarge
- m7i-flex.12xlarge
- m7i-flex.16xlarge
- m7i.large
- m7i.xlarge
- m7i.2xlarge
- m7i.4xlarge
- m7i.8xlarge
- m7i.12xlarge
- m7i.16xlarge
- m7i.24xlarge
- m7i.48xlarge
- m7i.metal-24xl
- m7i.metal-48xl
- m8g.medium
- m8g.large
- m8g.xlarge
- m8g.2xlarge
- m8g.4xlarge
- m8g.8xlarge
- m8g.12xlarge
- m8g.16xlarge
- m8g.24xlarge
- m8g.48xlarge
- m8g.metal-24xl
- m8g.metal-48xl
- m8gd.medium
- m8gd.large
- m8gd.xlarge
- m8gd.2xlarge
- m8gd.4xlarge
- m8gd.8xlarge
- m8gd.12xlarge
- m8gd.16xlarge
- m8gd.24xlarge
- m8gd.48xlarge
- m8gd.metal-24xl
- m8gd.metal-48xl
- p4d.24xlarge
- p4de.24xlarge
- p5.4xlarge
- p5.48xlarge
- p5e.48xlarge
- p5en.48xlarge
- p6-b200.48xlarge
- p6-b300.48xlarge
- p6e-gb200.36xlarge
- r6g.medium
- r6g.large
- r6g.xlarge
- r6g.2xlarge
- r6g.4xlarge
- r6g.8xlarge
- r6g.12xlarge
- r6g.16xlarge
- r6g.metal
- r6gd.medium
- r6gd.large
- r6gd.xlarge
- r6gd.2xlarge
- r6gd.4xlarge
- r6gd.8xlarge
- r6gd.12xlarge
- r6gd.16xlarge
- r6gd.metal
- r7g.medium
- r7g.large
- r7g.xlarge
- r7g.2xlarge
- r7g.4xlarge
- r7g.8xlarge
- r7g.12xlarge
- r7g.16xlarge
- r7g.metal
- r7gd.medium
- r7gd.large
- r7gd.xlarge
- r7gd.2xlarge
- r7gd.4xlarge
- r7gd.8xlarge
- r7gd.12xlarge
- r7gd.16xlarge
- r7gd.metal
- r8g.medium
- r8g.large
- r8g.xlarge
- r8g.2xlarge
- r8g.4xlarge
- r8g.8xlarge
- r8g.12xlarge
- r8g.16xlarge
- r8g.24xlarge
- r8g.48xlarge
- r8g.metal-24xl
- r8g.metal-48xl
2025-04-22 11:46:24 +02:00
ConstraintDescription: "Must be a valid EC2 instance type"
MediaNodeInstanceType:
Description: "Specifies the EC2 instance type for your OpenVidu Media Nodes"
Type: String
Default: c6a.xlarge
AllowedValues:
- t3.nano
- t3.micro
- t3.small
2025-04-22 11:46:24 +02:00
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
- t3a.nano
- t3a.micro
- t3a.small
- t3a.medium
- t3a.large
- t3a.xlarge
- t3a.2xlarge
- t4g.nano
- t4g.micro
- t4g.small
- t4g.medium
- t4g.large
- t4g.xlarge
- t4g.2xlarge
2025-04-22 11:46:24 +02:00
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
- c5.metal
- c5a.large
- c5a.xlarge
- c5a.2xlarge
- c5a.4xlarge
- c5a.8xlarge
- c5a.12xlarge
- c5a.16xlarge
- c5a.24xlarge
- c5ad.large
- c5ad.xlarge
- c5ad.2xlarge
- c5ad.4xlarge
- c5ad.8xlarge
- c5ad.12xlarge
- c5ad.16xlarge
- c5ad.24xlarge
- c5d.large
- c5d.xlarge
- c5d.2xlarge
- c5d.4xlarge
- c5d.9xlarge
- c5d.12xlarge
- c5d.18xlarge
- c5d.24xlarge
- c5d.metal
- c5n.large
- c5n.xlarge
- c5n.2xlarge
- c5n.4xlarge
- c5n.9xlarge
- c5n.18xlarge
- c5n.metal
2025-04-22 11:46:24 +02:00
- c6a.large
- c6a.xlarge
- c6a.2xlarge
- c6a.4xlarge
- c6a.8xlarge
- c6a.12xlarge
- c6a.16xlarge
- c6a.24xlarge
- c6a.32xlarge
- c6a.48xlarge
- c6a.metal
- c6g.medium
- c6g.large
- c6g.xlarge
- c6g.2xlarge
- c6g.4xlarge
- c6g.8xlarge
- c6g.12xlarge
- c6g.16xlarge
- c6g.metal
- c6gd.medium
- c6gd.large
- c6gd.xlarge
- c6gd.2xlarge
- c6gd.4xlarge
- c6gd.8xlarge
- c6gd.12xlarge
- c6gd.16xlarge
- c6gd.metal
- c6gn.medium
- c6gn.large
- c6gn.xlarge
- c6gn.2xlarge
- c6gn.4xlarge
- c6gn.8xlarge
- c6gn.12xlarge
- c6gn.16xlarge
2025-04-22 11:46:24 +02:00
- c6i.large
- c6i.xlarge
- c6i.2xlarge
- c6i.4xlarge
- c6i.8xlarge
- c6i.12xlarge
- c6i.16xlarge
- c6i.24xlarge
- c6i.32xlarge
- c6i.metal
- c6id.large
- c6id.xlarge
- c6id.2xlarge
- c6id.4xlarge
- c6id.8xlarge
- c6id.12xlarge
- c6id.16xlarge
- c6id.24xlarge
- c6id.32xlarge
- c6id.metal
- c6in.large
- c6in.xlarge
- c6in.2xlarge
- c6in.4xlarge
- c6in.8xlarge
- c6in.12xlarge
- c6in.16xlarge
- c6in.24xlarge
- c6in.32xlarge
- c6in.metal
2025-04-22 11:46:24 +02:00
- c7a.medium
- c7a.large
- c7a.xlarge
- c7a.2xlarge
- c7a.4xlarge
- c7a.8xlarge
- c7a.12xlarge
- c7a.16xlarge
- c7a.24xlarge
- c7a.32xlarge
- c7a.48xlarge
- c7a.metal-48xl
- c7g.medium
- c7g.large
- c7g.xlarge
- c7g.2xlarge
- c7g.4xlarge
- c7g.8xlarge
- c7g.12xlarge
- c7g.16xlarge
- c7g.metal
- c7gd.medium
- c7gd.large
- c7gd.xlarge
- c7gd.2xlarge
- c7gd.4xlarge
- c7gd.8xlarge
- c7gd.12xlarge
- c7gd.16xlarge
- c7gd.metal
- c7gn.medium
- c7gn.large
- c7gn.xlarge
- c7gn.2xlarge
- c7gn.4xlarge
- c7gn.8xlarge
- c7gn.12xlarge
- c7gn.16xlarge
- c7gn.metal
- c7i-flex.large
- c7i-flex.xlarge
- c7i-flex.2xlarge
- c7i-flex.4xlarge
- c7i-flex.8xlarge
- c7i-flex.12xlarge
- c7i-flex.16xlarge
2025-04-22 11:46:24 +02:00
- c7i.large
- c7i.xlarge
- c7i.2xlarge
- c7i.4xlarge
- c7i.8xlarge
- c7i.12xlarge
- c7i.16xlarge
- c7i.24xlarge
- c7i.48xlarge
- c7i.metal-24xl
- c7i.metal-48xl
- c8g.medium
- c8g.large
- c8g.xlarge
- c8g.2xlarge
- c8g.4xlarge
- c8g.8xlarge
- c8g.12xlarge
- c8g.16xlarge
- c8g.24xlarge
- c8g.48xlarge
- c8g.metal-24xl
- c8g.metal-48xl
- c8gd.medium
- c8gd.large
- c8gd.xlarge
- c8gd.2xlarge
- c8gd.4xlarge
- c8gd.8xlarge
- c8gd.12xlarge
- c8gd.16xlarge
- c8gd.24xlarge
- c8gd.48xlarge
- c8gd.metal-24xl
- c8gd.metal-48xl
- g4dn.xlarge
- g4dn.2xlarge
- g4dn.4xlarge
- g4dn.8xlarge
- g4dn.12xlarge
- g4dn.16xlarge
- g4dn.metal
- g5.xlarge
- g5.2xlarge
- g5.4xlarge
- g5.8xlarge
- g5.12xlarge
- g5.16xlarge
- g5.24xlarge
- g5.48xlarge
- g5g.xlarge
- g5g.2xlarge
- g5g.4xlarge
- g5g.8xlarge
- g5g.16xlarge
- g5g.metal
- g6.xlarge
- g6.2xlarge
- g6.4xlarge
- g6.8xlarge
- g6.12xlarge
- g6.16xlarge
- g6.24xlarge
- g6.48xlarge
- g6e.xlarge
- g6e.2xlarge
- g6e.4xlarge
- g6e.8xlarge
- g6e.12xlarge
- g6e.16xlarge
- g6e.24xlarge
- g6e.48xlarge
- g6f.large
- g6f.xlarge
- g6f.2xlarge
- g6f.4xlarge
- g7e.2xlarge
- g7e.4xlarge
- g7e.8xlarge
- g7e.12xlarge
- g7e.24xlarge
- g7e.48xlarge
- gr6.4xlarge
- gr6.8xlarge
- gr6f.4xlarge
- m6a.large
- m6a.xlarge
- m6a.2xlarge
- m6a.4xlarge
- m6a.8xlarge
- m6a.12xlarge
- m6a.16xlarge
- m6a.24xlarge
- m6a.32xlarge
- m6a.48xlarge
- m6a.metal
- m6g.medium
- m6g.large
- m6g.xlarge
- m6g.2xlarge
- m6g.4xlarge
- m6g.8xlarge
- m6g.12xlarge
- m6g.16xlarge
- m6g.metal
- m6gd.medium
- m6gd.large
- m6gd.xlarge
- m6gd.2xlarge
- m6gd.4xlarge
- m6gd.8xlarge
- m6gd.12xlarge
- m6gd.16xlarge
- m6gd.metal
- m6i.large
- m6i.xlarge
- m6i.2xlarge
- m6i.4xlarge
- m6i.8xlarge
- m6i.12xlarge
- m6i.16xlarge
- m6i.24xlarge
- m6i.32xlarge
- m6i.metal
- m6id.large
- m6id.xlarge
- m6id.2xlarge
- m6id.4xlarge
- m6id.8xlarge
- m6id.12xlarge
- m6id.16xlarge
- m6id.24xlarge
- m6id.32xlarge
- m6id.metal
- m6idn.large
- m6idn.xlarge
- m6idn.2xlarge
- m6idn.4xlarge
- m6idn.8xlarge
- m6idn.12xlarge
- m6idn.16xlarge
- m6idn.24xlarge
- m6idn.32xlarge
- m6idn.metal
2025-04-22 11:46:24 +02:00
- m6in.large
- m6in.xlarge
- m6in.2xlarge
- m6in.4xlarge
- m6in.8xlarge
- m6in.12xlarge
- m6in.16xlarge
- m6in.24xlarge
- m6in.32xlarge
- m6in.metal
- m7a.medium
- m7a.large
- m7a.xlarge
- m7a.2xlarge
- m7a.4xlarge
- m7a.8xlarge
- m7a.12xlarge
- m7a.16xlarge
- m7a.24xlarge
- m7a.32xlarge
- m7a.48xlarge
- m7a.metal-48xl
- m7g.medium
- m7g.large
- m7g.xlarge
- m7g.2xlarge
- m7g.4xlarge
- m7g.8xlarge
- m7g.12xlarge
- m7g.16xlarge
- m7g.metal
- m7gd.medium
- m7gd.large
- m7gd.xlarge
- m7gd.2xlarge
- m7gd.4xlarge
- m7gd.8xlarge
- m7gd.12xlarge
- m7gd.16xlarge
- m7gd.metal
- m7i-flex.large
- m7i-flex.xlarge
- m7i-flex.2xlarge
- m7i-flex.4xlarge
- m7i-flex.8xlarge
- m7i-flex.12xlarge
- m7i-flex.16xlarge
- m7i.large
- m7i.xlarge
- m7i.2xlarge
- m7i.4xlarge
- m7i.8xlarge
- m7i.12xlarge
- m7i.16xlarge
- m7i.24xlarge
- m7i.48xlarge
- m7i.metal-24xl
- m7i.metal-48xl
- m8g.medium
- m8g.large
- m8g.xlarge
- m8g.2xlarge
- m8g.4xlarge
- m8g.8xlarge
- m8g.12xlarge
- m8g.16xlarge
- m8g.24xlarge
- m8g.48xlarge
- m8g.metal-24xl
- m8g.metal-48xl
- m8gd.medium
- m8gd.large
- m8gd.xlarge
- m8gd.2xlarge
- m8gd.4xlarge
- m8gd.8xlarge
- m8gd.12xlarge
- m8gd.16xlarge
- m8gd.24xlarge
- m8gd.48xlarge
- m8gd.metal-24xl
- m8gd.metal-48xl
- p4d.24xlarge
- p4de.24xlarge
- p5.4xlarge
- p5.48xlarge
- p5e.48xlarge
- p5en.48xlarge
- p6-b200.48xlarge
- p6-b300.48xlarge
- p6e-gb200.36xlarge
- r6g.medium
- r6g.large
- r6g.xlarge
- r6g.2xlarge
- r6g.4xlarge
- r6g.8xlarge
- r6g.12xlarge
- r6g.16xlarge
- r6g.metal
- r6gd.medium
- r6gd.large
- r6gd.xlarge
- r6gd.2xlarge
- r6gd.4xlarge
- r6gd.8xlarge
- r6gd.12xlarge
- r6gd.16xlarge
- r6gd.metal
- r7g.medium
- r7g.large
- r7g.xlarge
- r7g.2xlarge
- r7g.4xlarge
- r7g.8xlarge
- r7g.12xlarge
- r7g.16xlarge
- r7g.metal
- r7gd.medium
- r7gd.large
- r7gd.xlarge
- r7gd.2xlarge
- r7gd.4xlarge
- r7gd.8xlarge
- r7gd.12xlarge
- r7gd.16xlarge
- r7gd.metal
- r8g.medium
- r8g.large
- r8g.xlarge
- r8g.2xlarge
- r8g.4xlarge
- r8g.8xlarge
- r8g.12xlarge
- r8g.16xlarge
- r8g.24xlarge
- r8g.48xlarge
- r8g.metal-24xl
- r8g.metal-48xl
2025-04-22 11:46:24 +02:00
ConstraintDescription: "Must be a valid EC2 instance type"
InitialMeetAdminPassword:
Description: 'Initial password for the "admin" user in OpenVidu Meet. If not provided, a random password will be generated.'
Type: String
NoEcho: true
Default: ''
# Only allow alphanumeric characters
AllowedPattern: '^[A-Za-z0-9_-]*$'
ConstraintDescription: 'Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to generate a random password.'
InitialMeetApiKey:
Description: 'Initial API key for OpenVidu Meet. If not provided, no API key will be set and the user can set it later from Meet Console.'
Type: String
NoEcho: true
Default: ''
# Only allow alphanumeric characters
AllowedPattern: '^[A-Za-z0-9_-]*$'
ConstraintDescription: 'Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to not set an initial API key.'
2025-04-22 11:46:24 +02:00
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
AllowedPattern: ^.+$
ConstraintDescription: must be the name of an existing EC2 KeyPair.
OperatingSystem:
Description: EC2 Operating System
Type: String
Default: "Ubuntu-24"
AllowedValues: ['Ubuntu-22', 'Ubuntu-24']
2025-04-22 11:46:24 +02:00
InitialNumberOfMediaNodes:
Type: Number
Default: 1
Description: Number of initial media nodes to deploy
MinNumberOfMediaNodes:
Type: Number
Default: 1
Description: Minimum number of media nodes to deploy
MaxNumberOfMediaNodes:
Type: Number
Default: 5
Description: Maximum number of media nodes to deploy
ScaleTargetCPU:
Type: Number
Default: 50
Description: Target CPU percentage to scale up or down
S3AppDataBucketName:
Type: String
Description: Name of the S3 bucket to store data and recordings. If empty, a bucket will be created
S3ClusterDataBucketName:
Type: String
Description: Name of the S3 bucket to store cluster data. If empty, a bucket will be created
AdditionalInstallFlags:
Description: Additional optional flags to pass to the OpenVidu installer (comma-separated, e.g., "--flag1=value, --flag2").
Type: String
Default: ""
AllowedPattern: '^[A-Za-z0-9, =_.\-]*$' # Allows letters, numbers, comma, space, underscore, dot, equals, and hyphen
ConstraintDescription: Must be a comma-separated list of flags (for example, --flag=value, --bool-flag).
2025-04-22 11:46:24 +02:00
OpenViduVPC:
Description: "Dedicated VPC for OpenVidu cluster"
Type: AWS::EC2::VPC::Id
AllowedPattern: ^.+$
ConstraintDescription: You must specify a VPC ID
OpenViduMasterNodeSubnets:
Description: "Subnets for OpenVidu Master Node"
Type: List<AWS::EC2::Subnet::Id>
AllowedPattern: ^.+$
ConstraintDescription: You must specify a list of subnet IDs
OpenViduMediaNodeSubnets:
Description: "Subnets for OpenVidu Media Nodes"
Type: List<AWS::EC2::Subnet::Id>
AllowedPattern: ^.+$
ConstraintDescription: You must specify a list of subnet IDs
LoadBalancerSubnets:
Description: "(Optional) Public subnets for the internet-facing Load Balancer. Leave empty to place the Load Balancer in the OpenViduMasterNodeSubnets (default behavior). Set it to dedicated public subnets to keep the Master Nodes in private subnets with NAT egress."
Type: CommaDelimitedList
Default: ""
2025-04-22 11:46:24 +02:00
MasterNodesDiskSize:
Description: Size of the disk in GB
Type: Number
Default: 100
MinValue: 50
ConstraintDescription: The disk size must be at least 50 GB
Metadata:
'AWS::CloudFormation::Interface':
ParameterGroups:
- Label:
default: Domain and Load Blancer configuration
Parameters:
- DomainName
- OpenViduCertificateARN
- Label:
default: OpenVidu High Availability configuration
Parameters:
- OpenViduLicense
- RTCEngine
- Label:
default: OpenVidu Meet configuration
Parameters:
- InitialMeetAdminPassword
- InitialMeetApiKey
2025-04-22 11:46:24 +02:00
- Label:
default: EC2 Instance configuration
Parameters:
- MasterNodeInstanceType
- MediaNodeInstanceType
- KeyName
- OperatingSystem
2025-04-22 11:46:24 +02:00
- Label:
default: Media Nodes Autoscaling Group configuration
Parameters:
- InitialNumberOfMediaNodes
- MinNumberOfMediaNodes
- MaxNumberOfMediaNodes
- ScaleTargetCPU
- Label:
default: S3 bucket for application data, cluster data and recordings
Parameters:
- S3AppDataBucketName
- S3ClusterDataBucketName
- Label:
default: VPC configuration
Parameters:
- OpenViduVPC
- OpenViduMasterNodeSubnets
- OpenViduMediaNodeSubnets
- LoadBalancerSubnets
2025-04-22 11:46:24 +02:00
- Label:
default: Volumes configuration
Parameters:
- MasterNodesDiskSize
- Label:
default: "(Optional) Additional Installer Flags"
Parameters:
- AdditionalInstallFlags
2025-04-22 11:46:24 +02:00
Conditions:
CreateRecordingsBucket: !Equals [!Ref S3AppDataBucketName, ""]
CreateClusterDataBucket: !Equals [!Ref S3ClusterDataBucketName, ""]
HasLoadBalancerSubnets: !Not [!Equals [!Join ["", !Ref LoadBalancerSubnets], ""]]
IsMasterGraviton: !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 't4g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6gn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7gn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c8g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r7g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6e-gb200']
IsMediaGraviton: !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 't4g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6gn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7gn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c8g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r7g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6e-gb200']
IsMasterNodeNvidia: !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g4dn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'gr6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'gr6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g7e']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p4d']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p4de']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5en']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6-b200']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6-b300']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6e-gb200']
IsMediaNodeNvidia: !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g4dn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'gr6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'gr6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g7e']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p4d']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p4de']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5en']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6-b200']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6-b300']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6e-gb200']
2025-04-22 11:46:24 +02:00
Mappings:
ArmImage:
# aws ssm get-parameters-by-path --path "/aws/service/canonical/ubuntu/" --recursive --query "Parameters[*].Name" > canonical-ami.txt
Ubuntu-22:
ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/jammy/stable/current/arm64/hvm/ebs-gp2/ami-id}}'
Ubuntu-24:
ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/noble/stable/current/arm64/hvm/ebs-gp3/ami-id}}'
AmdImage:
Ubuntu-22:
ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id}}'
Ubuntu-24:
ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/noble/stable/current/amd64/hvm/ebs-gp3/ami-id}}'
2025-04-22 11:46:24 +02:00
Resources:
OpenViduSharedInfo:
Type: AWS::SecretsManager::Secret
UpdateReplacePolicy: Retain
DeletionPolicy: Delete
Properties:
Name: !Sub openvidu-ha-${AWS::Region}-${AWS::StackName}
Description: Secret for OpenVidu High Availability to store deployment info and seed secret
# All the values are initialized by one master node and shared with the rest of the nodes
SecretString: |
{
"OPENVIDU_URL": "none",
"MEET_INITIAL_ADMIN_USER": "none",
"MEET_INITIAL_ADMIN_PASSWORD": "none",
"MEET_INITIAL_API_KEY": "none",
"LIVEKIT_URL": "none",
"LIVEKIT_API_KEY": "none",
"LIVEKIT_API_SECRET": "none",
"DASHBOARD_URL": "none",
"GRAFANA_URL": "none",
"MINIO_URL": "none",
2025-04-22 11:46:24 +02:00
"DOMAIN_NAME": "none",
"OPENVIDU_PRO_LICENSE": "none",
"OPENVIDU_RTC_ENGINE": "none",
"REDIS_PASSWORD": "none",
"MONGO_ADMIN_USERNAME": "none",
"MONGO_ADMIN_PASSWORD": "none",
"MONGO_REPLICA_SET_KEY": "none",
"MINIO_ACCESS_KEY": "none",
"MINIO_SECRET_KEY": "none",
"DASHBOARD_ADMIN_USERNAME": "none",
"DASHBOARD_ADMIN_PASSWORD": "none",
"GRAFANA_ADMIN_USERNAME": "none",
"GRAFANA_ADMIN_PASSWORD": "none",
"ENABLED_MODULES": "none",
"OPENVIDU_VERSION": "none",
"ALL_SECRETS_GENERATED": "false"
2025-04-22 11:46:24 +02:00
}
# One SSM parameter per master: atomic per-key writes
MasterNode1PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-1-private-ip
Type: String
Value: "none"
MasterNode2PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-2-private-ip
Type: String
Value: "none"
MasterNode3PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-3-private-ip
Type: String
Value: "none"
MasterNode4PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-4-private-ip
Type: String
Value: "none"
2025-04-22 11:46:24 +02:00
S3AppDataBucketResource:
Type: 'AWS::S3::Bucket'
Properties:
### Unique bucket name using Stack ID
BucketName: !Join ["-" , [ 'openvidu-appdata', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls : true
RestrictPublicBuckets: true
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Condition: CreateRecordingsBucket
S3ClusterDataBucketResource:
Type: 'AWS::S3::Bucket'
Properties:
### Unique bucket name using Stack ID
BucketName: !Join ["-" , [ 'openvidu-clusterdata', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls : true
RestrictPublicBuckets: true
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Condition: CreateClusterDataBucket
# -------------------------
# Normalize the master node subnet list to exactly 4 entries, one per OpenVidu Master Node
2025-04-22 11:46:24 +02:00
# -------------------------
SubnetProcessorFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub 'SubnetProcessor-${AWS::Region}-${AWS::StackName}'
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import cfnresponse
def lambda_handler(event, context):
try:
# Process event data
subnets = event['ResourceProperties']['Subnets']
# Ensure we have at least four subnets by cycling through the available subnets
subnets = (subnets * 4)[:4] # Repeat the list to have at least 4 elements and then take the first 4
# Prepare the response
responseData = {
'Subnet1': subnets[0],
'Subnet2': subnets[1],
'Subnet3': subnets[2],
'Subnet4': subnets[3],
}
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {'Message': str(e)})
Runtime: python3.12
Timeout: 120
SubnetProcessor:
Type: Custom::SubnetProcessor
Properties:
ServiceToken: !GetAtt SubnetProcessorFunction.Arn
Subnets: !Ref OpenViduMasterNodeSubnets
LambdaLogGroup:
UpdateReplacePolicy: Retain
DeletionPolicy: Delete
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}'
RetentionInDays: 7
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: LambdaLogsPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}:*'
OpenViduMasterNodeRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: "/"
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
2025-04-22 11:46:24 +02:00
Policies:
- PolicyName: !Sub openvidu-master-policy-${AWS::Region}-${AWS::StackName}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- autoscaling:SetInstanceHealth
Resource: '*'
Condition:
StringEquals:
'aws:ResourceTag/aws:cloudformation:stack-id': !Ref 'AWS::StackId'
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:UpdateSecret
Resource: !Ref OpenViduSharedInfo
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:PutParameter
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/openvidu/${AWS::StackName}/*
2025-04-22 11:46:24 +02:00
- Fn::If:
- CreateRecordingsBucket
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub ${S3AppDataBucketResource.Arn}/*
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}/*
- Fn::If:
- CreateRecordingsBucket
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !GetAtt S3AppDataBucketResource.Arn
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}
- Fn::If:
- CreateClusterDataBucket
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub ${S3ClusterDataBucketResource.Arn}/*
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub arn:${AWS::Partition}:s3:::${S3ClusterDataBucketName}/*
- Fn::If:
- CreateClusterDataBucket
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !GetAtt S3ClusterDataBucketResource.Arn
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !Sub arn:${AWS::Partition}:s3:::${S3ClusterDataBucketName}
RoleName:
Fn::Join:
# Generate a not too long and unique role name
# Getting a unique identifier from the stack id
- ''
- - openvidu-master-role-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
OpenViduMediaNodeRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: !Sub openvidu-media-policy-${AWS::Region}-${AWS::StackName}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref OpenViduSharedInfo
- Effect: Allow
Action:
- ssm:GetParameter
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/openvidu/${AWS::StackName}/*
2025-04-22 11:46:24 +02:00
- Effect: Allow
Action:
- autoscaling:SetInstanceHealth
- autoscaling:CompleteLifecycleAction
- autoscaling:RecordLifecycleActionHeartbeat
Resource: '*'
Condition:
StringEquals:
'aws:ResourceTag/aws:cloudformation:stack-name': !Ref 'AWS::StackName'
- Fn::If:
- CreateRecordingsBucket
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub ${S3AppDataBucketResource.Arn}/*
- Effect: Allow
Action:
- s3:DeleteObject
- s3:GetObject
- s3:PutObject
Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}/*
- Fn::If:
- CreateRecordingsBucket
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !GetAtt S3AppDataBucketResource.Arn
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}
RoleName:
Fn::Join:
# Generate a not too long and unique role name
# Getting a unique identifier from the stack id
- ''
- - openvidu-media-role-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
OpenViduMasterInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub OpenViduMasterInstanceProfile-${AWS::Region}-${AWS::StackName}
Roles:
- !Ref OpenViduMasterNodeRole
OpenViduMediaInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub OpenViduMediaInstanceProfile-${AWS::Region}-${AWS::StackName}
Roles:
- !Ref OpenViduMediaNodeRole
OpenViduMasterLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Metadata:
Comment: Launch template for OpenVidu Master Node
AWS::CloudFormation::Init:
config:
files:
'/usr/local/bin/install.sh':
content: !Sub |
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
2025-04-22 11:46:24 +02:00
DOMAIN=
YQ_VERSION=v4.53.6
2025-04-22 11:46:24 +02:00
# Install dependencies
apt-get update && apt-get install -y \
curl \
unzip \
jq \
wget
wget https://github.com/mikefarah/yq/releases/download/${!YQ_VERSION}/yq_linux_$(dpkg --print-architecture).tar.gz -O - |\
tar xz && mv yq_linux_$(dpkg --print-architecture) /usr/bin/yq
2025-04-22 11:46:24 +02:00
AWS_CLI_VERSION=2.36.40
# Install aws-cli if not already installed
if ! command -v aws &> /dev/null; then
curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m)-${!AWS_CLI_VERSION}.zip" -o "awscliv2.zip"
unzip -qq awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
fi
2025-04-22 11:46:24 +02:00
# Signal to notify instance is waiting
2025-04-22 11:46:24 +02:00
SIGNAL_NAME="$1"
# Exported earlier in this instance's UserData
MASTER_NODE_NUM="${!MASTER_NODE_NUM}"
2025-04-22 11:46:24 +02:00
# Token for IMDSv2
TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"
# Get own private IP
PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"
if [[ "$PRIVATE_IP" == "" ]]; then
echo "Error: Private IP not found"
exit 1
fi
aws ssm put-parameter \
--region ${AWS::Region} \
--name "/openvidu/${AWS::StackName}/master-node-${!MASTER_NODE_NUM}-private-ip" \
--value "${!PRIVATE_IP}" \
--type String \
--overwrite > /dev/null
2025-04-22 11:46:24 +02:00
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
# If the private IP is the same as the first master node, generate the secrets
if [[ $MASTER_NODE_NUM -eq 1 ]] && [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
DOMAIN="$(/usr/local/bin/store_secret.sh save DOMAIN_NAME "${DomainName}")"
# Publish access URLs early so other nodes can read them during phase 2
# (idempotent: after_install rewrites these same byte-identical values later)
/usr/local/bin/store_secret.sh save OPENVIDU_URL "https://${!DOMAIN}/"
/usr/local/bin/store_secret.sh save LIVEKIT_URL "wss://${!DOMAIN}/"
/usr/local/bin/store_secret.sh save DASHBOARD_URL "https://${!DOMAIN}/dashboard/"
/usr/local/bin/store_secret.sh save GRAFANA_URL "https://${!DOMAIN}/grafana/"
/usr/local/bin/store_secret.sh save MINIO_URL "https://${!DOMAIN}/minio-console/"
2025-04-22 11:46:24 +02:00
OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
# Store version so media nodes can use it to install the same version
/usr/local/bin/store_secret.sh save OPENVIDU_VERSION "${!OPENVIDU_VERSION}"
# Meet initial admin user and password
MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
if [[ "${InitialMeetAdminPassword}" != '' ]]; then
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_PASSWORD "${InitialMeetAdminPassword}")"
else
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MEET_INITIAL_ADMIN_PASSWORD)"
fi
if [[ "${InitialMeetApiKey}" != '' ]]; then
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "${InitialMeetApiKey}")"
else
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "")"
fi
2025-04-22 11:46:24 +02:00
# Store usernames and generate random passwords
REDIS_PASSWORD="$(/usr/local/bin/store_secret.sh generate REDIS_PASSWORD)"
MONGO_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
MONGO_REPLICA_SET_KEY="$(/usr/local/bin/store_secret.sh generate MONGO_REPLICA_SET_KEY)"
MINIO_ACCESS_KEY="$(/usr/local/bin/store_secret.sh save MINIO_ACCESS_KEY "minioadmin")"
MINIO_SECRET_KEY="$(/usr/local/bin/store_secret.sh generate MINIO_SECRET_KEY)"
DASHBOARD_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save DASHBOARD_ADMIN_USERNAME "dashboardadmin")"
DASHBOARD_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate DASHBOARD_ADMIN_PASSWORD)"
GRAFANA_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save GRAFANA_ADMIN_USERNAME "grafanaadmin")"
GRAFANA_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate GRAFANA_ADMIN_PASSWORD)"
LIVEKIT_API_KEY="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_KEY "API" 12)"
LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)"
ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED_MODULES "observability,v2compatibility,openviduMeet")"
2025-04-22 11:46:24 +02:00
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
fi
# Fetch the shared secret again, waiting until master-1 has generated all secrets.
# Bounded (up to 900s at 5s): a genuinely stuck master-1 still surfaces after the deadline.
SECRETS_WAIT_ATTEMPTS=0
SECRETS_WAIT_MAX=180
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
2025-04-22 11:46:24 +02:00
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
if [[ "${!ALL_SECRETS_GENERATED}" == "true" ]]; then
break
fi
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
echo "Error: Secrets not generated"
exit 1
fi
sleep 5
done
2025-04-22 11:46:24 +02:00
# sending the signal call
cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region}
IP_WAIT_ATTEMPTS=0
IP_WAIT_MAX=360
2025-04-22 11:46:24 +02:00
while true; do
MASTER_NODE_1_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_2_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_3_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_4_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
# Check if all master nodes have published their private IPs
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_3_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_4_PRIVATE_IP" ]]; then
2025-04-22 11:46:24 +02:00
break
fi
IP_WAIT_ATTEMPTS=$((IP_WAIT_ATTEMPTS + 1))
if [[ $IP_WAIT_ATTEMPTS -ge $IP_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for all master nodes to publish their private IPs to SSM"
exit 1
fi
2025-04-22 11:46:24 +02:00
sleep 5
done
# Comma-separated 1-4: must match installer's --master-node-private-ip-list format
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
# Re-read: other (non-IP) values still come from the shared secret.
# GetSecretValue is eventually consistent: retry until a read returns the generated values
SECRET_READ_ATTEMPTS=0
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
break
fi
SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
echo "Error: shared secret still incomplete after 5 minutes of stale reads"
exit 1
fi
sleep 5
done
2025-04-22 11:46:24 +02:00
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
MONGO_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_USERNAME')
MONGO_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_PASSWORD')
MONGO_REPLICA_SET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MONGO_REPLICA_SET_KEY')
MINIO_ACCESS_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_ACCESS_KEY')
MINIO_SECRET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_SECRET_KEY')
DASHBOARD_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_USERNAME')
DASHBOARD_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_PASSWORD')
GRAFANA_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_USERNAME')
GRAFANA_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_PASSWORD')
MEET_INITIAL_ADMIN_USER=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_USER')
MEET_INITIAL_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
MEET_INITIAL_API_KEY=""
if [[ "${InitialMeetApiKey}" != '' ]]; then
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
fi
2025-04-22 11:46:24 +02:00
LIVEKIT_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_KEY')
LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET')
ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS=(
"--no-tty"
"--install"
"--environment=aws"
"--deployment-type='ha'"
"--node-role='master-node'"
"--external-load-balancer"
"--master-node-private-ip-list='$MASTER_NODE_PRIVATE_IP_LIST'"
"--openvidu-pro-license='$OPENVIDU_PRO_LICENSE'"
"--domain-name='$DOMAIN'"
"--enabled-modules='$ENABLED_MODULES'"
"--rtc-engine=$OPENVIDU_RTC_ENGINE"
"--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-initial-admin-password=$MEET_INITIAL_ADMIN_PASSWORD"
"--meet-initial-api-key=$MEET_INITIAL_API_KEY"
2025-04-22 11:46:24 +02:00
"--livekit-api-key=$LIVEKIT_API_KEY"
"--livekit-api-secret=$LIVEKIT_API_SECRET"
)
# Include additional installer flags provided by the user
if [[ "${AdditionalInstallFlags}" != "" ]]; then
IFS=',' read -ra EXTRA_FLAGS <<< "${AdditionalInstallFlags}"
for extra_flag in "${!EXTRA_FLAGS[@]}"; do
# Trim whitespace around each flag
extra_flag="$(echo -e "${!extra_flag}" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')"
if [[ "$extra_flag" != "" ]]; then
COMMON_ARGS+=("$extra_flag")
fi
done
fi
2025-04-22 11:46:24 +02:00
# Construct the final command
FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}")"
# Install OpenVidu
exec bash -c "$FINAL_COMMAND"
mode: '000755'
owner: root
group: root
'/usr/local/bin/config_s3.sh':
content: !Sub
- |
#!/bin/bash
set -e
# Install dir and config dir
INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"
# Config S3 bucket
EXTERNAL_S3_ENDPOINT="https://s3.${AWS::Region}.amazonaws.com"
EXTERNAL_S3_REGION="${AWS::Region}"
EXTERNAL_S3_PATH_STYLE_ACCESS="false"
EXTERNAL_S3_BUCKET_APP_DATA=${S3AppDataBucketResourceName}
EXTERNAL_S3_BUCKET_CLUSTER_DATA=${S3ClusterDataBucketResourceName}
sed -i "s|EXTERNAL_S3_ENDPOINT=.*|EXTERNAL_S3_ENDPOINT=$EXTERNAL_S3_ENDPOINT|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s|EXTERNAL_S3_REGION=.*|EXTERNAL_S3_REGION=$EXTERNAL_S3_REGION|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s|EXTERNAL_S3_PATH_STYLE_ACCESS=.*|EXTERNAL_S3_PATH_STYLE_ACCESS=$EXTERNAL_S3_PATH_STYLE_ACCESS|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s|EXTERNAL_S3_BUCKET_APP_DATA=.*|EXTERNAL_S3_BUCKET_APP_DATA=$EXTERNAL_S3_BUCKET_APP_DATA|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s|EXTERNAL_S3_BUCKET_CLUSTER_DATA=.*|EXTERNAL_S3_BUCKET_CLUSTER_DATA=$EXTERNAL_S3_BUCKET_CLUSTER_DATA|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
- S3AppDataBucketResourceName: !If
- CreateRecordingsBucket
- !Ref S3AppDataBucketResource
- !Ref S3AppDataBucketName
S3ClusterDataBucketResourceName: !If
- CreateClusterDataBucket
- !Ref S3ClusterDataBucketResource
- !Ref S3ClusterDataBucketName
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/after_install.sh':
content: !Sub |
#!/bin/bash
set -e
# Only master 1 writes the shared secret, to avoid a lost-update race
if [[ "${!MASTER_NODE_NUM:-}" != "1" ]]; then
exit 0
fi
2025-04-22 11:46:24 +02:00
# Get current shared secret
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
# Save access URLs
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_URL="https://${!DOMAIN}/"
LIVEKIT_URL="wss://${!DOMAIN}/"
2025-04-22 11:46:24 +02:00
DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
GRAFANA_URL="https://${!DOMAIN}/grafana/"
MINIO_URL="https://${!DOMAIN}/minio-console/"
# Update shared secret
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
2025-04-22 11:46:24 +02:00
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$DOMAIN"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_URL": "'"$DASHBOARD_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_URL": "'"$GRAFANA_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_URL": "'"$MINIO_URL"'" }')"
# Update shared secret
aws secretsmanager update-secret \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--secret-string "$SHARED_SECRET"
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/update_config_from_secret.sh':
content: !Sub |
#!/bin/bash
set -e
# Token for IMDSv2
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Get current shared secret
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
# Installation directory
INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"
MASTER_NODE_CONFIG_DIR="${!INSTALL_DIR}/config/node"
# Replace DOMAIN_NAME
export DOMAIN=$(echo $SHARED_SECRET | jq -r .DOMAIN_NAME)
if [[ -n "$DOMAIN" ]]; then
sed -i "s/DOMAIN_NAME=.*/DOMAIN_NAME=$DOMAIN/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
else
exit 1
fi
# Replace rest of the values
sed -i "s/REDIS_PASSWORD=.*/REDIS_PASSWORD=$(echo $SHARED_SECRET | jq -r .REDIS_PASSWORD)/" "${!MASTER_NODE_CONFIG_DIR}/master_node.env"
sed -i "s/OPENVIDU_RTC_ENGINE=.*/OPENVIDU_RTC_ENGINE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_RTC_ENGINE)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/OPENVIDU_PRO_LICENSE=.*/OPENVIDU_PRO_LICENSE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_PRO_LICENSE)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/MONGO_ADMIN_USERNAME=.*/MONGO_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .MONGO_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/MONGO_ADMIN_PASSWORD=.*/MONGO_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .MONGO_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/DASHBOARD_ADMIN_USERNAME=.*/DASHBOARD_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .DASHBOARD_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/DASHBOARD_ADMIN_PASSWORD=.*/DASHBOARD_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .DASHBOARD_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/MINIO_ACCESS_KEY=.*/MINIO_ACCESS_KEY=$(echo $SHARED_SECRET | jq -r .MINIO_ACCESS_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/MINIO_SECRET_KEY=.*/MINIO_SECRET_KEY=$(echo $SHARED_SECRET | jq -r .MINIO_SECRET_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/GRAFANA_ADMIN_USERNAME=.*/GRAFANA_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .GRAFANA_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/GRAFANA_ADMIN_PASSWORD=.*/GRAFANA_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .GRAFANA_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/LIVEKIT_API_KEY=.*/LIVEKIT_API_KEY=$(echo $SHARED_SECRET | jq -r .LIVEKIT_API_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/LIVEKIT_API_SECRET=.*/LIVEKIT_API_SECRET=$(echo $SHARED_SECRET | jq -r .LIVEKIT_API_SECRET)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
sed -i "s/MEET_INITIAL_ADMIN_USER=.*/MEET_INITIAL_ADMIN_USER=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_ADMIN_USER)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
sed -i "s/MEET_INITIAL_ADMIN_PASSWORD=.*/MEET_INITIAL_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
if [[ "${InitialMeetApiKey}" != '' ]]; then
sed -i "s/MEET_INITIAL_API_KEY=.*/MEET_INITIAL_API_KEY=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_API_KEY)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
fi
2025-04-22 11:46:24 +02:00
sed -i "s/ENABLED_MODULES=.*/ENABLED_MODULES=$(echo $SHARED_SECRET | jq -r .ENABLED_MODULES)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
# Update URLs in secret
OPENVIDU_URL="https://${!DOMAIN}/"
LIVEKIT_URL="wss://${!DOMAIN}/"
2025-04-22 11:46:24 +02:00
DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
GRAFANA_URL="https://${!DOMAIN}/grafana/"
MINIO_URL="https://${!DOMAIN}/minio-console/"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
2025-04-22 11:46:24 +02:00
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$DOMAIN"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_URL": "'"$DASHBOARD_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_URL": "'"$GRAFANA_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_URL": "'"$MINIO_URL"'" }')"
aws secretsmanager update-secret \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--secret-string "$SHARED_SECRET"
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/update_secret_from_config.sh':
content: !Sub |
#!/bin/bash
set -e
# Get current shared secret
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
# Installation directory
INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"
MASTER_NODE_CONFIG_DIR="${!INSTALL_DIR}/config/node"
# Update shared secret
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"REDIS_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh REDIS_PASSWORD "${!MASTER_NODE_CONFIG_DIR}/master_node.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$(/usr/local/bin/get_value_from_config.sh DOMAIN_NAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_RTC_ENGINE": "'"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_RTC_ENGINE "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_PRO_LICENSE": "'"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_PRO_LICENSE "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MONGO_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MONGO_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_ACCESS_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MINIO_ACCESS_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_SECRET_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MINIO_SECRET_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_API_KEY": "'"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_API_SECRET": "'"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_SECRET "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_ADMIN_USER": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_USER "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
if [[ "${InitialMeetApiKey}" != '' ]]; then
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_API_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_API_KEY "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
fi
2025-04-22 11:46:24 +02:00
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"ENABLED_MODULES": "'"$(/usr/local/bin/get_value_from_config.sh ENABLED_MODULES "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
# Update shared secret
aws secretsmanager update-secret \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--secret-string "$SHARED_SECRET"
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/get_value_from_config.sh':
content: |
#!/bin/bash
set -e
# Function to get the value of a given key from the environment file
get_value() {
local key="$1"
local file_path="$2"
# Use grep to find the line with the key, ignoring lines starting with #
# Use awk to split on '=' and print the second field, which is the value
local value=$(grep -E "^\s*$key\s*=" "$file_path" | awk -F= '{print $2}' | sed 's/#.*//; s/^\s*//; s/\s*$//')
# If the value is empty, return "none"
if [ -z "$value" ]; then
echo "none"
else
echo "$value"
fi
}
# Check if the correct number of arguments are supplied
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <key> <file_path>"
exit 1
fi
# Get the key and file path from the arguments
key="$1"
file_path="$2"
# Get and print the value
get_value "$key" "$file_path"
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/store_secret.sh':
content: !Sub |
#!/bin/bash
set -e
# Modes: save, generate
# save mode: save the secret in the secret manager
# generate mode: generate a random password and save it in the secret manager
MODE="$1"
SHARED_SECRET="$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id ${OpenViduSharedInfo} \
--query SecretString --output text)"
if [[ "$MODE" == "generate" ]]; then
SECRET_KEY_NAME="$2"
PREFIX="${!3:-}"
LENGTH="${!4:-44}"
RANDOM_PASSWORD="$(openssl rand -base64 64 | tr -d '+/=\n' | cut -c -${!LENGTH})"
RANDOM_PASSWORD="${!PREFIX}${!RANDOM_PASSWORD}"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"'"$SECRET_KEY_NAME"'": "'"$RANDOM_PASSWORD"'"}')"
aws secretsmanager update-secret \
--region ${AWS::Region} \
--secret-id ${OpenViduSharedInfo} \
--secret-string "$SHARED_SECRET" > /dev/null 2>&1
echo "$RANDOM_PASSWORD"
elif [[ "$MODE" == "save" ]]; then
SECRET_KEY_NAME="$2"
SECRET_VALUE="$3"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"'"$SECRET_KEY_NAME"'": "'"$SECRET_VALUE"'"}')"
aws secretsmanager update-secret \
--region ${AWS::Region} \
--secret-id ${OpenViduSharedInfo} \
--secret-string "$SHARED_SECRET" > /dev/null 2>&1
echo "$SECRET_VALUE"
else
exit 1
fi
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/restart.sh':
content: |
#!/bin/bash
set -e
# Stop all services
systemctl stop openvidu
# Update config from secret
/usr/local/bin/update_config_from_secret.sh
# Start all services
systemctl start openvidu
mode: "000755"
owner: "root"
group: "root"
Properties:
LaunchTemplateName: !Sub 'openvidu-ha-master-${AWS::Region}-${AWS::StackName}'
LaunchTemplateData:
# Enable IMDSv2
MetadataOptions:
HttpEndpoint: enabled
HttpPutResponseHopLimit: 1
HttpTokens: required
IamInstanceProfile:
Name: !Ref OpenViduMasterInstanceProfile
ImageId: !If
- IsMasterGraviton
- !If
- IsMasterNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/arm64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
- !If
- IsMasterNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/x86_64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
2025-04-22 11:46:24 +02:00
InstanceType: !Ref MasterNodeInstanceType
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref OpenViduMasterNodeSG
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: !Ref MasterNodesDiskSize
VolumeType: gp3
DeleteOnTermination: true
OpenViduMasterNode1:
Type: AWS::EC2::Instance
# Depends on its own SSM parameter so its put-parameter can't race the parameter's creation
DependsOn: MasterNode1PrivateIpParameter
2025-04-22 11:46:24 +02:00
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 1
SubnetId: !GetAtt SubnetProcessor.Subnet1
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -eu -o pipefail
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
CFN_BOOTSTRAP_VERSION=2.0-39
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=1
2025-04-22 11:46:24 +02:00
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
# Config S3 bucket
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
2025-04-22 11:46:24 +02:00
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
MasterNodesWaitCondition1:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
Timeout: PT20M
2025-04-22 11:46:24 +02:00
Count: '1'
OpenViduMasterNode2:
Type: AWS::EC2::Instance
DependsOn: MasterNode2PrivateIpParameter
2025-04-22 11:46:24 +02:00
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 2
SubnetId: !GetAtt SubnetProcessor.Subnet2
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -eu -o pipefail
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
CFN_BOOTSTRAP_VERSION=2.0-39
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=2
2025-04-22 11:46:24 +02:00
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
# Config S3 bucket
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
2025-04-22 11:46:24 +02:00
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
MasterNodesWaitCondition2:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
Timeout: PT20M
2025-04-22 11:46:24 +02:00
Count: '1'
OpenViduMasterNode3:
Type: AWS::EC2::Instance
DependsOn: MasterNode3PrivateIpParameter
2025-04-22 11:46:24 +02:00
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 3
SubnetId: !GetAtt SubnetProcessor.Subnet3
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -eu -o pipefail
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
CFN_BOOTSTRAP_VERSION=2.0-39
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=3
2025-04-22 11:46:24 +02:00
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
# Config S3 bucket
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
2025-04-22 11:46:24 +02:00
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
MasterNodesWaitCondition3:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
Timeout: PT20M
2025-04-22 11:46:24 +02:00
Count: '1'
OpenViduMasterNode4:
Type: AWS::EC2::Instance
DependsOn: MasterNode4PrivateIpParameter
2025-04-22 11:46:24 +02:00
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 4
SubnetId: !GetAtt SubnetProcessor.Subnet4
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -eu -o pipefail
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
CFN_BOOTSTRAP_VERSION=2.0-39
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=4
2025-04-22 11:46:24 +02:00
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
# Config S3 bucket
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
2025-04-22 11:46:24 +02:00
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
MasterNodesWaitCondition4:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
Timeout: PT20M
2025-04-22 11:46:24 +02:00
Count: '1'
OpenViduMediaNodeLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Metadata:
Comment: Launch template for OpenVidu Media Node
AWS::CloudFormation::Init:
config:
files:
'/usr/local/bin/install.sh':
content: !Sub |
#!/bin/bash
set -e
YQ_VERSION=v4.53.6
2025-04-22 11:46:24 +02:00
# Install dependencies
apt-get update && apt-get install -y \
curl \
unzip \
jq \
wget
wget https://github.com/mikefarah/yq/releases/download/${!YQ_VERSION}/yq_linux_$(dpkg --print-architecture).tar.gz -O - |\
tar xz && mv yq_linux_$(dpkg --print-architecture) /usr/bin/yq
2025-04-22 11:46:24 +02:00
AWS_CLI_VERSION=2.36.40
# Install aws-cli if not already installed
if ! command -v aws &> /dev/null; then
curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m)-${!AWS_CLI_VERSION}.zip" -o "awscliv2.zip"
unzip -qq awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
fi
2025-04-22 11:46:24 +02:00
# Token for IMDSv2
TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"
# Get own private IP
PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"
SHARED_SECRET="$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')"
if [[ "$SHARED_SECRET" == "none" ]]; then
echo "Error: Shared secret not found"
exit 1
fi
SECRETS_WAIT_ATTEMPTS=0
SECRETS_WAIT_MAX=360
while true; do
# Validate content, not just the flag: stale eventually-consistent reads can return pre-generation values
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
break
fi
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for shared secrets to be generated"
exit 1
fi
sleep 5
SHARED_SECRET="$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')"
done
2025-04-22 11:46:24 +02:00
# Get OpenVidu Media Nodes version to deploy
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then
echo "OpenVidu version not found"
exit 1
fi
IP_WAIT_ATTEMPTS=0
IP_WAIT_MAX=360
while true; do
MASTER_NODE_1_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_2_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_3_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_4_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_3_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_4_PRIVATE_IP" ]]; then
break
fi
IP_WAIT_ATTEMPTS=$((IP_WAIT_ATTEMPTS + 1))
if [[ $IP_WAIT_ATTEMPTS -ge $IP_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for all master nodes to publish their private IPs to SSM"
exit 1
fi
sleep 5
done
2025-04-22 11:46:24 +02:00
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS=(
"--no-tty"
"--install"
"--environment=aws"
"--deployment-type='ha'"
"--node-role='media-node'"
"--master-node-private-ip-list=$MASTER_NODE_PRIVATE_IP_LIST"
"--private-ip=$PRIVATE_IP"
"--redis-password=$REDIS_PASSWORD"
)
# Construct the final command with all arguments
FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}")"
# Install OpenVidu
exec bash -c "$FINAL_COMMAND"
mode: '000755'
owner: root
group: root
'/usr/local/bin/set_as_unhealthy.sh':
content: !Sub |
#!/bin/bash
set -e
# Token for IMDSv2
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Get own instance ID
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
# Set instance as unhealthy
aws autoscaling set-instance-health \
--region ${AWS::Region} \
--instance-id "$INSTANCE_ID" \
--health-status Unhealthy
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/stop_media_node.sh':
content: !Sub |
#!/bin/bash
set -e
# Token for IMDSv2
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Get own instance ID
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
ASG_NAME=openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
# Execute if docker is installed
if [ -x "$(command -v docker)" ]; then
echo "Stopping media node services and waiting for termination..."
docker container kill --signal=SIGQUIT openvidu 2>/dev/null || true
docker container kill --signal=SIGQUIT ingress 2>/dev/null || true
docker container kill --signal=SIGQUIT egress 2>/dev/null || true
for agent_container in $(docker ps --filter "label=openvidu-agent=true" --format '{{.Names}}' 2>/dev/null); do
docker container kill --signal=SIGQUIT "$agent_container" 2>/dev/null || true
done
2025-04-22 11:46:24 +02:00
TIME_PASSED=0
HEARTBEAT_MAX=1800
# Wait for running containers to not be openvidu, ingress, egress or an openvidu agent
while [ "$(docker ps --filter 'label=openvidu-agent=true' -q 2>/dev/null | wc -l)" -gt 0 ] || \
[ "$(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null)" = "true" ] || \
[ "$(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null)" = "true" ] || \
[ "$(docker inspect -f '{{.State.Running}}' egress 2>/dev/null)" = "true" ]; do
2025-04-22 11:46:24 +02:00
echo "Waiting for containers to stop..."
sleep 5
TIME_PASSED=$((TIME_PASSED+5))
if [ $TIME_PASSED -ge $HEARTBEAT_MAX ]; then
echo "Increase lifecycle hook timeout to continue waiting for termination"
# Increase lifecycle hook timeout
aws autoscaling record-lifecycle-action-heartbeat \
--region ${AWS::Region} \
--lifecycle-hook-name StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName} \
--auto-scaling-group-name "$ASG_NAME" \
--instance-id "$INSTANCE_ID"
TIME_PASSED=0
fi
done
fi
aws autoscaling complete-lifecycle-action \
--region ${AWS::Region} \
--lifecycle-hook-name StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName} \
--auto-scaling-group-name "$ASG_NAME" \
--lifecycle-action-result CONTINUE \
--instance-id "$INSTANCE_ID"
mode: "000755"
owner: "root"
group: "root"
Properties:
LaunchTemplateName: !Sub 'openvidu-ha-media-${AWS::Region}-${AWS::StackName}'
LaunchTemplateData:
# Enable IMDSv2 by default
MetadataOptions:
HttpEndpoint: enabled
HttpPutResponseHopLimit: 1
HttpTokens: required
IamInstanceProfile:
Arn: !GetAtt OpenViduMediaInstanceProfile.Arn
SecurityGroupIds:
- !GetAtt OpenViduMediaNodeSG.GroupId
ImageId: !If
- IsMediaGraviton
- !If
- IsMediaNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/arm64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
- !If
- IsMediaNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/x86_64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
2025-04-22 11:46:24 +02:00
KeyName: !Ref KeyName
InstanceType: !Ref MediaNodeInstanceType
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -eu -o pipefail
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
CFN_BOOTSTRAP_VERSION=2.0-39
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
cfn-init --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMediaNodeLaunchTemplate
2025-04-22 11:46:24 +02:00
export HOME="/root"
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
MASTER_IP_1=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_2=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_3=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_4=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_HEALTHY=false
for i in $(seq 1 360); do
for MASTER_IP in "$MASTER_IP_1" "$MASTER_IP_2" "$MASTER_IP_3" "$MASTER_IP_4"; do
if [ "$MASTER_IP" != "none" ] && [ -n "$MASTER_IP" ] && curl -sf "http://$MASTER_IP:7880/health/caddy" >/dev/null 2>&1; then
MASTER_HEALTHY=true
break
fi
done
if [ "$MASTER_HEALTHY" = "true" ]; then
break
fi
sleep 5
done
if [ "$MASTER_HEALTHY" != "true" ]; then
echo "[OpenVidu] no master node became healthy after 30 minutes"
/usr/local/bin/set_as_unhealthy.sh
exit 1
fi
2025-04-22 11:46:24 +02:00
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
# Wait for the app
# /usr/local/bin/check_app_ready.sh
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp3
DeleteOnTermination: true
VolumeSize: !If [IsMediaNodeNvidia, 100, 50]
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeASG:
DependsOn:
- OpenViduMediaInstanceProfile
- OpenViduMasterInstanceProfile
- StopMediaNodeCloudWatchEventRule
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: !Sub openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMediaNodeLaunchTemplate
Version: !GetAtt OpenViduMediaNodeLaunchTemplate.DefaultVersionNumber
TargetGroupARNs:
- !Ref OpenViduMediaNodeRTMPTG
2025-04-22 11:46:24 +02:00
MinSize: !Ref MinNumberOfMediaNodes
MaxSize: !Ref MaxNumberOfMediaNodes
DesiredCapacity: !Ref InitialNumberOfMediaNodes
VPCZoneIdentifier: !Ref OpenViduMediaNodeSubnets
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Media Node
PropagateAtLaunch: true
StopMediaNodeLifecycleHook:
Type: 'AWS::AutoScaling::LifecycleHook'
Properties:
LifecycleHookName: !Sub StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName}
AutoScalingGroupName: !Ref OpenViduMediaNodeASG
LifecycleTransition: 'autoscaling:EC2_INSTANCE_TERMINATING'
DefaultResult: 'CONTINUE'
HeartbeatTimeout: 3600
StopMediaNodeDocumentRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ssm.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ssm:DescribeInstanceInformation
- ssm:ListCommands
- ssm:ListCommandInvocations
Resource: "*"
- Effect: Allow
Action:
- ssm:SendCommand
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::document/AWS-RunShellScript
- Action:
- ssm:SendCommand
Resource: !Sub arn:${AWS::Partition}:ec2:*:*:instance/*
Condition:
StringEquals:
'aws:ResourceTag/aws:cloudformation:stack-name': !Ref 'AWS::StackName'
Effect: Allow
PolicyName: SSM-Automation-Policy
StopMediaNodeAutomationDocument:
Type: AWS::SSM::Document
Properties:
Name:
Fn::Join:
# Generate a not too long and unique document name
# Getting a unique identifier from the stack id
- ''
- - 'StopMediaNodeAutomationDocument-'
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
DocumentType: Automation
Content:
schemaVersion: '0.3'
assumeRole: "{{automationAssumeRole}}"
description: This document stops the OpenVidu services in a Media Node and
terminates the instance. It stop the OpenVidu services without interrupting
the running sessions, ingress and egress running in the Media Node.
Also it sends a CONTINUE signal to the Auto Scaling Group to continue the
instance termination when the services are stopped.
parameters:
InstanceId:
type: String
automationAssumeRole:
type: String
default: !GetAtt StopMediaNodeDocumentRole.Arn
description: "(Required) The ARN of the role that allows Automation to
perform the actions."
mainSteps:
- name: RunCommand
action: aws:runCommand
inputs:
DocumentName: AWS-RunShellScript
InstanceIds:
- "{{ InstanceId }}"
Parameters:
# 24 hours as a timeout to wait for the instance to stop all services
executionTimeout: "60"
commands:
- nohup /usr/local/bin/stop_media_node.sh > /var/log/stop_media_node.log 2>&1 &
StopMediaNodeCloudWatchEventRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ssm:StartAutomationExecution
Resource:
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-execution/*
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:document/${StopMediaNodeAutomationDocument}
2025-04-22 11:46:24 +02:00
PolicyName: !Sub StopMediaNodeCloudWatchEventPolicy-${AWS::Region}-${AWS::StackName}
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- iam:PassRole
Resource: !GetAtt StopMediaNodeDocumentRole.Arn
PolicyName: Pass-Role-SSM-Automation-Policy
StopMediaNodeCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
Description: Rule to trigger the StopMediaNodeAutomationDocument when an instance is terminated
EventPattern:
source:
- aws.autoscaling
detail-type:
- EC2 Instance-terminate Lifecycle Action
detail:
AutoScalingGroupName:
- !Sub openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
Name:
Fn::Join:
# Generate a not too long and unique rule name
# Getting a unique identifier from the stack id
- ''
- - StopMediaNodeCloudWatchEventRule-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
Targets:
- Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${StopMediaNodeAutomationDocument}:$DEFAULT
RoleArn: !GetAtt StopMediaNodeCloudWatchEventRole.Arn
Id:
Fn::Join:
# Generate a not too long and unique target id
# Getting a unique identifier from the stack id
- ''
- - StopMediaNodeCloudWatchEventRule-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
InputTransformer:
InputPathsMap:
instanceid: "$.detail.EC2InstanceId"
InputTemplate: |
{
"InstanceId": [ <instanceid> ]
}
OpenViduMediaNodeASGScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AutoScalingGroupName: !Ref OpenViduMediaNodeASG
PolicyType: TargetTrackingScaling
EstimatedInstanceWarmup: 120
TargetTrackingConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ASGAverageCPUUtilization
TargetValue: !Ref ScaleTargetCPU
OpenViduMasterNodeSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for OpenVidu Master Node
GroupName: !Sub openvidu-ha-master-sg-${AWS::Region}-${AWS::StackName}
VpcId: !Ref OpenViduVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
OpenViduLoadBalancerToMasterHTTPIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
2025-04-22 11:46:24 +02:00
OpenViduLoadBalancerToMasterIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7880
ToPort: 7880
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
OpenViduMasterToMasterRedisIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7000
ToPort: 7001
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterNodeClusterPortIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7880
ToPort: 7880
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeToMasterRedisIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7000
ToPort: 7001
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
OpenViduMasterToMasterMinioIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9100
ToPort: 9100
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterMinioConsoleSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9101
ToPort: 9101
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterMinioIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9100
ToPort: 9100
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
OpenViduMasterToMasterMongoIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 20000
ToPort: 20000
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterMongoIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 20000
ToPort: 20000
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
OpenViduMasterToMasterMimirGrpcIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9095
ToPort: 9095
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterMimirGossipIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7946
ToPort: 7946
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterHTTPMimirSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9009
ToPort: 9009
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
OpenViduMasterToMasterLokiGrpcIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9096
ToPort: 9096
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterLokiGossipIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 7947
ToPort: 7947
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterDashboardsIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 5000
ToPort: 5000
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterGrafanaIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 3000
ToPort: 3000
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterHTTPLokiSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 3100
ToPort: 3100
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
OpenViduMasterToMasterV2CompatibilityIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 4443
ToPort: 4443
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterV2CompatibilityWebhookIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol: tcp
FromPort: 4443
ToPort: 4443
SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMasterToMasterMeetIngress:
2025-04-22 11:46:24 +02:00
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 9080
ToPort: 9080
2025-04-22 11:46:24 +02:00
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMasterCustomAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 6080
ToPort: 6080
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMediaNodeToMasterMeetWebhookIngress:
2025-04-22 11:46:24 +02:00
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol: tcp
FromPort: 9080
ToPort: 9080
2025-04-22 11:46:24 +02:00
SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterCustomAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol: tcp
FromPort: 6080
ToPort: 6080
SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for OpenVidu Media Node
GroupName: !Sub openvidu-ha-media-sg-${AWS::Region}-${AWS::StackName}
VpcId: !Ref OpenViduVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 7881
ToPort: 7881
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 7881
ToPort: 7881
CidrIpv6: ::/0
2025-04-22 11:46:24 +02:00
- IpProtocol: udp
FromPort: 7885
ToPort: 7885
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 7885
ToPort: 7885
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 50000
ToPort: 60000
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 50000
ToPort: 60000
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 50000
ToPort: 60000
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 50000
ToPort: 60000
CidrIpv6: ::/0
2025-04-22 11:46:24 +02:00
OpenViduLoadBalancerToMediaNodeRTMPIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMediaNodeSG
IpProtocol: tcp
FromPort: 1945
ToPort: 1945
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
OpenViduLoadBalancerToMediaNodeIngressHealthCheckSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMediaNodeSG
IpProtocol: tcp
FromPort: 9092
ToPort: 9092
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
OpenViduMasterToMediaNodeServerIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMediaNodeSG
IpProtocol: tcp
FromPort: 7880
ToPort: 7880
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduMasterToMediaNodeClientIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMediaNodeSG
IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduTurnTLSMasterNodeToMediaNodeIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMediaNodeSG
IpProtocol: tcp
FromPort: 5349
ToPort: 5349
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
OpenViduTurnTLSLoadBalancerToMediaNodeIngressSG:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref OpenViduMasterNodeSG
IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
2025-04-22 11:46:24 +02:00
OpenViduLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for the Load Balancer
GroupName: !Sub openvidu-ha-lb-sg-${AWS::Region}-${AWS::StackName}
VpcId: !Ref OpenViduVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 1935
ToPort: 1935
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 1935
ToPort: 1935
CidrIpv6: ::/0
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name:
Fn::Join:
# Generate a not too long and unique load balancer name
# Getting a unique identifier from the stack id
- ''
- - OpenViduHA-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
Subnets: !If [HasLoadBalancerSubnets, !Ref LoadBalancerSubnets, !Ref OpenViduMasterNodeSubnets]
2025-04-22 11:46:24 +02:00
SecurityGroups:
- !Ref OpenViduLoadBalancerSG
Type: network
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Load Balancer
OpenViduMasterNodeWithTurnTLSHTTPListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref OpenViduMasterNodeHTTPTG
LoadBalancerArn: !Ref LoadBalancer
Port: 80
Protocol: TCP
OpenViduMasterNodeWithTurnTLSListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref OpenViduMasterNodeWithTurnTLSTG
LoadBalancerArn: !Ref LoadBalancer
Port: 443
Protocol: TLS
Certificates:
- CertificateArn: !Ref OpenViduCertificateARN
2025-04-22 11:46:24 +02:00
OpenViduRTMPMediaNodeListener:
Type: 'AWS::ElasticLoadBalancingV2::Listener'
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref OpenViduMediaNodeRTMPTG
LoadBalancerArn: !Ref LoadBalancer
Port: 1935
Protocol: TLS
Certificates:
- CertificateArn: !Ref OpenViduCertificateARN
OpenViduMasterNodeHTTPTG:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name:
Fn::Join:
# Generate a not too long and unique target id
# Getting a unique identifier from the stack id
- ''
- - OVHTTP-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
TargetType: instance
Targets:
- Id: !Ref OpenViduMasterNode1
- Id: !Ref OpenViduMasterNode2
- Id: !Ref OpenViduMasterNode3
- Id: !Ref OpenViduMasterNode4
VpcId: !Ref OpenViduVPC
Port: 80
Protocol: TCP
Matcher:
HttpCode: '200'
HealthCheckIntervalSeconds: 10
HealthCheckPath: /health/caddy
HealthCheckProtocol: HTTP
HealthCheckPort: '7880'
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 4
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master HTTP Target Group
OpenViduMasterNodeWithTurnTLSTG:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name:
Fn::Join:
# Generate a not too long and unique target id
# Getting a unique identifier from the stack id
- ''
- - OVTurnTLSMaster-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
TargetType: instance
Targets:
- Id: !Ref OpenViduMasterNode1
- Id: !Ref OpenViduMasterNode2
- Id: !Ref OpenViduMasterNode3
- Id: !Ref OpenViduMasterNode4
VpcId: !Ref OpenViduVPC
Port: 443
Protocol: TCP
Matcher:
HttpCode: '200'
HealthCheckIntervalSeconds: 10
HealthCheckPath: /health/caddy
HealthCheckProtocol: HTTP
HealthCheckPort: '7880'
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 4
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN TLS Master Target Group
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeRTMPTG:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name:
Fn::Join:
# Generate a not too long and unique target id
# Getting a unique identifier from the stack id
- ''
- - OVRTMP-
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
VpcId: !Ref OpenViduVPC
Port: 1945
Protocol: TCP
Matcher:
HttpCode: '200'
HealthCheckIntervalSeconds: 10
HealthCheckPath: /
HealthCheckProtocol: HTTP
# Ingress health check port
HealthCheckPort: '9092'
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 4
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 60
Tags:
- Key: Name
Value: !Sub ${AWS::StackName} - OpenVidu HA - RTMP Target Group
Outputs:
ServicesAndCredentials:
Description: Services and credentials
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/secretsmanager/home?region=${AWS::Region}#!/secret?name=openvidu-ha-${AWS::Region}-${AWS::StackName}