mirror of https://github.com/OpenVidu/openvidu.git
3814 lines
144 KiB
YAML
3814 lines
144 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
|
|
|
|
MinNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 1
|
|
Description: Minimum number of media nodes to deploy
|
|
|
|
MaxNumberOfMediaNodes:
|
|
Type: Number
|
|
Default: 5
|
|
Description: Maximum number of media nodes to deploy
|
|
|
|
ScaleTargetCPU:
|
|
Type: Number
|
|
Default: 50
|
|
Description: Target CPU percentage to scale up or down
|
|
|
|
S3AppDataBucketName:
|
|
Type: String
|
|
Description: Name of the S3 bucket to store data and recordings. If empty, a bucket will be created
|
|
|
|
S3ClusterDataBucketName:
|
|
Type: String
|
|
Description: Name of the S3 bucket to store cluster data. If empty, a bucket will be created
|
|
|
|
AdditionalInstallFlags:
|
|
Description: Additional optional flags to pass to the OpenVidu installer (comma-separated, e.g., "--flag1=value, --flag2").
|
|
Type: String
|
|
Default: ""
|
|
AllowedPattern: '^[A-Za-z0-9, =_.\-]*$' # Allows letters, numbers, comma, space, underscore, dot, equals, and hyphen
|
|
ConstraintDescription: Must be a comma-separated list of flags (for example, --flag=value, --bool-flag).
|
|
|
|
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."
|
|
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: 100
|
|
MinValue: 50
|
|
ConstraintDescription: The disk size must be at least 50 GB
|
|
|
|
Metadata:
|
|
'AWS::CloudFormation::Interface':
|
|
ParameterGroups:
|
|
- Label:
|
|
default: Domain and Load Blancer configuration
|
|
Parameters:
|
|
- DomainName
|
|
- OpenViduCertificateARN
|
|
- 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:
|
|
- 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, ""]
|
|
HasLoadBalancerSubnets: !Not [!Equals [!Join ["", !Ref LoadBalancerSubnets], ""]]
|
|
TurnTLSIsEnabled: !And [!Not [!Equals [!Ref TurnDomainName, ""]], !Not [!Equals [!Ref TurnCertificateARN, ""]]]
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
# sending the signal call
|
|
cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region}
|
|
|
|
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" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
|
|
if [ ! -s "$INSTALLER_SCRIPT" ]; then
|
|
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
|
|
exit 1
|
|
fi
|
|
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
|
|
|
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
|
|
OPENVIDU_READY=false
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
echo "[OpenVidu] not healthy after 300s, restarting once"
|
|
systemctl restart openvidu || true
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
|
|
|
|
# Launch on reboot
|
|
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
|
|
|
|
|
|
MasterNodesWaitCondition1:
|
|
Type: 'AWS::CloudFormation::WaitCondition'
|
|
CreationPolicy:
|
|
ResourceSignal:
|
|
Timeout: PT20M
|
|
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
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
|
|
|
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
|
|
OPENVIDU_READY=false
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
echo "[OpenVidu] not healthy after 300s, restarting once"
|
|
systemctl restart openvidu || true
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
|
|
|
|
# Launch on reboot
|
|
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
|
|
|
|
|
|
MasterNodesWaitCondition2:
|
|
Type: 'AWS::CloudFormation::WaitCondition'
|
|
CreationPolicy:
|
|
ResourceSignal:
|
|
Timeout: PT20M
|
|
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
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
|
|
|
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
|
|
OPENVIDU_READY=false
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
echo "[OpenVidu] not healthy after 300s, restarting once"
|
|
systemctl restart openvidu || true
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
|
|
|
|
# Launch on reboot
|
|
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
|
|
|
|
|
|
MasterNodesWaitCondition3:
|
|
Type: 'AWS::CloudFormation::WaitCondition'
|
|
CreationPolicy:
|
|
ResourceSignal:
|
|
Timeout: PT20M
|
|
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
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
|
|
|
|
# Config S3 bucket
|
|
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
|
|
|
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
|
|
OPENVIDU_READY=false
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [ "$OPENVIDU_READY" != "true" ]; then
|
|
echo "[OpenVidu] not healthy after 300s, restarting once"
|
|
systemctl restart openvidu || true
|
|
for i in $(seq 1 60); do
|
|
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
|
|
OPENVIDU_READY=true
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
# Update shared secret
|
|
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
|
|
|
|
# Launch on reboot
|
|
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
|
|
|
|
MasterNodesWaitCondition4:
|
|
Type: 'AWS::CloudFormation::WaitCondition'
|
|
CreationPolicy:
|
|
ResourceSignal:
|
|
Timeout: PT20M
|
|
Count: '1'
|
|
|
|
OpenViduMediaNodeLaunchTemplate:
|
|
Type: AWS::EC2::LaunchTemplate
|
|
Metadata:
|
|
Comment: Launch template for OpenVidu Media Node
|
|
AWS::CloudFormation::Init:
|
|
config:
|
|
files:
|
|
'/usr/local/bin/install.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
YQ_VERSION=v4.53.6
|
|
|
|
# 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" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
|
|
if [ ! -s "$INSTALLER_SCRIPT" ]; then
|
|
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
|
|
exit 1
|
|
fi
|
|
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
|
|
|
|
# Common arguments
|
|
COMMON_ARGS=(
|
|
"--no-tty"
|
|
"--install"
|
|
"--environment=aws"
|
|
"--deployment-type='ha'"
|
|
"--node-role='media-node'"
|
|
"--master-node-private-ip-list=$MASTER_NODE_PRIVATE_IP_LIST"
|
|
"--private-ip=$PRIVATE_IP"
|
|
"--redis-password=$REDIS_PASSWORD"
|
|
)
|
|
|
|
# Construct the final command with all arguments
|
|
FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}")"
|
|
|
|
# Install OpenVidu
|
|
exec bash -c "$FINAL_COMMAND"
|
|
mode: '000755'
|
|
owner: root
|
|
group: root
|
|
'/usr/local/bin/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/set_as_unhealthy.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
# Token for IMDSv2
|
|
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
|
|
|
|
# Get own instance ID
|
|
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
|
|
|
|
# Set instance as unhealthy
|
|
aws autoscaling set-instance-health \
|
|
--region ${AWS::Region} \
|
|
--instance-id "$INSTANCE_ID" \
|
|
--health-status Unhealthy
|
|
mode: "000755"
|
|
owner: "root"
|
|
group: "root"
|
|
'/usr/local/bin/stop_media_node.sh':
|
|
content: !Sub |
|
|
#!/bin/bash
|
|
set -e
|
|
# Token for IMDSv2
|
|
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
|
|
|
|
# Get own instance ID
|
|
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
|
|
ASG_NAME=openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
|
|
|
|
# Execute if docker is installed
|
|
if [ -x "$(command -v docker)" ]; then
|
|
echo "Stopping media node services and waiting for termination..."
|
|
docker container kill --signal=SIGQUIT openvidu 2>/dev/null || true
|
|
docker container kill --signal=SIGQUIT ingress 2>/dev/null || true
|
|
docker container kill --signal=SIGQUIT egress 2>/dev/null || true
|
|
for agent_container in $(docker ps --filter "label=openvidu-agent=true" --format '{{.Names}}' 2>/dev/null); do
|
|
docker container kill --signal=SIGQUIT "$agent_container" 2>/dev/null || true
|
|
done
|
|
TIME_PASSED=0
|
|
HEARTBEAT_MAX=1800
|
|
# Wait for running containers to not be openvidu, ingress, egress or an openvidu agent
|
|
while [ "$(docker ps --filter 'label=openvidu-agent=true' -q 2>/dev/null | wc -l)" -gt 0 ] || \
|
|
[ "$(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null)" = "true" ] || \
|
|
[ "$(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null)" = "true" ] || \
|
|
[ "$(docker inspect -f '{{.State.Running}}' egress 2>/dev/null)" = "true" ]; do
|
|
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
|
|
|
|
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 --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMediaNodeLaunchTemplate
|
|
|
|
export HOME="/root"
|
|
|
|
# Install OpenVidu
|
|
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
|
|
|
|
MASTER_IP_1=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
|
|
MASTER_IP_2=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
|
|
MASTER_IP_3=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
|
|
MASTER_IP_4=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
|
|
MASTER_HEALTHY=false
|
|
for i in $(seq 1 360); do
|
|
for MASTER_IP in "$MASTER_IP_1" "$MASTER_IP_2" "$MASTER_IP_3" "$MASTER_IP_4"; do
|
|
if [ "$MASTER_IP" != "none" ] && [ -n "$MASTER_IP" ] && curl -sf "http://$MASTER_IP:7880/health/caddy" >/dev/null 2>&1; then
|
|
MASTER_HEALTHY=true
|
|
break
|
|
fi
|
|
done
|
|
if [ "$MASTER_HEALTHY" = "true" ]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
if [ "$MASTER_HEALTHY" != "true" ]; then
|
|
echo "[OpenVidu] no master node became healthy after 30 minutes"
|
|
/usr/local/bin/set_as_unhealthy.sh
|
|
exit 1
|
|
fi
|
|
|
|
# Start OpenVidu
|
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
|
|
|
|
# Wait for the app
|
|
# /usr/local/bin/check_app_ready.sh
|
|
BlockDeviceMappings:
|
|
- DeviceName: /dev/sda1
|
|
Ebs:
|
|
VolumeType: gp3
|
|
DeleteOnTermination: true
|
|
VolumeSize: !If [IsMediaNodeNvidia, 100, 50]
|
|
|
|
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]
|
|
MinSize: !Ref MinNumberOfMediaNodes
|
|
MaxSize: !Ref MaxNumberOfMediaNodes
|
|
DesiredCapacity: !Ref InitialNumberOfMediaNodes
|
|
VPCZoneIdentifier: !Ref OpenViduMediaNodeSubnets
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - Media Node
|
|
PropagateAtLaunch: true
|
|
|
|
StopMediaNodeLifecycleHook:
|
|
Type: 'AWS::AutoScaling::LifecycleHook'
|
|
Properties:
|
|
LifecycleHookName: !Sub StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName}
|
|
AutoScalingGroupName: !Ref OpenViduMediaNodeASG
|
|
LifecycleTransition: 'autoscaling:EC2_INSTANCE_TERMINATING'
|
|
DefaultResult: 'CONTINUE'
|
|
HeartbeatTimeout: 3600
|
|
|
|
StopMediaNodeDocumentRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service:
|
|
- ssm.amazonaws.com
|
|
Action:
|
|
- sts:AssumeRole
|
|
Policies:
|
|
- PolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- ssm:DescribeInstanceInformation
|
|
- ssm:ListCommands
|
|
- ssm:ListCommandInvocations
|
|
Resource: "*"
|
|
- Effect: Allow
|
|
Action:
|
|
- ssm:SendCommand
|
|
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::document/AWS-RunShellScript
|
|
- Action:
|
|
- ssm:SendCommand
|
|
Resource: !Sub arn:${AWS::Partition}:ec2:*:*:instance/*
|
|
Condition:
|
|
StringEquals:
|
|
'aws:ResourceTag/aws:cloudformation:stack-name': !Ref 'AWS::StackName'
|
|
Effect: Allow
|
|
PolicyName: SSM-Automation-Policy
|
|
|
|
StopMediaNodeAutomationDocument:
|
|
Type: AWS::SSM::Document
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique document name
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - 'StopMediaNodeAutomationDocument-'
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
DocumentType: Automation
|
|
Content:
|
|
schemaVersion: '0.3'
|
|
assumeRole: "{{automationAssumeRole}}"
|
|
description: This document stops the OpenVidu services in a Media Node and
|
|
terminates the instance. It stop the OpenVidu services without interrupting
|
|
the running sessions, ingress and egress running in the Media Node.
|
|
Also it sends a CONTINUE signal to the Auto Scaling Group to continue the
|
|
instance termination when the services are stopped.
|
|
parameters:
|
|
InstanceId:
|
|
type: String
|
|
automationAssumeRole:
|
|
type: String
|
|
default: !GetAtt StopMediaNodeDocumentRole.Arn
|
|
description: "(Required) The ARN of the role that allows Automation to
|
|
perform the actions."
|
|
mainSteps:
|
|
- name: RunCommand
|
|
action: aws:runCommand
|
|
inputs:
|
|
DocumentName: AWS-RunShellScript
|
|
InstanceIds:
|
|
- "{{ InstanceId }}"
|
|
Parameters:
|
|
# 24 hours as a timeout to wait for the instance to stop all services
|
|
executionTimeout: "60"
|
|
commands:
|
|
- nohup /usr/local/bin/stop_media_node.sh > /var/log/stop_media_node.log 2>&1 &
|
|
|
|
|
|
StopMediaNodeCloudWatchEventRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service:
|
|
- events.amazonaws.com
|
|
Action:
|
|
- sts:AssumeRole
|
|
Policies:
|
|
- PolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- ssm:StartAutomationExecution
|
|
Resource:
|
|
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-execution/*
|
|
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:document/${StopMediaNodeAutomationDocument}
|
|
PolicyName: !Sub StopMediaNodeCloudWatchEventPolicy-${AWS::Region}-${AWS::StackName}
|
|
- PolicyDocument:
|
|
Version: '2012-10-17'
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- iam:PassRole
|
|
Resource: !GetAtt StopMediaNodeDocumentRole.Arn
|
|
PolicyName: Pass-Role-SSM-Automation-Policy
|
|
|
|
StopMediaNodeCloudWatchEventRule:
|
|
Type: AWS::Events::Rule
|
|
Properties:
|
|
Description: Rule to trigger the StopMediaNodeAutomationDocument when an instance is terminated
|
|
EventPattern:
|
|
source:
|
|
- aws.autoscaling
|
|
detail-type:
|
|
- EC2 Instance-terminate Lifecycle Action
|
|
detail:
|
|
AutoScalingGroupName:
|
|
- !Sub openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique rule name
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - StopMediaNodeCloudWatchEventRule-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
Targets:
|
|
- Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${StopMediaNodeAutomationDocument}:$DEFAULT
|
|
RoleArn: !GetAtt StopMediaNodeCloudWatchEventRole.Arn
|
|
Id:
|
|
Fn::Join:
|
|
# Generate a not too long and unique target id
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - StopMediaNodeCloudWatchEventRule-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
InputTransformer:
|
|
InputPathsMap:
|
|
instanceid: "$.detail.EC2InstanceId"
|
|
InputTemplate: |
|
|
{
|
|
"InstanceId": [ <instanceid> ]
|
|
}
|
|
|
|
OpenViduMediaNodeASGScalingPolicy:
|
|
Type: AWS::AutoScaling::ScalingPolicy
|
|
Properties:
|
|
AutoScalingGroupName: !Ref OpenViduMediaNodeASG
|
|
PolicyType: TargetTrackingScaling
|
|
EstimatedInstanceWarmup: 120
|
|
TargetTrackingConfiguration:
|
|
PredefinedMetricSpecification:
|
|
PredefinedMetricType: ASGAverageCPUUtilization
|
|
TargetValue: !Ref ScaleTargetCPU
|
|
|
|
OpenViduMasterNodeSG:
|
|
Type: AWS::EC2::SecurityGroup
|
|
Properties:
|
|
GroupDescription: Security group for OpenVidu Master Node
|
|
GroupName: !Sub openvidu-ha-master-sg-${AWS::Region}-${AWS::StackName}
|
|
VpcId: !Ref OpenViduVPC
|
|
SecurityGroupIngress:
|
|
- IpProtocol: tcp
|
|
FromPort: 22
|
|
ToPort: 22
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 22
|
|
ToPort: 22
|
|
CidrIpv6: ::/0
|
|
|
|
OpenViduLoadBalancerToMasterHTTPIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMasterNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 80
|
|
ToPort: 80
|
|
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
|
|
|
|
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: 1945
|
|
ToPort: 1945
|
|
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
|
|
|
|
OpenViduLoadBalancerToMediaNodeIngressHealthCheckSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 9092
|
|
ToPort: 9092
|
|
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
|
|
|
|
OpenViduMasterToMediaNodeServerIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 7880
|
|
ToPort: 7880
|
|
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
|
|
|
|
OpenViduMasterToMediaNodeClientIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 8080
|
|
ToPort: 8080
|
|
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
|
|
|
|
OpenViduTurnTLSMasterNodeToMediaNodeIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMediaNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 5349
|
|
ToPort: 5349
|
|
SourceSecurityGroupId: !Ref OpenViduMasterNodeSG
|
|
|
|
OpenViduTurnTLSLoadBalancerToMediaNodeIngressSG:
|
|
Type: AWS::EC2::SecurityGroupIngress
|
|
Properties:
|
|
GroupId: !Ref OpenViduMasterNodeSG
|
|
IpProtocol: tcp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG
|
|
|
|
OpenViduLoadBalancerSG:
|
|
Type: AWS::EC2::SecurityGroup
|
|
Properties:
|
|
GroupDescription: Security group for the Load Balancer
|
|
GroupName: !Sub openvidu-ha-lb-sg-${AWS::Region}-${AWS::StackName}
|
|
VpcId: !Ref OpenViduVPC
|
|
SecurityGroupIngress:
|
|
- IpProtocol: tcp
|
|
FromPort: 80
|
|
ToPort: 80
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 80
|
|
ToPort: 80
|
|
CidrIpv6: ::/0
|
|
- IpProtocol: tcp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 443
|
|
ToPort: 443
|
|
CidrIpv6: ::/0
|
|
- IpProtocol: tcp
|
|
FromPort: 1935
|
|
ToPort: 1935
|
|
CidrIp: 0.0.0.0/0
|
|
- IpProtocol: tcp
|
|
FromPort: 1935
|
|
ToPort: 1935
|
|
CidrIpv6: ::/0
|
|
|
|
LoadBalancer:
|
|
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique load balancer name
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - OpenViduHA-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
Subnets: !If [HasLoadBalancerSubnets, !Ref LoadBalancerSubnets, !Ref OpenViduMasterNodeSubnets]
|
|
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
|
|
Port: 1935
|
|
Protocol: TLS
|
|
Certificates:
|
|
- CertificateArn: !Ref OpenViduCertificateARN
|
|
|
|
OpenViduMasterNodeHTTPTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique target id
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - OVHTTP-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
TargetType: instance
|
|
Targets:
|
|
- Id: !Ref OpenViduMasterNode1
|
|
- Id: !Ref OpenViduMasterNode2
|
|
- Id: !Ref OpenViduMasterNode3
|
|
- Id: !Ref OpenViduMasterNode4
|
|
VpcId: !Ref OpenViduVPC
|
|
Port: 80
|
|
Protocol: TCP
|
|
Matcher:
|
|
HttpCode: '200'
|
|
HealthCheckIntervalSeconds: 10
|
|
HealthCheckPath: /health/caddy
|
|
HealthCheckProtocol: HTTP
|
|
HealthCheckPort: '7880'
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 3
|
|
UnhealthyThresholdCount: 4
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master HTTP Target Group
|
|
|
|
OpenViduMasterNodeWithTurnTLSTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique target id
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - OVTurnTLSMaster-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
TargetType: instance
|
|
Targets:
|
|
- Id: !Ref OpenViduMasterNode1
|
|
- Id: !Ref OpenViduMasterNode2
|
|
- Id: !Ref OpenViduMasterNode3
|
|
- Id: !Ref OpenViduMasterNode4
|
|
VpcId: !Ref OpenViduVPC
|
|
Port: 443
|
|
Protocol: TCP
|
|
Matcher:
|
|
HttpCode: '200'
|
|
HealthCheckIntervalSeconds: 10
|
|
HealthCheckPath: /health/caddy
|
|
HealthCheckProtocol: HTTP
|
|
HealthCheckPort: '7880'
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 3
|
|
UnhealthyThresholdCount: 4
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN TLS Master Target Group
|
|
|
|
OpenViduMediaNodeRTMPTG:
|
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
Properties:
|
|
Name:
|
|
Fn::Join:
|
|
# Generate a not too long and unique target id
|
|
# Getting a unique identifier from the stack id
|
|
- ''
|
|
- - OVRTMP-
|
|
- !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
|
|
VpcId: !Ref OpenViduVPC
|
|
Port: 1945
|
|
Protocol: TCP
|
|
Matcher:
|
|
HttpCode: '200'
|
|
HealthCheckIntervalSeconds: 10
|
|
HealthCheckPath: /
|
|
HealthCheckProtocol: HTTP
|
|
# Ingress health check port
|
|
HealthCheckPort: '9092'
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 3
|
|
UnhealthyThresholdCount: 4
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - RTMP Target Group
|
|
|
|
# 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
|
|
|
|
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
|
|
|
|
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
|