Aller au contenu

Création d'une Infrastructure 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.

Terminal window
.
├── 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_ec2
aws_profile: outscale
regions:
- eu-west-2
keyed_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 plugin aws_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 tags Application, Group et Env 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.yml
remote_user = outscale
callbacks_enabled = timer, profile_tasks, profile_roles
vault_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 :

Terminal window
@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 :

Terminal window
---
- 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 :

Terminal window
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 seconds
mercredi 24 janvier 2024 08:49:31 +0100 (0:00:00.814) 0:00:38.331 ******
==============================================================================
Upgrade packages ------------------------------------------------------- 30.58s
Gathering Facts --------------------------------------------------------- 1.72s
stephrobert.motd : Install Figlet --------------------------------------- 1.12s
stephrobert.motd : Disable Default motd --------------------------------- 0.93s
stephrobert.motd : Copy config to Disable Motd in sshd ------------------ 0.87s
Change Hostname --------------------------------------------------------- 0.85s
stephrobert.motd : Configure New Dynamic Motd in PAM -------------------- 0.82s
stephrobert.motd : Copy new dynamic motd -------------------------------- 0.73s
Add IP address of all hosts to all hosts | 80.247.5.193 ----------------- 0.52s
Add IP address of all hosts to all hosts | 80.247.5.193 ----------------- 0.19s
mercredi 24 janvier 2024 08:49:31 +0100 (0:00:00.815) 0:00:38.330 ******
===============================================================================
ansible.builtin.apt ---------------------------------------------------- 30.58s
stephrobert.motd -------------------------------------------------------- 4.46s
gather_facts ------------------------------------------------------------ 1.72s
ansible.builtin.hostname ------------------------------------------------ 0.85s
ansible.builtin.lineinfile ---------------------------------------------- 0.71s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
total ------------------------------------------------------------------ 38.32s

On se connecte à notre bastion :

Terminal window
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.174
outscale@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 :

Terminal window
├── 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 :

Terminal window
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 :

Terminal window
cd ../../playbooks
ansible-playbook provision.yml --limit test

On teste la connexion :

Terminal window
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 :

Terminal window
sudo apt install -y nginx

On teste que le port 80 est bien démarré :

Terminal window
ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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 and
working. 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 :

Terminal window
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.