diff --git a/recipe-packs/bicep/ACI-NGroups-Confidential.bicep b/recipe-packs/bicep/ACI-NGroups-Confidential.bicep new file mode 100644 index 00000000..84b7a567 --- /dev/null +++ b/recipe-packs/bicep/ACI-NGroups-Confidential.bicep @@ -0,0 +1,92 @@ +// Variables (using var instead of parameters since the ARM template uses variables) +var cgProfileName = 'cgp_1' +var nGroupsName = 'ngroup_confidential_basic' +var apiVersion = '2024-09-01-preview' +var desiredCount = 1 +var prefixCG = 'cg-confidential-' +var resourcePrefix = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/' + +// Container Group Profile with Confidential Computing +resource containerGroupProfile 'Microsoft.ContainerInstance/containerGroupProfiles@2024-09-01-preview' = { + name: cgProfileName + location: resourceGroup().location + properties: { + sku: 'Confidential' + confidentialComputeProperties: { + ccePolicy: '' + } + containers: [ + { + name: 'aci-helloworld' + properties: { + image: 'mcr.microsoft.com/azuredocs/aci-helloworld@sha256:565dba8ce20ca1a311c2d9485089d7ddc935dd50140510050345a1b0ea4ffa6e' + ports: [ + { + protocol: 'TCP' + port: 80 + } + ] + resources: { + requests: { + memoryInGB: json('1.0') + cpu: json('1.0') + } + } + } + } + ] + restartPolicy: 'Always' + ipAddress: { + ports: [ + { + protocol: 'TCP' + port: 80 + } + ] + type: 'Public' + } + osType: 'Linux' + } +} + +// NGroups for Confidential Computing +resource nGroups 'Microsoft.ContainerInstance/NGroups@2024-09-01-preview' = { + name: nGroupsName + location: resourceGroup().location + properties: { + elasticProfile: { + desiredCount: desiredCount + containerGroupNamingPolicy: { + guidNamingPolicy: { + prefix: prefixCG + } + } + } + containerGroupProfiles: [ + { + resource: { + id: '${resourcePrefix}Microsoft.ContainerInstance/containerGroupProfiles/${cgProfileName}' + } + } + ] + } + tags: { + cirrusTestScenario: 'confidential-1.basic' + } + dependsOn: [ + containerGroupProfile + ] +} + +// Outputs +output containerGroupProfileId string = containerGroupProfile.id +output nGroupId string = nGroups.id + +output result object = { + values: { + containerGroupProfileId: containerGroupProfile.id + nGroupsId: nGroups.id + location: resourceGroup().location + desiredCount: desiredCount + } +} diff --git a/recipe-packs/bicep/ACI-NGroups-LoadBalancer.bicep b/recipe-packs/bicep/ACI-NGroups-LoadBalancer.bicep new file mode 100644 index 00000000..a82cf072 --- /dev/null +++ b/recipe-packs/bicep/ACI-NGroups-LoadBalancer.bicep @@ -0,0 +1,493 @@ +@description('Container Instance API version') +@maxLength(32) +param apiVersion string = '2024-11-01-preview' + +@description('NGroups parameter name') +@maxLength(64) +param nGroupsParamName string = 'nGroups_resource_1' + +@description('Container Group Profile name') +@maxLength(64) +param containerGroupProfileName string = 'cgp_1' + +@description('Load Balancer name') +@maxLength(64) +param loadBalancerName string = 'slb_1' + +@description('Backend Address Pool name') +@maxLength(64) +param backendAddressPoolName string = 'bepool_1' + +@description('Virtual Network name') +@maxLength(64) +param vnetName string = 'vnet_1' + +@description('Subnet name') +@maxLength(64) +param subnetName string = 'subnet_1' + +@description('Network Security Group name') +@maxLength(64) +param networkSecurityGroupName string = 'nsg_1' + +@description('Inbound Public IP name') +@maxLength(64) +param inboundPublicIPName string = 'inboundPublicIP' + +@description('Outbound Public IP name') +@maxLength(64) +param outboundPublicIPName string = 'outboundPublicIP' + +@description('NAT Gateway name') +param natGatewayName string = 'natGateway1' + +@description('Frontend IP name') +@maxLength(64) +param frontendIPName string = 'loadBalancerFrontend' + +@description('HTTP Rule name') +@maxLength(64) +param httpRuleName string = 'httpRule' + +@description('Virtual Network address prefix') +@maxLength(64) +param vnetAddressPrefix string + +@description('Subnet address prefix') +@maxLength(64) +param subnetAddressPrefix string + +// @description('Desired container count') +// param desiredCount int = 3 + +@description('Availability zones') +param zones array = [] + +@description('Maintain desired count') +param maintainDesiredCount bool = true + +@description('Inbound NAT Rule name') +@maxLength(64) +param inboundNatRuleName string = 'inboundNatRule' + +@description('User Assigned Identity name') +@maxLength(64) +param userAssignedIdentityName string = 'uai_1' + +@description('Radius ACI Container Context') +param context object + +// Output the context object for debugging +output contextObject object = context + +// Variables +var cgProfileName = containerGroupProfileName +var nGroupsName = nGroupsParamName +var resourcePrefix = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/' +// var loadBalancerApiVersion = '2022-07-01' +// var vnetApiVersion = '2022-07-01' +// var publicIPVersion = '2022-07-01' +var ddosProtectionPlanName = 'ddosProtectionPlan' + +// Helper variables for probes +var hasReadinessProbe = contains(context.resource.properties.containers, 'readinessProbe') && context.resource.properties.containers.demo.readinessProbe != null +var hasLivenessProbe = contains(context.resource.properties.containers, 'livenessProbe') && context.resource.properties.containers.demo.livenessProbe != null + +// Get probe port with safe navigation +// var readinessProbePort = context.resource.properties.containers.?demo.?readinessProbe.?tcpSocket.?properties.?port ?? 80 +var livenessProbePort = context.resource.properties.containers.?demo.?livenessProbe.?tcpSocket.?properties.?port ?? 80 + +// Check if there's a Redis connection configured +var resourceProperties = context.resource.properties ?? {} +var connectionsConfig = resourceProperties.connections ?? {} +var hasRedisConnection = contains(connectionsConfig, 'redis') +// Get Redis connection outputs if available +var resourceConnections = context.resource.connections ?? {} +var redisOutputs = hasRedisConnection && contains(resourceConnections, 'redis') ? resourceConnections.redis : {} +var redisHost = hasRedisConnection && contains(redisOutputs, 'host') ? redisOutputs.host : '' +var redisPort = hasRedisConnection && contains(redisOutputs, 'port') ? redisOutputs.port : 0 + +// User Assigned Managed Identity +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: userAssignedIdentityName + location: resourceGroup().location +} + +// DDoS Protection Plan +resource ddosProtectionPlan 'Microsoft.Network/ddosProtectionPlans@2022-07-01' = { + name: ddosProtectionPlanName + location: resourceGroup().location +} + +// Network Security Group +resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-07-01' = { + name: networkSecurityGroupName + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'AllowHTTPInbound' + properties: { + access: 'Allow' + description: 'Allow Internet traffic on port range' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '80-331' + ] + direction: 'Inbound' + protocol: '*' + priority: 100 + sourceAddressPrefix: 'Internet' + sourcePortRange: '*' + } + } + { + name: 'AzureCloudInbound' + properties: { + access: 'Allow' + description: 'Allow Azure Cloud traffic on port range' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '3000' + ] + direction: 'Inbound' + protocol: '*' + priority: 110 + sourceAddressPrefix: 'AzureCloud' + sourcePortRange: '*' + } + } + ] + } +} + +// Inbound Public IP +resource inboundPublicIP 'Microsoft.Network/publicIPAddresses@2022-07-01' = { + name: inboundPublicIPName + location: resourceGroup().location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAddressVersion: 'IPv4' + publicIPAllocationMethod: 'Static' + idleTimeoutInMinutes: 4 + ipTags: [] + } +} + +// Outbound Public IP +resource outboundPublicIP 'Microsoft.Network/publicIPAddresses@2022-07-01' = { + name: outboundPublicIPName + location: resourceGroup().location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAddressVersion: 'IPv4' + publicIPAllocationMethod: 'Static' + idleTimeoutInMinutes: 4 + ipTags: [] + } +} + +// NAT Gateway +resource natGateway 'Microsoft.Network/natGateways@2022-07-01' = { + name: natGatewayName + location: resourceGroup().location + sku: { + name: 'Standard' + } + properties: { + idleTimeoutInMinutes: 4 + publicIpAddresses: [ + { + id: outboundPublicIP.id + } + ] + } + +} + +// Virtual Network +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-07-01' = { + name: vnetName + location: resourceGroup().location + properties: { + addressSpace: { + addressPrefixes: [ + vnetAddressPrefix + ] + } + subnets: [ + { + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + serviceEndpoints: [] + delegations: [ + { + name: 'Microsoft.ContainerInstance.containerGroups' + id: '${resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, subnetName)}/delegations/Microsoft.ContainerInstance.containerGroups' + properties: { + serviceName: 'Microsoft.ContainerInstance/containerGroups' + } + type: 'Microsoft.Network/virtualNetworks/subnets/delegations' + } + ] + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + networkSecurityGroup: { + id: networkSecurityGroup.id + } + natGateway: { + id: natGateway.id + } + } + type: 'Microsoft.Network/virtualNetworks/subnets' + } + ] + virtualNetworkPeerings: [] + enableDdosProtection: true + ddosProtectionPlan: { + id: ddosProtectionPlan.id + } + } + +} + +// Load Balancer +resource loadBalancer 'Microsoft.Network/loadBalancers@2022-07-01' = { + name: loadBalancerName + location: resourceGroup().location + sku: { + name: 'Standard' + } + properties: { + frontendIPConfigurations: [ + { + properties: { + publicIPAddress: { + id: inboundPublicIP.id + } + privateIPAllocationMethod: 'Dynamic' + } + name: frontendIPName + } + ] + backendAddressPools: [ + { + name: backendAddressPoolName + id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadBalancerName, backendAddressPoolName) + properties: { + loadBalancerBackendAddresses: [] + } + } + ] + probes: union( + [ + { + name: 'readinessProbe' + properties: { + protocol: 'Tcp' + port: context.resource.properties.containers.demo.ports.?http.?containerPort ?? 80 + intervalInSeconds: context.resource.properties.containers.?demo.?readinessProbe.?periodSeconds ?? 5 + numberOfProbes: context.resource.properties.containers.?demo.?readinessProbe.?failureThreshold ?? 1 + probeThreshold: context.resource.properties.containers.?demo.?readinessProbe.?successThreshold ?? 1 + } + } + ], + hasLivenessProbe ? [ + { + name: 'livenessProbe' + properties: { + protocol: 'Tcp' + port: livenessProbePort + intervalInSeconds: context.resource.properties.containers.demo.livenessProbe.?periodSeconds ?? 10 + numberOfProbes: context.resource.properties.containers.demo.livenessProbe.?failureThreshold ?? 3 + probeThreshold: context.resource.properties.containers.demo.livenessProbe.?successThreshold ?? 1 + } + } + ] : [] + ) + loadBalancingRules: [ + { + name: httpRuleName + properties: { + frontendIPConfiguration: { + id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', loadBalancerName, frontendIPName) + } + frontendPort: context.resource.properties.containers.demo.ports.?http.?containerPort ?? 80 + backendPort: context.resource.properties.containers.demo.ports.?http.?containerPort ?? 80 + enableFloatingIP: false + idleTimeoutInMinutes: 5 + protocol: 'Tcp' + enableTcpReset: true + loadDistribution: 'Default' + disableOutboundSnat: true + backendAddressPools: [ + { + id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadBalancerName, backendAddressPoolName) + } + ] + probe: { + id: resourceId('Microsoft.Network/loadBalancers/probes', loadBalancerName, 'readinessProbe') + } + } + } + ] + inboundNatRules: [] + outboundRules: [] + inboundNatPools: [] + } + dependsOn: [ + virtualNetwork + ] +} + +// ContainerGroupProfile resource - Create default CGProfile when platformOptions is not provided else use the CGProfile resource provided by the customer. +resource containerGroupProfile 'Microsoft.ContainerInstance/containerGroupProfiles@2024-09-01-preview' = { + name: cgProfileName + location: resourceGroup().location + properties: { + sku: 'Standard' + containers: [ + { + name: 'web' + properties: { + image: context.resource.properties.containers.demo.image + environmentVariables: [ + { + name: 'CONNECTION_REDIS_HOST' + value: redisHost + } + { + name: 'CONNECTION_REDIS_PORT' + value: redisPort + } + { + name: 'CONNECTION_REDIS_CONNECTIONSTRING' + secureValue: 'redis://${redisHost}:${redisPort},abortConnect=False' + } + ] + ports: [ + { + protocol: context.resource.properties.containers.demo.ports.?http.?protocol ?? 'TCP' + port: context.resource.properties.containers.demo.ports.?http.?containerPort ?? 80 + } + ] + resources: { + requests: { + memoryInGB: context.resource.properties.containers.demo.?resources.?requests.?memoryInMib != null ? context.resource.properties.containers.demo.?resources.?requests.?memoryInMib /1024 : json('1.0') + cpu: context.resource.properties.containers.demo.?resources.?requests.?cpu ?? json('1.0') + } + } + volumeMounts: [ + { + name: 'cachevolume' + mountPath: '/mnt/cache' // ephemeral volume path in container filesystem + } + ] + } + } + ] + volumes: [ + { + name: 'cachevolume' + emptyDir: {} // ephemeral volume + } + ] + restartPolicy: 'Always' + ipAddress: { + ports: [ + { + protocol: 'TCP' + port: context.resource.properties.containers.demo.ports.?http.?containerPort ?? 80 + } + ] + type: 'Private' + } + osType: 'Linux' + } +} + +// NGroups +resource nGroups 'Microsoft.ContainerInstance/NGroups@2024-09-01-preview' = { + name: nGroupsName + location: resourceGroup().location + zones: zones + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${resourcePrefix}Microsoft.ManagedIdentity/userAssignedIdentities/${userAssignedIdentityName}': {} + } + } + properties: { + elasticProfile: { + desiredCount: context.resource.?properties.?replicas ?? 2 + maintainDesiredCount: maintainDesiredCount + } + updateProfile: { + updateMode: 'Rolling' + } + containerGroupProfiles: [ + { + resource: { + id: '${resourcePrefix}Microsoft.ContainerInstance/containerGroupProfiles/${cgProfileName}' + } + containerGroupProperties: { + subnetIds: [ + { + id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, subnetName) + name: subnetName + } + ] + } + networkProfile: { + loadBalancer: { + backendAddressPools: [ + { + resource: { + id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadBalancerName, backendAddressPoolName) + } + } + ] + } + } + } + ] + } + tags: { + 'reprovision.enabled': 'true' + 'metadata.container.environmentVariable.orchestratorId': 'true' + 'rollingupdate.replace.enabled': 'true' + } + dependsOn: [ + containerGroupProfile + loadBalancer + virtualNetwork + userAssignedIdentity + ] +} + +// Outputs +output virtualNetworkId string = virtualNetwork.id +output subnetId string = virtualNetwork.properties.subnets[0].id +output loadBalancerId string = loadBalancer.id +output frontendIPConfigurationId string = loadBalancer.properties.frontendIPConfigurations[0].id +output backendAddressPoolId string = loadBalancer.properties.backendAddressPools[0].id +output inboundPublicIPId string = inboundPublicIP.id +output outboundPublicIPId string = outboundPublicIP.id +output inboundPublicIPFQDN string = contains(inboundPublicIP.properties, 'dnsSettings') && inboundPublicIP.properties.dnsSettings != null ? inboundPublicIP.properties.dnsSettings.fqdn : '' +output natGatewayId string = natGateway.id +output networkSecurityGroupId string = networkSecurityGroup.id +output ddosProtectionPlanId string = ddosProtectionPlan.id +output containerGroupProfileId string = containerGroupProfile.id +output nGroupsId string = nGroups.id +output readinessProbeId string = hasReadinessProbe ? resourceId('Microsoft.Network/loadBalancers/probes', loadBalancerName, 'readinessProbe') : '' +output livenessProbeId string = hasLivenessProbe ? resourceId('Microsoft.Network/loadBalancers/probes', loadBalancerName, 'livenessProbe') : '' +output userAssignedIdentityId string = userAssignedIdentity.id +output userAssignedIdentityClientId string = userAssignedIdentity.properties.clientId +output userAssignedIdentityPrincipalId string = userAssignedIdentity.properties.principalId diff --git a/recipe-packs/bicep/ACI-NGroups-LoadBalancer.json b/recipe-packs/bicep/ACI-NGroups-LoadBalancer.json new file mode 100644 index 00000000..b1e67a21 --- /dev/null +++ b/recipe-packs/bicep/ACI-NGroups-LoadBalancer.json @@ -0,0 +1,556 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6336561542319590498" + } + }, + "parameters": { + "apiVersion": { + "type": "string", + "defaultValue": "2024-11-01-preview", + "maxLength": 32, + "metadata": { + "description": "Container Instance API version" + } + }, + "nGroupsParamName": { + "type": "string", + "defaultValue": "nGroups_resource_1", + "maxLength": 64, + "metadata": { + "description": "NGroups parameter name" + } + }, + "containerGroupProfileName": { + "type": "string", + "defaultValue": "cgp_1", + "maxLength": 64, + "metadata": { + "description": "Container Group Profile name" + } + }, + "loadBalancerName": { + "type": "string", + "defaultValue": "slb_1", + "maxLength": 64, + "metadata": { + "description": "Load Balancer name" + } + }, + "backendAddressPoolName": { + "type": "string", + "defaultValue": "bepool_1", + "maxLength": 64, + "metadata": { + "description": "Backend Address Pool name" + } + }, + "vnetName": { + "type": "string", + "defaultValue": "vnet_1", + "maxLength": 64, + "metadata": { + "description": "Virtual Network name" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "subnet_1", + "maxLength": 64, + "metadata": { + "description": "Subnet name" + } + }, + "networkSecurityGroupName": { + "type": "string", + "defaultValue": "nsg_1", + "maxLength": 64, + "metadata": { + "description": "Network Security Group name" + } + }, + "inboundPublicIPName": { + "type": "string", + "defaultValue": "inboundPublicIP", + "maxLength": 64, + "metadata": { + "description": "Inbound Public IP name" + } + }, + "outboundPublicIPName": { + "type": "string", + "defaultValue": "outboundPublicIP", + "maxLength": 64, + "metadata": { + "description": "Outbound Public IP name" + } + }, + "natGatewayName": { + "type": "string", + "defaultValue": "natGateway1", + "metadata": { + "description": "NAT Gateway name" + } + }, + "frontendIPName": { + "type": "string", + "defaultValue": "loadBalancerFrontend", + "maxLength": 64, + "metadata": { + "description": "Frontend IP name" + } + }, + "httpRuleName": { + "type": "string", + "defaultValue": "httpRule", + "maxLength": 64, + "metadata": { + "description": "HTTP Rule name" + } + }, + "vnetAddressPrefix": { + "type": "string", + "maxLength": 64, + "metadata": { + "description": "Virtual Network address prefix" + } + }, + "subnetAddressPrefix": { + "type": "string", + "maxLength": 64, + "metadata": { + "description": "Subnet address prefix" + } + }, + "zones": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Availability zones" + } + }, + "maintainDesiredCount": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Maintain desired count" + } + }, + "inboundNatRuleName": { + "type": "string", + "defaultValue": "inboundNatRule", + "maxLength": 64, + "metadata": { + "description": "Inbound NAT Rule name" + } + }, + "context": { + "type": "object", + "metadata": { + "description": "Radius ACI Container Context" + } + } + }, + "variables": { + "cgProfileName": "[parameters('containerGroupProfileName')]", + "nGroupsName": "[parameters('nGroupsParamName')]", + "resourcePrefix": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/', subscription().subscriptionId, resourceGroup().name)]", + "ddosProtectionPlanName": "ddosProtectionPlan", + "hasReadinessProbe": "[and(contains(parameters('context').properties.containers, 'readinessProbe'), not(equals(parameters('context').properties.containers.readinessProbe, null())))]", + "hasLivenessProbe": "[and(contains(parameters('context').properties.containers, 'livenessProbe'), not(equals(parameters('context').properties.containers.livenessProbe, null())))]", + "readinessProbePort": "[if(and(and(and(and(variables('hasReadinessProbe'), contains(parameters('context').properties.containers.readinessProbe, 'tcpSocket')), not(equals(parameters('context').properties.containers.readinessProbe.tcpSocket, null()))), contains(parameters('context').properties.containers.readinessProbe.tcpSocket, 'properties')), contains(parameters('context').properties.containers.readinessProbe.tcpSocket.properties, 'port')), parameters('context').properties.containers.readinessProbe.tcpSocket.properties.port, 80)]", + "livenessProbePort": "[if(and(and(and(and(variables('hasLivenessProbe'), contains(parameters('context').properties.containers.livenessProbe, 'tcpSocket')), not(equals(parameters('context').properties.containers.livenessProbe.tcpSocket, null()))), contains(parameters('context').properties.containers.livenessProbe.tcpSocket, 'properties')), contains(parameters('context').properties.containers.livenessProbe.tcpSocket.properties, 'port')), parameters('context').properties.containers.livenessProbe.tcpSocket.properties.port, 80)]" + }, + "resources": [ + { + "type": "Microsoft.Network/ddosProtectionPlans", + "apiVersion": "2022-07-01", + "name": "[variables('ddosProtectionPlanName')]", + "location": "[resourceGroup().location]" + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2022-07-01", + "name": "[parameters('networkSecurityGroupName')]", + "location": "[resourceGroup().location]", + "properties": { + "securityRules": [ + { + "name": "AllowHTTPInbound", + "properties": { + "access": "Allow", + "description": "Allow Internet traffic on port range", + "destinationAddressPrefix": "*", + "destinationPortRanges": [ + "80-331" + ], + "direction": "Inbound", + "protocol": "*", + "priority": 100, + "sourceAddressPrefix": "Internet", + "sourcePortRange": "*" + } + } + ] + } + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-07-01", + "name": "[parameters('inboundPublicIPName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard", + "tier": "Regional" + }, + "properties": { + "publicIPAddressVersion": "IPv4", + "publicIPAllocationMethod": "Static", + "idleTimeoutInMinutes": 4, + "ipTags": [] + } + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2022-07-01", + "name": "[parameters('outboundPublicIPName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard", + "tier": "Regional" + }, + "properties": { + "publicIPAddressVersion": "IPv4", + "publicIPAllocationMethod": "Static", + "idleTimeoutInMinutes": 4, + "ipTags": [] + } + }, + { + "type": "Microsoft.Network/natGateways", + "apiVersion": "2022-07-01", + "name": "[parameters('natGatewayName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard" + }, + "properties": { + "idleTimeoutInMinutes": 4, + "publicIpAddresses": [ + { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('outboundPublicIPName'))]" + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('outboundPublicIPName'))]" + ] + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2022-07-01", + "name": "[parameters('vnetName')]", + "location": "[resourceGroup().location]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('vnetAddressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('subnetName')]", + "properties": { + "addressPrefix": "[parameters('subnetAddressPrefix')]", + "serviceEndpoints": [], + "delegations": [ + { + "name": "Microsoft.ContainerInstance.containerGroups", + "id": "[format('{0}/delegations/Microsoft.ContainerInstance.containerGroups', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName')))]", + "properties": { + "serviceName": "Microsoft.ContainerInstance/containerGroups" + }, + "type": "Microsoft.Network/virtualNetworks/subnets/delegations" + } + ], + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + }, + "natGateway": { + "id": "[resourceId('Microsoft.Network/natGateways', parameters('natGatewayName'))]" + } + }, + "type": "Microsoft.Network/virtualNetworks/subnets" + } + ], + "virtualNetworkPeerings": [], + "enableDdosProtection": true, + "ddosProtectionPlan": { + "id": "[resourceId('Microsoft.Network/ddosProtectionPlans', variables('ddosProtectionPlanName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/ddosProtectionPlans', variables('ddosProtectionPlanName'))]", + "[resourceId('Microsoft.Network/natGateways', parameters('natGatewayName'))]", + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + ] + }, + { + "type": "Microsoft.Network/loadBalancers", + "apiVersion": "2022-07-01", + "name": "[parameters('loadBalancerName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard" + }, + "properties": { + "frontendIPConfigurations": [ + { + "properties": { + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName'))]" + }, + "privateIPAllocationMethod": "Dynamic" + }, + "name": "[parameters('frontendIPName')]" + } + ], + "backendAddressPools": [ + { + "name": "[parameters('backendAddressPoolName')]", + "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', parameters('loadBalancerName'), parameters('backendAddressPoolName'))]", + "properties": { + "loadBalancerBackendAddresses": [] + } + } + ], + "probes": "[union(if(variables('hasReadinessProbe'), createArray(createObject('name', 'readinessProbe', 'properties', createObject('protocol', 'Tcp', 'port', variables('readinessProbePort'), 'intervalInSeconds', coalesce(tryGet(parameters('context').properties.containers.readinessProbe, 'periodSeconds'), 5), 'numberOfProbes', coalesce(tryGet(parameters('context').properties.containers.readinessProbe, 'failureThreshold'), 3), 'probeThreshold', coalesce(tryGet(parameters('context').properties.containers.readinessProbe, 'successThreshold'), 1)))), createArray()), if(variables('hasLivenessProbe'), createArray(createObject('name', 'livenessProbe', 'properties', createObject('protocol', 'Tcp', 'port', variables('livenessProbePort'), 'intervalInSeconds', coalesce(tryGet(parameters('context').properties.containers.livenessProbe, 'periodSeconds'), 10), 'numberOfProbes', coalesce(tryGet(parameters('context').properties.containers.livenessProbe, 'failureThreshold'), 3), 'probeThreshold', coalesce(tryGet(parameters('context').properties.containers.livenessProbe, 'successThreshold'), 1)))), createArray()))]", + "loadBalancingRules": [ + { + "name": "[parameters('httpRuleName')]", + "properties": { + "frontendIPConfiguration": { + "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', parameters('loadBalancerName'), parameters('frontendIPName'))]" + }, + "frontendPort": 80, + "backendPort": 80, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 15, + "protocol": "Tcp", + "enableTcpReset": true, + "loadDistribution": "Default", + "disableOutboundSnat": false, + "backendAddressPools": [ + { + "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', parameters('loadBalancerName'), parameters('backendAddressPoolName'))]" + } + ], + "probe": "[if(variables('hasReadinessProbe'), createObject('id', resourceId('Microsoft.Network/loadBalancers/probes', parameters('loadBalancerName'), 'readinessProbe')), null())]" + } + } + ], + "inboundNatRules": [ + { + "name": "[parameters('inboundNatRuleName')]", + "properties": { + "backendAddressPool": { + "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', parameters('loadBalancerName'), parameters('backendAddressPoolName'))]" + }, + "backendPort": 80, + "enableFloatingIP": false, + "enableTcpReset": false, + "frontendIPConfiguration": { + "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', parameters('loadBalancerName'), parameters('frontendIPName'))]" + }, + "frontendPortRangeEnd": 331, + "frontendPortRangeStart": 81, + "idleTimeoutInMinutes": 4, + "protocol": "Tcp" + } + } + ], + "outboundRules": [], + "inboundNatPools": [] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + ] + }, + { + "type": "Microsoft.ContainerInstance/containerGroupProfiles", + "apiVersion": "2024-09-01-preview", + "name": "[variables('cgProfileName')]", + "location": "[resourceGroup().location]", + "properties": { + "sku": "Standard", + "containers": [ + { + "name": "web", + "properties": { + "image": "[parameters('context').properties.containers.image]", + "ports": [ + { + "protocol": "[if(not(equals(parameters('context').properties.containers.ports, null())), coalesce(parameters('context').properties.containers.ports.additionalProperties.properties.protocol, 'TCP'), 'TCP')]", + "port": "[if(not(equals(parameters('context').properties.containers.ports, null())), parameters('context').properties.containers.ports.additionalProperties.properties.containerPort, 80)]" + } + ], + "resources": { + "requests": { + "memoryInGB": "[coalesce(div(tryGet(tryGet(parameters('context').properties.containers.resources, 'requests'), 'memoryInMib'), 1024), json('1.0'))]", + "cpu": "[coalesce(tryGet(tryGet(parameters('context').properties.containers.resources, 'requests'), 'cpu'), json('1.0'))]" + } + }, + "volumeMounts": [ + { + "name": "cacheVolume", + "mountPath": "/mnt/cache" + } + ] + } + } + ], + "volumes": [ + { + "name": "cacheVolume", + "emptyDir": {} + } + ], + "restartPolicy": "Always", + "ipAddress": { + "ports": [ + { + "protocol": "TCP", + "port": 80 + } + ], + "type": "Private" + }, + "osType": "Linux" + } + }, + { + "type": "Microsoft.ContainerInstance/ngroups", + "apiVersion": "2024-09-01-preview", + "name": "[variables('nGroupsName')]", + "location": "[resourceGroup().location]", + "zones": "[parameters('zones')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "elasticProfile": { + "desiredCount": "[coalesce(parameters('context').properties.replicas, 2)]", + "maintainDesiredCount": "[parameters('maintainDesiredCount')]" + }, + "updateProfile": { + "updateMode": "Rolling" + }, + "containerGroupProfiles": [ + { + "resource": { + "id": "[format('{0}Microsoft.ContainerInstance/containerGroupProfiles/{1}', variables('resourcePrefix'), variables('cgProfileName'))]" + }, + "containerGroupProperties": { + "subnetIds": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]", + "name": "[parameters('subnetName')]" + } + ] + }, + "networkProfile": { + "loadBalancer": { + "backendAddressPools": [ + { + "resource": { + "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', parameters('loadBalancerName'), parameters('backendAddressPoolName'))]" + } + } + ] + } + } + } + ] + }, + "tags": { + "reprovision.enabled": "true", + "metadata.container.environmentVariable.orchestratorId": "true", + "rollingupdate.replace.enabled": "true" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerInstance/containerGroupProfiles', variables('cgProfileName'))]", + "[resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + ] + } + ], + "outputs": { + "virtualNetworkId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" + }, + "subnetId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2022-07-01').subnets[0].id]" + }, + "loadBalancerId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName'))]" + }, + "frontendIPConfigurationId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName')), '2022-07-01').frontendIPConfigurations[0].id]" + }, + "backendAddressPoolId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/loadBalancers', parameters('loadBalancerName')), '2022-07-01').backendAddressPools[0].id]" + }, + "inboundPublicIPId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName'))]" + }, + "outboundPublicIPId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('outboundPublicIPName'))]" + }, + "inboundPublicIPFQDN": { + "type": "string", + "value": "[if(and(contains(reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName')), '2022-07-01'), 'dnsSettings'), not(equals(reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName')), '2022-07-01').dnsSettings, null()))), reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('inboundPublicIPName')), '2022-07-01').dnsSettings.fqdn, '')]" + }, + "natGatewayId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/natGateways', parameters('natGatewayName'))]" + }, + "networkSecurityGroupId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + }, + "ddosProtectionPlanId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/ddosProtectionPlans', variables('ddosProtectionPlanName'))]" + }, + "containerGroupProfileId": { + "type": "string", + "value": "[resourceId('Microsoft.ContainerInstance/containerGroupProfiles', variables('cgProfileName'))]" + }, + "nGroupsId": { + "type": "string", + "value": "[resourceId('Microsoft.ContainerInstance/ngroups', variables('nGroupsName'))]" + }, + "readinessProbeId": { + "type": "string", + "value": "[if(variables('hasReadinessProbe'), resourceId('Microsoft.Network/loadBalancers/probes', parameters('loadBalancerName'), 'readinessProbe'), '')]" + }, + "livenessProbeId": { + "type": "string", + "value": "[if(variables('hasLivenessProbe'), resourceId('Microsoft.Network/loadBalancers/probes', parameters('loadBalancerName'), 'livenessProbe'), '')]" + } + } +} \ No newline at end of file diff --git a/recipe-packs/terraform/README.md b/recipe-packs/terraform/README.md new file mode 100644 index 00000000..5ffffebe --- /dev/null +++ b/recipe-packs/terraform/README.md @@ -0,0 +1,139 @@ +# Terraform Configurations for Azure Container Instance NGroups + +This directory contains Terraform configurations for deploying Azure Container Instance NGroups with different networking setups. + +## Directory Structure + +``` +terraform/ +├── README.md # This file +├── ngroups-basic/ # Basic NGroups configuration +│ ├── main.tf # Basic setup with public networking +│ ├── variables.tf # Variable definitions +│ ├── outputs.tf # Output values +│ ├── terraform.tfvars.example # Example variable values +│ └── README.md # Detailed documentation +└── ngroups-gateway/ # Advanced NGroups with Application Gateway + ├── main.tf # Full networking stack with App Gateway + ├── variables.tf # Variable definitions + ├── outputs.tf # Output values + ├── terraform.tfvars.example # Example variable values + └── README.md # Detailed documentation +``` + +## Configurations + +### 1. NGroups Basic (`ngroups-basic/`) + +**Purpose**: Simple deployment of Container Group Profile and NGroups with public networking. + +**Use Cases**: +- Development and testing +- Simple container workloads +- Quick proof-of-concept deployments + +**Resources Created**: +- Container Group Profile with public IP +- NGroups with basic elastic scaling + +**Based on ARM Template**: `ACI-Ngroups-basic.yaml` + +### 2. NGroups Gateway (`ngroups-gateway/`) + +**Purpose**: Production-ready deployment with Application Gateway, Virtual Network, and comprehensive networking. + +**Use Cases**: +- Production workloads +- Load-balanced container applications +- Secure private networking +- High-availability setups + +**Resources Created**: +- Virtual Network with dedicated subnets +- Network Security Group with security rules +- Public IP and Application Gateway +- Container Group Profile with private networking +- NGroups with Application Gateway integration + +**Based on ARM Template**: `ACI-NGroups-Gateway.yaml` + +## Quick Start + +### Basic Configuration + +```bash +cd ngroups-basic +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your resource group name +terraform init +terraform plan +terraform apply +``` + +### Gateway Configuration + +```bash +cd ngroups-gateway +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your resource group name and desired settings +terraform init +terraform plan +terraform apply +``` + +## Prerequisites + +- **Terraform** >= 0.14 +- **Azure CLI** installed and authenticated +- **Azure Subscription** with appropriate permissions +- **Existing Resource Group** for deployment + +## Authentication + +Ensure you're authenticated with Azure: + +```bash +az login +az account set --subscription "your-subscription-id" +``` + +## Important Notes + +### Preview Features +Both configurations use Azure Container Instance preview features: +- **Container Group Profiles** +- **NGroups** + +These are deployed using ARM templates within Terraform until native support is available. + +### Resource Dependencies +- Basic: Minimal dependencies, quick deployment +- Gateway: Complex networking dependencies, longer deployment time + +### Cost Considerations +- Basic: Lower cost, minimal networking resources +- Gateway: Higher cost due to Application Gateway and networking components + +## Migration Between Configurations + +You can migrate from basic to gateway configuration: + +1. Export container configuration from basic setup +2. Deploy gateway configuration +3. Update DNS/routing to point to new Application Gateway +4. Destroy basic setup + +## Support and Troubleshooting + +- Check individual README files in each directory for detailed troubleshooting +- Review Azure Portal for resource deployment status +- Use `terraform plan` to preview changes before applying + +## Contributing + +When adding new configurations: +1. Create a new directory under `terraform/` +2. Include all standard Terraform files (main.tf, variables.tf, outputs.tf) +3. Provide comprehensive README documentation +4. Include example tfvars file +5. Update this main README with the new configuration details diff --git a/recipe-packs/terraform/aci-ngroups-confidential/README.md b/recipe-packs/terraform/aci-ngroups-confidential/README.md new file mode 100644 index 00000000..62fcdf6b --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-confidential/README.md @@ -0,0 +1,222 @@ +# Azure Container Instance NGroups with Confidential Computing - Terraform Configuration + +This Terraform configuration converts the ARM template for Azure Container Instance NGroups with Confidential Computing capabilities to Terraform format. This demonstrates the use of confidential computing features with elastic container scaling. + +## Architecture Overview + +This configuration deploys: + +1. **Container Group Profile** with Confidential Computing SKU +2. **NGroups** for elastic scaling of confidential container instances +3. **Confidential Compute Environment** with CCE (Confidential Computing Enclave) policy support + +## Prerequisites + +- Terraform >= 0.14 +- Azure CLI installed and authenticated +- An existing Azure Resource Group +- Azure subscription with access to Confidential Computing features +- Sufficient Azure permissions to create Container Instance resources + +## Resources Created + +### Confidential Computing Resources +- **Container Group Profile** (`azurerm_resource_group_template_deployment`) with Confidential SKU +- **NGroups** (`azurerm_resource_group_template_deployment`) for elastic scaling +- **CCE Policy** configuration for confidential computing + +## Usage + +### 1. Setup Variables + +Copy the example variables file: +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars` with your specific values: +```hcl +resource_group_name = "your-resource-group-name" +desired_count = 1 # Start with 1 for confidential computing +``` + +### 2. Initialize Terraform + +```bash +terraform init +``` + +### 3. Plan the Deployment + +```bash +terraform plan +``` + +### 4. Apply the Configuration + +```bash +terraform apply +``` + +## Configuration Details + +### Confidential Computing Features + +- **SKU**: Confidential (specialized for secure enclaves) +- **CCE Policy**: Configurable Confidential Computing Enclave policy +- **Secure Execution**: Containers run in hardware-protected environments +- **Memory Encryption**: Runtime memory encryption +- **Attestation**: Support for remote attestation of the secure environment + +### Container Configuration + +- **Desired Count**: 1 instance (default, suitable for confidential workloads) +- **Container Image**: ACI Hello World (configurable) +- **Networking**: Public IP addresses (configurable to Private) +- **Auto-scaling**: Managed by NGroups +- **Resource Allocation**: 1 CPU, 1GB RAM (optimized for confidential computing) + +### Security Features + +- **Hardware-level Security**: Intel SGX or AMD SEV-based protection +- **Encrypted Memory**: Runtime memory encryption +- **Secure Boot**: Verified boot process +- **Isolation**: Strong isolation between containers and host + +## Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `resource_group_name` | Resource Group name | - | ✅ | +| `api_version` | Container Instance API version | `2024-09-01-preview` | ❌ | +| `container_group_profile_name` | Profile name | `cgp_1` | ❌ | +| `ngroups_name` | NGroups name | `ngroup_confidential_basic` | ❌ | +| `desired_count` | Desired container count | `1` | ❌ | +| `prefix_cg` | Container naming prefix | `cg-confidential-` | ❌ | +| `container_image` | Container image | ACI Hello World | ❌ | +| `tags` | Resource tags | `{}` | ❌ | + +See `variables.tf` for complete variable list. + +## Outputs + +Key outputs include: +- Container Group Profile ID +- NGroups ID +- Resource Group name +- ARM deployment names + +## Important Notes + +### Preview Features +- **Confidential Computing** is a preview feature +- **Container Group Profiles** and **NGroups** are preview features +- These resources use ARM template deployments within Terraform +- Future versions may support native Terraform resources + +### Confidential Computing Considerations +- Limited to specific Azure regions with confidential computing support +- Requires specialized VM SKUs (DCsv2, DCsv3, etc.) +- May have different pricing compared to standard container instances +- Performance characteristics may differ from standard containers + +### Dependencies +- Confidential computing hardware availability in target region +- Proper Azure subscription permissions for confidential computing +- Compatible container images (some images may need modification) + +### Security and Compliance +- Ideal for processing sensitive data +- Supports regulatory compliance requirements +- Provides hardware-level data protection +- Enables secure multi-party computation scenarios + +## Troubleshooting + +### Common Issues + +1. **Region Availability**: Ensure confidential computing is available in your region +2. **Subscription Limits**: Check quota limits for confidential computing resources +3. **Container Compatibility**: Verify container images work with confidential computing +4. **CCE Policy**: Ensure CCE policy is properly configured if using custom policies + +### Monitoring + +Monitor the deployment through: +- Azure Portal for resource status +- Container Instance logs for application health +- Azure Monitor for confidential computing metrics +- Attestation logs for security verification + +## Cost Considerations + +Confidential computing resources typically have: +- **Premium Pricing**: Higher cost than standard container instances +- **Specialized Hardware**: Limited availability may affect pricing +- **Per-second Billing**: Based on confidential computing resources +- **Regional Variations**: Pricing may vary by region + +## Clean Up + +To destroy all resources: +```bash +terraform destroy +``` + +**Note**: This will delete all confidential computing resources. + +## Security Best Practices + +### CCE Policy Management +- Use specific CCE policies for production workloads +- Regularly update and validate policies +- Implement proper key management for encrypted data + +### Container Security +- Use verified and signed container images +- Implement proper access controls +- Monitor for security events and attestation failures + +### Network Security +- Consider using Private IP addresses for production +- Implement proper network segmentation +- Use Azure Private Link where applicable + +## Migration Path + +When native Terraform support becomes available: +1. Replace ARM template deployments with native resources +2. Update variable references +3. Test the migration in a development environment +4. Validate confidential computing functionality + +## Performance Considerations + +### Confidential Computing Performance +- **Startup Time**: May be longer than standard containers +- **Memory Overhead**: Additional memory used for security features +- **CPU Performance**: Slight overhead for encryption/decryption +- **Network Latency**: Potential impact from security processing + +### Scaling Behavior +- **Conservative Scaling**: Start with lower desired counts +- **Monitoring**: Close monitoring of performance metrics +- **Resource Allocation**: Adequate CPU/memory for security overhead + +## Use Cases + +### Ideal Scenarios +- **Sensitive Data Processing**: Financial, healthcare, personal data +- **Regulatory Compliance**: GDPR, HIPAA, SOX requirements +- **Multi-party Computation**: Secure collaboration between organizations +- **Edge Computing**: Secure processing at edge locations + +### Example Applications +- **Data Analytics**: Secure analysis of sensitive datasets +- **AI/ML Workloads**: Confidential machine learning training +- **Database Processing**: Secure database operations +- **API Services**: Secure API endpoints for sensitive operations + +## Support + +This configuration is based on the ARM template `ACI-NGroups-Confidential.yaml` and provides confidential computing capabilities while maintaining Terraform workflow benefits. diff --git a/recipe-packs/terraform/aci-ngroups-confidential/main.tf b/recipe-packs/terraform/aci-ngroups-confidential/main.tf new file mode 100644 index 00000000..7e40dfe5 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-confidential/main.tf @@ -0,0 +1,198 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.0" + } + } +} + +provider "azurerm" { + features {} +} + +# Data source for current resource group +data "azurerm_resource_group" "main" { + name = var.resource_group_name +} + +# Data source for current client configuration +data "azurerm_client_config" "current" {} + +# Container Group Profile ARM Template Deployment +resource "azurerm_resource_group_template_deployment" "container_group_profile" { + name = "cgp-confidential-deployment" + resource_group_name = data.azurerm_resource_group.main.name + deployment_mode = "Incremental" + + template_content = jsonencode({ + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = { + cgProfileName = { + type = "string" + } + apiVersion = { + type = "string" + } + } + resources = [ + { + apiVersion = "[parameters('apiVersion')]" + type = "Microsoft.ContainerInstance/containerGroupProfiles" + name = "[parameters('cgProfileName')]" + location = "[resourceGroup().location]" + properties = { + sku = "Confidential" + confidentialComputeProperties = { + ccePolicy = "" + } + containers = [ + { + name = "aci-helloworld" + properties = { + image = var.container_image + ports = [ + { + protocol = "TCP" + port = 80 + } + ] + resources = { + requests = { + memoryInGB = 1.0 + cpu = 1.0 + } + } + } + } + ] + restartPolicy = "Always" + ipAddress = { + ports = [ + { + protocol = "TCP" + port = 80 + } + ] + type = "Public" + } + osType = "Linux" + } + } + ] + outputs = { + containerGroupProfileId = { + type = "string" + value = "[resourceId('Microsoft.ContainerInstance/containerGroupProfiles', parameters('cgProfileName'))]" + } + } + }) + + parameters_content = jsonencode({ + cgProfileName = { + value = var.container_group_profile_name + } + apiVersion = { + value = var.api_version + } + }) + + tags = var.tags +} + +# NGroups ARM Template Deployment +resource "azurerm_resource_group_template_deployment" "ngroups" { + name = "ngroups-confidential-deployment" + resource_group_name = data.azurerm_resource_group.main.name + deployment_mode = "Incremental" + + template_content = jsonencode({ + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = { + nGroupsName = { + type = "string" + } + cgProfileName = { + type = "string" + } + apiVersion = { + type = "string" + } + desiredCount = { + type = "int" + } + prefixCG = { + type = "string" + } + resourcePrefix = { + type = "string" + } + } + resources = [ + { + apiVersion = "[parameters('apiVersion')]" + type = "Microsoft.ContainerInstance/NGroups" + name = "[parameters('nGroupsName')]" + location = "[resourceGroup().location]" + dependsOn = [ + "[concat('Microsoft.ContainerInstance/containerGroupProfiles/', parameters('cgProfileName'))]" + ] + properties = { + elasticProfile = { + desiredCount = "[parameters('desiredCount')]" + containerGroupNamingPolicy = { + guidNamingPolicy = { + prefix = "[parameters('prefixCG')]" + } + } + } + containerGroupProfiles = [ + { + resource = { + id = "[concat(parameters('resourcePrefix'), 'Microsoft.ContainerInstance/containerGroupProfiles/', parameters('cgProfileName'))]" + } + } + ] + } + tags = { + "cirrusTestScenario" = "confidential-1.basic" + } + } + ] + outputs = { + nGroupId = { + type = "string" + value = "[resourceId('Microsoft.ContainerInstance/NGroups', parameters('nGroupsName'))]" + } + } + }) + + parameters_content = jsonencode({ + nGroupsName = { + value = var.ngroups_name + } + cgProfileName = { + value = var.container_group_profile_name + } + apiVersion = { + value = var.api_version + } + desiredCount = { + value = var.desired_count + } + prefixCG = { + value = var.prefix_cg + } + resourcePrefix = { + value = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${data.azurerm_resource_group.main.name}/providers/" + } + }) + + tags = var.tags + + depends_on = [ + azurerm_resource_group_template_deployment.container_group_profile + ] +} diff --git a/recipe-packs/terraform/aci-ngroups-confidential/outputs.tf b/recipe-packs/terraform/aci-ngroups-confidential/outputs.tf new file mode 100644 index 00000000..b3091b8b --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-confidential/outputs.tf @@ -0,0 +1,22 @@ +output "container_group_profile_id" { + description = "The ID of the Container Group Profile" + value = jsondecode(azurerm_resource_group_template_deployment.container_group_profile.output_content).containerGroupProfileId.value +} + +output "ngroups_id" { + description = "The ID of the NGroups resource" + value = jsondecode(azurerm_resource_group_template_deployment.ngroups.output_content).nGroupId.value +} + +output "resource_group_name" { + description = "The name of the resource group" + value = data.azurerm_resource_group.main.name +} + +output "deployment_names" { + description = "Names of the ARM template deployments" + value = { + container_group_profile = azurerm_resource_group_template_deployment.container_group_profile.name + ngroups = azurerm_resource_group_template_deployment.ngroups.name + } +} diff --git a/recipe-packs/terraform/aci-ngroups-confidential/terraform.tfvars.example b/recipe-packs/terraform/aci-ngroups-confidential/terraform.tfvars.example new file mode 100644 index 00000000..09b48366 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-confidential/terraform.tfvars.example @@ -0,0 +1,23 @@ +# Example terraform.tfvars file +# Copy this file to terraform.tfvars and update the values + +# Required: Resource group name where resources will be deployed +resource_group_name = "your-resource-group-name" + +# Optional: Override default values if needed +# api_version = "2024-09-01-preview" +# container_group_profile_name = "cgp_1" +# ngroups_name = "ngroup_confidential_basic" +# desired_count = 1 +# prefix_cg = "cg-confidential-" + +# Optional: Custom container image +# container_image = "mcr.microsoft.com/azuredocs/aci-helloworld@sha256:565dba8ce20ca1a311c2d9485089d7ddc935dd50140510050345a1b0ea4ffa6e" + +# Optional: Custom tags +# tags = { +# scenario = "confidential-computing" +# purpose = "demo" +# environment = "test" +# owner = "your-name" +# } diff --git a/recipe-packs/terraform/aci-ngroups-confidential/variables.tf b/recipe-packs/terraform/aci-ngroups-confidential/variables.tf new file mode 100644 index 00000000..a539a6c7 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-confidential/variables.tf @@ -0,0 +1,49 @@ +variable "resource_group_name" { + description = "The name of the resource group where resources will be deployed" + type = string +} + +variable "api_version" { + description = "Container Instance API version" + type = string + default = "2024-09-01-preview" +} + +variable "container_group_profile_name" { + description = "Name of the Container Group Profile" + type = string + default = "cgp_1" +} + +variable "ngroups_name" { + description = "Name of the NGroups resource" + type = string + default = "ngroup_confidential_basic" +} + +variable "desired_count" { + description = "Desired number of container instances" + type = number + default = 1 +} + +variable "prefix_cg" { + description = "Prefix for container group naming" + type = string + default = "cg-confidential-" +} + +variable "container_image" { + description = "Container image to deploy" + type = string + default = "mcr.microsoft.com/azuredocs/aci-helloworld@sha256:565dba8ce20ca1a311c2d9485089d7ddc935dd50140510050345a1b0ea4ffa6e" +} + +variable "tags" { + description = "Tags to apply to resources" + type = map(string) + default = { + scenario = "confidential-computing" + purpose = "demo" + } +} diff --git a/recipe-packs/terraform/aci-ngroups-loadbalancer/README.md b/recipe-packs/terraform/aci-ngroups-loadbalancer/README.md new file mode 100644 index 00000000..8dbb3071 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-loadbalancer/README.md @@ -0,0 +1,224 @@ +# Azure Container Instance NGroups with Load Balancer - Terraform Configuration + +This Terraform configuration converts the ARM template for Azure Container Instance NGroups with Load Balancer integration to Terraform format. This is an advanced setup that includes comprehensive networking infrastructure, load balancing, NAT gateway, and DDoS protection. + +## Architecture Overview + +This configuration deploys a complete infrastructure stack: + +1. **Virtual Network** with dedicated subnet and Container Instance delegation +2. **Network Security Group** with security rules for HTTP traffic (ports 80-331) +3. **DDoS Protection Plan** for enhanced security +4. **NAT Gateway** for outbound internet connectivity +5. **Public IP addresses** for inbound and outbound traffic +6. **Standard Load Balancer** with health probes and load balancing rules +7. **Container Group Profile** with private networking +8. **NGroups** with Load Balancer integration for elastic scaling + +## Prerequisites + +- Terraform >= 0.14 +- Azure CLI installed and authenticated +- An existing Azure Resource Group +- Sufficient Azure permissions to create networking, load balancing, and container resources + +## Resources Created + +### Networking Infrastructure +- **Virtual Network** (`azurerm_virtual_network`) with DDoS protection +- **Subnet** with Container Instance delegation (`azurerm_subnet`) +- **Network Security Group** with HTTP security rules (`azurerm_network_security_group`) +- **DDoS Protection Plan** (`azurerm_network_ddos_protection_plan`) + +### Public IP and NAT +- **Inbound Public IP** for Load Balancer (`azurerm_public_ip`) +- **Outbound Public IP** for NAT Gateway (`azurerm_public_ip`) +- **NAT Gateway** for outbound connectivity (`azurerm_nat_gateway`) + +### Load Balancing +- **Standard Load Balancer** (`azurerm_lb`) +- **Backend Address Pool** (`azurerm_lb_backend_address_pool`) +- **Health Probe** (`azurerm_lb_probe`) +- **Load Balancing Rule** (`azurerm_lb_rule`) +- **Inbound NAT Rule** for port range 81-331 (`azurerm_lb_nat_rule`) + +### Container Instance Resources +- **Container Group Profile** (via ARM template deployment) +- **NGroups** with Load Balancer integration (via ARM template deployment) + +## Usage + +### 1. Setup Variables + +Copy the example variables file: +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars` with your specific values: +```hcl +resource_group_name = "your-resource-group-name" +desired_count = 50 # Adjust based on your needs +domain_name_label = "your-unique-domain-label" +``` + +### 2. Initialize Terraform + +```bash +terraform init +``` + +### 3. Plan the Deployment + +```bash +terraform plan +``` + +### 4. Apply the Configuration + +```bash +terraform apply +``` + +## Configuration Details + +### Network Configuration + +- **VNet Address Space**: `172.19.0.0/16` (default) +- **Subnet Address Space**: `172.19.1.0/24` (default) +- **Container Instance Delegation**: Enabled on subnet +- **NAT Gateway**: Provides outbound internet access +- **DDoS Protection**: Standard protection enabled + +### Security Configuration + +The Network Security Group includes: +- **AllowHTTPInbound**: Allows internet traffic on ports 80-331 +- **Priority**: 100 +- **Protocol**: All protocols (*) + +### Load Balancer Configuration + +- **SKU**: Standard Load Balancer +- **Frontend**: Public IP with configurable domain name +- **Backend Pool**: Integrated with NGroups container instances +- **Health Probe**: TCP probe on port 80 +- **Load Balancing Rule**: HTTP traffic (port 80) +- **NAT Rule**: Port range 81-331 for direct access + +### Container Configuration + +- **Desired Count**: 100 instances (default, configurable) +- **Maintain Desired Count**: Enabled +- **Container Image**: ACI Hello World (configurable) +- **Networking**: Private IP addresses within VNet +- **Auto-scaling**: Managed by NGroups +- **Load Balancer Integration**: Automatic backend pool membership + +## Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `resource_group_name` | Resource Group name | - | ✅ | +| `api_version` | Container Instance API version | `2024-09-01-preview` | ❌ | +| `desired_count` | Desired container count | `100` | ❌ | +| `vnet_address_prefix` | VNet address space | `172.19.0.0/16` | ❌ | +| `subnet_address_prefix` | Subnet CIDR | `172.19.1.0/24` | ❌ | +| `domain_name_label` | Public IP domain label | `ngroupsdemo` | ❌ | +| `container_image` | Container image | ACI Hello World | ❌ | +| `maintain_desired_count` | Maintain desired count | `true` | ❌ | + +See `variables.tf` for complete variable list. + +## Outputs + +Key outputs include: +- Virtual Network and subnet IDs +- Load Balancer ID and frontend IP +- Public IP addresses and FQDN +- Backend address pool and health probe IDs +- NAT Gateway and DDoS protection plan IDs +- Container Group Profile and NGroups deployment IDs + +## Important Notes + +### Preview Features +- **Container Group Profiles** and **NGroups** are preview features +- These resources use ARM template deployments within Terraform +- Future versions may support native Terraform resources + +### Dependencies +- The configuration properly handles complex resource dependencies +- Load Balancer is created before NGroups deployment +- NAT Gateway and DDoS protection are configured before VNet + +### Scaling and Performance +- NGroups automatically manages container instance scaling +- Load Balancer distributes traffic across container instances +- Health probes ensure only healthy instances receive traffic +- NAT Gateway provides dedicated outbound connectivity + +### Security Considerations +- DDoS protection provides enhanced security +- Network Security Group controls inbound traffic +- Private networking within VNet for container instances +- Outbound traffic routed through NAT Gateway + +## Troubleshooting + +### Common Issues + +1. **Domain Name Label Conflicts**: Ensure unique domain name labels +2. **Address Space Conflicts**: Verify subnet CIDRs don't overlap +3. **Load Balancer Startup**: May take 10-15 minutes to fully provision +4. **Container Health**: Check that containers respond to TCP health probes on port 80 + +### Monitoring + +Monitor the deployment through: +- Azure Portal for resource status +- Load Balancer metrics for traffic distribution +- Container Instance logs for application health +- NAT Gateway metrics for outbound connectivity + +## Cost Considerations + +This configuration includes several billable resources: +- **Standard Load Balancer**: Hourly charges + data processing +- **NAT Gateway**: Hourly charges + data processing +- **DDoS Protection Plan**: Monthly subscription fee +- **Public IP addresses**: Hourly charges for static IPs +- **Container Instances**: Per-second billing based on resources + +## Clean Up + +To destroy all resources: +```bash +terraform destroy +``` + +**Note**: This will delete all networking infrastructure and container resources. + +## Migration Path + +When native Terraform support becomes available for Container Group Profiles and NGroups: +1. Replace ARM template deployments with native resources +2. Update variable references +3. Test the migration in a development environment + +## Performance and Scaling + +### Load Balancer Features +- **Session Persistence**: Configurable via load balancing rules +- **Health Monitoring**: TCP health probes with configurable thresholds +- **Traffic Distribution**: Default round-robin distribution +- **Connection Draining**: Graceful handling of unhealthy instances + +### NGroups Auto-scaling +- **Desired Count Management**: Automatically maintains specified instance count +- **Zone Distribution**: Optional availability zone distribution +- **Container Lifecycle**: Automatic container restart and replacement + +## Support + +This configuration is based on the ARM template `ACI-NGroups-LoadBalancer.yaml` and maintains feature parity while providing Terraform workflow benefits. diff --git a/recipe-packs/terraform/aci-ngroups-loadbalancer/main.tf b/recipe-packs/terraform/aci-ngroups-loadbalancer/main.tf new file mode 100644 index 00000000..5b0b9956 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-loadbalancer/main.tf @@ -0,0 +1,387 @@ +# Configure the Azure Provider +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~>3.0" + } + } +} + +# Configure the Microsoft Azure Provider +provider "azurerm" { + features {} +} + +# Data source to get current resource group +data "azurerm_resource_group" "current" { + name = var.resource_group_name +} + +# Data source to get current subscription +data "azurerm_client_config" "current" {} + +# Local variables +locals { + api_version = var.api_version + ngroups_name = var.ngroups_param_name + container_group_profile_name = var.container_group_profile_name + load_balancer_name = var.load_balancer_name + backend_address_pool_name = var.backend_address_pool_name + vnet_name = var.vnet_name + subnet_name = var.subnet_name + network_security_group_name = var.network_security_group_name + inbound_public_ip_name = var.inbound_public_ip_name + outbound_public_ip_name = var.outbound_public_ip_name + outbound_public_ip_prefix_name = var.outbound_public_ip_prefix_name + nat_gateway_name = var.nat_gateway_name + frontend_ip_name = var.frontend_ip_name + http_rule_name = var.http_rule_name + health_probe_name = var.health_probe_name + vnet_address_prefix = var.vnet_address_prefix + subnet_address_prefix = var.subnet_address_prefix + desired_count = var.desired_count + zones = var.zones + maintain_desired_count = var.maintain_desired_count + domain_name_label = var.domain_name_label + inbound_nat_rule_name = var.inbound_nat_rule_name + + description = "This ARM template is an example template to test the load balancer integration with NGroups." + cg_profile_name = var.container_group_profile_name + prefix_cg = "cg-lin100-regional-lb-" + resource_prefix = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${data.azurerm_resource_group.current.name}/providers/" + load_balancer_api_version = "2022-07-01" + vnet_api_version = "2022-07-01" + public_ip_version = "2022-07-01" + ddos_protection_plan_name = "ddosProtectionPlan" +} + +# DDoS Protection Plan +resource "azurerm_network_ddos_protection_plan" "ddos_plan" { + name = local.ddos_protection_plan_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name +} + +# Network Security Group +resource "azurerm_network_security_group" "nsg" { + name = local.network_security_group_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + + security_rule { + name = "AllowHTTPInbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_ranges = ["80-331"] + source_address_prefix = "Internet" + destination_address_prefix = "*" + description = "Allow Internet traffic on port range" + } +} + +# Outbound Public IP for NAT Gateway +resource "azurerm_public_ip" "outbound_public_ip" { + name = local.outbound_public_ip_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + allocation_method = "Static" + sku = "Standard" + sku_tier = "Regional" + ip_version = "IPv4" + idle_timeout_in_minutes = 4 +} + +# Inbound Public IP for Load Balancer +resource "azurerm_public_ip" "inbound_public_ip" { + name = local.inbound_public_ip_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + allocation_method = "Static" + sku = "Standard" + sku_tier = "Regional" + ip_version = "IPv4" + idle_timeout_in_minutes = 4 + domain_name_label = local.domain_name_label +} + +# NAT Gateway +resource "azurerm_nat_gateway" "nat_gateway" { + name = local.nat_gateway_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + sku_name = "Standard" + idle_timeout_in_minutes = 4 +} + +# Associate NAT Gateway with Outbound Public IP +resource "azurerm_nat_gateway_public_ip_association" "nat_gateway_ip" { + nat_gateway_id = azurerm_nat_gateway.nat_gateway.id + public_ip_address_id = azurerm_public_ip.outbound_public_ip.id +} + +# Virtual Network +resource "azurerm_virtual_network" "vnet" { + name = local.vnet_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + address_space = [local.vnet_address_prefix] + + ddos_protection_plan { + id = azurerm_network_ddos_protection_plan.ddos_plan.id + enable = true + } + + depends_on = [ + azurerm_network_security_group.nsg, + azurerm_nat_gateway.nat_gateway + ] +} + +# Subnet +resource "azurerm_subnet" "subnet" { + name = local.subnet_name + resource_group_name = data.azurerm_resource_group.current.name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = [local.subnet_address_prefix] + + delegation { + name = "Microsoft.ContainerInstance.containerGroups" + + service_delegation { + name = "Microsoft.ContainerInstance/containerGroups" + } + } + + private_endpoint_network_policies_enabled = false + private_link_service_network_policies_enabled = true + + depends_on = [azurerm_virtual_network.vnet] +} + +# Associate NSG with Subnet +resource "azurerm_subnet_network_security_group_association" "subnet_nsg" { + subnet_id = azurerm_subnet.subnet.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +# Associate NAT Gateway with Subnet +resource "azurerm_subnet_nat_gateway_association" "subnet_nat" { + subnet_id = azurerm_subnet.subnet.id + nat_gateway_id = azurerm_nat_gateway.nat_gateway.id +} + +# Load Balancer +resource "azurerm_lb" "load_balancer" { + name = local.load_balancer_name + location = data.azurerm_resource_group.current.location + resource_group_name = data.azurerm_resource_group.current.name + sku = "Standard" + + frontend_ip_configuration { + name = local.frontend_ip_name + public_ip_address_id = azurerm_public_ip.inbound_public_ip.id + } + + depends_on = [ + azurerm_public_ip.inbound_public_ip, + azurerm_virtual_network.vnet + ] +} + +# Load Balancer Backend Address Pool +resource "azurerm_lb_backend_address_pool" "backend_pool" { + loadbalancer_id = azurerm_lb.load_balancer.id + name = local.backend_address_pool_name +} + +# Load Balancer Health Probe +resource "azurerm_lb_probe" "health_probe" { + loadbalancer_id = azurerm_lb.load_balancer.id + name = local.health_probe_name + protocol = "Tcp" + port = 80 + interval_in_seconds = 5 + number_of_probes = 2 + probe_threshold = 1 +} + +# Load Balancer Rule +resource "azurerm_lb_rule" "http_rule" { + loadbalancer_id = azurerm_lb.load_balancer.id + name = local.http_rule_name + protocol = "Tcp" + frontend_port = 80 + backend_port = 80 + frontend_ip_configuration_name = local.frontend_ip_name + backend_address_pool_ids = [azurerm_lb_backend_address_pool.backend_pool.id] + probe_id = azurerm_lb_probe.health_probe.id + enable_floating_ip = false + idle_timeout_in_minutes = 15 + enable_tcp_reset = true + load_distribution = "Default" + disable_outbound_snat = false +} + +# Load Balancer Inbound NAT Rule +resource "azurerm_lb_nat_rule" "inbound_nat_rule" { + resource_group_name = data.azurerm_resource_group.current.name + loadbalancer_id = azurerm_lb.load_balancer.id + name = local.inbound_nat_rule_name + protocol = "Tcp" + frontend_port_start = 81 + frontend_port_end = 331 + backend_port = 80 + frontend_ip_configuration_name = local.frontend_ip_name + enable_floating_ip = false + enable_tcp_reset = false + idle_timeout_in_minutes = 4 + backend_address_pool_id = azurerm_lb_backend_address_pool.backend_pool.id +} + +# Template deployment for Container Group Profile (using ARM template within Terraform) +resource "azurerm_resource_group_template_deployment" "container_group_profile" { + name = "cgp-deployment" + resource_group_name = data.azurerm_resource_group.current.name + deployment_mode = "Incremental" + + template_content = jsonencode({ + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = {} + variables = { + cgProfileName = local.cg_profile_name + apiVersion = local.api_version + } + resources = [ + { + apiVersion = local.api_version + type = "Microsoft.ContainerInstance/containerGroupProfiles" + name = local.cg_profile_name + location = data.azurerm_resource_group.current.location + properties = { + sku = "Standard" + containers = [ + { + name = "web" + properties = { + image = var.container_image + ports = [ + { + protocol = "TCP" + port = var.container_port + } + ] + resources = { + requests = { + memoryInGB = var.memory_gb + cpu = var.cpu_cores + } + } + } + } + ] + restartPolicy = "Always" + ipAddress = { + ports = [ + { + protocol = "TCP" + port = var.container_port + } + ] + type = "Private" + } + osType = "Linux" + } + } + ] + }) +} + +# Template deployment for NGroups +resource "azurerm_resource_group_template_deployment" "ngroups" { + name = "ngroups-deployment" + resource_group_name = data.azurerm_resource_group.current.name + deployment_mode = "Incremental" + + depends_on = [ + azurerm_resource_group_template_deployment.container_group_profile, + azurerm_lb.load_balancer, + azurerm_virtual_network.vnet + ] + + template_content = jsonencode({ + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" + contentVersion = "1.0.0.0" + parameters = {} + variables = { + cgProfileName = local.cg_profile_name + nGroupsName = local.ngroups_name + loadBalancerName = local.load_balancer_name + vnetName = local.vnet_name + subnetName = local.subnet_name + backendAddressPoolName = local.backend_address_pool_name + apiVersion = local.api_version + desiredCount = local.desired_count + maintainDesiredCount = local.maintain_desired_count + prefixCG = local.prefix_cg + resourcePrefix = local.resource_prefix + } + resources = [ + { + apiVersion = local.api_version + type = "Microsoft.ContainerInstance/NGroups" + name = local.ngroups_name + location = data.azurerm_resource_group.current.location + dependsOn = [ + "Microsoft.ContainerInstance/containerGroupProfiles/${local.cg_profile_name}" + ] + properties = { + elasticProfile = { + desiredCount = local.desired_count + maintainDesiredCount = local.maintain_desired_count + containerGroupNamingPolicy = { + guidNamingPolicy = { + prefix = local.prefix_cg + } + } + } + containerGroupProfiles = [ + { + resource = { + id = "${local.resource_prefix}Microsoft.ContainerInstance/containerGroupProfiles/${local.cg_profile_name}" + } + containerGroupProperties = { + subnetIds = [ + { + id = azurerm_subnet.subnet.id + name = local.subnet_name + } + ] + } + networkProfile = { + loadBalancer = { + backendAddressPools = [ + { + resource = { + id = azurerm_lb_backend_address_pool.backend_pool.id + } + } + ] + } + } + } + ] + } + zones = local.zones + tags = { + "cirrusTestScenario" = "lin-100.regional.loadbalancer" + "reprovision.enabled" = "true" + } + } + ] + }) +} diff --git a/recipe-packs/terraform/aci-ngroups-loadbalancer/outputs.tf b/recipe-packs/terraform/aci-ngroups-loadbalancer/outputs.tf new file mode 100644 index 00000000..4faf4b34 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-loadbalancer/outputs.tf @@ -0,0 +1,121 @@ +# Output values for the ACI NGroups LoadBalancer Terraform configuration + +output "resource_group_name" { + description = "Name of the resource group" + value = data.azurerm_resource_group.current.name +} + +output "resource_group_location" { + description = "Location of the resource group" + value = data.azurerm_resource_group.current.location +} + +output "subscription_id" { + description = "Azure subscription ID" + value = data.azurerm_client_config.current.subscription_id +} + +output "virtual_network_id" { + description = "ID of the Virtual Network" + value = azurerm_virtual_network.vnet.id +} + +output "virtual_network_name" { + description = "Name of the Virtual Network" + value = azurerm_virtual_network.vnet.name +} + +output "subnet_id" { + description = "ID of the subnet" + value = azurerm_subnet.subnet.id +} + +output "network_security_group_id" { + description = "ID of the Network Security Group" + value = azurerm_network_security_group.nsg.id +} + +output "ddos_protection_plan_id" { + description = "ID of the DDoS Protection Plan" + value = azurerm_network_ddos_protection_plan.ddos_plan.id +} + +output "nat_gateway_id" { + description = "ID of the NAT Gateway" + value = azurerm_nat_gateway.nat_gateway.id +} + +output "inbound_public_ip_address" { + description = "Inbound public IP address" + value = azurerm_public_ip.inbound_public_ip.ip_address +} + +output "inbound_public_ip_fqdn" { + description = "Fully qualified domain name of the inbound public IP" + value = azurerm_public_ip.inbound_public_ip.fqdn +} + +output "outbound_public_ip_address" { + description = "Outbound public IP address" + value = azurerm_public_ip.outbound_public_ip.ip_address +} + +output "load_balancer_id" { + description = "ID of the Load Balancer" + value = azurerm_lb.load_balancer.id +} + +output "load_balancer_frontend_ip_id" { + description = "ID of the Load Balancer frontend IP configuration" + value = azurerm_lb.load_balancer.frontend_ip_configuration[0].id +} + +output "backend_address_pool_id" { + description = "ID of the backend address pool" + value = azurerm_lb_backend_address_pool.backend_pool.id +} + +output "health_probe_id" { + description = "ID of the health probe" + value = azurerm_lb_probe.health_probe.id +} + +output "http_rule_id" { + description = "ID of the HTTP load balancing rule" + value = azurerm_lb_rule.http_rule.id +} + +output "inbound_nat_rule_id" { + description = "ID of the inbound NAT rule" + value = azurerm_lb_nat_rule.inbound_nat_rule.id +} + +output "container_group_profile_name" { + description = "Name of the container group profile" + value = local.cg_profile_name +} + +output "ngroups_name" { + description = "Name of the NGroups resource" + value = local.ngroups_name +} + +output "container_group_profile_deployment_id" { + description = "Deployment ID for container group profile" + value = azurerm_resource_group_template_deployment.container_group_profile.id +} + +output "ngroups_deployment_id" { + description = "Deployment ID for NGroups resource" + value = azurerm_resource_group_template_deployment.ngroups.id +} + +output "load_balancer_frontend_ip" { + description = "Frontend IP configuration of the Load Balancer" + value = local.frontend_ip_name +} + +output "backend_pool_name" { + description = "Backend address pool name" + value = local.backend_address_pool_name +} diff --git a/recipe-packs/terraform/aci-ngroups-loadbalancer/terraform.tfvars.example b/recipe-packs/terraform/aci-ngroups-loadbalancer/terraform.tfvars.example new file mode 100644 index 00000000..85ce9dea --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-loadbalancer/terraform.tfvars.example @@ -0,0 +1,48 @@ +# Example Terraform variables file for ACI NGroups LoadBalancer configuration +# Copy this file to terraform.tfvars and modify the values as needed + +# Required variables +resource_group_name = "my-ngroups-loadbalancer-rg" + +# Optional variables (defaults will be used if not specified) +# api_version = "2024-09-01-preview" +# ngroups_param_name = "nGroups_lin100_reg_lb" +# container_group_profile_name = "cgp_1" +# load_balancer_name = "slb_1" +# backend_address_pool_name = "bepool_1" +# vnet_name = "vnet_1" +# subnet_name = "subnet_1" +# network_security_group_name = "nsg_1" +# inbound_public_ip_name = "inboundPublicIP" +# outbound_public_ip_name = "outboundPublicIP" +# nat_gateway_name = "natGateway1" +# frontend_ip_name = "loadBalancerFrontend" +# http_rule_name = "httpRule" +# health_probe_name = "healthProbe" +# inbound_nat_rule_name = "inboundNatRule" + +# Network configuration +# vnet_address_prefix = "172.19.0.0/16" +# subnet_address_prefix = "172.19.1.0/24" + +# Scaling configuration +# desired_count = 100 +# maintain_desired_count = true +# zones = [] + +# Public IP configuration +# domain_name_label = "ngroupsdemo" + +# Container configuration +# container_image = "mcr.microsoft.com/azuredocs/aci-helloworld@sha256:565dba8ce20ca1a311c2d9485089d7ddc935dd50140510050345a1b0ea4ffa6e" +# container_port = 80 +# memory_gb = 1.0 +# cpu_cores = 1.0 + +# Tags +# tags = { +# cirrusTestScenario = "lin-100.regional.loadbalancer" +# "reprovision.enabled" = "true" +# environment = "development" +# project = "my-project" +# } diff --git a/recipe-packs/terraform/aci-ngroups-loadbalancer/variables.tf b/recipe-packs/terraform/aci-ngroups-loadbalancer/variables.tf new file mode 100644 index 00000000..5a1625d0 --- /dev/null +++ b/recipe-packs/terraform/aci-ngroups-loadbalancer/variables.tf @@ -0,0 +1,172 @@ +# Variable definitions for the ACI NGroups LoadBalancer Terraform configuration + +variable "resource_group_name" { + description = "Name of the Azure Resource Group" + type = string +} + +variable "api_version" { + description = "API version for Container Instance resources" + type = string + default = "2024-09-01-preview" +} + +variable "ngroups_param_name" { + description = "Name of the NGroups resource" + type = string + default = "nGroups_lin100_reg_lb" +} + +variable "container_group_profile_name" { + description = "Name of the Container Group Profile" + type = string + default = "cgp_1" +} + +variable "load_balancer_name" { + description = "Name of the Load Balancer" + type = string + default = "slb_1" +} + +variable "backend_address_pool_name" { + description = "Name of the backend address pool" + type = string + default = "bepool_1" +} + +variable "vnet_name" { + description = "Name of the Virtual Network" + type = string + default = "vnet_1" +} + +variable "subnet_name" { + description = "Name of the Subnet" + type = string + default = "subnet_1" +} + +variable "network_security_group_name" { + description = "Name of the Network Security Group" + type = string + default = "nsg_1" +} + +variable "inbound_public_ip_name" { + description = "Name of the inbound public IP address" + type = string + default = "inboundPublicIP" +} + +variable "outbound_public_ip_name" { + description = "Name of the outbound public IP address" + type = string + default = "outboundPublicIP" +} + +variable "outbound_public_ip_prefix_name" { + description = "Name of the NAT gateway public IP prefix" + type = string + default = "outBoundPublicIPPrefix" +} + +variable "nat_gateway_name" { + description = "Name of the NAT Gateway" + type = string + default = "natGateway1" +} + +variable "frontend_ip_name" { + description = "Name of the load balancer frontend IP configuration" + type = string + default = "loadBalancerFrontend" +} + +variable "http_rule_name" { + description = "Name of the HTTP load balancing rule" + type = string + default = "httpRule" +} + +variable "health_probe_name" { + description = "Name of the health probe" + type = string + default = "healthProbe" +} + +variable "vnet_address_prefix" { + description = "Address prefix for the Virtual Network" + type = string + default = "172.19.0.0/16" +} + +variable "subnet_address_prefix" { + description = "Address prefix for the subnet" + type = string + default = "172.19.1.0/24" +} + +variable "desired_count" { + description = "Desired number of container instances" + type = number + default = 100 +} + +variable "zones" { + description = "Availability zones for the resources" + type = list(string) + default = [] +} + +variable "maintain_desired_count" { + description = "Whether to maintain the desired count" + type = bool + default = true +} + +variable "domain_name_label" { + description = "Domain name label for the public IP" + type = string + default = "ngroupsdemo" +} + +variable "inbound_nat_rule_name" { + description = "Name of the inbound NAT rule" + type = string + default = "inboundNatRule" +} + +variable "container_image" { + description = "Container image to deploy" + type = string + default = "mcr.microsoft.com/azuredocs/aci-helloworld@sha256:565dba8ce20ca1a311c2d9485089d7ddc935dd50140510050345a1b0ea4ffa6e" +} + +variable "container_port" { + description = "Port to expose on the container" + type = number + default = 80 +} + +variable "memory_gb" { + description = "Memory allocation in GB" + type = number + default = 1.0 +} + +variable "cpu_cores" { + description = "CPU allocation" + type = number + default = 1.0 +} + +variable "tags" { + description = "Tags to apply to resources" + type = map(string) + default = { + cirrusTestScenario = "lin-100.regional.loadbalancer" + "reprovision.enabled" = "true" + environment = "development" + } +}