Exercice 07 : Pipeline CI/CD avec Cloud Build
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Écrire un fichier
cloudbuild.yamlavec plusieurs étapes - ✅ Déclencher un build Cloud Build depuis la CLI ou un push GitHub
- ✅ Builder une image Docker et la stocker dans Artifact Registry
- ✅ Déployer l'image sur Cloud Run automatiquement
Durée estimée : 40 minutes
Difficulté : ⭐⭐⭐☆☆ (Intermédiaire)
Prérequis : Docker installé localement, notions Dockerfile, compte GitHub optionnel
📖 Contexte
Cloud Build est le service CI/CD serverless de GCP. Vous ne gérez pas de serveurs d'intégration continue (pas de Jenkins, pas de GitLab Runner à maintenir). Cloud Build exécute chaque étape dans un conteneur Docker isolé, ce qui garantit la reproductibilité. Il s'intègre nativement avec GitHub, GitLab et Cloud Source Repositories.
📋 Énoncé
Construisez un pipeline qui teste, build et déploie une application web.
🧭 Déroulement de l'exercice
Tâche 1 : Créer l'application et le Dockerfile
bash
# Activer les APIs
gcloud services enable cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
run.googleapis.com
PROJECT_ID=$(gcloud config get-value project)
REGION=europe-west9
# Créer le projet applicatif
mkdir -p monapp && cd monapp
# Application web minimaliste (Python/Flask)
cat > app.py << 'EOF'
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({
'message': 'Déployé avec Cloud Build !',
'version': os.environ.get('APP_VERSION', '1.0.0'),
'environnement': os.environ.get('ENV', 'production')
})
@app.route('/health')
def health():
return jsonify({'status': 'ok'})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port)
EOF
# Requirements
cat > requirements.txt << 'EOF'
flask==3.0.0
gunicorn==21.2.0
EOF
# Dockerfile
cat > Dockerfile << 'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENV PORT=8080
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "2", "app:app"]
EOF
# Tests simples
cat > test_app.py << 'EOF'
import pytest
from app import app
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_index(client):
response = client.get('/')
assert response.status_code == 200
data = response.get_json()
assert 'message' in data
def test_health(client):
response = client.get('/health')
assert response.status_code == 200
assert response.get_json()['status'] == 'ok'
EOFTâche 2 : Créer l'Artifact Registry et le fichier cloudbuild.yaml
bash
# Créer un repository Docker dans Artifact Registry
gcloud artifacts repositories create monapp-repo \
--repository-format=docker \
--location=$REGION \
--description="Repository pour l'exercice Cloud Build"
# L'adresse complète du repository
REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/monapp-repo"
echo "Repository : $REPO"bash
# Créer le fichier de configuration Cloud Build
cat > cloudbuild.yaml << EOF
steps:
# Étape 1 : Installer les dépendances et lancer les tests
- name: 'python:3.12-slim'
entrypoint: bash
args:
- -c
- |
pip install --no-cache-dir -r requirements.txt pytest &&
python -m pytest test_app.py -v
id: tests
# Étape 2 : Builder l'image Docker
- name: 'gcr.io/cloud-builders/docker'
args:
- build
- -t
- '${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp:latest'
- -t
- '${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp:\$BUILD_ID'
- .
id: build
waitFor: ['tests']
# Étape 3 : Pousser l'image dans Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args:
- push
- '--all-tags'
- '${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp'
id: push
waitFor: ['build']
# Étape 4 : Déployer sur Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- run
- deploy
- monapp-service
- '--image=${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp:\$BUILD_ID'
- '--region=${REGION}'
- '--platform=managed'
- '--allow-unauthenticated'
- '--set-env-vars=APP_VERSION=\$BUILD_ID,ENV=production'
id: deploy
waitFor: ['push']
# Images poussées dans Artifact Registry
images:
- '${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp:latest'
- '${REGION}-docker.pkg.dev/\$PROJECT_ID/monapp-repo/monapp:\$BUILD_ID'
options:
logging: CLOUD_LOGGING_ONLY
machineType: 'E2_HIGHCPU_8' # Build plus rapide
EOF
echo "Fichier cloudbuild.yaml créé"Tâche 3 : Lancer le build manuellement
bash
# Accorder les permissions Cloud Run au service account Cloud Build
CB_SA="${PROJECT_ID}@cloudbuild.gserviceaccount.com"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$CB_SA" \
--role="roles/run.admin"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$CB_SA" \
--role="roles/iam.serviceAccountUser"
# Lancer le build depuis le dossier courant
gcloud builds submit . --config=cloudbuild.yaml
# Suivre les logs en temps réel
BUILD_ID=$(gcloud builds list --limit=1 --format="value(id)")
gcloud builds log $BUILD_ID --streamTâche 4 : Vérifier le déploiement
bash
# Voir les builds passés
gcloud builds list --limit=5 \
--format="table(id,status,createTime,duration)"
# Récupérer l'URL Cloud Run
SERVICE_URL=$(gcloud run services describe monapp-service \
--region=$REGION \
--format="value(status.url)")
echo "URL du service : $SERVICE_URL"
# Tester l'application déployée
curl $SERVICE_URL
curl $SERVICE_URL/health
# Voir les révisions Cloud Run (historique des déploiements)
gcloud run revisions list \
--service=monapp-service \
--region=$REGION \
--format="table(name,deployTime,status.conditions[0].status)"Tâche 5 : Nettoyer
bash
cd ..
gcloud run services delete monapp-service --region=$REGION --quiet
gcloud artifacts repositories delete monapp-repo --location=$REGION --quiet
rm -rf monapp/✅ Vérification du résultat
gcloud builds listaffiche le build en étatSUCCESS- Les tests pytest passent dans les logs du build
- L'image est visible dans Artifact Registry
curl $SERVICE_URL/healthretourne{"status":"ok"}
💡 À retenir
Structure d'un cloudbuild.yaml :
yaml
steps:
- name: 'image-docker' # Image utilisée pour cette étape
entrypoint: 'commande' # Optionnel (remplace ENTRYPOINT de l'image)
args: ['arg1', 'arg2'] # Arguments passés à la commande
id: 'mon-etape' # Nom pour les dépendances
waitFor: ['autre-etape'] # Attendre cette étape avant de démarrer
options:
machineType: 'E2_HIGHCPU_8' # Type de machine pour le buildVariables built-in Cloud Build :
| Variable | Valeur |
|---|---|
$PROJECT_ID | ID du projet GCP |
$BUILD_ID | ID unique du build |
$COMMIT_SHA | SHA du commit Git (si déclenché par Git) |
$BRANCH_NAME | Nom de la branche |
✨ Solution Complète
bash
# Lancer un build depuis le dossier courant
gcloud builds submit . --config=cloudbuild.yaml
# Lancer un build depuis GitHub (après avoir connecté le dépôt)
gcloud builds triggers run mon-trigger --branch=main