Intro à l'infrastructure en tant que code - IaC - avec Twilio (Partie 1)

March 09, 2021
Rédigé par

Présentation de l'infrastructure en tant que code - IaC - avec Twilio (Partie 1)

Ce post de blog est le premier d'une série en trois parties où nous explorons comment les processus et les outils IaC (« Infrastructure-as-Code », ou Infrastructure en tant que code) peuvent être utilisés pour déployer et gérer votre solution Twilio.

Dans cette partie, nous allons présenter les principes fondamentaux du processus IaC appliqués aux produits Twilio. À la fin de cet article, vous saurez déployer et gérer vos ressources Twilio à l'aide d'un outil spécialement conçu pour cet exercice.

Prérequis

Pour suivre ce tutoriel, vous devez disposer des éléments suivants :

  • Un compte Twilio. Vous pouvez en obtenir un gratuitement ici
  • La CLI Twilio. Vous pouvez l'installer en suivant les instructions ici
  • La CLI Pulumi. Vous pouvez l'installer en suivant les instructions ici
  • Facultatif : un compte Pulumi. Vous pouvez vous inscrire à Pulumi ici

Infrastructure en tant que code (IaC)

Avant de commencer, définissons le concept d'infrastructure en tant que code. Si vous connaissez déjà ce concept, vous pouvez ignorer ce paragraphe.

Avec l'essor des services cloud, la nécessité de gérer la configuration de l'infrastructure est devenue importante. Le mouvement DevOps a entraîné une transformation des pratiques de développement et des opérations. De plus, les besoins d'automatisation de l'intégration et la distribution de code (voir CI/CD) ont forcé de nombreux devs à mieux comprendre où leur code sera exécuté.

L'IaC aide les devs et les entreprises à se rapprocher de l'automatisation complète du cycle de vie de leurs logiciels. En général, le développement de nouvelles fonctionnalités suit communément les étapes suivantes :

  • Chaque dev dispose de son propre environnement local pour tester l'application pendant la phase de développement.
  • Pour chaque nouvelle fonctionnalité, il crée une nouvelle branche. Une fois l'opération terminée, il demande une révision du code (par exemple, une requête de merge).
  • La nouvelle fonctionnalité passe par des tests unitaires automatisés et de bout en bout sur un environnement de préparation spécifique.
  • Chaque dev impliqué dans la révision du code peut vouloir tester les modifications dans son propre environnement avant de les approuver.
  • Une fois la fonctionnalité approuvée, elle est fusionnée dans la branche de déploiement principale et déployée en production.

Le principal défi du cycle mentionné ci-dessus est de s'assurer que toutes les modifications apportées aux environnements sur lesquels la fonctionnalité est basée sont correctement reflétées dans tous les environnements développement ainsi qu'en production. Cela est particulièrement difficile lorsque les devs doivent réviser et tester le code de leurs pairs sans affecter leurs propres environnements de travail.

L'IaC contribue à rationaliser ce processus, car la configuration est stockée sous forme de code/scripts et transmise avec le code, ce qui permet d'appliquer facilement toute modification à l'environnement et de revenir en arrière sans intervention manuelle, souvent source d'erreurs.

Mettons cela en pratique avec un exemple.

Twilio TaskRouter et IAC

Dans cet exemple, nous allons nous concentrer sur Twilio TaskRouter. Toutefois, les principaux concepts peuvent être réutilisés pour tous les autres produits et ressources Twilio. Twilio TaskRouter est un moteur de routage basé sur les compétences pour les centres de contact. Il permet aux centres de contact de faire correspondre les tâches (telles que les appels entrants ou les SMS) aux collaborateurs appropriés. Si vous souhaitez en savoir plus sur TaskRouter et sur la mise en œuvre d'un routage simple basé sur le langage, regardez cette excellente vidéo.

Dans cet exemple, nous allons utiliser une logique de routage similaire, mais nous allons la mettre en œuvre en tant qu'IaC à l'aide de Pulumi. Commençons par initialiser notre projet : pour ce faire, vous devez installer un plug-in pour la CLI Twilio (voir la section sur les conditions préalables ci-dessus) nommé plugin-twilio-infra. Ce plug-in automatise la plupart des étapes nécessaires à la configuration d'un nouveau projet Pulumi. Ce plug-in peut être installé à l'aide de la commande suivante à partir d'un terminal ou d'une invite de commande :

twilio plugins:install twilio-infra-as-code/plugin-twilio-infra

