教程:使用 Ansible 更新 Azure 虚拟机规模集的自定义映像

重要

运行本文中的示例 playbook 需要 Ansible 2.8(或更高版本)。

Azure 虚拟机规模集是一项 Azure 功能,可让你配置一组相同的、负载均衡的 VM。 规模集不需要额外的成本,它们是从虚拟机构建的。 只需为基础的计算资源(例如 VM 实例、负载均衡器或托管磁盘存储)付费。 使用规模集时,会提供管理层和自动层来运行和缩放应用程序。 你可以改为手动创建和管理各个 VM。 但是,使用规模集有两个主要优点。 它们内置于 Azure 中,并自动缩放虚拟机以满足应用程序需求。

部署 VM 后,可以使用应用所需的软件配置 VM。 可创建自定义映像,而不是为每个 VM 执行此配置任务。 自定义映像是包含任何已安装软件的现有 VM 快照。 在配置规模集时,可以指定要用于该规模集的 VM 映像。 通过使用自定义映像,每个 VM 实例都为应用进行相同的配置。 有时,可能需要更新规模集的自定义映像。 该任务是本教程的重点。

在本文中,学习如何:

  • 使用 HTTPD 配置两个 VM
  • 从现有 VM 创建自定义映像
  • 从映像创建规模集
  • 更新自定义映像

先决条件

  • Azure 订阅:如果没有 Azure 订阅,请在开始之前创建一个免费帐户。

配置两个 VM

本部分中的 playbook 代码创建两个虚拟机并在这两个虚拟机上都安装了 HTTPD。

每个 VM 的 index.html 页面显示一个测试字符串:

  • 第一个 VM 显示值 Image A
  • 第二个 VM 显示值 Image B

此字符串用于模拟使用不同的软件配置每个 VM。

可通过两种方式获取示例 playbook:

  • 下载 playbook 并将其保存到 create_vms.yml

  • 创建名为 create_vms.yml 的新文件。 将以下代码插入新文件。

- name: Create two VMs (A and B) with HTTPS
  hosts: localhost
  connection: local
  vars:
    vm_name: vmforimage
    admin_username: testuser
    admin_password: Pass123$$$abx!
    location: eastus
  tasks:
  - name: Create a resource group
    azure_rm_resourcegroup:
      name: "{{ resource_group }}"
      location: "{{ location }}"

  - name: Create virtual network
    azure_rm_virtualnetwork:
      resource_group: "{{ resource_group }}"
      name: "{{ vm_name }}"
      address_prefixes: "10.0.0.0/16"

  - name: Create subnets for VM A and B
    azure_rm_subnet:
      resource_group: "{{ resource_group }}"
      virtual_network: "{{ vm_name }}"
      name: "{{ vm_name }}"
      address_prefix: "10.0.1.0/24"

  - name: Create Network Security Group that allows HTTP
    azure_rm_securitygroup:
      resource_group: "{{ resource_group }}"
      name: "{{ vm_name }}"
      rules:
        - name: HTTP
          protocol: Tcp
          destination_port_range: 80
          access: Allow
          priority: 1002
          direction: Inbound

  - name: Create public IP addresses for VM A and B
    azure_rm_publicipaddress:
      resource_group: "{{ resource_group }}"
      allocation_method: Static
      name: "{{ vm_name }}_{{ item }}"
    loop:
      - A
      - B
    register: pip_output

  - name: Create virtual network interface cards for VM A and B
    azure_rm_networkinterface:
      resource_group: "{{ resource_group }}"
      name: "{{ vm_name }}_{{ item }}"
      virtual_network: "{{ vm_name }}"
      subnet: "{{ vm_name }}"
      public_ip_name: "{{ vm_name }}_{{ item }}"
      security_group: "{{ vm_name }}"
    loop:
      - A
      - B

  - name: Create VM A and B
    azure_rm_virtualmachine:
      resource_group: "{{ resource_group }}"
      name: "{{ vm_name }}{{ item }}"
      admin_username: "{{ admin_username }}"
      admin_password: "{{ admin_password }}"
      vm_size: Standard_B1ms
      network_interfaces: "{{ vm_name }}_{{ item }}"
      image:
        offer: UbuntuServer
        publisher: Canonical
        sku: 16.04-LTS
        version: latest
    loop:
      - A
      - B

  - name: Create VM Extension
    azure_rm_virtualmachineextension:
      resource_group: "{{ resource_group }}"
      name: testVMExtension
      virtual_machine_name: "{{ vm_name }}{{ item }}"
      publisher: Microsoft.Azure.Extensions
      virtual_machine_extension_type: CustomScript
      type_handler_version: 2.0
      auto_upgrade_minor_version: true
      settings: {"commandToExecute": "sudo apt-get -y install apache2"}
    loop:
      - A
      - B

  - name: Create VM Extension
    azure_rm_virtualmachineextension:
      resource_group: "{{ resource_group }}"
      name: testVMExtension
      virtual_machine_name: "{{ vm_name }}{{ item }}"
      publisher: Microsoft.Azure.Extensions
      virtual_machine_extension_type: CustomScript
      type_handler_version: 2.0
      auto_upgrade_minor_version: true
      settings: {"commandToExecute": "printf '<html><body><h1>Image {{ item }}</h1></body></html>' >> index.html; sudo cp index.html /var/www/html/"}
    loop:
      - A
      - B

  - debug:
      msg: "Public IP Address A: {{ pip_output.results[0].state.ip_address }}"

  - debug:
      msg: "Public IP Address B: {{ pip_output.results[1].state.ip_address }}"

