Comment déployer des applications Web à charge équilibrée sur DigitalOcean avec CDK pour Terraform et TypeScript

De Get Docs
Aller à :navigation, rechercher

Introduction

L'infrastructure en tant que code (IaC) est une pratique d'automatisation du déploiement et des modifications de l'infrastructure en définissant les états des ressources et leurs relations dans le code. L'exécution de ce code crée ou modifie ensuite les ressources réelles dans le cloud. IaC permet aux ingénieurs d'utiliser un outil IaC comme Terraform (par HashiCorp) pour provisionner l'infrastructure.

Avec IaC, les modifications apportées à votre infrastructure peuvent passer par le même processus de révision de code que votre code d'application. Vous pouvez stocker le code dans le contrôle de version (comme Git) pour conserver un historique de l'état de votre infrastructure, et vous pouvez automatiser davantage le processus de déploiement avec des outils de niveau supérieur tels qu'un self-service plate-forme de développement interne (IDP).

Terraform est un outil IaC populaire indépendant de la plate-forme en raison de sa large prise en charge de nombreuses plates-formes, notamment GitHub, Cloudflare et DigitalOcean. La plupart des configurations Terraform sont écrites à l'aide d'un langage déclaratif appelé HashiCorp Configuration Language (HCL).

Le Kit de développement cloud pour Terraform (CDKTF) est un outil basé sur Terraform qui vous permet de définir une infrastructure à l'aide d'un langage de programmation familier (tel que TypeScript, Python ou Go) au lieu de HCL. Cet outil peut fournir une courbe d'apprentissage moins profonde pour les développeurs qui ne connaissent pas HCL, tout en permettant aux développeurs d'utiliser des fonctionnalités de programmation natives telles que des boucles, des variables et des fonctions.

Dans ce tutoriel, vous commencerez par installer le cdktf outil d'interface de ligne de commande (CLI). Ensuite, vous créerez un projet CDKTF dans TypeScript et définirez le projet avec deux serveurs NGINX dont la charge est équilibrée par un équilibreur de charge. Vous utiliserez ensuite cdktf pour déployer l'infrastructure. A la fin de ce tutoriel, vous disposerez d'un projet CDKTF à partir duquel vous pourrez construire pour étendre votre infrastructure.

Remarque : Ce didacticiel a été testé avec CDKTF 0.11.2 et Terraform 1.2.2.


Conditions préalables

Pour terminer ce tutoriel, vous aurez besoin de :

Étape 1 — Installation du cdktf CLI

Pour commencer, vous allez installer le cdktf outil de ligne de commande.

La cdktf CLI est disponible sous forme de package NPM. Si vous recherchez cdktf sur npmjs.com, vous trouverez deux packages portant le même nom : cdktf et cdktf-cli.

Conceptuellement, CDKTF est une couche d'abstraction au-dessus de Terraform. Il se compose de deux parties :

  • une bibliothèque contenant un ensemble de constructions natives du langage (telles que des fonctions et des classes) pour définir l'infrastructure. Cette partie est encapsulée dans le package npm cdktf. Par exemple, vous pouvez voir l'utilisation de la App et TerraformStack cours de la cdktf package dans l'exemple de projet CDKTF suivant :

    import { App, TerraformStack } from "cdktf";
    class APIStack extends TerraformStack {}
    const app = new App();
    new APIStack(app, "feature-x");
    app.synth();
  • un adaptateur qui analyse les constructions dans le projet CDKTF et les réduit à un ensemble de documents JSON, qui sont ensuite ingérés dans Terraform de la même manière que HCL est ingéré. Cet adaptateur est encapsulé dans un outil CLI appelé cdktf, fourni par le package cdktf-cli.

Pour installer le cdktf outil CLI, vous avez besoin de l'outil cdktf-cli forfait. Vous pouvez installer ce package globalement en utilisant npm, yarn, ou un gestionnaire de packages de votre choix.

À installer cdktf-cli avec npm, exécutez la commande suivante :

npm install --global [email protected]

Remarque : Il y aura probablement une version plus récente du cdktf-cli paquet après la publication de cet article. Vous pouvez essayer de suivre le didacticiel avec la dernière version en exécutant npm install --global [email protected] à la place, mais sachez que certaines sorties peuvent différer légèrement.


Vous pouvez également utiliser Homebrew sur macOS ou Linux pour installer le cdktf CLI comme formule cdktf :

brew install cdktf

Pour vérifier que l'installation a réussi, exécutez le cdktf commande sans arguments :

cdktf

Vous verrez une sortie semblable à la suivante :

OutputPlease pass a command to cdktf, here are all available ones:
cdktf

Commands:
  cdktf init                Create a new cdktf project from a template.
  cdktf get                 Generate CDK Constructs for Terraform providers and modules.
  cdktf convert             Converts a single file of HCL configuration to CDK for Terraform.
  cdktf deploy [stacks...]  Deploy the given stacks
  cdktf destroy [stacks..]  Destroy the given stacks
  cdktf diff [stack]        Perform a diff (terraform plan) for the given stack
  cdktf list                List stacks in app.
  cdktf login               Retrieves an API token to connect to Terraform Cloud.
  cdktf synth               Synthesizes Terraform code for the given app in a directory.
  cdktf watch [stacks..]    [experimental] Watch for file changes and automatically trigger a deploy
  cdktf output [stacks..]   Prints the output of stacks
  cdktf debug               Get debug information about the current project and environment
  cdktf completion          generate completion script

Options:
      --version                   Show version number
      --disable-logging           Dont write log files. Supported using the env CDKTF_DISABLE_LOGGING.
      --disable-plugin-cache-env  Dont set TF_PLUGIN_CACHE_DIR automatically.
      --log-level                 Which log level should be written.
  -h, --help                      Show help

