Intégration et déploiement continus avec Bitbucket, Jenkins et AWS CodeDeploy

Allez, on sort l’artillerie lourde avec un post assez long qui devrait vous aider à mettre en place un pipeline d’intégration et de déploiement continus efficace.

Vous allez voir, c’est super fastoche.

Vous m’avez cru hein ? Pauvres de vous !

Non en fait c’est relativement fastidieux, mais ça vaut le coup. Croyez-moi.

On ne parlera pas ici du flow GIT que vous utilisez. Que ce soit Git Flow, GitLab Flow ou GitHub flow, ce qui nous intéresse ici, c’est ce qu’il se passe quand la branche est mergée.

L’idée

On veut pouvoir :

  • Lancer un pipeline Bitbucket lors d’un merge sur une branche (ici on va dire development)
  • Ce pipeline lance les tests unitaires et tout le bousin que vous voulez lancer avant de build
  • Si le pipeline est green, on veut déclencher une build avec Jenkins (en appelant une url pour poker un job Jenkins)
  • Si la build est ok sur Jenkins, on veut lancer un déploiement OnSite à l’aide d’AWS CodeDeploy
  • On veut contrôler et agir en fonction des évènement déclenchés par CodeDeploy (BeforeInstall, AfterInstall…)
  • Rollback en cas de pépin lors du déploiement
  • Dans le même esprit, on veut pouvoir lancer un déploiement plus complexe qui va éviter les interruptions de service lors d’une mise en prod (déploiement bleu / vert de CodeDeploy)

Vous aurez besoin :

  • D’un compte Bitbucket
  • D’une petite appli, disons en PHP (par exemple un WordPress) ou un back Node, RoR, GO… c’est vous que ça regarde 🙂 )
  • D’un compte AWS sur lequel vous pouvez créer des instances EC2, créer des users et rôles avec IAM, des buckets S3 et travailler avec CodeDeploy. Ca nous sera utile pour monter un serveur Jenkins et un serveur de dev.
  • Un peu de café
  • Une dizaine de doigts
  • Le dernier album de Booba en fond sonore

Bitbucket pipeline

On commence par le commencement en créant tout de suite un pipeline sur Bitbucket. En fait c’est plutôt simple, vous avez juste à créer un ficher bitbucket-pipelines.yml à la racine de votre projet.

Il prend la forme suivante (ici c’est pour du Node) :

image: node:6.9.4

pipelines:
  default:
    - step:
        caches:
          - node
        script: # Modify the commands below to build your repository.
          - yarn 
          - yarn test # Par exemple hein !

Un autre exemple pour PHP issu de la doc Bitbucket :

image: php:7.1.1

pipelines:
 default:
   - step:
     caches:
       - composer
     script:
       - apt-get update && apt-get install -y unzip
       - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
       - composer install
       - vendor/bin/phpunit

Faites très attention à l’indentation, c’est du Yaml, et c’est très susceptible.

Bref, vous voyez l’idée, on utilise une VM dans laquelle on faire notre config, puis on lance nos tests.

Vous vous demandez peut être comment limiter l’exécution d’un pipeline à une branche en particulier ?

La réponse est ici 

Ca ressemble à ça :

pipelines:
  branches:
    master:
      - step:
          script:
            - echo "only on master"

Vous pouvez donc choisir sur quelle(s) branche(s) vont se lancer vos pipelines et gérer une configuration différente de l’une à l’autre. Ca claque non ?

Webhook ? Naaaaan

Si vous êtes comme moi (beau, intelligent, modeste et très très riche), vous voudrez avoir la main sur les webhooks que vous déclenchez.

Bitbucket ne permet pas à ma connaissance de conditionner le déclenchement d’un webhook en fonction du résultat des tests lancés dans un pipeline. En conséquence, vous pouvez potentiellement lancer un webhook qui va déployer votre appli alors que les tests sont red. C’est la merde.

Du coup, une fois que l’on aura installé et configuré notre serveur Jenkins, on reviendra sur la configuration du pipeline pour poker un endpoint d’un job.

Jenkins

Pour ceux qui ne connaissent pas Jenkins, un petit tour sur le site et tout devrait être clair.

On va ici utiliser Jenkins pour build notre application en récupérant notre dernier commit GIT. Ce qui fait entre autre l’intérêt de Jenkins, c’est que l’on peut construire un job qui suit une série d’actions.

