diff --git a/modules/Microsoft.ContainerInstance/containerGroups/.test/private/dependencies.bicep b/modules/Microsoft.ContainerInstance/containerGroups/.test/private/dependencies.bicep new file mode 100644 index 0000000000..2d569ac75c --- /dev/null +++ b/modules/Microsoft.ContainerInstance/containerGroups/.test/private/dependencies.bicep @@ -0,0 +1,47 @@ +@description('Required. The name of the managed identity to create.') +param managedIdentityName string + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-01-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.0.0.0/24' + ] + } + subnets: [ + { + name: 'defaultSubnet' + properties: { + addressPrefix: '10.0.0.0/24' + delegations: [ + { + name: 'Microsoft.ContainerInstance.containerGroups' + properties: { + serviceName: 'Microsoft.ContainerInstance/containerGroups' + } + } + ] + } + } + ] + } +} + +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + +@description('The resource ID of the created Virtual Network Subnet.') +output subnetResourceId string = virtualNetwork.properties.subnets[0].id diff --git a/modules/Microsoft.ContainerInstance/containerGroups/.test/private/deploy.test.bicep b/modules/Microsoft.ContainerInstance/containerGroups/.test/private/deploy.test.bicep new file mode 100644 index 0000000000..c72a39f178 --- /dev/null +++ b/modules/Microsoft.ContainerInstance/containerGroups/.test/private/deploy.test.bicep @@ -0,0 +1,129 @@ +targetScope = 'subscription' + +// ========== // +// Parameters // +// ========== // +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'ms.containerinstance.containergroups-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'cicgprivate' + +@description('Optional. Enable telemetry via a Globally Unique Identifier (GUID).') +param enableDefaultTelemetry bool = true + +// =========== // +// Deployments // +// =========== // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module resourceGroupResources 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-paramNested' + params: { + managedIdentityName: 'dep-<>-msi-${serviceShort}' + virtualNetworkName: 'dep-<>-vnet-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../deploy.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name)}-test-${serviceShort}' + params: { + enableDefaultTelemetry: enableDefaultTelemetry + name: '<>${serviceShort}001' + lock: 'CanNotDelete' + containers: [ + { + name: '<>-az-aci-x-001' + properties: { + command: [] + environmentVariables: [] + image: 'mcr.microsoft.com/azuredocs/aci-helloworld' + ports: [ + { + port: '80' + protocol: 'Tcp' + } + { + port: '443' + protocol: 'Tcp' + } + ] + resources: { + requests: { + cpu: 2 + memoryInGB: 4 + } + } + volumeMounts: [ + { + name: 'my-name' + mountPath: '/mnt/empty' + } + ] + } + } + { + name: '<>-az-aci-x-002' + properties: { + command: [] + environmentVariables: [] + image: 'mcr.microsoft.com/azuredocs/aci-helloworld' + ports: [ + { + port: '8080' + protocol: 'Tcp' + } + ] + resources: { + requests: { + cpu: 2 + memoryInGB: 2 + } + } + } + } + ] + ipAddressType: 'Private' + ipAddressPorts: [ + { + protocol: 'Tcp' + port: 80 + } + { + protocol: 'Tcp' + port: 443 + } + { + protocol: 'Tcp' + port: '8080' + } + ] + subnetId: resourceGroupResources.outputs.subnetResourceId + volumes: [ + { + emptyDir: {} + name: 'my-name' + } + ] + systemAssignedIdentity: true + userAssignedIdentities: { + '${resourceGroupResources.outputs.managedIdentityResourceId}': {} + } + } +} diff --git a/modules/Microsoft.ContainerInstance/containerGroups/deploy.bicep b/modules/Microsoft.ContainerInstance/containerGroups/deploy.bicep index 3c63537d84..54d9cae288 100644 --- a/modules/Microsoft.ContainerInstance/containerGroups/deploy.bicep +++ b/modules/Microsoft.ContainerInstance/containerGroups/deploy.bicep @@ -4,15 +4,24 @@ param name string @description('Required. The containers and their respective config within the container group.') param containers array -@description('Optional. Ports to open on the public IP address. Must include all ports assigned on container level.') +@description('Conditional. Ports to open on the public IP address. Must include all ports assigned on container level. Required if `ipAddressType` is set to `public`.') param ipAddressPorts array = [] @description('Optional. The operating system type required by the containers in the container group. - Windows or Linux.') param osType string = 'Linux' +@allowed([ + 'Always' + 'OnFailure' + 'Never' +]) @description('Optional. Restart policy for all containers within the container group. - Always: Always restart. OnFailure: Restart on failure. Never: Never restart. - Always, OnFailure, Never.') param restartPolicy string = 'Always' +@allowed([ + 'Public' + 'Private' +]) @description('Optional. Specifies if the IP is exposed to the public internet or private VNET. - Public or Private.') param ipAddressType string = 'Public' @@ -22,6 +31,50 @@ param imageRegistryCredentials array = [] @description('Optional. Location for all Resources.') param location string = resourceGroup().location +@allowed([ + 'Noreuse' + 'ResourceGroupReuse' + 'SubscriptionReuse' + 'TenantReuse' + 'Unsecure' +]) +@description('Optional. Specify level of protection of the domain name label.') +param autoGeneratedDomainNameLabelScope string = 'TenantReuse' + +@description('Optional. The Dns name label for the resource.') +param dnsNameLabel string = '' + +@allowed([ + 'Standard' + 'Dedicated' +]) +@description('Optional. Specify the Sku.') +param sku string = 'Standard' + +@description('Optional. If Non-Microsoft-managed encryption should be used, specify the key vaults base URL.') +param encryptionVaultBaseUrl string = '' + +@description('Optional. If Non-Microsoft-managed encryption should be used, specify the key name.') +param encrytionKeyName string = '' + +@description('Optional. If Non-Microsoft-managed encryption should be used, specify the key version.') +param encryptionKeyVersion string = '' + +@description('Optional. List of dns servers used by the containers for lookups.') +param dnsNameServers array = [] + +@description('Optional. DNS search domain which will be appended to each DNS lookup.') +param dnsSearchDomains string = '' + +@description('Optional. A list of container definitions which will be executed before the application container starts.') +param initContainers array = [] + +@description('Optional. Resource ID of the subnet. Only specify when ipAddressType is Private.') +param subnetId string = '' + +@description('Optional. Specify if volumes (emptyDir, AzureFileShare or GitRepo) shall be attached to your containergroup.') +param volumes array = [] + @allowed([ '' 'CanNotDelete' @@ -49,6 +102,46 @@ var identity = identityType != 'None' ? { userAssignedIdentities: !empty(userAssignedIdentities) ? userAssignedIdentities : null } : null +var dnsConfig = !empty(dnsNameServers) ? { + nameServers: dnsNameServers + searchDomains: dnsSearchDomains +} : null + +var encryptionProperties = !empty(encryptionVaultBaseUrl) ? { + vaultBaseUrl: encryptionVaultBaseUrl + keyName: encrytionKeyName + keyVersion: encryptionKeyVersion +} : null + +var subnetIds = !empty(subnetId) ? [ + { + id: subnetId + } +] : null + +var generatedDomainNameLabelScope = !empty(dnsNameServers) ? autoGeneratedDomainNameLabelScope : null + +var basicContainerProperties = { + containers: containers + dnsConfig: dnsConfig + encryptionProperties: encryptionProperties + imageRegistryCredentials: imageRegistryCredentials + initContainers: initContainers + restartPolicy: restartPolicy + osType: osType + ipAddress: { + type: ipAddressType + autoGeneratedDomainNameLabelScope: generatedDomainNameLabelScope + dnsNameLabel: dnsNameLabel + ports: ipAddressPorts + } + sku: sku + subnetIds: subnetIds + volumes: volumes +} + +var containerProperties = !empty(dnsNameServers) ? union(basicContainerProperties, { dnsConfig: dnsConfig }) : basicContainerProperties + resource defaultTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableDefaultTelemetry) { name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, location)}' properties: { @@ -66,16 +159,7 @@ resource containergroup 'Microsoft.ContainerInstance/containerGroups@2021-10-01' location: location identity: identity tags: tags - properties: { - containers: containers - imageRegistryCredentials: imageRegistryCredentials - restartPolicy: restartPolicy - osType: osType - ipAddress: { - type: ipAddressType - ports: ipAddressPorts - } - } + properties: containerProperties } resource containergroup_lock 'Microsoft.Authorization/locks@2017-04-01' = if (!empty(lock)) { diff --git a/modules/Microsoft.ContainerInstance/containerGroups/readme.md b/modules/Microsoft.ContainerInstance/containerGroups/readme.md index 2f6c2ddcba..f2b07e3afc 100644 --- a/modules/Microsoft.ContainerInstance/containerGroups/readme.md +++ b/modules/Microsoft.ContainerInstance/containerGroups/readme.md @@ -28,21 +28,37 @@ The top-level resource in Azure Container Instances is the container group. A co | `containers` | array | The containers and their respective config within the container group. | | `name` | string | Name for the container group. | +**Conditional parameters** + +| Parameter Name | Type | Description | +| :-- | :-- | :-- | +| `ipAddressPorts` | array | Ports to open on the public IP address. Must include all ports assigned on container level. Required if `ipAddressType` is set to `public`. | + **Optional parameters** | Parameter Name | Type | Default Value | Allowed Values | Description | | :-- | :-- | :-- | :-- | :-- | +| `autoGeneratedDomainNameLabelScope` | string | `'TenantReuse'` | `[Noreuse, ResourceGroupReuse, SubscriptionReuse, TenantReuse, Unsecure]` | Specify level of protection of the domain name label. | +| `dnsNameLabel` | string | `''` | | The Dns name label for the resource. | +| `dnsNameServers` | array | `[]` | | List of dns servers used by the containers for lookups. | +| `dnsSearchDomains` | string | `''` | | DNS search domain which will be appended to each DNS lookup. | | `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via a Globally Unique Identifier (GUID). | +| `encryptionKeyVersion` | string | `''` | | If Non-Microsoft-managed encryption should be used, specify the key version. | +| `encryptionVaultBaseUrl` | string | `''` | | If Non-Microsoft-managed encryption should be used, specify the key vaults base URL. | +| `encrytionKeyName` | string | `''` | | If Non-Microsoft-managed encryption should be used, specify the key name. | | `imageRegistryCredentials` | array | `[]` | | The image registry credentials by which the container group is created from. | -| `ipAddressPorts` | array | `[]` | | Ports to open on the public IP address. Must include all ports assigned on container level. | -| `ipAddressType` | string | `'Public'` | | Specifies if the IP is exposed to the public internet or private VNET. - Public or Private. | +| `initContainers` | array | `[]` | | A list of container definitions which will be executed before the application container starts. | +| `ipAddressType` | string | `'Public'` | `[Private, Public]` | Specifies if the IP is exposed to the public internet or private VNET. - Public or Private. | | `location` | string | `[resourceGroup().location]` | | Location for all Resources. | | `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. | | `osType` | string | `'Linux'` | | The operating system type required by the containers in the container group. - Windows or Linux. | -| `restartPolicy` | string | `'Always'` | | Restart policy for all containers within the container group. - Always: Always restart. OnFailure: Restart on failure. Never: Never restart. - Always, OnFailure, Never. | +| `restartPolicy` | string | `'Always'` | `[Always, Never, OnFailure]` | Restart policy for all containers within the container group. - Always: Always restart. OnFailure: Restart on failure. Never: Never restart. - Always, OnFailure, Never. | +| `sku` | string | `'Standard'` | `[Dedicated, Standard]` | Specify the Sku. | +| `subnetId` | string | `''` | | Resource ID of the subnet. Only specify when ipAddressType is Private. | | `systemAssignedIdentity` | bool | `False` | | Enables system assigned managed identity on the resource. | | `tags` | object | `{object}` | | Tags of the resource. | | `userAssignedIdentities` | object | `{object}` | | The ID(s) to assign to the resource. | +| `volumes` | array | `[]` | | Specify if volumes (emptyDir, AzureFileShare or GitRepo) shall be attached to your containergroup. | ### Parameter Usage: `imageRegistryCredentials` @@ -123,6 +139,110 @@ tags: {