Options can be specified via environment variables with the "CDKTF_" prefix (e.g. "CDKTF_OUTPUT")

La sortie vous montre les commandes disponibles. Dans le rest de ce didacticiel, vous acquerrez de l'expérience en utilisant cdktf init, cdktf get, cdktf deploy, et cdktf destroy.

Maintenant que vous avez installé le cdktf CLI, vous pouvez définir l'infrastructure en écrivant du code TypeScript.

Étape 2 - Création d'un nouveau projet CDKTF

Dans cette étape, vous utiliserez le cdktf CLI que vous venez d'installer pour créer un projet CDKTF passe-partout, sur lequel vous vous baserez dans les étapes suivantes.

Créez un répertoire qui hébergera le projet CDKTF en exécutant la commande suivante :

mkdir infra

Ensuite, naviguez dans le répertoire nouvellement créé :

cd infra/

Utilisez le cdktf init commande pour créer un échafaudage de projet CDKTF sur lequel vous vous baserez :

cdktf init --template=typescript --project-name=base --project-description="Base architecture" --local

CDKTF permet aux développeurs de définir une infrastructure à l'aide de TypeScript, Python, Java, C# ou Go. La --template=typescript l'option indique cdktf pour échafauder ce projet CDKTF en utilisant TypeScript.

Terraform (et donc CDKTF) garde une trace des ressources qu'il gère en enregistrant leurs définitions et leurs états dans des fichiers appelés Terraform state. La --local L'option indique à CDKTF de conserver ces fichiers d'état localement sur la machine en cours d'exécution cdktf (chaque fichier suit la structure de nommage terraform.<stack>.tfstate).

Après avoir exécuté la commande, la CLI peut vous demander la permission d'envoyer des rapports de plantage à l'équipe CDKTF pour les aider à améliorer le produit :

Output? Do you want to send crash reports to the CDKTF team? See https://www.terraform.io/cdktf/create-and-deploy/configuration-file for
 more information (Y/n)

Taper Y si vous souhaitez consentir ou N si vous n'êtes pas d'accord, appuyez sur ENTER.

cdktf créera ensuite l'échafaudage du projet et installera les packages. Lorsque le projet est échafaudé, vous verrez une sortie semblable à la suivante :

Output  Your cdktf typescript project is ready!

  cat help                Print this message

  Compile:
    npm run get           Import/update Terraform providers and modules (you should check-in this directory)
    npm run compile       Compile typescript code to javascript (or "npm run watch")
    npm run watch         Watch for changes and compile typescript in the background
    npm run build         Compile typescript

  Synthesize:
    cdktf synth [stack]   Synthesize Terraform resources from stacks to cdktf.out/ (ready for 'terraform apply')

  Diff:
    cdktf diff [stack]    Perform a diff (terraform plan) for the given stack

  Deploy:
    cdktf deploy [stack]  Deploy the given stack

  Destroy:
    cdktf destroy [stack] Destroy the stack

  Test:
    npm run test        Runs unit tests (edit __tests__/main-test.ts to add your own tests)
    npm run test:watch  Watches the tests and reruns them on change

  Upgrades:
    npm run upgrade        Upgrade cdktf modules to latest version
    npm run upgrade:next   Upgrade cdktf modules to latest "@next" version (last commit)

Vous verrez également quelques nouveaux fichiers ajoutés au infra annuaire. Les fichiers les plus importants sont cdktf.json et main.ts.

cdktf.json est le fichier de configuration pour le projet CDKTF. Si vous ouvrez le fichier, il affichera quelque chose comme ceci :

cdktf.json

{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "28c87598-4343-47a9-bb5d-8fb0e031c41b",
  "terraformProviders": [],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}

La app La propriété définit la commande qui sera exécutée pour synthétiser le code TypeScript en JSON compatible Terraform. Cette propriété indique que main.ts est le point d'entrée du projet CDKTF.

Si vous ouvrez le main.ts fichier, vous verrez quelque chose de similaire à ce qui suit :

main.ts

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, "infra");
app.synth();

Dans le langage de CDKTF, une collection de ressources d'infrastructure connexes peut être regroupée dans une pile. Par exemple, les ressources composant une application API, telles que les droplets, les équilibreurs de charge et les enregistrements DNS, peuvent être regroupées dans une seule pile nommée APIStack. Chaque pile conserve son propre état et peut être déployée, modifiée ou détruite indépendamment des autres piles. Une utilisation courante des piles consiste à avoir une pile pour la production et une pile distincte pour le développement.

Une application est un conteneur pour plusieurs piles. Par exemple, une application peut regrouper les piles de différents microservices.

L'échafaudage du projet CDKTF généré en main.ts contient une seule classe de pile appelée MyStack, ne définissant actuellement aucune ressource. Un exemple de MyStack est créé avec le nom infra, contenu dans une application appelée app. Dans les étapes suivantes, vous définirez les ressources d'infrastructure dans le MyStack constructeur.

Après avoir créé le projet, l'étape suivante consiste à configurer le projet CDKTF avec fournisseurs.

Étape 3 - Installation du fournisseur DigitalOcean

Dans cette étape, vous installerez le fournisseur DigitalOcean dans le projet CDKTF.

Providers sont des bibliothèques qui fournissent des instructions à Terraform (qui est utilisé par cdktf sous le capot) sur la façon de créer, mettre à jour et supprimer des ressources sur les fournisseurs de cloud, les fournisseurs SaaS et d'autres plateformes exposant des interfaces de programmation d'application (API). Les fournisseurs encapsulent la logique d'appel de ces API en amont dans des fonctions standard que Terraform peut appeler.