On va rester dans une relative simplicité pour cette démo, mais je vous recommande d’aller plus loin dans l’utilisation de Jenkins. C’est trop de la balle. Ca tue des mémés en morceau de 50.

On va installer Jenkins sur un EC2 chez Amazon Web Services, et comme on est vraiment fainéants, on va utiliser une image existante de chez Bitnami qui fait très bien le job.

Rendez-vous dans la console AWS, rubrique EC2 :

Allez dans le Marketplace, choisissez Jenkins Certified by Bitnami. L’image est éligible au free tiers, vous ne paierez rien pour l’utiliser (mais vous pourriez payer selon le type d’instance que vous souhaitez lancer).

On va prendre une T2.Micro, ca devrait faire l’affaire pour notre démo. Ceci dit dans un contexte pro, je vous recommande de prendre au moins une T2 Medium, voir plus si vous envisagez un usage intensif des jobs.

Une fois le type d’instance sélectionnées, pensez à créer un nouveau rôle IAM sur lequel vous attribuerez les droits dont vous avez besoin.

Utilisez un stockage de base (10go), ca devrait largement faire l’affaire.

Ajoutez un tag pour retrouver votre instance facilement dans vos instances EC2

Créez un nouveau groupe de sécurité (l’image Bitnami en a préconfigurée un pour vous)

Checkez une dernière fois votre config, puis cliquez sur Lancer.

Si besoin, créez votre paire de clé ou utilisez en une existante, au choix, puis lancez l’installation.

Au bout de quelques minutes, vous devriez pouvoir accéder à votre serveur via le DNS de votre instance :

http://ec2-ipdevotremachin.compute-1.amazonaws.com/jenkins

SHAZAM !

Où c’est qu’ils sont mes credentials ?

Excellente question ! Merci de l’avoir posé.

L’username par défaut est « user »

Le mot de passe est affiché dans les logs de votre machine, on y accède comme ceci :

En fouillant les logs, vous devriez tomber sur ça :

Il ne vous reste plus qu’à vous logger 🙂

Installez les plugins suggérés par Jenkins. Il ne vous veut que du bien.

Redémarrez ensuite Jenkins.

On va commencer par mettre à jour les plugins et en installer de nouveaux.

Cliquez sur Administrer Jenkins, puis sur gestion des plugins. Sélectionnez tous puis Téléchargez maintenant et installer après redémarrage.

Une fois que tout est téléchargez, cliquez sur :

Une fois que redémarré, allez dans Disponible et cherchez le plugin CodeDeploy. Installez-le.

Redémarrez à nouveau Jenkins.

Configurer un job

On va maintenant configurer un job Jenkins qui va :

  • Récupérer notre repository
  • Builder le projet
  • Déployer le projet sur une instance EC2 avec CodeDeploy

Cliquez sur « nouveau item », entrez un nom et choisissez Projet freestyle.

Allez directement dans Gestion de code source,

Ajoutez l’url de votre repo et vos identifiants (cliquez sur Ajouter dans Credentials puis Sélectionnez Jenkins).

Choisissez la branche qui va être utilisée pour les builds.

On va choisir de déclencher nos builds à distance

Définissez un token (un hash random assez sécurisé ne serait pas du luxe).

L’adresse de votre endpoint sera :

http://ec2-ipdevotremachine.compute-1.amazonaws.com/jenkins/job/nomdevotrejob/build?token=votretoken

ou

http://ec2-ipdevotremachine.compute-1.amazonaws.com/jenkins/job/nomdevotrejob/buildWithParameters?token=votretoken

Cliquez sur Sauvegarder. On reviendra sur la configuration du job un peu plus tard. On va configurer Jenkins pour pouvoir taper sur l’url qui permettra de déclencher les builds.

User & Crumbs

On va utiliser notre compte afin de s’authentifier auprès de Jenkins. Dans la vie réelle, vous créerez un user spécifique. 

Voici un GIST pour générer un crumb qui vous permettra de communiquer avec Jenkins. Vous avez simplement besoin de :

  • Récupérer votre Token en allant dans Users -> Configurer et en allant sur votre nom d’utilisateur.
  • Cliquez sur Show API Token

Une fois que vous avez votre token, ouvrez votre terminal et utilisez la commande suivante :

curl -s -u user:votreapitoken http://ec2-ipdevotremachine.computmazonaws.com/jenkins/crumbIssuer/api/json

