Nel precedente articolo 5a abbiamo visto quanto rapidamente i grandi rollout con Terraform possano scontrarsi con i limiti API, ad esempio quando i test di disaster recovery creano centinaia di risorse in parallelo e gli errori 429 scatenano una valanga di retry. Questa continuazione riprende da lì e mostra come, con l’API Gateway di Oracle Cloud Infrastructure e Amazon API Gateway, sia possibile gestire consapevolmente i limiti, osservarli in maniera pulita e renderli operativamente solidi tramite “Policy as Code”.
API Gateway: l’ultima ratio?
Gli API Gateway aiutano a rendere pianificabili i limiti API. Se usati correttamente, aggregano le chiamate API, applicano quote e throttling, forniscono materiale di osservabilità coerente e creano un punto centrale per operation e governance.
Per noi è soprattutto rilevante un aspetto: un Gateway non si limita a spostare il problema dei rate limit, ma ne consente la gestione attiva per team, per deployment e per route.
In Oracle Cloud Infrastructure si impostano dei binari tecnici con Usage Plan ed Entitlement. Questi agiscono direttamente sui deployment dell’API Gateway, ad esempio imponendo un limite rigido al numero di richieste al secondo nonché quote per minuto o per mese. Per enforcement e trasparenza sono disponibili metriche specifiche del servizio, come HttpResponses con le dimensioni deploymentId e httpStatusCode, che si possono allarmare in modo pulito. (Oracle Documentation).
Le categorie di log di servizio access ed execution sono i canali previsti dal servizio; vengono direttamente assegnate al deployment dell’API e sono da preferire rispetto alla log-archiviazione legacy su bucket. (Oracle Documentation)
Ecco un esempio per OCI (un esempio per AWS segue più avanti):
# 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 } }
In Amazon API Gateway si combinano tre leve: Stage- e Method-Throttling, Usage Plan con API Key e regole basate sul rate in AWS WAF per aggregazioni IP. Le metriche di CloudWatch 4XXError e 5XXError forniscono un sistema di allerta precoce e robusto a livello di Stage.
Importante: AWS WAFv2 oggi può essere associato solo a Stage REST-API, non alle HTTP API. (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 }
I throttling a livello di Stage, i Usage Plan e l’associazione con la WAF sono i pilastri fondamentali sul lato AWS. CloudWatch mette a disposizione, tra le altre, le metriche 4XXError con le dimensioni ApiName e Stage, il che semplifica l’attivazione di allarmi specifici per Stage. (AWS Documentation)
Testing e validazione con Terraform 1.10+
Per disporre di reti di sicurezza rapide e riproducibili si raccomanda il framework di testing nativo di Terraform. I Mock-Provider incapsulano le dipendenze esterne, mentre le Assertions verificano regole specifiche di progetto come le dimensioni massime dei batch o il comportamento in caso di limiti troppo bassi.
Pro-Tip: Utilizzi test brevi e significativi, che rafforzano i Suoi moduli contro configurazioni errate. (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 ] }
Note dalla pratica:
- Le Assertions devono essere su una sola riga,
- expect_failures si riferisce a Preconditions nominate, non a generici errori di tipo,
- Le risorse effimere, ad oggi (Terraform 1.12.0), sono utili soprattutto per token temporanei e query, ma non come sostituto universale dei Mock.
Monitoring + Alerting
L’osservabilità è la spina dorsale operativa della Sua strategia sugli API-Limit.
Su OCI il metodo più affidabile è lavorare direttamente con le metriche di servizio dell’API Gateway in combinazione con gli allarmi della piattaforma di Monitoring. Le dimensioni deploymentId e httpStatusCode consentono un filtraggio univoco sulle risposte 429. La sintassi in MQL è la seguente, presti attenzione ai nomi corretti delle dimensioni: (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" }
Su AWS si definiscono allarmi semplici e affidabili su 4XXError e 5XXError, integrati da uno Stage-wide Throttling. Nella pratica, gli allarmi su 4XXError segnalano in anticipo e in maniera ampia, mentre i WAF-Rate-Limit intercettano i picchi di carico. (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" }
Best Practices per l’esercizio in produzione
Pianificazione prima dell’ottimizzazione
Gli API Gateway devono adattarsi al Suo modello architetturale e operativo, non il contrario. Le seguenti pratiche si sono dimostrate efficaci e si basano sull’articolo 5a di questa serie:
Deployments scaglionati: Separi Foundation, Platform e Application Workloads, in modo che i singoli run restino piccoli e le quote non vengano superate cumulativamente.
Circuit-Breaker per IaC: Implementi Preconditions e Check che interrompono i run non appena i tassi di errore aumentano. In questo modo non consuma le quote di altri team.
Sfruttare finestre temporali: I rollout di grandi dimensioni dovrebbero avvenire al di fuori dei periodi di carico principale. Le pianificazioni CI sono strumenti operativi, non cosmetica.
Timeout e Retry dei Provider: Estenda i timeout solo in modo mirato, invece di gonfiarli globalmente. Per le risorse OCI può impostare limiti temporali per singola risorsa, ad esempio durante il deployment:
resource "oci_apigateway_deployment" "depl" { # ... your configuration ... timeouts { create = "30m" update = "30m" delete = "30m" } }
Gestione consapevole della parallelità: In Terraform Enterprise utilizzi TFE_PARALLELISM per Workspace, invece di fissare ovunque i flag -parallelism nelle linee di comando. Questo evita picchi di carico incontrollati ed è tracciabile.
Graceful Degradation: Costruisca percorsi opzionali che, in caso di limiti, ricadono su modalità operative più semplici invece di far fallire l’intero run.
Quote documentate: Quote centralizzate e mantenute per provider e servizio sono indispensabili. Solo chi conosce le quote può effettuare deployment limitati.
Policy as Code con Sentinel
Le Policy proteggono la qualità della piattaforma. La seguente Sentinel-Policy limita il numero massimo di nuove risorse per run. Può essere configurata come guardrail Must-Have in Terraform Enterprise e genera un avviso significativo per volumi elevati invece di un Hard Fail.
# 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 }
Integrazione con Terraform Enterprise
Molte delle misure discusse nell’articolo 5a sprigionano il loro effetto solo all’interno della pipeline.
Terraform Enterprise consente di codificare Parallelismo, impostazioni di runtime e configurazioni client Gateway come standard organizzativi. Per i clienti nell’UE con esigenze di sovranità dei dati, TFE è (attualmente) l’unico strumento disponibile.
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 }
Il controllo tramite TFE_PARALLELISM è documentato e validato nella pratica. Mantenga i valori conservativi e misuri l’impatto sulla durata di Plan e Apply.
Attenzione: Un incremento indiscriminato porta spesso a prestazioni peggiori a causa di un aumento delle risposte 429/5xx.
Conclusione: rispetto per l’API
Gli API-Limit sono spesso percepiti come un ostacolo, ma in realtà rappresentano una sorta di contratto operativo tra il Suo codice e la piattaforma. Un approccio centrato su Terraform con Rate Limit chiari, Quote e allarmi a livello di Gateway porta pianificabilità nelle pipeline CI, protegge le risorse trasversali ai team e aumenta sensibilmente la percentuale di successo dei Suoi run.
Le misure discusse nell’articolo 5a restano il primo strumento. Ulteriori API Gateway approfondiscono il controllo, armonizzano l’Observability e ancorano centralmente le Sue regole.
Nota: Chi rispetta i limiti, effettua deployment più sostenibili e robusti.