使用 ansible-playbook 命令运行 playbook,用资源组名称替换 myrg

ansible-playbook create-vms.yml --extra-vars "resource_group=myrg"

由于 playbook 的 debug 部分,ansible-playbook 命令将打印每个 VM 的 IP 地址。 请复制这些 IP 地址供稍后使用。

连接到两个 VM

在本部分中,你将连接到每个 VM。 如前一部分所述,字符串 Image AImage B 模拟具有不同配置的两个不同 VM。

使用上一节中的 IP 地址,打开浏览器并连接到每个 VM。

从每个 VM 创建映像

此时,你有两个 VM,其配置(它们的 index.html 文件)略有不同。

本部分中的 playbook 代码为每个 VM 创建自定义映像:

  • image_vmforimageA - 为在其主页上显示 Image A 的 VM 创建的自定义映像。
  • image_vmforimageB - 为在其主页上显示 Image B 的 VM 创建的自定义映像。

可通过两种方式获取示例 playbook:

  • 下载 playbook 并将其保存到 capture-images.yml

  • 创建名为 capture-images.yml 的新文件。 将以下代码插入新文件:

- name: Capture VM Images
  hosts: localhost
  connection: local
  vars:
    vm_name: vmforimage
  tasks:

  - name: Stop and generalize VMs
    azure_rm_virtualmachine:
      resource_group: "{{ resource_group }}"
      name: "{{ vm_name }}{{ item }}"
      generalized: yes
    loop:
      - A
      - B

  - name: Create an images from a VMs
    azure_rm_image:
      resource_group: "{{ resource_group }}"
      name: "image_{{ vm_name }}{{ item }}"
      source: "{{ vm_name }}{{ item }}"
    loop:
      - A
      - B

使用 ansible-playbook 命令运行 playbook,用资源组名称替换 myrg

ansible-playbook capture-images.yml --extra-vars "resource_group=myrg"

使用映像 A 创建规模集

在本部分中,使用了 playbook 配置以下 Azure 资源:

  • 公共 IP 地址
  • 负载均衡器
  • 规模集引用了 image_vmforimageA

可通过两种方式获取示例 playbook:

  • 下载 playbook 并将其保存到 create-vmss.yml

  • 创建名为 create-vmss.yml 的新文件。 将以下代码插入新文件:

