First commit

This commit is contained in:
Johan
2025-12-19 10:07:55 +01:00
commit 0a1abf97f1
17 changed files with 2313 additions and 0 deletions

462
README.md Normal file
View File

@@ -0,0 +1,462 @@
# Backend Python : démonstration conteneurisation, orchestration, CI/CD, DevSecOps et déploiement Cloud AWS
## Avant-propos
Ce projet est une démonstration complète des pratiques modernes de développement et de déploiement d'applications backend en Python.
A titre informatif, l'application est une simple API web construite avec FastAPI, qui expose un endpoint renvoyant un message de bienvenue.
Elle est lançable en local via :
- `cd src`
- `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000`
Cependant, il n'est pas nécessaire de lancer l'application en local pour suivre cette démonstration.
De la même façon, à noter que cette commande est utilisé pour formatter le code source avec `ruff` en local :
```bash
poetry run ruff format .
```
Elle n'est pas non plus nécessaire, dans la mesure où le code source est déjà formaté.
## Première partie : conteneurisation et déploiement avec Docker Desktop
### Contexte
Vous avez à votre disposition les éléments suivants :
* Le **code source** d'une application Python backend de type Web API.
* Un **`Dockerfile`** qui définit comment l'application doit être "empaquetée".
* **Docker Desktop** est installé et en cours d'exécution sur votre machine.
Votre mission est de suivre les étapes ci-dessous pour construire l'image de l'application et la lancer.
---
### Étapes à réaliser
#### 1. Prise de connaissance
Avant d'exécuter les commandes, prenez une minute pour :
1. Ouvrir et lire le contenu du **`Dockerfile`**. Essayez de comprendre ce que chaque ligne de commande (par exemple `FROM`, `COPY`, `RUN`, `CMD`) est censée faire.
2. Ouvrir un **terminal** sous PyCharm dans ce projet.
3. Vous assurez que vous êtes bien positionné dans le répertoire qui contient le `Dockerfile` et le code source de l'application. Vous pouvez utiliser la commande `dir` sur Windows pour vérifier la présence des fichiers.
#### 2. Étape 1 : construire l'image Docker
La première étape consiste à demander à Docker de lire le `Dockerfile` et de construire l'image.
Exécutez la commande suivante dans votre terminal :
> ```bash
> docker build -t hello-world-app:1.0 .
> ```
**Ce que fait cette commande :**
* `docker build` : c'est la commande principale pour construire une image.
* `-t hello-world-app:1.0` : l'option `-t` (pour *tag*) permet de **nommer** votre image. Ici, nous la nommons `hello-world-app` et lui donnons la version (tag) `1.0`.
* `.` : le point final est très important. Il indique à Docker où se trouve le contexte de build (c'est-à-dire les fichiers à utiliser), dans notre cas, le répertoire courant.
Attendez que le processus de build se termine. Vous devriez voir Docker exécuter les différentes étapes définies dans le `Dockerfile`.
Vous pouvez vérifier que l'image a bien été créée en exécutant la commande suivante :
> ```bash
> docker images
> ```
#### 3. Étape 2 : lancer le conteneur (déploiement)
Maintenant que l'image est construite, vous pouvez la "lancer" pour créer une instance de votre application : un **conteneur**.
*Note : Nous allons mapper le port externe 8080 (machine physique) sur le port 8080 interne au conteneur. Si votre Dockerfile expose un port différent, ajustez la commande en conséquence.*
Exécutez la commande suivante :
> ```bash
> docker run -d -p 8080:8000 -e SECRET_KEY="123456" --name mon-conteneur hello-world-app:1.0
> ```
**Ce que fait cette commande :**
* `docker run` : la commande pour démarrer un conteneur.
* `-d` : (pour *detached*) lance le conteneur en arrière-plan, pour que le terminal reste disponible.
* `-p 8080:8000` : (pour *publish*) mappe le port de votre machine (le premier `8080`) au port *interne* du conteneur (le second `8080`). C'est ce qui rend votre application accessible depuis votre machine.
* `-e SECRET_KEY` : définit une variable d'environnement `SECRET_KEY` à l'intérieur du conteneur avec la valeur `"123456"`. Cela peut être utile pour configurer des paramètres sensibles, et dépend de votre application.
* `--name mon-conteneur` : donne un nom facile à retenir à votre conteneur.
* `hello-world-app:1.0` : le nom de l'image que vous avez construite à l'étape précédente.
Vous pouvez vérifier que le conteneur est bien en cours d'exécution en utilisant la commande suivante :
> ```bash
> docker ps
> ```
Si elle n'est pas listée, utilisez `docker ps -a` pour voir tous les conteneurs, y compris ceux qui sont arrêtés.
Ainsi que pour voir les logs du conteneur :
> ```bash
> docker logs mon-conteneur
> ```
Si tout est fonctionnel, votre application devrait maintenant être en cours d'exécution à l'intérieur du conteneur Docker !
Vous pouvez tester l'URL `http://localhost:8080` dans votre navigateur ou utiliser un outil comme `curl` ou Postman pour interagir avec l'API.
```mermaid
graph LR
Utilisateur[Navigateur/Client] -- Requête --> MachineHote[Votre Machine Hôte<br/>localhost:8080]
subgraph MachineHote
direction LR
PortHote(Port 8080)
end
subgraph ConteneurDocker [Conteneur: mon-conteneur]
direction LR
PortConteneur(Port 8000) --- App[Application FastAPI]
end
PortHote -- Mappage de port -p 8080:8000 --> PortConteneur
```
-----
### Vérification
Vous avez terminé ! Pour vérifier que tout fonctionne :
1. **Ouvrez Docker Desktop :** Allez dans l'onglet **"Containers"**. Vous devriez voir votre conteneur nommé `mon-conteneur` avec le statut "Running".
2. **Vérifiez l'application :** Si l'application est une application web, ouvrez votre navigateur et rendez-vous sur `http://localhost:8080`. Vous devriez voir votre application s'afficher.
3. **(Optionnel) Commande de vérification :** Vous pouvez aussi taper `docker ps` dans votre terminal pour lister les conteneurs en cours d'exécution.
### Nettoyage
Pour arrêter et supprimer le conteneur, exécutez les commandes suivantes :
> ```bash
> docker stop mon-conteneur
> docker rm mon-conteneur
> ```
## Seconde partie : orchestration avec Kubernetes (Docker Desktop)
Nous allons maintenant prendre notre application, `hello-world-app`, et la déployer sur un vrai cluster Kubernetes (celui fourni par Docker Desktop).
**Objectif :** démontrer le passage de Docker à Kubernetes d'une application en utilisant des manifestes YAML.
---
### Prérequis
Pour cette démonstration, nous avons besoin de :
1. **Docker Desktop** avec l'option Kubernetes activé (Settings > Kubernetes > `Enable Kubernetes`).
2. De l'image `hello-world-app:1.0` que nous avons "buildée" localement.
3. Un dossier `k8s/` contenant nos deux fichiers : `deploiement.yaml` et `service.yaml`. Ils sont fournis. N'hésitez pas à les ouvrir pour voir leur contenu.
---
### Démonstration
#### Étape 1 : vérifier l'état initial
D'abord, assurons-nous que notre cluster est vide.
> ```bash
> # Affiche les pods (conteneurs), les services (réseaux), et les déploiements
> kubectl get pods,svc,deploy
> ```
Vous verrez, il n'y a rien à part le service "kubernetes" par défaut.
#### Étape 2 : le Secret
Notre application a besoin d'une `SECRET_KEY`. On ne la met pas en clair dans nos fichiers de configuration. On crée un objet "Secret" dans Kubernetes.
> ```bash
> # Je crée un secret nommé 'hello-world-secret'
> # Ce nom doit correspondre à ce qui est attendu dans votre deploiement.yaml
> kubectl create secret generic hello-world-secret --from-literal=SECRET_KEY='123456'
> ```
>
> ```bash
> # On peut vérifier qu'il existe
> kubectl get secrets
> ```
#### Étape 3 : explication des fichiers YAML
Les "recettes" sont déjà prêtes dans notre dossier `k8s/`.
Rapidement, ce qu'ils contiennent :
1. **`deploiement.yaml`** :
* Demande 1 copie (`replicas: 1`) de notre application (en production, on pourrait avoir plusieurs instances pour supporter une charge accrue).
* Utilise notre image `image: hello-world-app:1.0`.
* Crucial : `imagePullPolicy: IfNotPresent` pour qu'il utilise notre image locale, et ne tente pas de la télécharger depuis un registre distant.
* Injecte notre secret (`123456`) en tant que variable d'environnement `SECRET_KEY`.
* Donne au pod l'étiquette (label) `app: hello-world-app`.
2. **`service.yaml`** :
* Crée un service réseau.
* `type: LoadBalancer` pour l'exposer sur notre `localhost`.
* `selector: app: hello-world-app` pour qu'il sache quels pods cibler (ceux de notre déploiement).
* Mappe le port `8080` (navigateur) au port `8000` (conteneur).
#### Étape 4 : appliquer la configuration
Maintenant, appliquons *tout* le contenu du dossier `k8s/` en une seule commande.
> ```bash
> kubectl apply -f k8s/
> ```
Kubernetes va lire les deux fichiers et créer/mettre à jour les ressources.
#### Étape 5 : observer le déploiement
Voyons ce que Kubernetes est en train de faire.
> ```bash
> # -w signifie "watch", la commande va se rafraîchir toute seule
> kubectl get pods -w
> ```
Vous voyez ? Il est en `ContainerCreating`... et voilà, `Running` ! Notre application est lancée.
#### Étape 6 : vérification finale
Vérifions que notre service est prêt.
> ```bash
> # Remplacez 'hello-world-app-service' si votre service a un autre nom dans le YAML
> kubectl get service hello-world-app-service
> ```
Vous voyez, il a une `EXTERNAL-IP` (IP Externe) : `localhost`.
Cela signifie que si vous allez sur... [http://localhost:8080](http://localhost:8080)
Vous verrez un retour de notre application (API) qui tourne sur Kubernetes.
#### Schéma de ce qui se passe
```mermaid
graph LR
Utilisateur["1. Navigateur"] -- "Requête http://localhost:8080" --> ServiceK8S["2. Service: hello-world-app-service<br/>(type: LoadBalancer)"]
ServiceK8S -- "3. Sélectionne les pods<br/>(selector: app: hello-world-app)" --> PodK8S
subgraph PodK8S ["4. Pod: hello-world-app-..."]
Conteneur["5. Conteneur: hello-world-app:1.0<br/>(Port 8000)"]
end
PodK8S -- "Réponse" --> ServiceK8S
ServiceK8S -- "Réponse" --> Utilisateur
```
1. Votre **Navigateur** parle à `localhost:8080`.
2. Docker Desktop intercepte et envoie au **Service** (`hello-world-app-service`).
3. Le **Service** voit quels **Pods** ont l'étiquette `app: hello-world-app`.
4. Il envoie la requête au **Pod** (et donc à notre conteneur `hello-world-app:1.0`).
5. L'application répond.
---
### Nettoyage
Pour tout arrêter et supprimer, c'est aussi simple que d'appliquer la configuration :
> ```bash
> # Supprime toutes les ressources créées via le dossier k8s/
> kubectl delete -f k8s/
>
> # N'oubliez pas le secret !
> kubectl delete secret hello-world-secret
> ```
>
> ```bash
> # Si on vérifie à nouveau...
> kubectl get pods,svc,deploy
> ```
Tout a disparu. C'est la puissance de la configuration déclarative.
## Troisième partie : CI/CD et DevSecOps avec GitHub Actions, et déploiement Cloud AWS
Nous avons construit et testé notre application localement avec Docker et Kubernetes. Il est temps d'automatiser tout ce processus.
**Objectif :** mettre en place un pipeline de CI/CD (Intégration Continue / Déploiement Continu) complet. À chaque fois que nous pousserons du code, GitHub Actions va automatiquement :
1. **CI (Intégration)** : lancer les tests, le "linting" (qualité de code), et les scans de sécurité (DevSecOps).
2. **CD (Déploiement)** : si la CI passe, déployer automatiquement notre application sur le cloud AWS.
---
### Contexte
* Vous avez un projet fonctionnel sur votre machine.
* Vous disposez d'un fichier de workflow : `.github/workflows/ci-cd.yaml`.
* **Concernant AWS :** un environnement (Elastic Beanstalk) est **déjà configuré pour vous** sur le cloud. Vous n'avez PAS besoin de vous connecter à la console AWS. Votre seule mission est de fournir à GitHub les "clés" (Secrets) pour qu'il puisse s'y connecter à votre place.
---
### Étapes à réaliser
#### 1. Prise de connaissance du pipeline CI/CD
Ouvrez le fichier `.github/workflows/ci-cd.yaml` dans PyCharm.
Prenez 5 minutes pour le lire. Vous n'avez pas besoin de tout comprendre, mais identifiez sa structure :
* **`on: push: branches: [ master ]`** : le pipeline se déclenche à chaque `push` sur la branche `master`.
* **`jobs:`** : il y a 3 "gros" travaux (jobs) :
1. **`test`** : c'est la partie **CI / DevSecOps**. Ce job installe Python, lance les tests (`pytest`), vérifie la qualité du code (`ruff`), et scanne les failles de sécurité (`bandit`, `trivy`).
2. **`deploy-python-aws-eb`** : c'est la **CD**. Il déploie l'application sur AWS en mode "Python".
3. **`deploy-docker-aws-eb`** : un deuxième déploiement, cette fois en mode "Docker", sur un autre environnement.
```mermaid
graph TD
A["Développeur: git push<br/>(branche master)"] --> B{"GitHub Actions déclenchés"}
B --> C["Job 1 : CI / DevSecOps"]
subgraph C["Job 1 : CI / DevSecOps"]
C1["pytest"]
C2["ruff"]
C3["bandit"]
C4["trivy"]
end
C -- "Si succès" --> D["Job 2: deploy-python-aws-eb"]
C -- "Si succès" --> E["Job 3: deploy-docker-aws-eb"]
D --> F["Déploiement Python sur AWS"]
E --> G["Déploiement Docker sur AWS"]
```
#### 2. Initialiser Git dans PyCharm
Si ce n'est pas déjà fait, vous devez "dire" à PyCharm que ce projet est suivi par Git.
1. Allez dans le menu `VCS` (Version Control System).
2. Cliquez sur `Enable Version Control Integration...`.
3. Choisissez **Git** dans la liste.
#### 3. Créer le Dépôt Distant (GitHub)
1. Allez sur [GitHub.com](https://github.com) et connectez-vous.
2. Créez un **Nouveau Dépôt** (New Repository).
3. Nommez-le (ex: `hello-world-fastapi`).
4. Sélectionnez **`Private`** (privé).
5. **IMPORTANT :** ne cochez **AUCUNE** case (ni `README`, ni `.gitignore`). Laissez-le vide.
6. Cliquez sur `Create repository`.
#### 4. Configurer les Secrets AWS dans GitHub
C'est l'étape la plus sensible. GitHub a besoin des "clés" AWS pour s'y connecter.
1. Sur la page de votre nouveau dépôt GitHub, allez dans l'onglet `Settings` (Paramètres).
2. Dans le menu de gauche, naviguez vers `Secrets and variables` > `Actions`.
3. Cliquez sur `New repository secret`.
4. Créez le premier secret :
* Nom : `AWS_ACCESS_KEY_ID`
* Valeur : (insérez la valeur fournie dans le canal Teams de votre cours)
5. Créez le second secret :
* Nom : `AWS_SECRET_ACCESS_KEY`
* Valeur : (insérez la valeur fournie dans le canal Teams de votre cours)
> **Attention :** Les noms `AWS_ACCESS_KEY_ID` et `AWS_SECRET_ACCESS_KEY` doivent être **exactement** les mêmes que ceux utilisés dans le fichier `ci-cd.yaml`.
#### 5. Lier PyCharm et GitHub (Le "Push")
Votre code est en local, le dépôt est sur GitHub. Il faut maintenant les lier.
1. **Ajouter la "remote" :**
* Dans PyCharm, allez dans `Git` > `Manage Remotes...`.
* Cliquez sur le `+`.
* Pour l'**URL**, collez l'URL fournie par GitHub (celle en `https:// .../... .git`).
* Laissez le nom `origin`. Cliquez sur `OK`.
2. **Faire le premier Commit & Push :**
* Ouvrez la fenêtre `Commit` (souvent à gauche).
* Sélectionnez tous vos fichiers pour les "commiter" (les "Staged").
* Écrivez un message de commit (ex: `Initial commit`).
* Cliquez sur la flèche à côté de `Commit` et choisissez **`Commit and Push...`**.
* Confirmez le `push` vers la branche `origin/master`.
#### 6. Observer la magie (CI/CD)
1. Retournez sur votre dépôt GitHub.
2. Cliquez sur l'onglet **`Actions`**.
3. Vous devriez voir votre pipeline, avec votre message "Initial commit", en train de s'exécuter (une icône jaune).
4. Cliquez dessus pour voir les `jobs` (`test`, `deploy-docker`, `deploy-python`) s'exécuter en direct. Si tout se passe bien, ils passeront au vert.
#### 7. Provoquer un nouveau déclenchement
Votre pipeline fonctionne ! Faisons un petit changement pour le voir se re-déclencher.
1. Retournez dans PyCharm.
2. Ouvrez n'importe quel fichier, par exemple ce `README.md`.
3. Ajoutez un simple commentaire ou un espace vide quelque part.
4. Ouvrez la fenêtre `Commit` dans la barre latérale gauche.
5. Écrivez un message (ex: `Fake commit`).
6. Faites `Commit and Push...` à nouveau.
7. Retournez sur l'onglet `Actions` de GitHub : un nouveau pipeline s'est automatiquement lancé !
---
### Note importante : comment cela se passe en entreprise ?
Ce que nous avons fait est une **simplification pour l'exercice**.
#### Trunk Based Development
Avec la stratégie `Trunk Based Development`, on s'autorise à travailler directement sur la branche `master` (ou `main`), mais on s'autorise tout de même à créer des branches de fonctionnalité avec une durée de vie très courte (quelques heures). Une telle stratégie est recommandée pour exécuter la CI/CD plus souvent.
```mermaid
graph TD
M1["master (c1)"] --> M2["master (c2)"]
M2 --> F1["feature/A (fA1)"]
F1 --> M3["master (m1 - merge)"]
M3 --> F2["feature/B (fB1)"]
F2 --> M4["master (m2 - merge)"]
M4 --> M5["master (c3)"]
```
#### Git Flow
En entreprise, vous verrez également la stratégie `Git Flow`, qui est un peu plus formelle :
1. **Branche `master` (ou `main`) :** contient le code en production. Personne n'y touche directement.
2. **Branche `develop` :** contient la version en cours de développement.
3. **Branches de fonctionnalité (ou bug) :** pour chaque nouvelle tâche ou bug (ex: `feature/add-login`), vous créez une branche à partir de `develop`, avec une durée de vie plus longue (quelques jours).
**Le pipeline de CI (Tests) :**
Le pipeline de `test` (comme celui de notre job `test`) se déclencherait automatiquement lorsque vous essayez de "merger" (fusionner) votre branche de fonctionnalité dans `develop` (via une *Pull Request*).
**Le pipeline de CD (Déploiement) :**
Le déploiement en production (notre job `deploy-...`) ne se déclencherait **PAS** sur un simple commit, mais seulement lorsque le chef de projet décide de créer une nouvelle "version" (un **tag** Git, ex: `v1.0.1`).
```mermaid
graph TD
%% Définir les lignes principales
subgraph Ligne master
M0("init") --> M1("v1.0.0") --> M2("v1.0.1")
end
subgraph Ligne develop
D1("d1") --> D2("d2 (merge login)") --> D3("d3") --> D4("d4 (merge release)") --> D5("d5 (merge hotfix)")
end
%% Créer les liens entre les lignes
M0 --> D1;
%% Branche de fonctionnalité
D1 --> F1("feature/login")
F1 --> D2
%% Branche de release
D3 --> R1("release/v1.0")
R1 --> M1
R1 --> D4
%% Branche de hotfix
M1 --> H1("hotfix/auth-bug")
H1 --> M2
H1 --> D5
```