Zéro temps d'arrêt Déploiement continu Jenkins avec Terraform sur AWS
Publié: 2022-03-11Dans le monde d'Internet d'aujourd'hui où tout doit fonctionner 24h/24 et 7j/7, la fiabilité est essentielle. Cela se traduit par un temps d'arrêt quasi nul pour vos sites Web, en évitant la redoutable page d'erreur « Introuvable : 404 », ou d'autres interruptions de service pendant que vous déployez votre dernière version.
Supposons que vous ayez créé une nouvelle application pour votre client, ou peut-être pour vous-même, et que vous ayez réussi à obtenir une bonne base d'utilisateurs qui aiment votre application. Vous avez recueilli les commentaires de vos utilisateurs et vous vous adressez à vos développeurs pour leur demander de créer de nouvelles fonctionnalités et de préparer l'application pour le déploiement. Une fois cela prêt, vous pouvez soit arrêter l'intégralité de l'application et déployer la nouvelle version, soit créer un pipeline de déploiement CI/CD sans temps d'arrêt qui effectuerait tout le travail fastidieux consistant à envoyer une nouvelle version aux utilisateurs sans intervention manuelle.
Dans cet article, nous parlerons exactement de ce dernier, comment nous pouvons avoir un pipeline de déploiement continu d'une application Web à trois niveaux construite en Node.js sur AWS Cloud en utilisant Terraform comme orchestrateur d'infrastructure. Nous utiliserons Jenkins pour la partie déploiement continu et Bitbucket pour héberger notre base de code.
Référentiel de code
Nous utiliserons une application Web de démonstration à trois niveaux pour laquelle vous pouvez trouver le code ici.
Le référentiel contient du code pour le Web et la couche API. Il s'agit d'une application simple dans laquelle le module Web appelle l'un des points de terminaison de la couche API qui récupère en interne des informations sur l'heure actuelle à partir de la base de données et retourne à la couche Web.
La structure du dépôt est la suivante :
- API : code pour la couche API
- Web : code pour la couche Web
- Terraform : code pour l'orchestration de l'infrastructure à l'aide de Terraform
- Jenkins : code pour l'orchestrateur d'infrastructure pour le serveur Jenkins utilisé pour le pipeline CI/CD.
Maintenant que nous comprenons ce que nous devons déployer, discutons de ce que nous devons faire pour déployer cette application sur AWS, puis nous verrons comment intégrer cette application au pipeline CI/CD.
Images de cuisson
Étant donné que nous utilisons Terraform pour l'orchestrateur d'infrastructure, il est plus logique d'avoir des images prédéfinies pour chaque niveau ou application que vous souhaitez déployer. Et pour cela, nous utiliserions un autre produit de Hashicorp, c'est-à-dire Packer.
Packer est un outil open source qui aide à créer une Amazon Machine Image ou AMI, qui sera utilisée pour le déploiement sur AWS. Il peut être utilisé pour créer des images pour différentes plates-formes telles que EC2, VirtualBox, VMware et autres.
Voici un extrait de la façon dont le fichier de configuration Packer ( terraform/packer-ami-api.json
) est utilisé pour créer une AMI pour la couche API.
{ "builders": [{ "type": "amazon-ebs", "region": "eu-west-1", "source_ami": "ami-844e0bf7", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "api-instance {{timestamp}}" }], "provisioners": [ { "type": "shell", "inline": ["mkdir api", "sudo apt-get update", "sudo apt-get -y install npm nodejs-legacy"], "pause_before": "10s" }, { "type": "file", "source" : "../api/", "destination" : "api" }, { "type": "shell", "inline": ["cd api", "npm install"], "pause_before": "10s" } ] }
Et vous devez exécuter la commande suivante pour créer l'AMI :
packer build -machine-readable packer-ami-api.json
Nous exécuterons cette commande à partir de la version Jenkins plus loin dans cet article. De la même manière, nous utiliserons également le fichier de configuration Packer ( terraform/packer-ami-web.json
) pour la couche Web.
Passons en revue le fichier de configuration Packer ci-dessus et comprenons ce qu'il essaie de faire.
- Comme mentionné précédemment, Packer peut être utilisé pour créer des images pour de nombreuses plates-formes, et puisque nous déployons notre application sur AWS, nous utiliserons le constructeur "amazon-ebs", car c'est le constructeur le plus facile à utiliser.
- La deuxième partie de la configuration prend une liste d'approvisionneurs qui ressemblent davantage à des scripts ou à des blocs de code que vous pouvez utiliser pour configurer votre image.
- L'étape 1 exécute un fournisseur de shell pour créer un dossier d'API et installer Node.js sur l'image à l'aide de la propriété
inline
, qui est un ensemble de commandes que vous souhaitez exécuter. - L' étape 2 exécute un fournisseur de fichiers pour copier notre code source du dossier API sur l'instance.
- L'étape 3 exécute à nouveau un fournisseur de shell, mais cette fois utilise une propriété de script pour spécifier un fichier (terraform/scripts/install_api_software.sh) avec les commandes qui doivent être exécutées.
- L' étape 4 copie un fichier de configuration sur l'instance nécessaire pour Cloudwatch, qui est installé à l'étape suivante.
- L' étape 5 exécute un fournisseur de shell pour installer l'agent AWS Cloudwatch. L'entrée de cette commande serait le fichier de configuration copié à l'étape précédente. Nous parlerons de Cloudwatch en détail plus loin dans l'article.
- L'étape 1 exécute un fournisseur de shell pour créer un dossier d'API et installer Node.js sur l'image à l'aide de la propriété
Donc, essentiellement, la configuration de Packer contient des informations sur le constructeur que vous voulez, puis un ensemble d'approvisionneurs que vous pouvez définir dans n'importe quel ordre en fonction de la façon dont vous souhaitez configurer votre image.
Configurer un déploiement continu Jenkins
Ensuite, nous examinerons la configuration d'un serveur Jenkins qui sera utilisé pour notre pipeline CI/CD. Nous utiliserons également Terraform et AWS pour le configurer.
Le code Terraform pour configurer Jenkins se trouve dans le dossier jenkins/setup
. Passons en revue certaines des choses intéressantes à propos de cette configuration.
- Informations d' identification AWS : vous pouvez soit fournir l'ID de clé d'accès AWS et la clé d'accès secrète au fournisseur Terraform AWS (
instance.tf
), soit indiquer l'emplacement du fichier d'informations d'identification à la propriétéshared_credentials_file
dans le fournisseur AWS. - Rôle IAM : étant donné que nous exécuterons Packer et Terraform à partir du serveur Jenkins, ils accéderont aux services S3, EC2, RDS, IAM, d'équilibrage de charge et d'autoscaling sur AWS. Donc, soit nous fournissons nos informations d'identification sur Jenkins pour Packer & Terraform pour accéder à ces services, soit nous pouvons créer un profil IAM (
iam.tf
), à l'aide duquel nous créerions une instance Jenkins. - État de Terraform : Terraform doit maintenir l'état de l'infrastructure quelque part dans un fichier et, avec S3 (
backend.tf
), vous pouvez simplement le maintenir là, afin que vous puissiez collaborer avec d'autres collègues, et n'importe qui peut changer et déployer depuis l'état est maintenu dans un endroit éloigné. - Paire de clés publique/privée : vous devrez télécharger la clé publique de votre paire de clés avec l'instance afin de pouvoir vous connecter en ssh à l'instance Jenkins une fois qu'elle est opérationnelle. Nous avons défini une ressource
aws_key_pair
(key.tf
) dans laquelle vous spécifiez l'emplacement de votre clé publique à l'aide de variables Terraform.
Étapes pour configurer Jenkins :
Étape 1 : Pour conserver l'état distant de Terraform, vous devez créer manuellement un compartiment dans S3 qui peut être utilisé par Terraform. Ce serait la seule étape effectuée en dehors de Terraform. Assurez-vous d'exécuter AWS configure
avant d'exécuter la commande ci-dessous pour spécifier vos informations d'identification AWS.
aws s3api create-bucket --bucket node-aws-jenkins-terraform --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1
Étape 2 : Exécutez terraform init
. Cela initialisera l'état et le configurera pour qu'il soit stocké sur S3 et téléchargera le plug-in du fournisseur AWS.
Étape 3 : Exécutez terraform apply
. Cela vérifiera tout le code Terraform et créera un plan et montrera combien de ressources seront créées une fois cette étape terminée.
Étape 4 : Tapez yes
, puis l'étape précédente commencera à créer toutes les ressources. Une fois la commande terminée, vous obtiendrez l'adresse IP publique du serveur Jenkins.
Étape 5 : Ssh dans le serveur Jenkins, en utilisant votre clé privée. ubuntu
est le nom d'utilisateur par défaut pour les instances basées sur AWS EBS. Utilisez l'adresse IP renvoyée par la commande terraform apply
.
ssh -i mykey [email protected]
Étape 6 : Démarrez l'interface utilisateur Web Jenkins en accédant à http://34.245.4.73:8080
. Le mot de passe se trouve dans /var/lib/jenkins/secrets/initialAdminPassword
.
Étape 7 : Choisissez "Installer les plugins suggérés" et créez un utilisateur administrateur pour Jenkins.
Configuration du pipeline CI entre Jenkins et Bitbucket
- Pour cela, nous devons installer le plugin Bitbucket dans Jenkins. Accédez à Gérer Jenkins → Gérer les plugins et à partir des plugins disponibles , installez le plugin Bitbucket.
- Du côté du référentiel Bitbucket, accédez à Paramètres → Webhooks , ajoutez un nouveau webhook. Ce crochet enverra toutes les modifications du référentiel à Jenkins et cela déclenchera les pipelines.
Jenkins Pipeline pour créer/construire des images
- La prochaine étape consistera à créer des pipelines dans Jenkins.
- Le premier pipeline sera un projet Freestyle qui sera utilisé pour créer l'AMI de l'application à l'aide de Packer.
- Vous devez spécifier les informations d'identification et l'URL de votre référentiel Bitbucket.
- Spécifiez le déclencheur de génération.
- Ajoutez deux étapes de génération, une pour créer l'AMI pour le module d'application et les autres pour créer l'AMI pour le module Web.
- Une fois cela fait, vous pouvez enregistrer le projet Jenkins et maintenant, lorsque vous poussez quoi que ce soit vers votre référentiel Bitbucket, cela déclenchera une nouvelle construction dans Jenkins qui créerait l'AMI et pousserait un fichier Terraform contenant le numéro AMI de cette image vers le Compartiment S3 que vous pouvez voir sur les deux dernières lignes de l'étape de génération.
echo 'variable "WEB_INSTANCE_AMI" { default = "'${AMI_ID_WEB}'" }' > amivar_web.tf aws s3 cp amivar_web.tf s3://node-aws-jenkins-terraform/amivar_web.tf
Pipeline Jenkins pour déclencher le script Terraform
Maintenant que nous avons les AMI pour l'API et les modules Web, nous allons déclencher une génération pour exécuter le code Terraform afin de configurer l'ensemble de l'application, puis passer en revue les composants du code Terraform, ce qui permet à ce pipeline de déployer les modifications sans interruption de service.

