ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Structure de dépôt dbt pour le déploiement sur Cloud Function

Comment restructurer un dépôt de projet dbt pour le déploiement sur Cloud Function — le pattern sous-répertoire, main.py, requirements.txt et profiles.yml avec oauth.

Planté
dbtgcpdata engineeringautomation

Déployer dbt via Cloud Functions nécessite une organisation spécifique du dépôt. Cloud Functions attend que son fichier de point d’entrée (main.py) et requirements.txt se trouvent à la racine du répertoire source. Cela entre en conflit avec la structure habituelle d’un projet dbt, où dbt_project.yml et tous les dossiers dbt résident à la racine.

La solution : imbriquer le projet dbt dans un sous-répertoire, et placer les fichiers Cloud Function à la racine du dépôt.

La structure cible

repo-root/
├── dbt_transform/
│ ├── analyses/
│ ├── models/
│ ├── seeds/
│ ├── snapshots/
│ ├── tests/
│ ├── dbt_project.yml
│ ├── packages.yml
│ └── profiles.yml
├── main.py
└── requirements.txt

Le nom dbt_transform est arbitraire — nommez-le comme il convient pour votre projet. L’essentiel est que tous les fichiers dbt résident à l’intérieur, et que main.py se trouve à la racine aux côtés de requirements.txt.

Si vous partez d’un projet dbt existant (tout à la racine), vous effectuez simplement un mkdir dbt_transform && mv de tous les fichiers dbt à l’intérieur. Rien dans le projet dbt ne change ; seul le répertoire dans lequel il réside change.

Le profiles.yml

Le profiles.yml réside dans dbt_transform/, et non dans ~/.dbt/ comme vous pourriez l’avoir en local. C’est intentionnel — Cloud Functions n’a pas accès à votre répertoire personnel. Le main.py définit explicitement DBT_PROFILES_DIR pour pointer vers le sous-répertoire.

Utilisez method: oauth pour l’authentification. Cela indique à dbt-bigquery d’utiliser les Application Default Credentials, qui se résolvent en compte de service attaché à la Cloud Function au moment de l’exécution :

dbt_project_name:
outputs:
dev:
type: bigquery
method: oauth
project: gcp_project_name
dataset: dbt
location: EU
threads: 4
job_execution_timeout_seconds: 3500
job_retries: 1
priority: interactive
target: dev

N’utilisez pas method: service-account avec un fichier de clé ici — cela nécessiterait un fichier de clé sur disque, ce qu’on ne souhaite pas dans une Cloud Function. La méthode oauth associée à un compte de service attaché est plus propre : pas de secrets dans le dépôt, pas de rotation de clés, et cela fonctionne de la même manière dans les Cloud Functions et les Cloud Run Jobs.

Notez également que la target est dev ici. C’est correct pour une Cloud Function qui s’exécute en production — le nom de la cible n’est qu’un libellé. Ce qui compte, c’est que project et dataset pointent vers les ressources de production correctes. Si vous souhaitez une cible prod plus explicite, ajoutez-la et changez le champ target: en prod.

main.py

Le point d’entrée fait trois choses : définir la variable d’environnement DBT_PROFILES_DIR, se déplacer dans le répertoire du projet dbt, et invoquer dbt comme sous-processus :

import os
import subprocess
import logging
logging.basicConfig(level=logging.INFO)
def run_dbt(request):
try:
os.environ['DBT_PROFILES_DIR'] = '/workspace/dbt_transform'
dbt_project_dir = '/workspace/dbt_transform'
os.chdir(dbt_project_dir)
logging.info(f"Current working directory: {os.getcwd()}")
logging.info(f"Files in the current directory: {os.listdir('.')}")
# Install dbt packages
logging.info("Installing dbt packages...")
subprocess.run(['dbt', 'deps'], check=True, capture_output=True, text=True)
# Run dbt
result = subprocess.run(
['dbt', 'build'],
capture_output=True,
text=True
)
return result.stdout
except subprocess.CalledProcessError as e:
logging.error(f"Command '{e.cmd}' returned non-zero exit status {e.returncode}.")
logging.error(f"stdout: {e.stdout}")
logging.error(f"stderr: {e.stderr}")
return f"Error running dbt: {e.stderr}"
except Exception as e:
logging.error(f"Error running dbt: {str(e)}")
return f"Error running dbt: {str(e)}"

Quelques points à noter :

/workspace/dbt_transform est le chemin où Cloud Functions monte votre code source. Le préfixe /workspace/ est une convention de Cloud Functions 2ème génération. N’encodez pas en dur un chemin qui fonctionne sur votre machine locale.

dbt deps s’exécute au moment de l’exécution, pas au déploiement. Chaque invocation de la fonction réinstalle vos packages dbt. Cela ajoute une latence proportionnelle au nombre de packages que vous avez. Si vous utilisez de nombreux packages et que le temps d’exécution est important, les Cloud Run Jobs avec des packages intégrés dans l’image de conteneur valent la peine d’être envisagés.

dbt build exécute vos modèles, tests, seeds et snapshots dans l’ordre des dépendances. Si vous souhaitez seulement les modèles, utilisez dbt run. Pour ajouter des sélecteurs (ex. exécuter uniquement des tags ou chemins spécifiques), étendez la liste : ['dbt', 'build', '--select', 'tag:daily'].

La gestion des erreurs distingue subprocess.CalledProcessError (dbt a retourné un code de sortie non nul) des exceptions générales. Cela facilite le triage des logs — vous savez si dbt lui-même a échoué ou si quelque chose s’est mal passé dans le wrapper Python.

requirements.txt

Gardez-le minimal :

dbt-core
dbt-bigquery

Pour une utilisation en production, épinglez des versions spécifiques. Un déploiement qui fonctionne aujourd’hui ne devrait pas casser parce que dbt-core a publié une nouvelle version avec un changement incompatible la nuit :

dbt-core==1.9.1
dbt-bigquery==1.9.0

Vérifiez les releases de dbt-bigquery pour vérifier la compatibilité entre les versions de dbt-core et dbt-bigquery. Des versions incompatibles sont une source courante d’échecs silencieux.

Pourquoi cette organisation fonctionne

Le pattern sous-répertoire est un compromis. Il ajoute un niveau d’imbrication qui n’existe pas dans un projet dbt pur, ce qui signifie que les commandes dbt exécutées en local doivent l’être depuis dbt_transform/, ou vous devez passer --project-dir dbt_transform explicitement.

C’est un petit inconvénient qu’il vaut la peine d’accepter, car l’alternative — déplacer les fichiers Cloud Function dans la racine du projet dbt — encombre l’espace de noms dbt et peut perturber les outils qui s’attendent à une structure de projet dbt standard.

Si vous migrez ultérieurement vers des Cloud Run Jobs, le sous-répertoire dbt_transform/ reste. Le Cloud Run Job ne change que comment et quand il est invoqué — le projet dbt lui-même est inchangé. Cette portabilité vaut la peine d’être conçue dès le départ.