Vous obtiendrez un objet JSON de ce type :

{"_class":"hudson.security.csrf.DefaultCrumbIssuer","crumb":"votrecrumb","crumbRequestField":"Jenkins-Crumb"}

On termine par une petite modification dans la sécurité globale de Jenkins :

  • Allez dans Administrer Jenkins
  • Configurer la sécurité globale
  • Activer Se protéger contre les exploits…
  • Activer la compatibilité proxy

Maintenant, on peut tenter de contacter notre job en lançant l’url du endpoint :

curl -s -u user:apitoken -H 'Jenkins-Crumb:votrecrumb' http://ec2-ipdevotremachine.eu-west-1.compute.amazonaws.com/jenkins/job/nomdevotrejob/build?token=tokendevotrejob

En retournant sur Jenkins, vous devriez voir qu’un premier job a été exécuté.

On est plutôt pas mal !

Retour à Bitbucket mountain

On va revenir sur notre fichier de configuration yml au sein de notre projet afin de déclencher une build automatiquement via le pipeline Bitbucket.

Par contre, c’est plutôt crade de coller vos credentials directement dans le fichier de configuration. On aurait bien besoin de stocker ça dans des variables d’environnement.

Allez sur Bitbucket, choisissez votre repo, puis allez dans Settings puis enfin dans la rubrique Pipeline, cliquez sur Environment variables.

Ajoutez trois variables :

Dans votre fichier yml, ça donnera quelque chose comme ça :

image: php:7.1.1

pipelines:
 branches:
   dev:
     - step:
       caches:
         - composer
       script:
         - apt-get update && apt-get install -y unzip
         - curl -u $JENKINS_CREDENTIALS -H 'Jenkins-Crumb:$JENKINS_CRUMB' http://votremachine.compute-1.amazonaws.com/jenkins/job/votrejob/build?token=$JENKINS_JOB_TOKEN

Si tout est bien configuré, vous devriez voir un success sur Bitbucket :

Et si tout est vraiment bien configuré, vous devriez également voir un nouveau job exécuté depuis Jenkins :

Youpi !

CodeDeploy

Maintenant que l’on a un pipeline qui fonctionne bien, on va pouvoir avancer et ajouter une Action. Cette action lancera un déploiement auto sur une instance EC2 en mode OnSite. C’est à dire que l’on déploiera en live sur la machine sans gérer de groupe d’auto scaling (on y reviendra).

Avant de configurer cette action, on va devoir monter une instance EC2 qui hébergera notre site WordPress.

On va créer une instance de type T2 Micro basé sur une image AMI. Je vous laisse vous débrouiller pour créer l’instance, le rôle et le groupe de sécurité qui vont bien.

Une fois que c’est fait, connectez-vous en SSH à la machine et lancez les commandes suivantes :

sudo yum update -y
sudo yum install httpd
sudo service httpd start

La première met à jour votre distrib, la seconde installe httpd, la dernière lance httpd. Rien de bien folichon, mais vous devriez avoir un serveur web qui tourne :

On va maintenant installer l’agent CodeDeploy sur notre machine. On en aura besoin pour que les mises à jour soient bien envoyées sur le serveur et pour monitorer le tout :

cd /home/ec2-user

wget https://aws-codedeploy-eu-west-1.s3.amazonaws.com/latest/install

chmod +x ./install

sudo ./install auto

Notez la région en gras pour le wget. C’est à adapter en fonction de la région sur laquelle vous avez créé votre instance EC2.

Une fois l’agent installé, on check si tout est ok avec un :

sudo service codedeploy-agent status

Si vous avez un message de ce type, c’est gagné !

The AWS CodeDeploy agent is running as PID

Maintenant que notre instance tourne, c’est le moment de créer un Elastic Load Balancer, un bucket S3, et de configurer CodeDeploy côté AWS.

ELB

CodeDeploy a besoin d’ELB pour vérifier l’état de santé de vos machines, ca se configure en trois secondes.

Dans EC2, faites dérouler la page pour trouver le menu Equilibrage de charge.  Cliquez sur Equilibreurs de charge.

Choisissez Equilibreur de charge d’application.

Donnez un nom à votre ELB, laissez l’écouteur sur HTTP port 80 (pour cette démo, ce sera suffisant, mais vous devriez ajouter le SSL pour un projet pro), puis sélectionnez vos zones de disponibilités (choisissez les toutes si vous voulez).

