De qué manera hacer una instancia duplicada EC2 con Ansible[

por calpee

Muchas empresas como la mía utilizan en gran medida la infraestructura de AWS como servicio (IaaS). A veces queremos realizar una operación potencialmente peligrosa en una instancia EC2. Mientras no trabajemos con una infraestructura inmutable, es imperativo estar preparados para una reversión instantánea.[

Una de las soluciones es utilizar un script que realice la duplicación de instancias, pero en entornos modernos, donde la unificación es una esencia, sería más prudente utilizar software conocido más común en lugar de crear un script personalizado.[

¡Aquí viene el Ansible![

Ansible es un software de automatización simple. Maneja la gestión de la configuración, la implementación de aplicaciones, el aprovisionamiento en la nube, la ejecución de tareas ad-hoc, la automatización de la red y la orquestación de múltiples nodos. Se comercializa como una herramienta para realizar cambios complejos, como parches continuos sin tiempo de inactividad, por lo que lo hemos utilizado para esta sencilla tarea de creación de instantáneas.[

Requisitos[

Para este ejemplo, solo necesitaremos un Ansible, en mi caso fue la versión 2.9; en las versiones posteriores hay un cambio importante con la introducción de colecciones, así que sigamos con este por simplicidad.[

Debido a que trabajamos con AWS, necesitamos un conjunto mínimo de permisos, que incluyen permisos para crear:[

  • Instantáneas de AWS[
  • Registrar imágenes (AMI)[
  • Iniciar y detener EC2[

Preparación ambiental[

Desde que me veo obligado a trabajar en Windows, he utilizado instancias de Vagrant. A continuación, encontrará un contenido de Vagrantfile.[

Estamos lanzando una máquina virtual, con Centos 7 y Ansible instalados.[

Por razones de seguridad, Ansible, por defecto, ha deshabilitado la lectura de la configuración desde la ubicación montada, por lo tanto, tenemos que indicar implcity la ruta /vagrant/ansible.cfg.[

Listado 1. Vagrantfile para nuestra investigación[

Vagrant.configure("2") do |config|
  config.vm.box = "geerlingguy/centos7"
  config.vm.hostname = "awx"
  config.vm.provider "virtualbox" do |vb|
    vb.name = "AWX"
    vb.memory = "2048"
    vb.cpus = 3
  end
  config.vm.provision "shell", inline: "yum install -y git python3-pip"
  config.vm.provision "shell", inline: "pip3 install ansible==2.9.10"
  config.vm.provision "shell", inline: "echo 'export ANSIBLE_CONFIG=/vagrant/ansible.cfg' >> /home/vagrant/.bashrc"
end

Primeras tareas[

En las primeras líneas del Ansible especificamos algunos valores meta. La mayoría de ellos, como el nombre, los hosts y las tareas, son obligatorios. Otros proporcionan funciones auxiliares.[

Listado 2. primeras líneas del libro de jugadas duplicate_ec2.yml[

---
- name: yolo
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  vars:
    instance_id: i-deadbeef007

Tareas:[

    - name: Getting minimal set of facts for datetime
      setup:
        gather_subset: 'min'

    - set_fact:
        current_datetime: " ansible_date_time.iso8601 "

    - name: Install required pip packages
      become: yes
      pip:
        name:
          - boto3
          - boto

[

Desde la parte superior asignamos un nombre para determinar de qué trata este libro de jugadas.[

Dado que esto solo se conectará a AWS, tenemos que limitar la ejecución para que se ejecute en localhost y, para evitar intentos de SSH, agregamos el tipo de conexión local, lo que en realidad significa sin conexión, ejecución directa en una máquina.[

A continuación, procedemos a desactivar la recopilación de datos para acelerar nuestra ejecución. Become keyword determina si Ansible debe utilizar una cuenta privilegiada (por ejemplo, sudo). Como no lo necesitamos, es una buena costumbre desactivar los privilegios crecientes.[

La sección Vars define hechos utilizables en todo el libro de jugadas, actualmente solo tenemos uno, un ID de instancia.[

Finalmente comenzamos a trabajar realmente en la sección de tareas.[

En primer lugar, en el libro de jugadas necesitaremos el valor de fecha y hora, por lo que es necesario recopilar un conjunto mínimo de datos. Los posibles valores son:[

– todos – prácticamente todos los hechos, se establece por defecto si no se especifica recopilar_factos en la meta sección, – mínimo – información muy reducida, que no requiere profundizar en la configuración del sistema – hardware, – red, – virtual, – ohai y facter – dos hechos comunes proveedores, lea más sobre ohai en la documentación de Chef y Puppet para la especificación de facter.[

Ahora podemos obtener la fecha y hora, y dado que se usará varias veces en las tareas, la registramos como un hecho en nuestra segunda tarea.[

Finalmente, los módulos para el control de AWS requieren que los módulos de Python boto y boto3 funcionen, por lo que podemos asegurarnos de que están presentes ejecutando el módulo pip.[

Tenga en cuenta que podemos sobrescribir valores globales, en este ejemplo: escalamos nuestros privilegios estableciendo convertirse en: verdadero.[

Autenticación[

La autenticación de AWS puede tener diferentes formas, una simple: solo inicio de sesión y contraseña o más avanzada que requiere asumir roles.[

Además, podríamos vernos obligados a utilizar la autenticación multifactor, lo que complica aún más la autenticación.[

Para superar esto, debemos proporcionar: inicio de sesión de usuario, contraseña de usuario, serie del token MFA y código MFA actual.[

Dado que los secretos cifrados en la bóveda son bastante largos, para guardarlos, se han truncado en todos los listados.[

Listado 3. duplicate_ec2.yml mejorado con elementos de autenticación[

(...)
  vars:
    instance_id: i-deadbeef007
    aws_credentials:
      aws_region: eu-west-2
      aws_access_key: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          34613664316337623136383935636262353361643736666432666331623563636333363431626134
          6533636363383231...
      aws_secret_key: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          39386565376137663934333734316236346232643838623530386538303561393730373662626238
          6337636363353938663664...
      mfa_serial_number: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66306332383534343338373532633930373536663638303439633837613832643966303236396562
          393861366333333562336231666...

Tareas:[

(...)
    - pause:
        prompt: "Please enter Your MFA code: "
        echo: yes
      register: mfa_code

    - sts_assume_role:
        role_arn: "arn:aws:iam::807777736438:role/User"
        aws_region: " aws_credentials.aws_region "
        aws_access_key: " aws_credentials.aws_access_key "
        aws_secret_key: " aws_credentials.aws_secret_key "
        mfa_serial_number: " aws_credentials.mfa_serial_number "
        mfa_token: " mfa_code.user_input "
        role_session_name: "Snapshotting- instance_id "
      register: assumed_role

    - set_fact:
        aws_secrets: &aws_secrets
          aws_access_key: " assumed_role.sts_creds.access_key "
          aws_secret_key: " assumed_role.sts_creds.secret_key "
          security_token: " assumed_role.sts_creds.session_token "
          region: " aws_credentials.aws_region "

Lo primero que nos llama la atención son los grandes bloques de dígitos. Estas son cadenas encriptadas de ansible-vault. Los creamos llamando a `ansible-vault encrypt_string` e insertando los datos requeridos.[

Tenga en cuenta que ctrl + d no debe ir precedido de la tecla enter, de lo contrario, el carácter de nueva línea se incluirá en el valor del secreto.[

Listado 4. Ejemplo de uso de ansible-vault[

[vagrant@awx ~]$ ansible-vault encrypt_string
New Vault password:
Confirm New Vault password:
Reading plaintext input from stdin. (ctrl-d to end input)
This is the secret content!vault |
          $ANSIBLE_VAULT;1.1;AES256
          34363966326337613933623331306331613939303661303530613466613036346336613032333637
          3632313064336133383036396266633761643664656664620a626165343439393832643236613438
          32623339396130323531643862366532623434343931613165633931663739353065396234313034
          6165373262393764610a373763623865356131383133316638333635616665313463343563646564
          35353161613039303437383135383165393661343132623133663231653035376338
Encryption successful

Ahora necesitamos conseguir del usuario el elemento final de autorización: el código mfa.[

Dado que su vida útil es de solo 60 segundos, se ha proporcionado lo más tarde posible, por lo que lo solicitamos, utilizando el módulo `pause` DESPUÉS de la recopilación de datos y la instalación de los módulos, justo antes de que sea necesario. Registramos el valor de este módulo en la variable mfa_code para su posterior reutilización.[

Con el módulo `sts_assume_role` finalmente podemos asumir nuestro rol apropiado. Requiere un montón de valores, donde algunos de ellos son estáticos, algunos se originan en la sección de vars anterior y el último, “mfa_code.user_input”, del paso anterior. Por tanto, el resultado de la tarea debe almacenarse en otra variable.[

El paso siguiente es por conveniencia, reunimos nuestras variables, que serán más necesarias, en un solo hecho.[

Además: aquí usamos una característica de yaml llamada referencia de bloque.[

Podemos nombrar un bloque, usando el carácter `&` con un nombre y luego usarlo donde sea necesario.[

Obtener detalles de la instancia[

Como queremos hacer un clon de la instancia, necesitamos recopilar cierta información.[

Con Ansible, solo está llamando al módulo ec2_instance_info (advertencia: este módulo estaba cambiando de nombre como 3 veces en los últimos 2 años).[

Obligatorio necesitamos: aws_access_key, aws_secret_key :, security_token y región.[

Afortunadamente, los tenemos todos bajo el nombre de bloque Yaml `aws_secrets`. Podemos acceder a él usando el carácter `*` antes del nombre del bloque. Para usarlo, escribimos el operador de inserción de bloque `<<` y nos referimos al bloque usando un asterisco. ¡Voila! Ya no es necesario volver a escribir toda la información línea por línea. Por supuesto, también debemos aclarar qué instancia queremos.[

Para esto utilizamos filtros de palabras clave y proporcionamos qué valores usamos para la búsqueda.[

Listado 5. Recopilación de datos de instancia[

- name: Get data about ec2 instance  instance_id 
  ec2_instance_info:
    <<: *aws_secrets
    filters:
      instance-id: " instance_id "
  register: instance_facts

- set_fact:
    instance_data: " instance_facts.instances[0] "

- debug:
    msg: " to_nice_json "

Una vez más por conveniencia asignamos datos interesantes bajo el nuevo hecho. Es más simple escribir `instance_data` en lugar de` instance_facts.instances[0]’.[

Finalmente imprimimos los detalles interesantes a la pantalla.[

Para una mejor legibilidad, recomiendo usar el filtro `to_nice_json` al imprimir JSON, y en ansible.cfg podemos definir un valor para stdout_callback como debug, que proporcionará una impresión bonita para los errores.[

Listado 6. contenido ansible.cfg[

[defaults]
stdout_callback = debug
#This will make our output look much better.

Creación de instantáneas y AMI[

Para hacer instantáneas tenemos que llamar al módulo `ec2_snapshot` en cada volumen adjunto a la instancia original.[

Hace algún tiempo, el equipo de Ansible introdujo la palabra clave `loop`, mientras que en los libros de jugadas más antiguos podemos encontrar` with_items`, `with_list`, etc. en realidad cualquier cosa con` with_ * `.[

Loop tiene una interfaz unificada y con la ayuda de filtros puede cumplir el rol de cualquier “with_ *”.[

El ciclo más básico toma una lista, por ejemplo, de cadenas o dictados, y asigna valores subsiguientes a la variable “item” en cada iteración.[

En esta tarea, también se destaca el uso de llamadas asincrónicas, mediante el uso de palabras clave async: especifica el tiempo de espera para cada ejecución paralela y encuesta: define el intervalo entre comprobaciones de finalización. Ambos se definen en segundos.[

Listado 7. Tareas para crear instantáneas[

- name: Create snapshot of volumes
  ec2_snapshot:
    instance_id: " instance_id "
    device_name: " item.device_name "
    <<: *aws_secrets
    snapshot_tags:
        Name: " instance_data.tags.Name - item.device_name "
        id_instance: " instance_id "
        volume: " item.device_name "
        date_created: " current_datetime "
  loop: " instance_data.block_device_mappings "
  register: snapshots_list
  async: 1200
  poll: 5

- debug:
    msg: " to_nice_json "

- name: Snapshot data modification
  set_fact:
    # the most ugly piece of code I ever wrote in Ansible
    snapshots_2_reuse: " default([]) +
      [  'device_name': item.item.device_name, 'snapshot': item.snapshot_id  ] "
  loop: " snapshots_list.results "

- debug:
    msg: " to_nice_json "

- name: Create AMI from snapshot
  ec2_ami:
    <<: *aws_secrets
    name: " default(instance_id, true) - current_datetime "
    root_device_name: " snapshots_2_reuse[0].device_name "
    device_mapping:
      - device_name: " snapshots_2_reuse[0].device_name "
        snapshot_id: " snapshots_2_reuse[0].snapshot "
        delete_on_termination: true
  register: created_ami

- debug:
    msg: " to_nice_json "

Más tarde, necesitamos modificar la salida sobre las instantáneas creadas, puesto que solo requerimos un subconjunto de datos en forma de diccionarios.[

Dividamos esta expresión en pedazos para una mejor comprensión:[

snapshots_2_reuse: " snapshots_2_reuse

Ahora, agregamos a la lista un mapa desarrollado dinámicamente, con los factores nombrados correctamente.[

Al final asignamos el objeto creado a la variable `snapshots_2_reuse` y pasamos al siguiente elemento de la lista original.[

Tenga presente que `item.item.device_name` es preciso debido a que iteramos con la variable` item` mas cada elemento tiene su propio subelemento llamado `item` - vea el resultado de la labor de depuración anterior.[

Gracias a que es imposible crear una instancia de manera directa a partir de una instantánea, necesitamos tener una base de AMI en instantáneas del gadget raíz.[

Tenga presente la utilización extensivo de filtros mientras que consigue valor para el nombre de AMI.[

Procuramos emplear el valor de la etiqueta `Name`, si no está definido o está vacío (segundo factor` true` en el filtro) lo reemplazamos con `instance_id`, que siempre tiene que estar presente.[

Asimismo debemos confirmarnos de que no haya espacios, que son recurrentes en `Nombre`.[

Además de esto, en datetime requerimos liberarnos de los dos puntos, puesto que no se tienen la posibilidad de poner también en el nombre de AMI.[

Creando una instancia con respaldo[

Ahora, en nuestro viaje a través del libro de jugadas, estamos con una construcción de bloqueo / rescate.[

Es una versión de Ansible de captura de excepciones.[

Si algún paso falla en la sección `denegar`, va a llamar las tareas de` rescate` ahora.[

En este ejemplo especial en bloque poseemos 2 tareas: parar la instancia original y publicar la novedosa.[

Si Ansible no consigue alguno de ellos, rápidamente procederá a imprimir un mensaje de observación y también empezará la instancia original; si la labor de detención falla, Ansible notificará que la instancia se está ejecutando y marcará esta labor de salve como OK.[

Como es natural, los bloques se pueden anidar, por ejemplo, la sección de salve asimismo tiene la posibilidad de tener otra construcción de bloque / salve.[

Cabe indicar que el juego continúa si una sección de salve se completa de manera exitosa, dado que 'borra' el estado de error (mas no el informe), esto significa que no activará configuraciones max_fail_percentage ni any_errors_fatal, pero aparecerá en las estadísticas del libro de jugadas.[

Listado 8. Creación de instancias[

- name: Stop original instance and start new one
  block:
    - name: Stop original running instance
      ec2:
        <<: *aws_secrets
        state: stopped
        instance_id: " instance_id "

    - name: Launch new instance
      ec2:
        <<: *aws_secrets
        key_name: " instance_data.key_name "
        group_id: " list "
        instance_type: " instance_data.instance_type "
        image: " created_ami.image_id "
        wait: yes
        wait_timeout: 600
        instance_tags: " instance_data.tags "
        volumes: " snapshots_2_reuse[1:] "
        vpc_subnet_id: " instance_data.subnet_id "

  rescue:
    - debug:
        msg: "We've caught an error during new instance creation. Reverting by restoring previous instance"

    - name: Starting back the original running instance
      ec2:
        <<: *aws_secrets
        state: running
        instance_id: " instance_id "

Resumen[

En esta demostración, enseñamos de qué manera modificar un libro de jugadas de Ansible para iniciar sesión en AWS, hacer una instantánea de una instancia de EC2 determinada y crear una nueva basada en la original.[

También presentamos ciertos avisos y trucos para hacer mejor los libros de jugadas progresando su rendimiento, seguridad y legibilidad.[

Este documento revela que el Ansible, gracias a su simplicidad, puede eclipsar al Chef o bien Puppet, lo que para esta fácil tarea podría ser una exageración. En otras expresiones: Ansible sencillamente lo mantiene fácil.[

You may also like