Une fois le plug-in installé, vous pouvez vérifier qu'il fonctionne en utilisant la commande ci-dessous :

twilio infra --help

Créer un nouveau projet

Vous pouvez maintenant créer un nouveau projet qui inclut toutes les dépendances nécessaires. Créez un nouveau dossier my-first-iac-project et cd dans ce dossier :

mkdir my-first-iac-project && cd my-first-iac-project

Initialisons maintenant la CLI Pulumi. Dans cet exemple, nous allons utiliser le système de fichiers local comme système back-end de stockage. Vous pouvez l'initialiser en utilisant la commande ci-dessous :

pulumi login --local

N'hésitez pas à créer un compte Pulumi si vous préférez utiliser leur service pour stocker l'état de votre infrastructure.

Maintenant, utilisons le nouveau plug-in pour initialiser le projet :

twilio infra:new

Vous serez guidé parmi plusieurs choix, tels que les suivants :

  • Nom du projet.
  • Description du projet.
  • Nom de la pile : il s'agit du nom de la pile Pulumi pour ce projet. Vous trouverez ici de plus amples informations sur les piles. En résumé, une pile Pulumi indique différentes phases de développement ou d'environnement de préparation (par exemple, dev).
  • Phrase de passe : utilisée par Pulumi afin de générer une clé unique pour votre pile. Cette option n'est utilisée que si la CLI Pulumi utilise votre système de fichiers local comme back-end de stockage. N'oubliez pas la phrase de passe, car vous devrez accéder à la pile.

À la fin de ce processus, le dossier sera initialisé avec les fichiers suivants :

  • package.json : décrit votre projet et ses dépendances.
  • .gitignore : permet d'exclure des fichiers si vous utilisez git pour gérer votre code. Vous pouvez l'ignorer en toute sécurité si vous n'utilisez pas git
  • /node_modules : ce dossier contient toutes les dépendances, en particulier le client JavaScript twilio et le twilio-pulumi-provider.
  • Pulumi.yaml : ce fichier décrit votre projet Pulumi, à savoir le nom, la description et l'environnement d'exécution du projet. Le plug-in de la CLI Twilio définit l'environnement comme nodejs par défaut. La raison en est que le fournisseur dynamique Twilio (c'est-à-dire twilio-pulumi-provider) est actuellement disponible pour NodeJS uniquement.
  • index.js : c'est là que sera placé votre code de configuration. Nous reviendrons en détail sur ce point dans la section suivante.

Décrire l'infrastructure à l'aide de code

Dans cet exemple, nous allons créer une stratégie de routage au sein de TaskRouter de Twilio, qui aura deux files d'attente (en anglais et en espagnol) où les agents répondront aux appels. Pour construire ce projet à partir de zéro, vous aurez besoin d'un espace de travail (Workspace) TaskRouter, de deux files d'attente de tâches (TaskQueues), d'un flux de travail (Workflow) et d'au moins deux collaborateurs (Workers).

Pour décrire l'espace de travail TaskRouter à l'aide du fournisseur dynamique Twilio, ajoutez le code suivant à votre fichier index.js :

"use strict";
const pulumi = require("@pulumi/pulumi");

const { Resource } = require('twilio-pulumi-provider');

const workspace = new Resource("example-workspace", {
    resource: ["taskrouter", "workspaces"],
    attributes: {
        friendlyName: "Workspace created with Pulumi"
    }
});

Notez que la description d'un composant est aussi simple que l'instanciation d'un objet. Dans ce cas, nous utilisons la classe Resource exposée par le package twilio-pulumi-provider. Le premier argument est son ID, qui est utilisé par Pulumi pour effectuer le suivi de son état. Le deuxième argument est un objet décrivant le point de terminaison d'API et ses attributs :

  • resources : l'URL de l'API décomposée en éléments de tableau. Par exemple, dans ce cas, l'URL du point de terminaison d'API pour la création d'un espace de travail est https://taskrouter.twilio.com/v1/Workspaces. Nous utilisons le sous-domaine ( taskrouter) comme premier élément du tableau et le chemin (workspace) comme deuxième élément du tableau. Nous verrons plus tard comment convertir d'autres URL d'API en tableaux.
  • attributes : un objet qui décrit les paramètres des ressources que vous créez ou modifiez. Par exemple, dans ce cas, il s'agit des paramètres d'une nouvelle ressource d'espace de travail Twilio (vous pouvez consulter la documentation sur l'API pour obtenir plus de détails).

