Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# GitLab CI / CD et documentation

> Environnement de développement `linux`. Outils et programmes utilisés :
>
> * [Git](https://git-scm.com/downloads)
> * [docker](https://docs.docker.com/v17.12/install/)
> * [nodejs](https://nodejs.org/en/download/)
> * [npm](https://www.npmjs.com/get-npm)
> * [python3](https://www.python.org/downloads/)
> * [pip3](https://pip.pypa.io/en/stable/installing/)
> * [virtualenv](https://virtualenv.pypa.io/en/stable/installation/)
## Introduction
Pour suivre ce tutoriel, il faut commencer par `fork` ce dépôt GitLab [https://githepia.hesge.ch/cours-gl/atelier-gitlab-ci-cd](https://githepia.hesge.ch/cours-gl/atelier-gitlab-ci-cd) pour avoir une copie du projet dans votre namespace, et avoir le contenu en local sur votre machine de travail (`git clone ...`)
Ce dépôt contient une application [**Node.js**](https://nodejs.org) conçue pour ce tutoriel : une simple calculatrice qui effectue des additions et des soustractions, et qui enregistre l'historique des opérations dans une base de donnée [**MongoDB**](https://www.mongodb.com). Le code contient des tests unitaires utilisant [**Jest.js**](https://jestjs.io/). On peut aussi générer de la documentation utilisant la syntaxe [**JSDoc**](https://jsdoc.app) et l'outil de génération [**documentation.js**](https://documentation.js.org).
Voici la structure du projet :

## Installation et utilisation
### Mise en place de la base de données
Pour lancer l'application, il faut
1. avoir une base de données **MongoDB** qui tourne. Pour cela, Nous vous proposons de lancer un container docker pour la base de données et exposer le port `3001` :
```shell
docker run --name mongo_gl -d -p 3001:27017 mongo:3.4
```
Pour vérifier que le container fonctionne, taper la commande suivante `docker ps`, un container ayant le nom `mongo_gl` doit exister :

1. Dans la racine du projet, créer une copie du fichier `.env_example` et nommer le `.env`. Ce fichier va contenir les variables d'environnement utilisées par l'application **Node.js** (l'adresse ip et le port de la base de données par exemple).
3. Modifier le contenue de ce fichier pour qu'il s'adapte à votre base de données **MongoDB** en mettant les bonnes valeurs. Par exemple, si vous avez choisi que votre base de données tourne sur le port `3001` et que le nom de la base de données soit `tuto_gl`, alors le contenu du fichier `.env` sera le suivant : `DATABASE=mongodb://localhost:3001/tuto_gl`
### Lancer l'application Node.js
Toujours dans la racine du projet GitLab :
1. Installer les dépendances :
```shell
npm install
```
2. Lancer l'application :
```shell
npm run watch
```
Cette commande permet de lancer l'application utilisant l'outil `nodemon` qui va relancer l'exécution après chaque modification dans un des fichiers dans le dossier de l'application. Cet outil est très utile durant la phase de développement.
L'application est donc lancée sur le port `3000`. Dans un navigateur, visitez l'adresse [localhost:3000](localhost:3000) :

Essayez d’effectuer quelques opérations d'addition et de soustraction. Après quelques essaies, vous allez remarquer que les résultats des additions ne sont pas corrects (sciemment). Nous allons corriger ça plus tard dans le tutoriel.
### Lancer les tests unitaires `Jest`
Trois tests unitaires pour chaque opération arithmétique (addition et soustraction) sont implémentés dans ce projet. Les tests se trouve dans le fichier `compute.test.js` sous le répertoire `test` :
```javascript
// test Compute.add function
test.each([[1, 1, 2], [-1, 2, 1], [-2, -1, -3]])(
'%i + %i equals %i', (a, b, expected) => {
expect(operation.add(a, b)).toBe(expected);
}
);
// test Compute.sub function
test.each([[1, 1, 0], [-1, 1, -2], [5, 3, 2]])(
'%i - %i equals %i', (a, b, expected) => {
expect(operation.sub(a, b)).toBe(expected);
}
);
```
Pour lancer les tests :
```shell
npm test
```
Vous pouvez remarquer que les tests pour l'addition échouent (nous allons corriger ça dans la section suivante).

### Générer la documentation
Pour générer la documentation, nous avons à disposition deux commandes :
* `npm run docs:md` : cette commande va générer la documentation sous forme d'un fichier *Markdown* `doc.md` sous le dossier `documentation` dans la racine du projet
* `npm run docs:html` : cette commande va générer la documentation sous forme d'un site web statique *html* accessible depuis le ficher `index.html` sous le dossier `public/docs`
## Intégration, documentation et déploiement continus
Nous allons mettre en place 3 jobs dans notre **CI / CD pipeline** :
* Un job `test` pour lancer les tests
* Un deuxième job `doc` pour générer la documentation sous forme d'un fichier *Markdown*, et le publier comme objet (artifact)
* Un dernier job `deploy` pour déployer l'application sur un serveur et générer la documentation sous forme *html*
### Créer une branche de développement `dev`
Nous allons commencer par créer une nouvelle branche dans notre projet GitLab appelée `dev` :
```shell
git branch dev
git checkout dev
git push -u origin dev
```
Ces commandes permettent de créer la branche `dev` et la définissent comme la branche actuellement active.
### Créer le fichier de configuration `.gitlab-ci.yml`
Dans la racine du projet, créez un fichier `.gitlab-ci.yml` :
```yaml
image: node:latest
stages:
- test
- doc
before_script:
- npm install
test:
stage: test
script:
- npm test
doc:
stage: doc
artifacts:
paths:
- documentation/doc.md
script:
# Build documentation in md file that will be available from gitlab interface
- npm run docs:md
```
Avec cette configuration, deux étapes ont été définies : une étape `test` pour lancer les tests, et une étape `doc` pour la génération de la documentation, avec un artifact comme output de l'étape `doc`.
> Vous pouvez trouver des exemples de fichiers `.gitlab-ci.yml` [ici](https://docs.gitlab.com/ee/ci/examples/)
Une fois créé, poussez les changements locaux vers le projet distant :
```shell
git add --all
git commit -m "Add project's CI pipeline configuration"
git push origin dev
```
Maintenant, vous pouvez observer le pipeline gitlab-ci en cours d'exécution sous **CI/CD → Pipelines** dans votre projet GitLab :

Vous pouvez remarquer que le pipeline échoue à l'étape `test` tel qu'attendu.
Dorénavant, après chaque commit, le pipeline s'exécute, et on peut vérifier s'il réussit ou pas.
Corrigeons le code pour que les tests réussissent.
### Corriger le fonction de l'addition
Le fichier `compute.js` dans la racine du projet, contient les implémentations des opérations de soustraction et d'addition. Dans ce fichier, nous avant laissé sciemment une faute de calcul : signe `*` à la place du signe `+`.
Corrigez cette erreur, et poussez les changement vers le dépôt GitLab distant :
```shell
git add --all
git commit -m "Fix error in addition implementation"
git push origin dev
```
Maintenant, notre pipeline réussit les test, génère et publie la documentation dans un artifact :

Le bouton à droite vous permet de télécharger les artifacts générés :

### Déployer l'application
Jusqu'ici, notre application est disponible seulement en local sur notre machine, en mode développement, sur le port `3000`. Maintenant nous allons déployer notre application sur un serveur de déploiement, une instance **aws** par exemple.
Pour cela, il faut d'abord créer et configurer une instance **aws EC2**. Dans le dossier `aws_script`, nous avons mis à disposition un script en python3 `create_and_run_aws_instance.py` qui permet de créer et lancer une instance **aws** à partir d'une image personnalisée (avec **node** et **npm** installés et une base de données **MongoDB** qui écoute sur le port `27017`).
1. #### Créer des "*credentials*"
* Depuis la [console aws](console.aws.amazon.com), naviguez à **My security credentials** depuis la liste quand vous cliquez sur votre nom d'utilisateur (en haut à droite) :

* Cliquez sur le menu **Access keys (access key ID and secret access key)**, puis sur le bouton **Create New Access Key** :

* Dans la fenêtre qui s'affiche, cliquez sur **Download Key File**, nommez le fichier `rootkey.csv` et enregistrez le dans le dossier `aws_script` dans la racine du projet.
> Ce fichier contient vos identifiants (**AWSAccessKeyId** et **AWSSecretKey**), qui vont être consommés par le script python pour créer et lancer une instance aws dans votre compte. Le fichier contenu du fichier `rootkey.csv` dois être sous cette forme :
>
> ```
> AWSAccessKeyId=votre_access_key_id
> AWSSecretKey=votre_secret_key
> ```
2. #### Créer et lancer une instance ec2 aws
Dans le dossier `aws_script`, exécutez les commandes suivante :
```shell
# se déplacer dans le dossier où se trouve le script
cd aws_script
# créer un environnement virtuel python3
virtualenv venv
# activer l'environnement virtuel
source venv/bin/activate
# installer les dépendences
pip3 install -r requirements.txt
# lancer le script
python3 create_and_run_aws_instance.py
# désactiver l'environnement virtuel
deactivate
```
Le script `create_and_run_aws_instance.py` permet de :
* Créer une pair de clé pour l’accès ssh à une instance,
* Enregistrer cette pair de clé dans un fichier nommé `gl_tuto_key.pem`
* Créer un groupe de sécurité avec une règle pour l'accès ssh (port 22) et une règle pour l'application web (port 8080)
* Finalement, créer une instance `gl_tuto_instance` et afficher son adresse ip public affectée
Si le script est lancée pour la première fois, vous aurez le output suivant :

Notez l'adresse ip public affecté à cette instance aws, on en aura besoin dans la section suivante.
> Vous pouvez vérifier la création de l’instance dans la console aws :
>
> 
3. #### Ajouter l'étape "déploiement" dans le pipeline ci/cd
Ajoutez le contenu suivant dans le fichier `.gitlab-ci.yml` :
* Dans `stages`, ajoutez une nouvelle étape `deploy`
* A la fin du fichier, ajoutez une tache dans l'étape `deploy` :
```yaml
deploy:
stage: deploy
only:
- master
# Disable the cache for this job (not needed)
before_script: []
script:
# Any future command that fails will exit the script
- set -e
# Install ssh-agent if it's not installed to use scp to copy files into our server
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
# Add the secret key of our deployment server to this gitlab docker image.
# GitLab at times add spaces which will fail ssh into server
# and hence the [tr -d '\r'].
# Issue here https://gitlab.com/gitlab-examples/ssh-private-key/issues/1
- eval $(ssh-agent -s)
- echo "$PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
# Below script will disable the prompt we get whenever we ssh into the box
# and get the message like this: "The authenticity of the host 'ip address'...
# To disable this, we need to create a entry in ~/.ssh/config to look something
# like this:
# Host *
# StrictHostKeyChecking no
- mkdir -p ~/.ssh
- touch ~/.ssh/config
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config
- echo "deploying to ${SERVER_IP}"
# Create the .env file with database ip, port and name from gitlab environment variables
- echo "DATABASE=mongodb://${DB_IP}:${DB_PORT}/${DB_NAME}" >> .env
- cat .env
# Copy project files to the deployment server
- ssh ubuntu@${SERVER_IP} "mkdir -p app"
- scp -r app.js compute.js models package-lock.json package.json public
routes tests views .env ubuntu@${SERVER_IP}:app/
# Restart the project on the deployment server (using forever)
# https://www.npmjs.com/package/forever
- ssh ubuntu@${SERVER_IP} "
forever stopall;
cd ~/app;
npm install --only=prod;
npm run docs:html;
NODE_ENV=prod forever start app.js
"
```
La tâche de déploiement fait appel à des variables d'environnement qu'on peut définir directement depuis l'interface web de GitLab. Pour cela, dans la page de votre projet GitLab :
* Naviguez à `Settings`→`CD / DI`.
* Dans la partie `Environment variables `, ajoutez les variables suivantes :
| Nom variable | Description | Valeur |
| ------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| `DB_IP` | Adresse ip de la base de donnée. Dans notre cas, la base de données s'exécute dans le serveur de déploiement (ce qui est fortement déconseillé dans un environnement de déploiement) | localhost |
| `DB_NAME` | Le nom de la base de données | tuto_gl |
| `DB_PORT` | Le port sur lequel la base de données écoute | 27017 |
| `PRIVATE_KEY` | La clé privé permettant l’accès en ssh à notre serveur de déploiement | Copier-coller le contenu du fichier `gl_tuto_key.pem` |
| `SERVER_IP` | L'adresse ip public du serveur de déploiement | L'adresse IP de l'instance aws créée par le script `create_and_run_aws_instance.py` |

* Finalement, cliquez sur `Save variables`
Poussez les changement vers le dépôt GitLab distant :
```shell
git add --all
git commit -m "Add deployment stage in ci pipeline"
git push origin dev
```
Notre pipeline réussit les test, génère et publie la documentation dans un artifact, mais n'exécute pas l'étape de déploiement :

Ce comportement est attendu vu que nous avons spécifier que l'étape de déploiement ne s'exécute que pour la branche `master` dans le fichier `.gitlab-ci.ymé` :
```yaml
deploy:
stage: deploy
only:
- master
```
Maintenant nous allons fusionner la branche "dev" avec la branche "master" :
```shell
# basculement vers la branche master
git checkout master
# fusion de la branche dev dans la branche actuelle (master)
git merge dev
# pousser les modification locale vers le dépôt distant
git push origin master
```
L'étape de déploiement est maintenant effectuée :

Maintenant l’application tourne sur votre serveur de déploiement sur le port 8080 : `SERVER_IP:8080`

> **Remarque:** Si vous voulez pousser des changements sans exécuter le pipeline de GitLab, vous pouvez ajouter `[skip ci]` dans votre message de commit.