+### Parameter Usage: `autoGeneratedDomainNameLabelScope` + +DNS name reuse is convenient for DevOps within any modern company. The idea of redeploying an application by reusing the DNS name fulfills an on-demand philosophy that secures cloud development. Therefore, it's important to note that DNS names that are available to anyone become a problem when one customer releases a name only to have that same name taken by another customer. This is called subdomain takeover. A customer releases a resource using a particular name, and another customer creates a new resource with that same DNS name. If there were any records pointing to the old resource, they now also point to the new resource. + +This field can only be used when the `ipAddressType` is set to `Public`. + +Allowed values are: +| Policy name | Policy definition | | | | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|---| +| unsecure | Hash will be generated based on only the DNS name. Avoiding subdomain takeover is not guaranteed if another customer uses the same DNS name. | | | | +| tenantReuse | Default Hash will be generated based on the DNS name and the tenant ID. Object's domain name label can be reused within the same tenant. | | | | +| subscriptionReuse | Hash will be generated based on the DNS name and the tenant ID and subscription ID. Object's domain name label can be reused within the same subscription. | | | | +| resourceGroupReuse | Hash will be generated based on the DNS name and the tenant ID, subscription ID, and resource group name. Object's domain name label can be reused within the same resource group. | | | | +| noReuse | Hash will not be generated. Object's domain label can't be reused within resource group, subscription, or tenant. | | | | + +

