Cree un laboratorio en 36 segundos con Ansible

Recientemente, estaba leyendo el excelente artículo de Alex Callejas “Cree un laboratorio en cinco minutos con tres comandos simples”, y me inspiró para documentar cómo automatizar el proceso usando Ansible.

En su artículo, Alex implementa una máquina virtual (VM) en un KVM instancia que se ejecuta en un sistema Linux local mediante:

  1. Carga de una imagen en la nube
  2. Personalizar imagen
  3. Instalación e inicio de la máquina virtual
  4. Acceder a la máquina virtual

yo suelo Ansible para automatizar este proceso, haciéndolo aún más flexible y repetible. Mi técnica se basa en variables para reutilizar la misma automatización, lo que me permite crear diferentes tipos de máquinas virtuales con un solo comando.

Primero, defina un rol de Ansible para aprovisionar máquinas virtuales en KVM.

Índice

    Crear el rol

    Para que la automatización del aprovisionamiento de KVM sea más reutilizable, cree un rol de Ansible para ella. Si nunca antes ha creado un rol, consulte mi artículo "8 pasos para desarrollar un rol de Ansible en Linux" para comprender los conceptos básicos.

    Cree un directorio de proyecto para esta automatización y cambie a él:

    $ mkdir -p kvmlab/roles && cd kvmlab/roles

    Luego inicialice el rol usando el comando ansible-galaxy:

    $ ansible-galaxy role init kvm_provision
    
    - Role kvm_provision was created successfully

    Cambie al directorio de roles recién creado y verifique que tenga la estructura básica de roles lista para personalizarse:

    $ cd kvm_provision
    $ ls
    defaults files handlers meta README.md tasks templates tests vars

    En aras de la brevedad en este artículo, no documentaré el rol, pero es mejor documentar los roles agregando información al archivo README y detalles y dependencias a meta/main.yaml.

    Este rol no utiliza subdirectorios files, handlers, y vars, para que puedas eliminarlos:

    $ rm -r files handlers vars

    Ahora que tiene lista su estructura básica de roles, comience a usarla configurando algunas variables predeterminadas.

    Definir variables predeterminadas

    Un rol hace que su automatización sea reutilizable al permitir que los usuarios especifiquen variables que cambian el comportamiento o el resultado del rol. Se recomienda establecer un valor predeterminado para todas las variables que expone al usuario final para asegurarse de que el rol no falle al ejecutarse si los usuarios no especifican algunas de estas variables.

    [ For more Ansible tips, download Jeff Geerling's eBook Ansible for DevOps. ]

    Para este rol, especifique las variables que permiten a un usuario cambiar el tipo de máquina virtual aprovisionada, incluidos su nombre, la cantidad de procesadores, la cantidad de RAM y la imagen de la nube que se usará.

    Agregue estas variables al archivo defaults/main.yml, así:

    ---
    # defaults file for kvm_provision
    base_image_name: Fedora-Cloud-Base-34-1.2.x86_64.qcow2
    base_image_url: https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/{{ base_image_name }}
    base_image_sha: b9b621b26725ba95442d9a56cbaa054784e0779a9522ec6eafff07c6e6f717ea
    libvirt_pool_dir: "/var/lib/libvirt/images"
    vm_name: f34-dev
    vm_vcpus: 2
    vm_ram_mb: 2048
    vm_net: default
    vm_root_pass: test123
    cleanup_tmp: no
    ssh_key: /root/.ssh/id_rsa.pub

    Tenga en cuenta que estos son solo valores predeterminados. El usuario puede anularlos especificando estos valores al llamar a este rol más tarde desde un libro de jugadas.

    A continuación, defina un archivo de plantilla de máquina virtual.

    Definir una plantilla de máquina virtual

    Para aprovisionar una máquina virtual KVM mediante Ansible, utilice el comunidad.libvirt.virt módulo. Este módulo requiere una definición de VM en formato XML de acuerdo con la sintaxis de libvirt. La forma más conveniente de obtener este archivo es volcar una definición de máquina virtual existente usando el comando virsh dumpxml. Para obtener más detalles, consulte mi artículo "8 subcomandos de Linux virsh para administrar máquinas virtuales desde la línea de comandos".

    Después de obtener una definición base de máquina virtual XML, debe convertirla en una plantilla que permita la automatización para aprovisionar diferentes máquinas virtuales en función de las variables recibidas por el usuario.

    [ For more details on creating template files, check How to create dynamic configuration files using Ansible templates. ]

    Para este ejemplo, defina un archivo de plantilla de máquina virtual vm-template.xml.j2 en el templates subdirectorio de funciones:

    <domain type="kvm">
      <name>{{ vm_name }}</name>
      <memory unit="MiB">{{ vm_ram_mb }}</memory>
      <vcpu placement="static">{{ vm_vcpus }}</vcpu>
      <os>
        <type arch="x86_64" machine="pc-q35-5.2">hvm</type>
        <boot dev='hd'/>
      </os>
      <cpu mode="host-model" check='none'/>
      <devices>
        <emulator>/usr/bin/qemu-system-x86_64</emulator>
        <disk type="file" device="disk">
          <driver name="qemu" type="qcow2"/>
          <source file="{{ libvirt_pool_dir }}/{{ vm_name }}.qcow2"/>
          <target dev='vda' bus="virtio"/>
          <address type="pci" domain='0x0000' bus="0x05" slot="0x00" function='0x0'/>
        </disk>
        <interface type="network">
          <source network='{{ vm_net }}'/>
          <model type="virtio"/>
          <address type="pci" domain='0x0000' bus="0x01" slot="0x00" function='0x0'/>
        </interface>
        <channel type="unix">
          <target type="virtio" name="org.qemu.guest_agent.0"/>
          <address type="virtio-serial" controller="0" bus="0" port="1"/>
        </channel>
        <channel type="spicevmc">
          <target type="virtio" name="com.redhat.spice.0"/>
          <address type="virtio-serial" controller="0" bus="0" port="2"/>
        </channel>
        <input type="tablet" bus="usb">
          <address type="usb" bus="0" port="1"/>
        </input>
        <input type="mouse" bus="ps2"/>
        <input type="keyboard" bus="ps2"/>
        <graphics type="spice" autoport="yes">
          <listen type="address"/>
          <image compression='off'/>
        </graphics>
        <video>
          <model type="qxl" ram='65536' vram='65536' vgamem='16384' heads="1" primary='yes'/>
          <address type="pci" domain='0x0000' bus="0x00" slot="0x01" function='0x0'/>
        </video>
        <memballoon model="virtio">
          <address type="pci" domain='0x0000' bus="0x06" slot="0x00" function='0x0'/>
        </memballoon>
        <rng model="virtio">
          <backend model="random">/dev/urandom</backend>
          <address type="pci" domain='0x0000' bus="0x07" slot="0x00" function='0x0'/>
        </rng>
      </devices>
    </domain>

    Tenga en cuenta que este código XML utiliza algunas de las variables que definió previamente en su defaults/main en lugar de los valores donde esperaría personalizar la plantilla. Estas variables se interpolan durante la representación posterior de este modelo. Por ejemplo, este archivo define el nombre de la VM usando la variable vm_name:

      <name>{{ vm_name }}</name>

    Ahora está listo para definir la acción realizada por el rol.

    Definir tareas

    Defina las tareas necesarias para implementar la máquina virtual según las variables predeterminadas y el archivo de plantilla que creó anteriormente. Editar archivo tasks/main.yml y defina la primera tarea para asegurar las dependencias requeridas del paquete guestfs-tools y python3-libvirt están instalados. Este rol requiere que estos paquetes se conecten a libvirt y personalizar la imagen virtual en un paso posterior.

    ---
    # tasks file for kvm_provision
    - name: Ensure requirements in place
      package:
        name:
          - guestfs-tools
          - python3-libvirt
        state: present
      become: yes

    Notar: Estos nombres de paquetes funcionan en Fedora Linux. Si está usando RHEL 8 o CentOS, use libguestfs-tools en lugar de guestfs-tools. Para otras distribuciones, ajuste en consecuencia.

    Luego agregue una tarea para obtener la lista de máquinas virtuales existentes. No desea que su automatización sobrescriba accidentalmente una VM existente, así que use esta información para omitir ciertas tareas en caso de que la VM ya esté aprovisionada.

    - name: Get VMs list
      community.libvirt.virt:
        command: list_vms
      register: existing_vms
      changed_when: no

    Esta tarea utiliza el virt módulo de colección community.libvirt, que interactúa con una instancia en ejecución de KVM con libvirt. Obtiene la lista de máquinas virtuales especificando el parámetro command: list_vms y guardar los resultados en una variable existing_vms.

    Tenga en cuenta que especifica el parámetro changed_when: no para esta tarea para asegurarse de que no esté marcada como modificada en los resultados del cuaderno de estrategias. Esta tarea no realiza ningún cambio en la máquina; solo verifica las máquinas virtuales existentes. Esta es una buena práctica cuando se desarrolla la automatización de Ansible para evitar informes de cambios falsos.

    A continuación, defina un bloque de tareas que se ejecuten solo cuando el nombre de la máquina virtual proporcionado por el usuario no exista:

    - name: Create VM if not exists
      block:

    Agregue la primera tarea de este bloque usando el módulo get_url para descargar la imagen base de la nube al /tmp directorio telefónico:

      - name: Download base image
        get_url:
          url: "{{ base_image_url }}"
          dest: "/tmp/{{ base_image_name }}"
          checksum: "sha256:{{ base_image_sha }}"

    Debido a que esta misma imagen base puede aprovisionar más máquinas virtuales, no desea modificar este archivo directamente. Cópielo en la ubicación final en libvirtpara personalizarlo y usarlo más tarde como un disco de máquina virtual:

      - name: Copy base image to libvirt directory
        copy:
          dest: "{{ libvirt_pool_dir }}/{{ vm_name }}.qcow2"
          src: "/tmp/{{ base_image_name }}"
          force: no
          remote_src: yes 
          mode: 0660
        register: copy_results

    Ahora usa el command módulo para ejecutar el comando virt-customize para personalizar la imagen de la nube, como hace Alex en su artículo. Esta variante utiliza variables para que el comando sea más flexible y reutilizable:

      - name: Configure the image
        command: |
          virt-customize -a {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 
          --hostname {{ vm_name }} 
          --root-password password:{{ vm_root_pass }} 
          --ssh-inject 'root:file:{{ ssh_key }}' 
          --uninstall cloud-init --selinux-relabel
        when: copy_results is changed

    Tenga en cuenta que pasa la opción adicional --selinux-relabel. Esta opción solo es necesaria para las imágenes basadas en Red Hat u otras imágenes que tengan habilitado SELinux. Debe eliminar esta opción si está usando una imagen que no usa SELinux. También puede usar una variable aquí para hacerlo más flexible, pero eso va más allá del alcance de este artículo.

    Luego defina la VM usando el módulo community.libvirt.virt, instanciando las plantillas de VM que creó anteriormente:

      - name: Define vm
        community.libvirt.virt:
          command: define
          xml: "{{ lookup('template', 'vm-template.xml.j2') }}"

    Finalmente, cierre este bloque agregando el when condición que especifica que el bloque solo debe ejecutarse si el nombre de la VM no está en la lista de VM:

      when: "vm_name not in existing_vms.list_vms"

    Para finalizar el rol, agregue una tarea para asegurarse de que la nueva máquina virtual se inicie y otra tarea para limpiar la imagen base de la nube. /tmp si el usuario especifica una variable cleanup_tmp: yes:

    - name: Ensure VM is started
      community.libvirt.virt:
        name: "{{ vm_name }}"
        state: running
      register: vm_start_results
      until: "vm_start_results is success"
      retries: 15
      delay: 2
    
    - name: Ensure temporary file is deleted
      file:
        path: "/tmp/{{ base_image_name }}"
        state: absent
      when: cleanup_tmp | bool

    Guarde y cierre este archivo. Tu papel ha terminado. Pruébelo agregándolo a un libro de jugadas.

    Usar el rol en un libro de jugadas

    Ahora que el rol está completo, finalmente puede usarlo.

    Vuelva al directorio del proyecto inicial:

    $ cd ../..
    $ ls
    roles

    Crear un nuevo archivo de libro de jugadas kvm_provision.yaml quien usa el kvm_provision papel, así:

    - name: Deploys VM based on cloud image
      hosts: localhost
      gather_facts: yes
      become: yes
      vars:
        pool_dir: "/var/lib/libvirt/images"
        vm: f34-lab01
        vcpus: 2
        ram_mb: 2048
        cleanup: no
        net: default
        ssh_pub_key: "/home/rgerardi/.ssh/id_rsa.pub"
    
      tasks:
        - name: KVM Provision role
          include_role:
            name: kvm_provision
          vars:
            libvirt_pool_dir: "{{ pool_dir }}"
            vm_name: "{{ vm }}"
            vm_vcpus: "{{ vcpus }}"
            vm_ram_mb: "{{ ram_mb }}"
            vm_net: "{{ net }}"
            cleanup_tmp: "{{ cleanup }}"
            ssh_key: "{{ ssh_pub_key }}"

    Notar: este libro de jugadas está ejecutando el comando como raíz debido al parámetro become: yes, que debería funcionar para la mayoría de los sistemas configurados con el valor predeterminado libvirt ajustes. Si su usuario habitual está autorizado para ejecutar máquinas virtuales mediante libvirt, puedes cambiarlo a become: no para ejecutar como un usuario regular. En este caso, actualice el pool_dir variable para definir la correcta libvirt directorio del grupo de almacenamiento para su usuario habitual.

    También asegúrese de configurar la variable ssh_pub_key a su clave pública SSH. Si no tiene una clave SSH, consulte "SSH sin contraseña mediante pares de claves públicas y privadas" para obtener instrucciones sobre cómo crear una.

    Dado que el rol usa módulos de la colección community.libvirt, instale la colección en su máquina local para tener acceso a los módulos:

    $ ansible-galaxy collection install community.libvirt
    Process install dependency map
    Starting collection install process
    Installing 'community.libvirt:1.0.2' to '/home/rgerardi/.ansible/collections/ansible_collections/community/libvirt'

    Ahora ejecute el libro de jugadas para aprovisionar la máquina virtual de acuerdo con los parámetros especificados. Opción de uso -K pregunta la sudo Se requiere contraseña para instalar los paquetes necesarios:

    $ ansible-playbook -K kvm_provision.yaml
    BECOME password: 
    [WARNIN]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
    
    PLAY [Deploys VM based on cloud image] ***************************************
    
    TASK [Gathering Facts] *******************************************************
    ok: [localhost]
    
    TASK [KVM Provision role] ****************************************************
    
    TASK [kvm_provision : Ensure requirements in place] **************************
    ok: [localhost]
    
    TASK [kvm_provision : Get VMs list] ******************************************
    ok: [localhost]
    
    TASK [kvm_provision : Download base image] ***********************************
    changed: [localhost]
    
    TASK [kvm_provision : Copy base image to libvirt directory] ******************
    changed: [localhost]
    
    TASK [kvm_provision : Configure the image] ***********************************
    changed: [localhost]
    
    TASK [kvm_provision : Define vm] *********************************************
    changed: [localhost]
    
    TASK [kvm_provision : Ensure VM is started] **********************************
    changed: [localhost]
    
    TASK [kvm_provision : Ensure temporary file is deleted] **********************
    skipping: [localhost]
    
    PLAY RECAP *******************************************************************
    localhost                  : ok=8    changed=5    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   

    Cuando termine el libro de jugadas, utilice virsh list para ver la máquina virtual en ejecución:

    $ virsh list 
     Id Name State
    ---------------------------
     12 f34-lab01 running

    Usar virsh domifaddr f34-lab01 para obtener la dirección IP para conectarse a la máquina virtual:

    $ virsh domifaddr f34-lab01
     Name MAC address Protocol Address
    -------------------------------------------------------------------------------
     vnet11 52:54:00:13:23:b9 ipv4 192.168.122.6/24

    Conéctese a la VM usando su dirección IP y clave SSH:

    $ ssh -i ~/.ssh/id_rsa [email protected]
    [[email protected] ~]# 

    También puede aprovisionar máquinas virtuales adicionales proporcionando parámetros directamente en la línea de comandos. Por ejemplo, para aprovisionar una segunda máquina virtual denominada f34-lab02, usar:

    $ ansible-playbook -K kvm_provision.yaml -e vm=f34-lab02

    Puede modificar cualquiera de las variables que definió en el vars sección del libro de jugadas En aras de la brevedad, no he agregado todas las variables de roles, pero puede hacerlo para modificar, por ejemplo, la imagen de nube base utilizada para implementar la máquina virtual.

    Ahorro de tiempo y rendimiento

    En su artículo, Alex llenó un laboratorio en menos de cinco minutos. Solo por diversión, veamos qué puede hacer con Ansible. Primero, elimine la imagen almacenada en caché para calcular el tiempo total, incluida la descarga de la imagen:

    $ rm /tmp/Fedora-Cloud-Base-34-1.2.x86_64.qcow2

    Ahora ejecute el libro de jugadas de nuevo con el time Comando para evaluar cuánto tiempo lleva:

    $ time ansible-playbook -K kvm_provision.yaml -e vm=f34-lab03
    
    <TRUNCATED OUTPUT>
    
    ansible-playbook -K kvm_provision.yaml -e vm=f34-lab03 9.18s user 2.35s system 31% cpu 36.203 total

    Se necesitaron 36 segundos para crear la máquina virtual de laboratorio, incluida la carga de la imagen en la nube. Si vuelve a ejecutar el libro de jugadas para aprovisionar otra máquina virtual con la imagen almacenada en caché, es aún más rápido:

    $ time ansible-playbook -K kvm_provision.yaml -e vm=f34-lab04
    
    <TRUNCATED OUTPUT>
    
    ansible-playbook -K kvm_provision.yaml -e vm=f34-lab04 5.84s user 1.08s system 29% cpu 23.792 total

    Aprovisionar una VM en 24 segundos no suena nada mal.

    ¿Y después?

    Con la automatización de Ansible, puede aprovisionar rápidamente una máquina virtual de laboratorio para realizar sus pruebas sin afectar su máquina. Debido a la reutilización y la velocidad de la automatización, puede aprovisionar y desaprovisionar máquinas virtuales según sea necesario. Este artículo no explicó cómo desaprovisionar máquinas virtuales, pero puede usar los mismos módulos para hacerlo.

    En un artículo futuro, exploraré cómo usar la automatización de Ansible para personalizar aún más la máquina virtual aprovisionada.

    Artículos de interés

    Subir