Il est important de noter que la façon dont nous décrivons la ressource avec le chemin d'API n'est pas le modèle le plus couramment utilisé par Pulumi ou d'autres plateformes IaC. Dans le cas du package twilio-pulumi-provider, nous avons choisi cette méthode, car elle évite de gérer l'implémentation d'une classe de ressource pour chaque point de terminaison ou de la mettre à jour avec la publication d'un nouveau point de terminaison d'API.

Ajoutons ensuite les files d'attente de tâches (TaskQueues), une pour l'anglais et une pour l'espagnol :

"use strict";
const pulumi = require("@pulumi/pulumi");

const { Resource } = require('twilio-pulumi-provider');

const workspace = new Resource("example-workspace", {
    resource: ["taskrouter", "workspaces"],
    attributes: {
        friendlyName: "Example Pulumi Workspace!"
    }
});

const TaskqueueEnglish = new Resource('taskqueue-english', {
  resource: [
    'taskrouter',
    { workspaces: workspace.sid },
    'taskQueues',
  ],
  attributes: {
    targetWorkers: `languages HAS "en"`,
    friendlyName: 'English Queue',
  },
});

const TaskqueueSpanish = new Resource('taskqueue-spanish', {
  resource: [
    'taskrouter',
    { workspaces: workspace.sid },
    'taskQueues',
  ],
  attributes: {
    targetWorkers: `languages HAS "es"`,
    friendlyName: 'Spanish Queue',
  },
});

