Skip to main content
Gryba.ca

How to deploy CML 2.9 to Azure using Terraform

Cisco Modeling Labs (CML) is a network simulation tool that I’m using as part of my CCNP studies. It’s affordable—I bought a personal license during Cisco’s Black Friday sale for $125 CAD, but there is also a free license that lets you run 5 or fewer nodes!—and can be deployed on cloud infrastructure to create a rich and extensible learning environment.

I opted to deploy CML in the cloud because I didn’t have an extra computer powerful enough to run it, and I didn’t want to buy and maintain more hardware. For my needs, infrastructure-as-a-service (IaaS) is relatively cheap, flexible, and convenient. Plus, it provides an opportunity to expand my understanding of cloud deployment and dip my toes into network automation.

CML cloud deployment frustrations

When I discovered CML’s cloud deployment tool chain, I felt a bit intimidated because the documentation assumes a basic familiarity with IaaS administration, which I did not have.

I also felt intimidated by the tool chain’s use of Terraform, a software with which I was unfamiliar. I wanted to spend my time and energy using CML, not learning another software just to get it up and running.

But, after many hours of frustrating attempts to deploy CML via alternate means, I relented, and took my time going through the tool chain documentation.

Eventually, I figured it out.

My humble contribution

Although Cisco’s cloud deployment documentation is helpful, it’s more geared toward Amazon Web Services (AWS). If you want to deploy to Azure, the documentation isn’t as detailed, and you need to do some additional legwork and cross-referencing to get things running. I can appreciate why someone new to IaaS in Azure may wish to have someone walk them through the steps.

What follows is my attempt at making explicit the step-by-step instructions for deploying CML 2.9 in Azure using Terraform. My hope is that this will help beginners who might have otherwise given up.

Update 2025-08-08: a kind reader emailed me asking for troubleshooting assistance, and I realised I make a mistake and omission in step 3.4.1. Thanks, Dimosthenis! :)

1. Create and configure Azure services

Before you deploy CML, you need to ensure your Azure account and services are properly configured.

1.1. Create Azure account and subscription

  1. Sign up for an Azure account at https://signup.azure.com/signup.
  2. From the Azure web portal, search for Subscriptions in the top search bar.
  3. Select the Subscriptions service from the search results.
  4. Click the Add button to start creating a new subscription.
  5. Complete the Create a subscription wizard to finish setup.

1.2. Install Azure CLI

Azure Command-Line Interface (CLI) is a cross-platform command-line tool to administer Azure resources. My guide uses Azure CLI to create and configure Azure resources, but you can also use the Azure Portal if you prefer a graphical user interface (GUI).

Note: for all CLI commands that follow, text inside angle brackets (e.g. <resource-group>) should be replaced with your own values. Do not include the brackets.

See Microsoft’s How to install the Azure CLI article for information about installing the tool.

On macOS, Azure CLI is installed using the Homebrew package manager.

  1. Open Terminal and run /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" to install Homebrew.
  2. Run brew update && brew install azure-cli to update Homebrew’s repository information and install Azure CLI.

1.3. Authorise Azure CLI

  1. Run az login to connect Azure CLI to Azure.
  2. Authenticate to Azure in the web browser window that opens.
  3. After you’ve authenticated, return to Azure CLI, and select the subscription you created in step 1.1.
  4. Copy your subscription ID for later use.

1.4. Create resource group

  1. Run az group create --name <resource-group> --location <location> to create a resource group, a container that holds related resources in Azure.

1.5. Create storage resources

  1. Run az storage account create --name <storage-account> --resource-group <resource-group> --location <location> --sku Standard_LRS --encryption-services blob to create a storage account.

    Note: you may wish to customise the above command; e.g. parameter --access-tier cool offers lower storage costs but higher access costs, and --sku Standard_LRS may not provide your desired availability.

  2. Run az ad signed-in-user show --query id -o tsv | az role assignment create --role "Storage Blob Data Contributor" --assignee @- --scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Storage/storageAccounts/<storage-account>" to assign the currently authenticated user the Storage Blob Data Contributor role, which is required in order to create and operate on a blob storage container.

    • If you forgot to copy your subscription ID from earlier, run az account show and copy the id value.
  3. Run az storage container create --account-name <storage-account> --name <container> --auth-mode login to create a blob storage container.