Par exemple, si vous deviez créer un nouveau Droplet DigitalOcean sans Terraform, vous devrez envoyer un POST requête au point de terminaison /v2/droplets de l'API DigitalOcean. Avec Terraform, vous devez plutôt installer le fournisseur DigitalOcean et définir une ressource digitalocean_droplet, similaire à l'exemple d'extrait de code suivant :

new Droplet(this, 'web', {
  image: 'ubuntu-20-04-x64',
  name,
  region: 'lon1',
  size: 's-1vcpu-1gb',
}

Vous pouvez ensuite utiliser le cdktf Outil CLI pour traduire ce code TypeScript en JSON compatible Terraform et le transmettre au fournisseur, qui effectuera les appels d'API appropriés pour créer le Droplet en votre nom.

Maintenant que vous comprenez ce qu'est un fournisseur, vous pouvez configurer le fournisseur DigitalOcean pour votre projet CDKTF.

Ouvrez le cdktf.json fichier et ajoutez la chaîne digitalocean/digitalocean au terraformProviders déployer:

cdktf.json

{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "28c87598-4343-47a9-bb5d-8fb0e031c41b",
  "terraformProviders": ["digitalocean/digitalocean"],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}

digitalocean/digitalocean est l'identifiant du fournisseur DigitalOcean sur le Terraform Registry.

Enregistrez et fermez le fichier.

Ensuite, exécutez cdktf get pour télécharger et installer le fournisseur.

cdktf get

cdktf get téléchargera le fournisseur, extraira le schéma, générera les classes TypeScript correspondantes et l'ajoutera en tant que module TypeScript sous .gen/providers/. Cette génération de code automatique vous permet d'utiliser n'importe quel fournisseur Terraform et module HCL avec CDKTF, et c'est ainsi que CDKTF peut fournir la complétion de code dans les éditeurs qui le prennent en charge.

Une fois que cdktf get termine l'exécution, vous verrez une sortie semblable à la suivante :

OutputGenerated typescript constructs in the output directory: .gen

Vous verrez également un nouveau répertoire appelé .gen contenant le code généré du fournisseur.

Dans cette étape, vous avez installé le digitalocean/digitalocean fournisseur dans le projet. Dans l'étape suivante, vous configurerez le fournisseur DigitalOcean avec les informations d'identification requises pour authentifier le fournisseur avec l'API DigitalOcean.

Étape 4 - Configuration du fournisseur DigitalOcean

Dans cette étape, vous allez configurer le fournisseur DigitalOcean avec votre jeton d'accès personnel DigitalOcean, qui permet au fournisseur d'appeler l'API DigitalOcean en votre nom.

Différents fournisseurs exigent et prennent en charge différentes informations d'identification pour l'authentification avec l'API en amont. Pour le fournisseur DigitalOcean, vous devez fournir votre jeton d'accès personnel DigitalOcean. Vous pouvez spécifier le jeton au fournisseur en le définissant comme le DIGITALOCEAN_TOKEN ou DIGITALOCEAN_ACCESS_TOKEN Variables d'environnement.

Exécutez la commande suivante dans votre terminal pour définir la variable d'environnement pour cette session de terminal.

export DIGITALOCEAN_ACCESS_TOKEN="your_personal_access_token"

Remarque : En appelant export, vous définissez la variable d'environnement uniquement pour cette session de terminal. Si vous fermez et rouvrez le terminal ou exécutez le cdktf commandes dans un autre terminal, vous devrez exécuter le export commande à nouveau pour que la variable d'environnement prenne effet.


Ensuite, vous spécifierez le fournisseur dans le MyStack class, qui vous permettra de définir les ressources fournies par le fournisseur au sein de votre pile. Mettre à jour le main.ts fichier à ce qui suit :

main.ts

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { DigitaloceanProvider } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new DigitaloceanProvider(this, 'provider')
    
  }
}

const app = new App();
new MyStack(app, "infra");
app.synth();

Le module pour le fournisseur se trouve à ./.gen/providers/digitalocean, qui a été généré automatiquement lorsque vous avez exécuté cdktf get.

Vous avez configuré le digitalocean/digitalocean fournisseur avec des informations d'identification à cette étape. Ensuite, vous commencerez à définir l'infrastructure qui fait partie de l'objectif de ce didacticiel.

Étape 5 - Définir les applications Web sur les droplets

Dans cette étape, vous allez définir deux serveurs NGINX, chacun servant des fichiers différents, déployés sur deux gouttelettes Ubuntu 20.04 identiques.

Vous commencez par la définition des deux Droplets. Modifier main.ts avec les modifications en surbrillance :

main.ts

...
import { DigitaloceanProvider, Droplet } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const dropletNames = ['foo', 'bar']
    const droplets = dropletNames.map(name => new Droplet(this, name, {
        image: 'ubuntu-20-04-x64',
        name,
        region: 'lon1',
        size: 's-1vcpu-1gb',
      })
    )
  }
}

Vous utilisez une boucle JavaScript native (Array.prototype.map()) pour éviter la duplication dans le code.

Tout comme si vous créiez le Droplet via la console, il y a plusieurs paramètres à spécifier :

  • image - la distribution Linux et la version que votre Droplet exécutera.
  • region - le centre de données dans lequel Droplet s'exécutera.
  • size - la quantité de ressources CPU et mémoire à réserver au Droplet.
  • name - un nom unique utilisé pour désigner la Droplet.

Les valeurs pour image, region, et size doivent être des choses que DigitalOcean prend en charge. Vous pouvez trouver les valeurs valides (appelées slugs) pour toutes les images de distribution Linux, tailles de gouttelettes et régions prises en charge sur la page DigitalOcean API Slugs. Vous pouvez trouver une liste complète des attributs obligatoires et facultatifs sur la page de documentation digitalocean_droplet.

Ajout d'une clé SSH

