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 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 Master en HTTPs (à condition d’avoir une zone publique sur Route53).
- Un process automatisé de mise à jour de la configuration du Master.
- Les logs du Master et des agents dans CloudWatch.
- Des alertes configurées pour surveiller l’EFS et le Master.
- Et le tout entièrement automatisé…
v1.0.1
du dépôt. Des modifications seront sans doute apportées
après l'écriture de cet article. Veillez donc à utiliser le tag v1.0.1
pour suivre cet article.N’hésitez pas à mettre en commentaires vos remarques et suggestions ou à contribuer sur GitHub :-)
Infrastructure
Master <-> Agent
Nous allons déployer Jenkins en mode Master/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 Master 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 Master 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 Master 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 Master 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 Master <-> Agent. Parmi ces protocoles, nous avons :
- SSH : Avec le protocole SSH, la communication est initiée par le Master 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 Master 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 Master : dans ce cas, la communication est initiée par l’agent et non par le Master. Le Master 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 Master. 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 master. Ce NLB écoutera sur deux ports : un port pour le protocole JNLP (50000) et un port pour accéder au Master en HTTP (8080). Le port JNLP est configuré au niveau du Master 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 Master dans ECS.
Un service ECS jenkins-master
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 Master (numExecutors: 0
).
Les agents JNLP ainsi que le Master 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 Master 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 Master 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 Master 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 Master, 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 Master.
- Désactiver de l’exécution des jobs sur le Master.
- …
Deux logs groupes CloudWatch sont utilisés : un pour le jenkins Master (/jenkins/master
) et un pour les logs des agents
(/jenkins/agents
). Les logs des agents correspondent aux logs lors de la communication Agents -> Master. 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 Master et 2 à l’agent :
- jenkins-master-ecs-execution : Il s’agit du rôle utilisé par ECS pour envoyer les logs du Master 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-master-ecs-task : Ce rôle est utilisé par le Master pour récupérer sa configuration depuis S3, lancer et stopper des tâches sur le cluster ECS. Il permet également au Master de passer les deux rôles plus bas aux agents. C’est ce rôle qu’il faudra modifier si le Master 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 Master 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 Master 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 v1.0.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 le apply terminé, attendez quelques minutes le temps que le Master démarre et s’enregiste auprès des deux load balancers. Le apply produira une sortie qui ressemblera à ça :
agents_log_group = "/jenkins/agents"
jenkins_credentials = {
"password" = "{n@jM{n4CYE[F@s_"
"username" = "admin"
}
master_log_group = "/jenkins/master"
jenkins_public_url = "http://alb-jenkins-master-237146470.eu-west-1.elb.amazonaws.com"
master_config_on_s3 = "s3://jenkins-jcasc-123456789416/jenkins-conf.yml"
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 jenkins_credentials
. Si après quelques minutes le Master n’est toujours pas disponible,
consultez le log groupe /jenkins/master
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 :example
.
Nous allons lancer le job example
pour vérifier la communication Agent -> Master : “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.example
.
Pour voir la configuration du plugin ECS : “Build Executor Status > Configure Clouds” :
Gestion de la configuration du Master
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 Master (voir le ticket #825 sur Github).
Cette approche peut être souhaitable si nous voulons gérer toute la configuration du Master 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 Master.
Cette variable correspond au numéro de version associé à la configuration du Master 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 Master 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 master_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 n’est pas faite pour fonctionner en multi-masters. Durant le laps de temps où les deux Masters 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 Master ou la perte de jobs en cours d’exécution. De manière générale, le bon fonctionnement du Master 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 Master : 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 Master (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 Master 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 Master. Des erreurs 500 peuvent également subvenir lors de la mise à jour du Master.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 Master qui ne démarre plus ou un problème au niveau du healthcheck du target groupe.jenkins-master-high-cpu-utilization
: Utilisation moyenne du CPU supérieure à 80 % sur une période de 5 mn.jenkins-master-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 Master.
Voir la variable Terraform master_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 ~25 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 Master.
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. - 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 Master. AWS Backup peut être utilisé pour cela.
- Si vous souhaitez facilement parcourir la configuration du Master (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.
- …