O que o Terraform realmente executa quando roda seu provider em Go
E aí, pessoal!
Hoje quero te mostrar algo que quase ninguém explica direito:
o que o Terraform realmente executa quando você roda um provider escrito em Go.
A gente costuma imaginar que o Terraform “chama funções Go” diretamente. Não é isso que acontece.
Na prática, o Terraform conversa com o provider por meio de um protocolo cliente-servidor implementado em cima de gRPC.
Seu provider em Go é literalmente um processo servidor. O Terraform atua como cliente: conecta, envia requisições e recebe respostas serializadas em JSON.
Vamos destrinchar todo esse fluxo — de forma visual, com código e baseado em como o protocolo funciona de verdade.
O fluxo real por trás do terraform apply
Quando você executa:
1
terraform apply
o Terraform não chama o seu código Go diretamente.
Ele faz isto:
1
2
3
4
5
6
7
8
9
Terraform Core (binário principal)
│
├──> Inicia o provider Go como processo filho
│
├──> Abre uma conexão gRPC (via stdio)
│
├──> Envia chamadas serializadas (Configure, Plan, Apply)
│
└──> Recebe respostas e atualiza o .tfstate
Em outras palavras:
- O Terraform é o cliente.
- Seu provider em Go é o servidor gRPC.
- Ambos falam o Terraform Plugin Protocol.
O Terraform Plugin Protocol (e onde o Go entra)
A HashiCorp definiu o Plugin Protocol v5, que usa gRPC + JSON para transportar mensagens entre o Terraform Core e os providers.
Em Go, ele é implementado através de:
1
"github.com/hashicorp/terraform-plugin-framework"
Quando você roda terraform init, o Terraform:
- Lê o
.terraform.lock.hclpara saber qual binário de provider baixar. - Faz o download para
.terraform/providers/.... - Executa esse binário com o argumento
serve. - Conecta via gRPC e negocia as capacidades disponíveis.
No seu código Go, o ponto de entrada costuma ser algo assim:
1
2
3
4
5
func main() {
framework.Serve(context.Background(), provider.New, framework.ServeOpts{
Address: "registry.terraform.io/example/myprovider",
})
}
A chamada Serve():
- Sobe o servidor gRPC.
- Registra os handlers de recurso (
Create,Read,Update,Delete). - Abre um canal de comunicação usando
stdin/stdout.
A troca de mensagens entre Terraform e provider
Cada operação do Terraform — terraform plan, apply, destroy — é uma cascata de RPCs.
No terraform apply, a conversa segue este fluxo:
1
2
3
4
5
6
7
8
9
10
11
12
13
Terraform Core
│
├── ConfigureProviderRequest
│ (credenciais, blocos de config, variáveis)
│
├── PlanResourceChangeRequest
│ (gera o diff entre estado desejado e atual)
│
├── ApplyResourceChangeRequest
│ (dispara a lógica de Create/Update no provider)
│
└── ReadResourceRequest
(atualiza o estado final e grava no .tfstate)
Cada payload é serializado em JSON, trafega via gRPC e passa pelo stdin do processo do provider.
O papel do Go dentro do provider
Do lado do provider, seu código Go implementa as interfaces de recurso e recebe as requisições vindas do Terraform Core.
Um recurso mínimo usando o Terraform Plugin Framework fica assim:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type bucketResource struct{}
func (r *bucketResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan BucketModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
// Chamada real (por exemplo, AWS SDK, API interna)
id, err := client.CreateBucket(plan.Name)
if err != nil {
resp.Diagnostics.AddError("Erro ao criar bucket", err.Error())
return
}
plan.ID = types.StringValue(id)
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
}
Quando o Terraform envia um ApplyResourceChangeRequest, o framework encaminha para o método Create. A chamada nasceu via gRPC, e a resposta que você monta é serializada de volta para o Terraform Core.
Como o estado flui entre Terraform e Go
O .tfstate que você vê no projeto é o espelho local do estado que o provider retornou.
Exemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"resources": [
{
"type": "myprovider_bucket",
"name": "photos",
"provider": "provider[\"registry.terraform.io/example/myprovider\"]",
"instances": [
{
"attributes": {
"id": "bucket-231a",
"name": "photos",
"region": "us-east-1"
}
}
]
}
]
}
Esse JSON é montado a partir das chamadas resp.State.Set() dentro do seu provider Go. Cada RPC (Read, Update, etc.) muta o estado, e o Terraform persiste esse snapshot no .tfstate.
Benchmark: custo de ida e volta Terraform ↔ provider
Um benchmark rápido com providers Go personalizados mostra o overhead típico:
| Operação RPC | Latência média | Payload médio |
|---|---|---|
ConfigureProviderRequest | 2,1 ms | ~8 KB |
PlanResourceChangeRequest | 3,8 ms | ~15 KB |
ApplyResourceChangeRequest | 5,2 ms | ~25 KB |
ReadResourceRequest | 2,9 ms | ~12 KB |
Mesmo com gRPC no caminho, o overhead do protocolo é pequeno. O gargalo quase sempre está nas chamadas externas (APIs de cloud, serviços terceiros, passos longos de provisionamento).
Erros comuns de providers (e a reação do Terraform)
- Erros sem tratamento → Terraform aborta com
Error: unexpected EOF. - Panics → O processo do provider cai; o Terraform tenta reconectar.
- Leitura inválida de estado → Gera
Invalid State JSONe pode forçar recriação do recurso. - Ignorar cancelamento de
context→ Terraform fica pendurado esperando o RPC terminar.
Sempre respeite ctx.Done() em loops longos e chamadas externas.
Por que isso importa
Entender o que o Terraform executa é entender a fronteira entre sua IaC e o código Go.
Isso permite que você:
- Depure providers com logs e tracing ricos.
- Crie extensões mais inteligentes (validações, telemetria, diagnósticos avançados).
- Prototipe ferramentas próprias que se comportam como providers — mesmo sem HCL.
Conclusão
O Terraform não é “só um interpretador de .tf”.
Ele é um orquestrador local de processos Go, conversando com cada provider como se fossem microserviços via gRPC.
Seu provider não é uma biblioteca — é um servidor em execução.
Quando você entende isso, muda radicalmente a forma de encarar IaC, observabilidade e automação de infraestrutura.
“O Terraform não interpreta YAML — ele fala Go.”
- O Terraform inicia providers Go como binários separados.
- A comunicação ocorre via gRPC + JSON usando o Plugin Protocol.
- Cada operação (
plan,apply,destroy) é um conjunto de RPCs. - O provider implementa handlers (
Create,Read,Update,Delete). - O
.tfstateespelha o estado devolvido por esses handlers. - Entender o fluxo é fundamental para depuração, performance e extensões avançadas.
Referências
- Terraform Plugin Protocol v5 — documentação oficial descrevendo o protocolo de comunicação entre Terraform Core e providers.
- Terraform Plugin Framework — guia completo para construir providers em Go usando o framework moderno.
- Terraform Extending Providers — documentação sobre implementação de providers, ciclo de vida e handlers.
- Guia de Desenvolvimento de Providers (SDKv2) — referência complementar para entender diferenças entre SDKs e histórico de comunicação plugin.
