Introduction
AWS Glue est le service serverless d'Amazon pour les ETL (Extract, Transform, Load) à grande échelle, idéal pour traiter des téraoctets de données structurées ou non sans gérer d'infrastructure. En 2026, avec l'essor de l'IA générative et des data lakes unifiés, Glue excelle dans l'orchestration de workflows complexes via Glue Studio, crawlers automatisés et jobs PySpark scalables.
Ce tutoriel avancé vous guide pour déployer un pipeline complet : ingestion depuis S3, catalogage automatique via crawler, transformation PySpark (nettoyage, agrégations, déduplication), et orchestration avec triggers conditionnels. Nous utilisons AWS CDK en Python pour une infrastructure as code (IaC) reproductible, surpassant les consoles manuelles.
Pourquoi c'est crucial ? Les data engineers pros bookmarkent ce setup car il intègre Delta Lake pour ACID, monitoring CloudWatch, et scaling auto jusqu'à 1000 DPU. À la fin, vous aurez un workflow production-ready traitant 1 To/jour. Temps estimé : 45 min de déploiement.
Prérequis
- Compte AWS avec droits administrateur (ou IAM dédié GlueFullAccess, S3FullAccess, CloudWatchLogsFullAccess).
- Python 3.10+ installé.
- AWS CLI v2 configuré (
aws configure). - AWS CDK v2 :
pip install aws-cdk-lib constructs - Node.js 18+ pour CDK synth (optionnel, CDK Python standalone).
- Connaissances avancées : PySpark, Boto3, Data Catalog.
Bootstrap CDK et init projet
mkdir glue-etl-pipeline && cd glue-etl-pipeline
pip install aws-cdk-lib constructs
cdk init app --language python
source .venv/bin/activate
pip install -r requirements.txt
cdk bootstrap aws://$(aws sts get-caller-identity --query Account --output text)/$(aws ec2 describe-regions --query 'Regions[0].RegionName' --output text)Ce script initialise un projet CDK Python, installe les libs nécessaires et bootstrappe l'environnement CDK dans votre compte AWS principal. Il crée un bucket de staging S3 pour les assets CDK. Évitez les pièges : activez toujours le venv Python et vérifiez la région par défaut (us-east-1 recommandée pour Glue).
Préparation des données sources
Avant de plonger dans Glue, préparez un bucket S3 avec données d'exemple : un CSV sale (duplicatas, nulls) simulant des logs IoT. Téléchargez un dataset public comme NYC Taxi ou générez-en un.
Structure : s3://mon-bucket-glue/input/nyc_taxi.csv. Cela permet de tester le crawler qui inférera le schéma (string, double, timestamp).
Stack CDK : Bucket S3 et IAM Role
from aws_cdk import Stack, aws_s3 as s3, aws_iam as iam, aws_glue as glue, aws_logs as logs, CfnOutput
from aws_cdk import App, Stack
from constructs import Construct
class GluePipelineStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# Bucket S3 pour input/output
self.input_bucket = s3.Bucket(self, "GlueInputBucket",
bucket_name=f"glue-demo-input-{self.account}-{self.region}",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
versioned=True,
removal_policy=RemovalPolicy.DESTROY
)
self.output_bucket = s3.Bucket(self, "GlueOutputBucket",
bucket_name=f"glue-demo-output-{self.account}-{self.region}",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
removal_policy=RemovalPolicy.DESTROY
)
CfnOutput(self, "InputBucketName", value=self.input_bucket.bucket_name)
CfnOutput(self, "OutputBucketName", value=self.output_bucket.bucket_name)
# IAM Role pour Glue
self.glue_role = iam.Role(self, "GlueETLRole",
assumed_by=iam.ServicePrincipal("glue.amazonaws.com"),
managed_policies=[
iam.ServicePrincipal("glue.amazonaws.com").add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSGlueServiceRole")),
]
)
self.glue_role.add_to_policy(iam.PolicyStatement(
actions=["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
resources=[self.input_bucket.bucket_arn + "/*", self.output_bucket.bucket_arn + "/*"]
))
self.glue_role.add_to_policy(iam.PolicyStatement(
actions=["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
resources=["*"]
))
CfnOutput(self, "GlueRoleArn", value=self.glue_role.role_arn)
app = App()
GluePipelineStack(app, "GluePipelineStack")
app.synth()Ce stack CDK crée deux buckets S3 versionnés (input/output) et un rôle IAM custom pour Glue avec accès S3/CloudWatch. Les outputs facilitent les références suivantes. Piège courant : Oubliez pas removal_policy=DESTROY pour clean lors des tests ; utilisez des noms uniques avec account/region pour éviter conflits globaux.
Upload données et script PySpark
#!/bin/bash
BUCKET_INPUT=$(aws cloudformation describe-stacks --stack-name GluePipelineStack --query 'Stacks[0].Outputs[?OutputKey==`InputBucketName`].OutputValue' --output text)
aws s3 cp nyc_taxi_sample.csv s3://$BUCKET_INPUT/input/ --recursive
cat > glue_etl_job.py << 'EOF'
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
args = getResolvedOptions(sys.argv, ['JOB_NAME'])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
# Lire CSV brut
ds = glueContext.create_dynamic_frame.from_catalog(database="glue_demo_db", table_name="nyc_taxi_raw")
# Transformer : Convert to DF, clean nulls/duplicates, aggregate
spark_df = ds.toDF()
spark_df_clean = spark_df.dropDuplicates(["pickup_datetime", "pickup_longitude"]).na.drop(subset=["total_amount"])
spark_df_agg = spark_df_clean.groupBy("pickup_datetime").agg({"total_amount": "sum", "passenger_count": "avg"}).withColumnRenamed("sum(total_amount)", "total_revenue").withColumnRenamed("avg(passenger_count)", "avg_passengers")
# Écrire Parquet partitionné
dynamic_frame_agg = DynamicFrame.fromDF(spark_df_agg.coalesce(1), glueContext, "agg_df")
glueContext.write_dynamic_frame.from_options(dynamic_frame_agg,
connection_type="s3",
connection_options={"path": "s3://OUTPUT_BUCKET/output/parquet/"},
format="parquet")
job.commit()
EOF
aws s3 cp glue_etl_job.py s3://$BUCKET_INPUT/scripts/Ce script bash upload les données CSV dans le bucket CDK et crée un script PySpark Glue complet : lit du Data Catalog, nettoie (duplicatas, nulls), agrège, écrit Parquet partitionné. Remplacez OUTPUT_BUCKET par référence réelle. Attention : coalesce(1) pour petits tests ; en prod, utilisez repartition(100) pour parallelism.
Déploiement du Crawler Glue
Les crawlers Glue automatisent la découverte de schémas dans S3 et peuplent le Data Catalog (Hive Metastore compatible). Pour advanced, configurez des classifiers custom (JSON/Avro) et scheduling. Ici, via CDK, on cible le dossier input pour inférer le schéma NYC Taxi.
Stack CDK : Crawler et base DB
from aws_cdk import Stack, aws_glue as glue, CfnOutput
from aws_cdk import RemovalPolicy
class CrawlerStack(Stack):
def __init__(self, scope: Construct, construct_id: str, input_bucket_name: str, glue_role_arn: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# Database Glue
database = glue.CfnDatabase(self, "GlueDemoDB",
catalog_id=self.account,
database_input=glue.CfnDatabase.DatabaseInputProperty(
name="glue_demo_db",
description="DB pour NYC Taxi ETL"
)
)
# Crawler
crawler = glue.CfnCrawler(self, "NYCTaxiCrawler",
role=glue_role_arn,
database_name="glue_demo_db",
description="Crawler pour input S3 NYC Taxi",
targets=glue.CfnCrawler.TargetsProperty(
s3_targets=[glue.CfnCrawler.S3TargetProperty(
path=f"s3://{input_bucket_name}/input/"
)]
),
schedule=glue.CfnCrawler.ScheduleProperty(schedule_expression="rate(1 day)"),
configuration="{\"Version\":1.0,\"CrawlerOutput\":{\"Partitions\":{\"AddOrUpdateBehavior\":\"InheritFromTable\"},\"Tables\":{\"AddOrUpdateBehavior\":\"MergeNewColumns\"}}}",
recrawl_policy=glue.CfnCrawler.RecrawlPolicyProperty(
recrawl_behavior="CRAWL_EVENT_MODE"
)
)
crawler.add_depends_on(database)
CfnOutput(self, "CrawlerName", value=crawler.ref)
# Intégrer dans app
app = App()
# Assume props from previous stack via SSM or manual
CrawlerStack(app, "CrawlerStack", input_bucket_name="glue-demo-input-XXXX-us-east-1", glue_role_arn="arn:aws:iam::XXXX:role/GlueETLRole")
app.synth()Ce stack ajoute une base Data Catalog et un crawler S3 programmé quotidiennement, avec config JSON pour merge schémas. Dépendance sur DB assurée. Piège : Utilisez CRAWL_EVENT_MODE pour triggers ; remplacez placeholders par outputs CDK réels via Fn.import_value en prod.
Stack CDK : Job PySpark ETL
from aws_cdk import Stack, aws_glue as glue, aws_iam as iam, CfnOutput
class JobStack(Stack):
def __init__(self, scope: Construct, construct_id: str, input_bucket_name: str, output_bucket_name: str, glue_role_arn: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
job = glue.CfnJob(self, "NYCTaxiETLJob",
role_arn=glue_role_arn,
command=glue.CfnJob.CommandProperty(
name="glueetl",
script_location=f"s3://{input_bucket_name}/scripts/glue_etl_job.py",
python_version="3"
),
default_arguments={
"--TempDir": f"s3://{input_bucket_name}/temp/",
"--OUTPUT_BUCKET": output_bucket_name,
"--job-bookmark-option": "job-bookmark-enable",
"--enable-job-insights": "true",
"--datalake-defaults": "true"
},
glue_version="4.0", # PySpark 3.3+ en 2026
number_of_workers=10,
worker_type="G.2X",
max_capacity=10.0,
timeout=60,
max_retries=3
)
CfnOutput(self, "JobName", value=job.ref)
app = App()
JobStack(app, "JobStack", "glue-demo-input-XXXX-us-east-1", "glue-demo-output-XXXX-us-east-1", "arn:aws:iam::XXXX:role/GlueETLRole")
app.synth()Définit un job Glue 4.0 (PySpark 3.4+) avec 10 workers G.2X, bookmarks pour reprise, Job Insights pour perf. Args passés au script. Attention : glue_version=4.0 pour 2026 ; scalez max_capacity >100 DPU en prod, testez timeout sur datasets larges.
Orchestration avec Workflow et Triggers
Pour un pipeline avancé, liez crawler -> job via workflow : trigger on-crawler-success lance le job. Ajoutez conditionnel (e.g., >100 partitions nouvelles).
Stack CDK : Workflow et Triggers
from aws_cdk import Stack, aws_glue as glue, CfnOutput
class WorkflowStack(Stack):
def __init__(self, scope: Construct, construct_id: str, crawler_name: str, job_name: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
workflow = glue.CfnWorkflow(self, "NYCTaxiWorkflow",
description="Workflow ETL NYC Taxi: Crawler -> Job",
default_run_properties={"conf": "spark.sql.adaptive.enabled=true"}
)
trigger = glue.CfnTrigger(self, "CrawlerSuccessTrigger",
name="trigger-crawler-to-job",
workflow_name=workflow.ref,
type="CONDITIONAL",
start_on=glue.CfnTrigger.StartOnProperty(
crawler_names=[crawler_name]
),
actions=[glue.CfnTrigger.ActionProperty(job_name=job_name)],
predicate=glue.CfnTrigger.PredicateProperty(
conditions=[glue.CfnTrigger.ConditionProperty(
logical_operator="EQUALS",
job_name="",
state="SUCCEEDED",
crawler_name=crawler_name
)]
)
)
CfnOutput(self, "WorkflowName", value=workflow.ref)
app = App()
WorkflowStack(app, "WorkflowStack", "NYCTaxiCrawler", "NYCTaxiETLJob")
app.synth()Crée un workflow avec trigger conditionnel : job démarre seulement si crawler réussit. Conf Spark pour optimisations AQE. Piège : type=CONDITIONAL requiert predicate ; monitor via aws glue get-workflow-run en CLI.
Déploiement complet CDK
cd glue_pipeline
cdk deploy GluePipelineStack CrawlerStack JobStack WorkflowStack --require-approval never
aws glue start-workflow-run --name $(aws cloudformation describe-stacks --stack-name WorkflowStack --query 'Stacks[0].Outputs[?OutputKey==`WorkflowName`].OutputValue' --output text)
aws glue get-workflow-run --name WORKFLOW_NAME --run-id $(aws glue start-workflow-run --name WORKFLOW_NAME --query 'RunId' --output text) --query 'Run.Parts[0].ExecutionStatus'Déploie tous les stacks en séquence et lance le workflow manuellement. --require-approval never pour CI/CD. Vérifiez status ; en prod, intégrez à CodePipeline avec approbations manuelles.
Bonnes pratiques
- Scaling dynamique : Utilisez
max_capacity=0avec DPUs auto (Glue 4.0+) pour coûts optimisés. - Delta Lake : Ajoutez
spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtensionpour transactions ACID sur output. - Monitoring : Activez Job Insights et CloudWatch metrics (DPU-seconds, erreurs OOM).
- Sécurité : Lake Formation pour gouvernance Data Catalog ; chiffrez S3 avec KMS.
- CI/CD : CDK en GitHub Actions, tests avec LocalStack pour PySpark unitaires.
Erreurs courantes à éviter
- Script PySpark sans
job.commit(): Job ne termine pas proprement, facturation infinie. - Crawler sans recrawl_policy : Schémas obsolètes sur data drift ; forcez
CRAWL_EVENT_MODE. - IAM trop permissif : Limitez à
s3://bucket/*; audit avec Access Analyzer. - Pas de bookmarks : Reprocessing total sur runs incrémentaux ; activez
--job-bookmark-option=job-bookmark-enable.
Pour aller plus loin
Approfondissez avec Glue Streaming pour Kafka/S3 realtime, ou Ray on Glue pour ML pipelines. Consultez la doc AWS Glue 2026.
Découvrez nos formations Learni sur AWS Data : certification Data Engineer Pro, ateliers CDK Glue. Rejoignez la communauté Discord pour Q&A live.