<a id="cloud-azure"></a>

# Microsoft Azure

In Juju, [Microsoft Azure](https://azure.microsoft.com/en-us) is a [machine cloud](https://documentation.ubuntu.com/juju/4.0/reference/cloud.md#machine-cloud) and works as described below.

#### NOTE
This reference assumes basic familiarity with Juju. If you are new to Juju, start with the [Tutorial](https://documentation.ubuntu.com/juju/4.0/tutorial.md#tutorial), then use this page together with the generic materials it links to and/or consult the [example workflows](#azure-appendix-example-workflows).

<a id="azure-requirements"></a>

## Requirements

Juju needs the Azure API permissions listed below to create and manage the Azure resources used during cloud registration and bootstrap:

- `Microsoft.Compute/skus` (read).
- `Microsoft.Resources/subscriptions/resourceGroups` (read, write, delete).
- `Microsoft.Resources/deployments/*` (write, read, delete, cancel, validate).
- `Microsoft.Network/networkSecurityGroups` (write, read, delete, join).
- `Microsoft.Network/virtualNetworks/*` (write, read, delete).
- `Microsoft.Compute/virtualMachineScaleSets/*` (write, read, delete, start, deallocate, restart, powerOff).
- `Microsoft.Network/virtualNetworks/subnets/*` (read, write, delete, join).
- `Microsoft.Compute/availabilitySets` (write, read, delete).
- `Microsoft.Network/publicIPAddresses` (write, read, delete, join) – optional for public-facing services.
- `Microsoft.Network/networkInterfaces` (write, read, delete, join).
- `Microsoft.Compute/virtualMachines` (write, read, delete, start, powerOff, restart, deallocate).
- `Microsoft.Compute/disks` (write, read, delete).

<a id="azure-concepts"></a>

## Concepts

The following table shows how Azure’s native abstractions map to Juju concepts:

| Azure                                                                                                                | Juju                                                                   |
|----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|
| [Resource Group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/overview#resource-groups) | [model](https://documentation.ubuntu.com/juju/4.0/reference/model.md#model) (roughly)   |
| [Virtual Machine](https://learn.microsoft.com/en-us/azure/virtual-machines/)                                         | [machine](https://documentation.ubuntu.com/juju/4.0/reference/machine.md#machine)         |
| Process or container within a VM                                                                                     | [unit](https://documentation.ubuntu.com/juju/4.0/reference/unit.md#unit)               |
| Collection of VMs running the same workload                                                                          | [application](https://documentation.ubuntu.com/juju/4.0/reference/application.md#application) |
| [Managed Disk](https://learn.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview)                      | [storage](https://documentation.ubuntu.com/juju/4.0/reference/storage.md#storage)         |
| [Subnet](https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-vnet-plan-design-arm)               | Network space (roughly)                                                |

<a id="azure-cloud-definition"></a>

## The cloud

See also: [Cloud](https://documentation.ubuntu.com/juju/4.0/reference/cloud.md#cloud), [Juju | Manage clouds](https://documentation.ubuntu.com/juju/4.0/howto/manage-clouds.md#manage-clouds), [Terraform Provider for Juju | Manage clouds](https://canonical.com/juju/docs/terraform-provider-juju/latest/howto/manage-clouds/#manage-clouds)

As for all machine clouds, the cloud is registered in Juju via a cloud definition, stored in `clouds.yaml` on the client (on Linux: `~/.local/share/juju/clouds.yaml`) and following this schema:

```yaml
clouds:
  <cloud-name>:  # Predefined name
    type: azure
    auth-types:
      - <auth-type>                # See Authentication types below
    regions:
      <region-name>:               # e.g. eastus
        endpoint: <endpoint>       # Region-specific Azure API endpoint
    config:                        # Optional: model config defaults
      <config-key>: <value>        # See Configuration keys below
```

<a id="azure-credential"></a>

## Credentials

See also: [Credential](https://documentation.ubuntu.com/juju/4.0/reference/credential.md#credential), [Juju | Manage credentials](https://documentation.ubuntu.com/juju/4.0/howto/manage-credentials.md#manage-credentials), [Terraform Provider for Juju | Manage credentials](https://canonical.com/juju/docs/terraform-provider-juju/latest/howto/manage-credentials/#manage-credentials)

As for all machine clouds, credentials are stored in `credentials.yaml` on the client and follow this schema:

```yaml
credentials:
  azure                          # Predefined cloud name for Azure
    <credential-name>:             # User-defined credential name
      auth-type: <auth-type>       # managed-identity | interactive | service-principal-secret (see Authentication types)
      <attribute>: <value>         # Auth-type-specific attributes (see below)
```

<a id="azure-credential-authentication-types"></a>

### Authentication types

Microsoft Azure supports the following authentication types:

<a id="azure-credential-managed-identity"></a>

#### `managed-identity`

**Requirements:**

- Juju 3.6+
- Managed identity created in Azure.
- Same subscription for managed identity and Juju resources.
- Credential addition must occur from Azure Cloud Shell or Azure-hosted jump host (for cloud metadata endpoint access).

**Behavior:** Controller uses managed identity for Azure API operations without storing credential secrets.

See more: [Appendix: How to create a managed identity](#azure-appendix-create-a-managed-identity), [Authenticate with managed identity (recommended)](#azure-appendix-workflow-1)

<a id="azure-credential-interactive"></a>

#### `interactive`

Browser-based OAuth flow. If using unconfined `juju` snap with Azure CLI logged in, subscription ID can be auto-filled.

**Note:** Optional fields `application-name` and `role-definition-name` must have unique values if specified.

**Version note:** Starting with Juju 3.6, can be combined with managed identity via `instance-role` constraint during bootstrap.

See more: [Authenticate with service principal secret and managed identity](#azure-appendix-workflow-2), [Authenticate with service principal secret only (dispreferred)](#azure-appendix-workflow-3)

<a id="azure-credential-service-principal-secret"></a>

#### `service-principal-secret`

Requires application ID, subscription ID, and client secret.

**Version note:** Starting with Juju 3.6, can be combined with managed identity via `instance-role` constraint during bootstrap.

See more: [Authenticate with service principal secret and managed identity](#azure-appendix-workflow-2), [Authenticate with service principal secret only (dispreferred)](#azure-appendix-workflow-3)

<a id="azure-credential-known-issues"></a>

#### Known issues

Credentials occasionally stop working over time. Refresh using credential update or re-add credential.

<a id="azure-controller"></a>

## Controllers

See also: [Controller](https://documentation.ubuntu.com/juju/4.0/reference/controller.md#controller), [Juju | Manage controllers](https://documentation.ubuntu.com/juju/4.0/howto/manage-controllers.md#manage-controllers), [Terraform Provider for Juju | Manage controllers](https://canonical.com/juju/docs/terraform-provider-juju/latest/howto/manage-controllers/#manage-controllers)

<a id="azure-controller-bootstrap-behavior"></a>

### Bootstrap behavior

Creates controller and initial model on Azure.

<a id="azure-controller-resources-created-at-bootstrap"></a>

### Resources created at bootstrap

The controller runs on an Azure VM provisioned using the same mechanisms as workload machines – see [Resources created per machine](#azure-machine-resources-created-per-machine) for the full per-machine resource model. Controller-specific differences are noted below.

**Compute**

- **Resource group**: Contains all resources for the model. Auto-generated name or user-specified via `resource-group-name` config.
- **Controller virtual machine**: Ubuntu LTS. Size configurable via `instance-type` constraint.

**Networking**

- **Virtual network**: Named `juju-internal-network` with `192.168.0.0/16` address space. User-configurable via `network` config.
- **Subnets**:
  - Controller subnet (`192.168.16.0/20`) for controller machines.
  - Internal subnet (`192.168.0.0/20`) for application machines.
- **Network security group**: Named `juju-internal-nsg`. Rules: SSH (port 22) to all machines, Juju API (port 17070) to controller subnet.

<a id="azure-model"></a>

## Models

See also: [Model](https://documentation.ubuntu.com/juju/4.0/reference/model.md#model), [Juju | Manage models](https://documentation.ubuntu.com/juju/4.0/howto/manage-models.md#manage-models), [Terraform Provider for Juju | Manage models](https://canonical.com/juju/docs/terraform-provider-juju/latest/howto/manage-models/#manage-models)

<a id="azure-model-configuration-keys"></a>

### Configuration keys

Microsoft Azure supports the following [cloud-specific model configuration keys](https://documentation.ubuntu.com/juju/4.0/reference/configuration/list-of-model-configuration-keys.md#model-config-cloud-specific-key):

**Networking**

<a id="azure-model-load-balancer-sku-name"></a>
- **`load-balancer-sku-name`**: Mirrors the LoadBalancerSkuName type in the Azure SDK. Type: `string`. Default: `"Standard"`. Mandatory.

<a id="azure-model-resource-group-name"></a>
- **`resource-group-name`**: If set, use the specified resource group for all model resources instead of creating one based on the model UUID. Type: `string`. Default: none. Immutable.

<a id="azure-model-network"></a>
- **`network`**: If set, use the specified virtual network for all model machines instead of creating one. Type: `string`. Default: none. Immutable.

<a id="azure-machine"></a>

## Machines

See also: [Machine](https://documentation.ubuntu.com/juju/4.0/reference/machine.md#machine), [Juju | Manage machines](https://documentation.ubuntu.com/juju/4.0/howto/manage-machines.md#manage-machines), [Terraform Provider for Juju | Manage machines](https://canonical.com/juju/docs/terraform-provider-juju/latest/howto/manage-machines/#manage-machines)

<a id="azure-machine-constraints"></a>

### Constraints

Microsoft Azure supports the following [constraints](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint):

#### NOTE
The constraints `instance-type` and `[arch, cores, mem]` are mutually exclusive.

**Compute**

- [arch](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-arch). Valid values: `amd64`, `arm64`.
- [container](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-container)
- [cores](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-cores)
- [instance-role](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-instance-role). Juju 3.6+. Valid values: `auto` or managed identity name in format `<resource-group>/<identity-name>` or `<subscription>/<resource-group>/<identity-name>`.
- [instance-type](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-instance-type). See Azure VM sizes documentation.
- [mem](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-mem)

**Networking**

- [allocate-public-ip](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-allocate-public-ip)

#### NOTE
The `zones` constraint is not supported on Azure. Instead, Juju uses [Azure availability sets](https://learn.microsoft.com/en-us/azure/virtual-machines/availability-set-overview): for each application, an availability set is created and all units of that application are placed within it. This protects against hardware and infrastructure failures within a region, but does not map to Juju’s zone abstraction — charms cannot query which zone they are in.

**Storage**

- [root-disk](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-root-disk). Minimum 30 GiB.
- [root-disk-source](https://documentation.ubuntu.com/juju/4.0/reference/constraint.md#constraint-root-disk-source). Specifies [storage pool](https://documentation.ubuntu.com/juju/4.0/reference/storage.md#storage-pool) for root disk. Enables encryption configuration.

<a id="azure-machine-placement-directives"></a>

### Placement directives

Microsoft Azure supports the following [placement directives](https://documentation.ubuntu.com/juju/4.0/reference/placement-directive.md#placement-directive):

- [subnet=<subnet>](https://documentation.ubuntu.com/juju/4.0/reference/placement-directive.md#placement-directive-subnet)

<a id="azure-machine-resources-created-per-machine"></a>

### Resources created per machine

Applies to all machines, including controller machines. Controller-specific defaults are documented in [Resources created at bootstrap](#azure-controller-resources-created-at-bootstrap).

**Compute**

- **Virtual machine**: Type configurable via `instance-type` constraint.

**Networking**

- **Network interface**: Connected to appropriate subnet (controller or internal) with dynamically-allocated private IP address.
- **Public IP address**: Static IPv4 address created by default. Disable via `allocate-public-ip` constraint.

**Storage**

- **OS disk**: 30 GiB minimum, `StandardSSD_LRS` type by default. Size and type configurable via `root-disk` and `root-disk-source` constraints.
- **Additional storage**: Created when requested via storage specifications.

**Resource tags:** All resources tagged with `juju-model` (model UUID), `juju-controller` (controller UUID), `juju-machine-name` (machine identifier).

<a id="azure-machine-networking-behavior"></a>

### Networking behavior

- **Spaces:** Azure supports multiple network devices. Supplying multiple [space](https://documentation.ubuntu.com/juju/4.0/reference/space.md#space) constraints or endpoint bindings will provision machines with NICs in subnets representing the union of specified spaces.
- **IP addressing**: Private IPs allocated dynamically via DHCP. Public IPs use static allocation.
- **Subnet placement**: Controller machines → `192.168.16.0/20`; application machines → `192.168.0.0/20`.
- **NSG rules**: SSH (port 22) accessible on all machines. Juju API (port 17070) accessible on controller subnet only.

<a id="azure-machine-storage-behavior"></a>

### Storage behavior

See also: [azure](#storage-provider-azure) for the Azure storage provider configuration options.

- **OS disk**: `StandardSSD_LRS` by default, minimum 30 GiB. Configurable via `root-disk` and `root-disk-source` constraints.
- **Additional disks**: Created via storage constraints using the configured storage pool.

<a id="azure-storage"></a>

## Storage

See also: [Storage](https://documentation.ubuntu.com/juju/4.0/reference/storage.md#storage), [Juju | Manage storage](https://documentation.ubuntu.com/juju/4.0/howto/manage-storage.md#manage-storage)

<a id="azure-storage-providers"></a>

### Storage providers

In addition to generic storage providers, Microsoft Azure provides the following [cloud-specific storage providers](https://documentation.ubuntu.com/juju/4.0/reference/storage.md#storage-provider-cloud-specific):

<a id="storage-provider-azure"></a>

#### `azure`

**Type:** Azure Managed Disks

**Configuration options:**

- `account-type`: Disk type. Default: `StandardSSD_LRS`.
  - `Standard_LRS`: Standard HDD
  - `StandardSSD_LRS`: Standard SSD — default (associated with pool `azure`)
  - `Premium_LRS`: Premium SSD (associated with pool `azure-premium`)

See more: [Azure Managed Disks Overview](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/managed-disks-overview)

<a id="azure-appendix-example-workflows"></a>

## Appendix: Example workflows

<a id="azure-appendix-workflow-1"></a>

### Authenticate with managed identity (recommended)

> *Requirements:*

> - Juju 3.6+.
> - A managed identity. See more: [Appendix: How to create a managed identity](#azure-appendix-create-a-managed-identity)
> - The managed identity and the Juju resources must be created on the same subscription.
> - The `add-credential` steps must be run from either [the Azure Cloud Shell](https://shell.azure.com/) or a jump host running in Azure in order to allow the cloud metadata endpoint to be reached.
1. Run `juju add-credential azure`; choose `managed-identity`; supply the requested information (the `managed-identity-path` must be of the form `<resourcegroup>/<identityname>`).
2. Bootstrap as usual.

#### TIP
With this workflow where you provide the managed identity during `add-credential` you avoid the need for either your Juju client or your Juju controller to store your credential secrets. Relatedly, the user running `add-credential` / `bootstrap` doesn’t need to have any credential secrets supplied to them.

<a id="azure-appendix-workflow-2"></a>

### Authenticate with service principal secret and managed identity

> *Requirements:*

> - Juju 3.6+.
> - A managed identity. See more: [Appendix: How to create a managed identity](#azure-appendix-create-a-managed-identity)
1. Add a service-principal-secret:
   - `interactive`  = “service-principal-via-browser” (recommended):
     - If you have the `azure` CLI and you are logged in and you want to use the currently logged in user: Run `/snap/juju/current/bin/juju add-credential azure`; choose `interactive`, then leave the subscription ID field empty – Juju will fill this in for you.
     - Otherwise: Run `juju add-credential azure`, choose `interactive`, then provide the subscription ID – Juju will open up a browser and you’ll be prompted to log in to Azure.
   - `service-principal-secret`: Run `juju add-credential azure`, then choose `service-principal-secret` and supply all the requested information.
2. During bootstrap, provide the managed identity to the controller by using the `instance-role` constraint.

#### TIP
With this workflow where you provide the managed identity during `bootstrap` you avoid the need for your Juju controller to store your credential secrets. Relatedly, the user running / `bootstrap` doesn’t need to have any credential secrets supplied to them.

<a id="azure-appendix-workflow-3"></a>

### Authenticate with service principal secret only (dispreferred)

1. Add a service-principal-secret:
   - `interactive`  = “service-principal-via-browser” (recommended):
     - If you have the `azure` CLI and you are logged in and you want to use the currently logged in user: Run `/snap/juju/current/bin/juju add-credential azure`; choose `interactive`, then leave the subscription ID field empty – Juju will fill this in for you.
     - Otherwise: Run `juju add-credential azure`, choose `interactive`, then provide the subscription ID – Juju will open up a browser and you’ll be prompted to log in to Azure.
   - `service-principal-secret`: Run `juju add-credential azure`, then choose `service-principal-secret` and supply all the requested information.
2. Bootstrap as usual.

<a id="azure-appendix-create-a-managed-identity"></a>

## Appendix: How to create a managed identity

#### CAUTION
This is just an example. For more information please see the upstream cloud documentation. See more: [Microsoft Azure | Managed identities](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview).

To create a managed identity for Juju to use, you will need to use the Azure CLI and be logged in to your account. This is a set up step that can be done ahead of time by an administrator.

The 4 values below need to be filled in according to your requirements.

```text
$ export group=someresourcegroup
$ export location=someregion
$ export role=myrolename
$ export identityname=myidentity
$ export subscription=mysubscription_id
```

The role definition and role assignment can be scoped to either the subscription or a particular resource group. If scoped to a resource group, this group needs to be provided to Juju when bootstrapping so that the controller resources are also created in that group.

For a subscription scoped managed identity:

```text
$ az group create --name "${group}" --location "${location}"
$ az identity create --resource-group "${group}" --name "${identityname}"
$ mid=$(az identity show --resource-group "${group}" --name "${identityname}" --query principalId --output tsv)
$ az role definition create --role-definition "{
  	\"Name\": \"${role}\",
  	\"Description\": \"Role definition for a Juju controller\",
  	\"Actions\": [
            	\"Microsoft.Compute/*\",
            	\"Microsoft.KeyVault/*\",
            	\"Microsoft.Network/*\",
            	\"Microsoft.Resources/*\",
            	\"Microsoft.Storage/*\",
            	\"Microsoft.ManagedIdentity/userAssignedIdentities/*\"
  	],
  	\"AssignableScopes\": [
        	\"/subscriptions/${subscription}\"
  	]
  }"
$ az role assignment create --assignee-object-id "${mid}" --assignee-principal-type "ServicePrincipal" --role "${role}" --scope "/subscriptions/${subscription}"
```

A resource scoped managed identity is similar except:

- the role definition assignable scopes becomes

```default
      \"AssignableScopes\": [
            \"/subscriptions/${subscription}/resourcegroups/${group}\"
      ]
```

- the role assignment scope becomes

`--scope "/subscriptions/${subscription}/resourcegroups/${group}"`
