Déployer Jenkins sur AWS Fargate
Déploiement et configuration automatisés de Jenkins sur AWS Fargate avec Terraform et Jenkins Configuration as Code.
Table des matières
Dans cet article, nous verrons comment déployer automatiquement Jenkins sur AWS Fargate avec Terraform et le plugin Jenkins Configuration as Code. Cette stack Terraform pourra servir de base pour ceux voulant aller plus loin dans le déploiement et l’administration d’un Jenkins prod-ready sur AWS.
Le déploiement de Jenkins sur Fargate est désormais possible grâce à la mise à jour récente de Fargate qui passe de la version 1.3.0 à la version 1.4.0. Cette mise à jour apporte le support d’Amazon Elastic File System (EFS) ainsi que d’autres changements. Le support EFS était l’un des plus demandés par la communauté. Sans EFS sur Fargate, il était impossible d’avoir un stockage persistant sur Fargate et donc stocker de manière durable la configuration de Jenkins.
À la fin de cet article, vous aurez :
- Un Jenkins Contrôleur (anciennement appelé Master) et sa configuration sur un système de fichier EFS.
- Un agent éphémère configuré sur Fargate et un job d’exemple utilisant cet agent.
- Un accès sécurisé au Controller en HTTPs (à condition d’avoir une zone publique sur Route53).
- Un process automatisé de mise à jour de la configuration du Contrôleur.
- Les logs du Contrôleur et des agents dans CloudWatch.
- Des alertes configurées pour surveiller l’EFS et le Contrôleur.
- Et le tout entièrement automatisé…
v2.1.0
du dépôt. Des modifications seront sans doute apportées après l’écriture de cet article. Veillez
donc à utiliser le tag v2.1.0
pour suivre cet article.N’hésitez pas à mettre en commentaires vos remarques et suggestions ou à contribuer sur GitHub :-)
Infrastructure
Communication Contrôleur <-> Agent
Nous allons déployer Jenkins en mode Contrôleur/Agent avec des agents éphémères. Un agent éphémère est une machine ou un conteneur dont le seul rôle est d’exécuter un ou plusieurs jobs puis s’éteindre par la suite, immédiatement ou après un certain temps. Un agent permanent quant à lui reste actif en attente d’instructions de la part du Contrôleur pour exécuter un ou plusieurs jobs. Dans les deux cas, les informations de l’exécution du job (logs, temps d’exécution, artifacts…) sont remontées au Contrôleur pour être stockées de manière permanente sous forme de fichiers XML principalement.
Dans cette architecture, aucun job ne s’exécute sur le Contrôleur qui se chargera uniquement d’orchestrer l’exécution des différents jobs et fournir une interface graphique aux utilisateurs. Elle présente plusieurs avantages dans la mesure où le plus gros du travail est effectué par les agents. Ainsi, on peut avoir un Contrôleur relativement petit en termes de CPU/RAM tout en disposant d’une grande ferme d’agents.
Jenkins supporte différents protocoles pour assurer la communication Contrôleur <-> Agent. Parmi ces protocoles, nous avons :
- SSH : Avec le protocole SSH, la communication est initiée par le Contrôleur qui se connecte au moment voulu à l’agent en lui envoyant les instructions à lancer. Il doit donc avoir accès aux agents (flux réseaux et clé publique SSH du Contrôleur déployé sur les agents) qui devront avoir Java installé au moins plus d’autres outils nécessaires au build. Ce protocole est surtout utilisé pour des agents permanents à l’aide du plugin “SSH Build Agents” ou le plugin “Amazon EC2” qui lance des instances EC2 à la demande.
- Java Web Start (Java Networking Launching Protocol ou JNLP) : Java Web Start permet de lancer des applications Java à partir d’un navigateur Web en cliquant sur un lien. Jenkins implémente ce protocole pour permettre aux agents JNLP de se connecter au Contrôleur : dans ce cas, la communication est initiée par l’agent et non par le Contrôleur. Le Contrôleur envoie son point d’accès (une URL ou une adresse IP) et un secret permettant à l’agent d’initier la communication qui peut se faire en headless (sans une interface graphique) ou avec une interface graphique. C’est ce dernier protocole que nous allons utiliser en headless avec le plugin Amazon ECS et des agents dans des conteneurs sur Fargate.
Architecture
L’architecture que l’on va mettre en place se présente comme suit :
Nous avons un ALB en frontal constituant le point d’entrée des utilisateurs vers le Jenkins Contrôleur. Il sera déployé dans les sous réseaux publics et écoutera sur les ports HTTP et HTTPs avec une redirection permanente vers HTTPs si vous disposez d’une zone publique sur Route53 (voir plus bas pour les détails).
Un NLB est utilisé pour la communication des agents vers le contrôleur. Ce NLB écoutera sur deux ports : un port pour le protocole JNLP (50000) et un port pour accéder au Contrôleur en HTTP (80 -> 8080). Le port JNLP est configuré au niveau du Contrôleur dans “Manage Jenkins > Configure Global Security > Agents > TCP port inbound agents”.
$JENKINS_HOME/init.groovy.d/
par exemple).Note : le redémarrage de Jenkins n’implique pas le démarrage d’un nouveau conteneur Contrôleur dans ECS.
Un service ECS jenkins-controller
est déployé avec un seul conteneur et attaché à 3 targets groupes : 1 pour
l’ALB en HTTP et 2 pour le NLB en HTTP et JNLP. Le groupe de sécurité du service n’autorise que l’ALB et le NLB en entrée.
L’ALB est autorisée grâce à son groupe de sécurité. Pour le NLB, les IPs de ses interfaces réseaux sont utilisées en ingress sachant
qu’un NLB ne peut avoir de groupe de sécurité. Le lancement des jobs sera désactivé sur le Contrôleur (numExecutors: 0
).
Les agents JNLP ainsi que le Contrôleur seront dans le même cluster ECS pour des raisons de simplicité.
Un système de fichier EFS (a.k.a NFS) stocke la configuration du Contrôleur dans le dossier /var/jenkins_home
. Il a un
point de montage dans chaque sous-réseau privé grâce auquel le conteneur se connecte à travers le port 2049. L’EFS aura un mode de
débit en rafales (mode burst) et
un mode de performance à usage général. Dans ce mode de débit, le système de fichier est
mis à l’échelle en fonction de sa taille avec un débit alloué de 50 Kio/s par Gio.
Dit autrement, plus la taille de la configuration du Contrôleur augmentera, plus les performances de l’EFS seront meilleures.
En mode rafale, des pics de consommation allant au-delà du debit alloué sont autorisés pendant un certain temps par jour.
Ce temps est également déterminé par la taille du système de fichiers.
Plus d’informations dans la documentation AWS.
BurstCreditBalance
. Cette métrique correspond
au nombre de crédits de burst que le système de fichiers possède à un instant t. À la création, le système de fichier dispose de 2,1To
de crédit. Ce crédit diminuera au fil du temps si le débit utilisé est supérieur au débit alloué pendant un certain temps.
Une fois ce crédit à 0, les performances de l’EFS seront grandement dégradées et le Contrôleur deviendra inutilisable.Une solution est de créer de gros fichiers à coup de
dd
dans l’EFS pour gagner en performances ou changer de mode de débit en débit alloué
(coûte relativement plus cher). Plus d’informations sur le monitoring EFS sur cette page.Lors du démarrage du Contrôleur, la configuration est récupérée depuis un bucket S3 versionné puis placée
dans /var/jenkins_home/jenkins.yaml
pour être lue automatiquement par le
plugin Jenkins Configuration as Code (JCasC). Ce plugin
permet de configurer Jenkins de manière déclarative à partir d’un ou plusieurs fichiers YAML. Terraform générera ce
fichier YAML pour le mettre dans le bucket S3. Le template est situé dans templates/jcasc.template.yml
. D’autres
exemples de fichiers de configuration sont disponibles
sur GitHub.
Dans notre cas, nous utilisons ce plugin pour :
- Créer un utilisateur admin avec un mot de passe aléatoire et désactiver les inscriptions des utilisateurs.
- Paramétrer le plugin Amazon ECS pour utiliser notre cluster ECS. Un template ECS ainsi qu’un label
example-agent
sont aussi créés. - Créer un job d’exemple utilisant le label configuré et qui affiche l’identité AWS du build ainsi que les variables d’environnement du build.
- Activer le protocole JNLP sur le port 50000 et désactiver le serveur SSH du Contrôleur.
- Désactiver de l’exécution des jobs sur le Contrôleur.
- …
Deux logs groupes CloudWatch sont utilisés : un pour le jenkins Contrôleur (/jenkins/controller
) et un pour les logs
des agents
(/jenkins/agents
). Les logs des agents correspondent aux logs lors de la communication Agents -> Contrôleur. Chaque
nouvel agent aura un nouveau log stream dédié.
Rôles IAM
4 rôles IAM sont utilisées dans cette stack dont 2 associés au Contrôleur et 2 à l’agent :
- jenkins-controller-ecs-execution : Il s’agit du rôle utilisé par ECS pour envoyer les logs du Contrôleur dans
CloudWatch. On utilise la stratégie gérée par AWS
AmazonECSTaskExecutionRolePolicy
qui donne aussi accès à Amazon ECR pour récupérer des images Docker. - jenkins-controller-ecs-task : Ce rôle est utilisé par le Contrôleur pour récupérer sa configuration depuis S3, lancer et stopper des tâches sur le cluster ECS. Il permet également au Contrôleur de passer les deux rôles plus bas aux agents. C’est ce rôle qu’il faudra modifier si le Contrôleur doit effectuer d’autres actions sur les services AWS (récupération de secrets ou de paramètres depuis SSM par exemple).
- jenkins-agents-ecs-execution : Ce rôle est utilisé par ECS pour envoyer les logs des agents dans CloudWatch. La
stratégie gérée par AWS
AmazonECSTaskExecutionRolePolicy
lui est aussi attachée. - jenkins-agents-ecs-task : Un rôle exemple attaché à notre agent. Si les agents doivent accéder à des ressources AWS pour builder une AMI ou lancer Terraform, c’est ce rôle qu’il faudra modifier. Il n’a aucune permission dans notre stack.
Images docker
Deux images Docker Alpine sont utilisées :
- Une pour le Contrôleur basée sur l’image officielle avec des plugins pré-installés tels que les plugins Amazon ECS et Jenkins Configuration as Code. Un script est utilisé en entrypoint pour récupérer la configuration depuis le bucket S3 avant le démarrage de Jenkins.
- Une pour les agents JNLP également basée sur l’image officielle des agents Jenkins.
Ces deux images pourront servir d’exemple pour ceux voulant aller plus loin dans la personnalisation du Contrôleur ainsi
que des agents. Voir le dossier docker/
.
Déploiement
Maintenant qu’on a planté le décor, passons aux choses sérieuses.
Prérequis
Pour lancer cette stack, il faut :
- Un VPC avec des sous-réseaux publics et privés. On partira du principe qu’ils sont bien configurés : table de routage, nat gateway, internet gateway, communication entre les sous réseaux publics et privés, etc.
- Un utilisateur IAM pour lancer Terraform. Cet utilisateur devra au moins avoir des droits étendus sur les services EC2, ECS, IAM, S3, Cloudwatch, EFS, Route53 et ACM.
- Terraform
0.12.X
pour déployer l’infrastructure (j’utilise la version0.12.25
). Un backend local est utilisé par défaut pour des raisons de simplicité. Dans un environnement de production, il faut obligatoirement un autre backend tel que S3 avec le versioning activé. - [Optionnel] Un domaine sur Route53 pour accéder à Jenkins en HTTPs avec une URL “user-friendly”.
Le déploiement se fera par défaut en Ireland. Modifiez la variable Terraform aws_region
pour déployer dans une autre
région. Voir le fichier variables.tf pour la liste de toutes les variables disponibles.
Lancement
Avant de lancer les commandes Terraform, il faut soit exporter le profile AWS (AWS_PROFILE
) pointant vers votre utilisateur IAM soit
exporter les variables d’environnement AWS_SECRET_ACCESS_KEY
et AWS_ACCESS_KEY_ID
associées à votre utilisateur IAM. Ensuite :
# Clonage du dépot depuis github
git clone https://github.com/haidaraM/terraform-jenkins-aws-fargate
cd terraform-jenkins-aws-fargate && git checkout v2.1.0
# Export des variables obligatoires
export TF_VAR_vpc_id="vpc-123456789"
export TF_VAR_private_subnets='["private-subnet-a", "private-subnet-b", "private-subnet-c"]'
export TF_VAR_public_subnets='["public-subnet-a", "public-subnet-b", "public-subnet-c"]'
# Lancement de Terraform
terraform init
terraform apply # 'yes' une fois le plan terminé
TF_VAR_vpc_id
, TF_VAR_private_subnets
et TF_VAR_public_subnets
sont à remplacer par celles de
votre VPC.Si vous disposez d’une zone sur Route53, exportez également la variable
TF_VAR_route53_zone_name="mondomaine.com"
.
Jenkins sera alors accessible sur https://jenkins.mondomaine.com.Une fois la commande apply terminée, attendez quelques minutes le temps que le Contrôleur démarre et s’enregistre auprès des deux load balancers. Nous aurons une sortie qui ressemble à ça :
agents_log_group = "/jenkins/agents"
controller_config_on_s3 = "s3://jenkins-jcasc-12345678910/jenkins-conf.yml"
controller_log_group = "/jenkins/controller"
jenkins_credentials = <sensitive>
jenkins_public_url = "http://alb-jenkins-controller-450011780.eu-west-1.elb.amazonaws.com"
Et voilà : vous avez un Jenkins déployé et configuré automatiquement 😄. Il sera accessible sur l’URL
dans jenkins_public_url
avec les credentials contenus dans jenkins_credentials
. Pour voir les credentials:
terraform output jenkins_credentials
Si après quelques minutes le Contrôleur n’est toujours pas disponible, consultez le log groupe /jenkins/controller
et
l’état du service dans la console AWS ECS.
Si tout s’est bien passé, une fois connecté vous devriez voir le job example
dans la page d’accueil :
Nous allons lancer le job example
pour vérifier la communication Agent -> Contrôleur : “example > Build now”. Ce job affiche
l’identité AWS du build ainsi que les variables d’environnement disponibles dans le contexte d’exécution du build. Ces variables comprennent les variables
fournies par Jenkins ainsi que les variables fournies par AWS aux conteneurs sur Fargate : AWS_EXECUTION_ENV=AWS_ECS_FARGATE
nous indique donc que le job s’est exécuté sur Fargate.Si le job ne démarre après quelques minutes, consultez les logs du Contrôleur ainsi que ceux des agents dans Cloudwatch.
Pour voir la configuration du plugin ECS : “Manage Jenkins > Manage Nodes and Clouds > Configure Clouds” ou
sur /configureClouds
:
On retrouve notre cluster ECS ainsi qu’un template d’agent. Le Contrôleur utilise un rôle IAM pour accéder au cluster. Vous pouvez également rajouter d’autres clusters ECS. Il faudra au préalable modifier le rôle IAM “jenkins-controller-ecs-task” pour avoir accès à ces clusters.
Gestion de la configuration du Contrôleur
En utilisant le plugin Jenkins Configuration as Code comme nous l’avions fait, certaines modifications effectuées depuis l’interface graphique seront écrasées au prochain démarrage d’un nouveau conteneur du Contrôleur (voir le ticket #825 sur Github).
Cette approche peut être souhaitable si nous voulons gérer toute la configuration du Contrôleur en Infra as Code.
Cependant, chaque modification du fichier YAML de configuration nécessitera le lancement d’un nouveau conteneur pour
être prise en compte. Pour ce faire, la variable d’environnement JENKINS_CONF_S3_VERSION_ID
est utilisée dans la task
definition du Contrôleur. Cette variable correspond au numéro de version associé à la configuration du Contrôleur
stockée dans le bucket S3. Elle changera à chaque modification du template YAML et entraînera la création d’une nouvelle
task définition qui aura pour conséquence l’arrêt de l’ancien conteneur et le démarrage automatique d’un nouveau
conteneur. Il y aura donc un temps d’indisponibilité du Contrôleur de l’ordre de quelques secondes/minutes le temps
que le nouveau conteneur soit prêt.
JENKINS_CONF_S3_VERSION_ID
de la task definition. Le point d’entrée de l’image Docker est configurée
pour supprimer un éventuel fichier YAML existant si cette variable n’est pas définie pour éviter d’écraser la configuration.ECS peut cependant aussi être paramétré pour lancer un nouveau conteneur et attendre qu’il soit à l’état RUNNING et
healthy auprès du load balancer avant d’arrêter l’ancien conteneur (voir la variable
Terraform controller_deployment_percentages
). Mettez les valeurs min=100
et max=200
pour avoir ce comportement. Par défaut, le conteneur est arrêté avant de lancer un nouveau.
Néanmoins, la version open source de Jenkins ne supporte pas plusieurs contrôleurs. Durant le laps de temps où les deux contrôleurs seront en cours d’exécution, vous pourriez avoir des erreurs du type “No valid crumb was included in the request” depuis l’interface graphique du Contrôleur ou la perte de jobs en cours d’exécution. De manière générale, le bon fonctionnement du Contrôleur n’est pas garanti dans ces conditions.
Une autre approche est de générer une URL S3 pré-signée à chaque modification de la configuration dans le bucket S3 puis appliquer cette configuration manuellement depuis l’interface graphique dans le menu “Manage Jenkins > Configuration as Code”. Cette approche ne nécessite pas un redémarrage du Contrôleur : la configuration est modifiée à chaud par le plugin.
Depuis la page du plugin Configuration as Code, on peut générer une documentation du fichier template et un schéma JSON en fonction des plugins installés. Notez aussi que tous les plugins ne sont pas compatibles avec la configuration as code. On se tournera vers du groovy dans ces cas.
Monitoring
Avant de commencer à monitorer une infrastructure/application, il est important d’identifier les composants ou ressources les plus critiques. Une fois ces composants identifiés, nous regardons quelles sont leurs métriques les plus pertinentes par rapport notre besoin qui est d’être informé (à l’avance ou non) de problèmes liés à notre application. Cela peut être des problèmes de performance ou de disponibilité par exemple.
Dans notre stack, les composants à surveiller de près sont le système de fichiers EFS (comme expliqué plus haut), le Jenkins Contrôleur (CPU et RAM) ainsi que l’ALB. Nos conteneurs étant sur Fargate, il n’y a rien de particulier à monitorer à ce niveau : AWS se charge de l’infrastructure sous-jacente.
Ainsi, les alarmes suivantes sont configurées pour envoyer les évènements vers un topic SNS (voir le
fichier monitoring.tf
) :
jenkins-efs-low-burst-credits-balance
: Le crédit EFS a atteint la moitié du crédit alloué par AWS. Voir plus haut pour plus de détails.jenkins-alb-too-many-5xx-errors
: Le Jenkins Contrôleur a renvoyé plus de 60 erreurs internes 500 à l’ALB sur une période de 5 mn. Dans le cas de cette alarme, il faut consulter les logs du Contrôleur. Des erreurs 500 peuvent également subvenir lors de la mise à jour du Contrôleur.jenkins-alb-no-healthy-target
: Il n’y a aucun target healthy enregistré auprès de l’ALB. Cette erreur peut être dûe au Contrôleur qui ne démarre plus ou un problème au niveau du healthcheck du target groupe.jenkins-controller-high-cpu-utilization
: Utilisation moyenne du CPU supérieure à 80 % sur une période de 5 mn.jenkins-controller-high-memory-utilization
: Utilisation moyenne de la mémoire supérieure à 75 % sur une période de 5 mn.
Si les alarmes liées au CPU et à la mémoire se déclenchent fréquemment, pensez à augmenter les ressources du Contrôleur.
Voir la variable Terraform controller_cpu_memory
.
Pour recevoir ces alarmes par mail, il faut configurer manuellement un abonnement au topic SNS depuis la console AWS ou par CLI. Terraform ne supporte pas l’abonnement d’une adresse email.
Limitations et inconvénients
Avoir un Jenkins entièrement sur Fargate présente quelques limitations et inconvénients :
- Temps de démarrage des jobs : démarrer un conteneur sur Fargate prend un peu plus de temps que sur une instance
EC2.
Comptez ~45s en moyenne pour démarrer un job sur Fargate vs ~20 s sur ECS EC2 avec une image docker de 261MB. Ce
temps correspond au temps entre le moment où Jenkins lance la tâche ECS et le moment où l’agent arrive à se connecter
au Contrôleur.
Ceci est dû en partie au temps de création d’une Elastic Network Interface (ENI) pour la tâche, chaque tâche sur Fargate ayant sa propre adresse IP avec le mode réseauawsvpc
qui est le seul supporté par Fargate à l’heure actuelle. À ce temps, il faut rajouter le temps de téléchargement de l’image Docker : pas de cache possible pour les images sur Fargate.
Avec un cluster EC2, on profite du caching des images sur les conteneurs instances et nous n’avons pas forcément besoin d’une ENI pour chaque tâche. Le conteneur peut utiliser les interfaces réseaux de l’EC2 (modehost
) ou celle de Docker (modebridge
) pour communiquer.Mise à jour décembre 2023: Ce temps de démarrage peut être optimiser en utilisant SOCI. Voir mon article sur SOCI.- Pas de mode privilégié pour faire du Docker in Docker (DinD) : Lancer un conteneur en mode privilégié permet de faire du Docker dans du Docker : le conteneur Docker privilégié aura accès directement au Docker host. Ceci est généralement considéré comme une mauvaise pratique au vu de la faille de sécurité que cela implique. Néanmoins, cette fonctionnalité est indispensable si on veut construire des images Docker dans notre chaîne de CI/CD par exemple.
Sachez qu’il est possible d’avoir simultanément des agents sur Fargate et sur EC2. Ces deux inconvénients peuvent donc être contournés pour certains jobs critiques qui auraient besoin de se lancer le plus rapidement possible ou de faire du DinD.
Pour aller plus loin
- Sauvegarde de l’EFS : pas la peine de rappeler l’importance de faire des sauvegardes régulières du Contrôleur. AWS Backup peut être utilisé pour cela.
- Si vous souhaitez facilement parcourir la configuration du Contrôleur (par curiosité ou pour fixer des fichiers corrompus), vous pouvez utiliser Cloud Commander. Il s’agit d’un gestionnaire de fichiers dans le navigateur. Il dispose d’une CLI et d’un éditeur de texte intégré.
- Activer les access logs de l’ALB.
- …