Dans l’article précédent 5a, nous avons vu à quelle vitesse de grands déploiements Terraform atteignent les limites d’API, par exemple lorsque des tests DR créent des centaines de ressources en parallèle et que des erreurs 429 déclenchent en cascade des tentatives de réexécution. Cette suite reprend le fil et montre comment gérer consciemment ces limites avec l’API Gateway d’Oracle Cloud Infrastructure et d’Amazon API Gateway, comment les observer proprement et comment les rendre robustes à l’exploitation via « Policy as Code ».
API Gateway : l’ultima ratio ?
Les API Gateways aident à rendre les limites d’API prévisibles. Bien utilisées, elles regroupent les appels d’API, appliquent des quotas et du throttling, fournissent des données d’observabilité cohérentes et établissent un point central pour l’exploitation et la gouvernance.
Ce qui nous importe surtout : une Gateway ne fait pas que déplacer le problème des Rate Limits, elle permet son pilotage actif par équipe, par déploiement et par route.
Avec Oracle Cloud Infrastructure, vous définissez des garde-fous techniques au moyen de Usage Plans et d’Entitlements. Ceux-ci s’appliquent directement aux déploiements d’API Gateway, par exemple avec un taux maximal par seconde et des quotas par minute ou par mois. Pour l’application et la transparence, le service fournit des métriques spécifiques comme HttpResponses avec des dimensions deploymentId et httpStatusCode, qui se prêtent bien à la mise en place d’alarmes (Oracle Documentation).
Les catégories de journaux de service access et execution sont les canaux prévus par le service ; ils sont directement associés au déploiement d’API et sont préférables à l’archivage de journaux dans des buckets legacy (Oracle Documentation).
Voici un exemple pour OCI (un exemple pour AWS suit plus bas) :
# Terraform >= 1.10, OCI Provider 7.14.0 terraform { required_version = ">= 1.10" required_providers { oci = { source = "oracle/oci", version >= "7.14.0" } } } provider "oci" { region = var.region } variable "region" { type = string description = "OCI region, e.g., eu-frankfurt-1" validation { condition = can(regex("^[a-z]+-[a-z0-9]+-[0-9]+$", var.region)) error_message = "Region must match a pattern like 'eu-frankfurt-1'." } } variable "compartment_id" { type = string description = "Compartment OCID used for gateway, logs, and alarms" } # Optional: Many organizations manage the API deployment separately. # We intentionally reference it via a variable to keep the example focused. variable "api_deployment_id" { type = string description = "OCID of the API Gateway deployment" validation { condition = can(regex("^ocid1\\..+", var.api_deployment_id)) error_message = "api_deployment_id must be a valid OCID." } } # Enable service logs for 'access' and 'execution' resource "oci_logging_log_group" "apigw" { compartment_id = var.compartment_id display_name = "apigw-logs" } resource "oci_logging_log" "apigw_access" { log_group_id = oci_logging_log_group.apigw.id display_name = "apigateway-access" log_type = "SERVICE" is_enabled = true configuration { source { category = "access" resource = var.api_deployment_id service = "apigateway" } } } resource "oci_logging_log" "apigw_execution" { log_group_id = oci_logging_log_group.apigw.id display_name = "apigateway-execution" log_type = "SERVICE" is_enabled = true configuration { source { category = "execution" resource = var.api_deployment_id service = "apigateway" } } } # Usage plan with rate limit & minute quota resource "oci_apigateway_usage_plan" "team_plan" { compartment_id = var.compartment_id display_name = "team-standard-plan" entitlements { name = "default" description = "Standard quota for CI runs" rate_limit { unit = "SECOND" value = 50 } quota { unit = "MINUTE" value = 2000 reset_policy = "CALENDAR" operation_on_breach = "REJECT" } targets { deployment_id = var.api_deployment_id } } lifecycle { prevent_destroy = true } }
Avec Amazon API Gateway, vous combinez trois leviers : le throttling au niveau des stages et des méthodes, les Usage Plans avec API Keys, ainsi que des règles basées sur le taux dans AWS WAF pour les agrégations par IP. Les métriques CloudWatch 4XXError et 5XXError constituent un système d’alerte précoce robuste au niveau des stages.
Important : AWS WAFv2 peut aujourd’hui uniquement être associé aux stages REST-API, et non aux HTTP APIs. (AWS Documentation, Terraform Registry)
# Amazon API Gateway (REST) – stage throttling, usage plan, WAF terraform { required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws", version = ">= 5.0" } } } provider "aws" { region = var.aws_region } data "aws_region" "current" {} resource "aws_api_gateway_rest_api" "tf_api" { name = "terraform-at-scale" } resource "aws_api_gateway_resource" "status" { rest_api_id = aws_api_gateway_rest_api.tf_api.id parent_id = aws_api_gateway_rest_api.tf_api.root_resource_id path_part = "status" } resource "aws_api_gateway_method" "get_status" { rest_api_id = aws_api_gateway_rest_api.tf_api.id resource_id = aws_api_gateway_resource.status.id http_method = "GET" authorization = "NONE" } resource "aws_api_gateway_integration" "get_status_mock" { rest_api_id = aws_api_gateway_rest_api.tf_api.id resource_id = aws_api_gateway_resource.status.id http_method = aws_api_gateway_method.get_status.http_method type = "MOCK" } resource "aws_api_gateway_deployment" "this" { rest_api_id = aws_api_gateway_rest_api.tf_api.id depends_on = [aws_api_gateway_integration.get_status_mock] } resource "aws_api_gateway_stage" "prod" { rest_api_id = aws_api_gateway_rest_api.tf_api.id deployment_id = aws_api_gateway_deployment.this.id stage_name = "prod" method_settings { resource_path = "/*" http_method = "*" metrics_enabled = true logging_level = "INFO" data_trace_enabled = false throttling_burst_limit = 100 throttling_rate_limit = 50 } } resource "aws_api_gateway_usage_plan" "plan" { name = "team-standard-plan" api_stages { api_id = aws_api_gateway_rest_api.tf_api.id stage = aws_api_gateway_stage.prod.stage_name } throttle_settings { burst_limit = 100 rate_limit = 50 } quota_settings { limit = 2000 period = "DAY" } } resource "aws_api_gateway_api_key" "ci_key" { name = "ci-runs" enabled = true # If 'value' is omitted, the service generates a secure key automatically. } resource "aws_api_gateway_usage_plan_key" "ci_key_bind" { key_id = aws_api_gateway_api_key.ci_key.id key_type = "API_KEY" usage_plan_id = aws_api_gateway_usage_plan.plan.id } # WAFv2 rate-based rule (REGIONAL) – only for REST API stages, not HTTP APIs resource "aws_wafv2_web_acl" "apigw_waf" { name = "apigw-waf" description = "Rate limit per source IP" scope = "REGIONAL" default_action { allow {} } rule { name = "rate-limit" priority = 1 action { block {} } statement { rate_based_statement { limit = 500 aggregate_key_type = "IP" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "apigw-waf" sampled_requests_enabled = true } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "apigw-waf" sampled_requests_enabled = true } } resource "aws_wafv2_web_acl_association" "stage_assoc" { resource_arn = "arn:aws:apigateway:${data.aws_region.current.name}::/restapis/${aws_api_gateway_rest_api.tf_api.id}/stages/${aws_api_gateway_stage.prod.stage_name}" web_acl_arn = aws_wafv2_web_acl.apigw_waf.arn }
Les throttles au niveau des stages, les Usage Plans et l’association WAF constituent les piliers côté AWS. CloudWatch propose entre autres les métriques 4XXError avec les dimensions ApiName et Stage, ce qui simplifie le déclenchement d’alarmes par stage (AWS Documentation).
Tests et validation avec Terraform 1.10+
Pour des filets de sécurité rapides et reproductibles, il est recommandé d’utiliser le framework de test natif de Terraform. Les Mock Providers encapsulent les dépendances externes, tandis que les assertions vérifient des règles spécifiques au projet comme la taille maximale des batches ou le comportement en cas de limites trop basses.
Astuce pro : utilisez volontairement des tests concis et parlants qui renforcent vos modules contre les erreurs de configuration. (HashiCorp Developer)
# tests/api_limits.tftest.hcl test { # optional name and timeouts can be added here } variables { # Global default variables for all runs in this test file max_batch_size = 50 } # Example: The plan must never try to create more than 50 new resources run "enforce_small_batches" { command = plan assert { condition = length([for rc in run.plan.resource_changes : rc if contains(rc.change.actions, "create")]) <= var.max_batch_size error_message = "Too many new resources in a single run – split the deployment into smaller batches." } } # Example: We expect a failure of a named precondition # (Preconditions are defined in your modules/resources) run "expect_precondition_failure" { command = plan expect_failures = [ precondition.api_limits_reasonable ] }
Conseils issus de la pratique :
- Les assertions doivent tenir sur une seule ligne,
- expect_failures se réfère à des Preconditions nommées, et non à des erreurs de type générales,
- Les ressources éphémères sont, à ce jour (Terraform 1.12.0), surtout adaptées aux tokens temporaires et aux requêtes ponctuelles, mais pas comme substitut universel des Mocks.
Monitoring + Alerting
L’observabilité est l’épine dorsale opérationnelle de votre stratégie de gestion des limites d’API.
Sur OCI, le moyen le plus fiable consiste à travailler directement avec les métriques de service de l’API Gateway en combinaison avec les alarmes de la plateforme Monitoring. Les dimensions deploymentId et httpStatusCode permettent un filtrage précis des réponses 429. La syntaxe en MQL est la suivante, veillez à employer les noms de dimensions corrects : (Oracle Documentation)
# OCI: Alarm on sustained HTTP 429 responses at deployment level resource "oci_ons_notification_topic" "ops" { compartment_id = var.compartment_id name = "ops-alerts" } resource "oci_ons_subscription" "ops_mail" { compartment_id = var.compartment_id topic_id = oci_ons_notification_topic.ops.id protocol = "EMAIL" endpoint = var.alert_email } resource "oci_monitoring_alarm" "apigw_429" { compartment_id = var.compartment_id metric_compartment_id = var.compartment_id display_name = "APIGW 429 bursts" is_enabled = true severity = "CRITICAL" destinations = [oci_ons_notification_topic.ops.id] message_format = "ONS_OPTIMIZED" pending_duration = "PT1M" # 1 minute resolution = "1m" # Correct dimensions according to API Gateway metrics: deploymentId, httpStatusCode query = <<-EOT HttpResponses[1m]{deploymentId="${var.api_deployment_id}", httpStatusCode="429"}.sum() > 5 EOT body = "Increased rate of HTTP 429 on API Gateway deployment: {{triggerValue}}/min" }
Sur AWS, vous définissez des alarmes simples et robustes sur 4XXError et 5XXError, complétées par un throttling au niveau des stages. En pratique, les alarmes sur 4XXError préviennent tôt et largement, tandis que les limites de taux WAF absorbent les pics de charge. (AWS Documentation)
# AWS: CloudWatch alarm on 4XX errors (stage-wide) resource "aws_cloudwatch_metric_alarm" "api_4xx_spike" { alarm_name = "apigw-prod-4xx-spike" comparison_operator = "GreaterThanThreshold" evaluation_periods = 1 period = 60 statistic = "Sum" threshold = 50 namespace = "AWS/ApiGateway" metric_name = "4XXError" dimensions = { ApiName = aws_api_gateway_rest_api.tf_api.name Stage = aws_api_gateway_stage.prod.stage_name } alarm_description = "Elevated client errors on 'prod' stage" }
Bonnes pratiques pour l’exploitation en production
Planification avant optimisation
Les API Gateways doivent s’intégrer à votre modèle d’architecture et d’exploitation, et non l’inverse. Les pratiques suivantes se sont révélées efficaces et s’appuient sur l’article 5a de cette série :
Déploiements échelonnés : Séparez Foundation, plateforme et workloads applicatifs afin que chaque run reste limité et que les quotas ne soient pas cumulés.
Circuit Breaker pour l’IaC : Mettez en œuvre des Preconditions et des contrôles qui interrompent les runs dès que les taux d’erreurs augmentent. Ainsi, vous n’épuisez pas les quotas d’autres équipes.
Exploiter les fenêtres temporelles : Les déploiements massifs doivent avoir lieu en dehors des périodes de charge maximale. Les plannings CI sont des moyens opérationnels, pas un élément cosmétique.
Timeouts et Retries côté provider : Allongez les timeouts uniquement de manière ciblée, au lieu de les gonfler globalement. Pour les ressources OCI, vous pouvez définir des limites de temps par ressource, par exemple lors du déploiement :
resource "oci_apigateway_deployment" "depl" { # ... your configuration ... timeouts { create = "30m" update = "30m" delete = "30m" } }
Gérer consciemment la parallélisation : Dans Terraform Enterprise, définissez TFE_PARALLELISM par workspace, au lieu de câbler partout des flags -parallelism en ligne de commande. Cela évite des pics de charge incontrôlés et reste traçable.
Dégradation progressive (Graceful Degradation) : Construisez des chemins optionnels qui basculent vers des modes de fonctionnement simplifiés en cas de limites, plutôt que d’échouer complètement un run.
Quotas documentés : Des quotas centralisés par provider et par service sont indispensables. Seuls ceux qui connaissent les quotas peuvent déployer de manière maîtrisée.
Policy as Code avec Sentinel
Les Policies protègent la qualité de la plateforme. La Sentinel Policy suivante limite le nombre maximal de nouvelles ressources par run. Elle peut être définie comme garde-fou obligatoire (Must-Have) dans Terraform Enterprise et produit, pour de gros volumes, un avertissement explicite au lieu d’un échec brutal.
# sentinel/policies/api_limit_guard.sentinel import "tfplan/v2" as tfplan max_resources_per_run = 50 resources_to_create = filter tfplan.resource_changes as _, rc { rc.change.actions contains "create" } main = rule { length(resources_to_create) <= max_resources_per_run } warn_high_resource_count = rule when length(resources_to_create) > 30 { print("WARNING: High resource volume detected.") print("Consider reducing parallelism or splitting the deployment.") true }
Intégration avec Terraform Enterprise
Beaucoup des mesures discutées dans l’article 5a ne déploient leur effet qu’à l’intérieur du pipeline.
Terraform Enterprise permet de coder la parallélisation, les paramètres d’exécution et les configurations client du Gateway comme standards organisationnels. Pour les clients situés dans l’UE ayant des exigences en matière de souveraineté des données, TFE est (à ce jour) l’unique solution de référence.
terraform { required_version = ">= 1.10" required_providers { tfe = { source = "hashicorp/tfe", version = ">= 0.65.0" } } } provider "tfe" { hostname = var.tfe_hostname # e.g., tfe.example.eu token = var.tfe_token } resource "tfe_workspace" "prod" { name = "production-infra" organization = var.tfe_org queue_all_runs = true # Consider 'false' if your maturity model requires manual gates terraform_version = "1.10.5" working_directory = "live/prod" } resource "tfe_variable_set" "api_limits" { name = "api-limit-controls" description = "Controls for parallelism and API client defaults" organization = var.tfe_org } # Control Terraform parallelism via TFE_PARALLELISM resource "tfe_variable" "parallelism" { key = "TFE_PARALLELISM" value = "5" category = "env" description = "Terraform parallelism for API limit control" variable_set_id = tfe_variable_set.api_limits.id } # Example of passing a client header for downstream API gateway policies resource "tfe_variable" "client_header" { key = "TF_VAR_apigw_client_header" value = "X-CI-Run: ${timestamp()}" category = "env" description = "Example header for downstream API gateway policies" variable_set_id = tfe_variable_set.api_limits.id }
Le pilotage via TFE_PARALLELISM est documenté et éprouvé. Conservez des valeurs conservatrices et mesurez l’impact sur la durée des Plan et Apply.
Attention : augmenter cette valeur sans discernement mène souvent à une performance dégradée à cause de réponses 429/5xx plus fréquentes.
Conclusion : respectez l’API
Les limites d’API sont souvent perçues comme un obstacle, mais elles constituent en réalité une sorte de contrat opérationnel entre votre code et la plateforme. Une approche centrée sur Terraform, avec des Rate Limits clairs, des quotas et une alarme au niveau du Gateway, apporte de la prévisibilité dans les pipelines CI, protège les ressources inter-équipes et augmente nettement le taux de succès de vos runs.
Les mesures discutées dans l’article 5a demeurent le premier levier. Les API Gateways supplémentaires approfondissent le contrôle, harmonisent l’observabilité et ancrent vos règles de manière centralisée.
À retenir : ceux qui respectent les limites déploient de façon plus durable et plus robuste.