A
u
Belaid Youba Hadj Arab, Etienne Deneuve
Jun 4, 2023 · 6 min read

Deploying a virtual machine with Terraform and Ansible

Side view worker wearing gloves

In this second part, we finally get down to the nitty-gritty: vm deployment. Make sure you’ve followed the steps in the previous article, and we’re ready to go

1. Azure

  1. The first thing to do is to connect to Azure and deploy our resources:

    <!-- cspell:disable -->
    ```bash
    az login --tenant YOUR-TENANT
    ```

Note that there are several ways to connect to Azure, here with a tenant, but also without. Do an az login -h to choose the one that suits you most.

2. Terraform

2.1. Creating .tf files

  1. Create a folder for our Terraform code:

    <!-- cspell:disable -->
    ```bash
    mkdir terraform-vm-tuto
    cd terraform-vm-tuto
    # and if you're using vscode
    code .
    ```
  2. In this folder, we’ll create 5 files: main.tf, variables.tf,providers.tf, network.tf and outputs.tf.

  3. Let’s start with providers.tf. Its role is to declare the providers that will be used to deploy resources:

    <!-- cspell:disable -->
    ```hcl
    terraform {
    required_providers {
    azurerm = {
    source = "hashicorp/azurerm"
    version = "@>2.0"
    }
    tls = {
    source = "hashicorp/tls"
    version = "@>4.0"
    }
    }
    }
    provider "azurerm" {
    features {}
    }
    ```
  4. Then main.tf. This is where you declare the resources to be deployed, such as the resource group and the virtual machine, as well as their configurations:

    <!-- cspell:disable -->
    ```hcl
    resource "azurerm_resource_group" "tf_rg" {
    location = var.resource_group_location
    name = var.resource_group_name
    }
    resource "azurerm_linux_virtual_machine" "tf_my_terraform_vm" {
    name = "myVM"
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    network_interface_ids = [azurerm_network_interface.tf_my_terraform_nic.id]
    size = "Standard_DS1_v2"
    os_disk {
    name = "myOsDisk"
    caching = "ReadWrite"
    storage_account_type = "Premium_LRS"
    }
    source_image_reference {
    publisher = "Canonical"
    offer = "0001-com-ubuntu-server-jammy"
    sku = "22_04-lts-gen2"
    version = "latest"
    }
    computer_name = var.computer_name
    admin_username = var.admin_username
    disable_password_authentication = true
    admin_ssh_key {
    username = var.admin_username
    public_key = tls_private_key.tf_public_ssh.public_key_openssh
    }
    }
    ```
  5. Next comes network.tf. Here you declare vnet, subnet and other networking related resources:

    <!-- cspell:disable -->
    ```bash
    resource "azurerm_virtual_network" "tf_my_terraform_network" {
    name = "myVnet"
    address_space = ["10.0.0.0/16"]
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    }
    resource "azurerm_subnet" "tf_my_terraform_subnet" {
    name = "mySubnet"
    resource_group_name = azurerm_resource_group.tf_rg.name
    virtual_network_name = azurerm_virtual_network.tf_my_terraform_network.name
    address_prefixes = ["10.0.1.0/24"]
    }
    resource "azurerm_public_ip" "tf_my_terraform_public_ip" {
    name = "myPublicIP"
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    allocation_method = "Dynamic"
    }
    resource "azurerm_network_security_group" "tf_my_terraform_nsg" {
    name = "myNetworkSecurityGroup"
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    security_rule {
    name = "SSH"
    priority = 1001
    direction = "Inbound"
    access = "Allow"
    protocol = "Tcp"
    source_port_range = "*"
    destination_port_range = "22"
    source_address_prefix = "*"
    destination_address_prefix = "*"
    }
    }
    resource "azurerm_network_interface" "tf_my_terraform_nic" {
    name = "myNIC"
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    ip_configuration {
    name = "my_nic_configuration"
    subnet_id = azurerm_subnet.tf_my_terraform_subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id = azurerm_public_ip.tf_my_terraform_public_ip.id
    }
    }
    resource "azurerm_network_interface_security_group_association" "example" {
    network_interface_id = azurerm_network_interface.tf_my_terraform_nic.id
    network_security_group_id = azurerm_network_security_group.tf_my_terraform_nsg.id
    }
    resource "tls_private_key" "tf_public_ssh" {
    algorithm = "RSA"
    rsa_bits = 4096
    }
    ```
  6. The variables.tf file will contain all the variables used to configure the resources. It’s always a good idea to configure them via variables, as it will be easier to modify them afterwards to suit your needs:

    <!-- cspell:disable -->
    ```hcl
    variable "resource_group_name" {
    type = string
    default = "rg-terraform-ansible-tuto"
    description = "nom du ressource groupe"
    }
    variable "resource_group_location" {
    type = string
    default = "westeurope"
    description = "localisation du ressource groupe"
    }
    variable "computer_name" {
    type = string
    default = "myvm"
    description = "nom de la machine virtual"
    }
    variable "admin_username" {
    type = string
    default = "azureadmin"
    description = "nom de l'utilisateur admin"
    }
    variable "vnet_address_space" {
    type = list(string)
    default = ["10.0.0.0/16"]
    description = "plage ip du virtual network"
    }
    variable "subnet_address_space" {
    type = list(string)
    default = ["10.0.1.0/24"]
    description = "plage ip du sub-network"
    }
    ```
  7. And outputs.tf :

    <!-- cspell:disable -->
    ```hcl
    output "public_ip_address" {
    value = azurerm_linux_virtual_machine.tf_my_terraform_vm.public_ip_address
    }
    output "tls_private_key" {
    value = tls_private_key.tf_public_ssh.private_key_pem
    sensitive = true
    }
    ```