1.6. Create SSH key pair

  1. Run az sshkey create --name <ssh-key-name> --resource-group <resource-group> --encryption-type Ed25519 to create an SSH key pair. Note the file path to the SSH key pair.

    Note: by default, az sshkey create creates a key pair with filenames that your CML’s SSH server won’t expect; to avoid a Permission denied (publickey) error message, it’s easiest to rename the private key to something the SSH server will expect, i.e. id_ed25519.

  2. Run ls <ssh-file-path> to confirm that id_ed25519 and id_ed25519.pub do not already exist.

    • If they don’t already exist, proceed with the following steps; otherwise, note the private key file name for use in step 6.1, and move to step 1.6.4.
  3. Run mv <ssh-file-path>/<generated-private-key> <ssh-file-path>/id_ed25519 && <ssh-file-path>/<generated-public-key> <ssh-file-path>/id_ed25519.pub to rename your SSH key pair to CML’s SSH server’s expected value.

  4. Run chmod 600 <ssh-file-path>/<private-key> to update the private key permissions.

2. Upload CML images and software

Although you can use Azure CLI to upload data to a blob container, Microsoft also publishes an alternative tool specifically designed for high-performance data transfer: AzCopy.

2.1. Download AzCopy

See Microsoft’s Get started with AzCopy article for more information about installing the AzCopy.

On an Apple Silicon macOS device, use the AzCopy Arm64 preview portable binary.

  1. Download the AzCopy Arm64 preview portable binary.
  2. Double-click the file to unzip it.
  3. In Terminal, run cd <file-path-to-azcopy> to change your working directory to where the AzCopy binary file was extracted.

2.2. Authorise AzCopy

  1. Run az account show --query tenantId -o tsv to find your Azure tenant ID, which will be used in the next step.
  2. Run ./azcopy login --tenant-id <tenant-id> to connect AzCopy to your storage account.
  3. Copy the authorisation code displayed in the command output.
  4. Open a web browser, navigate to https://microsoft.com/devicelogin, paste the code, and click Next.
  5. Authenticate to Azure, and click Continue to confirm that you are trying to sign in to Azure Storage AzCopy.

2.3. Download CML images and software

  1. Log in to Cisco Learning Network Store’s My Account page to find your active CML order.
  2. Click View license(s) in the appropriate CML order, and click copy to copy the registration token. Keep this value for later use.
  3. Click Download to be redirected to the CML-Personal 2.9.0 Software Download page.
  4. Download cml2_p_2.9.0-3_amd64-3-pkg.zip and refplat_p-20250616-fcs-iso.zip.
  5. Double-click the files to unzip them. Note the file paths for later use.
  6. Run the signature verification programs per the README files’ instructions to verify the integrity of the downloaded files.
  7. Double-click refplat-20250616-fcs.iso to mount the volume.
  8. Run ./azcopy copy "<pkg-path>/cml2_2.9.0-3_amd64-3.pkg" "https://<storage-account>.blob.core.windows.net/<container>/cml2_2.9.0-3_amd64-3.pkg" to copy the CML server upgrade package to your blob storage container.
  9. Run ./azcopy copy "<refplat-volume-path>/REFPLAT/*" "https://<storage-account>.blob.core.windows.net/<container>/refplat" --recursive to copy the node-definitions and virl-base-images folders to your blob storage container.

3. Configure cloud-cml tool chain

Now that Azure services have been created, configured, and your blob storage container has the required CML images and software, you’re ready to configure Cisco DevNet’s cloud-cml tool chain.

3.1. Clone cloud-cml repository

  1. Run git clone https://github.com/CiscoDevNet/cloud-cml.git <repo-file-path> to clone the remote GitHub repository to your local device.

3.2. Prepare the tool chain for Azure

  1. Run <repo-file-path>/prepare.sh to modify and prepare the tool chain.
  2. Enter no when asked to enable AWS.
  3. Enter yes when asked to enable Azure.
  4. Enter no when asked to enable CyberArk Conjur.
  5. Enter no when asked to enable Hashicorp Vault.