- Nous créons un autre projet Jenkins freestyle,
nodejs-terraform
, qui exécuterait le code Terraform pour déployer l'application. - Nous allons d'abord créer un identifiant de type "texte secret" dans le domaine global des identifiants, qui sera utilisé comme entrée du script Terraform. Comme nous ne voulons pas coder en dur le mot de passe du service RDS dans Terraform et Git, nous transmettons cette propriété à l'aide des informations d'identification Jenkins.
- Vous devez définir les informations d'identification et l'URL similaires à l'autre projet.
- Dans la section build trigger, nous allons lier ce projet à l'autre de manière à ce que ce projet démarre lorsque le précédent est terminé.
- Ensuite, nous pourrions configurer les informations d'identification que nous avons ajoutées précédemment au projet à l'aide de liaisons, afin qu'elles soient disponibles à l'étape de construction.
- Nous sommes maintenant prêts à ajouter une étape de génération, qui téléchargera les fichiers de script Terraform (
amivar_api.tf
etamivar_web.tf
) qui ont été téléchargés sur S3 par le projet précédent, puis exécutera le code Terraform pour créer l'intégralité de l'application sur AWS.
Si tout est configuré correctement, maintenant si vous poussez du code vers votre référentiel Bitbucket, il devrait déclencher le premier projet Jenkins suivi du second et vous devriez avoir votre application déployée sur AWS.
Terraform Zero Downtime Config pour AWS
Voyons maintenant ce qu'il y a dans le code Terraform qui permet à ce pipeline de déployer le code sans aucun temps d'arrêt.
La première chose est que Terraform fournit ces blocs de configuration du cycle de vie pour les ressources dans lesquelles vous avez une option create_before_destroy
comme indicateur, ce qui signifie littéralement que Terraform doit créer une nouvelle ressource du même type avant de détruire la ressource actuelle.
Nous exploitons maintenant cette fonctionnalité dans les ressources aws_autoscaling_group
et aws_launch_configuration
. Ainsi, aws_launch_configuration
configure le type d'instance EC2 à provisionner et la manière dont nous installons le logiciel sur cette instance, et la ressource aws_autoscaling_group
fournit un groupe de mise à l'échelle automatique AWS.
Un hic intéressant ici est que toutes les ressources de Terraform doivent avoir une combinaison unique de nom et de type. Ainsi, à moins que vous n'ayez un nom différent pour les nouveaux aws_autoscaling_group
et aws_launch_configuration
, il ne sera pas possible de détruire l'actuel.
Terraform gère cette contrainte en fournissant une propriété name_prefix
à la ressource aws_launch_configuration
. Une fois cette propriété définie, Terraform ajoutera un suffixe unique à toutes les ressources aws_launch_configuration
, puis vous pourrez utiliser ce nom unique pour créer une ressource aws_autoscaling_group
.
Vous pouvez vérifier le code pour tout ce qui précède dans terraform/autoscaling-api.tf
resource "aws_launch_configuration" "api-launchconfig" { name_prefix = "api-launchconfig-" image_ instance_type = "t2.micro" security_groups = ["${aws_security_group.api-instance.id}"] user_data = "${data.template_file.api-shell-script.rendered}" iam_instance_profile = "${aws_iam_instance_profile.CloudWatchAgentServerRole-instanceprofile.name}" connection { user = "${var.INSTANCE_USERNAME}" private_key = "${file("${var.PATH_TO_PRIVATE_KEY}")}" } lifecycle { create_before_destroy = true } } resource "aws_autoscaling_group" "api-autoscaling" { name = "${aws_launch_configuration.api-launchconfig.name}-asg" vpc_zone_identifier = ["${aws_subnet.main-public-1.id}"] launch_configuration = "${aws_launch_configuration.api-launchconfig.name}" min_size = 2 max_size = 2 health_check_grace_period = 300 health_check_type = "ELB" load_balancers = ["${aws_elb.api-elb.name}"] force_delete = true lifecycle { create_before_destroy = true } tag { key = "Name" value = "api ec2 instance" propagate_at_launch = true } }
Et le deuxième défi avec des déploiements sans temps d'arrêt est de s'assurer que votre nouveau déploiement est prêt à commencer à recevoir la demande. Le simple déploiement et le démarrage d'une nouvelle instance EC2 ne suffisent pas dans certaines situations.
Pour résoudre ce problème, aws_launch_configuration
a une propriété user_data
qui prend en charge la propriété native AWS user_data
à l'aide de laquelle vous pouvez transmettre n'importe quel script que vous souhaitez exécuter au démarrage de nouvelles instances dans le cadre du groupe d'autoscaling. Dans notre exemple, nous suivons le journal du serveur d'application et attendons que le message de démarrage soit là. Vous pouvez également vérifier le serveur HTTP et voir quand ils sont en place.
until tail /var/log/syslog | grep 'node ./bin/www' > /dev/null; do sleep 5; done
Parallèlement à cela, vous pouvez également activer une vérification ELB au niveau de la ressource aws_autoscaling_group
, ce qui garantira que la nouvelle instance a été ajoutée pour réussir la vérification ELB avant que Terraform ne détruise les anciennes instances. Voici à quoi ressemble la vérification ELB pour la couche API ; il vérifie que le point de terminaison /api/status
renvoie le succès.
resource "aws_elb" "api-elb" { name = "api-elb" subnets = ["${aws_subnet.main-public-1.id}"] security_groups = ["${aws_security_group.elb-securitygroup.id}"] listener { instance_port = "${var.API_PORT}" instance_protocol = "http" lb_port = 80 lb_protocol = "http" } health_check { healthy_threshold = 2 unhealthy_threshold = 2 timeout = 3 target = "HTTP:${var.API_PORT}/api/status" interval = 30 } cross_zone_load_balancing = true connection_draining = true connection_draining_timeout = 400 tags { Name = "my-elb" } }
Résumé et prochaines étapes
Cela nous amène donc à la fin de cet article; avec un peu de chance, à ce jour, soit votre application est déjà déployée et fonctionne avec un pipeline CI/CD sans temps d'arrêt en utilisant un déploiement Jenkins et les meilleures pratiques Terraform, soit vous êtes un peu plus à l'aise pour explorer ce territoire et faire en sorte que vos déploiements nécessitent aussi peu d'intervention manuelle que possible. possible.
Dans cet article, la stratégie de déploiement utilisée est appelée déploiement bleu-vert dans lequel nous avons une installation actuelle (bleu) qui reçoit le trafic en direct pendant que nous déployons et testons la nouvelle version (vert), puis nous les remplaçons une fois la nouvelle version est tout prêt. Outre cette stratégie, il existe d'autres façons de déployer votre application, qui sont bien expliquées dans cet article, Introduction aux stratégies de déploiement. Adapter une autre stratégie est maintenant aussi simple que de configurer votre pipeline Jenkins.
De plus, dans cet article, j'ai supposé que toutes les nouvelles modifications apportées à l'API, au Web et aux couches de données sont compatibles, vous n'avez donc pas à vous soucier du fait que la nouvelle version communique avec une version plus ancienne. Mais, en réalité, ce n'est peut-être pas toujours le cas. Pour résoudre ce problème, lors de la conception de votre nouvelle version/fonctionnalités, pensez toujours à la couche de compatibilité descendante, sinon vous devrez modifier vos déploiements pour gérer également cette situation.
Les tests d'intégration manquent également dans ce pipeline de déploiement. Comme vous ne voulez pas que quoi que ce soit soit livré à l'utilisateur final sans avoir été testé, c'est certainement quelque chose à garder à l'esprit lorsque vient le temps d'appliquer ces stratégies à vos propres projets.
Si vous souhaitez en savoir plus sur le fonctionnement de Terraform et sur la manière dont vous pouvez le déployer sur AWS à l'aide de la technologie, je vous recommande Terraform AWS Cloud : Sane Infrastructure Management où son collègue Toptaler Radoslaw Szalski explique Terraform et vous montre ensuite les étapes nécessaires pour configurer un multi -Configuration d'un environnement et d'une configuration Terraform prête pour la production pour une équipe