Création d'une Infra avec Terraform, Ansible et Packer (la suite)
Mise à jour :
La suite du guide sur la création d’une infrastructure sur le cloud Outscale.
Configuration de l’inventaire dynamique Ansible
Maintenant que les bases de notre infrastructure sont en place, je vais vous présenter comment utiliser Ansible pour automatiser la configuration des instances dans notre environnement cloud. L’aspect le plus intéressant et puissant ici est l’utilisation d’un inventaire dynamique, qui permet à Ansible de s’adapter automatiquement aux changements dans notre infrastructure cloud (faite avec Terraform). )
L’inventaire dynamique est une fonctionnalité d’Ansible qui permet de générer automatiquement un inventaire des instances à partir de sources comme AWS, Azure et dans notre cas, Outscale.
Pour configurer l’inventaire dynamique, nous utilisons le plugin AWS, Outscale étant compatible avec l’API Outscale cela va fonctionner. C’est ce plugin qui se chargera d’obtenir les informations sur les instances.
Créer un nouveau répertoire nommé playbooks
pour y déposer vos fichiers
Ansible.
.├── playbooks└── terraform ├── bastion └── infrastructure
Créer un fichier yaml
dont le nom doit se terminer par .aws_ec2.yml
. Ici, je
vais l’appeler demo.aws_ec2.yml
:
plugin: amazon.aws.aws_ec2aws_profile: outscaleregions: - eu-west-2keyed_groups:- key: tags.Application separator: ''- key: tags.Group separator: ''- key: tags.Env separator: ''hostnames: - ip-address - private-ip-address
Quelques explications :
plugin: amazon.aws.aws_ec2
: Indique qu’Ansible utilise le pluginaws_ec2
pour interagir avec AWS EC2.aws_profile: outscale
: Spécifie le profil AWS à utiliser pour l’authentification. Cela correspond à un profil défini précédemment.regions: - eu-west-2
: Définit la région AWS où le plugin doit chercher les instances.keyed_groups
: Permet de créer des groupes dynamiques basés sur des tags spécifiques. Dans votre cas, il crée des groupes basés sur les tagsApplication
,Group
etEnv
sans séparateur (separator: ''
).hostnames: - ip-address - private-ip-address
: Définit les attributs à utiliser comme noms d’hôtes dans l’inventaire, ici les adresses IP publiques et privées des instances.
Pour que cela fonctionne, il faut créer un fichier de configuration contenant ces lignes :
[defaults]inventory = demo.aws_ec2.ymlremote_user = outscalecallbacks_enabled = timer, profile_tasks, profile_rolesvault_password_file = .vault_pass
[inventory]enable_plugins = aws_ec2
Cela permet de définir l’inventaire à utiliser, ainsi que l’utilisateur distant qui sera chargé d’appliquer les taches Ansible.
Testons-le :
@all: |--@ungrouped: |--@aws_ec2: | |--80.247.5.193 |--@robert: | |--80.247.5.193 |--@bastion: | |--80.247.5.193 |--@bastions: | |--80.247.5.193
Cela fonctionne !
Voici le playbook que je vais lancer dessus qui va configurer une certain nombre de choses :
---- name: Test Ansible hosts: aws_ec2 gather_facts: true roles: - role: stephrobert.motd vars: motd_banner: " {{ tags.Name }} " pre_tasks: - name: Upgrade packages ansible.builtin.apt: upgrade: true update_cache: true become: true - name: Change Hostname ansible.builtin.hostname: name: "{{ hostvars[inventory_hostname].tags.Name }}.{{ hostvars[inventory_hostname].tags.Env }}.local" tags: name become: true - name: Add IP address of all hosts to all hosts | {{ inventory_hostname }} ansible.builtin.lineinfile: dest: /etc/hosts regexp: "^.*{{ hostvars[item].tags.Name }}.{{ hostvars[item].tags.Env }}.local" line: "{{ (hostvars[item].ansible_all_ipv4_addresses | select('match', '192.168.') | list)[0] }} {{ hostvars[item].tags.Name }}.{{ hostvars[item].tags.Env }}.local {{ hostvars[item].tags.Name }}" state: present when: hostvars[item].ansible_nodename is defined with_items: "{{ groups.aws_ec2 }}" become: true - name: Add IP address of all hosts to all hosts | {{ inventory_hostname }} vars: ansible_python_interpreter: /usr/bin/python3 ansible.builtin.lineinfile: dest: /etc/hosts regexp: "^.*{{ hostvars[item].tags.Name }}.{{ hostvars[item].tags.Env }}.local" line: "{{ (hostvars[item].ansible_all_ipv4_addresses | select('match', '192.168.') | list)[0] }} {{ hostvars[item].tags.Name }}.{{ hostvars[item].tags.Env }}.local {{ hostvars[item].tags.Name }}" state: present when: hostvars[item].ansible_nodename is defined with_items: "{{ groups.all }}" delegate_to: localhost run_once: true become: true
Ce playbook modifie le message d’accueil, fais la mise à jour des packages,
change le nom de la machine et créé des entrées dans le fichier /etc/hosts
des
machines distantes et locales (je n’ai pas de serveur DNS). Testons son
exécution :
ansible-playbook provision.yml --limit bastion
PLAY [Test Ansible] ***
TASK [Gathering Facts] ***mercredi 24 janvier 2024 08:48:52 +0100 (0:00:00.007) 0:00:00.008 ******mercredi 24 janvier 2024 08:48:52 +0100 (0:00:00.007) 0:00:00.007 ******ok: [80.247.5.193]
Playbook run took 0 days, 0 hours, 0 minutes, 38 secondsmercredi 24 janvier 2024 08:49:31 +0100 (0:00:00.814) 0:00:38.331 ******==============================================================================Upgrade packages ------------------------------------------------------- 30.58sGathering Facts --------------------------------------------------------- 1.72sstephrobert.motd : Install Figlet --------------------------------------- 1.12sstephrobert.motd : Disable Default motd --------------------------------- 0.93sstephrobert.motd : Copy config to Disable Motd in sshd ------------------ 0.87sChange Hostname --------------------------------------------------------- 0.85sstephrobert.motd : Configure New Dynamic Motd in PAM -------------------- 0.82sstephrobert.motd : Copy new dynamic motd -------------------------------- 0.73sAdd IP address of all hosts to all hosts | 80.247.5.193 ----------------- 0.52sAdd IP address of all hosts to all hosts | 80.247.5.193 ----------------- 0.19smercredi 24 janvier 2024 08:49:31 +0100 (0:00:00.815) 0:00:38.330 ******===============================================================================ansible.builtin.apt ---------------------------------------------------- 30.58sstephrobert.motd -------------------------------------------------------- 4.46sgather_facts ------------------------------------------------------------ 1.72sansible.builtin.hostname ------------------------------------------------ 0.85sansible.builtin.lineinfile ---------------------------------------------- 0.71s~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~total ------------------------------------------------------------------ 38.32s
On se connecte à notre bastion :
ssh bastion _ _ _ | |__ __ _ ___| |_(_) ___ _ __ | '_ \ / _` / __| __| |/ _ \| '_ \ | |_) | (_| \__ \ |_| | (_) | | | | |_.__/ \__,_|___/\__|_|\___/|_| |_|
System info: Hostname····: bastion.robert.local Distro······: Ubuntu 22.04.3 LTS Kernel······: Linux 5.15.0-91-generic Uptime······: up 24 minutes Load········: 0.10 (1m), 0.09 (5m), 0.03 (15m) Processes···: 101 (root), 7 (user), 108 (total) CPU·········: Intel Xeon Processor (Skylake) (2 vCPU) Memory······: 149Mi used, 3.4Gi avail, 3.8Gi total
Disk usage: / 7% used out of 42G [===···············································] /boot/efi 6% used out of 110M [===···············································]
Last login: Wed Jan 24 07:49:30 2024 from 90.110.185.174outscale@bastion:~$
Nous jouerons ce playbook à chaque fois que nous créerons une nouvelle instance !
Création d’une instance dans le réseau privé
Après avoir configuré le bastion, je me concentre maintenant sur le sous-réseau privé. C’est ici que notre service web sera hébergé, isolé du trafic Internet direct pour une sécurité accrue. Cette section est essentielle pour assurer que notre service web fonctionne de manière optimale tout en étant protégé.
On crée un nouveau dossier terraform
appelé test :
├── playbooks└── terraform ├── bastion ├── infrastructure └── test
On procède comme précédemment. Éditez le fichier providers.tf
et changer la
clé du backend :
terraform { backend "s3" { profile = "outscale" bucket = "lab-terraform" key = "service/terraform.state" region = "eu-west-2" endpoint = "https://oos.eu-west-2.outscale.com" skip_credentials_validation = true skip_region_validation = true } required_providers { outscale = { source = "outscale/outscale" version = "0.10.0" } aws = { } }}
On crée notre instance test dans le réseau privé avec ce code :
data "outscale_images" "images" { filter { name = "image_names" values = ["ubuntu_2204_steph"] }}
data "outscale_subnet" "private_subnet" { filter { name = "tag_values" values = ["private_subnet"] }}
data "outscale_security_group" "sg-all-all" { security_group_name = "seg-all-all"}
resource "outscale_keypair" "keypair01" { keypair_name = "test" public_key = file("~/.ssh/id_ed25519.pub")}
resource "outscale_vm" "test" { image_id = data.outscale_images.images.images[0].image_id vm_type = "tinav5.c1r1p2" keypair_name = outscale_keypair.keypair01.keypair_name security_group_ids = [data.outscale_security_group.sg-all-all.id] subnet_id = data.outscale_subnet.private_subnet.id state = "running" tags { key = "Name" value = "test" } tags { key = "Application" value = "test" } tags { key = "Group" value = "test" } tags { key = "Env" value = "robert" }}
On peut appliquer :
terraform apply...
Changes to Outputs: + public_name = (known after apply)outscale_keypair.keypair01: Creating...outscale_keypair.keypair01: Creation complete after 1s [id=test]outscale_vm.test: Creating...outscale_vm.test: Still creating... [10s elapsed]outscale_vm.test: Still creating... [20s elapsed]outscale_vm.test: Creation complete after 28s [id=i-04ed2427]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
On applique notre playbook Ansible :
cd ../../playbooks
ansible-playbook provision.yml --limit test
On teste la connexion :
ssh 192.168.3.200
_ _ | |_ ___ ___| |_ | __/ _ \/ __| __| | || __/\__ \ |_ \__\___||___/\__|
System info: Hostname····: test.robert.local Distro······: Ubuntu 22.04.3 LTS Kernel······: Linux 5.15.0-91-generic Uptime······: up 11 minutes Load········: 0.09 (1m), 0.19 (5m), 0.10 (15m) Processes···: 86 (root), 7 (user), 93 (total) CPU·········: Intel Xeon Processor (Skylake) (1 vCPU) Memory······: 167Mi used, 634Mi avail, 957Mi total
Disk usage: / 7% used out of 42G [===···············································] /boot/efi 6% used out of 110M [===···············································]
Last login: Wed Jan 24 08:32:07 2024 from 192.168.2.113
On installe nginx :
sudo apt install -y nginx
On teste que le port 80 est bien démarré :
ss -tlnpState Recv-Q Send-Q Local Address:Port Peer Address:Port ProcessLISTEN 0 511 0.0.0.0:80 0.0.0.0:*LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*LISTEN 0 128 0.0.0.0:22 0.0.0.0:*LISTEN 0 511 [::]:80 [::]:*LISTEN 0 128 [::]:22 [::]:*
Création d’un load balancer
Un load balancer distribue le trafic réseau entrant entre plusieurs machines virtuelles (VM) du Cloud public ou d’un réseau. Les VM enregistrées auprès d’un load balancer sont appelées des VM back-ends. Vous pouvez enregistrer autant de VM back-ends que nécessaire auprès d’un load balancer et vous pouvez enregistrer ou retirer des VM back-ends à tout moment selon vos besoins.
En fonction de ses security groups, une VM back-end peut recevoir soit le trafic venant uniquement du load balancer soit le trafic venant à la fois du load balancer et d’une autre source (par exemple un autre load balancer, internet, ou autres).
Le load balancer vérifie la santé des VM back-ends pour déterminer les VM saines vers lesquelles il peut distribuer du trafic.
Ici, je vais utiliser un load balancer de type internet. Dans le fichier
main.tf
du répertoire test ajoutez ces lignes :
data "outscale_subnet" "public_subnet" { filter { name = "tag_values" values = ["public_subnet"] }}
resource "outscale_load_balancer" "load_balancer" { load_balancer_name = "test-balancer" listeners { backend_port = 80 backend_protocol = "TCP" load_balancer_protocol = "TCP" load_balancer_port = 80 } # subregion_names = ["eu-west-2a"] security_groups = [data.outscale_security_group.sg-all-all.security_group_id] load_balancer_type = "internet-facing" subnets = [data.outscale_subnet.public_subnet.subnet_id] tags { key = "name" value = "test" }}
resource "outscale_load_balancer_vms" "outscale_load_balancer_test" { load_balancer_name = outscale_load_balancer.load_balancer.load_balancer_name backend_vm_ids = [outscale_vm.test.id]}
Dans le fichier output.tf
ajoutez ces lignes :
output "public_name" { value = outscale_load_balancer.load_balancer.dns_name}
Vous remarquez que je déploie le load balancer dans le sous-réseau public et je le lie à l’instance test qui se trouve dans le sous réseau privé !
On le déploie :
terraform apply -auto-approve...
public_name = "test-balancer-964931616.eu-west-2.lbu.outscale.com"
On peut vérifier l’état du load balancer avec la CLI d’AWS :
aws elb describe-instance-health --load-balancer-name test-balancer --instance i-04ed2427 --endpoint https://lbu.eu-west-2.outscale.com
{ "InstanceStates": [ { "InstanceId": "i-04ed2427", "State": "InService", "ReasonCode": "N/A", "Description": "N/A" } ]}
Il est bien en service. Si ce n’est pas le cas vérifier que depuis le réseau
public, vous pouvez atteindre le port 80 de l’instance test
avec la commande
netcat
ou un simple.
On teste depuis l’accès depuis internet :
curl http://test-balancer-964931616.eu-west-2.lbu.outscale.com
<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p>
<p>For online documentation and support please refer to<a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p></body></html>
Cela fonctionne 👍
Faire le ménage
Dans chaque répertoire Terraform, en commençant par l’instance test, puis l’instance bastion et pour finir l’infrastructure :
terraform destroy -auto-approve
Conclusion
En conclusion, la création d’une infrastructure réseau sur le cloud Outscale en utilisant Terraform, Ansible et Packer est une démarche complexe, mais enrichissante. Nous avons parcouru les étapes de configuration de l’environnement, création du VPC et des sous-réseaux, mise en place du sous-réseau public et privé, intégration du Load Balancer et l’automatisation avec Ansible. Nous avons également abordé les bonnes pratiques et les techniques de dépannage et de restauration.
Ce projet illustre l’importance d’une approche structurée et réfléchie dans la gestion d’infrastructures cloud. Les compétences et outils que nous avons utilisés sont essentiels pour tout professionnel DevOps souhaitant évoluer dans l’environnement cloud moderne.
Je vous encourage à explorer davantage ces outils et à les adapter à vos propres besoins en infrastructure. Le potentiel d’apprentissage et d’optimisation est immense dans le domaine du cloud computing.