(Integración de HashiCorp Packer con AWS EBS)

packerami002

Pre-requisito

  • Conocimiento básico en manejo del SO GNU/Linux
  • Tener una cuenta en AWS y conocimiento básico del Billing de AWS.
  • Conocimiento de los servicios de AWS: IAM y EC2 (Instancias, EBS y AMI’s).
  • Conocimiento básico en manejo y sintaxis de archivos JSON ( Javascript Object Notation)

Objetivo

Crear una AMI (Amazon Machine Image) propia, totalmente personalizada, con paquetes y configuración deseada, basandonos en una AMI pre-existente, con ayuda de la herramienta Packer de Hashicorp.

Introducción

En AWS más precisamente dentro del servicio EC2, cuando creamos una AMI tambien estamos creando y almacenando un volumen, esto también es conocido como el Root Device de dicha AMI, el storage de dicho volumen se puede hacer usando Amazon EBS-Backed AMI esto es un volumen EBS a partir de un snapshot, la otra es usando Amazon Instance Store-Backed AMI que es básicamente un volumen creado a partir de un template almacenado en un bucket S3, a continuación se puede ver en el siguiente cuadro la diferencia entre ambos métodos de storage de root device (fuente: AWS oficial):

packerami007

Nosotros vamos a crear nuestra AMI usando EBS para el almacenamiento de nuestro Root Device, de todas maneras, independientemente del método de almacenamiento, si quisiéramos armar una AMI propia con las herramientas nativas de AWS, podríamos hacerlo a través de su consola web, o mejor aún, utilizando AWS CLI create-image, pero antes que nada, deberíamos comenzar con una AMI-base, crear una instancia con ella, lanzarla y personalizarla, todo esto antes de realizar el proceso de creación de la AMI; es acá donde me presentaron una herramienta llamada Packer la cual nos simplifica bastante todo el proceso.

Packer es una tool open source creada por la empresa HashiCorp (empresa creadora del popular software de IaaC Terraform), Packer básicamente lo que hace es construir imágenes de cualquier tipo de máquina podemos usar Packer para construir nuestras AMI’s en AWS, pero también podemos reproducir la misma imagen en un file VMDK para VMware, o Azure, LXC, OpenStack, Gcloud etc, Packer es totalmente multiprovider, para más información pueden entrar al sitio oficial de Packer, una aclaración muy importante (que la hace Packer en su sitio oficial también) es que Packer no pretende reemplazar a las tools de configuration management como ansible, chef o puppet, Packer solamente se encarga de empaquetar una imagen de máquina, y de hecho Packer puede implementar chef, puppet o ansible para instalar paquetes dentro de dicha imagen

Instalemos Packer

Vamos a instalar Packer en nuestro Debian, pero sentite libre de instalar en el SO que mas te guste, en la pagina de descargas de Packer podes elejir la arquitectura y el SO que quieras.

Es un solo binario, por lo cual vamos a descargarlo y moverlo para comenzar a utilizarlo de inmediato:

$ curl -X GET https://releases.hashicorp.com/packer/1.4.1/packer_1.4.1_linux_amd64.zip --output packer_1.4.1_linux_amd64.zip
$ unzip packer_1.4.1_linux_amd64.zip
$ sudo mv packer /usr/local/bin/
$ packer --help

packerami008

Configuramos el Packer Template

Packer maneja una serie de objetos para trabajar, en primer lugar están los builders que son los responsables de la creación de las imágenes propiamente dichas, Packer posee muchos builders para distintos proveedores, en nuestro ejemplo vamos a usar el builder amazon-ebs y además vamos a utilizar nuestro builder con un archivo de variables para modularizar un poco el esquema; luego están los provisioners que Packer utiliza para instalar y configurar cosas dentro de la imagen, en los provisioners se pueden especificar comandos o hasta scripts completos, nosotros vamos a referenciar un script completo en bash por ejemplo; por último (en este ejemplo particular) están los post-processors, los hay de muchos tipos, pero en el ejemplo vamos a usar uno de tipo manifest el cual nos va a devolver un json con una lista de todos los artefactos producidos por Packer durante su ejecución.

Vamos a ver un poco mejor de qué se trata todo esto con un ejemplo, armemos la siguiente estructura con el siguiente contenido:

packerami009

