mirror of https://github.com/OpenVidu/openvidu.git
4258 lines
167 KiB
YAML
4258 lines
167 KiB
YAML
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: mediasoup
|
|
|
|
MasterNodeInstanceType:
|
|
Description: "Specifies the EC2 instance type for your OpenVidu Master Node"
|
|
Type: String
|
|
Default: c6a.xlarge
|
|
AllowedValues:
|
|
- t3.nano
|
|
- t3.micro
|
|
- t3.small
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
- 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
|
|
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.'
|
|
|
|
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']
|
|
|
|
InitialNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 1
|
|
Description: Number of initial media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
|
|
|
|
MinNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 1
|
|
Description: Minimum number of media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
|
|
|
|
MaxNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 5
|
|
Description: Maximum number of media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
|
|
|
|
ScaleTargetCPU:
|
|
Type: Number
|
|
Default: 50
|
|
Description: Target CPU percentage to scale up or down. Ignored when FixedNumberOfMediaNodes > 0
|
|
|
|
FixedNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 0
|
|
MinValue: 0
|
|
Description: If > 0, deploys a fixed number of media nodes with no autoscaling (InitialNumberOfMediaNodes, MinNumberOfMediaNodes, MaxNumberOfMediaNodes and ScaleTargetCPU are ignored). If 0 (default), the deployment is elastic and autoscaling is enabled
|
|
|
|
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).
|
|
|
|
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 outbound internet access (for example, a NAT gateway)."
|
|
Type: CommaDelimitedList
|
|
Default: ""
|
|
|
|
TurnDomainName:
|
|
Description: "(Optional) Domain for TURN over TLS, served by a dedicated TURN Load Balancer that forwards it directly to the Media Nodes instead of through the Master Nodes. Set it together with TurnCertificateARN and point it (A/ALIAS) to the TurnLoadBalancerDNS output. If LoadBalancerSubnets is also set and the Media Nodes are in other (private) subnets, a second Load Balancer (TurnUDPLoadBalancerDNS output) also forwards TURN over UDP 443 to the Media Nodes, so media goes over UDP, with TURN over TLS as the fallback."
|
|
Type: String
|
|
Default: ""
|
|
|
|
TurnCertificateARN:
|
|
Description: "(Optional) ACM certificate ARN for TurnDomainName, used on the TURN Load Balancer 443 TLS listener."
|
|
Type: String
|
|
Default: ""
|
|
|
|
MasterNodesDiskSize:
|
|
Description: Size of the disk in GB
|
|
Type: Number
|
|
Default: 200
|
|
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
|
|
- TurnDomainName
|
|
- TurnCertificateARN
|
|
- Label:
|
|
default: OpenVidu High Availability configuration
|
|
Parameters:
|
|
- OpenViduLicense
|
|
- RTCEngine
|
|
- Label:
|
|
default: OpenVidu Meet configuration
|
|
Parameters:
|
|
- InitialMeetAdminPassword
|
|
- InitialMeetApiKey
|
|
- Label:
|
|
default: EC2 Instance configuration
|
|
Parameters:
|
|
- MasterNodeInstanceType
|
|
- MediaNodeInstanceType
|
|
- KeyName
|
|
- OperatingSystem
|
|
- Label:
|
|
default: Media Nodes Autoscaling Group configuration
|
|
Parameters:
|
|
- FixedNumberOfMediaNodes
|
|
- 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
|
|
- Label:
|
|
default: Volumes configuration
|
|
Parameters:
|
|
- MasterNodesDiskSize
|
|
- Label:
|
|
default: "(Optional) Additional Installer Flags"
|
|
Parameters:
|
|
- AdditionalInstallFlags
|
|
|
|
Conditions:
|
|
CreateRecordingsBucket: !Equals [!Ref S3AppDataBucketName, ""]
|
|
CreateClusterDataBucket: !Equals [!Ref S3ClusterDataBucketName, ""]
|
|
FixedNumberOfMediaNodesEnabled: !Not [ !Equals [!Ref FixedNumberOfMediaNodes, 0] ]
|
|
AutoscalingEnabled: !Equals [!Ref FixedNumberOfMediaNodes, 0]
|
|
HasLoadBalancerSubnets: !Not [!Equals [!Join ["", !Ref LoadBalancerSubnets], ""]]
|
|
TurnTLSIsEnabled: !And [!Not [!Equals [!Ref TurnDomainName, ""]], !Not [!Equals [!Ref TurnCertificateARN, ""]]]
|
|
# Media Nodes behind the TURN Load Balancer in other subnets than the Load Balancers (private subnets)
|
|
TurnUDPIsEnabled: !And
|
|
- !Condition TurnTLSIsEnabled
|
|
- !Condition HasLoadBalancerSubnets
|
|
- !Not [!Equals [!Join [",", !Ref LoadBalancerSubnets], !Join [",", !Ref OpenViduMediaNodeSubnets]]]
|
|
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']
|
|
|
|
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}}'
|
|
|
|
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",
|
|
"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"
|
|
}
|
|
|
|
# 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"
|
|
|
|
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
|
|
# -------------------------
|
|
SubnetProcessorFunction:
|
|
Type: AWS::Lambda::Function
|
|
Properties:
|
|
FunctionName: !Sub 'SubnetProcessor-${AWS::Region}-${AWS::StackName}'
|
|
Handler: index.lambda_handler
|
|
Role: !GetAtt LambdaExecutionRole.Arn
|
|
Code:
|
|
ZipFile: |
|
|
import boto3
|
|
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],
|
|
}
|
|
|
|
# Media Nodes are private when their subnets do not assign public IPs
|
|
media_subnets = event['ResourceProperties'].get('MediaSubnets', [])
|
|
media_private = 'false'
|
|
if event['RequestType'] != 'Delete' and media_subnets:
|
|
described = boto3.client('ec2').describe_subnets(SubnetIds=media_subnets)['Subnets']
|
|
if any(not s.get('MapPublicIpOnLaunch', False) for s in described):
|
|
media_private = 'true'
|
|
responseData['MediaNodesPrivate'] = media_private
|
|
|
|
# TURN over UDP through the TURN UDP Load Balancer, only for private Media Nodes:
|
|
# public ones keep announcing their own public IP
|
|
turn_udp = event['ResourceProperties'].get('TurnUDP', 'false') == 'true'
|
|
responseData['TurnUDPEnabled'] = 'true' if turn_udp and media_private == 'true' else 'false'
|
|
|
|
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
|
|
MediaSubnets: !Ref OpenViduMediaNodeSubnets
|
|
TurnUDP: !If [TurnUDPIsEnabled, 'true', 'false']
|
|
|
|
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}:*'
|
|
- Effect: Allow
|
|
Action:
|
|
- ec2:DescribeSubnets
|
|
Resource: '*'
|
|
|
|
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
|
|
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}/*
|
|
- 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}/*
|
|
- 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
|
|
DOMAIN=
|
|
YQ_VERSION=v4.53.6
|
|
|
|
# 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
|
|
|
|
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
|
|
|
|
# Signal to notify instance is waiting
|
|
SIGNAL_NAME="$1"
|
|
|
|
# Exported earlier in this instance's UserData
|
|
MASTER_NODE_NUM="${!MASTER_NODE_NUM}"
|
|
|
|
# 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
|
|
|
|
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/"
|
|
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
|
|
|
|
# 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")"
|
|
|
|
# Every store_secret.sh call above is a read-modify-write of the whole JSON, and a read
|
|
# right after a write can still return the previous version (eventual consistency): the
|
|
# key written a moment earlier is then silently dropped (seen live: LIVEKIT_API_KEY back
|
|
# to "none", every master waiting 5 minutes on an incomplete secret and failing). So
|
|
# write the complete set once more from the values above, together with
|
|
# ALL_SECRETS_GENERATED, and read it back until all of it is really there.
|
|
SECRETS_WANTED="$(jq -n \
|
|
--arg DOMAIN_NAME "${!DOMAIN}" \
|
|
--arg OPENVIDU_URL "https://${!DOMAIN}/" \
|
|
--arg LIVEKIT_URL "wss://${!DOMAIN}/" \
|
|
--arg DASHBOARD_URL "https://${!DOMAIN}/dashboard/" \
|
|
--arg GRAFANA_URL "https://${!DOMAIN}/grafana/" \
|
|
--arg MINIO_URL "https://${!DOMAIN}/minio-console/" \
|
|
--arg OPENVIDU_PRO_LICENSE "${!OPENVIDU_PRO_LICENSE}" \
|
|
--arg OPENVIDU_RTC_ENGINE "${!OPENVIDU_RTC_ENGINE}" \
|
|
--arg OPENVIDU_VERSION "${!OPENVIDU_VERSION}" \
|
|
--arg MEET_INITIAL_ADMIN_USER "${!MEET_INITIAL_ADMIN_USER}" \
|
|
--arg MEET_INITIAL_ADMIN_PASSWORD "${!MEET_INITIAL_ADMIN_PASSWORD}" \
|
|
--arg MEET_INITIAL_API_KEY "${!MEET_INITIAL_API_KEY}" \
|
|
--arg REDIS_PASSWORD "${!REDIS_PASSWORD}" \
|
|
--arg MONGO_ADMIN_USERNAME "${!MONGO_ADMIN_USERNAME}" \
|
|
--arg MONGO_ADMIN_PASSWORD "${!MONGO_ADMIN_PASSWORD}" \
|
|
--arg MONGO_REPLICA_SET_KEY "${!MONGO_REPLICA_SET_KEY}" \
|
|
--arg MINIO_ACCESS_KEY "${!MINIO_ACCESS_KEY}" \
|
|
--arg MINIO_SECRET_KEY "${!MINIO_SECRET_KEY}" \
|
|
--arg DASHBOARD_ADMIN_USERNAME "${!DASHBOARD_ADMIN_USERNAME}" \
|
|
--arg DASHBOARD_ADMIN_PASSWORD "${!DASHBOARD_ADMIN_PASSWORD}" \
|
|
--arg GRAFANA_ADMIN_USERNAME "${!GRAFANA_ADMIN_USERNAME}" \
|
|
--arg GRAFANA_ADMIN_PASSWORD "${!GRAFANA_ADMIN_PASSWORD}" \
|
|
--arg LIVEKIT_API_KEY "${!LIVEKIT_API_KEY}" \
|
|
--arg LIVEKIT_API_SECRET "${!LIVEKIT_API_SECRET}" \
|
|
--arg ENABLED_MODULES "${!ENABLED_MODULES}" \
|
|
--arg ALL_SECRETS_GENERATED "true" \
|
|
'$ARGS.named')"
|
|
SECRETS_VERIFIED=false
|
|
for SECRETS_WRITE_ATTEMPT in $(seq 1 30); do
|
|
CURRENT_SECRET="$(aws secretsmanager get-secret-value \
|
|
--region ${AWS::Region} \
|
|
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
|
|
--query SecretString --output text)"
|
|
if echo "$CURRENT_SECRET" | jq -e --argjson want "$SECRETS_WANTED" \
|
|
'. as $cur | $want | to_entries | all(.value == $cur[.key])' > /dev/null; then
|
|
SECRETS_VERIFIED=true
|
|
break
|
|
fi
|
|
aws secretsmanager update-secret \
|
|
--region ${AWS::Region} \
|
|
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
|
|
--secret-string "$(echo "$CURRENT_SECRET" | jq --argjson want "$SECRETS_WANTED" '. + $want')" > /dev/null 2>&1
|
|
sleep 2
|
|
done
|
|
if [[ "$SECRETS_VERIFIED" != "true" ]]; then
|
|
echo "Error: the shared secret could not be verified complete after 30 writes"
|
|
exit 1
|
|
fi
|
|
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')
|
|
|
|
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
|
|
|
|
# The WaitCondition is signalled by the UserData once OpenVidu is healthy
|
|
|
|
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')
|
|
|
|
# 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
|
|
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
|
|
|
|
# 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
|
|
|
|
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
|
|
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" "https://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"
|
|
|
|
# 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"
|
|
"--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
|
|
|
|
# Dedicated TURN Load Balancer (only when TurnDomainName and TurnCertificateARN are set,
|
|
# the same condition as the TURN Load Balancer resources)
|
|
if [[ "${TurnDomainName}" != "" && "${TurnCertificateARN}" != "" ]]; then
|
|
COMMON_ARGS+=("--turn-domain-name=${TurnDomainName}")
|
|
fi
|
|
|
|
# TURN UDP Load Balancer: each Media Node announces the Load Balancer IP of its zone
|
|
if [[ "${SubnetProcessor.TurnUDPEnabled}" == "true" ]]; then
|
|
COMMON_ARGS+=("--force-media-node-public-ip")
|
|
fi
|
|
|
|
# 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/registry_mirror.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
# Pull-through cache of Docker Hub for the Media Nodes. Only when the Media Nodes are
|
|
# in private subnets: they all reach Docker Hub from the same NAT IP, and Docker Hub
|
|
# rate-limits anonymous pulls per IP. Media Nodes with a public IP pull directly.
|
|
if [[ "${SubnetProcessor.MediaNodesPrivate}" != "true" ]]; then
|
|
exit 0
|
|
fi
|
|
REGISTRY_VERSION=v3.1.2
|
|
ARCH="$(dpkg --print-architecture)"
|
|
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o /tmp/registry.tar.gz \
|
|
"https://github.com/distribution/distribution/releases/download/${!REGISTRY_VERSION}/registry_${!REGISTRY_VERSION#v}_linux_${!ARCH}.tar.gz"
|
|
tar xzf /tmp/registry.tar.gz -C /usr/local/bin registry
|
|
rm -f /tmp/registry.tar.gz
|
|
mkdir -p /etc/registry-mirror
|
|
cat > /etc/registry-mirror/config.yml <<'EOF'
|
|
version: 0.1
|
|
log:
|
|
level: info
|
|
storage:
|
|
filesystem:
|
|
rootdirectory: /var/lib/registry-mirror
|
|
delete:
|
|
enabled: true
|
|
http:
|
|
addr: :5555
|
|
proxy:
|
|
remoteurl: https://registry-1.docker.io
|
|
ttl: 168h
|
|
EOF
|
|
cat > /etc/systemd/system/registry-mirror.service <<'EOF'
|
|
[Unit]
|
|
Description=Docker Hub pull-through cache for the OpenVidu Media Nodes
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
# No OpenTelemetry collector: do not export traces
|
|
Environment=OTEL_TRACES_EXPORTER=none
|
|
ExecStart=/usr/local/bin/registry serve /etc/registry-mirror/config.yml
|
|
# Unprivileged: a transient user that can only write its own cache
|
|
# (/var/lib/registry-mirror); 5555 needs no capabilities
|
|
DynamicUser=yes
|
|
StateDirectory=registry-mirror
|
|
CapabilityBoundingSet=
|
|
PrivateDevices=yes
|
|
ProtectKernelTunables=yes
|
|
ProtectKernelModules=yes
|
|
ProtectControlGroups=yes
|
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
RestrictNamespaces=yes
|
|
LockPersonality=yes
|
|
SystemCallArchitectures=native
|
|
SystemCallFilter=@system-service
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable --now registry-mirror
|
|
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
|
|
# 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}/"
|
|
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"'" }')"
|
|
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
|
|
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}/"
|
|
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"'" }')"
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
|
|
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
|
|
|
|
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
|
|
|
# Registry mirror for Media Nodes in private subnets (not required: they fall back to Docker Hub)
|
|
/usr/local/bin/registry_mirror.sh || echo "[OpenVidu] registry mirror not started"
|
|
|
|
export MASTER_NODE_NUM=1
|
|
|
|
# Report the result to CloudFormation: success only once OpenVidu is healthy on this node
|
|
signal_cfn() { cfn-signal -e "$1" --reason "$2" --stack ${AWS::StackId} --resource MasterNodesWaitCondition1 --region ${AWS::Region} || true; }
|
|
fail() { echo "[OpenVidu] $1"; signal_cfn 1 "Master Node 1: $1"; exit 1; }
|
|
trap 'fail "unexpected error at line $LINENO"' ERR
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition1" || fail "error installing OpenVidu"
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || fail "error configuring S3 bucket"
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || fail "error starting OpenVidu"
|
|
|
|
# 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
|
|
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
fail "OpenVidu not healthy after installing"
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || fail "error updating shared secret"
|
|
signal_cfn 0 "OpenVidu installed and healthy"
|
|
|
|
# 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
|
|
Count: '1'
|
|
|
|
OpenViduMasterNode2:
|
|
Type: AWS::EC2::Instance
|
|
DependsOn: MasterNode2PrivateIpParameter
|
|
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
|
|
|
|
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
|
|
|
|
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
|
|
|
# Registry mirror for Media Nodes in private subnets (not required: they fall back to Docker Hub)
|
|
/usr/local/bin/registry_mirror.sh || echo "[OpenVidu] registry mirror not started"
|
|
|
|
export MASTER_NODE_NUM=2
|
|
|
|
# Report the result to CloudFormation: success only once OpenVidu is healthy on this node
|
|
signal_cfn() { cfn-signal -e "$1" --reason "$2" --stack ${AWS::StackId} --resource MasterNodesWaitCondition2 --region ${AWS::Region} || true; }
|
|
fail() { echo "[OpenVidu] $1"; signal_cfn 1 "Master Node 2: $1"; exit 1; }
|
|
trap 'fail "unexpected error at line $LINENO"' ERR
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition2" || fail "error installing OpenVidu"
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || fail "error configuring S3 bucket"
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || fail "error starting OpenVidu"
|
|
|
|
# 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
|
|
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
fail "OpenVidu not healthy after installing"
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || fail "error updating shared secret"
|
|
signal_cfn 0 "OpenVidu installed and healthy"
|
|
|
|
# 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
|
|
Count: '1'
|
|
|
|
OpenViduMasterNode3:
|
|
Type: AWS::EC2::Instance
|
|
DependsOn: MasterNode3PrivateIpParameter
|
|
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
|
|
|
|
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
|
|
|
|
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
|
|
|
# Registry mirror for Media Nodes in private subnets (not required: they fall back to Docker Hub)
|
|
/usr/local/bin/registry_mirror.sh || echo "[OpenVidu] registry mirror not started"
|
|
|
|
export MASTER_NODE_NUM=3
|
|
|
|
# Report the result to CloudFormation: success only once OpenVidu is healthy on this node
|
|
signal_cfn() { cfn-signal -e "$1" --reason "$2" --stack ${AWS::StackId} --resource MasterNodesWaitCondition3 --region ${AWS::Region} || true; }
|
|
fail() { echo "[OpenVidu] $1"; signal_cfn 1 "Master Node 3: $1"; exit 1; }
|
|
trap 'fail "unexpected error at line $LINENO"' ERR
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition3" || fail "error installing OpenVidu"
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || fail "error configuring S3 bucket"
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || fail "error starting OpenVidu"
|
|
|
|
# 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
|
|
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
fail "OpenVidu not healthy after installing"
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || fail "error updating shared secret"
|
|
signal_cfn 0 "OpenVidu installed and healthy"
|
|
|
|
# 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
|
|
Count: '1'
|
|
|
|
OpenViduMasterNode4:
|
|
Type: AWS::EC2::Instance
|
|
DependsOn: MasterNode4PrivateIpParameter
|
|
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
|
|
|
|
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
|
|
|
|
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
|
|
|
# Registry mirror for Media Nodes in private subnets (not required: they fall back to Docker Hub)
|
|
/usr/local/bin/registry_mirror.sh || echo "[OpenVidu] registry mirror not started"
|
|
|
|
export MASTER_NODE_NUM=4
|
|
|
|
# Report the result to CloudFormation: success only once OpenVidu is healthy on this node
|
|
signal_cfn() { cfn-signal -e "$1" --reason "$2" --stack ${AWS::StackId} --resource MasterNodesWaitCondition4 --region ${AWS::Region} || true; }
|
|
fail() { echo "[OpenVidu] $1"; signal_cfn 1 "Master Node 4: $1"; exit 1; }
|
|
trap 'fail "unexpected error at line $LINENO"' ERR
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition4" || fail "error installing OpenVidu"
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || fail "error configuring S3 bucket"
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || fail "error starting OpenVidu"
|
|
|
|
# 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
|
|
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
fail "OpenVidu not healthy after installing"
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || fail "error updating shared secret"
|
|
signal_cfn 0 "OpenVidu installed and healthy"
|
|
|
|
# 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
|
|
Count: '1'
|
|
|
|
OpenViduMediaNodeLaunchTemplate:
|
|
Type: AWS::EC2::LaunchTemplate
|
|
Metadata:
|
|
Comment: Launch template for OpenVidu Media Node
|
|
AWS::CloudFormation::Init:
|
|
config:
|
|
files:
|
|
'/etc/openvidu/turn-udp-lb-dns':
|
|
Fn::If:
|
|
- TurnUDPIsEnabled
|
|
- content: !GetAtt TurnUDPLoadBalancer.DNSName
|
|
mode: '000644'
|
|
owner: root
|
|
group: root
|
|
- !Ref AWS::NoValue
|
|
'/usr/local/bin/install.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
YQ_VERSION=v4.53.6
|
|
|
|
# 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
|
|
|
|
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
|
|
|
|
# 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
|
|
|
|
# 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
|
|
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')
|
|
|
|
# Registry mirror of the Master Nodes, before the installer pulls the images
|
|
/usr/local/bin/config_registry_mirror.sh "$MASTER_NODE_1_PRIVATE_IP" "$MASTER_NODE_2_PRIVATE_IP" "$MASTER_NODE_3_PRIVATE_IP" "$MASTER_NODE_4_PRIVATE_IP" \
|
|
|| echo "Registry mirror not configured: pulling from Docker Hub"
|
|
|
|
# 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" "https://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"
|
|
|
|
# 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"
|
|
)
|
|
|
|
# TURN UDP Load Balancer: announce its public IP in this Availability Zone, so clients get
|
|
# turn:<that IP>:443 for TURN over UDP. Its zonal DNS name (<az>.<DNS name>) resolves to it.
|
|
# With cross-zone load balancing any of its IPs reaches every Media Node, so use any of
|
|
# them if this zone has none. Without it, clients fall back to TURN over TLS.
|
|
if [[ "${SubnetProcessor.TurnUDPEnabled}" == "true" ]]; then
|
|
TURN_UDP_LB_DNS="$(cat /etc/openvidu/turn-udp-lb-dns 2>/dev/null || true)"
|
|
AZ="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone)"
|
|
PUBLIC_IP=""
|
|
for i in $(seq 1 60); do
|
|
if [[ "$TURN_UDP_LB_DNS" == "" ]]; then
|
|
break
|
|
fi
|
|
PUBLIC_IP="$(getent ahostsv4 "$AZ.$TURN_UDP_LB_DNS" | awk 'NR == 1 { print $1 }')"
|
|
if [[ "$PUBLIC_IP" == "" ]]; then
|
|
PUBLIC_IP="$(getent ahostsv4 "$TURN_UDP_LB_DNS" | awk 'NR == 1 { print $1 }')"
|
|
fi
|
|
if [[ "$PUBLIC_IP" != "" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [[ "$PUBLIC_IP" == "" ]]; then
|
|
echo "TURN UDP Load Balancer IP not found: clients will use TURN over TLS"
|
|
PUBLIC_IP="$PRIVATE_IP"
|
|
fi
|
|
COMMON_ARGS+=("--public-ip=$PUBLIC_IP")
|
|
fi
|
|
|
|
# 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/config_registry_mirror.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
# Pull Docker Hub images through the registry mirror of the Master Nodes
|
|
# (registry_mirror.sh), only when this Media Node has no public IP. Docker falls
|
|
# back to Docker Hub if no mirror answers.
|
|
# Usage: config_registry_mirror.sh <master-1-ip> <master-2-ip> <master-3-ip> <master-4-ip>
|
|
if [[ "${SubnetProcessor.MediaNodesPrivate}" != "true" ]]; then
|
|
exit 0
|
|
fi
|
|
TOKEN="$(curl -sf -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"
|
|
if curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4 > /dev/null; then
|
|
exit 0
|
|
fi
|
|
MASTER_IPS=("$@")
|
|
# Wait up to 5 minutes for a mirror: on a new stack, the Master Nodes start it at boot
|
|
MIRROR_READY=false
|
|
for i in $(seq 1 60); do
|
|
for MASTER_IP in "${!MASTER_IPS[@]}"; do
|
|
if curl -sf --max-time 3 "http://$MASTER_IP:5555/v2/" > /dev/null; then
|
|
MIRROR_READY=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$MIRROR_READY" == "true" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [[ "$MIRROR_READY" != "true" ]]; then
|
|
echo "No registry mirror answers in the Master Nodes: pulling from Docker Hub"
|
|
exit 0
|
|
fi
|
|
MIRRORS="$(printf '%s:5555\n' "${!MASTER_IPS[@]}" | jq -R . | jq -s .)"
|
|
mkdir -p /etc/docker
|
|
if [[ ! -s /etc/docker/daemon.json ]]; then
|
|
echo '{}' > /etc/docker/daemon.json
|
|
fi
|
|
jq --argjson m "$MIRRORS" \
|
|
'."registry-mirrors" = ($m | map("http://" + .)) | ."insecure-registries" = ((."insecure-registries" // []) + $m | unique)' \
|
|
/etc/docker/daemon.json > /etc/docker/daemon.json.tmp
|
|
mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json
|
|
if systemctl is-active --quiet docker; then
|
|
systemctl restart docker
|
|
fi
|
|
echo "Pulling Docker Hub images through: $(jq -c '."registry-mirrors"' /etc/docker/daemon.json)"
|
|
mode: "000755"
|
|
owner: "root"
|
|
group: "root"
|
|
'/usr/local/bin/openvidu-media-health.sh':
|
|
content: |
|
|
#!/bin/bash
|
|
# OpenVidu Media Node health watchdog.
|
|
#
|
|
# Asks the cloud to replace this Media Node with a new one, keeping the number of
|
|
# Media Nodes, through /usr/local/bin/openvidu-media-replace.sh, when:
|
|
# - its bootstrap failed (the user-data wrote the bootstrap-failed marker): after a
|
|
# cool-down, so a persistent failure does not churn nodes and can be inspected,
|
|
# - its bootstrap did not finish BOOTSTRAP_DEADLINE_SEC after boot,
|
|
# - LiveKit fails its health check (HTTP 200 on PROBE_URL): for HARD_UNHEALTHY_AFTER_SEC
|
|
# while it is down (connection refused or closed), or for SOFT_UNHEALTHY_AFTER_SEC
|
|
# while it answers an error or times out. Soft failures are not counted while the CPU
|
|
# is saturated (an overloaded LiveKit reports Not Ready), and no failure is counted in
|
|
# the WARMUP_SEC after openvidu.service (re)started.
|
|
# Nothing is replaced while no master answers a Redis PING (MASTER_ADDRS): a master outage
|
|
# fails the health check of every Media Node at once, and a new node could not bootstrap
|
|
# either; LiveKit failures are not counted until a master answers again. Once requested,
|
|
# the replacement is requested again every REPLACE_RETRY_SEC until the node goes away.
|
|
#
|
|
# Settings: /etc/openvidu/media-health.env (sourced every cycle; MASTER_ADDRS="ip:port ...")
|
|
# Pause: touch /etc/openvidu/media-health.disabled (drain scripts use /run/openvidu-media-health.paused)
|
|
# Logs: journalctl -u openvidu-media-health
|
|
#
|
|
# This file is embedded verbatim in CloudFormation !Sub blocks, Bicep strings and
|
|
# Terraform heredocs, so it must never contain a dollar sign or a percent sign followed
|
|
# by an opening brace, nor two opening braces in a row.
|
|
|
|
STATE_DIR=/var/lib/openvidu-media-health
|
|
INSTALLED_MARKER="$STATE_DIR/installed"
|
|
BOOTSTRAP_FAILED_MARKER="$STATE_DIR/bootstrap-failed"
|
|
REPLACE_SCRIPT=/usr/local/bin/openvidu-media-replace.sh
|
|
ENV_FILE=/etc/openvidu/media-health.env
|
|
PAUSE_FILE=/etc/openvidu/media-health.disabled
|
|
DRAIN_PAUSE_FILE=/run/openvidu-media-health.paused
|
|
|
|
log() { echo "[media-health] $*"; }
|
|
|
|
# Seconds since boot: monotonic, unaffected by clock steps
|
|
uptime_sec() { cut -d. -f1 /proc/uptime; }
|
|
|
|
# Busy CPU percentage since the previous call, in BUSY (0 on the first call)
|
|
cpu_busy() {
|
|
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ < /proc/stat
|
|
total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
|
|
idle=$((c_idle + c_iowait))
|
|
BUSY=0
|
|
if [ -n "$PREV_TOTAL" ] && [ "$total" -gt "$PREV_TOTAL" ]; then
|
|
BUSY=$((100 * (total - PREV_TOTAL - idle + PREV_IDLE) / (total - PREV_TOTAL)))
|
|
fi
|
|
PREV_TOTAL=$total
|
|
PREV_IDLE=$idle
|
|
}
|
|
|
|
# openvidu.service is running and entered the active state less than WARMUP_SEC ago
|
|
# (a stopped or failed openvidu.service is not starting: no warm-up for it)
|
|
openvidu_starting() {
|
|
case "$(systemctl show -p ActiveState --value openvidu 2>/dev/null)" in
|
|
active|activating|reloading) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
since=$(systemctl show -p ActiveEnterTimestampMonotonic --value openvidu 2>/dev/null)
|
|
case "$since" in ''|0|*[!0-9]*) return 1 ;; esac
|
|
[ $(($(uptime_sec) - since / 1000000)) -lt "$WARMUP_SEC" ]
|
|
}
|
|
|
|
# A master answers a Redis PING (+PONG, or -NOAUTH when a password is required). A bare
|
|
# TCP connect would also succeed against a wedged Redis.
|
|
master_serving() {
|
|
[ -n "$MASTER_ADDRS" ] || return 1
|
|
for addr in $MASTER_ADDRS; do
|
|
host=$(echo "$addr" | cut -d: -f1)
|
|
port=$(echo "$addr" | cut -d: -f2)
|
|
reply=$(timeout 5 bash -c 'exec 3<>"/dev/tcp/$0/$1" && printf "PING\r\n" >&3 && head -c 7 <&3' "$host" "$port" 2>/dev/null)
|
|
case "$reply" in
|
|
+PONG*|-NOAUTH*) return 0 ;;
|
|
esac
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Request the replacement of this node ($1 = reason). Gated on a master answering, except
|
|
# in the bootstrap phase before the master addresses are known.
|
|
request_replacement() {
|
|
now=$(uptime_sec)
|
|
if [ -n "$REQUESTED_AT" ] && [ $((now - REQUESTED_AT)) -lt "$REPLACE_RETRY_SEC" ]; then
|
|
return
|
|
fi
|
|
if [ -n "$MASTER_ADDRS" ] || [ -f "$INSTALLED_MARKER" ]; then
|
|
if ! master_serving; then
|
|
if [ -z "$HOLD_LOGGED_AT" ] || [ $((now - HOLD_LOGGED_AT)) -ge 600 ]; then
|
|
log "$1, but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not replacing this node"
|
|
HOLD_LOGGED_AT=$now
|
|
fi
|
|
return
|
|
fi
|
|
fi
|
|
log "$1: requesting the replacement of this node"
|
|
if "$REPLACE_SCRIPT" "$1"; then
|
|
REQUESTED_AT=$now
|
|
log "replacement requested"
|
|
else
|
|
log "the replacement request failed, retrying in $INTERVAL_SEC s"
|
|
fi
|
|
}
|
|
|
|
mkdir -p "$STATE_DIR"
|
|
REQUESTED_AT=""
|
|
HOLD_LOGGED_AT=""
|
|
FAILED_SEEN_AT=""
|
|
FAIL_SINCE=""
|
|
SATURATED=""
|
|
PREV_TOTAL=""
|
|
PREV_IDLE=""
|
|
log "started"
|
|
|
|
while true; do
|
|
PROBE_URL=http://127.0.0.1:7880/
|
|
INTERVAL_SEC=30
|
|
HARD_UNHEALTHY_AFTER_SEC=300
|
|
SOFT_UNHEALTHY_AFTER_SEC=600
|
|
CPU_SATURATED_PCT=90
|
|
WARMUP_SEC=900
|
|
BOOTSTRAP_DEADLINE_SEC=5400
|
|
BOOTSTRAP_FAILURE_COOLDOWN_SEC=600
|
|
REPLACE_RETRY_SEC=600
|
|
MASTER_ADDRS=""
|
|
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
|
|
NOW=$(uptime_sec)
|
|
cpu_busy
|
|
|
|
if [ -f "$PAUSE_FILE" ] || [ -f "$DRAIN_PAUSE_FILE" ]; then
|
|
FAIL_SINCE=""
|
|
elif [ -f "$BOOTSTRAP_FAILED_MARKER" ]; then
|
|
if [ -z "$FAILED_SEEN_AT" ]; then
|
|
FAILED_SEEN_AT=$NOW
|
|
log "bootstrap failed ($(head -c 300 "$BOOTSTRAP_FAILED_MARKER")): replacing this node in $BOOTSTRAP_FAILURE_COOLDOWN_SEC s"
|
|
fi
|
|
if [ $((NOW - FAILED_SEEN_AT)) -ge "$BOOTSTRAP_FAILURE_COOLDOWN_SEC" ]; then
|
|
request_replacement "bootstrap failed: $(head -c 300 "$BOOTSTRAP_FAILED_MARKER")"
|
|
fi
|
|
elif [ ! -f "$INSTALLED_MARKER" ]; then
|
|
if [ "$NOW" -ge "$BOOTSTRAP_DEADLINE_SEC" ]; then
|
|
request_replacement "bootstrap not finished $BOOTSTRAP_DEADLINE_SEC s after boot"
|
|
fi
|
|
elif curl -sf -o /dev/null --connect-timeout 3 --max-time 15 "$PROBE_URL"; then
|
|
[ -n "$FAIL_SINCE" ] && log "LiveKit healthy again after $((NOW - FAIL_SINCE)) s"
|
|
FAIL_SINCE=""
|
|
SATURATED=""
|
|
else
|
|
RC=$?
|
|
case "$RC" in
|
|
7|52|56) KIND=hard ;;
|
|
*) KIND=soft ;;
|
|
esac
|
|
if openvidu_starting; then
|
|
FAIL_SINCE=""
|
|
elif ! master_serving; then
|
|
# A master outage fails the health check of every Media Node at once: not counted,
|
|
# so LiveKit gets the whole window to reconnect once a master answers again
|
|
FAIL_SINCE=""
|
|
if [ -z "$HOLD_LOGGED_AT" ] || [ $((NOW - HOLD_LOGGED_AT)) -ge 600 ]; then
|
|
log "LiveKit health check failed (curl exit $RC), but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not counted"
|
|
HOLD_LOGGED_AT=$NOW
|
|
fi
|
|
elif [ "$KIND" = soft ] && [ "$BUSY" -ge "$CPU_SATURATED_PCT" ]; then
|
|
[ -n "$SATURATED" ] || log "LiveKit not ready (curl exit $RC) with the CPU $BUSY% busy: not counted"
|
|
SATURATED=1
|
|
FAIL_SINCE=""
|
|
else
|
|
SATURATED=""
|
|
if [ -z "$FAIL_SINCE" ]; then
|
|
FAIL_SINCE=$NOW
|
|
log "LiveKit health check failed (curl exit $RC)"
|
|
fi
|
|
LIMIT=$SOFT_UNHEALTHY_AFTER_SEC
|
|
[ "$KIND" = hard ] && LIMIT=$HARD_UNHEALTHY_AFTER_SEC
|
|
if [ $((NOW - FAIL_SINCE)) -ge "$LIMIT" ]; then
|
|
request_replacement "LiveKit unhealthy for $((NOW - FAIL_SINCE)) s (curl exit $RC)"
|
|
fi
|
|
fi
|
|
fi
|
|
sleep "$INTERVAL_SEC"
|
|
done
|
|
mode: "000755"
|
|
owner: "root"
|
|
group: "root"
|
|
'/usr/local/bin/openvidu-media-replace.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
# Ask the Auto Scaling group to replace this Media Node with a new one, keeping the
|
|
# count. Called by openvidu-media-health.sh (same cgroup): it never stops or signals
|
|
# the watchdog, which re-requests until this node goes away.
|
|
mkdir -p /var/lib/openvidu-media-health
|
|
echo "$(date -u +%FT%TZ) $1" >> /var/lib/openvidu-media-health/replace-requests.log
|
|
# Let stop_media_node.sh know this node is unhealthy, so it does not wait for sessions
|
|
touch /run/openvidu-unhealthy
|
|
# Withdrawn if the request fails: the node then stays, and if it recovers
|
|
# a later scale-in must still wait for its sessions
|
|
trap '[ $? -eq 0 ] || rm -f /run/openvidu-unhealthy' EXIT
|
|
|
|
# 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-health Unhealthy makes the ASG terminate this node through the existing
|
|
# lifecycle hook (drain) and launch a replacement. Idempotent; retry transient API errors.
|
|
for attempt in $(seq 1 5); do
|
|
if aws autoscaling set-instance-health \
|
|
--region ${AWS::Region} \
|
|
--instance-id "$INSTANCE_ID" \
|
|
--health-status Unhealthy; then
|
|
exit 0
|
|
fi
|
|
echo "[openvidu-media-replace] set-instance-health failed (attempt $attempt/5), retrying in 12s" >&2
|
|
sleep 12
|
|
done
|
|
exit 1
|
|
mode: "000755"
|
|
owner: "root"
|
|
group: "root"
|
|
'/etc/systemd/system/openvidu-media-health.service':
|
|
content: |
|
|
[Unit]
|
|
Description=OpenVidu Media Node health watchdog
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/openvidu-media-health.sh
|
|
Restart=always
|
|
RestartSec=30
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
mode: "000644"
|
|
owner: "root"
|
|
group: "root"
|
|
'/usr/local/bin/stop_media_node.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
# Drain: pause and stop the health watchdog so it does not replace this node
|
|
touch /run/openvidu-media-health.paused
|
|
systemctl stop openvidu-media-health 2>/dev/null || true
|
|
# 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
|
|
TIME_PASSED=0
|
|
HEARTBEAT_MAX=1800
|
|
# An unhealthy node being replaced should not wait for sessions: kill now
|
|
if [ -f /run/openvidu-unhealthy ]; then
|
|
docker ps -q | xargs -r docker kill >/dev/null 2>&1 || true
|
|
fi
|
|
# 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
|
|
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]
|
|
KeyName: !Ref KeyName
|
|
InstanceType: !Ref MediaNodeInstanceType
|
|
UserData:
|
|
Fn::Base64: !Sub |
|
|
#!/bin/bash
|
|
set -eu -o pipefail
|
|
|
|
# Record a media-node bootstrap failure so the health watchdog (started below)
|
|
# replaces this node after a cool-down. Route user-data failure exits through this.
|
|
ov_media_bootstrap_failed() {
|
|
echo "[OpenVidu] media node bootstrap failed: $1"
|
|
mkdir -p /var/lib/openvidu-media-health
|
|
echo "$1" > /var/lib/openvidu-media-health/bootstrap-failed
|
|
exit 1
|
|
}
|
|
|
|
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
|
|
|
|
apt-get update && apt-get install -y \
|
|
python3-pip \
|
|
ec2-instance-connect \
|
|
curl \
|
|
unzip
|
|
|
|
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
|
|
|
|
cfn-init --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMediaNodeLaunchTemplate
|
|
|
|
export HOME="/root"
|
|
|
|
# aws-cli v2, hoisted from install.sh so the watchdog's replace script can call
|
|
# the ASG API even if bootstrap hangs before install.sh runs
|
|
AWS_CLI_VERSION=2.36.40
|
|
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
|
|
|
|
# Start the health watchdog before install.sh so a hung bootstrap is covered too
|
|
systemctl daemon-reload
|
|
systemctl enable --now openvidu-media-health
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh || ov_media_bootstrap_failed "install.sh failed"
|
|
|
|
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')
|
|
|
|
# Health watchdog config: master addresses for the anti-storm Redis PING guard
|
|
mkdir -p /etc/openvidu
|
|
MASTER_ADDRS=""
|
|
for MASTER_IP in "$MASTER_IP_1" "$MASTER_IP_2" "$MASTER_IP_3" "$MASTER_IP_4"; do
|
|
if [ "$MASTER_IP" != "none" ] && [ -n "$MASTER_IP" ]; then
|
|
MASTER_ADDRS="$MASTER_ADDRS $MASTER_IP:7001"
|
|
fi
|
|
done
|
|
echo "MASTER_ADDRS=\"$(echo $MASTER_ADDRS | xargs)\"" > /etc/openvidu/media-health.env
|
|
|
|
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
|
|
ov_media_bootstrap_failed "no master node became healthy after 30 minutes"
|
|
fi
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || ov_media_bootstrap_failed "error starting OpenVidu"
|
|
|
|
# Bootstrap finished: switch the watchdog from the bootstrap deadline to runtime checks
|
|
mkdir -p /var/lib/openvidu-media-health
|
|
touch /var/lib/openvidu-media-health/installed
|
|
|
|
# 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]
|
|
|
|
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
|
|
- !If [TurnTLSIsEnabled, !Ref OpenViduTurnMediaNodeTG, !Ref AWS::NoValue]
|
|
- !If [TurnUDPIsEnabled, !Ref OpenViduTurnUDPMediaNodeTG, !Ref AWS::NoValue]
|
|
MinSize: !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !Ref MinNumberOfMediaNodes]
|
|
MaxSize: !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !Ref MaxNumberOfMediaNodes]
|
|
DesiredCapacity: !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !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}
|
|
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
|
|
Condition: AutoscalingEnabled
|
|
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
|
|
|
|
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
|
|
|
|
OpenViduMediaNodeToMasterRegistryMirrorIngress:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMasterNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 5555
|
|
ToPort: 5555
|
|
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
|
|
|
|
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:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMasterNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 9080
|
|
ToPort: 9080
|
|
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
|
|
|
|
OpenViduMasterToMasterCustomAppIngress:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMasterNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 6080
|
|
ToPort: 6080
|
|
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
|
|
|
|
OpenViduMediaNodeToMasterMeetWebhookIngress:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
|
|
IpProtocol: tcp
|
|
FromPort: 9080
|
|
ToPort: 9080
|
|
SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId
|
|
|
|
OpenViduMediaNodeToMasterCustomAppIngress:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
|
|
IpProtocol: tcp
|
|
FromPort: 6080
|
|
ToPort: 6080
|
|
SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId
|
|
|
|
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
|
|
- 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
|
|
|
|
OpenViduLoadBalancerToMediaNodeRTMPIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 1935
|
|
ToPort: 1935
|
|
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
|
|
|
|
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: 1945
|
|
ToPort: 1945
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 1945
|
|
ToPort: 1945
|
|
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]
|
|
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: !If [TurnTLSIsEnabled, !Ref OpenViduMasterNodeTG, !Ref OpenViduMasterNodeWithTurnTLSTG]
|
|
LoadBalancerArn: !Ref LoadBalancer
|
|
Port: 443
|
|
Protocol: TLS
|
|
Certificates:
|
|
- CertificateArn: !Ref OpenViduCertificateARN
|
|
|
|
OpenViduRTMPMediaNodeListener:
|
|
Type: 'AWS::ElasticLoadBalancingV2::Listener'
|
|
Properties:
|
|
DefaultActions:
|
|
- Type: forward
|
|
TargetGroupArn: !Ref OpenViduMediaNodeRTMPTG
|
|
LoadBalancerArn: !Ref LoadBalancer
|
|
# RTMPS on 1945: the port OpenVidu advertises in the RTMP Ingress URL behind a load balancer
|
|
Port: 1945
|
|
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
|
|
|
|
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
|
|
- ''
|
|
- - OVRTMPS-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
VpcId: !Ref OpenViduVPC
|
|
# RTMP port of the ingress service on the Media Nodes
|
|
Port: 1935
|
|
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
|
|
|
|
# Master Node HTTP (Caddy 7880) for the main 443 listener when TurnDomainName is set.
|
|
OpenViduMasterNodeTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
- ''
|
|
- - OpenVidu-
|
|
- !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: 7880
|
|
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 Target Group
|
|
|
|
# Dedicated TURN Load Balancer (only when TurnDomainName + TurnCertificateARN are set).
|
|
# Terminates TLS on 443 and forwards to the Media Nodes' TURN TLS port 5349, so TURN
|
|
# over TLS is relayed by the Media Nodes themselves instead of going through the
|
|
# Master Nodes.
|
|
TurnLoadBalancerSG:
|
|
Type: AWS::EC2::SecurityGroup
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
GroupDescription: Security group for the dedicated TURN Load Balancer
|
|
GroupName: !Sub openvidu-ha-turn-lb-sg-${AWS::Region}-${AWS::StackName}
|
|
VpcId: !Ref OpenViduVPC
|
|
SecurityGroupIngress:
|
|
- IpProtocol: tcp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
CidrIpv6: ::/0
|
|
- !If
|
|
- TurnUDPIsEnabled
|
|
- IpProtocol: udp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
CidrIp: 0.0.0.0/0
|
|
- !Ref AWS::NoValue
|
|
|
|
TurnLoadBalancerToMediaNode5349Ingress:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 5349
|
|
ToPort: 5349
|
|
SourceSecurityGroupId: !Ref TurnLoadBalancerSG
|
|
|
|
TurnLoadBalancer:
|
|
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
- ''
|
|
- - OpenViduHA-Turn-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
Subnets: !If [HasLoadBalancerSubnets, !Ref LoadBalancerSubnets, !Ref OpenViduMediaNodeSubnets]
|
|
SecurityGroups:
|
|
- !Ref TurnLoadBalancerSG
|
|
Type: network
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN Load Balancer
|
|
|
|
OpenViduTurnMediaNodeTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
- ''
|
|
- - OVTurnMedia-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
VpcId: !Ref OpenViduVPC
|
|
Port: 5349
|
|
Protocol: TCP
|
|
HealthCheckProtocol: TCP
|
|
HealthCheckPort: '5349'
|
|
HealthCheckIntervalSeconds: 10
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 3
|
|
UnhealthyThresholdCount: 4
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN Media Target Group
|
|
|
|
TurnMediaNodeListener:
|
|
Type: 'AWS::ElasticLoadBalancingV2::Listener'
|
|
Condition: TurnTLSIsEnabled
|
|
Properties:
|
|
DefaultActions:
|
|
- Type: forward
|
|
TargetGroupArn: !Ref OpenViduTurnMediaNodeTG
|
|
LoadBalancerArn: !Ref TurnLoadBalancer
|
|
Port: 443
|
|
Protocol: TLS
|
|
Certificates:
|
|
- CertificateArn: !Ref TurnCertificateARN
|
|
|
|
# TURN over UDP 443 (only with Media Nodes outside the Load Balancer subnets). A second Load Balancer,
|
|
# because an NLB cannot have a UDP and a TLS listener on the same port. It keeps the static IP that
|
|
# AWS assigns to it in each zone, and the Media Nodes announce the one of their zone to the clients.
|
|
TurnUDPLoadBalancer:
|
|
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
|
Condition: TurnUDPIsEnabled
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
- ''
|
|
- - OpenViduHA-TurnUDP-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
Scheme: internet-facing
|
|
Subnets: !Ref LoadBalancerSubnets
|
|
SecurityGroups:
|
|
- !Ref TurnLoadBalancerSG
|
|
LoadBalancerAttributes:
|
|
- Key: load_balancing.cross_zone.enabled
|
|
Value: 'true'
|
|
Type: network
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN UDP Load Balancer
|
|
|
|
OpenViduTurnUDPMediaNodeTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Condition: TurnUDPIsEnabled
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
- ''
|
|
- - OVTurnUDP-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
VpcId: !Ref OpenViduVPC
|
|
Port: 443
|
|
Protocol: UDP
|
|
HealthCheckProtocol: TCP
|
|
HealthCheckPort: '5349'
|
|
HealthCheckIntervalSeconds: 10
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 3
|
|
UnhealthyThresholdCount: 4
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN UDP Media Target Group
|
|
|
|
TurnUDPMediaNodeListener:
|
|
Type: 'AWS::ElasticLoadBalancingV2::Listener'
|
|
Condition: TurnUDPIsEnabled
|
|
Properties:
|
|
DefaultActions:
|
|
- Type: forward
|
|
TargetGroupArn: !Ref OpenViduTurnUDPMediaNodeTG
|
|
LoadBalancerArn: !Ref TurnUDPLoadBalancer
|
|
Port: 443
|
|
Protocol: UDP
|
|
|
|
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}
|
|
LoadBalancerDNS:
|
|
Description: DNS of the main Load Balancer - point DomainName (A/ALIAS) here
|
|
Value: !GetAtt LoadBalancer.DNSName
|
|
|
|
TurnLoadBalancerDNS:
|
|
Condition: TurnTLSIsEnabled
|
|
Description: DNS of the dedicated TURN Load Balancer - point TurnDomainName (A/ALIAS) here
|
|
Value: !GetAtt TurnLoadBalancer.DNSName
|
|
|
|
TurnUDPLoadBalancerDNS:
|
|
Condition: TurnUDPIsEnabled
|
|
Description: DNS of the TURN UDP Load Balancer (no record needed; clients send media over UDP 443 to the IPs it resolves to)
|
|
Value: !GetAtt TurnUDPLoadBalancer.DNSName
|