Introduction

Pour un homelab, un service personnel ou une petite application à trafic intermittent, une infrastructure dimensionnée pour fonctionner 24h/24 est rarement justifiée. Une machine virtuelle Compute Engine ou un cluster Kubernetes (GKE) introduit immédiatement un coût fixe, ainsi que la maintenance de l’OS, du réseau et des mises à jour de sécurité, même lorsque l’application ne reçoit aucune requête.

Dans ce contexte, le scale-to-zero constitue un prérequis architectural. Cloud Run représente le compromis retenu ici : il exécute une image OCI standard, monte automatiquement en charge et peut revenir à zéro instance entre deux requêtes.

L’architecture présentée dans cet article vise trois objectifs :

  • Éliminer le coût d’une capacité de calcul allouée en permanence.
  • Déléguer à Google Cloud l’administration des serveurs hôtes, tout en conservant la maintenance de l’application et de son image conteneur.
  • Automatiser l’infrastructure et les déploiements avec Terraform et GitLab CI, sans clé de compte de service statique.

Le socle présenté privilégie donc le coût plancher et la simplicité opérationnelle. Il peut servir de base à une production de petite taille, mais une exigence forte de SLA, de WAF, de haute disponibilité SQL ou de reprise multi-région implique des composants supplémentaires et un coût fixe assumé.

En bref

  • Cloud Run convient aux charges intermittentes grâce au scale-to-zero et à la facturation à la requête.
  • Firestore Native préserve un modèle entièrement serverless lorsque les données peuvent être organisées en documents.
  • Cloud SQL reste nécessaire pour le relationnel strict ou les logiciels qui l’imposent, mais introduit un coût d’instance permanent.
  • Cloud Storage (GCS Mount) permet de monter un bucket en volume FUSE pour persister des caches applicatifs (ex: Smarty en PHP) ou des uploads de CMS sans coût de serveur de fichiers permanent (NFS/Filestore).
  • Domain Mapping est adapté aux environnements non critiques ; un Application Load Balancer est préférable pour une production nécessitant SLA, WAF ou routage avancé.
  • Workload Identity Federation permet à GitLab CI de déployer sans stocker de clé JSON pérenne.

1. Vision globale et architecture cible

L’architecture a été conçue pour dissocier strictement le plan d’exécution applicatif (runtime) du plan d’administration et de déploiement (CI/CD) :

  • Sur le plan de l’exécution : le choix s’est porté sur Cloud Run afin d’exécuter des conteneurs standardisés sans devoir provisionner de nœuds de calcul. La persistance principale est confiée à Firestore pour conserver un modèle entièrement managé sans instance résiduelle, tandis que les secrets sont isolés dans Secret Manager et injectés au démarrage.
  • Sur le plan du déploiement : GitLab CI pilote l’infrastructure via Terraform sans héberger de fichier d’identifiants (service account key json). L’authentification repose sur l’échange de jetons OIDC avec Workload Identity Federation, ce qui supprime le risque de fuite de clés pérennes dans le référentiel de code.

Le détail de ces deux plans est décrit ci-dessous.

A. Plan d’exécution applicatif (Runtime) : Le modèle Zero Trust en pratique

L’architecture runtime applique les principes stricts du Zero Trust (« Never Trust, Always Verify ») : aucune confiance implicite n’est accordée à un composant sur la simple base de son emplacement réseau.

  1. Point d’entrée sécurisé et chiffrement en transit : L’utilisateur accède à l’application exclusivement via un point d’entrée HTTPS chiffré en TLS (certificat SSL géré automatiquement).
  2. Isolation et architecture multi-conteneurs (sidecar) : toutes les instances Cloud Run sont isolées (gVisor en première génération, microVMs en seconde génération pour une compatibilité Linux étendue). L’architecture tire parti du support multi-conteneurs de Cloud Run pour embarquer, aux côtés du conteneur applicatif principal, un conteneur sidecar auxiliaire (comme le Cloud SQL Auth Proxy). Les conteneurs cohabitent dans la même instance, partagent le réseau local (localhost), le même cycle de vie et le compte de service d’exécution (sa-run), tout en isolant strictement leurs responsabilités.
  3. Contrôle d’accès piloté par l’identité (IAM-based) : les échanges entre le conteneur Cloud Run et les services managés (Firestore, Secret Manager, Cloud Storage, Cloud SQL) reposent sur l’identité du compte de service runtime (sa-run), et non sur une clé JSON ou une liste blanche d’IP. Le conteneur interroge le serveur de métadonnées local (metadata.google.internal), qui lui délivre dynamiquement des jetons éphémères (jetons d’accès OAuth 2.0 et JWT / ID tokens signés). Les SDK Google Cloud exploitent ces identifiants via Application Default Credentials (ADC) pour authentifier chaque appel d’API et garantir des communications chiffrées en TLS sans aucun secret statique dans le code.

