From e2d05aa8ef941325050b4d37688cb92eb2d51736 Mon Sep 17 00:00:00 2001 From: Piwccle Date: Tue, 27 Oct 2026 19:03:50 +0100 Subject: [PATCH] openvidu-deployment: Azure Elastic and HA templates can be redeployed Redeploying an Elastic or High Availability template on Azure always failed, so no parameter could be changed after the first deployment: - The Master Nodes' subnets were child resources outside the virtual network. A redeploy PUTs the virtual network with the subnets it lists, so ARM tried to delete the Master Nodes' subnets while they were in use (InUseSubnetCannotBeDeleted). Every subnet is now declared inside the virtual network; the symbolic names stay as `existing` references. - Azure Automation returns a webhook's URI only when it creates it, so the scale-in action group got an empty serviceUri on every redeploy ('Service Uri' must not be empty). Every deployment now creates the scale-in webhook under a new name, from a utcNow() parameter (new on every CLI deployment) and the deployment's name (new on every portal Redeploy, which refills the parameters with their previous values). - The Media Nodes' scale set no longer sets its capacity: a redeploy with a capacity below the scale-in-protected Media Nodes running was refused. The autoscale profile's default seeds the size at creation. Verified live on Elastic: two redeploys in a row succeeded. A redeploy still cannot set the autoscale minimum or maximum below the Media Nodes running (UnsupportedCapacity); the CHANGELOG and the documentation say how to remove them first. --- openvidu-deployment/CHANGELOG.md | 15 ++++ .../elastic/azure/cf-openvidu-elastic.bicep | 29 +++++-- .../elastic/azure/cf-openvidu-elastic.json | 37 ++++----- .../pro/ha/azure/cf-openvidu-ha.bicep | 59 ++++++++------ .../pro/ha/azure/cf-openvidu-ha.json | 80 +++++++++---------- 5 files changed, 128 insertions(+), 92 deletions(-) diff --git a/openvidu-deployment/CHANGELOG.md b/openvidu-deployment/CHANGELOG.md index c51dc4c53..5f3861532 100644 --- a/openvidu-deployment/CHANGELOG.md +++ b/openvidu-deployment/CHANGELOG.md @@ -77,3 +77,18 @@ in the [openvidu-oracle](https://github.com/OpenVidu/openvidu-oracle) and domain could be derived from a temporary public IP, or the instance could start without Internet access and fail its installation. The Elastic IP is now known in advance, and the instance waits up to 10 minutes for it before installing anything. + +#### Azure + +- Elastic and High Availability: redeploying the template always failed, so no parameter could be changed + after the first deployment: + - `InUseSubnetCannotBeDeleted`: the Master Nodes' subnets were declared outside the virtual network, so a + redeploy tried to delete them while the Master Nodes used them. Every subnet is now declared inside the + virtual network. + - `'Service Uri' must not be empty`: Azure Automation returns the URI of a webhook only when it creates it, + so the scale-in action group lost it on every redeploy. Every deployment now creates the scale-in webhook + under a new name. The webhooks of earlier deployments stay unused in the Automation account until they + expire. + + A redeploy still cannot set the minimum or maximum number of Media Nodes below the Media Nodes + running (`UnsupportedCapacity`); the administration documentation describes how to remove them first. diff --git a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep index 37272d2c7..111503eeb 100644 --- a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep +++ b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep @@ -1440,7 +1440,9 @@ resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@20 sku: { name: mediaNodeInstanceType tier: 'Standard' - capacity: initialNumberOfMediaNodes + // No capacity: the template never writes the number of Media Nodes, so a redeploy can + // never fail on the scale-in-protected Media Nodes. The autoscale profile's default + // seeds the size at creation and its minimum/maximum keep it. } properties: { overprovision: true @@ -1596,13 +1598,16 @@ param automationAccountName string = '' var isEmptyAutomationAccountName = automationAccountName == '' +@description('Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal).') +param scaleInWebhookNameSeed string = utcNow() + module webhookModule '../../shared/webhookdeployment.json' = { params: { automationAccountName: isEmptyAutomationAccountName ? uniqueString(resourceGroup().id, openviduMasterNode.id) : automationAccountName runbookName: 'scaleInRunbook' - webhookName: 'webhookForScaleIn' + webhookName: 'webhookForScaleIn-${uniqueString(deployment().name, scaleInWebhookNameSeed)}' WebhookExpiryTime: '2035-03-30T00:00:00Z' _artifactsLocation: 'https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1' } @@ -1724,18 +1729,26 @@ resource vnet_OV 'Microsoft.Network/virtualNetworks@2023-11-01' = { } } } + { + name: 'firstSubnetForMasterNodes' + properties: { + addressPrefix: networkSettings.subnetAddressPrefixMaster1 + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + } ] } } -resource subnetMasterNode 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { +// Every subnet is declared INSIDE the virtual network. A redeploy PUTs the virtual network with +// the subnets the template lists, so a subnet declared as a separate child resource was missing +// from that list and ARM tried to delete it while the Master Node used it +// (InUseSubnetCannotBeDeleted): no Elastic/HA deployment could be redeployed. The symbolic names +// below are `existing` references, so the rest of the template uses them as before. +resource subnetMasterNode 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = { parent: vnet_OV name: 'firstSubnetForMasterNodes' - properties: { - addressPrefix: networkSettings.subnetAddressPrefixMaster1 - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Enabled' - } } resource netInterfaceMasterNode 'Microsoft.Network/networkInterfaces@2023-11-01' = { diff --git a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json index 5d4da278a..70a3322c7 100644 --- a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json +++ b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.46.1.21595", - "templateHash": "11077733597635088578" + "templateHash": "13646640563547871932" } }, "parameters": { @@ -156,6 +156,13 @@ "description": "Automation Account Name to create a runbook inside it for scale in" } }, + "scaleInWebhookNameSeed": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal)." + } + }, "storageAccountName": { "type": "string", "defaultValue": "", @@ -415,8 +422,7 @@ }, "sku": { "name": "[parameters('mediaNodeInstanceType')]", - "tier": "Standard", - "capacity": "[parameters('initialNumberOfMediaNodes')]" + "tier": "Standard" }, "properties": { "overprovision": true, @@ -690,6 +696,14 @@ "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]" } } + }, + { + "name": "firstSubnetForMasterNodes", + "properties": { + "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster1]", + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled" + } } ] }, @@ -697,19 +711,6 @@ "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]" ] }, - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]", - "properties": { - "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster1]", - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Enabled" - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" - ] - }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", @@ -741,7 +742,7 @@ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]" + "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { @@ -1340,7 +1341,7 @@ "value": "scaleInRunbook" }, "webhookName": { - "value": "webhookForScaleIn" + "value": "[format('webhookForScaleIn-{0}', uniqueString(deployment().name, parameters('scaleInWebhookNameSeed')))]" }, "WebhookExpiryTime": { "value": "2035-03-30T00:00:00Z" diff --git a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep index 64e9eca02..73a4297db 100644 --- a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep +++ b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep @@ -1840,7 +1840,9 @@ resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@20 sku: { name: mediaNodeInstanceType tier: 'Standard' - capacity: initialNumberOfMediaNodes + // No capacity: the template never writes the number of Media Nodes, so a redeploy can + // never fail on the scale-in-protected Media Nodes. The autoscale profile's default + // seeds the size at creation and its minimum/maximum keep it. } properties: { overprovision: true @@ -1997,13 +1999,16 @@ param automationAccountName string = '' var isEmptyAutomationAccountName = automationAccountName == '' +@description('Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal).') +param scaleInWebhookNameSeed string = utcNow() + module webhookModule '../../shared/webhookdeployment.json' = { params: { automationAccountName: isEmptyAutomationAccountName ? uniqueString(resourceGroup().id, openviduMasterNode1.id) : automationAccountName runbookName: 'scaleInRunbook' - webhookName: 'webhookForScaleIn' + webhookName: 'webhookForScaleIn-${uniqueString(deployment().name, scaleInWebhookNameSeed)}' WebhookExpiryTime: '2035-03-30T00:00:00Z' _artifactsLocation: 'https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1' } @@ -2281,37 +2286,45 @@ resource vnet_OV 'Microsoft.Network/virtualNetworks@2023-11-01' = { } } } + { + name: 'firstSubnetForMasterNodes' + properties: { + addressPrefix: networkSettings.subnetAddressPrefixMaster1 + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + natGateway: { + id: natGateway.id + } + } + } + { + name: 'secondSubnetForMasterNodes' + properties: { + addressPrefix: networkSettings.subnetAddressPrefixMaster2 + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + natGateway: { + id: natGateway.id + } + } + } ] } } -resource subnetMasterNode1 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { +// Every subnet is declared INSIDE the virtual network. A redeploy PUTs the virtual network with +// the subnets the template lists, so a subnet declared as a separate child resource was missing +// from that list and ARM tried to delete it while the Master Node used it +// (InUseSubnetCannotBeDeleted): no Elastic/HA deployment could be redeployed. The symbolic names +// below are `existing` references, so the rest of the template uses them as before. +resource subnetMasterNode1 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = { parent: vnet_OV name: 'firstSubnetForMasterNodes' - properties: { - addressPrefix: networkSettings.subnetAddressPrefixMaster1 - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Enabled' - natGateway: { - id: natGateway.id - } - } } -resource subnetMasterNode2 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { +resource subnetMasterNode2 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = { parent: vnet_OV name: 'secondSubnetForMasterNodes' - properties: { - addressPrefix: networkSettings.subnetAddressPrefixMaster2 - privateEndpointNetworkPolicies: 'Disabled' - privateLinkServiceNetworkPolicies: 'Enabled' - natGateway: { - id: natGateway.id - } - } - dependsOn: [ - subnetMasterNode1 - ] } resource netInterfaceMasterNode1 'Microsoft.Network/networkInterfaces@2023-11-01' = { diff --git a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json index e76865489..353f2d739 100644 --- a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json +++ b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.46.1.21595", - "templateHash": "4153702520103052048" + "templateHash": "4663408180279198376" } }, "parameters": { @@ -163,6 +163,13 @@ "description": "Automation Account Name to create a runbook inside it for scale in" } }, + "scaleInWebhookNameSeed": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal)." + } + }, "storageAccountName": { "type": "string", "defaultValue": "", @@ -548,8 +555,7 @@ }, "sku": { "name": "[parameters('mediaNodeInstanceType')]", - "tier": "Standard", - "capacity": "[parameters('initialNumberOfMediaNodes')]" + "tier": "Standard" }, "properties": { "overprovision": true, @@ -976,48 +982,36 @@ "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]" } } + }, + { + "name": "firstSubnetForMasterNodes", + "properties": { + "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster1]", + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "natGateway": { + "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" + } + } + }, + { + "name": "secondSubnetForMasterNodes", + "properties": { + "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster2]", + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "natGateway": { + "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" + } + } } ] }, "dependsOn": [ + "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]" ] }, - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]", - "properties": { - "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster1]", - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "natGateway": { - "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" - ] - }, - { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', variables('networkSettings').vNetName, 'secondSubnetForMasterNodes')]", - "properties": { - "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster2]", - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "natGateway": { - "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" - ] - }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", @@ -1053,7 +1047,7 @@ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]" + "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { @@ -1091,7 +1085,7 @@ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'secondSubnetForMasterNodes')]" + "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { @@ -1129,7 +1123,7 @@ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]" + "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { @@ -1167,7 +1161,7 @@ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'secondSubnetForMasterNodes')]" + "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { @@ -2154,7 +2148,7 @@ "value": "scaleInRunbook" }, "webhookName": { - "value": "webhookForScaleIn" + "value": "[format('webhookForScaleIn-{0}', uniqueString(deployment().name, parameters('scaleInWebhookNameSeed')))]" }, "WebhookExpiryTime": { "value": "2035-03-30T00:00:00Z"