Dans le cadre des conditions préalables, vous avez téléchargé une clé publique SSH sans mot de passe sur votre compte DigitalOcean et noté son nom. Vous allez maintenant utiliser ce nom pour récupérer l'ID de la clé SSH et le transmettre dans la définition de votre Droplet.

Étant donné que la clé SSH a été ajoutée manuellement à votre compte DigitalOcean, il ne s'agit pas d'une ressource gérée par votre configuration Terraform actuelle. Si vous essayez de définir une nouvelle ressource digitalocean_ssh_key, une nouvelle clé SSH sera créée au lieu d'utiliser celle existante.

À la place, vous allez définir une nouvelle digitalocean_ssh_key source de données. Dans Terraform, les sources de données sont utilisées pour récupérer des informations sur l'infrastructure qui ne sont pas gérées par la configuration actuelle de Terraform. En d'autres termes, ils fournissent une vue en lecture seule de l'état de l'infrastructure externe préexistante. Une fois qu'une source de données est définie, vous pouvez utiliser les données ailleurs dans votre configuration Terraform.

Toujours dedans main.ts et au sein du constructeur de MyStack, définir un nouveau DataDigitaloceanSshKey source de données, et transmettez le nom que vous avez attribué à votre clé SSH (ici, le nom est do_cdktf):

main.ts

...
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const dropletNames = ['foo', 'bar']
    const sshKey = new DataDigitaloceanSshKey(this, 'sshKey', {
      name: 'do_cdktf',
    })
    const droplets = dropletNames.map(name => new Droplet(this, name, {
    ...
  }
}
...

Ensuite, mettez à jour la définition du droplet pour inclure la clé SSH :

main.ts

...
const droplets = dropletNames.map(name => new Droplet(this, name, {
  image: 'ubuntu-20-04-x64',
  name,
  region: 'lon1',
  size: 's-1vcpu-1gb',
  sshKeys: [sshKey.id.toString()]
}))
...

Une fois provisionné, vous pouvez accéder au droplet à l'aide d'une clé SSH privée au lieu d'un mot de passe.

Spécification du script de données utilisateur pour installer NGINX

Vous avez maintenant défini deux Droplets identiques exécutant Ubuntu, configurés avec un accès SSH. La tâche suivante consiste à installer NGINX sur chaque Droplet.

Lors de la création d'un droplet, un outil appelé CloudInit démarre le serveur. CloudInit peut accepter un fichier appelé données utilisateur, qui peut modifier la façon dont le serveur est amorcé. Les données utilisateur peuvent être n'importe cloud-config fichiers ou scripts que le serveur peut interpréter, tels que les scripts Bash.

Dans le reste de cette étape, vous allez créer un script Bash et le spécifier en tant que données utilisateur du Droplet. Le script installera NGINX dans le cadre du processus d'amorçage. De plus, le script remplacera également le contenu du /var/www/html/index.html file (le fichier par défaut servi par NGINX) avec le nom d'hôte et l'adresse IP du Droplet, ce qui amènera les deux serveurs NGINX à servir des fichiers différents. Dans l'étape suivante, vous placerez ces deux serveurs NGINX derrière un équilibreur de charge ; en servant différents fichiers, il sera clair si l'équilibreur de charge distribue les requêtes correctement ou non.

Toujours dedans main.ts, ajouter un nouveau userData propriété à l'objet de configuration du droplet :

main.ts

...
class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const droplets = dropletNames.map(name => new Droplet(this, name, {
      image: 'ubuntu-20-04-x64',
      name,
      region: 'lon1',
      size: 's-1vcpu-1gb',
      sshKeys: [sshKey.id.toString()],
      userData: `#!/bin/bash

apt-get -y update
apt-get -y install nginx
export HOSTNAME=$(curl -s http://169.254.169.254/metadata/v1/hostname)
export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
echo Droplet: $HOSTNAME, IP Address: $PUBLIC_IPV4 > /var/www/html/index.html
`
    }))
  }
}

Attention : Assurez-vous qu'il n'y a pas de nouvelles lignes avant le shebang (#!); sinon, le script risque de ne pas être exécuté.


Lorsque le droplet est provisionné pour la première fois, le script sera exécuté en tant que root utilisateur. Il utilisera le gestionnaire de paquets d'Ubuntu, APT, pour installer le nginx forfait. Il utilisera ensuite le Metadata Service de DigitalOcean pour récupérer des informations sur lui-même et écrira le nom d'hôte et l'adresse IP dans index.html, qui est servi par NGINX.

Dans cette étape, vous avez défini les deux Droplets exécutant Ubuntu, configuré chacun avec un accès SSH et installé NGINX à l'aide de la fonctionnalité de données utilisateur. Dans l'étape suivante, vous définirez un équilibreur de charge qui se placera devant ces serveurs NGINX et le configurerez pour équilibrer la charge de manière circulaire.

Étape 6 - Définir un équilibreur de charge

Dans cette étape, vous allez définir un DigitalOcean Load Balancer en définissant une instance de la ressource digitalocean_loadbalancer.

Toujours dedans main.ts, ajoutez la définition suivante pour un équilibreur de charge à la fin du MyStack constructeur:

main.ts

...
import { App, Fn, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    new Loadbalancer(this, 'lb', {
      name: 'default',
      region: 'lon1',
      algorithm: 'round_robin',
      forwardingRule: [{
        entryProtocol: 'http',
        entryPort: 80,
        targetProtocol: 'http',
        targetPort: 80,
      }],
      dropletIds: droplets.map((droplet) => Fn.tonumber(droplet.id))
    })
  }
}
...

La forwardingRule l'argument indique à l'équilibreur de charge d'écouter les requêtes HTTP sur le port 80 et les transmettre à chacune des gouttelettes sur le port 80.