Plan d'exécution applicatif (Runtime)

B. Plan de déploiement CI/CD (GitLab -> GCP)

Le développeur pousse son code sur un dépôt GitLab. Lors de l’exécution, le pipeline GitLab CI s’authentifie auprès de GCP sans aucune clé statique grâce à Workload Identity Federation (WIF) : GitLab émet un jeton éphémère OpenID Connect (OIDC) signé attestant de l’identité du job, que Google Cloud valide pour lui accorder des droits d’accès temporaires via impersonation de Service Account. Une fois authentifié, le pipeline utilise Cloud Build pour construire l’image conteneur, la publie sur Artifact Registry et applique le déploiement déclaratif de l’infrastructure avec Terraform ou Cloud Deploy.

Plan de déploiement CI/CD (GitLab -> GCP)

Rôle de chaque brique

Composant Rôle principal Bénéfice clé
Cloud Run Compute serverless de conteneurs stateless Scale-to-zero, aucun serveur hôte à administrer et facturation à l’usage en mode request-based.
Firestore Native Base NoSQL managée Disponibilité managée selon la localisation choisie, sans coût d’instance ; les opérations, le stockage et le réseau restent facturés.
Cloud SQL Base relationnelle (MySQL / PostgreSQL) Alternative ACID classique. Attention : ne scale pas à 0 (coût fixe permanent d’instance) et le dimensionnement/scaling CPU/RAM doit être géré et anticipé par le DevOps.
Cloud Storage (GCS Mount) Stockage objet et montage de volumes FUSE Persistance transparente de fichiers (uploads de CMS, médias) et de caches générés sur disque (ex: Smarty en PHP) sans serveur NFS dédié.
Secret Manager Gestionnaire centralisé de secrets Injection native des tokens et clés d’API au démarrage du conteneur sans fichiers en clair.
Terraform Infrastructure as Code (IaC) déclarative Reproductibilité stricte des environnements (Staging / Prod), état stocké sur Cloud Storage.
GitLab CI + WIF Pipeline d’intégration et déploiement continu Automatisation sécurisée sans aucune clé de compte de service (credentials.json) statique.

Le paradigme « Zero Server Management »

L’objectif de cette architecture est de minimiser les tâches d’exploitation bas niveau :

  1. Aucun serveur hôte ni OS d’infrastructure à administrer : finies les montées de version d’OS sur des machines virtuelles, la sécurisation des accès SSH, la gestion des utilisateurs/groupes système et la configuration réseau bas niveau (adresses IP, règles de filtrage hôte, routage local). La maintenance du système d’exploitation se limite désormais à la mise à jour de l’image de base du conteneur (base image) directement dans le pipeline GitLab CI/CD.

  2. Aucun patching de kernel hôte ni d’hyperviseur : l’infrastructure physique, la sécurité matérielle et l’isolation (gVisor en première génération ou microVM en seconde génération) sont intégralement déléguées à Google Cloud, qui applique les correctifs et la maintenance de manière transparente. Cette invisibilité repose sur le caractère strictement stateless (sans état) du compute : une instance Cloud Run est éphémère et interchangeable. Elle ne conserve aucun état, session utilisateur ni fichier persistant en mémoire ou sur disque local entre deux requêtes ; toutes les données durables sont obligatoirement externalisées vers des services de persistance dédiés (Firestore, Cloud SQL, Cloud Storage). Cloud Run peut ainsi démarrer, recycler ou arrêter des conteneurs à tout moment sans risque d’incohérence pour l’application.

  3. Disponibilité régionale managée : Cloud Run répartit les instances dans la région sélectionnée sans configuration de cluster. Une stratégie de reprise entre régions reste toutefois à concevoir si le RTO ou le RPO l’exige.

L’effort d’ingénierie se déplace ainsi vers le dépôt GitLab et le conteneur : logique métier, mises à jour des dépendances et de l’image applicative, observabilité et conformité des règles Terraform.


2. Compute et exposition : Cloud Run et Domain Mapping

Scale-to-zero et gestion des cold starts

L’avantage principal de Cloud Run réside dans sa capacité à réduire son allocation de ressources à zéro instance en l’absence de trafic. Ce mécanisme introduit toutefois une contrainte opérationnelle : la latence de démarrage à froid (cold start) lors de la première requête.

Pour limiter cet impact sans maintenir d’instance minimale facturée en continu (min-instances = 0), plusieurs leviers sont combinés :

  • Activation du CPU Startup Boost (--cpu-boost ou startup_cpu_boost = true) : alloue temporairement davantage de CPU durant l’initialisation du conteneur afin d’accélérer l’amorçage des runtimes (Go, Node.js, PHP-FPM, JVM). Ce supplément de calcul est facturé pendant la phase de démarrage : il faut donc vérifier que le gain de latence justifie son activation.

  • Probes de démarrage et de vitalité (Startup & Liveness Probes) : elles permettent à Cloud Run d’attendre la disponibilité réelle du serveur applicatif avant de lui router des requêtes utilisateurs. Dans l’exemple suivant, l’application dispose d’environ 30 secondes pour devenir prête :