+ +Parameter JSON format + +```json +"autoGeneratedDomainNameLabelScope": { + "value": "Unsecure" + }, +``` + +
+ +
+ +Bicep format + +```bicep +autoGeneratedDomainNameLabelScope: 'Unsecure' +``` + +
+

+ +### Parameter Usage: `volumes` + +By default, Azure Container Instances are stateless. If the container is restarted, crashes, or stops, all of its state is lost. To persist state beyond the lifetime of the container, you must mount a volume from an external store. Currently, Azure volume mounting is only supported on a linux based image. + +You can mount: + +- an Azure File Share (make sure the storage account has a service endpoint when running the container in private mode!) +- a secret +- a GitHub Repository +- an empty local directory + +

+ +Parameter JSON format + +```json +"volumes": [ + { + "azureFile": { + "readOnly": "bool", + "shareName": "string", + "storageAccountKey": "string", + "storageAccountName": "string" + }, + "emptyDir": {}, + "gitRepo": { + "directory": "string", + "repository": "string", + "revision": "string" + }, + "name": "string", + "secret": {} + } + ] +``` + +
+ +
+ +Bicep format + +```bicep +volumes: [ + { + azureFile: { + readOnly: bool + shareName: 'string' + storageAccountKey: 'string' + storageAccountName: 'string' + } + emptyDir: any() + gitRepo: { + directory: 'string' + repository: 'string' + revision: 'string' + } + name: 'string' + secret: {} + } + ] +``` + +
+

