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

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

在本文中,学习如何:

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

先决条件

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

向 Ansible 添加 WinRM 支持

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

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

pip install "pywinrm>=0.3.0"

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

创建资源组

创建名为的 azure_windows_vm.yml Ansible playbook,并将以下内容复制到 playbook:

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

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

要点

  • 设置为 hostslocalhost ,并在 connection_local_ 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

网络安全组定义哪些流量可以进入 VM,哪些不可以。

要打开 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 网络安全组,通过允许端口 59855986,允许 WinRM 流量从 Ansible 服务器到远程主机。

创建虚拟机

下一步是创建虚拟机,该虚拟机使用在本文的前述部分创建的所有资源。

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

{{ password }}admin_password 值是一个 Ansible 变量,其中包含 Windows VM 密码。 要安全填充该变量,请在 playbook 的开头添加一个 var_prompts 条目。

- 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/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 脚本可通过创建自签名证书并打开所需的端口来配置 WinRM 以连接 Ansible。
  • azure_rm_publicipaddress_info 模块从 Azure 查询公共 IP 地址,然后 set_fact 将输出存储在变量中供 wait_for 模块使用。

完整的示例 Ansible playbook

此部分列出在本文中从头至尾生成的整个示例 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/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 虚拟机

创建新的 Ansible playbook,并将 connect_azure_windows_vm.yml 以下内容复制到 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:

运行 Ansible playbook。

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

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

要点

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

清理资源

  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 部分,因此在命令完成时,将显示结果。

后续步骤