startup_probe {
  http_get {
    path = "/healthz"
    port = 8080
  }
  initial_delay_seconds = 0
  period_seconds        = 2
  timeout_seconds       = 1
  failure_threshold     = 15
}
  • Gestion de la concurrence (concurrency) : par défaut, Cloud Run peut router jusqu’à 80 requêtes concurrentes sur une seule instance. Ajuster cette valeur selon la nature du serveur applicatif (ex. 80 pour un serveur Go ou Node.js asynchrone, 10 à 20 pour des architectures pré-fork/process-based comme PHP-FPM).

  • Allocation CPU : privilégier le mode CPU alloué uniquement pendant le traitement des requêtes pour les applications web classiques afin de maximiser les économies.

Exposition publique : Domain Mapping pour démarrer, Load Balancer pour la production

Concernant l’exposition publique du service, deux approches sont possibles dans l’écosystème Google Cloud pour Cloud Run :

  • Domain Mapping natif Cloud Run : il associe un nom de domaine personnalisé (auth.example.com ou photos.example.com) au service et gère automatiquement le certificat TLS, sans coût fixe additionnel. Cette fonctionnalité reste toutefois en Preview / disponibilité limitée ; Google signale des limites de latence et ne la recommande pas pour les services de production. Je la réserve donc au homelab, aux démonstrateurs et aux services non critiques.
  • Global External Application Load Balancer : c’est le choix adapté à une production nécessitant un composant GA, un certificat personnalisé, Cloud Armor, Cloud CDN, du routage par chemin ou plusieurs backends. Il introduit un coût fixe auquel s’ajoutent le traitement des données et l’egress.

La documentation Google détaille les limites actuelles du Domain Mapping Cloud Run. Le choix revient à arbitrer explicitement entre coût plancher et exigences de production.

# Domain Mapping natif Cloud Run : environnement non critique
resource "google_cloud_run_domain_mapping" "app_domain" {
  location = var.region
  name     = var.environment == "prod" ? "app.kapable.info" : "app-${var.environment}.kapable.info"

  metadata {
    namespace = var.project_id
  }

  spec {
    route_name = google_cloud_run_v2_service.app.name
  }
}
Critère Domain Mapping natif Global External Application Load Balancer
Statut Preview / disponibilité limitée, non recommandé par Google pour la production Généralement disponible
Coût fixe Pas de coût propre au mapping Coût de la règle de transfert, puis traitement des données et egress
Certificat TLS Certificat Google géré automatiquement Certificat Google géré ou certificat personnalisé
Fonctionnalités avancées Mapping simple à la racine d’un domaine Cloud CDN, Cloud Armor, routage par chemin et backends multiples
Recommandation Homelab, démonstrateur, service non critique Production avec SLA, WAF ou routage avancé

Gestion du trafic : révisions, canary releases et rollback par routage

Chaque modification de configuration ou d’image conteneur génère une révision immuable dans Cloud Run.

  • Rollback par routage : en cas d’incident en production, une commande gcloud run services update-traffic --to-revisions=REVISION_NAME=100 réoriente le trafic vers la version précédente sans reconstruire ni redéployer l’image.

  • Canary Release (Split de trafic) : possibilité d’orienter 10 % du trafic sur une nouvelle version candidate (candidate-v2) et 90 % sur la version stable, vérifiant ainsi le comportement avant bascule totale.

# Répartir 90 % sur la version stable et 10 % sur la version candidate
gcloud run services update-traffic app \
  --region=europe-west1 \
  --to-revisions=app-00001-abc=90,app-00002-def=10

3. Persistance et stockage des données : Firestore, Cloud SQL et volumes Cloud Storage (GCS Mount)

Tableau comparatif Firestore Native et Cloud SQL

Firestore Native : préserver un modèle entièrement serverless

Le choix de la base de données conditionne directement le modèle opérationnel et la trajectoire des coûts. Firestore Native est un bon candidat pour les microservices, applications web et API capables d’adopter un modèle orienté documents ou clé-valeur. Le service ne nécessite aucune instance à maintenir et adapte automatiquement sa capacité, mais les lectures, écritures, suppressions, index, stockage et flux réseau restent facturés.

L’exemple ci-dessous choisit europe-west1 : la base est donc régionale. Une localisation multi-régionale peut être retenue lorsque les exigences de disponibilité et de reprise justifient son coût.

resource "google_firestore_database" "database" {
  project     = var.project_id
  name        = "(default)"
  location_id = "europe-west1"
  type        = "FIRESTORE_NATIVE"
}

