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

重要

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

Azure 虚拟机规模集 是一项 Azure 功能,可用于配置一组相同的负载均衡 VM。 规模集无需额外付费,它们是从虚拟机生成的。 只需为基础计算资源(例如 VM 实例、负载均衡器或托管磁盘存储)付费。 使用规模集时,提供管理和自动化层,以支持应用程序的运行和扩展。 可以改为手动创建和管理单个 VM。 但是,使用规模集有两个关键优势。 它们内置于 Azure 中,可自动缩放虚拟机以满足应用程序需求。

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

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

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

先决条件

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

配置两个 VM

本部分中的 playbook 代码创建两个虚拟机,其中两个虚拟机都安装了 HTTPD。

index.html每个虚拟机的页面上会显示一个测试字符串。

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

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

可通过两种方法获取示例剧本:

  • 下载 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。 如前一部分所述,字符串 Image AImage B 模拟具有两个不同配置的 VM。

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

从每个 VM 创建映像

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

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

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

可通过两种方法获取示例剧本:

  • 下载 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 创建规模集

在本节中,运行手册用于配置以下 Azure 资源:

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

可通过两种方法获取示例剧本:

  • 下载 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 的特定部分,该 ansible-playbook 命令将打印出规模集的 IP 地址。 复制此 IP 地址供以后使用。

连接到规模集

使用上一部分的 IP 地址连接到规模集。

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

规模集引用命名 image_vmforimageA的自定义映像。 自定义映像 image_vmforimageA 是从主页显示 Image A 的虚拟机创建的。

因此,你将看到一个主页,显示Image A

继续下一部分时,将浏览器窗口保持打开状态。

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

在本部分中,playbook 代码将规模集的镜像从 image_vmforimageA 更改为 image_vmforimageB。 此外,由规模集部署的所有现有虚拟机都会被更新。

可通过两种方法获取示例剧本:

  • 下载 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部分,命令完成后会显示结果。

后续步骤