soutenance/content/50_realisations.md

382 lines
15 KiB
Markdown
Raw Permalink Normal View History

\newpage
# Mes réalisations
Ce projet m'a amené à participer sur de nombreux sujets. En voici deux qui, je
l'espère, illustreront bien ce que j'ai rencontré.
Je parlerais de **livraison continue** (CI/cd) et d'**états Terraform**.
## Réalisation 1 : Livraison continue
### Contexte
Durant le court laps de temps accordé au projet, nous arrivions à la fin, nous
étions en manque de ressources humaines :
* le travail de l'équipe n'était **pas toujours qualitatif**,
* parfois cela **ne répondait pas au besoin énoncé**,
* d'autres fois **une ressource prenait plus de temps que prévu** sur sa tâche.
Il en résulta **peu de moyens pour répondre à tous les besoins** du projet.
Cela raccourcissait le temps des tâches restantes.
En pareille situation il a fallu **faire avec l'existant** afin de créér une
chaîne de publication logicielle. J'ai pris en charge cette tâche.
### Analyse
À ce moment du projet nous n'avions que peu d'éléments :
* un dépôt applicatif (nommé **upstream**),
* un dépôt contenant les charts Helm qui utilisent les images publiées du
dépôt applicatif (nommé **charts**),
* et un dépôt contenant l'infrastructure de production (nommé **infra**).
Chaque dépôt peut utiliser l'intégration continue de Gitlab, appelée Gitlab CI
et utilisant des pipelines.
\newpage
On souhaite **créer une chaîne d'intégration continue** (CI, Continuous
Integration) pour passer d'un dépôt à l'autre suivant **une chaîne**, comme le
montre le schéma suivant :
![Schéma de la chaîne des dépôts](./media/schema_chaine_depots.png){height=40%}
Cela fait penser au chapitre précédent où nous parlions du [sujet de la
promotion logicielle](#collab). Suivant **quelle(s) règle(s)** pouvons-nous
**passer d'un dépôt à l'autre**, d'une version à l'autre ou (in)valider le
fonctionnement ?
Après plusieurs heures de réflexions, de dessins en tous genres et du recul,
j'ai constaté que :
* quoiqu'il arrive la **pipeline va lancer des tests** sur le code,
* et dans la **situation où l'on pose une étiquette** (appelé **tag** sur un
dépôt Git), c'est qu'on souhaite valider - indirectement - le travail effectué.
\newpage
Ainsi dans la situation où une étiquette apparaît, la pipeline diffère
légèrement. Il y a donc globalement **2 règles à suivre**, représentées par le
schéma suivant :
![Schéma des 2 règles principales pour la CI d'un dépôt](./media/schema_regles_ci.png){height=50%}
Comme le résultat positif des tests engendre la compilation puis la
publication d'une image sur un registre, une idée apparaît donc :
**informer les autres dépôts que l'image est disponible** !
### Solution
Suivant l'idée précédente qui consiste à informer les autres dépôts de la
publication récente d'une version, il a fallu trouver une solution.
Nous sommes sur Gitlab, avons des dépôts Git, et des pipelines qui se lancent
quand un commit est effectué. Dans la situation où une étiquette est posée,
pourquoi ne pas **utiliser Git lui-même à l'aide d'un commit sur le dépôt
suivant** ? Ainsi, sans outils supplémentaires, nous pouvons lier les
dépôts entre eux.
```{=latex}
\begin{sidewaysfigure}
En suivant cette logique, nous obtenons le schéma représentant la pipeline de
chaque dépôt~:
\includegraphics{./media/schema_3_pipelines.png}
\caption{Schéma des 3 pipelines}
\end{sidewaysfigure}
```
\newpage
Ce qui donne le savant mélange suivant :
![Schéma de toutes les pipelines imbriquées](./media/schema_toutes_pipelines.png){height=73%}
\newpage
Ainsi nous avons, par exemple, le code suivant permettant d'informer le dépôt **charts** qu'une nouvelle étiquette a été posée sur le dépôt **upstream** une fois le backend et le frontend publiés sur le registre Docker de Gitlab :
```bash {.numberLines}
inform-charts:
stage: inform-next
variables:
BACKEND_CHARTFILE_PATH: charts/backend/Chart.yaml
FRONTEND_CHARTFILE_PATH: charts/frontend/Chart.yaml
script:
- |
if ! [ -z "$COMMIT_TAG" ]; then
# Configure git user
git config --global user.email "git@dossmann.net"
git config --global user.name "CI Pipeline"
# Get CHARTS repository
git clone https://blankoworld:${ACCESS_TOKEN}@${CHARTS_REPOSITORY}
cd charts
# Update files
sed -i "s/^version: \".*\"/version: \"${COMMIT_TAG}\"/" \
"${BACKEND_CHARTFILE_PATH}"
sed -i "s/^appVersion: \".*\"/appVersion:\"${COMMIT_TAG}-back\"/" \
"${BACKEND_CHARTFILE_PATH}"
sed -i "s/^version: \".*\"/version: \"${COMMIT_TAG}\"/"
"${FRONTEND_CHARTFILE_PATH}"
sed -i "s/^appVersion: \".*\"/appVersion:\"${COMMIT_TAG}-front\"/" \
"${FRONTEND_CHARTFILE_PATH}"
# Add them to git staged area
git add "${BACKEND_CHARTFILE_PATH}" "${FRONTEND_CHARTFILE_PATH}"
# Commit and push result (to launch CHARTS CI for new code)
git commit -m "chore(release): Update back/front image version"
git tag $COMMIT_TAG
git push origin main --tags
fi
needs: [push-backend,push-frontend]
```
**Le code a dû être modifié (raccourcissement des lignes) pour les fins de
rédaction du présent document**.
La **ligne 27 à 29** posent un tag sur le dépôt **charts** en mettant
simplement à jour les versions utilisées du backend et du frontend dans les
charts Helm sur le registre Gitlab.
### Résultats
Après plusieurs points de détails corrigés, nous avons pu **automatiser
correctement la chaîne de production logicielle** de la **phase de
développement** jusqu'à **la production** en passant **par une validation
manuelle** après déploiement en environnement de pré-production **via
l'intégration continue de Gitlab** (Gitlab CI).
Ceci nous a permis de facilement **travailler sur 2 environnements**
distincts :
* l'environnement de **pré-production**,
* et l'environnement de **production**.
### Limites
Cette technique, bien qu'efficace, a ses limites :
* pour fonctionner sans développer plus, il a fallu **utiliser le même numéro
de version sur tous les dépôts** : on ne fait que passer le tag courant
au dépôt suivant. **Que faire si les numéros de version dérivent ?**,
* plus on ajoute de dépôts dans la chaîne, plus il y aura de code à faire et
plus il y aura de **complexité à chaîner les dépôts**.
Trouver une solution temporaire et efficace est - potentiellement - facile.
Mais avoir **une solution pérenne demande de l'expérience et plus de temps**.
### Amélioration(s) possibles(s)
Cette expérience a été enrichissante. Bien que ce système ait répondu à nos
besoins du moment, je pense qu'il serait envisageable de procéder autrement.
Dans un premier temps, **utiliser des dépôts indépendants** avec leur propre
pipeline qui **teste** puis **publie** sur des **dépôts utilisables** par
d'autres projets. Puis **chaque dépôt pourrait avoir un script de montée de
version** pour définir les contraintes spécifiques au projet, au dernier
numéro de version, etc.
Ainsi un dépôt d'infrastructure, par exemple, pourrait utiliser des versions
spécifiques de chacun de ses dépôts indépendants. Et ce serait à lui d'aller
regarder quelle est la dernière version d'un module ou d'une application. Et
d'agir en conséquence : lancer une batterie de tests puis intégrer la
nouvelle version disponible.
Ceci éviterait d'ajouter une quantité astronomique de code Gitlab dans le
dépôt initial (celui de l'application par exemple). Et cela **laisse la
responsabilité aux personnes suivantes** (qui utilisent le dépôt initial) plutôt
qu'aux personnes qui s'occupent du code dans le dépôt initial.
\newpage
## Réalisation 2 : États Terraform
### Contexte
Pendant la création du dépôt **infra** contenant - entre autre -
l'infrastructure de notre environnement de production, **nous avons subis
quelques pertes des états Terraform** posés sur le registre Gitlab.
Non seulement **les états Terraform étaient continuellement cassés** par les
collaborateurs, mais l'ignorance de ces derniers sur le fonctionnement de
Terraform nous a mis **une belle épine dans le pied**.
Il a fallu agir. Sur mon temps libre - au grand dam de ma famille - j'ai
décidé d'y regarder de plus près.
### Analyse
Comme nous avons plusieurs environnements, l'état Terraform va décrire chaque
fois un environnement. Nous aurons besoin au minimum des états Terraform
suivants :
* un état **gitlab-ci** pour les pipelines de test,
* un état **staging** pour l'environnement de pré-production,
* un état **prod** pour l'environnement de production,
* et éventuellement **un état Terraform par développeur**.
C'est ce dernier point que je cherchais à résoudre : **faciliter le
travail du développeur** sur Terraform **pour éviter toute bévue**.
Nous constatons également :
* le **manque de documentation** par le développeur pour savoir comment
procéder pour travailler sur Terraform,
* que **trop d'erreurs manuelles** sont effectués lors de l'utilisation des
commandes Terraform.
C'est ce qui **nécessite une simplification** et/ou un changement.
### Solution
Dans le Logiciel Libre, dans la plupart des dépôts, nous retrouvons un fichier
**Makefile** qui décrit les différentes étapes de compilation d'une
application, de son installation ou de la création d'une image.
Pourquoi ne pas partir sur cette solution habituelle et l'adapter à notre
cas ?
Ainsi l'idée serait de **reprendre les commandes habituelles de Terraform**, à
savoir :
* **init**,
* **plan**,
* **apply**,
* **destroy**,
* **fmt**,
* et **graph**.
J'ai imaginé mettre à disposition les mots-clés suivants avec le fichier
**Makefile** :
* **make init** - pour **initialiser l'état Terraform**,
* **make plan** - pour **vérifier les changements attendus** entre l'état Terraform local et l'environnement distant,
* **make apply** - pour **appliquer les changements**,
* **make destroy** - pour **supprimer l'ensemble des ressources distantes**,
* **make format** - pour **formater le contenu des fichiers \*.tf** trouvés,
* **make graph** - pour **générer une image vectorielle des dépendances entre
ressources** du projet,
* et **make clean** - pour **nettoyer les fichiers Terraform** non nécessaires.
**Ces commandes ne résolvent pas le problème en tant que tel**. C'est ce qui
est derrière qui va résoudre le souci : **des scripts qui vérifient que
tout soit OK** avant d'appliquer les commandes.
Ainsi l'**utilisation d'un fichier .env**, non disponible dans le dépôt de
code, permet de charger les informations utiles au choix d'un état Terraform
et de l'environnement distant à atteindre pour travailler.
La solution réside dans le fait que des scripts soient lancés sous chacune des
commandes make et qu'ils vérifient l'existence et le contenu du fichier
**.env**.
S'ajoute à cela une **documentation dans le fichier README.md**.
\newpage
Exemple de script avec **make init** pour bien comprendre de quoi il est
question :
```bash {.numberLines}
#!/usr/bin/env bash
#
# init.sh
#
# Initialize for a Developer
ENV_FILE="${PWD}/.env"
BACKEND_HTTP_FILE="${PWD}/backend.hcl"
# Need variables in this file
source "${ENV_FILE}" \
|| echo "Fichier ${ENV_FILE} manquant." \
|| exit 1
echo "[INFO] 'USERNAME': ${TF_HTTP_USERNAME}"
if ! [[ -f "${BACKEND_HTTP_FILE}" ]]; then
echo "[ERR] ${BACKEND_HTTP_FILE} manquant!"
exit 1
fi
terraform init \
-reconfigure \
-backend-config=${BACKEND_HTTP_FILE} $@
```
Et le contenu du fichier **env.example**, partagé aux développeurs comme d'un
template pour fabriquer le fichier **.env** :
```bash
# TOKEN DOIT avoir permissions read/write
export TF_HTTP_USERNAME="PseudoGitlab"
export TF_STATE_NAME="dev"
# Cf.https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
export TF_HTTP_PASSWORD="monPersonnalAccessTokenGitlab"
# Backend HTTP
export TF_HTTP_ADDRESS="<myurl>/state/${TF_STATE_NAME}"
export TF_HTTP_LOCK_ADDRESS="<myurl>/state/${TF_STATE_NAME}/lock"
export TF_HTTP_UNLOCK_ADDRESS="<myurl>/state/${TF_STATE_NAME}/lock"
# Accès AWS
export AWS_ACCESS_KEY_ID="AWS-access-key-id"
export AWS_SECRET_ACCESS_KEY="secret-AWS-access-key"
```
Pour faciliter la création de nouveaux dépôts utilisant Terraform, j'ai choisi
d'inclure ce travail dans un [dépôt dit **template** sur
Gitlab](https://gitlab.com/devu42/templates/terraform).
### Résultats
**À l'usage le travail sur Terraform s'est amélioré**. En procédant ainsi,
toute erreur se produisant sur Terraform n'affectait pas les autres
collaborateurs. **Chacun avait son état Terraform**.
La **documentation a permis** aux développeurs **de comprendre facilement**
quoi changer pour travailler. Et ils se sont sentis moins perdus.
### Limites
Toutefois, procéder ainsi a quelques limites&nbsp;:
* **toute modification** sur les éléments du Makefile, les scripts, la
documentation, etc. **doit être faite sur CHAQUE dépôt**. Ainsi plus on a de
dépôt, plus on consomme de temps à mettre à jour (avec des oublis possibles),
* **si le template évolue**, les dépôts ayant utilisé le template **n'ont plus
les mises à jour**. On pourrait imaginer faire un **git rebase, mais cela
casserait l'historique des dépôts** ou bien demanderait d'effectuer du travail
sur une branche Git à part.
**C'est très limitant**.
### Amélioration(s) possibles(s)
**Cette solution** a fait ses preuves. Elle **a été utile au moment où nous en
avions grandement besoin**. Elle permet de prendre de l'expérience à ce sujet.
Nous pourrions imaginer améliorer cela avec&nbsp;:
* **une forme de dépendance du fichier Makefile** et des scripts avec le dépôt
**template** utilisé (par exemple via une commande de mise à jour fournie),
* avoir une possibilité de **choisir entre la commande Terraform et OpenTofu**
par l'utilisation d'une variable d'environnement,
* et **permettre l'ajout d'arguments** aux commandes **make apply**, voire
ajouter une commande **make apply-autoapprove**.
Il est aussi possible qu'il existe une commande de type **wrapper** (qui
utilise Terraform et ses options) pour en faciliter l'usage, tel que
**kubectx** pour Kubernetes.
## Conclusion
Deux expériences variées et différentes qui s'imbriquent pourtant dans le
processus de production logicielle et de livraison continue. Il y a beaucoup
de choses à dire sur chacun des sujets, tellement ils sont passionnants. Ils
amènent également à prendre des initiatives et tester localement des outils.
Nous parlerons d'ailleurs dans le prochain chapitre d'une situation de travail
ayant amené à faire une recherche. Nous pourrons ainsi voir plus en détail le
processus de travail suivi afin d'aboutir à ce résultat.