Jan 17 2022
Ressource Partagée Terraform par l’Exemple: les Clés SSH
Terraform permet d’automatiser la création d’infrastructures dans le cloud, ce qu’on appelle communément l’infrastructure as code. Il nous faut créer une machine virtuelle, laquelle doit contenir les clés SSH de 3 administrateurs. Le but sera de faire en sorte que cette ressource partagée Terraform soit réutilisable par d’autres modules. Cet exemple sur le Cloud IBM s’appuie sur le plugin IBM pour Terraform mais la méthode reste valide pour les autres cloud providers évidemment.
Je n’ai pas mis la création du VPC, des subnets et security groups pour gagner en lisibilité.
Ressources dans un Module Unique
Commençons par créer les fichiers ssh.tf contenant le code créant les clés des administrateurs, et vm.tf le code de création du serveur dans un même répertoire. Les clés sont données en paramètre à la VM.
resource "ibm_is_ssh_key" "user1_sshkey" {
name = "user1"
public_key = "ssh-rsa AAAAB3[...]k+XR=="
}
resource "ibm_is_ssh_key" "user2_sshkey" {
name = "user2"
public_key = "ssh-rsa AAAAB3[...]Zo9R=="
}
resource "ibm_is_ssh_key" "user3_sshkey" {
name = "user3"
public_key = "ssh-rsa AAAAB3[...]67GqV="
}
resource "ibm_is_instance" "server1" {
name = "server1"
image = var.image
profile = var.profile
vpc = ibm_is_vpc.vpc.id
zone = var.zone1
primary_network_interface {
subnet = ibm_is_subnet.subnet1.id
security_groups = [ibm_is_vpc.vpc.default_security_group]
}
keys = [
ibm_is_ssh_key.user1_sshkey.id,
ibm_is_ssh_key.user2_sshkey.id,
ibm_is_ssh_key.user3_sshkey.id
]
}
Le code est simple mais pose un problème majeur:
Les clés SSH ne sont pas réutilisables dans un autre module Terraform. Si on copie/colle ce code pour créer une 2me VM, une erreur indiquera que les clés existent déjà. L’ajout d’une clé nécessite de modifier les 2 fichiers Terraform.
Ressources Communes Terraform
Il faut donc créer les clés SSH dans un module Terraform indépendant et les rendre accessibles depuis les autres modules. On peut y parvenir en exportant les id des clés grâce aux valeurs outputs. Les outputs permettent de rendre des variables accessibles en ligne de commande ou à d’autres modules Terraform pour les réutiliser.
Déplaçons la déclaration des clés dans un nouveau répertoire Terraform auquel nous ajoutons une sortie output ssh_keys qui renvoie un tableau de leurs id, puisque c’est ce qu’attendent les VM en paramètre.
resource "ibm_is_ssh_key" "user1_sshkey" {
name = "user1"
public_key = "ssh-rsa AAAAB3[...]k+XR=="
}
resource "ibm_is_ssh_key" "user2_sshkey" {
name = "user2"
public_key = "ssh-rsa AAAAB3[...]Zo9R=="
}
resource "ibm_is_ssh_key" "user3_sshkey" {
name = "user3"
public_key = "ssh-rsa AAAAB3[...]67GqV="
}
output "ssh_keys" {
value = [
ibm_is_ssh_key.user1_sshkey.id,
ibm_is_ssh_key.user2_sshkey.id,
ibm_is_ssh_key.user3_sshkey.id
]
}
Après avoir lancé terraform apply, on peut afficher les valeurs output avec terraform output:
$ terraform output
ssh_keys = [
"r010-3e98b94b-9518-4e11-9ac4-a014120344dc",
"r010-b271dce5-4744-48c3-9001-a620e99563d9",
"r010-9358c6ab-0eed-4de7-a4a0-4ba20b2c04c9",
]
C’est exactement ce que nous voulions. Ne reste qu’à récupérer le contenu de l’output sous forme de data lookup pour l’exploiter dans le module VM.
data "terraform_remote_state" "ssh_keys" {
backend = "local"
config = {
path = "../ssh_keys/terraform.tfstate"
}
}
resource "ibm_is_instance" "server1" {
name = "server1"
image = var.image
profile = var.profile
primary_network_interface {
subnet = ibm_is_subnet.subnet1.id
security_groups = [ibm_is_vpc.vpc.default_security_group]
}
vpc = ibm_is_vpc.vpc.id
zone = var.zone1
keys = data.terraform_remote_state.ssh_keys.outputs.ssh_keys
}
C’est beaucoup mieux, on est capable de gérer les clés SSH indépendamment des autres modules Terraform et de les réutiliser comme bon nous semble. Le path du data lookup est le chemin relatif vers le répertoire contenant le fichier ssh.tf.
Variables sous Forme de Liste
C’est pas mal mais on pourrait rendre la création des ressources partagées (ici les clés SSH) plus élégante.
En effet, l’ajout d’une nouvelle clé se fait à 2 endroits: créer une ressource Terraform, et l’ajouter aux valeurs retournées dans l’output. Ce qui est contraignant et générateur d’erreurs.
De plus, ca reste assez difficile à lire et il serait plus clair de séparer valeurs et code.
Pour cela, nous allons stocker les clés dans une table de type map dans un fichier terraform.tfvars, qui sera chargé automatiquement.
ssh_keys = {
"user1" = "ssh-rsa AAAAB3[...]k+XR=="
"user2" = "ssh-rsa AAAAB3[...]Zo9R=="
"user3" = "ssh-rsa AAAAB3[...]67GqV="
}
Dans ssh.tf, nous allons ensuite exécuter une boucle sur ce tableau de clé/valeur pour créer les ressources, et les exporter dans l’output.
# Définition du tableau
variable "ssh_keys" {
type = map(string)
}
resource "ibm_is_ssh_key" "keys" {
for_each = var.ssh_keys
name = each.key
public_key = each.value
}
output "ssh_keys" {
value = values(ibm_is_ssh_key.keys)[*].id
}
La récupération des valeurs est un peu complexe. J’ai commencé par sortir en output values(ibm_is_ssh_key.keys) pour analyser la structure et ainsi récupérer les ids.
Au final, une nouvelle ressource partagée (une clé SSH dans notre cas) se fait en l’ajoutant simplement dans un tableau, dans un fichier ne contenant que des variables. A un seul endroit. N’importe qui peut s’en charger sans même lire ou comprendre le code.