使用 Ansible 在 Azure 中创建 Windows 虚拟机

本文介绍如何使用 Ansible 在 Azure 中部署 Windows Server 2019 VM。

在这篇文章中,你将学会如何:

  • 创建资源组
  • 创建虚拟网络、公共 IP、网络安全组和网络接口
  • 部署 Windows Server 虚拟机
  • 通过 WinRM 连接到虚拟机
  • 运行 Ansible playbook 以配置 Windows IIS

先决条件

  • Azure 订阅:如果没有 Azure 订阅,请在开始之前创建 一个免费帐户
  • Azure 服务主体创建服务主体,记下以下值: appIddisplayName密码租户

向 Ansible 添加 WinRM 支持

若要通过 WinRM 进行通信,Ansible 控制服务器需要 python 包 pywinrm

在 Ansible 服务器上运行以下命令以安装 pywinrm

pip install "pywinrm>=0.3.0"

有关详细信息,请参阅 适用于 Ansible 的 Windows 远程管理

创建资源组

创建一个名为 azure_windows_vm.yml 的 Ansible 剧本,并将以下内容复制到该剧本中:

---
- name: Create Azure VM
  hosts: localhost
  connection: local
  tasks:

  - name: Create resource group
    azure_rm_resourcegroup:
      name: myResourceGroup
      location: eastus

要点

  • hosts 设为 localhost ,在 Ansible 服务器上直接本地运行 playbook。

创建虚拟网络和子网

将以下任务添加到 azure_windows_vm.yml Ansible playbook 以创建虚拟网络:

  - 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

创建公共 IP 地址

将以下任务添加到 azure_windows_vm.yml playbook 以创建公共 IP 地址:

  - 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 }}"

要点

  • Ansible register 模块用于将 azure_rm_publicipaddress 的输出存储在名为 output_ip_address 的变量中。
  • debug 模块用于将 VM 的公共 IP 地址输出到控制台。

创建网络安全组和 NIC

网络安全组定义了允许和不允许到达虚拟机的流量。

若要打开 WinRM 和 HTTP 端口,请将以下任务添加到 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

要点

  • 虚拟网络接口卡将 VM 连接到其虚拟网络、公共 IP 地址和安全组。
  • azure_rm_securitygroup 创建了一个 Azure 网络安全组,用于允许端口 5985 和端口 5986,以便允许从 Ansible 服务器到远程主机的 WinRM 流量。

创建虚拟机

接下来,创建使用本文前面部分创建的所有资源的虚拟机。

将以下任务添加到 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

admin_password值为{{ password }}包含 Windows VM 密码的 Ansible 变量。 若要安全地填充该变量,请将条目 var_prompts 添加到 playbook 的开头。

- name: Create Azure VM
  hosts: localhost
  connection: local
  vars_prompt:
    - name: password
      prompt: "Enter local administrator password"
  tasks:

要点

  • 避免将敏感数据存储为纯文本。 使用var_prompts在运行时填充变量。 添加 no_log: true 以防止密码记录。

配置 WinRM 侦听器

Ansible 使用 PowerShell 通过 WinRM 连接和配置 Windows 远程主机。

若要配置 WinRM,请添加以下 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

在完全配置 WinRM 之前,Ansible 无法连接到 VM。

请在 playbook 中添加以下任务以等待 WinRM 连接:

  - 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

要点

  • azure_rm_virtualmachineextension 模块允许在本地 Azure Windows 上运行 PowerShell 脚本。 ConfigureRemotingForAnsible.ps1运行 PowerShell 脚本通过创建自签名证书并打开 Ansible 连接所需的端口来配置 WinRM。
  • azure_rm_publicipaddress_info 模块查询 Azure 中的公共 IP 地址,然后将 set_fact 输出存储在变量中供 wait_for 模块使用。

完整示例 Ansible 剧本

本节列出了你在本文过程中已构建的整个 Ansible playbook 示例。

---
- 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

连接到 Windows 虚拟机

创建一个名为connect_azure_windows_vm.yml的新 Ansible 剧本,并将以下内容复制到该剧本中:

---
- 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:

运行 Ansible 剧本。

ansible-playbook connect_azure_windows_vm.yml -i <publicIPaddress>,

<publicIPaddress> 替换为您的虚拟机地址。

要点

  • Ansible 的配置确定 Ansible 如何连接和向远程主机进行身份验证。 若要连接到 Windows 主机,需要定义的变量取决于 WinRM 连接类型和所选的身份验证选项。 有关详细信息,请参阅 “连接到 Windows 主机 ”和 “Windows 身份验证选项”。
  • 在公共 IP 地址后面添加逗号会绕过 Ansible 的清单分析程序。 通过此技术,无需清单文件即可运行剧本。

清理资源

  1. 将以下代码保存为 delete_rg.yml.

    ---
    - hosts: localhost
      tasks:
        - name: Deleting resource group - "{{ name }}"
          azure_rm_resourcegroup:
            name: "{{ name }}"
            state: absent
          register: rg
        - debug:
            var: rg
    
  2. 使用 ansible-playbook 命令来运行 playbook。 将占位符替换为要删除的资源组的名称。 资源组中的所有资源都将被删除。

    ansible-playbook delete_rg.yml --extra-vars "name=<resource_group>"
    

    要点

    • 因为 playbook 中的register变量和debug部分,命令完成后会显示结果。

后续步骤