Create a Windows virtual machine in Azure using Ansible
This article shows how to deploy a Windows Server 2019 VM in Azure with Ansible.
In this article, you learn how to:
- Create a resource group
- Create a virtual network, public IP, network security group, and network interface
- Deploy a Windows Server virtual machine
- Connect to the virtual machine via WinRM
- Run an Ansible playbook to configure Windows IIS
- Azure subscription: If you don't have an Azure subscription, create a free account before you begin.
- Azure service principal: Create a service principal, making note of the following values: appId, displayName, password, and tenant.
Install Ansible: Do one of the following options:
- Install and configure Ansible on a Linux virtual machine
- Configure Azure Cloud Shell
To communicate over WinRM, Ansible control server needs the python package pywinrm
.
Run the following command on the Ansible server to install pywinrm
:
pip install "pywinrm>=0.3.0"
For more information, see Windows Remote Management for Ansible.
Create an Ansible playbook named azure_windows_vm.yml
and copy the following contents into the playbook:
---
- name: Create Azure VM
hosts: localhost
connection: local
tasks:
- name: Create resource group
azure_rm_resourcegroup:
name: myResourceGroup
location: eastus
Key points:
- Setting
hosts
to localhost andconnection
as_local_
runs the playbook locally on the Ansible server.
Add the following tasks to the azure_windows_vm.yml
Ansible playbook to create a virtual network:
- name: Create virtual network
azure_rm_virtualnetwork:
resource_group: myResourceGroup
name: vNet
address_prefixes: "10.0.0.0/16"
- name: Add subnet
azure_rm_subnet:
resource_group: myResourceGroup
name: subnet
address_prefix: "10.0.1.0/24"
virtual_network: vNet
Add the following tasks to the azure_windows_vm.yml
playbook to create a public IP address:
- name: Create public IP address
azure_rm_publicipaddress:
resource_group: myResourceGroup
allocation_method: Static
name: pip
register: output_ip_address
- name: Output public IP
debug:
msg: "The public IP is {{ output_ip_address.state.ip_address }}"
Key points:
- Ansible
register
module is used to store the output fromazure_rm_publicipaddress
in a variable calledoutput_ip_address
. - The
debug
module is used to output the public IP address of the VM to the console.
Network security group defines what traffic is allowed and not allowed to reach the VM.
To open the WinRM and HTTP ports, add the following tasks to the azure_windows_vm.yml
Ansible playbook:
- name: Create Network Security Group
azure_rm_securitygroup:
resource_group: myResourceGroup
name: networkSecurityGroup
rules:
- name: 'allow_rdp'
protocol: Tcp
destination_port_range: 3389
access: Allow
priority: 1001
direction: Inbound
- name: 'allow_web_traffic'
protocol: Tcp
destination_port_range:
- 80
- 443
access: Allow
priority: 1002
direction: Inbound
- name: 'allow_powershell_remoting'
protocol: Tcp
destination_port_range:
- 5985
- 5986
access: Allow
priority: 1003
direction: Inbound
- name: Create a network interface
azure_rm_networkinterface:
name: nic
resource_group: myResourceGroup
virtual_network: vNet
subnet_name: subnet
security_group: networkSecurityGroup
ip_configurations:
- name: default
public_ip_address_name: pip
primary: True
Key points:
- A virtual network interface card connects your VM to its virtual network, public IP address, and security group.
- The
azure_rm_securitygroup
creates an Azure network security group to allow WinRM traffic from the Ansible server to the remote host by allowing port5985
and5986
.
Next create a virtual machine that uses all the resources you've created in the previous sections of this article.
Add the following task to the azure_windows_vm.yml
Ansible playbook:
- name: Create VM
azure_rm_virtualmachine:
resource_group: myResourceGroup
name: win-vm
vm_size: Standard_DS1_v2
admin_username: azureuser
admin_password: "{{ password }}"
network_interfaces: nic
os_type: Windows
image:
offer: WindowsServer
publisher: MicrosoftWindowsServer
sku: 2019-Datacenter
version: latest
no_log: true
The admin_password
value of {{ password }}
is an Ansible variable that contains the Windows VM password. To securely populate that variable, add a var_prompts
entry to the beginning of the playbook.
- name: Create Azure VM
hosts: localhost
connection: local
vars_prompt:
- name: password
prompt: "Enter local administrator password"
tasks:
Key points:
- Avoid storing sensitive data as plain text. Use
var_prompts
to populate variables at run time. Addno_log: true
to prevent passwords from being log.
Ansible uses PowerShell to connect and configure Windows remote hosts via WinRM.
To configure WinRM, add the following ext azure_rm_virtualmachineextension
:
- name: Create VM script extension to enable HTTPS WinRM listener
azure_rm_virtualmachineextension:
name: winrm-extension
resource_group: myResourceGroup
virtual_machine_name: win-vm
publisher: Microsoft.Compute
virtual_machine_extension_type: CustomScriptExtension
type_handler_version: '1.9'
settings: '{"fileUris": ["https://raw.githubusercontent.com/ansible/ansible-documentation/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"],"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ConfigureRemotingForAnsible.ps1"}'
auto_upgrade_minor_version: true
Ansible can't connect to the VM until WinRM is fully configured.
Add the following tasks to your playbook to wait for the WinRM connection:
- name: Get facts for one Public IP
azure_rm_publicipaddress_info:
resource_group: myResourceGroup
name: pip
register: publicipaddresses
- name: set public ip address fact
set_fact: publicipaddress="{{ publicipaddresses | json_query('publicipaddresses[0].ip_address')}}"
- name: wait for the WinRM port to come online
wait_for:
port: 5986
host: '{{ publicipaddress }}'
timeout: 600
Key points:
- The
azure_rm_virtualmachineextension
module allows you to run a PowerShell script locally on the Azure Windows. Running theConfigureRemotingForAnsible.ps1
PowerShell script configures WinRM by creating self-signed certificates and opening the necessary ports for Ansible to connect. - The
azure_rm_publicipaddress_info
module queries the public IP address from Azure thenset_fact
stores the output in a variable for thewait_for
module to use.
This section lists the entire sample Ansible playbook that you've built up over the course of this article.
---
- name: Create Azure VM
hosts: localhost
connection: local
vars_prompt:
- name: password
prompt: "Enter local administrator password"
tasks:
- name: Create resource group
azure_rm_resourcegroup:
name: myResourceGroup
location: eastus
- name: Create virtual network
azure_rm_virtualnetwork:
resource_group: myResourceGroup
name: vNet
address_prefixes: "10.0.0.0/16"
- name: Add subnet
azure_rm_subnet:
resource_group: myResourceGroup
name: subnet
address_prefix: "10.0.1.0/24"
virtual_network: vNet
- name: Create public IP address
azure_rm_publicipaddress:
resource_group: myResourceGroup
allocation_method: Static
name: pip
register: output_ip_address
- name: Output public IP
debug:
msg: "The public IP is {{ output_ip_address.state.ip_address }}"
- name: Create Network Security Group
azure_rm_securitygroup:
resource_group: myResourceGroup
name: networkSecurityGroup
rules:
- name: 'allow_rdp'
protocol: Tcp
destination_port_range: 3389
access: Allow
priority: 1001
direction: Inbound
- name: 'allow_web_traffic'
protocol: Tcp
destination_port_range:
- 80
- 443
access: Allow
priority: 1002
direction: Inbound
- name: 'allow_powershell_remoting'
protocol: Tcp
destination_port_range:
- 5985
- 5986
access: Allow
priority: 1003
direction: Inbound
- name: Create a network interface
azure_rm_networkinterface:
name: nic
resource_group: myResourceGroup
virtual_network: vNet
subnet_name: subnet
security_group: networkSecurityGroup
ip_configurations:
- name: default
public_ip_address_name: pip
primary: True
- name: Create VM
azure_rm_virtualmachine:
resource_group: myResourceGroup
name: win-vm
vm_size: Standard_DS1_v2
admin_username: azureuser
admin_password: "{{ password }}"
network_interfaces: nic
os_type: Windows
image:
offer: WindowsServer
publisher: MicrosoftWindowsServer
sku: 2019-Datacenter
version: latest
no_log: true
- name: Create VM script extension to enable HTTPS WinRM listener
azure_rm_virtualmachineextension:
name: winrm-extension
resource_group: myResourceGroup
virtual_machine_name: win-vm
publisher: Microsoft.Compute
virtual_machine_extension_type: CustomScriptExtension
type_handler_version: '1.9'
settings: '{"fileUris": ["https://raw.githubusercontent.com/ansible/ansible-documentation/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"],"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ConfigureRemotingForAnsible.ps1"}'
auto_upgrade_minor_version: true
- name: Get facts for one Public IP
azure_rm_publicipaddress_info:
resource_group: myResourceGroup
name: pip
register: publicipaddresses
- name: set public ip address fact
set_fact: publicipaddress="{{ publicipaddresses | json_query('publicipaddresses[0].ip_address')}}"
- name: wait for the WinRM port to come online
wait_for:
port: 5986
host: '{{ publicipaddress }}'
timeout: 600
Create a new Ansible playbook named connect_azure_windows_vm.yml
and copy the following contents into the playbook:
---
- hosts: all
vars_prompt:
- name: ansible_password
prompt: "Enter local administrator password"
vars:
ansible_user: azureuser
ansible_connection: winrm
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
tasks:
- name: Test connection
win_ping:
Run the Ansible playbook.
ansible-playbook connect_azure_windows_vm.yml -i <publicIPaddress>,
Replace <publicIPaddress>
with your virtual machine's address.
Key points:
- Ansible's configuration determines how Ansible connects and authenticates to remote hosts. The variables you need to define to connect to a Windows host depend on your WinRM connection type and the authentication option you've chosen. For more information, see Connecting to a Windows Host and Windows Authentication Options.
- Adding a comma after the public IP address bypasses Ansible's inventory parser. This technique allows you to run playbooks without an inventory file.
Save the following code as
delete_rg.yml
.--- - hosts: localhost tasks: - name: Deleting resource group - "{{ name }}" azure_rm_resourcegroup: name: "{{ name }}" state: absent register: rg - debug: var: rg
Run the playbook using the ansible-playbook command. Replace the placeholder with the name of the resource group to be deleted. All resources within the resource group will be deleted.
ansible-playbook delete_rg.yml --extra-vars "name=<resource_group>"
Key points:
- Because of the
register
variable anddebug
section of the playbook, the results display when the command finishes.
- Because of the