Projet de fin de module DevOps & Cloud Computing — Master Excellence en Intelligence Artificielle
Faculté des Sciences Ben M'Sick — Université Hassan II de Casablanca — 2025–2026
- Vue d'ensemble
- Architecture
- Prérequis
- Structure du projet
- Étape 1 — Terraform
- Étape 2 — Création des VMs Oracle Cloud
- Étape 3 — Ansible
- Étape 4 — Cluster Kubernetes
- Étape 5 — Application Flask
- Étape 6 — Pipeline CI/CD
- Étape 7 — Déploiement Kubernetes
- Étape 8 — Monitoring
- Accès aux services
- Problèmes rencontrés et solutions
Ce projet met en place une infrastructure DevOps complète et automatisée sur Oracle Cloud Free Tier, couvrant l'ensemble du cycle de vie applicatif : du provisionnement de l'infrastructure jusqu'au monitoring en production.
Stack technologique :
| Outil | Rôle |
|---|---|
| Terraform | Provisionnement réseau Oracle Cloud |
| Ansible | Configuration des VMs (Docker, Kubernetes) |
| Docker | Conteneurisation multi-architecture (amd64 + arm64) |
| Kubernetes (kubeadm) | Orchestration des conteneurs |
| GitHub Actions | Pipeline CI/CD automatisé |
| Prometheus + Grafana | Monitoring et visualisation |
Machine Locale (WSL Ubuntu 24.04)
├── Terraform → Réseau Oracle Cloud (VCN, Subnet, Security Lists)
├── Ansible → Configuration Docker + Kubernetes sur les VMs
└── GitHub → Pipeline CI/CD (test → build → deploy)
Oracle Cloud (af-casablanca-1)
├── k8s-master 84.8.221.147 (privée: 10.0.0.52)
│ ├── Kubernetes Control Plane (API Server, Scheduler, etcd)
│ └── Stack monitoring (Prometheus + Grafana)
└── k8s-worker 84.8.223.153 (privée: 10.0.0.108)
├── Kubernetes Worker Node
└── Application Flask — 2 replicas — port 30000
Flux CI/CD:
git push → Tests pytest → Docker Build (arm64+amd64) → Docker Hub → kubectl deploy → Pods Running
Spécifications des VMs :
| VM | Shape | IP Publique | IP Privée | Rôle |
|---|---|---|---|---|
| k8s-master | VM.Standard.A1.Flex (2 OCPU, 12 GB) | 84.8.221.147 | 10.0.0.52 | Control Plane |
| k8s-worker | VM.Standard.A1.Flex (2 OCPU, 12 GB) | 84.8.223.153 | 10.0.0.108 | Worker Node |
- WSL2 avec Ubuntu 24.04
- Compte Oracle Cloud Free Tier (région af-casablanca-1)
- Compte GitHub avec accès Actions
- Compte Docker Hub
- Python 3.11+
devops-project/
├── Présentation_Infrastructure DevOps Automatisée sur Oracle Cloud CICD.pdf
├── Rapport_Infrastructure_DevOps_Automatisée_sur_Oracle_Cloud.pdf
├── ansible/
│ ├── inventory.ini # Hôtes cibles (master + worker)
│ └── playbook-setup.yml # Installation Docker + Kubernetes
├── app/
│ ├── app.py # API REST Flask (/, /health, /ui)
│ ├── Dockerfile # Image multi-arch python:3.11-slim
│ ├── requirements.txt # Flask, Gunicorn, Flask-CORS
│ ├── static/
│ │ └── index.html # Dashboard web de supervision
│ └── tests/
│ └── test_app.py # Tests pytest (test_home, test_health)
├── k8s/
│ ├── deployment.yaml # 2 replicas, probes, resource limits
│ └── service.yaml # NodePort 30000 → port 5000
└── terraform/
├── main.tf # VCN, Subnet, Security Lists, Instances
├── terraform.tfstate # État courant de l'infrastructure
└── terraform.tfstate.backup
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg \
--dearmor -o /usr/share/keyrings/hashicorp.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] \
https://apt.releases.hashicorp.com noble main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y terraform
⚠️ Utilisernoble(Ubuntu 24.04) et nonfocal(Ubuntu 20.04).
mkdir -p ~/.oci
mv ~/Downloads/hajar*.pem ~/.oci/oci_api_key.pem
chmod 600 ~/.oci/oci_api_key.pemCréer ~/.oci/config :
[DEFAULT]
user=ocid1.user.oc1..xxxxxxxxxxxxxxxx
fingerprint=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
tenancy=ocid1.tenancy.oc1..xxxxxxxxxxxxxxxx
region=af-casablanca-1
key_file=~/.oci/oci_api_key.pemGénérer la clé SSH :
ssh-keygen -t rsa -b 4096 -C "hajar@devops-project"# Fix réseau WSL2
echo "13.224.83.61 registry.terraform.io" | sudo tee -a /etc/hosts
cd terraform/
terraform init
terraform plan
terraform applyRésultat : VCN devops-vcn et subnet public-subnet créés.
⚠️ Erreur404-NotAuthorizedOrNotFoundlors de la création des VMs — restrictions IAM Free Tier. Les VMs ont été créées manuellement (voir Étape 2) en réutilisant le réseau Terraform.
Depuis la console Oracle Cloud → Compute → Instances → Create Instance :
| Paramètre | Valeur |
|---|---|
| Nom | k8s-master / k8s-worker |
| Image | Canonical Ubuntu 22.04 |
| Shape | VM.Standard.A1.Flex |
| OCPUs | 2 |
| RAM | 12 GB |
| Réseau | VCN devops-vcn + subnet public |
| Clé SSH | Coller le contenu de ~/.ssh/id_rsa.pub |
Security List — Règles Ingress à ouvrir :
| Port | Protocole | Usage |
|---|---|---|
| 22 | TCP | SSH |
| 6443 | TCP | Kubernetes API Server |
| 10250 | TCP | kubelet |
| 30000–32767 | TCP | NodePort (application) |
| 31000 | TCP | Grafana |
| Tous | ICMP | Ping / diagnostic |
pip3 install ansible --break-system-packages# ansible/inventory.ini
[master]
k8s-master ansible_host=84.8.221.147 ansible_user=ubuntu \
ansible_ssh_private_key_file=~/.ssh/id_rsa
[worker]
k8s-worker ansible_host=84.8.223.153 ansible_user=ubuntu \
ansible_ssh_private_key_file=~/.ssh/id_rsa
[all:vars]
ansible_python_interpreter=/usr/bin/python3cd ansible/
# Test de connectivité
ansible all -i inventory.ini -m ping
# Configuration complète
ansible-playbook -i inventory.ini playbook-setup.ymlLe playbook installe sur les deux VMs :
- Docker CE (arch=arm64, repo jammy)
- containerd avec
SystemdCgroup=true - kubeadm, kubelet, kubectl v1.28 (version fixée)
- Modules noyau
overlayetbr_netfilter - Paramètres sysctl pour Kubernetes
- Désactivation du swap
ssh ubuntu@84.8.221.147
# Configurer containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' \
/etc/containerd/config.toml
sudo systemctl restart containerd
# Initialiser le cluster
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-advertise-address=10.0.0.52
# Configurer kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Installer Flannel (plugin réseau)
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# Obtenir la commande join pour le worker
kubeadm token create --print-join-commandssh ubuntu@84.8.223.153
# Même configuration containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' \
/etc/containerd/config.toml
sudo systemctl restart containerd
# Rejoindre le cluster (commande obtenue depuis le master)
sudo kubeadm join 10.0.0.52:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# devops-vcn-571552 Ready <none> ... v1.28.15
# vcn-devops Ready control-plane ... v1.28.15
⚠️ Après redémarrage des VMs, si le port 6443 devient inaccessible, réinitialiser iptables :sudo iptables -F && sudo iptables -X sudo iptables -t nat -F && sudo iptables -t nat -X sudo iptables -P INPUT ACCEPT sudo iptables -P FORWARD ACCEPT sudo iptables -P OUTPUT ACCEPT
| Route | Réponse |
|---|---|
GET / |
{"message": "API DevOps...", "status": "running", "version": "1.0.0"} |
GET /health |
{"status": "healthy"} — utilisé par les sondes Kubernetes |
GET /ui |
Dashboard web de supervision |
cd app/
pip install -r requirements.txt pytest
pytest tests/ -vdocker build -t flask-devops:local .
docker run -p 5000:5000 flask-devops:local
curl http://localhost:5000/healthGitHub → Settings → Secrets and variables → Actions :
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME |
Nom d'utilisateur Docker Hub |
DOCKERHUB_TOKEN |
Token Docker Hub (Account Settings → Security) |
KUBE_CONFIG |
Contenu de ~/.kube/config encodé en base64 |
MASTER_SSH_KEY |
Clé SSH privée (~/.ssh/id_rsa) |
# Générer KUBE_CONFIG (sur le master)
cat ~/.kube/config | base64 -w 0Push → [test] → [build-push] → [deploy]
- test :
pytest app/tests/ -v— échec = pipeline stoppé - build-push : Docker Buildx multi-arch (
linux/amd64,linux/arm64) → Docker Hub - deploy :
kubectl set image+kubectl rollout status— rolling update sans downtime
Le fichier .github/workflows/ci-cd.yml se trouve à la racine du dépôt GitHub.
# Copier les manifests sur le master
scp -r k8s/ ubuntu@84.8.221.147:~/k8s/
# Appliquer
ssh ubuntu@84.8.221.147
kubectl apply -f ~/k8s/deployment.yaml
kubectl apply -f ~/k8s/service.yaml
# Vérifier
kubectl get pods
kubectl get svcCaractéristiques du déploiement :
- 2 replicas (haute disponibilité)
livenessProbesur/health(redémarre si le pod ne répond plus)readinessProbesur/health(attend que le pod soit prêt avant d'envoyer du trafic)- Limites : 64–128 Mi RAM · 100–200m CPU
ssh ubuntu@84.8.221.147
# Installer Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Ajouter le repo
helm repo add prometheus-community \
https://prometheus-community.github.io/helm-charts
helm repo update
# Installer le stack complet
kubectl create namespace monitoring
helm install kube-prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--set grafana.service.type=NodePort \
--set grafana.service.nodePort=31000
# Vérifier
kubectl get pods -n monitoringRécupérer le mot de passe Grafana :
kubectl get secret -n monitoring kube-prometheus-grafana \
-o jsonpath='{.data.admin-password}' | base64 -dDashboards Grafana à importer :
| ID | Nom |
|---|---|
| 6417 | Kubernetes Cluster (Prometheus) |
| 1860 | Node Exporter Full |
| 15760 | Kubernetes cluster monitoring |
| Service | URL |
|---|---|
| API Flask | http://84.8.223.153:30000 |
| Health check | http://84.8.223.153:30000/health |
| Dashboard UI | http://84.8.223.153:30000/ui |
| Grafana | http://84.8.223.153:31000 |
| Problème | Cause | Solution |
|---|---|---|
Unable to locate package terraform |
Repo focal au lieu de noble |
Utiliser noble dans l'URL du dépôt HashiCorp |
404-NotAuthorizedOrNotFound Terraform |
Restrictions IAM Free Tier root | Créer les VMs manuellement via la console Oracle |
NumCPU: 1, minimum: 2 kubeadm |
VMs avec 1 OCPU | Redimensionner à 2 OCPUs dans la console Oracle |
br_netfilter not found |
Module noyau non chargé | sudo modprobe br_netfilter + sysctl |
| Port 6443 inaccessible | Firewall Oracle Cloud | Ajouter règle Ingress TCP 6443 dans Security List |
exec format error pods |
Image x86 sur ARM64 | Build multi-arch avec QEMU + Docker Buildx |
| TLS certificate error CI/CD | IP publique absente du certificat | insecure-skip-tls-verify: true dans kubeconfig |
| Port 6443 timeout après redémarrage | Règles iptables réinitialisées | Vider iptables et relancer kubelet |
No such file or directory: /app/static/ |
Dossier absent du Dockerfile | Ajouter COPY static/ static/ dans le Dockerfile |
Hajar ELKHALIDI & Nohaila ICHOU — Master Excellence en Intelligence Artificielle — FSBM — Université Hassan II de Casablanca — 2025–2026