AWS EC2 - Les AMI

Accueil Tags Recherche

18 Avril 2023

AWS EC2 - Les AMI

À quoi servent les AWS AMI, et comment les utiliser ?

Cet article s’inscrit dans un dossier sur la gestion de machines EC2 avec AWS CloudFormation. Vous pouvez retrouver l’intégralité du code utilisé en fin d’article.

Introduction et disclaimers

Avant de commencer ce long dossier sur EC2, je tiens à signaler que j’utiliserai comme à l’accoutumée CDK pour définir l’infrastructure nécessaire. Il vous sera bien sûr toujours possible de faire la même chose avec Terraform ou en passant par l’AWS Console, mais ces alternatives ne seront pas détaillées dans mes articles pour des raisons de concision.

Les ressources que nous allons créer seront de petite taille, mais vont malgré tout engendrer des coûts minimes (environ 30 centimes par jour). Je veillerai à n’utiliser que des ressources disponibles dans l’offre gratuite d’AWS. Il reste de votre responsabilité de surveiller ces coûts, et surtout de supprimer les ressources créées si vous n’en avez plus l’utilité. L’utilisation d’une stack CloudFormation permettra une suppression simple rapide de toutes ces ressources.

Pour ce faire, vous pouvez initialiser une nouvelle application CDK dans un dossier vide :

mkdir workshop
cd workshop
cdk init app --language=python
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cdk deploy

Tout le code qui sera présenté dans la suite de ces articles sera à placer dans le constructeur de votre Stack, soit dans le fichier workshop/workshop_stack.py, à l’intérieur de la fonction __init__().

À tout moment, vous pouvez supprimer la stack entière et toutes ses ressources avec la commande suivante (ou en passant par l’AWS Console, dans la section CloudFormation):

cdk destroy

Les Amazon Image Machines (AMI)

Dès la première minute où vous allez travailler avec des instances EC2, vous entendrez parler de la notion d’AMI. Voyons donc ce que sont concrètement ces AMI, et comment les utiliser.

Si vous voulez provisionner une instance EC2 chez AWS, il va vous falloir définir au minimum trois choses :

  • Le type et la taille de l’instance (t4g.small, m5.large, t3.nano…)
  • L’image à utiliser (ou AMI, pour Amazon Machine Image)
  • Le VPC dans lequel déployer l’instance

On choisit par exemple une AMI Amazon Linux 2 AMD64 HVM à lancer sur une instance t2.micro (avec donc 1 vCPU et 1 Gio de mémoire), le tout dans un nouveau VPC. Mais une fois cette instance provisionnée, qu’allez-vous réellement avoir de disponible ?

Pour le savoir, regardons de plus près ce que contient une AMI :

  • Un instantané, qui sera copié sur l’instance
  • Des autorisations de lancement (pour restreindre les comptes AWS qui peuvent utiliser cette AMI)
  • Un mappage de périphérique de stockage, définissant les volumes à attacher à l’instance lors de son lancement

Les AMI sont tout simplement des machines virtuelles pré-configurées, que vous allez pouvoir déployer sur les instances dont vous avez besoin.

Créer une instance EC2

Voici une base de travail qui va vous permettre de créer un nouveau VPC, ainsi qu’une instance EC2. La partie VPC ne nous intéresse pas pour le moment, j’ai uniquement ajouté quelques options pour réduire de nombre de ressources créées par défaut (une seule zone de disponibilité, et un seul sous-réseau public). En ce qui concerne l’instance EC2, on retrouve bien nos trois éléments (le type d’instance, l’AMI et le VPC) :

vpc = ec2.Vpc(self, "Vpc",
  subnet_configuration = [
    ec2.SubnetConfiguration(
      name = "public",
      subnet_type = ec2.SubnetType.PUBLIC,
      cidr_mask = 24)],
  max_azs = 1)

instance = ec2.Instance(self, "Instance",
  # Type d'instance : t2.micro
  instance_type = ec2.InstanceType.of(
    instance_class = ec2.InstanceClass.T2,
    instance_size = ec2.InstanceSize.MICRO),
  # AMI à utiliser
  machine_image = ec2.MachineImage.generic_linux({
    "eu-west-3": "ami-01fde5e5b31e98551"}),
  # VPC dans lequel déployer l'instance
  vpc = vpc)

Lancez le déploiement de la stack avec cdk deploy et vous devriez à présent retrouver le VPC et votre instance dans l’AWS Console, ou l’AWS CLI :

aws ec2 describe-vpcs \
  --region eu-west-3 \
  --filter "Name=tag:Name,Values=WorkshopStack/Vpc" \
  | jq '.Vpcs[0].VpcId'