La dropletIds spécifiez les droplets auxquels l'équilibreur de charge transmettra les requêtes. Il faut un nombre, mais la valeur de droplet.id est une chaîne. Par conséquent, vous avez utilisé la fonction Fn.tonumber Terraform pour convertir la chaîne Droplet ID value en nombre.

Remarque : Vous avez utilisé Fn.tonumber Fonction Terraform ici au lieu du JavaScript natif parseInt parce que la valeur de droplet.id est inconnu jusqu'à ce que le droplet soit provisionné. Les fonctions Terraform sont conçues pour fonctionner sur des valeurs d'exécution inconnues avant que Terraform n'applique une configuration.


Enregistrez et fermez le fichier.

Vous avez maintenant défini deux Droplets et un équilibreur de charge qui se trouve devant eux. Ton main.ts devrait ressembler à ceci :

main.ts

import { Construct } from "constructs";
import { App, Fn, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new DigitaloceanProvider(this, 'provider')

    const dropletNames = ['foo', 'bar']
    const sshKey = new DataDigitaloceanSshKey(this, 'sshKey', {
      name: 'do_cdktf',
    })
    const droplets = dropletNames.map(name => new Droplet(this, name, {
        image: 'ubuntu-20-04-x64',
        name,
        region: 'lon1',
        size: 's-1vcpu-1gb',
        sshKeys: [sshKey.id.toString()],
        userData: `#!/bin/bash

apt-get -y update
apt-get -y install nginx
export HOSTNAME=$(curl -s http://169.254.169.254/metadata/v1/hostname)
export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
echo Droplet: $HOSTNAME, IP Address: $PUBLIC_IPV4 > /var/www/html/index.html
`
      })
    )

    new Loadbalancer(this, 'lb', {
      name: 'default',
      region: 'lon1',
      algorithm: 'round_robin',
      forwardingRule: [{
        entryProtocol: 'http',
        entryPort: 80,
        targetProtocol: 'http',
        targetPort: 80,
      }],
      dropletIds: droplets.map((droplet) => Fn.tonumber(droplet.id))
    })
  }
}

const app = new App();
new MyStack(app, "infra");
app.synth();

Dans l'étape suivante, vous utiliserez le cdktf Outil CLI pour actualiser l'ensemble de votre projet CDKTF.

Étape 7 - Provisionnement de votre infrastructure

Dans cette étape, vous utiliserez le cdktf Outil CLI pour provisionner les droplets et les équilibreurs de charge que vous avez définis dans les étapes précédentes.

Assurez-vous que vous êtes dans le infra/ répertoire et ont défini le DIGITALOCEAN_ACCESS_TOKEN variable d'environnement pour votre session de terminal, puis exécutez la cdktf deploy commande:

cdktf deploy

Vous devriez voir une sortie semblable à la suivante :

Outputinfra  Initializing the backend...
infra  Initializing provider plugins...
infra  - Reusing previous version of digitalocean/digitalocean from the dependency lock file
infra  - Using previously-installed digitalocean/digitalocean v2.19.0
infra  Terraform has been successfully initialized!
infra  Terraform used the selected providers to generate the following execution
       plan. Resource actions are indicated with the following symbols:
       + create
       
       Terraform will perform the following actions:
infra    # digitalocean_droplet.bar (bar) will be created
         + resource "digitalocean_droplet" "bar" {
       + backups              = false
       + created_at           = (known after apply)
       + disk                 = (known after apply)
       + graceful_shutdown    = false
       + id                   = (known after apply)
       + image                = "ubuntu-20-04-x64"
       + ipv4_address         = (known after apply)
       + ipv4_address_private = (known after apply)
       + ipv6                 = false
       + ipv6_address         = (known after apply)
       + locked               = (known after apply)
       + memory               = (known after apply)
       + monitoring           = false
       + name                 = "bar"
       + price_hourly         = (known after apply)
       + price_monthly        = (known after apply)
       + private_networking   = (known after apply)
       + region               = "lon1"
       + resize_disk          = true
       + size                 = "s-1vcpu-1gb"
       + ssh_keys             = [
       + "34377800",
       ]
       + status               = (known after apply)
       + urn                  = (known after apply)
       + user_data            = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1"
       + vcpus                = (known after apply)
       + volume_ids           = (known after apply)
       + vpc_uuid             = (known after apply)
       }

         # digitalocean_droplet.foo (foo) will be created
         + resource "digitalocean_droplet" "foo" {
       + backups              = false
       + created_at           = (known after apply)
       + disk                 = (known after apply)
       + graceful_shutdown    = false
       + id                   = (known after apply)
       + image                = "ubuntu-20-04-x64"
       + ipv4_address         = (known after apply)
       + ipv4_address_private = (known after apply)
       + ipv6                 = false
       + ipv6_address         = (known after apply)
       + locked               = (known after apply)
       + memory               = (known after apply)
       + monitoring           = false
       + name                 = "foo"
       + price_hourly         = (known after apply)
       + price_monthly        = (known after apply)
       + private_networking   = (known after apply)
       + region               = "lon1"
       + resize_disk          = true
       + size                 = "s-1vcpu-1gb"
       + ssh_keys             = [
       + "34377800",
       ]
       + status               = (known after apply)
       + urn                  = (known after apply)
       + user_data            = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1"
       + vcpus                = (known after apply)
       + volume_ids           = (known after apply)
       + vpc_uuid             = (known after apply)
       }

         # digitalocean_loadbalancer.lb (lb) will be created
         + resource "digitalocean_loadbalancer" "lb" {
       + algorithm                        = "round_robin"
       + disable_lets_encrypt_dns_records = false
       + droplet_ids                      = (known after apply)
       + enable_backend_keepalive         = false
       + enable_proxy_protocol            = false
       + id                               = (known after apply)
       + ip                               = (known after apply)
       + name                             = "default"
       + redirect_http_to_https           = false
       + region                           = "lon1"
       + size_unit                        = (known after apply)
       + status                           = (known after apply)
       + urn                              = (known after apply)
       + vpc_uuid                         = (known after apply)

       + forwarding_rule {
       + certificate_id   = (known after apply)
       + certificate_name = (known after apply)
       + entry_port       = 80
       + entry_protocol   = "http"
       + target_port      = 80
       + target_protocol  = "http"
       + tls_passthrough  = false
       }

       + healthcheck {
       + check_interval_seconds   = (known after apply)
       + healthy_threshold        = (known after apply)
       + path                     = (known after apply)
       + port                     = (known after apply)
       + protocol                 = (known after apply)
       + response_timeout_seconds = (known after apply)
       + unhealthy_threshold      = (known after apply)
       }

       + sticky_sessions {
       + cookie_name        = (known after apply)
       + cookie_ttl_seconds = (known after apply)
       + type               = (known after apply)
       }
       }

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

       Saved the plan to: plan

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