Cliquez sur suivant. AWS vous dira que c’est pas bien de ne pas mettre HTTPS. Prenez acte, cliquez violemment sur Suivant.

Définissez votre groupe de sécurité (créez en un nouveau ou utilisez celui que vous avez créé pour votre serveur web). Cliquez sur suivant.

C’est là que ça commence à devenir intéressant. Votre ELB va checker l’état de santé de vos machines. Par défaut, il tape sur l’url de vos serveurs, mais vous pouvez définir un fichier à interroger (par exemple, health.html).

Ce qui est intéressant ici, c’est qu’ELB va créer un groupe cible. Ce groupe cible contiendra les instances que vous voulez vérifier. Vous pouvez donc surveiller un ensemble d’instances EC2, pas juste une seule.

Mettez un nom pour votre groupe cible, et laissez les autres paramètres par défaut. Cliquez sur Suivant

Vous allez pouvoir enregistrer vos cibles. En l’occurence on en a qu’une : notre instance EC2 créée pour faire office de serveur web. Sélectionnez là et ajoutez la aux membres.

Cliquez sur suivant, vérifiez vos réglages, puis cliquez sur Créer (ou croyez moi sur parole et cliquez aveuglément sur Créer, c’est vous que ça regarde).

L’équilibreur va se créer rapidement.

S3

On a besoin d’un bucket s3 pour envoyer nos builds depuis Jenkins. En deux mots, Jenkins va build notre application, déposer un zip sur notre bucket, et CodeDeploy va aller chercher ce ZIP pour déployer notre application.

Créez un bucket s3 privé et gardez le nom du bucket dans un coin de votre tête.

CodeDeploy

Maintenant que notre bouzin est configuré, il ne nous reste plus qu’à créer la configuration CodeDeploy.

Choisissez Déploiement personnalisé et cliquez sur Ignorer la procédure.

Notez quand même que CodeDeploy propose un exemple de déploiement. C’est rapide à installer et ça permet de comprendre rapidement le fonctionnement dans un contexte bleu – vert. A tester donc.

  • Indiquez un nom d’application et un nom de groupe de déploiement.
  • Choisissez Déplacement sur site

On va ajouter notre instance avec l’onglet Instances Amazon EC2. On reviendra sur la configuration avec des Groupes Auto Scaling ultérieurement.

Choisissez le tag Name puis l’instance EC2 que vous avez créé

On va laisser la configuration du déploiement sur OneAtTime :

Maintenant on doit fournir l’ARN du rôle qui sera utilisé par CodeDeploy. Ca peut sembler un poil complexe (comme la plupart des trucs de la doc d’AWS) mais pour faire simple :

  • Ouvrez IAM dans un nouvel onglet
  • Créez un nouveau rôle, choisissez CodeDeploy dans la liste des services AWS
  • Cliquez sur Suivant, vous devriez avoir une stratégie déjà assignée (AWSCodeDeployRole)
  • Donnez un nom à votre rôle (codedeployrole par exemple)
  • Allez voir le détail du rôle et notez son ARN (au tout début)

On va en profiter pour créer un user IAM qui aura les droits suffisants pour Jenkins.

  • Créez un nouvel utilisateur
  • Appelez le par exemple « CodeDeployJenkins »
  • Choisissez Accès par programmation
  • Attachez lui les stratégies CodeDeployFullAccess, S3FullAccess et EC2FullAccess
  • Vérifiez, créez
  • Sauvegardez les credentials

Revenez maintenant sur votre onglet CodeDeploy et renseignez l’ARN que vous venez de copier, puis cliquez sur Créer.

Maintenant que l’on a notre application CodeDeploy, on va pouvoir finir la configuration Jenkins 🙂

On y est presque

Ca en fait du taff, hein !

Bonne nouvelle, c’est pas terminé. Maintenant on termine la configuration de Jenkins et après, ce sera que du bonheur.

Promis.

Retour sur Jenkins donc. Editez votre job, puis, dans Actions après le build, cliquez sur Ajouter une action et choisissez Deploy an application with CodeDeploy

Vous allez vous retrouver avec un formulaire à remplir. Il est long, certes, mais tous les champs ne sont pas obligatoires :

AWS CodeDeploy Application Name : Le nom de votre application CodeDeploy