# "vpc-002eb550de44ebcba"
aws ec2 describe-instances \
  --region eu-west-3 \
  --filter "Name=vpc-id,Values=vpc-002eb550de44ebcba"\
  | jq '.Reservations[0].Instances[0].InstanceId'
# "i-0538d3d79b3da5976"

Rechercher des AMI

Dans l’exemple précédent, je parlais d’utiliser une AMI Amazon Linux 2 AMD64 HVM. En récupérant un peu plus d’informations sur notre instance, on peut constater qu’elle semble correspondre à ces pré-requis :

aws ec2 describe-instances \
  --region eu-west-3 \
  --filter "Name=instance-id,Values=i-0538d3d79b3da5976" \
  | jq '.Reservations[0].Instances[0] | {
    "id": .InstanceId, 
    "type": .InstanceType, 
    "architecture": .Architecture, 
    "platform": .PlatformDetails, 
    "cpu": (.CpuOptions.CoreCount * .CpuOptions.ThreadsPerCore)
  }'
# {
#   "id": "i-0538d3d79b3da5976",
#   "type": "t2.micro",
#   "architecture": "x86_64",
#   "platform": "Linux/UNIX",
#   "cpu": 1
# }

Mais comment savoir que l’AMI ami-01fde5e5b31e98551 correspond bien à un AMI Amazon Linux 2 AMD64 HVM ? Et surtout comment trouver l’AMI ID à partir des spécifications voulues ?

Pour cela nous allons utiliser la commande aws ec2 describe-images qui permet d’aller chercher dans les AMI publiques partagées soit par Amazon directement, soit par des tiers. Dans mon cas, j’ai filtré les images disponibles pour ne récupérer que celles proposées par Amazon, avec quelques paramètres supplémentaires pour cibler un Amazon Linux 2 AMD64 HVM :

aws ec2 describe-images \
  --region eu-west-3 \
  --owners amazon \
  --filters "Name=architecture,Values=x86_64" \
            "Name=name,Values=amzn2-ami-hvm*" \
            "Name=root-device-type,Values=ebs" \
  | jq '.Images[] | {"id": .ImageId, "name": .Name}'
# {
#   "id": "ami-0f7ae306040d67a2d",
#   "name": "amzn2-ami-hvm-2.0.20221103.3-x86_64-ebs"
# }
# {
#   "id": "ami-0df20bc35d0026955",
#   "name": "amzn2-ami-hvm-2.0.20220316.0-x86_64-ebs"
# }
# {
#   "id": "ami-05cb6b584fc3c8ac8",
#   "name": "amzn2-ami-hvm-2.0.20220207.1-x86_64-gp2"
# }
# …

Comme il y en a un certain nombre (qui sont en fait les différentes mises à jour apportées à cette image), j’ordonne la liste par date de création, et ne récupère que la plus récente (attention, entre le moment où cet article sort et celui où vous faites vos tests, il y aura probablement de nouvelles versions, et vous n’aurez pas la même AMI affichée) :

aws ec2 describe-images \
  --region eu-west-3 \
  --owners amazon \
  --filters "Name=architecture,Values=x86_64" \
            "Name=name,Values=amzn2-ami-hvm*" \
            "Name=root-device-type,Values=ebs" \
  | jq '.Images | sort_by(.CreationDate) | reverse[0] | {"id": .ImageId, "name": .Name}'
# {
#   "id": "ami-01fde5e5b31e98551",
#   "name": "amzn2-ami-hvm-2.0.20230320.0-x86_64-gp2"
# }

L’option --filters de la commande vous permet de réduire le nombre de résultats retournés, avant de retravailler cette liste en local avec jq. Techniquement vous pourriez tout mettre dans le filtre jq, mais vu le nombre d’images disponibles le temps de réponse de votre commande serait beaucoup trop important.

Ici nous avons filtré sur du Amazon Linux 2, mais vous pouvez tout aussi bien rechercher par exemple du Ubuntu 22.04, à l’aide du filtre Name=name,Values=ubuntu/*22.04*. Le format de ces noms d’AMI varie selon les providers, et il faut généralement tâtonner un peu avant de trouver le pattern qui vous permettra par la suite de filtrer proprement.

Vous avez à présent les bases pour rechercher des AMI à utiliser avec vos instances !

Utiliser la dernière version d’Amazon Linux

Si vous utilisez Amazon Linux, AWS vous facilite un peu la tâche en publiant un paramètre SSM qui contient l’AMI ID de la version la plus récente disponible. Précédemment, nous avons lancé la commande suivante :

aws ec2 describe-images \
  --region eu-west-3 \
  --owners amazon \
  --filters "Name=architecture,Values=x86_64" \
            "Name=name,Values=amzn2-ami-hvm*" \
            "Name=root-device-type,Values=ebs" \
  | jq '.Images | sort_by(.CreationDate) | reverse[0] | {"id": .ImageId, "name": .Name}'
# {
#   "id": "ami-01fde5e5b31e98551",
#   "name": "amzn2-ami-hvm-2.0.20230320.0-x86_64-gp2"
# }

Mais il est aussi possible de directement récupérer d’ID de cet AMI via le paramètre SSM dédié :

aws ssm get-parameters \
  --region eu-west-3 \
  --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 \
  | jq '.Parameters[0].Value'
# "ami-01fde5e5b31e98551"

Ce paramètre SSM peut aussi être utilisé directement dans CDK lors de la définition de votre instance :

instance = ec2.Instance(self, "Instance",
  
  # On utilise un paramètre SSM au lieu d'un AMI ID en dur
  machine_image = ec2.MachineImage.from_ssm_parameter(
    parameter_name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2"))

Attention toutefois ! N’utilisez cette technique que pour des instances qui peuvent être détruites à tout instant. En effet, à chaque mise à jour de l’AMI le paramètre SSM sera mis à jour, et cela déclenchera un écart de template CloudFormation. Comme l’ID de l’AMI aura changé, l’instance sera complètement supprimée et une nouvelle viendra prendre sa place.

EBS versus Instance Store

Les plus perspicaces auront noté dans mes commandes la présence du filtre suivant : Name=root-device-type,Values=ebs. Il faut savoir que les AMI entrent toutes dans une des deux catégories suivantes :

  • celles basées sur EBS
  • celles basées sur le stockage d’instances

Pour être tout à fait honnête, le filtre ne sert ici absolument à rien car les AMI Amazon Linux 2 sont toutes basées sur EBS. En revanche, vous trouverez les deux cas pour les AMI Amazon Linux (génération 1) ou les AMI Ubuntu, j’ai donc jugé intéressant d’ajouter ce filtre afin d’évoquer cette notion importante.

Je vous renvoie à la documentation pour connaître les différences en détail, mais en voici les principales :

  • Les AMI basées sur EBS ont des volumes rattachés via le réseau. Les performances de ces volumes sont naturellement un peu moins élevées (à moins que vous n’ayez une utilisation avancée d’EC2 vous ne verrez pas la différence), mais ils sont persistants, ce qui signifie que vous pouvez redémarrer votre instance sans perte de donnée, ce qui est le cas d’usage le plus courant
  • Les AMI basées sur le stockage d’instance (Instance Store) ont des volumes temporaires physiquement rattachés à votre instance. Des performances élevées donc, mais pas de persistance de la donnée en cas de redémarrage

La différence est très importante, et le choix du type d’AMI à utiliser dépend complètement de l’utilisation que vous aurez de votre instance.

Conclusion

Vous avez maintenant compris ce qu’étaient les AMI et comment créer des instances EC2, mais pour le moment l’instance est vide, et nous ne savons même pas comment y accéder. Dans les prochains articles nous découvrirons comment se connecter à votre nouvelle instance, ainsi que les 4 utilitaires CloudFormation fournis par AWS pour vous aider à gérer vos instances au quotidien.

En passant, sachez qu’il est possible de créer sa propre AMI à partir d’une instance ou d’un snapshot, afin d’avoir tout ce dont vous avez besoin préinstallé et d’éviter d’avoir à configurer chaque nouvelle instance. Ne travaillant pas pour le moment avec des short lived instances, je n’ai pas encore eu l’occasion d’approfondir le sujet. Je vous redirige donc aujourd’hui vers la documentation, mais un article sur la création d’AMI n’est pas à exclure dans le futur.

Voir l'intégralité du code
from aws_cdk import (
  Stack,
  aws_ec2 as ec2,
)
from constructs import Construct

class WorkshopStack(Stack):

  def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
    super().__init__(scope, construct_id, **kwargs)

    vpc = ec2.Vpc(self, "Vpc",
      subnet_configuration = [
        ec2.SubnetConfiguration(
          name = "public",
          subnet_type = ec2.SubnetType.PUBLIC,
          cidr_mask = 24)],
      max_azs = 1)

    instance = ec2.Instance(self, "Instance",
      # Type d'instance : t2.micro
      instance_type = ec2.InstanceType.of(
        instance_class = ec2.InstanceClass.T2,
        instance_size = ec2.InstanceSize.MICRO),
      # AMI à utiliser
      machine_image = ec2.MachineImage.generic_linux({
        "eu-west-3": "ami-01fde5e5b31e98551"}),
      # VPC dans lequel déployer l'instance
      vpc = vpc)

Liens

Référence AWS - Les types d’AMI
Référence AWS CDK - EC2::Vpc
Référence AWS CDK - EC2::Instance
Référence AWS CLI - ec2 describe-instances
Référence AWS CLI - ec2 describe-images
Référence AWS CLI - ssm get-parameter