Sommaire
Les bases de Packer
Packer est un logiciel open source fourni par Hashicorp. En une phrase, c’est un logiciel qui vous sert à faire des images de manière automatisée. Il introduit un modèle pour vos images vous permettant d’uniformiser vos déploiements. Il est multi-providers et se déploie très facilement sur AWS, GCP et Azure. Il est stable et simple d'utilisation, en une commande, vous pourrez avoir votre instance déployée, configurée et uniformisée. Tout ça, en un rien de temps !
HCL vs JSON
Les fichiers Packer sont reconnaissables par leur extension “.pkr.hcl”. Le langage utilisé pour rédiger ces fichiers est l'HashiCorp Configuration Language 2 (HCL2). Si vous faites du Terraform, vous ne serez pas perdu car il s’agit du même formalisme ! C’est l’un des avantages de l’uniformisation des logiciels HashiCorp.
Nous préférons le HCL2 au json du fait qu’il soit plus léger à lire et écrire. Depuis la version 1.5.0 il est possible de créer les fichiers Packer en HCL2. Auparavant l’ensemble des modèles Packer était en json. Si vous avez déjà mis en place des fichiers Packer en json, pas de panique ! HashiCorp a tout prévu : il existe une commande Packer permettant de mettre à jour vos fichiers json en HCL2.
packer hcl2_upgrade -with-annotations docker-ubuntu.pkr.hcl
Rien de plus simple !
Build d’une image
Pour la construction de vos images, il y a deux commandes principales.
Tout d’abord, cette commande :
packer validate docker-ubuntu.pkr.hcl
(packer validate) permet de valider le fichier Packer avant de build. C’est toujours utile ;)
Et, la commande :
packer build docker-ubuntu.pkr.hcl
(packer build). Comme son nom l’indique, elle sert à construire le modèle que vous allez envoyer. Vous aurez seulement besoin de ces commandes ! Pour le reste, je vous invite à regarder en faisant un :
packer --help
Usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
console creates a console for testing variable interpolation
fix fixes templates from old versions of packer
fmt Rewrites HCL2 config files to canonical format
hcl2_upgrade transform a JSON template into an HCL2 configuration
init Install missing plugins or upgrade plugins
inspect see components of a template
plugins Interact with Packer plugins and catalog
validate check that a template is valid
version Prints the Packer version
Les différents types de bloc
Ici, je vais référencer les blocs et leur utilité en HCL2.
Le bloc build
C’est le bloc qui indique quelles sont les actions à mener pour construire l’image machine finale. Dans ce bloc, vous avez la possibilité de renseigner des providers. Ils indiquent comment ajouter le contenu additionnel et vos configurations sur l’image machine finale. Vous avez la possibilité d’utiliser le provider shell pour lancer des commandes shell sur votre instance, le provider PowerShell sous Windows, le provider file pour envoyer des fichiers, des dossiers ou des zips sur l’instance. Ce sont les providers de base. Voici un exemple de bloc build utilisant un provider Shell :
build {
name = "learn-packer"
sources = [
"source.docker.ubuntu",
]
provisioner "shell" {
environnement_vars = [
"FOO=hello world",
]
inline = [
"echo Addind file to Docker container",
"echo \"FOO is $FOO\" > example.txt"
]
}
}
Point positif, la communauté, gentille comme tout, vous met à disposition tout plein d'autres providers dont UN qui nous intéresse tout particulièrement 😏 Le provider Ansible ! 🥳
Le bloc variable
variable "region" {
type = string
default = "${env("AWS_REGION")}"
}
Vous devez sûrement le reconnaître, il est semblable à celui sur Terraform. En effet, les configurations des blocs variables sont les mêmes.
Il vous faut mettre le type de variable et, éventuellement, une valeur par défaut.
Mais vous pouvez également indiquer que la variable est sensible, pour ne pas faire apparaître sa valeur lors de l'exécution de Packer comme ceci :
variable "foo" {
sensitive = true
default = {
key = "SECR3TP4SSW0RD"
}
}
Le bloc amazon-ami
Ce bloc vous permet de trouver votre AMI (Amazon Machine Image) de base sur Amazon AWS. Il ressemble à ça :
data "amazon-ami" "basic-example" {
filters = {
virtualization-type = "hvm"
name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"
root-device-type = "ebs"
}
owners = ["099720109477"]
most_recent = true
}
Ce bloc à pour but de chercher votre image de base que vous allez configurer par la suite. Vous allez trouver l’image grâce à des filtres. Vous pouvez également utiliser des RegExp pour pouvoir avoir accès à la dernière version de l’image.
Le bloc source amazon-ebs
Ce bloc va vous permettre de configurer l’environnement cible où sera construite l’instance. Vous allez définir tout le réseau ou déployer votre instance, le futur nom de l’ami, la configuration du stockage et le type d’instance.
source "amazon-ebs" "instance" {
ami_name = "${var.ami_name}"
ami_users = ["${var.user_id}"]
associate_public_ip_address = "true"
instance_type = "t3.medium"
launch_block_device_mappings {
delete_on_termination = true
device_name = "/dev/xvda"
encrypted = true
kms_key_id = "alias/${var.aws_target_account}"
volume_size = 100
}
max_retries = "2"
source_ami = "${data.amazon-ami.basic-example.id}"
ssh_username = "ec2-user"
}
Comment générer ses images
Nous cherchons à construire des images machine pour plusieurs besoins sur différents environnements. Nous gérons par exemple des instances de proxy ou des instances pour notre orchestrateur de containers. Pour ça nous avons des configurations différentes pour chaque type d'image, c’est pour cette raison que nous utilisons les modèles Packer. Finalement, tout dépend de votre infrastructure et de vos besoins !
Schématisation
Voici un grand schéma, complexe et complet, que nous allons expliquer dans la suite.
Pré-requis
Tout d’abord, nous devons créer le conteneur que nous allons utiliser dans GitLab CI.
Nous avons besoin de tous les outils nécessaires pour lancer nos images. Pour cela nous allons installer Ansible, Packer, l’AWS Cli et toutes les librairies nécessaires au bon fonctionnement de ces outils dont jinja2, netaddr, Boto3. Ce conteneur, nous allons l’appeler notre Workstation.
Il vous faut également créer un utilisateur AWS IAM. Voici les droits qui lui seront associés :
Les droits pour utiliser la clé KMS qui chiffrera le volume EBS créé par Packer
Les droits EC2 pour créer l’instance EC2 utilisée par Packer
Les droits EBS pour créer et gérer le volume utilisé par l’instance
Les droits IAM pour associer un rôle à cette instance
Il ne faudra pas oublier de renseigner cet utilisateur dans la CI (Sous forme de variable masquée par exemple).
Le CI / CD
Au niveau de notre CI, nous avons plusieurs points importants à prendre en compte. Nous devons faire une pipeline de CI/CD qui nous permet de déployer tout type d’image et dans tout type d’environnement. Pour cela, nous allons utiliser les Anchors et les Parallel sur Gitlab CI.
.build: &build-job
image: registry.gitlab.com:4567/elodie.billiot/packer-aws-weshare/workstation:xxx
stage: build
environment:
name: $ENVIRONEMENT
script:
- ansible-galaxy collection install -r ansible/requirements.yml
- ansible-galaxy install -r ansible/requirements.yml -p ./ansible/roles --force
- packer validate -var-file=packer/variables.pkr.hcl -var "ami_name=$ENVIRONEMENT-$AMI-$CI_COMMIT_SHORT_SHA-ami" -var "aws_target_account=$ENVIRONEMENT" -var "user_id=$ACCOUNT" packer/$AMI.pkr.hcl
- packer build -var-file=packer/variables.pkr.hcl -var "ami_name=$ENVIRONEMENT-$AMI-$CI_COMMIT_SHORT_SHA-ami" -var "aws_target_account=$ENVIRONEMENT" -var "user_id=$ACCOUNT" packer/$AMI.pkr.hcl
parallel:
matrix:
- AMI: ["master", "front", "back"]
Nous n'avons qu'un seul stage, le stage de build qui va construire notre AMI. Pour cet Anchors, qui est notre modèle de job pour tous les environnements et les types d’instance, nous avons besoin de télécharger les collections et rôles Ansible (2 premières lignes du script) et ensuite nous effectuons les commandes Packer de validation du modèle puis le build.
Dans les parallel nous avons comme paramètre la variable AMI. C’est sur cette valeur que nous allons pouvoir lancer plusieurs jobs en parallèle.
Voici les jobs spécifiques aux environnements, (ici nous prendrons l’exemple de l’environnement de sandbox et de dev).
build:sandbox:
<<: *build-job
variables:
ENVIRONEMENT: sandbox
ACCOUNT: $SANDBOX_ACCOUNT
AWS_REGION: eu-west-1
rules:
- if: $CI_COMMIT_TAG == "sandbox"
changes:
- packer/$AMI.pkr.hcl
- ansible/$AMI.yml
- ansible/requirement.yml
when: manual
build:dev:
<<: *build-job
variables:
ENVIRONEMENT: dev
ACCOUNT: $DEV_ACCOUNT
AWS_REGION: eu-west-1
rules:
- if: $CI_COMMIT_TAG == "develop"
changes:
- packer/$AMI.pkr.hcl
- ansible/$AMI.yml
- ansible/requirement.yml
- config
when: manual
Ces jobs se lancent seulement si certains fichiers ont été modifiés sur une branche particulière.
Après que le build soit passé, vous avez enfin une AMI !
Vous pouvez maintenant aller dans votre code Terraform et utiliser cette nouvelle AMI.
Voici un exemple avec la data aws_ami et le remplacement dans un launch template AWS :
data "aws_ami" "new-linux" {
most_recent = true
filter {
name = "name"
values = ["staging-new-*-ami"]
}
owners = ["123456789"]
}
resource "aws_launch_template" "new_launch_template" {
name_prefix = "${terraform.workspace}-new-lt"
image_id = data.aws_ami.new-linux.id
instance_initiated_shutdown_behavior = "stop"
instance_type = "t3.medium"
key_name = "test-ssh"
vpc_security_group_ids = [aws_vpc.id]
}
Et voilà ! Vous avez industrialisé la construction de vos images.
Pour finir, c’est un projet qui peut clairement vous aider pour industrialiser la création de vos images machines. C’est un super projet d’automatisation qui peut vous simplifier la vie. En tant que DevOps, ce sujet peut devenir rapidement une priorité ! Si vous voulez mener ce projet, les sources sont disponibles ici https://gitlab.com/elodie.billiot/packer-x-aws