Please review the diff output above for infra
❯ Approve  Applies the changes outlined in the plan.
  Dismiss
  Stop

Remarque : CDKTF est toujours en cours de développement et la sortie peut différer de celle indiquée ci-dessus.


Cet affichage répertorie toutes les ressources et propriétés qui cdktf prévoit de créer, de mettre à jour et de détruire. Certaines valeurs, telles que l'ID d'un Droplet, ne sont connues qu'après le provisionnement de la ressource. Pour ceux-là, vous verrez (known after apply) comme valeur de propriété dans la sortie.

Passez en revue la liste des ressources pour vous assurer qu'elle correspond à vos attentes. Ensuite, utilisez les touches fléchées pour sélectionner l'option Approuver et appuyez sur ENTER.

Vous verrez une sortie semblable à la suivante :

Outputinfra  digitalocean_droplet.foo (foo): Creating...
       digitalocean_droplet.bar (bar): Creating...
infra  digitalocean_droplet.bar (bar): Still creating... [10s elapsed]
infra  digitalocean_droplet.foo (foo): Still creating... [10s elapsed]


1 Stack deploying     0 Stacks done     0 Stacks waiting

Cette sortie vous indique que cdktf communique avec l'API DigitalOcean pour créer le Droplet. cdktf crée d'abord les droplets, car l'équilibreur de charge dépend de l'ID du droplet, qui est inconnu jusqu'à ce que les droplets soient provisionnés.

La création de gouttelettes prend généralement moins d'une minute. Une fois les Droplets provisionnés, cdktf passe à la création de l'équilibreur de charge.

Outputinfra  digitalocean_droplet.bar (bar): Creation complete after 54s [id=298041598]
infra  digitalocean_droplet.foo (foo): Creation complete after 55s [id=298041600]
infra  digitalocean_loadbalancer.lb (lb): Creating...
infra  digitalocean_loadbalancer.lb (lb): Still creating... [10s elapsed]

L'équilibreur de charge peut prendre plus de temps. Une fois l'équilibreur de charge créé, vous verrez un résumé indiquant que la pile a été déployée avec succès.

Outputinfra  digitalocean_loadbalancer.lb (lb): Still creating... [1m30s elapsed]
infra  digitalocean_loadbalancer.lb (lb): Creation complete after 1m32s [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1]
infra  
       Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
       

No outputs found.

Vous pouvez maintenant visiter la console DigitalOcean, où vous pouvez voir un équilibreur de charge nommé default et deux gouttelettes saines nommées foo et bar, chacun servant de cible pour l'équilibreur de charge.

Vous pouvez tester que NGINX s'exécute et diffuse correctement le contenu en visitant l'adresse IP de chaque Droplet. Vous devriez voir un texte semblable au suivant :

Droplet: bar, IP Address: droplet_ip

Si vous ne voyez pas cette chaîne de texte ou si le serveur ne répond pas, vérifiez que les données utilisateur que vous avez spécifiées sont correctes et qu'aucun caractère (y compris les nouvelles lignes) ne précède le shebang (#!). Vous pouvez également vous connecter en SSH au droplet à l'aide de votre clé privée SSH et consulter les journaux de sortie générés par CloudInit sur /var/log/cloud-init-output.log:

ssh -i path_to_ssh_private_key [email protected]_ip

Une fois que vous avez confirmé que les droplets sont opérationnels et diffusent du contenu, vous pouvez commencer à tester l'équilibreur de charge. Pour ce faire, envoyez quelques demandes.

Exécutez la commande suivante depuis votre terminal pour envoyer dix requêtes à l'équilibreur de charge :

for run in {1..10}; do curl http://load_balancer_ip/; done

Vous devriez voir une sortie similaire à la suivante, bien que les adresses IP affichées soient différentes :

OutputDroplet: foo, IP Address: droplet_foo_ip
Droplet: bar, IP Address: droplet_bar_ip
Droplet: foo, IP Address: droplet_foo_ip
Droplet: bar, IP Address: droplet_bar_ip
Droplet: bar, IP Address: droplet_bar_ip
Droplet: foo, IP Address: droplet_foo_ip
Droplet: bar, IP Address: droplet_bar_ip
Droplet: foo, IP Address: droplet_foo_ip
Droplet: bar, IP Address: droplet_bar_ip
Droplet: foo, IP Address: droplet_foo_ip

Il montre que les requêtes adressées à l'équilibreur de charge ont été transmises cinq fois à chaque droplet, ce qui indique que l'équilibreur de charge fonctionne.

