Déployer son infrastructure Google Cloud Platform grâce à Terraform
Comment gérer son infrastructure dans un contexte cloud ? Hashicorp répond à cette problématique via Terraform. Nous vous proposons de découvrir cet outil avec une mise en application sur Google Cloud Platform.
Présentation de Terraform
Terraform est un outil open source édité par Hashicorp, qui s'inscrit dans la mouvance Infrastructure as Code. Il permet de décrire votre infrastructure et les services qui gravitent autour sous la forme de fichiers de configuration. Créé en 2014, Terraform est jeune, mais très activement développé. Orienté cloud providers (Amazon Web Services, Google Cloud Platform, ...) à ses débuts, Terraform s'est peu à peu étoffé, pour aujourd'hui proposer de configurer d'autres types de services. Vous pouvez par exemple configurer vos comptes sur Github et les associer à vos organisations, gérer vos données MySQL, vos entrées Dyn… La liste complète se trouve bien évidemment sur leur documentation en ligne.
Liste non exhaustive des providers supportés par Terraform
L'une des forces de Terraform est de centraliser tous ces providers dans un outil unique, et surtout, d'en décrire leur configuration seulement. Cela limite l'aspect "plomberie" d'un script bash qui pourrait remplir le même usage. Le descripteur de ressources utilise le Hashicorp Configuration Language, permettant de profiter des spécificités de chaque provider, même s'ils proposent le même type de service. L'idée ici n'est pas d'offrir une abstraction unique, mais bien une meilleure manière de faire.
Terraform utilise le pattern "ressource" et se soucie de l'idempotence : on décrit l'état cible souhaité, il génère un graphe entre le code et l'état de l'infrastructure. Si aucun delta n'est détecté, il n'appliquera rien. Si la ressource n'existe pas, elle sera créée. Si une configuration est incorrecte, elle sera modifiée pour aller dans le sens du fichier descripteur.
Les ressources à créer sont rassemblées dans des "topologies", qui sont des ensembles cohérent de ressources qui peuvent s'instancier ensemble. Par exemple nous pouvons avoir une topologie pour le réseau et une autre pour les instances.
L'application d'une topologie Terraform se fait en 3 phases :
- $ terraform plan Cette commande permet de visualiser le plan d'exécution (les actions qui seront faites) sans les appliquer (mode Dry Run).
- $ terraform apply Permet d'appliquer le plan d'exécution en créant, mettant à jour ou supprimant les éléments de l'infrastructure gérés.
- $ terraform destroy Permet de supprimer les éléments d'infrastructure gérés par Terraform
Sous le capot, quand la topologie est appliquée, Terraform crée un fichier d'état appelé tfstate. Cet état peut être stocké en local, mais aussi sur Amazon S3, Google Cloud Storage, Microsoft Azure Storage ou encore etcd … (https://www.terraform.io/docs/state/remote).
Lors d'un terraform plan ou apply, Terraform va utiliser ce fichier pour récupérer les états supposés des éléments, mais surtout, leurs ID techniques à partir desquels il est possible de rafraîchir les différents attributs de ces objets , etc...
Le Cloud selon Google
Pour illustrer Terraform, nous allons déployer une application simple : un serveur web derrière un firewall. La plateforme cloud sur laquelle nous allons provisionner (= le provider) notre application sera Google Cloud Platform.
Google Cloud Platform est le service de cloud lancé par Google en 2011. Il se positionne en tant que IaaS en permettant d'instancier des ressources à la demande (Réseau, Instance, Firewall …), mais possède également une offre PaaS (Google App Engine), Big Data, de conteneurs ...
Mis à part quelques différences dans les concepts et le design, les services proposés par Google Cloud Platform sont des services IaaS classiques. Les différences vont se trouver sur les services de plus haut niveau, comme le Big Data, le Machine Learning, ou encore l'hébergement de conteneurs, qui se fait par exemple sur une stack Kubernetes sur Google Cloud, alors que AWS propose une implémentation très "vanilla" d'un orchestrateur de conteneurs (ECS).
Mise en place d'un réseau et d'une Instance sur Google Cloud Platform
Nous allons déployer une topologie composée :
- d'un "network"
- d'un "subnetwork"
- d'un "firewall" laissant les ports 80 et 22 ouverts en tcp
- d'une "instance" avec un Startup Script installant nginx
Commençons par créer un Compte de Service : https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances Lorsque le fichier key.json est récupéré, nous l'exportons en variable d'environnement :
export GOOGLE_PROJECT="<your_project_id>" export GOOGLE_CLOUD_KEYFILE_JSON=$(cat <your_json_key_file>)
Ce fichier nous permet d'interagir avec les APIs de Google Cloud Platform en étant identifié avec un compte de service. Nous créons un fichier topology.tf contenant la description minimale de notre provider :
provider "google" { region = "europe-west1" }
Pour créer des objets dans notre infrastructure, nous allons créer dans le langage Terraform des ressources. Une ressource Terraform correspond donc à un objet de la Google Cloud Platform (par exemple un network ou une instance).
Nous pouvons d'ores et déjà faire un terraform plan :
$ 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
No changes. Infrastructure is up-to-date. This means that Terraform could not detect any differences between your configuration and the real physical resources that exist. As a result, Terraform doesn't need to do anything.
La sortie nous indique qu'il n'y a pas de changements à faire sur l'infrastructure, ce qui est normal puisque nous n'avons pas créé de ressource.
resource "google_compute_network" "default" { name = "custom-network1" auto_create_subnetworks = "false" }
Nous pouvons voir ici la structure d'une ressource sous Terraform :
- "google_compute_network" : est le nom de la ressource de l'objet Terraform qui correspond à un réseau sur Google Cloud Platform
- "default" : est le nom de l'instanciation de notre objet
- les arguments
Nous souhaitons gérer nous-même la création des "subnetworks" d'où le paramètre "auto_create_subnetworks" à false.
Lorsque nous faisons le plan, Terraform nous informe qu'il créera le Network si nous appliquons la topologie :
+ google_compute_network.default auto_create_subnetworks: "false" gateway_ipv4: "" name: "custom-network1" self_link: " Plan: 1 to add, 0 to change, 0 to destroy.
Maintenant nous allons créer le sous-réseau avec une plage d'adresses IP en 192.168.0.0/24 associé au réseau créé précédement :
resource "google_compute_subnetwork" "subnet1" { name = "subnet1" ip_cidr_range = "192.168.0.0/24" network = "${google_compute_network.default.self_link}" }
On peut ici voir l'utilisation d'une variable : "${google_compute_network.default.self_link}". Elle fait référence au network créé dans l'étape précédente.
Nous avons donc la coquille : le réseau et le sous-réseau ; il nous manque les firewalls que nous allons associer à l'instance.
Les firewalls (pare-feu) se posent au niveau du réseau. Chaque règle va permettre d'autoriser en sortie ou en entrée des flux sur des ports spécifiques. Nous souhaitons que seules les instances avec le tag "nginx" soient accessibles sur les ports 80 et 22 :
resource "google_compute_firewall" "http-nginx" { name = "http-nginx" network = "${google_compute_network.default.name}" target_tags = ["nginx"] source_ranges = ["0.0.0.0/0"]
allow { protocol = "tcp" ports = ["80"] } }
resource "google_compute_firewall" "ssh-nginx" { name = "ssh-nginx" network = "${google_compute_network.default.name}" target_tags = ["nginx"] source_ranges = ["0.0.0.0/0"]
allow { protocol = "tcp" ports = ["22"] } }
Point intéressant, nous ouvrons les ports 80 et 22 seulement sur les instances portant le tag "nginx", via l'attribut target_tags. Ainsi si nous ajoutons a posteriori des instances avec ce tag, elles auront immédiatement ces ports ouverts.
Avant de créer l'instance, il nous faut préparer un script Shell pour installer nginx, qui sera exécuté à chaque démarrage.
Créer le fichier install-nginx.sh, contenant :
#!/bin/bash sudo apt-get update sudo apt-get -y install nginx
Notre configuration indiquera qu'il doit être lancé au démarrage de la VM. Nous pouvons maintenant créer l'instance :
resource "google_compute_instance" "nginx" { name = "nginx" machine_type = "n1-standard-1" tags = ["nginx"] metadata_startup_script = "${file("./install-nginx.sh")}" disk { image = "debian-cloud/debian-8" }
zone = "${var.zone}" network_interface { subnetwork = "europe-west1-d" access_config { // Ephemeral IP : The Ip address will be available until the instance is alive, when the stack is destroy and apply again, the public Ip is a new one } } }
L'image source est la "debian-cloud/debian-8". On attribue le tag "nginx" à l'instance pour que les firewalls donnent accès sur les ports ssh et http.
Nous souhaitons avoir en sortie de la topologie l'adresse publique de l'instance. Nous allons utiliser un output qui pointe sur un attribut de l'interface réseau de l'instance.
output "nginx_public_ip" { value = "${google_compute_instance.nginx. network_interface.0.access_config.0.assigned_nat_ip}" }
Si vous faites "terraform apply" votre topologie sera déployée avec les différents objets et en sortie l'IP de l'instance.
$ Ouputs:
nginx_public_ip = 35.187.71.62
Et voilà ! Notre application (Nginx) est désormais provisionnée, prête à l'emploi !
N'oubliez pas de faire "terraform destroy" pour supprimer les ressources une fois que vous avez fini.
Dans notre exemple, Terraform nous a permis de déployer un environnement simple dans un langage compréhensible et partageable, par exemple sur un dépôt Git (donc versionable). Sans Terraform, nous aurions dû passer par l'interface graphique ou utiliser un script Bash. Cependant l'outil nous apporte une abstraction non négligeable et simplifie l'écriture. Le cycle de vie de l'infrastructure peut alors être versionné en tant que code. La gestion du patrimoine de code Terraform doit ensuite être pensée comme du Code avec toutes les bonnes pratiques du Software Craftmanship : Tests, Peer reviews, ...
Nous avons vu ici un cas simple, Terraform offre la possibilité de manipuler des objets plus complexes, la mise en place de plusieurs topologies ainsi qu'un DSL étoffé avec des notions de variables, de modules, de fonctions (par exemple la gestion des listes, des maps)...