mirror of https://github.com/OpenVidu/openvidu.git
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 1mpull/912/head
parent
a0c005275a
commit
508feac072
|
|
@ -1043,12 +1043,12 @@ Parameters:
|
||||||
ConstraintDescription: You must specify a list of subnet IDs
|
ConstraintDescription: You must specify a list of subnet IDs
|
||||||
|
|
||||||
LoadBalancerSubnets:
|
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
|
Type: CommaDelimitedList
|
||||||
Default: ""
|
Default: ""
|
||||||
|
|
||||||
TurnDomainName:
|
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
|
Type: String
|
||||||
Default: ""
|
Default: ""
|
||||||
|
|
||||||
|
|
@ -1336,6 +1336,7 @@ Resources:
|
||||||
Role: !GetAtt LambdaExecutionRole.Arn
|
Role: !GetAtt LambdaExecutionRole.Arn
|
||||||
Code:
|
Code:
|
||||||
ZipFile: |
|
ZipFile: |
|
||||||
|
import boto3
|
||||||
import cfnresponse
|
import cfnresponse
|
||||||
|
|
||||||
def lambda_handler(event, context):
|
def lambda_handler(event, context):
|
||||||
|
|
@ -1353,6 +1354,16 @@ Resources:
|
||||||
'Subnet3': subnets[2],
|
'Subnet3': subnets[2],
|
||||||
'Subnet4': subnets[3],
|
'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)
|
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1367,6 +1378,7 @@ Resources:
|
||||||
Properties:
|
Properties:
|
||||||
ServiceToken: !GetAtt SubnetProcessorFunction.Arn
|
ServiceToken: !GetAtt SubnetProcessorFunction.Arn
|
||||||
Subnets: !Ref OpenViduMasterNodeSubnets
|
Subnets: !Ref OpenViduMasterNodeSubnets
|
||||||
|
MediaSubnets: !Ref OpenViduMediaNodeSubnets
|
||||||
|
|
||||||
LambdaLogGroup:
|
LambdaLogGroup:
|
||||||
UpdateReplacePolicy: Retain
|
UpdateReplacePolicy: Retain
|
||||||
|
|
@ -1397,6 +1409,10 @@ Resources:
|
||||||
- logs:CreateLogStream
|
- logs:CreateLogStream
|
||||||
- logs:PutLogEvents
|
- logs:PutLogEvents
|
||||||
Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}:*'
|
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:
|
OpenViduMasterNodeRole:
|
||||||
Type: AWS::IAM::Role
|
Type: AWS::IAM::Role
|
||||||
|
|
@ -1848,6 +1864,59 @@ Resources:
|
||||||
mode: '000755'
|
mode: '000755'
|
||||||
owner: root
|
owner: root
|
||||||
group: 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':
|
'/usr/local/bin/config_s3.sh':
|
||||||
content: !Sub
|
content: !Sub
|
||||||
- |
|
- |
|
||||||
|
|
@ -2186,6 +2255,9 @@ Resources:
|
||||||
|
|
||||||
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
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
|
export MASTER_NODE_NUM=1
|
||||||
|
|
||||||
# Install OpenVidu
|
# Install OpenVidu
|
||||||
|
|
@ -2265,6 +2337,9 @@ Resources:
|
||||||
|
|
||||||
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
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
|
export MASTER_NODE_NUM=2
|
||||||
|
|
||||||
# Install OpenVidu
|
# Install OpenVidu
|
||||||
|
|
@ -2344,6 +2419,9 @@ Resources:
|
||||||
|
|
||||||
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
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
|
export MASTER_NODE_NUM=3
|
||||||
|
|
||||||
# Install OpenVidu
|
# Install OpenVidu
|
||||||
|
|
@ -2423,6 +2501,9 @@ Resources:
|
||||||
|
|
||||||
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
|
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
|
export MASTER_NODE_NUM=4
|
||||||
|
|
||||||
# Install OpenVidu
|
# 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"
|
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')
|
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
|
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
|
||||||
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
|
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"
|
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'
|
mode: '000755'
|
||||||
owner: root
|
owner: root
|
||||||
group: 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':
|
'/usr/local/bin/set_as_unhealthy.sh':
|
||||||
content: !Sub |
|
content: !Sub |
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
@ -2990,6 +3125,15 @@ Resources:
|
||||||
ToPort: 7880
|
ToPort: 7880
|
||||||
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
|
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
|
||||||
|
|
||||||
|
OpenViduMediaNodeToMasterRegistryMirrorIngress:
|
||||||
|
Type: AWS::EC2::SecurityGroupIngress
|
||||||
|
Properties:
|
||||||
|
GroupId: !Ref OpenViduMasterNodeSG
|
||||||
|
IpProtocol: tcp
|
||||||
|
FromPort: 5555
|
||||||
|
ToPort: 5555
|
||||||
|
SourceSecurityGroupId: !Ref OpenViduMediaNodeSG
|
||||||
|
|
||||||
OpenViduMediaNodeToMasterRedisIngressSG:
|
OpenViduMediaNodeToMasterRedisIngressSG:
|
||||||
Type: AWS::EC2::SecurityGroupIngress
|
Type: AWS::EC2::SecurityGroupIngress
|
||||||
Properties:
|
Properties:
|
||||||
|
|
@ -3500,9 +3644,9 @@ Resources:
|
||||||
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Target Group
|
Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Target Group
|
||||||
|
|
||||||
# Dedicated TURN Load Balancer (only when TurnDomainName + TurnCertificateARN are set).
|
# 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
|
# Terminates TLS on 443 and forwards to the Media Nodes' TURN TLS port 5349, so TURN
|
||||||
# of clients that cannot reach the Media Nodes directly is relayed by the Media Nodes
|
# over TLS is relayed by the Media Nodes themselves instead of going through the
|
||||||
# themselves instead of going through the Master Nodes.
|
# Master Nodes.
|
||||||
TurnLoadBalancerSG:
|
TurnLoadBalancerSG:
|
||||||
Type: AWS::EC2::SecurityGroup
|
Type: AWS::EC2::SecurityGroup
|
||||||
Condition: TurnTLSIsEnabled
|
Condition: TurnTLSIsEnabled
|
||||||
|
|
@ -3589,6 +3733,9 @@ Outputs:
|
||||||
ServicesAndCredentials:
|
ServicesAndCredentials:
|
||||||
Description: Services and credentials
|
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}
|
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:
|
TurnLoadBalancerDNS:
|
||||||
Condition: TurnTLSIsEnabled
|
Condition: TurnTLSIsEnabled
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue