Exécution sur AWS EC2

1. Objectif

Création d’une instance EC2 avec aws cli.

2. Prérequis aws-cli

Une installation du programme aws-cli (Installation de l’AWS CLI), sa configuration (Configuration de l’AWS CLI) et le paramétrage des variables d’environnement sont des pré-requis.

pip install boto boto3 awscli --upgrade --user
export ANSIBLE_HOST_KEY_CHECKING=False
export ANSIBLE_HOST_KEY_CHECKING=False >> ~/.profile
echo "export AWS_ACCESS_KEY_ID="AKIA2VVIC6KHZOMRV3XX"" >> .profile
echo "export AWS_SECRET_ACCESS_KEY="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"" >> .profile
echo "export AWS_DEFAULT_REGION="eu-west-1"" >> .profile
ssh-keygen -b 4096 -t rsa -f /tmp/sshkey -q -N ""
aws configure
cat ~.aws/*
aws ec2 describe-instances

3. Prérequis AWS EC2

Pour lancer une instance EC2, il est nécessaire de disposer de quelques ressource préalable :

  1. AMI : une image de référence
  2. VPC : un switch virtuel auquel va se connecter l’instance EC2 avec des règles d’accès entrant (au minimum TCP22)
  3. Clé : une clé privée SSH pour se connecter à l’instance. Il est nécessaire de prendre connaissance du nom d’utilisateur
  4. Script Cloud-init : un script à lancer au démarrage de l’instance pour un approvisionnement automatique

4. Amazon Machine Image (AMI)

Une Amazon Machine Image (AMI) fournit les informations requises pour lancer une instance, qui est un serveur virtuel dans le cloud. Vous devez spécifier une AMI source lorsque vous lancez une instance. Lorsque vous avez besoin de plusieurs instances configurées de manière identique, il est possible de lancer plusieurs instances à partir d’une même AMI. Lorsque vous avez besoin d’instances configurées de manière différente, vous pouvez utiliser différentes AMI pour lancer ces instances.

Une AMI comprend les éléments suivants :

  • Un modèle d’image pour le volume racine de l’instance (par exemple, un système d’exploitation, un serveur d’applications et des applications)
  • Les autorisations de lancement qui contrôlent les comptes AWS qui peuvent utiliser l’AMI pour lancer les instances

Un “mappage” de périphérique de stockage en mode bloc qui spécifie les volumes à attacher à l’instance lorsqu’elle est lancée

https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/AMIs.html

Cette commande effectue une recherche d’une AMI Linux Amazon (Amazon Linux AMI) pour une instance “x86_64 HVM GP2” :

aws ec2 describe-images --filters "Name=description,Values=Amazon Linux AMI * x86_64 HVM GP2" --query 'Images[*].[CreationDate, Description, ImageId]' --output text | sort -k 1 | tail
2018-01-03T19:01:53.000Z Amazon Linux AMI 2017.09.1.20180103 x86_64 HVM GP2 ami-8715a2fa
2018-01-08T18:42:47.000Z Amazon Linux AMI 2017.09.1.20180108 x86_64 HVM GP2 ami-fe03b483
2018-01-15T19:12:53.000Z Amazon Linux AMI 2017.09.1.20180115 x86_64 HVM GP2 ami-8ee056f3
2018-01-18T23:08:21.000Z Amazon Linux AMI 2017.09.1.20171120 x86_64 HVM GP2 ami-27e85e5a
2018-03-07T06:59:00.000Z Amazon Linux AMI 2017.09.1-testlongids.20180307 x86_64 HVM GP2 ami-08f0e11237ddeb2f0
2018-03-07T06:59:52.000Z Amazon Linux AMI 2017.09.1.20180307 x86_64 HVM GP2 ami-4f55e332
2018-04-13T00:25:52.000Z Amazon Linux AMI 2018.03.0.20180412 x86_64 HVM GP2 ami-cae150b7
2018-05-08T18:10:54.000Z Amazon Linux AMI 2018.03.0.20180508 x86_64 HVM GP2 ami-969c2deb
2018-06-22T22:24:50.000Z Amazon Linux AMI 2018.03.0.20180622 x86_64 HVM GP2 ami-d50bbaa8
2018-08-11T02:29:44.000Z Amazon Linux AMI 2018.03.0.20180811 x86_64 HVM GP2 ami-0ebc281c20e89ba4b

Retenons l’AMI ami-0ebc281c20e89ba4b comme la plus récente.

export AWS_IMAGE="ami-0ebc281c20e89ba4b"

5. Trouver une AMI Linux

Exemple : Rechercher l’AMI Amazon Linux 2 actuelle

aws ec2 describe-images --owners amazon \
--filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????-x86_64-gp2' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'

Exemple : Rechercher l’AMI Amazon Linux actuelle

aws ec2 describe-images --owners amazon \
--filters 'Name=name,Values=amzn-ami-hvm-????.??.?.????????-x86_64-gp2' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'

Exemple : Rechercher l’AMI Ubuntu Server 18.04 LTS actuelle

aws ec2 describe-images --owners 099720109477 \
--filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-????????' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'

Exemple : Rechercher l’AMI Red Hat Enterprise Linux 7.6 actuelle

aws ec2 describe-images --owners 309956199498 \
--filters 'Name=name,Values=RHEL-7.6_HVM_GA*' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'

Exemple : Rechercher l’AMI SUSE Linux Enterprise Server 15 actuelle

aws ec2 describe-images --owners amazon \
--filters 'Name=name,Values=suse-sles-15-v????????-hvm-ssd-x86_64' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'

6. VPC

Un Virtual Private Cloud (VPC) est un réseau virtuel dédié logiquement isolé des autres réseaux virtuels dans le cloud AWS. On peut lancer des ressources AWS, comme des instances Amazon EC2, dans un VPC, spécifier une plage d’adresses IP pour le VPC, ajouter des sous-réseaux, associer des groupes de sécurité et configurer des tables de routage.

Un sous-réseau est une plage d’adresses IP dans le VPC.

aws ec2 describe-vpcs --output table
--------------------------------------------------
|                  DescribeVpcs                  |
+------------------------------------------------+
||                     Vpcs                     ||
|+-----------------------+----------------------+|
||  CidrBlock            |  172.31.0.0/16       ||
||  DhcpOptionsId        |  dopt-93e8ccfa       ||
||  InstanceTenancy      |  default             ||
||  IsDefault            |  True                ||
||  OwnerId              |  733718180495        ||
||  State                |  available           ||
||  VpcId                |  vpc-476c332e        ||
|+-----------------------+----------------------+|
|||           CidrBlockAssociationSet          |||
||+----------------+---------------------------+||
|||  AssociationId |  vpc-cidr-assoc-a9c5c4c0  |||
|||  CidrBlock     |  172.31.0.0/16            |||
||+----------------+---------------------------+||
||||              CidrBlockState              ||||
|||+---------------+--------------------------+|||
||||  State        |  associated              ||||
|||+---------------+--------------------------+|||
export AWS_VPC="vpc-476c332e"
export AWS_VPC=$(aws ec2 describe-vpcs --query 'Vpcs[*].VpcId' --output text)
LABID="${USER}${RANDOM}"
aws ec2 create-security-group \
        --group-name $LABID-demo-lab \
        --description "$LABID Demo Lab Security Group" \
        --vpc-id $AWS_VPC
aws ec2 authorize-security-group-ingress \
        --group-name $LABID-demo-lab \
        --protocol tcp \
        --port 22 \
        --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
        --group-name $LABID-demo-lab \
        --protocol tcp \
        --port 80 \
        --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
        --group-name $LABID-demo-lab \
        --protocol tcp \
        --port 3000 \
        --cidr 0.0.0.0/0
aws ec2 describe-security-groups \
        --group-names $LABID-demo-lab \
        --output table
----------------------------------------------------------
|                 DescribeSecurityGroups                 |
+--------------------------------------------------------+
||                    SecurityGroups                    ||
|+--------------+---------------------------------------+|
||  Description |  francois606 Demo Lab Security Group  ||
||  GroupId     |  sg-0a518bf1e2fd2cc83                 ||
||  GroupName   |  francois606-demo-lab                 ||
||  OwnerId     |  733718180495                         ||
||  VpcId       |  vpc-476c332e                         ||
|+--------------+---------------------------------------+|
|||                    IpPermissions                   |||
||+----------------------------------+-----------------+||
|||  FromPort                        |  80             |||
|||  IpProtocol                      |  tcp            |||
|||  ToPort                          |  80             |||
||+----------------------------------+-----------------+||
||||                     IpRanges                     ||||
|||+---------------------+----------------------------+|||
||||  CidrIp             |  0.0.0.0/0                 ||||
|||+---------------------+----------------------------+|||
|||                    IpPermissions                   |||
||+----------------------------------+-----------------+||
|||  FromPort                        |  22             |||
|||  IpProtocol                      |  tcp            |||
|||  ToPort                          |  22             |||
||+----------------------------------+-----------------+||
||||                     IpRanges                     ||||
|||+---------------------+----------------------------+|||
||||  CidrIp             |  0.0.0.0/0                 ||||
|||+---------------------+----------------------------+|||
|||                    IpPermissions                   |||
||+--------------------------------+-------------------+||
|||  FromPort                      |  3000             |||
|||  IpProtocol                    |  tcp              |||
|||  ToPort                        |  3000             |||
||+--------------------------------+-------------------+||
||||                     IpRanges                     ||||
|||+---------------------+----------------------------+|||
||||  CidrIp             |  0.0.0.0/0                 ||||
|||+---------------------+----------------------------+|||
|||                 IpPermissionsEgress                |||
||+------------------------------------+---------------+||
|||  IpProtocol                        |  -1           |||
||+------------------------------------+---------------+||
||||                     IpRanges                     ||||
|||+---------------------+----------------------------+|||
||||  CidrIp             |  0.0.0.0/0                 ||||
|||+---------------------+----------------------------+|||

On retiendra l’ID du groupe de sécurité

export AWS_SGID="sg-0a518bf1e2fd2cc83"

7. Clé d’accès

aws ec2 create-key-pair --key-name $LABID-demo-lab-key --query 'KeyMaterial' --output text > ~/.ssh/$LABID-demo-lab-key.pem
aws ec2 describe-key-pairs --key-name $LABID-demo-lab-key
{
    "KeyPairs": [
        {
            "KeyName": "demo-lab-key",
            "KeyFingerprint": "75:96:3c:ae:00:4f:27:88:af:35:52:a1:b9:cd:6d:0e:ff:2c:f4:58"
        }
    ]
}

Restreindre les droits

chmod 400 ~/.ssh/$LABID-demo-lab-key.pem

8. Lancer une instance t2.micro

aws ec2 run-instances \
    --instance-type t2.micro \
    --key-name $LABID-demo-lab-key \
    --security-group-ids $AWS_SGID \
    --image-id $AWS_IMAGE
{
    "Instances": [
        {
            "Monitoring": {
                "State": "disabled"
            },
            "PublicDnsName": "",
            "StateReason": {
                "Message": "pending",
                "Code": "pending"
            },
            "State": {
                "Code": 0,
                "Name": "pending"
            },
            "EbsOptimized": false,
            "LaunchTime": "2018-10-28T12:34:52.000Z",
            "PrivateIpAddress": "172.31.35.250",
            "ProductCodes": [],
            "VpcId": "vpc-476c332e",
            "CpuOptions": {
                "CoreCount": 1,
                "ThreadsPerCore": 1
            },
            "StateTransitionReason": "",
            "InstanceId": "i-03f6971fddaff8558",
            "ImageId": "ami-0ebc281c20e89ba4b",
            "PrivateDnsName": "ip-172-31-35-250.eu-west-3.compute.internal",
            "KeyName": "demo-lab-key",
            "SecurityGroups": [
                {
                    "GroupName": "demo-lab",
                    "GroupId": "sg-04edf90e659364a62"
                }
            ],
            "ClientToken": "",
            "SubnetId": "subnet-074deb4a",
            "InstanceType": "t2.micro",
            "NetworkInterfaces": [
                {
                    "Status": "in-use",
                    "MacAddress": "0e:3e:0a:fc:59:56",
                    "SourceDestCheck": true,
                    "VpcId": "vpc-476c332e",
                    "Description": "",
                    "NetworkInterfaceId": "eni-092f4bd7687ea0b89",
                    "PrivateIpAddresses": [
                        {
                            "PrivateDnsName": "ip-172-31-35-250.eu-west-3.compute.internal",
                            "Primary": true,
                            "PrivateIpAddress": "172.31.35.250"
                        }
                    ],
                    "PrivateDnsName": "ip-172-31-35-250.eu-west-3.compute.internal",
                    "Attachment": {
                        "Status": "attaching",
                        "DeviceIndex": 0,
                        "DeleteOnTermination": true,
                        "AttachmentId": "eni-attach-0f2b364177e190547",
                        "AttachTime": "2018-10-28T12:34:52.000Z"
                    },
                    "Groups": [
                        {
                            "GroupName": "demo-lab",
                            "GroupId": "sg-04edf90e659364a62"
                        }
                    ],
                    "Ipv6Addresses": [],
                    "OwnerId": "733718180495",
                    "SubnetId": "subnet-074deb4a",
                    "PrivateIpAddress": "172.31.35.250"
                }
            ],
            "SourceDestCheck": true,
            "Placement": {
                "Tenancy": "default",
                "GroupName": "",
                "AvailabilityZone": "eu-west-3c"
            },
            "Hypervisor": "xen",
            "BlockDeviceMappings": [],
            "Architecture": "x86_64",
            "RootDeviceType": "ebs",
            "RootDeviceName": "/dev/xvda",
            "VirtualizationType": "hvm",
            "AmiLaunchIndex": 0
        }
    ],
    "ReservationId": "r-091126bd779f312ce",
    "Groups": [],
    "OwnerId": "733718180495"
}

9. Instances EC2

Amazon Elastic Compute Cloud (Amazon EC2) est un service Web qui fournit une capacité de calcul sécurisée et redimensionnable dans le cloud. Destiné aux développeurs, il est conçu pour faciliter l’accès aux ressources de cloud computing à l’échelle du Web.

Type d’instances

Types de virtualisation AMI Linux

Instances dédiées Amazon EC2

aws ec2 describe-instances
aws ec2 describe-instances --output table
aws ec2 describe-instances --filters "Name=instance.group-name,Values=$LABID-demo-lab"
aws ec2 describe-instances --filters "Name=instance-type,Values=t2.micro" --query Reservations[].Instances[].InstanceId
aws ec2 describe-instances --filters "Name=instance.group-name,Values=$LABID-demo-lab" --query Reservations[].Instances[].InstanceId --output text
i-01295da4c589e3178
export AWS_INSTANCE="$(aws ec2 describe-instances --filters "Name=instance.group-name,Values=$LABID-demo-lab" --query Reservations[].Instances[].InstanceId --output text)"
aws ec2 describe-instances \
    --instance-ids $AWS_INSTANCE \
    --query "Reservations[*].Instances[*].PublicDnsName" --output text
INSTANCE=$(aws ec2 describe-instances --instance-ids $AWS_INSTANCE --query "Reservations[*].Instances[*].PublicDnsName" --output text)

10. Connexion à l’instance

Pour une AMI Linux Amazon :

ssh -i ~/.ssh/$LABID-demo-lab-key.pem ec2-user@$INSTANCE

Pour une AMI Ubuntu :

ssh -i ~/.ssh/$LABID-demo-lab-key.pem ubuntu@$INSTANCE

11. Terminer une instance

aws ec2 stop-instances --instance-ids $AWS_INSTANCE && aws ec2 terminate-instances --instance-ids $AWS_INSTANCE

12. Supprimer une paire de clés

aws ec2 delete-key-pair --key-name $LABID-demo-lab-key

13. Supprimer un groupe de sécurité

aws ec2 delete-security-group --group-name $LABID-demo-lab

14. Déploiement avec Cloud-init

Exécution de commandes sur votre instance Linux lors du lancement : on ajoute le paramètres --user-data file://my_script.shlors de la création de l’instance avec la commande aws ec2 run-instances, my_script.sh étant le script qui s’exécutera au démarrage. C’est la solution Cloud-init qui est utilisée dans ce cas. Beaucoup d’autres prestataires utilisent ce logiciel pour approvisionner des instances.

  • Approvisionnement de stack Python, Ansible, encore LAMP ou encoreGitlab Runner.

15. AWS automatisé en Bash

Voici un script Bash qui automatise la procédure de création d’une instance EC2, le contenu d’un fichier init.sh configure l’instance (--user-data file://init.sh) :

#!/bin/bash
# To Launch one Ubuntu Xenial EC2 instance
# Define several variables
DATE_ID=$(date +%s)
SG_NAME="ec2-sg-$DATE_ID"
KEY_NAME="ec2-key-$DATE_ID"
INSTANCE_TYPE="t2.micro"
SCRIPT="--user-data file://init.sh"
# Create a security group
echo -e "1. Create a security group"
SGID=$(aws ec2 create-security-group --group-name $SG_NAME \
--description "gitlab runner Security Group" \
--vpc-id $(aws ec2 describe-vpcs | jq -r .Vpcs[].VpcId) | jq -r .GroupId)
# Add an ingress rule for ssh management trafic
echo -e "2. Add an ingress rule for ssh management trafic"
aws ec2 authorize-security-group-ingress --group-name $SG_NAME \
--protocol tcp --port 22 --cidr 0.0.0.0/0
# Delete the old key anyway
echo -e "3. Delete the old SSH private key anyway"
rm -f ~/.ssh/$KEY_NAME.pem
# Create a new SSH private key
echo -e "4. Create a new SSH private key and restrict rights to only you"
aws ec2 create-key-pair --key-name $KEY_NAME --query 'KeyMaterial' \
--output text > ~/.ssh/$KEY_NAME.pem
# Restrict rights on the key
chmod 400 ~/.ssh/$KEY_NAME.pem
# Get an Ubuntu Xenial Image ID
echo -e "5. Get an Ubuntu Xenial Image ID"
IMAGE_ID=$(aws ec2 describe-images --owners 099720109477 \
--filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-????????' 'Name=state,Values=available' \
--output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId')
# Launch the EC2 instance
echo -e "6. Launch the EC2 instance with type, key name, security group and image id"
INSTANCE_ID=$(aws ec2 run-instances --instance-type $INSTANCE_TYPE --key-name $KEY_NAME \
--security-group-ids $SGID --image-id $IMAGE_ID $SCRIPT | jq -r '.Instances[] | .InstanceId')
# Get the instance ID
echo -e "7. Get the instance IP"
INSTANCE_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID | jq -r .Reservations[].Instances[].PublicIpAddress)
# Store the connexion informations into the ~/aws-ec2-instances.csv
echo "$INSTANCE_ID,~/.ssh/$KEY_NAME.pem,$INSTANCE_IP" >> ~/aws-ec2-instances.csv
# Final Message
echo -e "   To get an ssh terminal use this command:"
echo -e "   ssh -i ~/.ssh/$KEY_NAME.pem ubuntu@$INSTANCE_IP"
echo -e "   This information is stored in ~/aws-ec2-instances.csv"

Voici la script init.sh :

#!/bin/bash
sudo apt upate && sudo apt -y install python-minimal