Cas d’usage Cloud SQL : gestion des contraintes relationnelles

Cloud SQL devient incontournable lorsque l’application repose sur un logiciel packagé qui exige nativement un backend relationnel — c’est le cas de nombreuses applications hébergées comme Nextcloud, Piwigo, WordPress ou Keycloak, dont le schéma de données n’est pas négociable côté exploitant. Attention toutefois aux contraintes opérationnelles : contrairement à Cloud Run et Firestore, Cloud SQL ne propose pas de scale-to-zero (l’instance tourne et facture 24h/24) et la scalabilité (CPU, RAM, réplicas de lecture) doit être supervisée et ajustée par l’équipe d’exploitation.

  • Dimensionnement initial pour le développement : les tiers partagés db-f1-micro et db-g1-small réduisent le coût d’un environnement de test ou d’un service non critique, mais ils ne sont pas couverts par le SLA Cloud SQL. Une production exigeante doit utiliser un tier dédié et, si nécessaire, une configuration haute disponibilité.

  • Auto-scaling du stockage : activer storage_auto_resize = true pour ne payer que les disques consommés tout en évitant la saturation.

  • Sauvegardes automatiques et rétention :

settings {
  tier = "db-f1-micro"
  backup_configuration {
    enabled                        = true
    start_time                     = "03:00"
    point_in_time_recovery_enabled = false # à activer sur la prod critique
  }
}

Connectivité sécurisée : Cloud SQL Auth Proxy en sidecar

Une option consiste à exécuter le Cloud SQL Auth Proxy v2 en conteneur sidecar dans la révision Cloud Run. Il gère l’autorisation IAM et chiffre la connexion sans imposer de liste d’adresses IP autorisées. Selon le langage et la topologie réseau, l’intégration Cloud SQL native ou un Cloud SQL Language Connector peut être plus simple.

# Extrait du déploiement multi-conteneurs Cloud Run
--container=app
--image=europe-west1-docker.pkg.dev/.../app:latest
--set-env-vars=DB_HOST=127.0.0.1,DB_PORT=3306,DB_USER=runsa-p
--depends-on=cloudsql-proxy

--container=cloudsql-proxy
--image=gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.25.2
--cpu=1
--memory=512Mi
--args=--auto-iam-authn,--address=127.0.0.1,--port=3306,PROJECT:REGION:INSTANCE
--startup-probe=tcpSocket.port=3306,periodSeconds=2,failureThreshold=10

Astuce d’ingénierie (Authentification IAM Cloud SQL MySQL) : Avec --auto-iam-authn, le proxy utilise le token IAM du Service Account Cloud Run pour s’authentifier. Sur Cloud SQL MySQL, l’utilisateur SQL créé est tronqué au nom court du compte (account_id, ex: runsa-p), limité à 32 caractères dans MySQL.

Le proxy ne fournit aucun pool de connexions : chaque connexion applicative correspond à une connexion Cloud SQL. Le pool doit être configuré dans l’application ou confié à un composant dédié comme PgBouncer pour PostgreSQL ou ProxySQL pour MySQL.

Stockage de fichiers et caches applicatifs : Montage Cloud Storage (GCS Mount / FUSE)

Certaines applications (CMS, plateformes web, runtimes PHP ou outils tiers) nécessitent d’accéder à un système de fichiers standard sur disque pour conserver des données persistantes, sans pour autant justifier le coût permanent d’un serveur NFS dédié ou d’une instance Google Cloud Filestore (dont le forfait de base commence à 1 To).

Cloud Run intègre nativement le support des montages de volumes Cloud Storage s’appuyant sur Cloud Storage FUSE (gcsfuse). Le service connecte de manière transparente un bucket GCS sous forme de répertoire local dans le conteneur.

Cas d’usage typiques :

  • Caches de compilation de templates (ex : Smarty en PHP, Twig) : les moteurs de templates compilent les vues sous forme de scripts PHP sur disque (templates_c). Le montage d’un bucket GCS permet de conserver ce cache entre deux créations ou redémarrages d’instances et d’éviter de recompiler les vues à chaque cold start.
  • Uploads et médias pour CMS (WordPress, Ghost, Drupal, Piwigo) : persistance directe des fichiers téléversés par les utilisateurs (dossiers uploads/ ou media/) sans devoir installer de plugin spécifique ni réécrire l’application pour utiliser un SDK objet.
  • Partage de données statiques ou assets : lecture directe de fichiers volumineux ou de modèles précalculés sans consommer la mémoire vive (RAM) du conteneur.

Déclaration Terraform (Cloud Run v2) :

resource "google_storage_bucket" "app_data" {
  name          = "${var.project_id}-app-data"
  location      = "europe-west1"
  force_destroy = false

  uniform_bucket_level_access = true
}

