Infrastructure As Code sous AWS avec Terraform (publié dans GLMF 216)

Infrastructure As Code sous AWS avec Terraform

Dans cet article, je vous propose de plonger au coeur de Terraform, outil permettant d’automatiser le déploiement d’infrastructures complètes sur des plateformes IaaS avec un langage de description simple et flexible.

1 Infrastructure As Code

Ces dernières années, de nombreux outils sont apparus visant à rapprocher les méthodes de travail des développeurs de celles des sysadmins dans le but d’automatiser, de simplifier et de sécuriser les déploiements. Des outils comme Puppet, Salt, Chef ou Ansible ont ainsi rapidement émergé pour l’administration des systèmes d’exploitation et des applications.
Alors que le Cloud et la généralisation des API s’est développée, sont apparus cette fois des outils similaires pour l’orchestration de l’infrastructure socle. Plutôt que de déployer manuellement, nous allons pouvoir travailler avec des documents textuels décrivant l’état du système souhaité, intégrables dans des systèmes de gestion de version de type GIT et bénéficier des avantages qui s’ensuivent.
Terraform s’inscrit dans cette démarche en fournissant des interfaces vers plusieurs Cloud : Azure, Google, OpenStack, AWS, etc. C’est ce dernier que je vais utiliser pour la démonstration. Il sera donc nécessaire de disposer d’un compte AWS, sachant qu’un compte gratuit est suffisant pour explorer l’ensemble des éléments que nous allons parcourir. Qui dit interface ne dit cependant pas abstraction, chaque cloud (provider) possède ses caractéristiques et Terraform ne propose pas de passer de l’un à l’autre sans réécriture massive du code.
Alors que des outils tels que Puppet ou Ansible disposent de modules vers plusieurs cloud publics, leur finalité est tournée vers la gestion de configuration. Terraform ou CloudFormation (l’outil proposé par Amazon) sont orientés orchestration d’infrastructures et s’acquitteront mieux de la tâche. Ainsi, il n’est pas nécessaire dans le code d’écrire les dépendances entres les éléments, ce que l’on ferait avec Puppet par exemple. C’est l’orchestrateur de Terraform qui se charge de réaliser le graph de séquencement à votre place.

2 Déploiement d’une première machine virtuelle

2.1 Préparation de l’environnement

L’installation de Terraform ne saurait être plus simple. En effet, Terraform est écrit en Go et est distribué sous la forme d’un binaire compilé en statique. Il suffit de télécharger le zip de l’application et une fois celui-ci extrait, s’assurer que le binaire se trouve bien dans le PATH :
curl -O https://releases.hashicorp.com/terraform/0.11.2/terraform_0.11.2_linux_amd64.zip
unzip terraform_0.11.2_linux_amd64.zip
cp terraform /usr/local/bin
Afin de pouvoir dialoguer avec l’API AWS, il est nécessaire d’avoir préalablement créé un compte Programmatic Access sur le portail AWS. Nous allons ensuite pouvoir définir dans un fichier main.tf les credentials obtenus pour la région souhaitée, pour ma part il s’agit de la région eu-west-3. Une région est une zone géographique dans laquelle l’infrastructure sera déployée. Une région dispose elle-même de plusieurs datacenter regroupés en zones de disponibilité. Bien répartir une infrastructure au sein des zones de disponibilité permet de disposer du meilleur taux de disponibilité possible. La région eu-west-3 correspond à la nouvelle région France. Cela va sans dire, ce fichier doit être protégé au plus haut niveau.

provider "aws" {
  access_key = "ABCD1234J54PXLDF4IC4WMVA"
  secret_key = "28prpojfngldfgPcgiv79Q/J+8o7ksdfsTjmmE2QQBRa"
  region     = "eu-west-3"
}

On va enfin pouvoir lancer la commande terraform init qui va permettre d’instancier notre environnement et indiquer à Terraform qu’il aura besoin du plugin aws

terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.7.1)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 1.7"
[...]

2.2 Instanciation d’une VM :

Une VM AWS est une ressource de type Instance EC2. Pour déployer une VM dans cet environnement, il suffit de déclarer trois éléments, un nom, un modèle de dimensionnement de serveur et enfin un modèle de système d’exploitation. Voici donc à quoi ressemble notre fichier hosts.tf contenant ces éléments élémentaires :

