openvidu-deployment: HA AWS - LoadBalancerDNS output and Docker Hub cache for private Media Nodes

- Add a LoadBalancerDNS output with the DNS name of the main Load Balancer, to point
  DomainName at.
- Media Nodes in private subnets all reach Docker Hub from the same NAT gateway IP, and
  Docker Hub rate-limits anonymous pulls per IP. The Master Nodes now run a pull-through
  cache of Docker Hub (CNCF Distribution registry, version pinned by REGISTRY_VERSION,
  TCP 5555; 5000 is the Dashboard) and Media Nodes without a public IP use it as their
  Docker registry mirror, falling back to Docker Hub if no cache answers. The
  SubnetProcessor function reports whether the Media Node subnets assign public IPs, so
  nothing is installed when they do.
- Make the TurnDomainName and LoadBalancerSubnets descriptions generic.


Assisted-by: claude-code - Anthropic - Claude Opus 5.5 1m
pull/912/head
cruizba 2026-09-25 18:38:33 +02:00
parent a0c005275a
commit 508feac072
1 changed files with 152 additions and 5 deletions

View File

@ -1043,12 +1043,12 @@ Parameters:
ConstraintDescription: You must specify a list of subnet IDs
LoadBalancerSubnets:
Description: "(Optional) Public subnets for the internet-facing Load Balancer. Leave empty to place the Load Balancer in the OpenViduMasterNodeSubnets (default behavior). Set it to dedicated public subnets to keep the Master Nodes in private subnets with NAT egress."
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 a dedicated TURN Load Balancer in front of the Media Nodes. Set it (with TurnCertificateARN) when the Media Nodes run in private subnets, so media is relayed by the Media Nodes instead of the Master Nodes. Point it (A/ALIAS) to the TurnLoadBalancerDNS output."
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: ""
@ -1336,6 +1336,7 @@ Resources:
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import boto3
import cfnresponse
def lambda_handler(event, context):
@ -1353,6 +1354,16 @@ Resources:
'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:
@ -1367,6 +1378,7 @@ Resources:
Properties:
ServiceToken: !GetAtt SubnetProcessorFunction.Arn
Subnets: !Ref OpenViduMasterNodeSubnets
MediaSubnets: !Ref OpenViduMediaNodeSubnets
LambdaLogGroup:
UpdateReplacePolicy: Retain
@ -1397,6 +1409,10 @@ Resources:
- 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
@ -1848,6 +1864,59 @@ Resources:
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 /var/lib/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
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
- |
@ -2186,6 +2255,9 @@ Resources:
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
@ -2265,6 +2337,9 @@ Resources:
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
@ -2344,6 +2419,9 @@ Resources:
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
@ -2423,6 +2501,9 @@ Resources:
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
@ -2565,6 +2646,10 @@ Resources:
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"
@ -2594,6 +2679,56 @@ Resources:
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
@ -2990,6 +3125,15 @@ Resources:
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:
@ -3500,9 +3644,9 @@ Resources:
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 media
# of clients that cannot reach the Media Nodes directly is relayed by the Media Nodes
# themselves instead of going through the Master Nodes.
# 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
@ -3589,6 +3733,9 @@ 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