resource "google_cloud_run_v2_service" "app" {
  name     = "app-service"
  location = "europe-west1"

  template {
    service_account = google_service_account.sa_run.email

    containers {
      image = "europe-west1-docker.pkg.dev/${var.project_id}/app/image:latest"

      # Point de montage local dans le conteneur
      volume_mounts {
        name       = "gcs-media-cache"
        mount_path = "/var/www/html/templates_c" # ou /var/www/html/wp-content/uploads
      }
    }

    # Volume adossé au bucket Cloud Storage
    volumes {
      name = "gcs-media-cache"
      gcs {
        bucket    = google_storage_bucket.app_data.name
        read_only = false
      }
    }
  }
}

Bonnes pratiques d’exploitation : Cloud Storage FUSE émule un système de fichiers au-dessus du stockage objet. Il est idéal pour les lectures/écritures séquentielles de fichiers et la mise en cache de gabarits. En revanche, il n’est pas conçu pour les verrous concurrents intensifs (file locking POSIX complet) ou les moteurs de bases de données embarqués (tels que SQLite).


4. Sécurité et gestion des identités (IAM & WIF)

Élimination des clés statiques de compte de service avec Workload Identity Federation

L’intégration entre un outil CI/CD tiers (GitLab CI, GitHub Actions) et Google Cloud repose historiquement sur l’exportation de clés privées de comptes de service au format JSON. Cette pratique présente des risques majeurs : persistance des clés dans les variables CI, absence fréquente de rotation et risque d’exfiltration.

L’architecture élimine ces identifiants statiques grâce à Workload Identity Federation (WIF). Le mécanisme fonctionne par échange de jetons :

  1. GitLab CI génère un jeton JWT OpenID Connect (OIDC) signé attestant de l’identité du pipeline, de la branche et du projet source.
  2. Le Security Token Service (STS) de GCP valide ce jeton auprès de GitLab et délivre un jeton d’accès temporaire (durée de vie limitée à une heure).
  3. Le pipeline endosse le rôle IAM configuré pour appliquer les configurations Terraform et pousser l’image sur Artifact Registry.

Ce dispositif garantit qu’aucun secret pérenne n’est stocké en dehors de l’infrastructure Google Cloud.

# 1. Pool d'identité
resource "google_iam_workload_identity_pool" "gitlab_pool" {
  workload_identity_pool_id = "gitlab-pool-${var.environment}"
  display_name              = "GitLab Identity Pool (${var.environment})"
}

# 2. Provider OIDC avec condition restrictive
resource "google_iam_workload_identity_pool_provider" "gitlab_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.gitlab_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "gitlab-provider"
  display_name                       = "GitLab Provider"

  attribute_mapping = {
    "google.subject"          = "assertion.sub"
    "attribute.project_id"    = "assertion.project_id"
    "attribute.namespace_id"  = "assertion.namespace_id"
    "attribute.ref"           = "assertion.ref"
  }

  # Restreindre l'accès au projet GitLab attendu et aux branches/tags protégés.
  attribute_condition = "assertion.project_id == \"${var.gitlab_project_id}\" && assertion.namespace_id == \"${var.gitlab_namespace_id}\" && assertion.ref_protected == true"

  oidc {
    issuer_uri        = "https://gitlab.com"
    allowed_audiences = ["https://gitlab.com"]
  }
}

# 3. Autorisation d'impersonation du Service Account CI
resource "google_service_account_iam_member" "wif_binding" {
  service_account_id = google_service_account.gitlab_ci.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.gitlab_pool.name}/attribute.project_id/${var.gitlab_project_id}"
}

Les identifiants numériques de projet et de namespace sont préférables aux noms modifiables. Pour une production limitée à une branche ou à un tag précis, la condition peut être complétée avec assertion.ref.

Bootstrap initial

Le pool WIF, son provider et le premier compte de service CI ne peuvent pas être créés par un pipeline qui en dépend déjà. Ils doivent être initialisés une fois avec une identité d’administration distincte, puis gérés par Terraform.

Dans le pipeline .gitlab-ci.yml, l’authentification se fait dynamiquement à chaque job :

.gcp_auth:
  image: google/cloud-sdk:slim
  id_tokens:
    GCP_OIDC_TOKEN:
      aud: "https://gitlab.com"
  before_script:
    - echo "${GCP_OIDC_TOKEN}" > .ci_job_jwt
    - gcloud iam workload-identity-pools create-cred-config "${GCP_WORKLOAD_IDENTITY_PROVIDER}"
        --service-account="${GCP_SERVICE_ACCOUNT}"
        --output-file=.gcp_temp_cred.json
        --credential-source-file=.ci_job_jwt
    - export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/.gcp_temp_cred.json
    - gcloud auth login --cred-file=.gcp_temp_cred.json

Principe du moindre privilège : segmentation stricte des identités