Lors de la création des ressources TaskQueue, nous devons transmettre le SID (ou l'ID unique) de l'espace de travail TaskRouter. En fait, lorsque vous utilisez l'API, l'URL est :https://taskrouter.twilio.com/v1/Workspaces/{WorkspaceSid}/TaskQueues, où {WorkspaceSid} correspond au SID de l'espace de travail auquel appartient la nouvelle file d'attente de tâches. Pour ce faire, nous utilisons l'attribut sid de la ressource workspace créée à l'étape précédente. La capacité à référencer d'autres ressources et leurs attributs (par exemple, le SID, le nom convivial, etc.) est l'une des caractéristiques les plus importantes des plateformes IaC.

Créons maintenant les deux collaborateurs (Workers), en ajoutant les définitions de ressource workerOne et workerTwo au bas du fichier :

"use strict";
const pulumi = require("@pulumi/pulumi");

const { Resource } = require('twilio-pulumi-provider');

const workspace = new Resource("example-workspace", {
    resource: ["taskrouter", "workspaces"],
    attributes: {
        friendlyName: "Example Pulumi Workspace!"
    }
});

// ....

const workerOne = new Resource('worker-one', {
  resource: [
    'taskrouter',
    { workspaces: workspace.sid },
    'workers',
  ],
  attributes: {
    friendlyName: 'Alice',
    attributes: JSON.stringify({ languages: ['en', 'es'] }),
  },
});

const workerTwo = new Resource('worker-two', {
  resource: [
    'taskrouter',
    { workspaces: workspace.sid },
    'workers',
  ],
  attributes: {
    friendlyName: 'Bob',
    attributes: JSON.stringify({ languages: ['en'] }),
  },
});

Enfin, nous allons rassembler toutes les ressources et ajouter le code du flux de travail (Workflow) au bas du fichier. Veillez à remplacer assignmentCallbackUrl par l'adresse de votre webhook :

// ...

const WorkflowIncomingRequests = new Resource('workflow-incoming-requests', {
  resource: [
    'taskrouter',
    { workspaces: workspace.sid },
    'workflows',
  ],
  attributes: {
    assignmentCallbackUrl: 'http://example.org',
    friendlyName: 'Incoming Customer Care Requests',
    taskReservationTimeout: 1200,
    configuration: pulumi
      .all([TaskqueueEnglish.sid, TaskqueueSpanish.sid])
      .apply(([englishTaskQueueSid, spanishTaskQueueSid]) =>
        JSON.stringify({
          task_routing: {
            filters: [
              {
                friendlyName: 'Language - Spanish',
                expression: `selected_language=='es'`,
                targets: [
                  {
                    queue: spanishTaskQueueSid,
                  },
                ],
              },
              {
                friendlyName: 'Language - English',
                targets: [
                  {
                    queue: englishTaskQueueSid,
                  },
                ],
                expression: `selected_language=='en'`,
              },
            ],
          },
        })
      ),
    },
  });

Dans le code ci-dessus, nous utilisons pulumi.all().apply() pour générer l'élément configuration du flux de travail. Nous devons l'utiliser, car les attributs des ressources que nous avons créées précédemment dans le fichier ne sont pas directement accessibles en tant que propriétés des objets de ressource (c'est-à-dire que vous ne pouvez pas simplement utiliser TaskqueueEnglish.sid pour accéder au SID de la file d'attente TaskqueueEnglish). Avec Pulumi, nous devons attendre que les deux SID de TaskQueue génèrent une charge utile sous forme de chaîne, puis appliquer cette valeur comme entrée de configuration. Pour en savoir plus sur la façon dont Pulumi gère les entrées et les sorties, reportez-vous à la section relative aux entrées et aux sorties du modèle de programmation Pulumi.

Créer des ressources

Vous pouvez maintenant tester la mise en œuvre et créer les ressources réelles dans ce projet Twilio.

Tout d'abord, assurez-vous que votre CLI Twilio est connectée au projet sur lequel vous souhaitez déployer ces ressources. Si vous n'êtes pas sûr du projet auquel vous êtes connecté, vous pouvez utiliser twilio profiles:list afin de répertorier tous les profils, puis twilio profiles:use afin de sélectionner celui que vous souhaitez utiliser. Si votre projet n'est pas répertorié, utilisez twilio profiles:create afin de vous connecter à un nouveau projet Twilio, puis exécutez twilio profiles:use <profile_name> pour le sélectionner.

Utilisons à présent la commande twilio infra:deploy de la CLI pour déployer les ressources que nous avons créées :

twilio infra:deploy

Previewing update (dev):
     Type                               Name                        Plan       
 +   pulumi:pulumi:Stack                my-first-iac-project-dev    create     
 +   ├─ pulumi-nodejs:dynamic:Resource  example-workspace           create     
 +   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-spanish           create     
 +   ├─ pulumi-nodejs:dynamic:Resource  worker-one                  create     
 +   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-english           create     
 +   ├─ pulumi-nodejs:dynamic:Resource  worker-two                  create     
 +   └─ pulumi-nodejs:dynamic:Resource  workflow-incoming-requests  create     

Resources:
    + 7 to create

Do you want to perform this update?  [Use arrows to move, enter to select, type to filter]
  yes
> no
  details

Cette première partie de la sortie de commande donne un aperçu des ressources qui seront créées. Si cela vous convient, sélectionnez yes et appuyez sur Entrée. Si tout s'est correctement exécuté, vous devriez voir ce qui suit :

Updating (dev):
     Type                               Name                        Status      
 +   pulumi:pulumi:Stack                my-first-iac-project-dev    created     
 +   ├─ pulumi-nodejs:dynamic:Resource  example-workspace           created     
 +   ├─ pulumi-nodejs:dynamic:Resource  worker-two                  created     
 +   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-english           created     
 +   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-spanish           created     
 +   ├─ pulumi-nodejs:dynamic:Resource  worker-one                  created     
 +   └─ pulumi-nodejs:dynamic:Resource  workflow-incoming-requests  created     

Resources:
    + 7 created

Si vous accédez maintenant à votre console Twilio pour passer en revue les Espaces de travail TaskRouter, vous verrez qu'un nouvel espace de travail a été créé, portant le nom Workspace created with Pulumi. Si vous cliquez dessus, vous devriez voir que les deux files d'attente et les deux collaborateurs ont été créés :

Capture d'écran de l'espace de travail Taskrouter

Modifier une ressource existante

Vous pouvez aussi créer une nouvelle ressource Twilio facilement via les API. L'approche IaC est particulièrement efficace lorsque vous devez modifier une ressource.

Nous allons expliquer cela par un exemple. Supposons que vous travailliez en équipe et que chaque dev dispose de son propre projet Twilio. Tout au long du cycle de développement, vous devez vous assurer que tous les devs disposent des mêmes ressources dans leur projet. L'approche traditionnelle (API) serait comme suit :

  • Au début du projet, distribuez un script qui utilise des API/SDK pour créer toutes les ressources nécessaires
  • Tous les devs exécuteront le script pour approvisionner leur compte
  • Chaque fois qu'il est nécessaire de modifier une ressource, vous devez distribuer un nouveau script qui met à jour la ou les ressources Twilio
  • Si un nouveau dev rejoint l'équipe (ou si un membre existant souhaite lancer un nouveau projet), il doit exécuter le premier script pour le provisionnement et tous les scripts suivants pour effectuer les modifications (dans l'ordre spécifique)

Cette approche n'est pas évolutive et vous risquez de devoir gérer plusieurs scripts.