resource "aws_instance" "srv1" {
  ami = "ami-8ee056f3"
  instance_type = "t2.micro"
  tags {
    Name = "srv1"
  }
}

L’étape suivante consiste à générer le fichier de plan. Terraform va entreprendre de lister les étapes nécessaires pour arriver à l’état décrit dans les fichiers de configuration.

$ terraform plan -out=terraform.plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.srv1
      id:                           
      ami:                          "ami-8ee056f3"
[...]
      instance_type:                "t2.micro"
[...]

Plan: 1 to add, 0 to change, 0 to destroy.

A ce stade, Terraform n’a encore poussé aucune modification sur le tenant AWS. La génération du plan est justement l’étape de revue des changements avant validation en production ou intégration au système de gestion de version. En cas de modification concurrente, jouer un plan plutôt que d’appliquer les configurations en cours permet de s’assurer de ce qui sera précisément déroulé. Pour déployer les changements il faut appliquer le plan :

$ terraform apply terraform.plan
aws_instance.srv1: Creating...
  ami:                          "" => "ami-8ee056f3"
  [...]
aws_instance.srv1: Still creating... (10s elapsed)
aws_instance.srv1: Creation complete after 15s (ID: i-06d8b2e3fcd42e3c6)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Voilà, en seulement 15 lignes de code, nous nous sommes connectés à notre infrastructure AWS et nous avons créé notre premier serveur.

2.3 Connexion SSH :

Notre VM est déployée dans notre cloud AWS, elle dispose d’une adresse IP publique, toutefois, il ne nous est pas possible encore de nous y connecter. Il nous manque en effet deux choses, une règle de firewall dans notre Security Group AWS autorisant les connexions SSH ainsi qu’une clé RSA pour l’authentification par clé publique. Il faut savoir que dans une infrastructure AWS, le filtrage est réalisé au niveau de l’enveloppe de la machine virtuelle via le Security Group. Un Security Group contient une ou plusieurs règles de filtrage et le même groupe peut être affecté à plusieurs instances.
Définissons donc un fichier securitygroups.tf pour autoriser le SSH entrant depuis notre adresse IP :