On découple l’identité de déploiement de l’identité d’exécution. Les rôles exacts dépendent des ressources Terraform administrées ; le schéma ci-dessous illustre le périmètre applicatif et n’inclut pas l’identité de bootstrap :

Segmentation des identités IAM : SA CI/CD vs SA Runtime

Gestion des secrets : intégration native avec Secret Manager

Les secrets applicatifs résiduels (clés d’API tierces externes non évitables, tokens de webhook) ne sont jamais commités ni injectés en variables GitLab CI. Ils sont stockés dans Google Secret Manager et injectés par Cloud Run lors du déploiement. Pour une variable d’environnement, mieux vaut épingler une version numérique afin de contrôler explicitement la rotation :

gcloud run deploy my-app \
  --update-secrets=JWT_SECRET=jwt-secret-key-prod:1,SESSION_SECRET=session-secret-key-prod:1

Le Service Account runtime se voit attribuer uniquement le rôle roles/secretmanager.secretAccessor sur les secrets qui le concernent.


5. FinOps et optimisation budgétaire

Facturation à l’usage réel

Cloud Run applique une tarification granulaire :

$$\text{Coût} = (\text{vCPU-s} \times P_{\text{CPU}}) + (\text{GiB-s} \times P_{\text{RAM}}) + (\text{Nb}_{\text{req}} \times P_{\text{req}})$$

Avec la facturation request-based, les instances sont facturées lorsqu’elles démarrent, s’arrêtent ou traitent des requêtes. Le Free Tier mensuel comprend notamment 2 millions de requêtes, 180 000 vCPU-secondes et 360 000 GiB-secondes. Un service à faible trafic peut donc rester dans cette enveloppe, mais le nombre de requêtes ne suffit pas à estimer la facture : la durée moyenne, la mémoire, le CPU, l’egress et les services annexes sont déterminants.

Comparatif de la structure des coûts

Les montants varient selon la région et évoluent avec les tarifs Google Cloud. Le tableau suivant compare donc les coûts planchers et leurs principaux facteurs, plutôt que de présenter une estimation artificiellement précise.

Critère Firestore + Cloud Run Cloud SQL + Cloud Run VM Compute Engine
Capacité de calcul à vide Aucune instance Cloud Run Instance SQL allouée 24h/24 VM allouée 24h/24
Coût plancher Stockage Firestore, logs, images et DNS éventuels Compute SQL, stockage, sauvegardes, logs et réseau Compute, disque, adresse IP, sauvegardes et réseau
Variation avec le trafic Requêtes, durée CPU/RAM, opérations Firestore et egress Usage Cloud Run, auquel s’ajoute le plancher Cloud SQL Capacité provisionnée, puis éventuel redimensionnement
Charge d’exploitation Infrastructure managée ; application et image à maintenir Infrastructure managée, plus dimensionnement, connexions et fenêtres de maintenance SQL OS, runtime, réseau, sauvegardes et application

À titre indicatif, un db-f1-micro peut démarrer autour d’une dizaine d’euros par mois selon la région, hors stockage et sauvegardes. Ce tier partagé est destiné au développement et aux usages non critiques ; il ne bénéficie pas du SLA Cloud SQL. Les estimations doivent être recalculées avec le Google Cloud Pricing Calculator au moment du déploiement.

Alertes budgétaires, quotas et cloisonnement par projet GCP

Pour garantir une gouvernance financière stricte :

  1. Cloisonnement par projet GCP : utiliser au minimum un projet distinct par environnement et par frontière d’isolation significative. Cette séparation simplifie IAM, les quotas et le suivi des dépenses sans multiplier inutilement les projets.

  2. Budgets et alertes : configurer des seuils par projet (50 %, 80 %, 100 %, 120 %) avec notification par email ou webhook. Une alerte signale une dérive mais ne bloque pas la consommation ; lorsqu’ils sont disponibles pour le compte, les spend caps permettent d’imposer une coupure au prix d’une indisponibilité du service.

  3. Plafond d’instances Cloud Run : définir max-instances à partir des tests de charge, du budget et de la capacité des dépendances, notamment du nombre maximal de connexions SQL. Cette limite constitue une protection budgétaire best effort et peut être brièvement dépassée lors d’un pic ; elle ne remplace pas Cloud Armor ou un mécanisme de limitation de débit contre les attaques applicatives.


6. Déploiement continu et Infrastructure as Code (DevOps)

Structuration des environnements Terraform et verrouillage du State

Le code d’infrastructure doit être parfaitement isolé entre les environnements avec un backend distant Google Cloud Storage (GCS) bénéficiant du verrouillage d’état automatique. Pour éviter la duplication de code tout en garantissant une isolation stricte des états, on laisse le bloc backend non configuré dans backend.tf (backend "gcs" {}) et on injecte dynamiquement la configuration du state et des variables selon l’environnement ciblé :