Avec l'approche IaC, il vous suffit de modifier le script d'origine pour ajouter/modifier/supprimer des ressources, et de valider (commit) le script dans votre répertoire de code partagé.

Mettons cela en pratique. Supposons que vous vouliez modifier le nom du collaborateur, passant de Alice à Maria. Pour ce faire, il suffit de modifier la propriété de workerOne dans le code :

...
const workerOne = new Resource('worker-one', {
  resource: ['taskrouter', { workspaces: workspace.sid }, 'workers'],
  attributes: {
    // friendlyName: 'Alice',
    friendlyName: 'Maria',
    attributes: JSON.stringify({ languages: ['en', 'es'] }),
  },
});
…

Si vous exécutez à nouveau la commande de déploiement, vous verrez ce qui suit :

Previewing update (dev):
     Type                               Name                      Plan       Info
     pulumi:pulumi:Stack                my-first-iac-project-dev             
 ~   └─ pulumi-nodejs:dynamic:Resource  worker-one                update     [diff: ~attributes]

Resources:
    ~ 1 to update
    6 unchanged

Cela signifie qu'une ressource (« worker-one ») sera mise à jour car l'un des éléments (attributes) a changé par rapport au dernier déploiement. Sélectionnez yes et appuyez sur Entrée. Vous verrez ce qui suit :

Updating (dev):
     Type                               Name                      Status      Info
     pulumi:pulumi:Stack                my-first-iac-project-dev              
 ~   └─ pulumi-nodejs:dynamic:Resource  worker-one                updated     [diff: ~attributes]

Resources:
    ~ 1 updated
    6 unchanged

Comme vous pouvez le voir dans la sortie de commande, Pulumi a détecté une modification de worker-one et a modifié la ressource déployée en conséquence. Si vous accédez à la page dédiée aux espaces de travail TaskRouter dans la console Twilio, vous verrez que le nom du collaborateur a changé :

Collaborateurs TaskRouter mis à jour

Pour vous assurer que tous les autres devs mettent à jour leur espace de travail, vous devez distribuer (par exemple, valider le fichier dans un répertoire partagé) le nouveau fichier de configuration (index.js dans notre cas) et leur demander d'exécuter la commande de déploiement (par exemple, ajouter le script de déploiement comme post-mergegithook)

De plus, si le script IaC fait partie d'un pipeline CI/CD, les ressources de préparation/production seront mises à jour en conséquence lors du prochain déploiement (plus d'informations à ce sujet dans la deuxième partie de cette série de blog).

Détruire des ressources

Après cet exemple, vous souhaitez peut-être supprimer les ressources que vous venez de créer. Avec l'IaC, cette opération est très facile. Il suffit d'exécuter la commande suivante :

twilio infra:destroy

Après avoir confirmé que vous souhaitez effectuer l'opération, toutes les ressources créées sont supprimées de votre projet :

Destroying (dev):
     Type                                                         Name                                          Status      
 -   pulumi:pulumi:Stack                              my-first-iac-project-dev            deleted     
 -   ├─ pulumi-nodejs:dynamic:Resource  workflow-incoming-requests  deleted     
 -   ├─ pulumi-nodejs:dynamic:Resource  worker-two                               deleted     
 -   ├─ pulumi-nodejs:dynamic:Resource  worker-one                               deleted     
 -   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-english                   deleted     
 -   ├─ pulumi-nodejs:dynamic:Resource  taskqueue-spanish                  deleted     
 -   └─ pulumi-nodejs:dynamic:Resource  example-workspace                deleted     

Resources:
    - 7 deleted

Conclusion

Comme vous pouvez le constater, l'IaC apporte de nombreux avantages au cycle de développement. Cette approche contribue à rationaliser la création de ressources dans tous les environnements (développement, préparation, production) et à effectuer le suivi de toutes les modifications apportées aux ressources. Vous pouvez l'intégrer facilement à votre pipeline CI/CD, réduisant ainsi les erreurs humaines tout en accélérant le déploiement. Selon l'outil IaC utilisé, les devs n'ont pas besoin d'apprendre à utiliser un nouveau langage ou de nouvelles API pour configurer les ressources en amont. Et nous n'avons abordé qu'en surface la façon dont l'IaC peut vous aider à faire évoluer votre projet. Dans les prochaines parties de la série, nous allons voir comment intégrer l'IaC dans le pipeline et nous allons étudier plus en détail certaines des fonctionnalités offertes par Pulumi.

Étapes suivantes