resource "aws_security_group" "sg_ssh" {
  name = "sg_ssh"
  description = "Permettre le SSH depuis mon IP"

  ingress {
    from_port = 22
    to_port   = 22
    protocol  = "tcp"
    cidr_blocks = ["1.2.3.4/32"]
  }
  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Dans un second fichier sshkeys.tf, nous allons indiquer notre clé publique ssh préalablement générée avec la commande ssh-keygen :

resource "aws_key_pair" "sshdeploy" {
  key_name   = "aws-ssh-key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCw/0DWBotXagD8mHGge0AcbaSIOadyJ6L65BGt99OoeRcZaYzCa2djY1dPCTUOtjbUrm7bLO8Yg7YePivJEEbnke2SbEJ8g2ClJ5tpmVV84v7TyVAcU1EcAM81h/NLEdm9jC6bPp0I0bgiTNtqypPZNPRN9V5HPG9xmHpotg05irkbhtGiNRQ3hmFsFOKkvz4Ch27s8oFIYyy0juUxWnqByV5XfW32rcBt5u4UkKGdMNi/KXlHJPCkp6WgZsTFuIiHi0RgHrRQUuKn5i+0lKePKtlb7fS2IukKLVfrJwc76AxfJ0C9UZj4KRcd/dtu6Wtt4oMRKgQxqRyjpfkGfT9j julien@private"
}

Notre clé publique SSH est notre Security Group sont des définitions de ressources réutilisables dans plusieurs composants EC2. Nous allons désormais les appliquer à l’instance précédemment déployée. Le fichiers hosts.tf devient donc :

resource "aws_instance" "srv1" {
  ami = "ami-8ee056f3"
  instance_type = "t2.micro"
  key_name = "aws-ssh-key"

  tags {
    Name = "srv1"
  }
  security_groups = ["${aws_security_group.sg_serveurs.name}"]
}

Relançons notre plan pour voir comment Terraform se comporte :

terraform plan -out=terraform.plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aws_instance.srv1: Refreshing state... (ID: i-06d8b2e3fcd42e3c6)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
-/+ destroy and then create replacement

Terraform will perform the following actions:

-/+ aws_instance.srv1 (new resource required)
      id:                                    "i-06d8b2e3fcd42e3c6" =>  (forces new resource)
[...]
      security_groups.3547975260:            "" => "sg_serveurs" (forces new resource)
      security_groups.3814588639:            "default" => "" (forces new resource)
[...]

  + aws_key_pair.sshdeploy
[...]
  + aws_security_group.sg_serveurs
[...]

Plan: 3 to add, 0 to change, 1 to destroy.

------------------------------------------------------------------------

This plan was saved to: terraform.plan

To perform exactly these actions, run the following command to apply:
    terraform apply "terraform.plan"

Le plan nous apprend qui va ajouter deux éléments : les security group et la clé SSH. Il nous apprend surtout que pour pouvoir mettre en œuvre les éléments requis, il devra supprimer la VM et la recréer.
Un terraform apply plus tard, il est désormais possible de se connecter à la machine re-créée. Pour retrouver l’adresse IP de la VM nouvellement déployée pour s’y connecter, c’est simple, elle est stockée dans le fichier tfstate :

$ grep public_ip terraform.tfstate
                            "associate_public_ip_address": "true",
                            "public_ip": "52.47.127.27",

Le compte utilisateur à utiliser se nomme ec2-user qui est le compte par défaut :

$ ssh ec2-user@52.47.127.27
The authenticity of host '52.47.127.27 (52.47.127.27)' can't be established.
ECDSA key fingerprint is SHA256:Vct9yEmZjaLCHHI/Da3sfQkQ1uS+oKN2jmh/HYCFI/A.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.47.127.27' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
1 package(s) needed for security, out of 1 available
Run "sudo yum update" to apply all updates.

2.4 Ajout d’un volume à notre instance

L’ajout d’un volume ESB (Elastic Block Storage) se fait en deux étapes. Premièrement, il faut créer le volume avec la volumétrie attendue et dans une seconde déclaration, il faut attacher ce volume à l’instance EC2. Ajoutons donc ces lignes à notre fichiers hosts.tf :

resource "aws_volume_attachment" "ebs_att" {
  device_name = "/dev/xvdb"
  volume_id   = "${aws_ebs_volume.www_srv1.id}"
  instance_id = "${aws_instance.srv1.id}"
}

resource "aws_ebs_volume" "www_srv1" {
  availability_zone = "${aws_instance.example.availability_zone}"
  size              = 10
}

Il suffit de lancer les commandes terraform plan et terraform apply décrites plus haut pour lancer le déploiement.
Pour l’attachement du volume, Terraform est capable de récupérer son id via une variable qu’il définit (aws_ebs_volume.www_srv1.id) obtenue depuis le type de ressource et le nom de celle-ci.
Il est également possible d’attacher un volume directement dans une ressource aws_instance :

  ebs_block_device {
    device_name = "/dev/xvdb"
    volume_size = 10
  }

Toutefois, dans cette configuration, Terraform va procéder à une nouvelle création de la ressource aws_instance au lieu de créer une ressource EBS et de faire un appel à Attach pour la lier à l’instance. Il est donc important de vérifier ce que le plan va réaliser sur celle-ci.

2.5 Ajout de la post installation

A l’issue du déploiement de l’instance, AWS permet d’exposer un script de post-installation via l’attribut user_data qui est passé à la VM lors de son premier démarrage via le service de metadonnées. Celui-ci est accédé par l’instance via une requête sur l’adresse http://169.254.169.254/latest/meta-data/. Ces scripts permettent de manière non exhaustive de réaliser les tâches suivantes :
– mise à jour du système
– déploiement d’un agent de gestion de configuration type Puppet ou Chef
– installation de paquets
– création de comptes utilisateurs
– configurer le gestionnaire de paquets
– commandes shell, etc…
Deux modes opératoires sont supportés, un simple script bash conventionnel ou Cloud Init qui permet d’exploiter un langage de description de plus haut niveau et donc de s’affranchir jusqu’à un certain point de la distribution. Inconvénient cependant, certaines commandes peuvent faire doublon avec Puppet ou Chef. Petit bémol avec AWS, l’instance Cloud Init proposée n’embarque pas l’ensemble des fonctionnalités de l’outil.
Je vous propose à titre d’illustration ce court exemple qui met à jour les packages et installe quelques paquets élémentaires.

#cloud-config

# Mise à jour des packages
repo_update: true
repo_upgrade: true

packages:
 - screen
 - htop
 - httpd

runcmd:
 - service httpd start
 - chkconfig httpd on

On ajoute également dans la déclaration de notre ressource aws_instance une indication précisant que le service de metadonnées doit être contacté. La déclaration de l’instance devient donc :

resource "aws_instance" "srv1" {
  ami = "ami-8ee056f3"
  instance_type = "t2.micro"
  key_name = "aws-ssh-key"
  tags {
    Name = "srv1"
  }
  security_groups = ["${aws_security_group.sg_serveurs.name}"]
  user_data = "${file("postinstall.yml")}"
}

Naturellement, il faudra passer par le couple terraform plan et terraform apply déployer une nouvelle instance disposant de cette post install. Afin d’identifier une anomalie à la post installation, il est possible de rajouter cette ligne dans le fichier afin de générer un log :

output : { all : '| tee -a /var/log/cloud-init-output.log' }

2.6 Fin de partie

L’une des fonctionnalités essentielles de Terraform réside dans sa capacité à gérer l’ensemble du cycle de vie du projet décrit dans les configurations. Nous avons vu précédemment les étapes de provisionning et de mise à jour, la dernière phase du cycle de vie consiste à détruire l’infrastructure lorsqu’elle n’est plus utilisée. Comme Terraform maintient la liste des ressources qu’il gère il est simple pour lui de construire le graphe de suppression de celles-ci. De plus, Terraform ne touchera que les ressources qu’il gère directement.
Contrairement à un outil comme Chef ou Puppet qui va demander de positionner une condition de suppression sur chacune des ressources gérées, Terraform gère les actions au niveau global via une simple commande (nécessitant heureusement une confirmation) terraform destroy :

$ terraform destroy
[...]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - aws_ebs_volume.www_srv1

  - aws_instance.srv1

  - aws_key_pair.sshdeploy

  - aws_security_group.sg_serveurs

  - aws_volume_attachment.ebs_att


Plan: 0 to add, 0 to change, 5 to destroy.

Do you really want to destroy?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes
[...]
Destroy complete! Resources: 5 destroyed.

3 Une infrastructure complète

Jusqu’ici nous avons joué avec une unique machine virtuelle afin d’effleurer les possibilités de l’outil. Voyons au travers d’un projet plus réaliste et pertinent l’utilisation de cet outil. Je vous propose ainsi de mettre en œuvre un réseau dédié hébergeant deux serveurs web, un load balancer frontal, la gestion du DNS et enfin une base de données MariaDB en mode Database As A Service.

3.1 Mettons en place le réseau

Pour ce nouveau projet, nous allons conserver nos deux fichiers main.tf et sshkeys.tf. Je tiens à préciser que les noms de fichiers que j’ai donné jusqu’ici n’ont rien d’impératif mais sont là pour donner un semblant d’organisation. Commençons par déclarer un VPC, pour Virtual Private Cloud, c’est à dire un réseau isolé et dédié, avec une passerelle par défaut pour les instances qui seront déployées par la suite.

resource "aws_vpc" "vpc_main" {
  cidr_block = "172.16.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true
}
resource "aws_internet_gateway" "default" {
  vpc_id = "${aws_vpc.vpc_main.id}"
}
resource "aws_route" "wan_access" {
  route_table_id          = "${aws_vpc.vpc_main.main_route_table_id}"
  destination_cidr_block  = "0.0.0.0/0"
  gateway_id              = "${aws_internet_gateway.default.id}"
}

Ce VPC disposera de deux réseaux que nous allons explicitement placer dans deux zones de disponibilité au sein de la région eu-west.

resource "aws_subnet" "webservers-a" {
  vpc_id                  = "${aws_vpc.vpc_main.id}"
  cidr_block              = "172.16.10.0/24"
  map_public_ip_on_launch = true
  availability_zone = "eu-west-3a"
}

resource "aws_subnet" "webservers-b" {
  vpc_id                  = "${aws_vpc.vpc_main.id}"
  cidr_block              = "172.16.20.0/24"
  map_public_ip_on_launch = true
  availability_zone = "eu-west-3b"
}

3.2 Des variables

Afin de factoriser notre code, nous allons procéder à un début de définition de variables. En pratique, il serait souhaitable de maximiser l’utilisation des variables au sein de vos configurations Terraform. Par soucis de lisibilité je ne les ai pas utilisées jusqu’ici et je les présente pour ouvrir des perspectives au lecteur.

variable "http_port" {
  description = "Port pour les requêtes HTTP"
  default = 80
}
variable "ssh_port" {
  description = "Port pour les requêtes SSH"
  default = 22
}
variable "mariadb_port" {
  description = "Port pour les connexions MariaDB"
  default = 3306
}

3.3 Déclarons deux instances

Maintenant que nos réseaux sont en place, nous allons pouvoir y positionner un serveur web sur chacun d’entre eux. Je déroge aux bonnes pratiques en affectant une adresse IP publique à ces instances. Le but étant de vous permettre d’y accéder simplement. Par soucis de simplicité, le user_data est un simple script shell.

resource "aws_instance" "www-a" {
  ami = "ami-8ee056f3"
  instance_type = "t2.micro"
  key_name = "aws-ssh-key"
  vpc_security_group_ids = ["${aws_security_group.sg_www.id}"]
  user_data = <<EOF
#!/bin/bash
yum -y update
yum -y install httpd
chkconfig httpd on
service httpd start
EOF
  subnet_id              = "${aws_subnet.webservers-a.id}"
  associate_public_ip_address = "true"
}

resource "aws_instance" "www-b" {
  ami = "ami-8ee056f3"
  instance_type = "t2.micro"
  key_name = "aws-ssh-key"
  vpc_security_group_ids = ["${aws_security_group.sg_www.id}"]
  user_data = <<EOF
#!/bin/bash
yum -y update
yum -y install httpd
chkconfig httpd on
service httpd start
EOF
  subnet_id              = "${aws_subnet.webservers-b.id}"
  associate_public_ip_address = "true"
}

resource "aws_security_group" "sg_www" {
  name = "sg_www"
  vpc_id = "${aws_vpc.vpc_main.id}"
  ingress {
    from_port = "${var.http_port}"
    to_port   = "${var.http_port}"
    protocol  = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port = "${var.ssh_port}"
    to_port   = "${var.ssh_port}"
    protocol  = "tcp"
    cidr_blocks = ["1.2.3.4/32"]
  }

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

3.4 Ajoutons un équilibreur de charge

Enfin, on peut déclarer notre équilibreur de charge ou Elastic Load Balancer en langage AWS. Il est raccroché aux deux sous-réseaux avec nos deux instances précédentes en backend.

resource "aws_elb" "http-lb" {
  name = "http-lb"
  subnets = ["${aws_subnet.webservers-a.id}","${aws_subnet.webservers-b.id}"]
  security_groups = ["${aws_security_group.sg_elb.id}"]
  instances       = ["${aws_instance.www-a.id}", "${aws_instance.www-b.id}"]
  listener {
    lb_port = "${var.http_port}"
    lb_protocol = "http"
    instance_port = "${var.http_port}"
    instance_protocol = "http"
  }

  health_check {
    healthy_threshold = 2
    unhealthy_threshold = 2
    timeout = 3
    interval = 5
    target = "TCP:${var.http_port}"
  }
}
resource "aws_security_group" "sg_elb" {
  name = "sg_elb"
  vpc_id = "${aws_vpc.vpc_main.id}"

  ingress {
    from_port = "${var.http_port}"
    to_port   = "${var.http_port}"
    protocol  = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  lifecycle {
    create_before_destroy = true
  }
}

Comme vous avez dû vous en rendre compte, les instances sont raccrochées à un sous domaine AWS, dans mon cas eu-west-3.compute.amazonaws.com. Plutôt qu’aller ouvrir le portail EC2 ou utiliser la peu appropriée commande grep que je vous ai fait utiliser précédemment, nous allons utiliser la fonctionnalité des outputs. Ainsi, à l’issue de l’instanciation de la ressource ELB, nous allons afficher son FQDN afin de pouvoir valider simplement le bon fonctionnement.

output "elb_dns_name" {
  value = "${aws_elb.http-lb.dns_name}"
}

A ce stade, si vous appliquez votre plan, l’accès à l’adresse renvoyée par Terraform doit déjà vous permettre d’afficher une belle « Amazon Linux AMI Test Page ».

3.5 Du DNS as a Service

Le DNS AWS c’est plutôt sympathique mais dans la vie réelle, les utilisateurs accèdent aux ressources avec un nom de domaine personnalisé. AWS propose un service de DNS as a Service appelé Route53. Il existe une notion de DNS privé pour les ressources internes du VPC ou de DNS public pour les ressources exposées. Nous allons déclarer le sous-domaine aws.domaine.fr à adapter avec votre propre domaine et déclarer des enregistrements de ressources sur chacune des adresses IP de notre infrastructure. Bien évidemment, pour que ce soit fonctionnel, il faut également déclarer une délégation DNS dans la zone supérieure vers les DNS AWS ce qui sort du cadre de cet article.

resource "aws_route53_zone" "publicdns" {
  name         = "aws.domaine.fr"
}

resource "aws_route53_record" "www-a" {
  zone_id = "${aws_route53_zone.publicdns.zone_id}"
  name    = "www-a.aws.domaine.fr"
  type    = "A"
  ttl = "60"
  records = ["${aws_instance.www-a.public_ip}"]
}

resource "aws_route53_record" "www-b" {
  zone_id = "${aws_route53_zone.publicdns.zone_id}"
  name    = "www-b.aws.morot.fr"
  type    = "A"
  ttl = "60"
  records = ["${aws_instance.www-b.public_ip}"]
}

resource "aws_route53_record" "www" {
  zone_id = "${aws_route53_zone.publicdns.zone_id}"
  name = "www.aws.domaine.fr"
  type = "CNAME"
  ttl = "60"
  records = ["${aws_elb.http-lb.dns_name}"]
}

3.6 Et enfin notre base de données

L’application web déployée sur nos deux instances utilisera une base de données et par conséquent nous allons utiliser le service AWS Relational Database as a Service ou RDS.
L’utilisation du service RDS implique trois ressources, la déclaration des subnet groups dans lesquels seront rattachés vos instances RDS, par simplicité je réutilise les réseaux des instances mais de manière optimale il faudrait dédier un subnet.

resource "aws_db_subnet_group" "rds_subnet" {
    name = "rds_subnet_grpp"
    subnet_ids = ["${aws_subnet.webservers-a.id}","${aws_subnet.webservers-b.id}"]
}

Nous déclarons ensuite notre base de données à proprement parler avec les identifiants d’accès et son type. Une particularité est le final snapshot. C’est à dire qu’à la suppression de la base de données RDS, AWS réalisera un instantané de celle-ci pouvant être restauré. Ce snapshot n’est donc pas supprimé par Terraform.

resource "aws_db_instance" "myawsdb" {
  allocated_storage    = 20
  engine               = "mariadb"
  engine_version       = "10.1.26"
  instance_class       = "db.t2.micro"
  name                 = "myawsdb"
  username             = "user"
  password             = "password"
  db_subnet_group_name = "${aws_db_subnet_group.rds_subnet.id}"
  skip_final_snapshot = false
  multi_az = true
  final_snapshot_identifier = "myawsdbfinalsnap"
  vpc_security_group_ids = ["${aws_security_group.sg_rds.id}"]
}

Et enfin, nous avons notre security group pour autoriser le port TCP/3306 depuis l’ensemble des subnets de notre VPC.

resource "aws_security_group" "sg_rds" {
  name = "sg_rds"
  vpc_id = "${aws_vpc.vpc_main.id}"
  ingress {
    from_port = "${var.mariadb_port}"
    to_port   = "${var.mariadb_port}"
    protocol  = "tcp"
    cidr_blocks = ["172.16.0.0/16"]
  }
  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  lifecycle {
    create_before_destroy = true
  }
}

3.7 Etape finale !

Voilà, nous avons progressivement construit notre infrastructure. Que vous ayez tout décrit dans un fichier dédié ou éclaté la configuration n’a pas d’importance mais juste une bonne pratique pour améliorer la lisibilité sur des infrastructures importantes.
Il vous reste juste à lancer les commandes connues jusqu’ici :

terraform init
terraform plan -out=terraform.plan
terraform apply terraform.plan

Pensez à l’issue de l’expérimentation à faire un destroy pour ne pas être facturé au-delà de ce qui est permis dans l’utilisation du compte gratuit AWS.

Conclusion

J’espère que vous aurez apprécié la puissance d’un tel outil. Dans la continuité de cet article, je vous propose de poursuivre avec les auto scaling groups afin de permettre à votre infra d’évoluer automatiquement pour suivre les besoins de montée en charge, exploiter une supervision CloudWatch, un VPN site à site vers votre VPC et pousser une NAT Gateway pour supprimer les adresses IP publiques inutiles ou encore faire cohabiter votre outil de gestion de configuration à l’issue de la post-installation.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.