Remarque : L'équilibreur de charge peut ne pas toujours équilibrer parfaitement les deux Droplets ; vous pouvez constater que quatre requêtes ont été envoyées à un Droplet et six à l'autre. Ce comportement est normal.


Dans cette étape, vous avez utilisé cdktf pour provisionner vos ressources, puis vous avez utilisé la console DigitalOcean pour découvrir les adresses IP de vos Droplets et de votre équilibreur de charge. Vous avez ensuite envoyé des requêtes à chaque droplet et équilibreur de charge pour confirmer qu'ils fonctionnent.

À l'étape suivante, vous obtiendrez les adresses IP des gouttelettes et de l'équilibreur de charge sans vous connecter à la console DigitalOcean.

Étape 8 - Sortie d'informations

À l'étape précédente, vous deviez vous connecter à la console DigitalOcean pour obtenir les adresses IP de votre Droplet et de votre équilibreur de charge. Dans cette étape, vous modifierez légèrement votre code afin que ces informations soient imprimées dans la sortie du cdktf deploy commande, vous épargnant un voyage à la console.

Terraform enregistre la configuration et l'état de ses ressources gérées dans des fichiers d'état. Pour votre infra pile, le fichier d'état peut être trouvé à infra/terraform.infra.tfstate. Vous pourrez trouver les adresses IP des Droplets et de l'équilibreur de charge dans ce fichier d'état.

Cependant, trier un fichier volumineux peut être peu pratique. Le CDKTF fournit le TerraformOutput construct, que vous pouvez utiliser pour générer des variables et les rendre disponibles en dehors de la pile. Toutes les sorties sont imprimées dans stdout après cdktf deploy est exécuté. Fonctionnement cdktf output peut également imprimer des sorties à tout moment.

Remarque : Bien que vous n'utilisiez que les sorties pour imprimer des informations sur la console dans ce didacticiel, sa véritable puissance provient des piles utilisant les sorties d'autres piles comme entrée, une fonctionnalité connue sous le nom de références de pile croisée.


Mettre à jour le main.ts fichier pour inclure les sorties des adresses IP de l'équilibreur de charge et des Droplets :

main.ts

import { Construct } from "constructs";
import { App, Fn, TerraformOutput, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const lb = new Loadbalancer(this, 'lb', {
      ...
    })

    new TerraformOutput(this, "loadBalancerIP", {
      value: lb.ip,
    });

    droplets.forEach((droplet, index) => new TerraformOutput(this, `droplet${index}IP`, {
      value: droplet.ipv4Address
    }))
  }
}
...

Enregistrez et fermez le fichier.

Courir cdktf deploy pour actualiser le changement :

cdktf deploy

Dans la sortie, vous devriez voir quelque chose de similaire à ce qui suit :

Output─────────────────────────────────────────────────────────────────────────────

Changes to Outputs:
+ droplet0IP     = "droplet_foo_ip"
+ droplet1IP     = "droplet_bar_ip"
+ loadBalancerIP = "load_balancer_ip"

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.

─────────────────────────────────────────────────────────────────────────────

Cette sortie vous indique qu'aucune modification de l'infrastructure ne sera apportée, uniquement ce qui est sorti de la pile.

Utilisez les touches fléchées pour sélectionner Approuver, puis appuyez sur ENTER. À la fin de la sortie du terminal, vous devriez voir quelque chose de similaire à :

Outputinfra
droplet0IP = droplet_foo_ip
droplet1IP = droplet_bar_ip
loadBalancerIP = load_balancer_ip

Maintenant, chaque fois que tu cours cdktf deploy ou cdktf output, l'adresse IP des gouttelettes et les équilibreurs de charge sont imprimés dans la sortie du terminal, supprimant la nécessité d'accéder à ces informations à partir de la console DigitalOcean.

Vous avez maintenant provisionné deux Droplets et un équilibreur de charge et confirmé qu'ils fonctionnent. Vous pouvez utiliser le projet CDKTF que vous avez développé comme base pour définir une infrastructure plus sophistiquée (vous pouvez trouver une implémentation de référence sur do-community / digitalocean-cdktf-typescript).

Les ressources provisionnées dans ce didacticiel entraîneront des frais. Si vous n'avez pas l'intention d'utiliser l'infrastructure créée, vous devez la détruire. Dans la prochaine et dernière étape, vous allez nettoyer le projet en détruisant les ressources créées dans ce tutoriel.

Étape 9 - Détruire votre infrastructure

Dans cette étape, vous supprimerez toutes les ressources créées dans ce didacticiel.

Toujours au sein du infra/ répertoire, exécutez cdktf destroy:

cdktf destroy

Vous devriez voir une sortie semblable à la suivante :

Outputinfra  Initializing the backend...
infra  Initializing provider plugins...
infra  - Reusing previous version of digitalocean/digitalocean from the dependency lock file
infra  - Using previously-installed digitalocean/digitalocean v2.19.0
infra  Terraform has been successfully initialized!
infra  digitalocean_droplet.bar (bar): Refreshing state... [id=298041598]
       digitalocean_droplet.foo (foo): Refreshing state... [id=298041600]
infra  digitalocean_loadbalancer.lb (lb): Refreshing state... [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1]
infra  Terraform used the selected providers to generate the following execution
       plan. Resource actions are indicated with the following symbols:
       - destroy
       
       Terraform will perform the following actions:
