382 lines
15 KiB
Markdown
382 lines
15 KiB
Markdown
\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 :
|
|
|
|
{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 :
|
|
|
|
{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 :
|
|
|
|
{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 :
|
|
|
|
* **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 :
|
|
|
|
* **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.
|