baseAMI.json

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": "",
    "vpc_region": "",
    "vpc_id": "",
    "vpc_public_sn_id": "",
    "source_ami": "",
    "vpc_public_sg_id": "",
    "instance_type": "",
    "ssh_username": ""
  },
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}" ,
      "secret_key": "{{user `aws_secret_key`}}",
      "region": "{{user `vpc_region`}}",
      "vpc_id": "{{user `vpc_id`}}",
      "subnet_id": "{{user `vpc_public_sn_id`}}",
      "associate_public_ip_address": true,
      "security_group_id": "{{user `vpc_public_sg_id`}}",
      "source_ami": "{{user `source_ami`}}",
      "instance_type": "{{user `instance_type`}}",
      "ssh_username": "{{user `ssh_username`}}",
      "ami_name": "ami-custom-con-packer",
      "ami_groups": "all",
      "launch_block_device_mappings": [
    {
      "device_name": "/dev/sda1",
      "volume_type": "gp2",
      "volume_size": "10",
      "delete_on_termination": true
    }
      ]
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "script": "baseInit.sh"
    }
  ],
  "post-processors": [
    {
      "type": "manifest",
      "output": "manifest.json",
      "strip_path": true
    }
  ]
}

NOTA: como pueden ver en nuestro template, tenemos el builder, provisioners de tipo shellscript y el post-processors de tipo manifest.

vars.json (reemplazar por los valores de tu cuenta aws)

{
  "aws_access_key": "${REEMPLAZAR_POR_AWS_ACCESS_KEY_ID}",
  "aws_secret_key": "${REEMPLAZAR_POR_AWS_SECRET_ACCESS_KEY}",
  "vpc_region": "${REEMPLAZAR_POR_vpc_region}",
  "vpc_id": "${REEMPLAZAR_POR_vpc_id}",
  "vpc_public_sn_id": "${REEMPLAZAR_POR_vpc_public_sn_id}",
  "vpc_public_sg_id": "${REEMPLAZAR_POR_vpc_public_sg_id}",
  "source_ami": "ami-024a64a6685d05041",
  "instance_type": "${REEMPLAZAR_POR_instance_type}",
  "ssh_username": "ubuntu"
}

NOTA: como AMI base voy a usar la ami-024a64a6685d05041 (es un Ubuntu 18), podes elegir la AMI que mas te guste, pero tene en cuenta que vas a tener que modificar el provisioner ya que el script está armado para correr en Ubuntu.

baseInit.sh

#!/bin/bash -e

main() {
  sudo apt-get update

  # Instalamos Docker
  sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
  curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
  sudo apt update
  sudo apt install -y docker-ce
  sudo usermod -aG docker $(whoami)

  # también kubectl...
  curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
  echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
  sudo apt update
  sudo apt -y install kubectl
  
  # y porque no k3s...
  curl -sfL https://get.k3s.io | sh -
  
  # podemos seguir instalando todos los paquetes que se nos ocurra, configuraciones etc,
  # todo para armar nuestra AMI de acuerdo a nuestras necesidades o la de nuestro cliente.


}

main

NOTA: básicamente vamos a construir una AMI basada en Ubuntu, con Docker y k3s incluido mediante nuestro provisioner de Packer.

Packer en Acción

Ya tenemos armado nuestro template, las variables seteadas, y nuestro provisioner, ahora vamos a dejar que Packer haga su magia con el siguiente comando:

$ packer build -var-file=vars.json baseAMI.json

Vamos a ver en la misma consola las acciones de Packer:

  • Busca la AMI base y la lanza como una instancia EC2 temporal llamada Packer Builder de hecho si navegamos hacia nuestra consola EC2 vamos a ver dicha instancia levantada.

  • Luego se conecta vía ssh a la máquina Packer Builder y ejecuta el provisioner (en el ejemplo es nuestro script bash).

packerami012

packerami011

  • Arma la AMI custom (nombrada en el builder como ami-custom-con-packer) con todas las modificaciones que metimos a través del script, y no solo eso, también registra la AMI por nosotros.

  • Por último hace un clean-up de la instancia Packer Builder junto con los volumes, keypairs etc, y levanta el post-processor de hecho podemos ver en nuestro directorio de trabajo que se creó el archivo manifest.json con el detalle de los artifacts creados.

packerami014

packerami016

Ahora podemos verificar nuestra AMI en la consola de EC2 y también podemos lanzar una instancia basada en ella y conectarnos, para verificar que realmente se construyó la imagen con las instalaciones que cargamos en nuestro script:

IMPORTANTE: no te olvides de hacer clean-up de todo cuando termines, y recordá siempre tener bien configurada tu Billing Alarm en CloudWatch, es la mejor manera de evitar “sorpresas” en dólares.

packerami013

packerami015

Como vemos en nuestra instancia EC2 basada en la AMI que acabamos de crear, Docker se instaló correctamente, al igual que kubectl, y nuestro cluster k3s se está terminando de levantar. Gracias a Packer pudimos crear una imagen Ubuntu con Docker, kubectl y k3s pre-instalados, en pocos segundos y sin configuraciones ni instalaciones manuales.

Ahora vos!! podes crear las AMI’s que quieras con Packer, la documentación oficial está muy completa y tiene ejemplos base de todos los providers compatibles con la tool (de hecho el archivo baseAMI es de un ejemplo oficial).

Fuentes:

Invitame un CaféInvitame un Café