+ ### Parameter Usage: `userAssignedIdentities` You can specify multiple user assigned identities to a resource by providing additional resource IDs using the following format: @@ -191,7 +311,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic // Required parameters containers: [ { - name: '<>-az-aci-x-001' + name: 'jpe-az-aci-x-001' properties: { command: [] environmentVariables: [] @@ -215,7 +335,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic } } { - name: '<>-az-aci-x-002' + name: 'jpe-az-aci-x-002' properties: { command: [] environmentVariables: [] @@ -235,7 +355,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic } } ] - name: '<>cicgcom001' + name: 'jpecicgcom001' // Non-required parameters enableDefaultTelemetry: '' ipAddressPorts: [ @@ -273,7 +393,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic "containers": { "value": [ { - "name": "<>-az-aci-x-001", + "name": "jpe-az-aci-x-001", "properties": { "command": [], "environmentVariables": [], @@ -297,7 +417,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic } }, { - "name": "<>-az-aci-x-002", + "name": "jpe-az-aci-x-002", "properties": { "command": [], "environmentVariables": [], @@ -319,7 +439,7 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic ] }, "name": { - "value": "<>cicgcom001" + "value": "jpecicgcom001" }, // Non-required parameters "enableDefaultTelemetry": { @@ -455,3 +575,222 @@ module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bic