terraform/
├── backend.tf                # Déclaration du backend GCS partiel : backend "gcs" {}
├── provider.tf               # Provider Google / Google-Beta
├── services.tf               # Activation des APIs nécessaires
├── wif.tf                    # Workload Identity Federation & SA CI
├── firestore.tf              # Base de données Firestore
├── cloudsql.tf               # Instance et users Cloud SQL
├── piwigo.tf / app.tf        # Cloud Run, Domain Mapping & IAM
├── variables.tf              # Déclaration des types et variables
├── backend-config/
│   ├── staging.tfvars        # bucket = "tf-state-staging-xxxx", prefix = "app"
│   └── prod.tfvars           # bucket = "tf-state-prod-xxxx", prefix = "app"
└── tfvars/
    ├── staging.tfvars        # project_id = "proj-staging", environment = "staging", cpu = 1, memory = "512Mi"
    └── prod.tfvars           # project_id = "proj-prod", environment = "prod", cpu = 2, memory = "1Gi"

Cette approche permet d’émuler la logique de stacks ou d’environnements multiples sur une même base de code, avec une séparation physique étanche des fichiers d’état (state files) :

# 1. Initialiser le backend GCS pour l'environnement cible (ex: staging)
terraform init -reconfigure -backend-config=backend-config/staging.tfvars

# 2. Générer le plan d'exécution avec les variables spécifiques
terraform plan -var-file=tfvars/staging.tfvars -out=tfplan-staging

# 3. Appliquer les changements de manière déterministe
terraform apply tfplan-staging

Dans le pipeline CI/CD, cette dissociation permet d’exécuter exactement les mêmes manifests Terraform d’un environnement à l’autre en changeant uniquement les arguments -backend-config et -var-file passés au job.

Pipeline GitLab CI : validation continue et promotion d’images

Le cycle de livraison implémente les bonnes pratiques DevOps :

  1. Validation & Plan sur les Merge Requests (Lint, SAST, terraform plan).

  2. Build unique de conteneur en Nonprod / Staging : l’image Docker est compilée une seule fois sur Cloud Build et stockée sur Google Artifact Registry.

  3. Déploiement en Production par promotion d’image : la production réutilise le même condensé d’image SHA256 validé en staging, garantissant une stricte reproductibilité.

stages:
  - validate
  - build
  - deploy-staging
  - deploy-prod

variables:
  IMAGE_URI: "${REGION}-docker.pkg.dev/${STAGING_PROJECT_ID}/app-repo/app"

build-image:
  extends: .gcp_auth
  stage: build
  script:
    - gcloud builds submit --project="${STAGING_PROJECT_ID}" --tag="${IMAGE_URI}:${CI_COMMIT_SHA}" .
    - DIGEST="$(gcloud artifacts docker images describe "${IMAGE_URI}:${CI_COMMIT_SHA}" --format='value(image_summary.digest)')"
    - printf 'IMAGE_REF=%s@%s\n' "${IMAGE_URI}" "${DIGEST}" > image.env
  artifacts:
    reports:
      dotenv: image.env
  rules:
    - if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"'

# Déploiement Staging automatique sur push master/main
deploy-staging:
  extends: .gcp_auth
  stage: deploy-staging
  needs:
    - job: build-image
      artifacts: true
  script:
    - |
      gcloud run deploy my-app-staging \
        --image="${IMAGE_REF}" \
        --project="${STAGING_PROJECT_ID}" \
        --region="${REGION}" \
        --service-account=sa-run-staging@${STAGING_PROJECT_ID}.iam.gserviceaccount.com \
        --cpu-boost
  rules:
    - if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"'

# Déploiement Production manuel sur Tag de version
deploy-prod:
  extends: .gcp_auth
  stage: deploy-prod
  script:
    - |
      DIGEST="$(gcloud artifacts docker images describe "${IMAGE_URI}:${CI_COMMIT_SHA}" --format='value(image_summary.digest)')"
      IMAGE_REF="${IMAGE_URI}@${DIGEST}"
      gcloud run deploy my-app-prod \
        --image="${IMAGE_REF}" \
        --project="${PROD_PROJECT_ID}" \
        --region="${REGION}" \
        --service-account=sa-run-prod@${PROD_PROJECT_ID}.iam.gserviceaccount.com \
        --cpu-boost
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+/'
      when: manual

Le job de production résout le tag associé au commit puis déploie son digest immuable. Il échoue si cette image n’a pas déjà été construite et validée en staging. Pour une image stockée dans un autre projet, le service agent Cloud Run de production doit disposer de roles/artifactregistry.reader sur le dépôt source ; les tags peuvent également être rendus immuables dans Artifact Registry.

Gestion du cycle de vie des conteneurs sur Artifact Registry

