Le logiciel Terraform est aujourd’hui très utile dès que l’on souhaite faire de l’Infrastructure as Code, notamment par sa capacité à créer, mettre à jour mais aussi supprimer tout une pile applicative chez un fournisseur de services.
A l’image d’autres outils comme Ansible, Chef ou encore Puppet, Terraform utilise un langage dédié de type déclaratif pour décrire les ressources qui vont être approvisionnées.
Ce langage dédié, le HashiCorp Configuration Language (HCL) dans le cas de Terraform, peut-être vu comme une faiblesse car :
- il impose d’acquérir de nouvelles connaissances spécifiques
- il peut poser problème pour s’intégrer avec la boîte à outils des développeurs
- il n’est surtout pas aussi riche que la plupart des langages de programmation impératifs
Certains projets comme Pulumi l’ont bien compris et proposent de gérer une infrastructure avec des langages plus classiques comme JavaScript, Python ou encore Go, tout en conservant l’idempotence des solutions avec langage dédié.
HashiCorp, la société à l’origine de Terraform, vient de prendre en compte ce besoin et a annoncé sur son blog un nouveau projet : Cloud Development Kit (CDK) for Terraform
Dans cet article je vais vous présenter une petite infrastructure hébergée chez Scaleway, décrite d’abord dans le langage dédié de Terraform puis son adaptation en TypeScript à l’aide de CDK for Terraform.
Utilisation de HCL avec Terraform
Les ressources que l’on va gérer sont les suivantes :
- 2 adresses IP publiques
- 1 groupe de sécurité permettant le ping et le SSH
- 1 source de données correspondant à l’image Ubuntu Bionic, ce qui permettra de récupérer son identifiant
- 3 serveurs de type
DEV1-S
dont seuls les 2 premiers auront une adresse IP publique
Par ailleurs j’afficherais en sortie de l’exécution de Terraform les adresses IP publiques ainsi que les adresses IP privées des serveurs.
Le code source Terraform correspondant à cette infrastructure est par exemple le suivant :
provider "scaleway" {
version = "1.16.0"
}
variable "ip_count" {
default = 2
}
variable "server_count" {
default = 3
}
variable "server_type" {
default = "DEV1-S"
}
variable "server_image" {
default = "ubuntu_bionic"
}
resource "scaleway_instance_ip" "cdktf" {
count = var.ip_count
}
resource "scaleway_instance_security_group" "cdktf" {
name = "cdktf"
stateful = true
inbound_default_policy = "drop"
outbound_default_policy = "accept"
inbound_rule {
action = "accept"
protocol = "ICMP"
}
inbound_rule {
action = "accept"
protocol = "TCP"
port = 22
}
}
data "scaleway_marketplace_image_beta" "cdktf" {
label = var.server_image
}
resource "scaleway_instance_server" "cdktf" {
count = var.server_count
type = var.server_type
image = data.scaleway_marketplace_image_beta.cdktf.id
name = "cdktf-${count.index + 1}"
security_group_id = scaleway_instance_security_group.cdktf.id
ip_id = count.index < var.ip_count ? scaleway_instance_ip.cdktf[count.index].id : null
}
output "public_ips" {
value = scaleway_instance_ip.cdktf[*].address
}
output "private_ips" {
value = scaleway_instance_server.cdktf[*].private_ip
}
L’application de ce code source se fait avec les commandes terraform plan
, terraform apply
et terraform destroy
habituelles.
Utilisation de TypeScript avec CDK for Terraform
Installation de CDK for Terraform
L’utilisation de CDK for Terraform en langage TypeScript nécessite quelques pré-requis :
- Terraform évidement, dans une version >= 0.12
- Node.js pour interpréter le code TypeScript, en version >= 12.16
- Yarn pour gérer les paquets Node.js, en version >= 1.21
La première opération que nous allons lancer est l’installation globale, au niveau système, de l’outillage CDK for Terraform :
$ sudo npm install -g cdktf-cli
Préparation du projet
Nous allons ensuite initialiser notre projet CDK for Terraform en précisant, pour cet exemple, de ne pas s’interfacer avec le service en ligne Terraform Cloud :
$ mkdir cdktf-scaleway
$ cd cdktf-scaleway
$ cdktf init --template typescript --local
En plus des fichiers habituels pour un logiciel écrit en TypeScript, on retrouve dans l’arborescence du projet :
- un fichier de configuration
cdktf.json
pour CDF for Terraform, pour l’instant utilisant le provider Terraform AWS :
{
"language": "typescript",
"app": "npm run --silent compile && node main.js",
"terraformProviders": [
"aws@~> 2.0"
]
}
- un fichier d’aide
help
rappelant différentes commandes utiles au projet - un fichier de description
main.ts
de notre infrastructure, pour l’instant sans aucune ressources :
import { Construct } from 'constructs';
import { App, TerraformStack } from 'cdktf';
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
// define resources here
}
}
const app = new App();
new MyStack(app, 'cdktf-scaleway');
app.synth();
Ajout du provider Scaleway
Comme expliqué plus haut, le projet CDK for Terraform est pré-configuré avec AWS. Nous allons donc ajouter le provider Scaleway dans son fichier de configuration JSON :
{
"language": "typescript",
"app": "npm run --silent compile && node main.js",
"terraformProviders": [
"aws@~> 2.0",
"scaleway@~> 1.16"
]
}
En plus de cette configuration, il faut à présent générer les constructs
, un équivalent côté CDK des types de ressource de Terraform :
$ cdktf get
Generated typescript constructs in the output directory: .gen
Cela a pour effet de créer un fichier TypeScript pour chaque type de ressource du provider Scaleway :
$ ls .gen/providers/scaleway
account-ssh-key.ts data-scaleway-instance-security-group.ts data-scaleway-security-group.ts instance-security-group.ts lb-backend-beta.ts registry-namespace-beta.ts user-data.ts
baremetal-server.ts data-scaleway-instance-server.ts data-scaleway-volume.ts instance-server.ts lb-beta.ts scaleway-provider.ts volume-attachment.ts
data-scaleway-account-ssh-key.ts data-scaleway-instance-volume.ts index.ts instance-volume.ts lb-certificate-beta.ts security-group-rule.ts volume.ts
data-scaleway-baremetal-offer.ts data-scaleway-lb-ip-beta.ts instance-ip-reverse-dns.ts ip-reverse-dns.ts lb-frontend-beta.ts security-group.ts
data-scaleway-bootscript.ts data-scaleway-marketplace-image-beta.ts instance-ip.ts ip.ts lb-ip-beta.ts server.ts
data-scaleway-image.ts data-scaleway-registry-image-beta.ts instance-placement-group.ts k8s-cluster-beta.ts object-bucket.ts ssh-key.ts
data-scaleway-instance-image.ts data-scaleway-registry-namespace-beta.ts instance-security-group-rules.ts k8s-pool-beta.ts rdb-instance-beta.ts token.ts
Ajout du code TypeScript
Tout est prêt maintenant pour déclarer notre infrastructure dans le fichier main.ts
, par exemple de cette façon :
import { Construct } from 'constructs';
import { App, TerraformOutput, TerraformStack, Token } from 'cdktf';
import { DataScalewayMarketplaceImageBeta, InstanceIp, InstanceServer, InstanceSecurityGroup } from './.gen/providers/scaleway';
class MyStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
// define resources here
const ipCount = 2;
const serverCount = 3;
const serverType = 'DEV1-S';
const serverImage = 'ubuntu_bionic';
const ips: InstanceIp[] = [];
for (let i = 0; i < ipCount; i++) {
const ip = new InstanceIp(this, `cdktf-ip-${i}`);
ips.push(ip);
}
const securityGroup = new InstanceSecurityGroup(this, 'cdktf-security-group', {
name: 'cdktf',
stateful: true,
inboundDefaultPolicy: 'drop',
outboundDefaultPolicy: 'accept',
inboundRule: [
{
action: 'accept',
protocol: 'ICMP'
},
{
action: 'accept',
protocol: 'TCP',
port: 22
}
]
});
const image = new DataScalewayMarketplaceImageBeta(this, 'cdktf-image', {
label: serverImage
});
const servers: InstanceServer[] = [];
for (let i = 0; i < serverCount; i++) {
const server = new InstanceServer(this, `cdktf-server-${i}`, {
type: serverType,
image: Token.asString(image.id),
name: `cdktf-${i + 1}`,
securityGroupId: Token.asString(securityGroup.id),
ipId: i < ipCount ? Token.asString(ips[i].id) : undefined
});
servers.push(server);
}
new TerraformOutput(this, 'publics-ips', {
value: ips.map(ip => ip.address)
});
new TerraformOutput(this, 'private-ips', {
value: servers.map(server => server.privateIp)
});
}
}
const app = new App();
new MyStack(app, 'cdktf-scaleway');
app.synth();
On retrouve à présent l’ensemble des ressources Terraform de notre infrastructure sous forme d’instances de classes TypeScript.
On peut noter comme différences par rapport au langage HCL :
- une unicité des identifiants des ressources au niveau d’une
TerraformStack
et non plus une unicité au niveau d’un type de ressource - un remplacement de la directive
count
de HCL par une boucle TypeScript, ici de typefor
- une utilisation de la classe
Token
pour traiter le cas des variables en évaluation retardée, ce qui est le cas desid
des ressources
Pour ce dernier point qui peut être délicat, se référer à la documentation pour en savoir plus.
Gestion de notre infrastructure
La première façon d’appliquer le code source TypeScript est de lancer la commande cdktf synth
afin de de générer dans le répertoire cdktf.out
du code compatible avec les commandes terraform
habituelles.
La seconde façon, celle que je vous conseille, est de passer par les commandes équivalentes avec cdktf
.
La commande terraform plan
qui permet d’afficher le plan d’exécution de Terraform devient ainsi la commande cdktf diff
:
$ cdktf diff
⠴ initializing cdktf-scaleway...
⠧ planning cdktf-scaleway...
⠙ planning cdktf-scaleway...
Stack: cdktf-scaleway
Resources
+ SCALEWAY_INSTANCE_IP cdktfip0 scaleway_instance_ip.cdktfscaleway_cdktfip0_C3F2203D
+ SCALEWAY_INSTANCE_IP cdktfip1 scaleway_instance_ip.cdktfscaleway_cdktfip1_988D7B00
+ SCALEWAY_INSTANCE_SE cdktfsecuritygroup scaleway_instance_security_group.cdktfscaleway_cdktfsecuritygroup_C024279A
+ SCALEWAY_INSTANCE_SE cdktfserver0 scaleway_instance_server.cdktfscaleway_cdktfserver0_E94A5AA0
+ SCALEWAY_INSTANCE_SE cdktfserver1 scaleway_instance_server.cdktfscaleway_cdktfserver1_BFAA20A7
+ SCALEWAY_INSTANCE_SE cdktfserver2 scaleway_instance_server.cdktfscaleway_cdktfserver2_86C2D83C
Diff: 6 to create, 0 to update, 0 to delete.
Pour la commande terraform apply
qui permet le déploiement de notre infrastructure, c’est la commande cdktf deploy
qu’il faudra lancer :
$ cdktf deploy
⠸ initializing cdktf-scaleway...
⠴ planning cdktf-scaleway...
⠸ planning cdktf-scaleway...
⠋ Deploying Stack: cdktf-scaleway
Deploying Stack: cdktf-scaleway
Deploying Stack: cdktf-scaleway
Resources
✔ SCALEWAY_INSTANCE_IP cdktfip0 scaleway_instance_ip.cdktfscaleway_cdktfip0_C3F2203D
✔ SCALEWAY_INSTANCE_IP cdktfip1 scaleway_instance_ip.cdktfscaleway_cdktfip1_988D7B00
✔ SCALEWAY_INSTANCE_SE cdktfsecuritygroup scaleway_instance_security_group.cdktfscaleway_cdktfsecuritygroup_C024279A
✔ SCALEWAY_INSTANCE_SE cdktfserver0 scaleway_instance_server.cdktfscaleway_cdktfserver0_E94A5AA0
✔ SCALEWAY_INSTANCE_SE cdktfserver1 scaleway_instance_server.cdktfscaleway_cdktfserver1_BFAA20A7
✔ SCALEWAY_INSTANCE_SE cdktfserver2 scaleway_instance_server.cdktfscaleway_cdktfserver2_86C2D83C
Summary: 6 created, 0 updated, 0 destroyed.
Output: cdktfscaleway_privateips_71ECA41F = 10.64.162.6710.64.120.1310.68.112.183
cdktfscaleway_publicsips_8A46FC6B = 51.15.143.5051.158.69.38
A noter un défaut de jeunesse dans l’affichage des listes en sortie mais rien de grave, le fichier d’état Terraform est correct.
On peut ensuite se connecter sur la console d’administration Scaleway pour vérifier que les serveurs ont bien été créés :
Enfin pour détruire notre infrastructure, c’est la commande cdktf destroy
qui remplace la commande terraform destroy
habituelle :
$ cdktf destroy
⠸ initializing cdktf-scaleway...
⠴ planning cdktf-scaleway...
⠸ planning cdktf-scaleway...
⠋ Destroying Stack: cdktf-scaleway
Destroying Stack: cdktf-scaleway
Resources
✔ SCALEWAY_INSTANCE_IP cdktfip0 scaleway_instance_ip.cdktfscaleway_cdktfip0_C3F2203D
✔ SCALEWAY_INSTANCE_IP cdktfip1 scaleway_instance_ip.cdktfscaleway_cdktfip1_988D7B00
✔ SCALEWAY_INSTANCE_SE cdktfsecuritygroup scaleway_instance_security_group.cdktfscaleway_cdktfsecuritygroup_C024279A
✔ SCALEWAY_INSTANCE_SE cdktfserver0 scaleway_instance_server.cdktfscaleway_cdktfserver0_E94A5AA0
✔ SCALEWAY_INSTANCE_SE cdktfserver1 scaleway_instance_server.cdktfscaleway_cdktfserver1_BFAA20A7
✔ SCALEWAY_INSTANCE_SE cdktfserver2 scaleway_instance_server.cdktfscaleway_cdktfserver2_86C2D83C
Summary: 6 destroyed.
Conclusion
Comme on a pu le voir à travers cet exemple, il est très aisé de passer d’une infrastructure décrite dans le langage HCL de Terraform à une infrastructure décrite en TypeScript à l’aide de CDK for Terraform.
Sachant que n’importe quel provider Terraform existant devrait fonctionner avec l’outil, son périmètre d’utilisation est très large.
Le produit étant tout jeune, il n’est cependant pas encore recommandé pour une utilisation en production mais j’espère que d’ici quelques mois ce sera le cas.
Au final un produit HashiCorp de plus à ajouter dans votre boîte à outils DevOps !
Si vous avez des questions ou des remarques, n’hésitez pas à me laisser un commentaire.