infra    # digitalocean_droplet.bar (bar) will be destroyed
         - resource "digitalocean_droplet" "bar" {
       - backups              = false -> null
       - created_at           = "2022-05-02T10:04:16Z" -> null
       - disk                 = 25 -> null
       - graceful_shutdown    = false -> null
       - id                   = "298041598" -> null
       - image                = "ubuntu-20-04-x64" -> null
       - ipv4_address         = "droplet_bar_public_ip" -> null
       - ipv4_address_private = "droplet_bar_private_ip" -> null
       - ipv6                 = false -> null
       - locked               = false -> null
       - memory               = 1024 -> null
       - monitoring           = false -> null
       - name                 = "bar" -> null
       - price_hourly         = 0.00744 -> null
       - price_monthly        = 5 -> null
       - private_networking   = true -> null
       - region               = "lon1" -> null
       - resize_disk          = true -> null
       - size                 = "s-1vcpu-1gb" -> null
       - ssh_keys             = [
       - "34377800",
       ] -> null
       - status               = "active" -> null
       - tags                 = [] -> null
       - urn                  = "do:droplet:298041598" -> null
       - user_data            = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" -> null
       - vcpus                = 1 -> null
       - volume_ids           = [] -> null
       - vpc_uuid             = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null
       }

         # digitalocean_droplet.foo (foo) will be destroyed
         - resource "digitalocean_droplet" "foo" {
       - backups              = false -> null
       - created_at           = "2022-05-02T10:04:16Z" -> null
       - disk                 = 25 -> null
       - graceful_shutdown    = false -> null
       - id                   = "298041600" -> null
       - image                = "ubuntu-20-04-x64" -> null
       - ipv4_address         = "droplet_foo_public_ip" -> null
       - ipv4_address_private = "droplet_foo_private_ip" -> null
       - ipv6                 = false -> null
       - locked               = false -> null
       - memory               = 1024 -> null
       - monitoring           = false -> null
       - name                 = "foo" -> null
       - price_hourly         = 0.00744 -> null
       - price_monthly        = 5 -> null
       - private_networking   = true -> null
       - region               = "lon1" -> null
       - resize_disk          = true -> null
       - size                 = "s-1vcpu-1gb" -> null
       - ssh_keys             = [
       - "34377800",
       ] -> null
       - status               = "active" -> null
       - tags                 = [] -> null
       - urn                  = "do:droplet:298041600" -> null
       - user_data            = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" -> null
       - vcpus                = 1 -> null
       - volume_ids           = [] -> null
       - vpc_uuid             = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null
       }

         # digitalocean_loadbalancer.lb (lb) will be destroyed
         - resource "digitalocean_loadbalancer" "lb" {
       - algorithm                        = "round_robin" -> null
       - disable_lets_encrypt_dns_records = false -> null
       - droplet_ids                      = [
       - 298041598,
       - 298041600,
       ] -> null
       - enable_backend_keepalive         = false -> null
       - enable_proxy_protocol            = false -> null
       - id                               = "4f9ae2b7-b649-4fb4-beed-96b95bb72dd1" -> null
       - ip                               = "load_balancer_ip" -> null
       - name                             = "default" -> null
       - redirect_http_to_https           = false -> null
       - region                           = "lon1" -> null
       - size_unit                        = 1 -> null
       - status                           = "active" -> null
       - urn                              = "do:loadbalancer:4f9ae2b7-b649-4fb4-beed-96b95bb72dd1" -> null
       - vpc_uuid                         = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null

       - forwarding_rule {
       - entry_port      = 80 -> null
       - entry_protocol  = "http" -> nul
infra  l
       - target_port     = 80 -> null
       - target_protocol = "http" -> null
       - tls_passthrough = false -> null
       }

       - healthcheck {
       - check_interval_seconds   = 10 -> null
       - healthy_threshold        = 5 -> null
       - path                     = "/" -> null
       - port                     = 80 -> null
       - protocol                 = "http" -> null
       - response_timeout_seconds = 5 -> null
       - unhealthy_threshold      = 3 -> null
       }

       - sticky_sessions {
       - cookie_ttl_seconds = 0 -> null
       - type               = "none" -> null
       }
       }

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

       Saved the plan to: plan

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

Please review the diff output above for infra
❯ Approve  Applies the changes outlined in the plan.
  Dismiss
  Stop

Cette fois, au lieu de montrer + à côté de chaque ressource, il affiche -, indiquant que CDKTF prévoit de détruire la ressource. Passez en revue les modifications proposées, puis utilisez les touches fléchées pour sélectionner Approuver et appuyez sur ENTER. Le fournisseur DigitalOcean va maintenant communiquer avec l'API DigitalOcean pour détruire les ressources.

Outputinfra  digitalocean_loadbalancer.lb (lb): Destroying... [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1]
infra  digitalocean_loadbalancer.lb (lb): Destruction complete after 1s
infra  digitalocean_droplet.bar (bar): Destroying... [id=298041598]
       digitalocean_droplet.foo (foo): Destroying... [id=298041600]

L'équilibreur de charge a été supprimé en premier car il n'a aucune dépendance (aucune autre ressource ne fait référence à l'équilibreur de charge dans ses entrées). Étant donné que l'équilibreur de charge fait référence aux gouttelettes, elles ne peuvent être détruites qu'après la destruction de l'équilibreur de charge.

Une fois les ressources détruites, vous verrez la ligne suivante imprimée dans la sortie :

OutputDestroy complete! Resources: 3 destroyed.

Conclusion

Dans ce didacticiel, vous avez utilisé CDKTF pour provisionner et détruire une page Web à charge équilibrée, composée de deux gouttelettes DigitalOcean exécutant des serveurs NGINX, servies derrière un équilibreur de charge. Vous affichez également des informations sur les ressources sur le terminal.

CDKTF est une couche d'abstraction au-dessus de Terraform. Une bonne compréhension de Terraform est utile pour comprendre CDKTF. Si vous souhaitez en savoir plus sur Terraform, vous pouvez lire la série Comment gérer l'infrastructure avec Terraform, qui couvre Terraform en profondeur.

Vous pouvez également consulter la documentation officielle CDK for Terraform et tutoriels pour en savoir plus sur CDKTF.