---
- hosts: localhost
  vars:
    vmss_name: vmsstest
    location: eastus
    admin_username: vmssadmin
    admin_password: User123!!!abc
    vm_name: vmforimage
    image_name: "image_vmforimageA"

  tasks:

    - name: Create public IP address
      azure_rm_publicipaddress:
        resource_group: "{{ resource_group }}"
        allocation_method: Static
        name: "{{ vmss_name }}"
      register: pip_output

    - name: Create a load balancer
      azure_rm_loadbalancer:
        name: "{{ vmss_name }}lb"
        location: "{{ location }}"
        resource_group: "{{ resource_group }}"
        public_ip: "{{ vmss_name }}"
        probe_protocol: Tcp
        probe_port: 80
        probe_interval: 10
        probe_fail_count: 3
        protocol: Tcp
        load_distribution: Default
        frontend_port: 80
        backend_port: 80
        idle_timeout: 4
        natpool_frontend_port_start: 50000
        natpool_frontend_port_end: 50040
        natpool_backend_port: 22
        natpool_protocol: Tcp

    - name: Create a scale set
      azure_rm_virtualmachinescaleset:
        resource_group: "{{ resource_group }}"
        name: "{{ vmss_name }}"
        vm_size: Standard_DS1_v2
        admin_username: "{{ admin_username }}"
        admin_password: "{{ admin_password }}"
        ssh_password_enabled: true
        capacity: 2
        virtual_network_name: "{{ vm_name }}"
        subnet_name: "{{ vm_name }}"
        upgrade_policy: Manual
        tier: Standard
        managed_disk_type: Standard_LRS
        os_disk_caching: ReadWrite
        image:
          name: "{{ image_name }}"
          resource_group: "{{ resource_group }}"
        load_balancer: "{{ vmss_name }}lb"

    - debug:
        msg: "Scale set public IP address: {{ pip_output.state.ip_address }}"

使用 ansible-playbook 命令运行 playbook,用资源组名称替换 myrg

ansible-playbook create-vmss.yml --extra-vars "resource_group=myrg"

由于 playbook 的 debug 部分,ansible-playbook 命令将输出规模集的 IP 地址。 请复制此 IP 地址供稍后使用。

连接到规模集

使用上一节中的 IP 地址,连接到规模集。

如前一部分所述,字符串 Image AImage B 模拟具有不同配置的两个不同 VM。

规模集引用名为 image_vmforimageA 的自定义映像。 自定义映像 image_vmforimageA 是从主页显示 Image A 的 VM 中创建的。

因此,你会看到一个显示 Image A 的主页。

在继续进行下一部分时,请保持浏览器窗口处于打开状态。

更改规模集和升级实例中的自定义映像

本部分中的 playbook 代码会更改规模集的映像 - 从 image_vmforimageA 更改为 image_vmforimageB。 此外,还会更新由规模集部署的所有当前虚拟机。

可通过两种方式获取示例 playbook:

  • 下载 playbook 并将其保存到 update-vmss-image.yml

  • 创建名为 update-vmss-image.yml 的新文件。 将以下代码插入新文件:

- name: Update scale set image reference
  hosts: localhost
  connection: local
  vars:
    vmss_name: vmsstest
    image_name: image_vmforimageB
    admin_username: vmssadmin
    admin_password: User123!!!abc
  tasks:

  - name: Update scale set - second image
    azure_rm_virtualmachinescaleset:
      resource_group: "{{ resource_group }}"
      name: "{{ vmss_name }}"
      vm_size: Standard_DS1_v2
      admin_username: "{{ admin_username }}"
      admin_password: "{{ admin_password }}"
      ssh_password_enabled: true
      capacity: 3
      virtual_network_name: "{{ vmss_name }}"
      subnet_name: "{{ vmss_name }}"
      upgrade_policy: Manual
      tier: Standard
      managed_disk_type: Standard_LRS
      os_disk_caching: ReadWrite
      image:
        name: "{{ image_name }}"
        resource_group: "{{ resource_group }}"
      load_balancer: "{{ vmss_name }}lb"

  - name: List all of the instances
    azure_rm_virtualmachinescalesetinstance_facts:
      resource_group: "{{ resource_group }}"
      vmss_name: "{{ vmss_name }}"
    register: instances

  - debug:
      var: instances

  - name: manually upgrade all the instances 
    azure_rm_virtualmachinescalesetinstance:
      resource_group: "{{ resource_group }}"
      vmss_name: "{{ vmss_name }}"
      instance_id: "{{ item.instance_id }}"
      latest_model: yes
    with_items: "{{ instances.instances }}"

使用 ansible-playbook 命令运行 playbook,用资源组名称替换 myrg

ansible-playbook update-vmss-image.yml --extra-vars "resource_group=myrg"

返回到浏览器并刷新页面,查看虚拟机的基础自定义映像是否已更新。

清理资源

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

后续步骤