Pour éviter l’accumulation indéfinie de gigaoctets d’anciennes couches Docker, configurez des politiques de nettoyage (Cleanup Policies) déclarées directement dans Terraform :

resource "google_artifact_registry_repository" "docker_repo" {
  location      = "europe-west1"
  repository_id = "app-repo"
  format        = "DOCKER"

  # 1. Garder les 10 versions les plus récentes pour permettre les rollbacks
  cleanup_policies {
    id     = "keep-recent-versions"
    action = "KEEP"
    most_recent_versions {
      keep_count = 10
    }
  }

  # 2. Supprimer les images sans tag (dangling) après 24h
  cleanup_policies {
    id     = "delete-untagged"
    action = "DELETE"
    condition {
      tag_state  = "UNTAGGED"
      older_than = "86400s"
    }
  }
}

7. Exploitation, observabilité et limites de la stack

Métriques clés et centralisation des logs

L’architecture serverless réduit la plomberie nécessaire à l’observabilité, sans remplacer l’instrumentation applicative :

  • Cloud Logging : capture automatique de stdout et stderr de tous les conteneurs au format JSON structuré avec enrichissement automatique du contexte d’exécution (ID de révision, timestamp précis, latence de la requête HTTP).

  • OpenTelemetry & Cloud Trace : possibilité d’exporter les traces OTLP vers GitLab Observability ou Cloud Trace pour mesurer précisément les goulets d’étranglement (appels Firestore, requêtes SQL, latence inter-services).

Définition d’alertes simples et pragmatiques

Dans Cloud Monitoring, trois métriques constituent un point de départ pragmatique. Les seuils ci-dessous sont des exemples à calibrer avec les objectifs de service et le trafic réel :

  1. Taux d’erreurs HTTP 5xx : alerte déclenchée si le ratio (requêtes 5xx / requêtes totales) dépasse 1 % sur une fenêtre glissante de 5 minutes.

  2. Latence 95e percentile (p95) : alerte si le temps de réponse p95 dépasse 2 secondes.

  3. Saturation des conteneurs : alerte si l’utilisation de la mémoire conteneur franchit 85 % du quota alloué.

Limites et compromis à anticiper

Cette stack présente plusieurs contraintes techniques à intégrer dès la conception :

Contrainte Limite Cloud Run Solution / Alternative
Timeout de requête Maximum 60 minutes (défaut : 5 min) Pour des tâches batchs asynchrones de plusieurs heures, déléguer vers Cloud Run Jobs ou Cloud Workflows.
Protocoles non-HTTP/gRPC Pas de trafic TCP/UDP arbitraire entrant Utiliser des WebSockets (supportés nativement sur Cloud Run) ou se tourner vers Compute Engine / GKE pour les protocoles custom.
Système de fichiers persistant local Le système de fichiers racine est éphémère et en RAM Utiliser des Volumes Cloud Storage FUSE montés directement sur Cloud Run (disponible nativement) ou stocker les médias sur GCS.
Taille maximale des requêtes HTTP (Upload) Plafond de 32 Mo par requête HTTP/1 (payload body) Générer des Signed URLs Cloud Storage (GCS) pour que le navigateur uploade directement les gros fichiers sur GCS sans saturer le conteneur.
Connexions SQL concurrentes Risque d’épuisement lors du scale-out ; le Cloud SQL Auth Proxy ne fournit pas de pooling Limiter le pool applicatif et, si nécessaire, ajouter PgBouncer pour PostgreSQL ou ProxySQL pour MySQL.

Conclusion

Le choix d’architecture peut se résumer en trois décisions. Cloud Run avec une facturation à la requête convient lorsque l’absence de trafic doit supprimer le coût de calcul. Firestore Native conserve ce modèle serverless si l’application accepte une persistance documentaire ; Cloud SQL reste le bon choix lorsqu’un modèle relationnel ou un logiciel existant l’impose, au prix d’un coût fixe. Enfin, Domain Mapping peut suffire pour un homelab ou un service non critique, tandis qu’un Application Load Balancer devient nécessaire lorsque les exigences de production — SLA, WAF, CDN ou routage avancé — priment sur le coût plancher.

Ce modèle a fait ses preuves sur le terrain sans la moindre difficulté : des applications open source majeures (WordPress, Nextcloud, Piwigo, Drupal) tout comme des développements modernes d’agents et de microservices IA (pipelines RAG, APIs d’inférence) tournent parfaitement sur cette architecture. Elles profitent d’un environnement nativement sécurisé, isolé et adossé à une stack OPS cohérente qui réduit la charge d’exploitation au strict minimum.

Terraform rend ces arbitrages reproductibles et GitLab CI, associé à Workload Identity Federation, permet de les appliquer sans clé statique. Le résultat n’est pas une architecture universelle, mais un socle explicite dont chaque coût et chaque compromis peuvent être adaptés au niveau de service réellement attendu.

Références officielles