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.
chore/update-openvidu-actions-1.0.25
Piwccle 2026-10-27 19:03:50 +01:00
parent 84b96c4035
commit e2d05aa8ef
5 changed files with 128 additions and 92 deletions

View File

@ -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.

View File

@ -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' = {

View File

@ -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"

View File

@ -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' = {

View File

@ -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"