2.1.1. Deploying multiple vm

  1. In the code above, we’ve only deployed one virtual machine, so if we want to deploy 2 or 3, we’ll just have to create more blocks. You can also use the meta-argument: count.

  2. To do this, add :

    <!-- cspell:disable -->
    ```hcl
    resource "azurerm_linux_virtual_machine" "tf_my_terraform_vm" {
    count = 3 # le nombre de vm qui sera déployé est de 3, par exmeple
    name = var.vm_names[count.index]
    location = azurerm_resource_group.tf_rg.location
    resource_group_name = azurerm_resource_group.tf_rg.name
    network_interface_ids = [azurerm_network_interface.tf_my_terraform_nic.id]
    size = "Standard_DS1_v2"
    os_disk {
    name = var.vm_os_disk_names[count.index]
    caching = "ReadWrite"
    storage_account_type = "Premium_LRS"
    }
    source_image_reference {
    publisher = "Canonical"
    offer = "0001-com-ubuntu-server-jammy"
    sku = "22_04-lts-gen2"
    version = "latest"
    }
    computer_name = var.computer_names[count.index]
    admin_username = var.admin_username[count.index]
    disable_password_authentication = true
    admin_ssh_key {
    username = var.admin_username[count.index]
    public_key = tls_private_key.tf_public_ssh.public_key_openssh
    }
    }
    ```
  3. We modify some variables in the variables.tf file:

    <!-- cspell:disable -->
    ```hcl
    variable "computer_name" {
    type = list(string)
    default = ["myvm","myvm2","myvm3"]
    description = "nom de la machine virtual"
    }
    variable "admin_username" {
    type = list(string)
    default = ["azureadmin","azureadmin2","azureadmin3"]
    description = "nom de l'utilisateur admin"
    }
    ```

2.2 Initialization and deployment

  1. Now that the files have been created, we’re going to initialize Terraform. To do this, go to the root of the folder created above:

    Terminal window
    terraform init
  2. Once initialization is complete, we can run an optional command to check the syntax of our code:

    Terminal window
    terraform validate
  3. Once our code has been verified, we’ll generate the plan using the command :

    Terminal window
    terraform plan -out main.tfplan

    Note that you can name your plan whatever you like, here we’ve chosen main.tfplan.

  4. This is the moment we’ve been waiting for! With this command, we can finally deploy our vm :

    Terminal window
    terraform apply "main.tfplan

    Note that it’s possible to do a terraform apply without using a terraform plan, but it’s always advisable to generate a plan, as it’s very useful for consistent, stable deployment.

  5. Thanks to the outputs we’ll get our vm’s ip address and private ssh key:

Terminal window
# ip recovery
terraform output public_ip_address
# retrieve private ssh key
terraform output -raw tls_private_key > @/.ssh/vm_id_rsa
  1. Before connecting to the vm, we must first restrict access to the private key:

    Terminal window
    chmod 600 @/.ssh/vm_id_rsa
  2. We can now try to connect to the vm:

    Terminal window
    ssh -i @/.ssh/vm_id_rsa azureadmin@<public_ip_address>
  • Now we just need to configure it using Ansible, but that’s for the next blog.
Ansible Terraform Azure

Related articles