AWS CodeDeploy Deployment Group : Le nom de votre groupe de déploiement (pour moi, par exemple, c’est dev)

AWS CodeDeploy Deployment Config : CodeDeployDefault.OneAtATime

AWS Region : Spécifiez votre région

S3 Bucket : Le nom du bucket que l’on a créé précédemment

S3 Prefix : Vous pouvez, si vous voulez, prefixer le bucket pour taper sur un dossier spécifique. Ca peut vous éviter par exemple de créer un bucket pour dev, un pour prod etc. On ne va pas en mettre ici.

  • Cochez le bouton radio  Deploy Revision
  • Cochez Use Access/Secret keys et utilisez les identifiants que l’on a créé plus haut.
  • Cliquez sur Sauver

Appspec.yml

Au début de ce post (le plus long que j’ai jamais écris je crois), on a créé un fichier yml pour lancer notre pipeline avec Bitbucket. CodeDeploy fonctionne de la même manière et attend de trouver un fichier appspec.yml à la racine de votre projet pour le déployer avec – si besoin, des scripts qui sont lancés à plusieurs phases du déploiement.

Ca ressemble à ça :

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html/
hooks:
  BeforeInstall:
    - location: scripts/install_dependencies
      timeout: 300
      runas: root
    - location: scripts/start_server
      timeout: 300
      runas: root
  AfterInstall:
    - location: scripts/prepare_installation
      timeout: 300
      runas: root
  ApplicationStop:
    - location: scripts/stop_server
      timeout: 300
      runas: root

Rien de trop violent :

  • On choisi les fichiers qu’on veut envoyer (ici, tout)
  • On choisi le path de destination sur l’instance (ici /var/www/html/)
  • Puis on lance des hooks en fonction des états du déploiement

Les scripts sont localisés, à la racine du projet, dans le dossier scripts, vous pouvez mettre ce que vous voulez (par exemple, nettoyez le cache, aller choper des fichiers de configuration sur un S3, montez un EFS… sky is the limit)

Exemple :

#!/bin/bash
sudo yum install -y httpd

Veillez à bien créer un exec pour chaque script.

Notez également que runas:root foire une fois sur deux, c est pour cette raison que j’ai mis un sudo dans l’exemple ci-dessus.

Reste plus qu’à commit push tout ça et voir si on a tout péter !

Comme on a déjà configuré le pipeline pour poket Jenkins, et qu’on a configuré Jenkins pour poker CodeDeploy, on a plus qu’à attendre ?

Naaaaan, gardez un oeil sur Jenkins et sur le job qui va s’exécuter. Si le job tombe en erreur, vous pouvez allez voir le détail et jeter un oeil à Console Outputs pour trouver d’où vient l’erreur.

Si tout se passe bien, CodeDeploy devrait passer chaque étape avec succès. Vous pouvez suivre le déploiement en cours en vous rendant dans l’application CodeDeploy et en affichant les évènements.

On peut confirmer l’installation en allant sur la machine et en jetant un oeil à /var/www/html

A partir de là, vous pouvez faire à peu près tout ce que vous voulez.

Déploiement bleu – vert

Je vais vous épargner quelques paragraphes en parlant juste rapidement d’un autre type de déploiement avec CodeDeploy : le déploiement bleu – vert.

Dans l’idée, le déploiement bleu – vert permet de mettre en place des déploiements sans aucune interruption de service.

Ca s’appuie sur les groupes d’auto scaling (qui vous permette de gérer la montée en charge de votre application en créant plus d’instances EC2).

En gros, lorsque vous lancez CodeDeploy :

  • Une copie de votre groupe d’auto scaling est effectuée
  • Cette copie (et donc toutes les instances en faisant partie) reçoit la nouvelle build
  • Si le déploiement s’effectuent sans erreur, le traffic est redirigé vers le nouveau groupe AS
  • Si le déploiement plante, le traffic reste sur le groupe initial.

C’est nettement plus long que le déploiement sur site, naturellement, mais c’est très clairement une stratégie qui défonce des mémés en morceaux de 50, d’une part parce que ça n’interrompt pas le service, mais aussi parce que le groupe copié au moment du déploiement correspond à la réponse en montée en charge que vous pourriez avoir au même moment. Sans compter le rollback qui permet vraiment de s’assurer de ne rien planter en prod.

Ca lance-patate à mort.

Bref, si vous avez des questions, les commentaires sont faits pour ça.

Bisous.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *