Tutorial: Learn about Linux virtual machine management with Azure CLI

When deploying resources to Azure, you have tremendous flexibility when deciding what types of resources to deploy, where they are located, and how to set them up. However, that flexibility may open more options than you would like to allow in your organization. As you consider deploying resources to Azure, you might be wondering:

  • How do I meet legal requirements for data sovereignty in certain countries/regions?
  • How do I control costs?
  • How do I ensure that someone does not inadvertently change a critical system?
  • How do I track resource costs and bill it accurately?

This article addresses those questions. Specifically, you:

  • Assign users to roles and assign the roles to a scope so users have permission to perform expected actions but not more actions.
  • Apply policies that prescribe conventions for resources in your subscription.
  • Lock resources that are critical to your system.
  • Tag resources so you can track them by values that make sense to your organization.

This article focuses on the tasks you take to implement governance. For a broader discussion of the concepts, see Governance in Azure.

Use Azure Cloud Shell

Azure hosts Azure Cloud Shell, an interactive shell environment that you can use through your browser. You can use either Bash or PowerShell with Cloud Shell to work with Azure services. You can use the Cloud Shell preinstalled commands to run the code in this article without having to install anything on your local environment.

To start Azure Cloud Shell:

Option Example/Link
Select Try It in the upper-right corner of a code block. Selecting Try It doesn't automatically copy the code to Cloud Shell. Example of Try It for Azure Cloud Shell
Go to https://shell.azure.com, or select the Launch Cloud Shell button to open Cloud Shell in your browser. Launch Cloud Shell in a new window
Select the Cloud Shell button on the menu bar at the upper right in the Azure portal. Cloud Shell button in the Azure portal

To run the code in this article in Azure Cloud Shell:

  1. Start Cloud Shell.

  2. Select the Copy button on a code block to copy the code.

  3. Paste the code into the Cloud Shell session by selecting Ctrl+Shift+V on Windows and Linux or by selecting Cmd+Shift+V on macOS.

  4. Select Enter to run the code.

If you choose to install and use Azure CLI locally, this tutorial requires that you're running the Azure CLI version 2.0.30 or later. Run az --version to find the version. If you need to install or upgrade, see Install Azure CLI.

Understand scope

Before creating any items, let's review the concept of scope. Azure provides four levels of management: management groups, subscription, resource group, and resource. Management groups are in a preview release. The following image shows an example of these layers.

Scope

You apply management settings at any of these levels of scope. The level you select determines how widely the setting is applied. Lower levels inherit settings from higher levels. When you apply a setting to the subscription, that setting is applied to all resource groups and resources in your subscription. When you apply a setting on the resource group, that setting is applied the resource group and all its resources. However, another resource group does not have that setting.

Usually, it makes sense to apply critical settings at higher levels and project-specific requirements at lower levels. For example, you might want to make sure all resources for your organization are deployed to certain regions. To accomplish this requirement, apply a policy to the subscription that specifies the allowed locations. As other users in your organization add new resource groups and resources, the allowed locations are automatically enforced.

In this tutorial, you apply all management settings to a resource group so you can easily remove those settings when done.

Let's create that resource group.

az group create --name myResourceGroup --location "East US"

Currently, the resource group is empty.

Role-based access control

You want to make sure users in your organization have the right level of access to these resources. You don't want to grant unlimited access to users, but you also need to make sure they can do their work. Role-based access control enables you to manage which users have permission to complete specific actions at a scope.

To create and remove role assignments, users must have Microsoft.Authorization/roleAssignments/* access. This access is granted through the Owner or User Access Administrator roles.

For managing virtual machine solutions, there are three resource-specific roles that provide commonly needed access:

Instead of assigning roles to individual users, it's often easier to use an Azure Active Directory group that has users who need to take similar actions. Then, assign that group to the appropriate role. For this article, either use an existing group for managing the virtual machine, or use the portal to create an Azure Active Directory group.

After creating a new group or finding an existing one, use the az role assignment create command to assign the new Azure Active Directory group to the Virtual Machine Contributor role for the resource group.

adgroupId=$(az ad group show --group <your-group-name> --query objectId --output tsv)

az role assignment create --assignee-object-id $adgroupId --role "Virtual Machine Contributor" --resource-group myResourceGroup

If you receive an error stating Principal <guid> does not exist in the directory, the new group hasn't propagated throughout Azure Active Directory. Try running the command again.

Typically, you repeat the process for Network Contributor and Storage Account Contributor to make sure users are assigned to manage the deployed resources. In this article, you can skip those steps.

Azure Policy

Azure Policy helps you make sure all resources in subscription meet corporate standards. Your subscription already has several policy definitions. To see the available policy definitions, use the az policy definition list command:

az policy definition list --query "[].[displayName, policyType, name]" --output table

You see the existing policy definitions. The policy type is either BuiltIn or Custom. Look through the definitions for ones that describe a condition you want assign. In this article, you assign policies that:

  • Limit the locations for all resources.
  • Limit the SKUs for virtual machines.
  • Audit virtual machines that don't use managed disks.

In the following example, you retrieve three policy definitions based on the display name. You use the az policy assignment create command to assign those definitions to the resource group. For some policies, you provide parameter values to specify the allowed values.

# Get policy definitions for allowed locations, allowed SKUs, and auditing VMs that don't use managed disks
locationDefinition=$(az policy definition list --query "[?displayName=='Allowed locations'].name | [0]" --output tsv)
skuDefinition=$(az policy definition list --query "[?displayName=='Allowed virtual machine SKUs'].name | [0]" --output tsv)
auditDefinition=$(az policy definition list --query "[?displayName=='Audit VMs that do not use managed disks'].name | [0]" --output tsv)

# Assign policy for allowed locations
az policy assignment create --name "Set permitted locations" \
  --resource-group myResourceGroup \
  --policy $locationDefinition \
  --params '{ 
      "listOfAllowedLocations": {
        "value": [
          "eastus", 
          "eastus2"
        ]
      }
    }'

# Assign policy for allowed SKUs
az policy assignment create --name "Set permitted VM SKUs" \
  --resource-group myResourceGroup \
  --policy $skuDefinition \
  --params '{ 
      "listOfAllowedSKUs": {
        "value": [
          "Standard_DS1_v2", 
          "Standard_E2s_v2"
        ]
      }
    }'

# Assign policy for auditing unmanaged disks
az policy assignment create --name "Audit unmanaged disks" \
  --resource-group myResourceGroup \
  --policy $auditDefinition

The preceding example assumes you already know the parameters for a policy. If you need to view the parameters, use:

az policy definition show --name $locationDefinition --query parameters

Deploy the virtual machine

You have assigned roles and policies, so you're ready to deploy your solution. The default size is Standard_DS1_v2, which is one of your allowed SKUs. The command creates SSH keys if they don't exist in a default location.

az vm create --resource-group myResourceGroup --name myVM --image UbuntuLTS --generate-ssh-keys

After your deployment finishes, you can apply more management settings to the solution.

Lock resources

Resource locks prevent users in your organization from accidentally deleting or modifying critical resources. Unlike role-based access control, resource locks apply a restriction across all users and roles. You can set the lock level to CanNotDelete or ReadOnly.

To create or delete management locks, you must have access to Microsoft.Authorization/locks/* actions. Of the built-in roles, only Owner and User Access Administrator are granted those actions.

To lock the virtual machine and network security group, use the az lock create command:

# Add CanNotDelete lock to the VM
az lock create --name LockVM \
  --lock-type CanNotDelete \
  --resource-group myResourceGroup \
  --resource-name myVM \
  --resource-type Microsoft.Compute/virtualMachines

# Add CanNotDelete lock to the network security group
az lock create --name LockNSG \
  --lock-type CanNotDelete \
  --resource-group myResourceGroup \
  --resource-name myVMNSG \
  --resource-type Microsoft.Network/networkSecurityGroups

To test the locks, try running the following command:

az group delete --name myResourceGroup

You see an error stating that the delete operation can't be completed because of a lock. The resource group can only be deleted if you specifically remove the locks. That step is shown in Clean up resources.

Tag resources

You apply tags to your Azure resources to logically organize them by categories. Each tag consists of a name and a value. For example, you can apply the name "Environment" and the value "Production" to all the resources in production.

To add two tags to a resource group, use the az group update command:

az group update -n myResourceGroup --set tags.Environment=Test tags.Dept=IT

Let's suppose you want to add a third tag. Run the command again with the new tag. It is appended to the existing tags.

az group update -n myResourceGroup --set tags.Project=Documentation

Resources don't inherit tags from the resource group. Currently, your resource group has three tags but the resources do not have any tags. To apply all tags from a resource group to its resources, and retain existing tags on resources, use the following script:

# Get the tags for the resource group
jsontag=$(az group show -n myResourceGroup --query tags)

# Reformat from JSON to space-delimited and equals sign
t=$(echo $jsontag | tr -d '"{},' | sed 's/: /=/g')

# Get the resource IDs for all resources in the resource group
r=$(az resource list -g myResourceGroup --query [].id --output tsv)

# Loop through each resource ID
for resid in $r
do
  # Get the tags for this resource
  jsonrtag=$(az resource show --id $resid --query tags)
  
  # Reformat from JSON to space-delimited and equals sign
  rt=$(echo $jsonrtag | tr -d '"{},' | sed 's/: /=/g')
  
  # Reapply the updated tags to this resource
  az resource tag --tags $t$rt --id $resid
done

Alternatively, you can apply tags from the resource group to the resources without keeping the existing tags:

# Get the tags for the resource group
jsontag=$(az group show -n myResourceGroup --query tags)

# Reformat from JSON to space-delimited and equals sign
t=$(echo $jsontag | tr -d '"{},' | sed 's/: /=/g')

# Get the resource IDs for all resources in the resource group
r=$(az resource list -g myResourceGroup --query [].id --output tsv)

# Loop through each resource ID
for resid in $r
do
  # Apply tags from resource group to this resource
  az resource tag --tags $t --id $resid
done

To combine several values in a single tag, use a JSON string.

az group update -n myResourceGroup --set tags.CostCenter='{"Dept":"IT","Environment":"Test"}'

To remove all tags on a resource group, use:

az group update -n myResourceGroup --remove tags

To apply tags to a virtual machine, use the az resource tag command. Any existing tags on the resource aren't retained.

az resource tag -n myVM \
  -g myResourceGroup \
  --tags Dept=IT Environment=Test Project=Documentation \
  --resource-type "Microsoft.Compute/virtualMachines"

Find resources by tag

To find resources with a tag name and value, use the az resource list command:

az resource list --tag Environment=Test --query [].name

You can use the returned values for management tasks like stopping all virtual machines with a tag value.

az vm stop --ids $(az resource list --tag Environment=Test --query "[?type=='Microsoft.Compute/virtualMachines'].id" --output tsv)

View costs by tag values

After applying tags to resources, you can view costs for resources with those tags. It takes a while for cost analysis to show the latest usage, so you may not see the costs yet. When the costs are available, you can view costs for resources across resource groups in your subscription. Users must have subscription level access to billing information to see the costs.

To view costs by tag in the portal, select your subscription and select Cost Analysis.

Cost analysis

Then, filter by the tag value, and select Apply.

View cost by tag

You can also use the Azure Billing APIs to programmatically view costs.

Clean up resources

The locked network security group can't be deleted until the lock is removed. To remove the lock, retrieve the IDs of the locks and provide them to the az lock delete command:

vmlock=$(az lock show --name LockVM \
  --resource-group myResourceGroup \
  --resource-type Microsoft.Compute/virtualMachines \
  --resource-name myVM --output tsv --query id)
nsglock=$(az lock show --name LockNSG \
  --resource-group myResourceGroup \
  --resource-type Microsoft.Network/networkSecurityGroups \
  --resource-name myVMNSG --output tsv --query id)
az lock delete --ids $vmlock $nsglock

When no longer needed, you can use the az group delete command to remove the resource group, VM, and all related resources. Exit the SSH session to your VM, then delete the resources as follows:

az group delete --name myResourceGroup

Next steps

In this tutorial, you created a custom VM image. You learned how to:

  • Assign users to a role
  • Apply policies that enforce standards
  • Protect critical resources with locks
  • Tag resources for billing and management

Advance to the next tutorial to learn how to identify changes and manage package updates on a virtual machine.