+ +

Example 3: Private

+ +
+ +via Bicep module + +```bicep +module containerGroups './Microsoft.ContainerInstance/containerGroups/deploy.bicep' = { + name: '${uniqueString(deployment().name)}-test-cicgprivate' + params: { + // Required parameters + containers: [ + { + name: '<>-az-aci-x-001' + properties: { + command: [] + environmentVariables: [] + image: 'mcr.microsoft.com/azuredocs/aci-helloworld' + ports: [ + { + port: '80' + protocol: 'Tcp' + } + { + port: '443' + protocol: 'Tcp' + } + ] + resources: { + requests: { + cpu: 2 + memoryInGB: 4 + } + } + volumeMounts: [ + { + mountPath: '/mnt/empty' + name: 'my-name' + } + ] + } + } + { + name: '<>-az-aci-x-002' + properties: { + command: [] + environmentVariables: [] + image: 'mcr.microsoft.com/azuredocs/aci-helloworld' + ports: [ + { + port: '8080' + protocol: 'Tcp' + } + ] + resources: { + requests: { + cpu: 2 + memoryInGB: 2 + } + } + } + } + ] + name: '<>cicgprivate001' + // Non-required parameters + enableDefaultTelemetry: '' + ipAddressPorts: [ + { + port: 80 + protocol: 'Tcp' + } + { + port: 443 + protocol: 'Tcp' + } + { + port: '8080' + protocol: 'Tcp' + } + ] + ipAddressType: 'Private' + lock: 'CanNotDelete' + subnetId: '' + systemAssignedIdentity: true + userAssignedIdentities: { + '': {} + } + volumes: [ + { + emptyDir: {} + name: 'my-name' + } + ] + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "containers": { + "value": [ + { + "name": "<>-az-aci-x-001", + "properties": { + "command": [], + "environmentVariables": [], + "image": "mcr.microsoft.com/azuredocs/aci-helloworld", + "ports": [ + { + "port": "80", + "protocol": "Tcp" + }, + { + "port": "443", + "protocol": "Tcp" + } + ], + "resources": { + "requests": { + "cpu": 2, + "memoryInGB": 4 + } + }, + "volumeMounts": [ + { + "mountPath": "/mnt/empty", + "name": "my-name" + } + ] + } + }, + { + "name": "<>-az-aci-x-002", + "properties": { + "command": [], + "environmentVariables": [], + "image": "mcr.microsoft.com/azuredocs/aci-helloworld", + "ports": [ + { + "port": "8080", + "protocol": "Tcp" + } + ], + "resources": { + "requests": { + "cpu": 2, + "memoryInGB": 2 + } + } + } + } + ] + }, + "name": { + "value": "<>cicgprivate001" + }, + // Non-required parameters + "enableDefaultTelemetry": { + "value": "" + }, + "ipAddressPorts": { + "value": [ + { + "port": 80, + "protocol": "Tcp" + }, + { + "port": 443, + "protocol": "Tcp" + }, + { + "port": "8080", + "protocol": "Tcp" + } + ] + }, + "ipAddressType": { + "value": "Private" + }, + "lock": { + "value": "CanNotDelete" + }, + "subnetId": { + "value": "" + }, + "systemAssignedIdentity": { + "value": true + }, + "userAssignedIdentities": { + "value": { + "": {} + } + }, + "volumes": { + "value": [ + { + "emptyDir": {}, + "name": "my-name" + } + ] + } + } +} +``` + +
+