L'implémentation d'un « pipeline CI/CD » (= Continous Integration / Continuous distribution) est une pratique DevOps moderne. Elle permet d'automatiser le déploiement du code, créé par les équipes de développement, dans l'environnement de production, selon une série d'étapes.
Elle comble généralement une série d'opérations qui échappe à la mission des développeu.rs.ses, investi(e)s notament dans le développement d'un logiciel applicatif.
Tout au long du cycle de vie d'une application, le CI/CD :
1️⃣ Déploie intégralement et automatiquement l'application depuis un répertoire (partagé ou privé) à destination d'un environnement de production
2️⃣ Garantit l'intégrité du ou des services internes au(x) serveur(s) de production en procédant à une série de tests automatisés
3️⃣ Assure l'envoi de logs/rapports réguliers aux équipes de développement en ce qui concerne l'analyse du code, les tests ou les éventuels processus en cours d'exécution
4️⃣ Permet l'augmentation de la fréquence des mises à jour
5️⃣ Facilite, pour les équipes de développement : la mise à niveau de certains fichiers de configuration en production (variables d'environnement, etc..)
6️⃣ Prémunit l'environnement de production contre les éventuelles erreurs humaines
[...] Comment implémenter un « pipeline CI/CD » via protocole SSH depuis un répertoire avec Github Actions ?
⚠️ Pré-requis
Notre implémentation nécessite la disposition d'un serveur distant (environnement de production) incluant une application active (liée à un répertoire Github accessible).
L'authentification de notre serveur auprès de Github doit être configurée de sorte à ce qu'elle ne requiert aucun mot de passe. Une authentification par paire de clés SSH sans phrase de sécurité est nécessaire.
ℹ️ Le protocole SSH (Port 22) de votre serveur distant doit être activé.
🔐 Création d'une paire de clés SSH côté serveur
Le plugin dépendant de l'action que nous souhaitons utiliser pour la réalisation de notre CI/CD requiert un accès SSH. Dès lorsque le plugin est authentifié par notre serveur via le protocole SSH, il peut alors intéragir avec notre serveur et exécuter notre futur workflow (permettant le déploiement de notre application).
Commencons par créer une paire de clés SSH, côté serveur, pour permettre à l'action que nous utilisons de se connecter à notre serveur distant via le protocole SSH.
Une fois la connexion SSH avec notre serveur établie, localisons-nous dans le répertoire .ssh
en utilisant cd ~/.ssh
puis générons notre nouvelle paire de clés ed25519 grâce à la commande :
ssh-keygen -t ed25519 -a 200 -C "Key Comment"
ℹ️ Le schéma de signature ssh-rsa utilisant l'algorithme de hachage SHA-1 a été dépréciée depuis OpenSSH 8.8 (2021). Nous utilisons alors une paire de clés ed25519.
> ssh-keygen -t ed25519 -a 200 -C "Key Comment"
` (...)
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/yourusername/.ssh/id_ed25519):
Created directory '/home/yourusername/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/yourusername/.ssh/id_ed25519.
Your public key has been saved in /home/yourusername/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:2m0FfefEAFeJ31mFw87DAfef3FA113ge Key Comment
The key's randomart image is:
+---[ED25519 256]----+
| . |
| |
| . |
|. . . . |
|. .=.o .S. |
| =o.o. .. . |
|o + . . o .. |
|.. . oE oo . |
|o. .o+o o |
+------[SHA256]------+
Notre paire de clés est à présent générée.
Deux fichiers intègrent dorénavant notre répertoire .ssh
: id_ed25519
et id_ed25519.pub
. Une clé privée et une clé publique.
Imprimons dès lors le contenu de notre clé publique id_ed25519.pub
en utilisant cat id_ed25519.pub
puis complétons notre fichier authorized_keys
par cette nouvelle clé en l'ajoutant à la fin du fichier. (= Utilisez sudo nano authorized_keys
)
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCb4oUsXZ51L9DmH3UqSnwOAUr9w6AOZa8bYH8qsJAhypAjH9YvQteVXXQEY0ybaVE1cmpIKEyC2jmC/jOJ4b7bivlo2hgnLOrj3FPkDWO2yNNio9RnPXREBx7 Your Comment
Notre clé privée ~/.ssh/id_ed25519
peut dorénavant permettre à notre action de s'authentifier via le protocole SSH. Copiez-là en utilisant cat id_ed25519
.
-----BEGIN OPENSSH PRIVATE KEY-----
mO2P1IFEA93md2eZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
2109fTUxOQAAACDytzhEI+btA5eW/JPqdBVZEYB4cBVrWdStrZrZzwXqsAAAAKD43x6J+N8e
iFEA9AAAtzc2gtZWQyFAEFAxAACDygzhEEAL1+13IB5eW/JPqdBVZEYB4cBVrWdSrZzmPfeFA
AAAECxDmZbgzG/rChoHYC0zux9C05Rvm2U6qCYTHpD982lJPdDOEQj4EgHl5b8k+p0FVkR
gHhwFWtZ1KGEAOFKAmtnPBeqwAAAAFm1lc3NhZ2UtYm94QG91FEAF9uZnIBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----
🌐 Mise à niveau du répertoire Github
Accédons à la page Github de notre project > Paramètres > Secrets et variables > Actions.
Quatre (ou cinq*) nouvelles variables doivent être ajoutées :
-
HOST
(adresse distante de votre serveur) -
USERNAME
(nom d'utilisateur principal) -
KEY
(clé privée id_ed25519) -
PORT
(22)
() Si vous avez configuré **une phrase de sécurité* lors de la génération de votre paire de clés SSH, une cinquième variable
PASSPHRASE
doit alors être créée et assignée.
Le contenu de notre fichier ~/.ssh/id_ed25519
doit être intégralement copié/collé dans le champ de valeur de notre variable KEY
.
Une fois ces variables enregistrées, le projet Github détient dès lors secrètement l'accès à notre serveur distant.
⚙️ Configuration du workflow Github Actions
Github inclut une rubrique "Actions" (Github Actions) spécifique à chaque projet, elle est dédiée à l'implémentation d'un « pipeline CI/CD ».
Cette plateforme va permettre une mise à l'écoute des évènements (= Push, Pull Request, Merge..) liés aux différentes branches de notre projet Github.
Lorsqu'un évènement survient, un runner (= serveur) exécute une action (code d'exécution) en tenant compte de la configuration d'un workflow (étapes du processus) et permet notament : le déploiement de notre code dans un environnement de production.
ℹ️ Un marketplace, rendu disponible par Github Actions, propose l'utilisation d'actions créées par des dévellopeu.rs.ses de la communauté (Ex: Deploy with Node.js, Deploy Node.js with Azure App, etc..)
Procédons à présent à la configuration de notre workflow.
Ouvrons la page de notre projet Github dans notre navigateur et rendons-nous dans l'onglet "Actions".
Le bouton "set up a workflow yourself" permet d'initialiser le fichier .github/workflows/main.yml
à la racine de notre projet Github et autorise ainsi la configuration d'un workflow personnalisé.
ℹ️ Ce fichier est capable d'accéder aux variables secrètes enregistrées (accessibles depuis Github.com > Paramètres > Secrets et variables > Actions)
Inaugurons la création de notre fichier en ajoutant les lignes suivantes :
name: CI/CD Deploy (using SSH)
on:
push:
branches:
- master
Le processus "CI/CD Deploy (using SSH)" est alors créé.
Il a pour rôle d'écouter les éventuels « Push » depuis la branche « master ».
Il nous faut à présent configurer une série d'étapes et d'actions à effectuer pour permettre à Github Actions de savoir quoi faire lorsqu'un(e) développeu.r.se « Push » du code sur la branche « master »
Pour cela, nous utiliserons l'action Github suivante : appleboy/ssh-action@v0.1.8
Cette action va permettre d'exécuter les différentes étapes de notre workflow de déploiement via l'utilisation du protocole SSH de notre serveur distant.
name: CI/CD Deploy (using SSH)
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Deploy to production server
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
script: printf "Hello World!"
Une nouvelle étape "Deploy to production server" est ajoutée, elle requiert l'utilisation de l'action appleboy/ssh-action@v0.1.8
.
Les variables secrètes host
, username
, key
et port
de notre répertoire sont alors récupérées et transmises à l'action pour permettre l'ouverture d'une session SSH depuis une authentification par paire de clés SSH.
ℹ️ Si vous avez configuré une phrase de sécurité lors de la création de votre paire de clés SSH, ajoutez la ligne :
passphrase: ${{ secrets.PASSPHRASE }}
en dessous de la lignekey: ${{ secrets.KEY }}
. Elle permettra la prise en charge de votre phrase de sécurité.
La propriété script
permet enfin d'ordonner une série d'opérations internes au serveur.
ℹ️ Des logs instantanées sont accessibles pour chaque déploiement et permettent le suivi du processus au fil de son exécution.
Il ne nous reste plus qu'à « commit » notre nouveau fichier .github/workflows/main.yml
pour assister au premier déploiement automatisé 🔥
Une fois les modifications effectuées, Github Actions détecte un nouveau « Push » sur la branche « master ».
Une connexion SSH est dès lors initiée par ssh-actions depuis les accès secrets fournis. La commande printf "Hello World!"
est alors exécutée au sein de notre serveur.
✅ Si l'authentification est approuvée par notre serveur, l'action retourne la synthèse des commandes à exécuter. Les réponses capturées, pour chaque commande, lors de l'exécution de l'étape "Deploy to production server", sont alors retransmises en temps réel.
======CMD======
printf "Hello World!"
======END======
Hello World!
==============================================
✅ Successfully executed commands to all host.
==============================================
❌ A contrario, le build sera un échec et cessera.
⚙️ Configuration du pipeline CI/CD
Github est dorénavant capable de détecter l'occurence d'un évènement et d'établir une connexion SSH avec notre serveur pour y exécuter une commande.
Une fois la connexion établie, il ne nous reste plus qu'à configurer la série d'opérations à exécuter via bash
.
Dans le cas d'une simple application React, les tâches à entreprendre seraient les suivantes :
- Se localiser dans le répertoire de notre application (nous utiliserons le répertoire
~/app-to-deploy
) - Effectuer un pull du répertoire et ainsi bénéficier d'une version de l'application up-to-date.
- Installer les dépendances du projet
- Build l'application
- Relancer l'application (via l'utilisation d'un process manager comme PM2)
Modifions dès lors notre fichier .github/workflows/main.yml
pour y inclure le pipeline de notre CI/CD :
name: CI/CD Deploy (using SSH)
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Deploy to production server
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
script: |
cd "app-to-deploy"
printf "[1/5] 📥 Pull of repository.."
git pull
printf "[2/5] 📦 Installation of packages.."
npm i
printf "[3/5] 🖨 Build of application.."
npm run build
printf "[4/5] 🔄 Restart the application.."
pm2 restart 0
printf "[5/5] ✅ Deploy is finished!"
Notre CI/CD est à présent fonctionnel 🎉
Les différentes opérations de notre script sont exécutées unes à unes. Elles seront ensuite retransmises via les logs du déploiement.
======CMD======
cd "app-to-deploy"
printf "[1/5] 📥 Pull of repository.."
git pull
printf "[2/5] 📦 Installation of packages.."
npm i
printf "[3/5] 🖨 Build of application.."
npm run build
printf "[4/5] 🔄 Restart the application.."
pm2 restart 0
printf "[5/5] ✅ Deploy is finished!"
======END======
out: [1/5] 📥 Pull of repository..
out: Updating ecb327c..934abbc
out: Fast-forward
out: src/App.js | 2 +-
out: 1 file changed, 1 insertion(+), 1 deletion(-)
out: [2/5] 📦 Installation of packages..
out: up to date, audited 1611 packages in 10s
out: 235 packages are looking for funding
out: run `npm fund` for details
out: 6 high severity vulnerabilities
out: To address all issues (including breaking changes), run:
out: npm audit fix --force
out: Run `npm audit` for details.
out: [3/5] 🖨 Build of application..
out: > app-to-deploy@0.1.0 build
out: > react-scripts build
out: Creating an optimized production build...
out: Compiled successfully.
out: File sizes after gzip:
out: 46.63 kB build/static/js/main.80a26327.js
out: 1.78 kB build/static/js/787.d08cf7c1.chunk.js
out: 541 B build/static/css/main.073c9b0a.css
out: The project was built assuming it is hosted at /.
out: You can control this with the homepage field in your package.json.
out: The build folder is ready to be deployed.
out: You may serve it with a static server:
out: npm install -g serve
out: serve -s build
out: Find out more about deployment here:
out: https://cra.link/deployment
out: [4/5] 🔄 Restart the application..Use --update-env to update environment variables
out: [PM2] Applying action restartProcessId on app [0](ids: [ '0' ])
out: [PM2] [my-app](0) ✓
out: ┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
out: │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
out: ├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
out: │ 0 │ my-app │ default │ N/A │ fork │ 34966 │ 0s │ 15 │ online │ 0% │ 3.4mb │ *** │ disabled │
out: └────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
out: [5/5] ✅ Deploy is finished!
==============================================
✅ Successfully executed commands to all host.
==============================================
Il ne tient plus qu'à vous de personnaliser le script d'exécution avant de procéder au déploiement de votre ou vos application(s)/serveur(s).
Top comments (0)