3.3. Install and configure Terraform

See Hashicorp’s Install Terraform web page for more information about installing Terraform.

On macOS, you can install Terraform using Homebrew.

  1. Run brew tap hashicorp/tap to install Hashicorp’s official repository of Homebrew packages.
  2. Run brew install hashicorp/tap/terraform to install Terraform.
  3. Update <repo-file-path>/variables.tf to include your Azure subscription and tenant IDs, replacing notset where appropriate.
    • If you forgot to copy these IDs from earlier, run az account show.

3.4. Edit config.yml configuration file

  1. Update <repo-file-path>/config.yml to ensure the following values are updated.
target: azure

[...]

azure:
  resource_group: <resource-group>
  size: <compute_size>
  [...]
  storage_account: <storage-account>
  container_name: <container>

[...]

common:
  key_name: <ssh-key-name>

[...]

secret:
  secrets:
    smartlicense_token:
      raw_secret: <registration_token>

[...]

app:
  software: cml2_2.9.0-3_amd64-3.pkg

[...]

license:
  flavor: CML_Personal
  1. Update <repo-file-path>/config.yml to comment out any refplat images you don’t want to load during deployment.
    • This speeds up deployment and saves you a bit of money, due to fewer storage blob container access charges.

4. Deploy CML

It’s now time to test your work—but don’t hold your breath! (Seriously, deployment can take 5-10 minutes. Go get a drink of water.)

  1. Run cd <repo-file-path> to change your working directory to your local cloud-cml repository.
  2. Run terraform init to initialise your Terraform configuration.
  3. Run terraform apply to apply your configuration.
  4. Review the proposed deployment plan, and enter yes to begin deployment.
  5. Wait 5–10 minutes for Terraform to deploy CML to Azure. (Get that drink of water!)
  6. If deployment is successful, you will be presented the following output.
cml2info = {
  "address" = "<ip-address>"
  "del" = "ssh -p1122 sysadmin@<ip-address> /provision/del.sh"
  "url" = "https://<ip-address>"
  "version" = "2.9.0+build.3"
}

Note: copy the del command for later use.

5. Use CML

Congratulations! You did it! Now comes the fun part—the reason why you were messing about with Terraform in the first place: labbing!

5.1. Access CML web portal

  1. Navigate to the CML web portal using the URL per step 4.6.
    • If you need the URL again, run terraform output cml2info.
  2. Run terraform output cml2secrets to get the generated secrets.
  3. Log in with username admin and the appropriate secret displayed in the previous step.

5.2. CML resources

New to CML? Want a great introduction? Check out Cisco U’s free Introduction to Network Simulations with Cisco Modeling Labs (CMLLAB) course.

You’ll also find lots of great, detailed information in Cisco’s CML User’s Guide.

Finally, there’s a ton of great content in Cisco DevNet’s cml-community GitHub repository!

6. Destroy CML

Fun time is over, but you’re not done yet! Make sure you destroy all the Azure resources Terraform created for you, otherwise you’re looking at a bill that could cost you hundreds (or thousands!) of dollars.

6.1. Remove CML license

Before destroying CML, it’s important that you remove its license, otherwise the license is not available for use by subsequent deployments.

  1. Ensure all labs are stopped, and no VMs are running.
  2. Run the del command presented to you after successful deployment.

    Note: if you did not rename your SSH private key to id_ed25519 in step 1.6, ensure that you explicitly state the key you want to use when running the del command; e.g. ssh -i <ssh-file-path>/<private-key> -p1122 sysadmin@<ip-address> /provision/del.sh.

  3. (Optional) If the previous command doesn’t work, you can manually remove the CML license by selecting, from within the CML web interface, ToolsLicensingActionsDeregister.

6.2. Terraform destroy

  1. Run terraform destroy from within your local cloud-cml repository to destroy all the Azure resources Terraform created for you.
  2. Wait a minute or two for Terraform to destroy CML.
  3. Check the Azure Portal to ensure that the resources have, indeed, been destroyed; if not, manually remove them from within the GUI. (Or try your hand at Azure CLI!)