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"