Introdução ao Pulumi: infraestrutura como código em sua linguagem favorita

1. O que é o Pulumi e por que ele se destaca no ecossistema IaC

Pulumi é uma plataforma de infraestrutura como código (IaC) que permite gerenciar recursos de nuvem usando linguagens de programação reais como TypeScript, Python, Go, C# e Java. Diferente de ferramentas como Terraform, que utilizam uma DSL (HCL) proprietária, o Pulumi permite que você escreva infraestrutura com a mesma sintaxe, bibliotecas e ferramentas que já usa no desenvolvimento de software.

As diferenças cruciais em relação ao Terraform incluem:
- Paradigma de programação: No Terraform, você declara recursos em HCL; no Pulumi, você usa loops, condicionais, funções e classes nativas da linguagem.
- Gerenciamento de estado: O Pulumi oferece backend gerenciado (Pulumi Cloud) ou auto-gerenciado (S3, Azure Blob, GCS), com suporte nativo a versionamento e colaboração.
- Provedores: O Pulumi possui provedores oficiais para AWS, Azure, GCP, Kubernetes e mais de 100 provedores comunitários.

Em comparação com o AWS CDK, o Pulumi se destaca por ser multi-cloud (não apenas AWS) e por oferecer uma experiência consistente entre provedores.

As vantagens práticas incluem:
- Reuso de bibliotecas e pacotes (npm, pip, Go modules)
- Uso de IDEs com autocomplete, refatoração e debugging nativo
- Testes unitários com frameworks como Jest, pytest e Ginkgo
- Depuração com breakpoints e logs diretamente no código

2. Configurando o ambiente e primeiro projeto

Para começar, instale o CLI do Pulumi:

# Linux/macOS
curl -fsSL https://get.pulumi.com | sh

# Windows (PowerShell)
iwr https://get.pulumi.com/install.ps1 -useb | iex

Configure o backend de estado. Vamos usar o Pulumi Cloud (gratuito para uso individual):

pulumi login

Acesse https://app.pulumi.com para criar uma conta e obter um token de acesso.

Crie seu primeiro projeto:

mkdir meu-projeto-pulumi
cd meu-projeto-pulumi
pulumi new aws-typescript

Isso gera a estrutura:
- Pulumi.yaml: arquivo de configuração do projeto
- Pulumi.dev.yaml: configurações específicas do stack dev
- index.ts: código principal da infraestrutura

Configure as credenciais da AWS:

pulumi config set aws:region us-east-1

3. Escrevendo sua primeira infraestrutura: recursos e stacks

Vamos criar uma VPC com subnets, security groups e uma instância EC2 em TypeScript:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Criação de uma VPC
const vpc = new aws.ec2.Vpc("minha-vpc", {
    cidrBlock: "10.0.0.0/16",
    enableDnsSupport: true,
    enableDnsHostnames: true,
    tags: { Name: "minha-vpc" },
});

// Subnet pública
const subnet = new aws.ec2.Subnet("minha-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
    mapPublicIpOnLaunch: true,
    availabilityZone: "us-east-1a",
    tags: { Name: "minha-subnet" },
});

// Security Group
const sg = new aws.ec2.SecurityGroup("meu-sg", {
    vpcId: vpc.id,
    ingress: [{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }],
    egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
    tags: { Name: "meu-sg" },
});

// Instância EC2
const instance = new aws.ec2.Instance("minha-instancia", {
    ami: "ami-0c55b159cbfafe1f0", // Amazon Linux 2
    instanceType: "t2.micro",
    subnetId: subnet.id,
    vpcSecurityGroupIds: [sg.id],
    tags: { Name: "minha-instancia" },
});

export const instancePublicIp = instance.publicIp;

Stacks permitem criar ambientes isolados:

pulumi stack init dev
pulumi stack init staging
pulumi stack init prod

Para alternar entre stacks:

pulumi stack select dev
pulumi up

4. Trabalhando com provedores e componentes reutilizáveis

O Pulumi suporta provedores oficiais para AWS, Azure, GCP, Kubernetes, Cloudflare, Datadog e muitos outros. Vamos criar um componente reutilizável para deploy serverless com API Gateway + Lambda:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Componente reutilizável
export class ServerlessAPI extends pulumi.ComponentResource {
    public readonly apiUrl: pulumi.Output<string>;

    constructor(name: string, args: ServerlessAPIArgs, opts?: pulumi.ComponentResourceOptions) {
        super("my:modules:ServerlessAPI", name, args, opts);

        const role = new aws.iam.Role(`${name}-role`, {
            assumeRolePolicy: JSON.stringify({
                Version: "2012-10-17",
                Statement: [{
                    Action: "sts:AssumeRole",
                    Principal: { Service: "lambda.amazonaws.com" },
                    Effect: "Allow",
                }],
            }),
        }, { parent: this });

        const lambda = new aws.lambda.Function(`${name}-fn`, {
            runtime: "nodejs18.x",
            handler: "index.handler",
            role: role.arn,
            code: new pulumi.asset.AssetArchive({
                ".": new pulumi.asset.FileArchive("./lambda"),
            }),
        }, { parent: this });

        const api = new aws.apigatewayv2.Api(`${name}-api`, {
            protocolType: "HTTP",
            routeKey: "$default",
            target: lambda.arn,
        }, { parent: this });

        this.apiUrl = api.apiEndpoint;
    }
}

// Uso do componente
const minhaAPI = new ServerlessAPI("minha-api", {});
export const url = minhaAPI.apiUrl;

5. Gerenciamento de estado, secrets e políticas de segurança

O fluxo de trabalho básico do Pulumi:

# Visualizar mudanças
pulumi preview

# Aplicar mudanças
pulumi up

# Destruir recursos
pulumi destroy

Para gerenciar segredos:

pulumi config set --secret db_password "minha-senha-super-secreta"

No código, acesse o segredo:

const config = new pulumi.Config();
const dbPassword = config.requireSecret("db_password");

Para aplicar políticas de governança com CrossGuard, crie um arquivo PulumiPolicy.yaml:

pulumi policy new aws-typescript

Exemplo de política que impede buckets S3 públicos:

import { PolicyPack } from "@pulumi/policy";
import { AwsResourceValidationPolicy } from "@pulumi/aws-policy";

const policies = new PolicyPack("meu-pacote", {
    policies: [
        {
            name: "s3-no-public-read",
            description: "Proíbe buckets S3 com acesso público de leitura",
            enforcementLevel: "mandatory",
            validateResource: (args, reportViolation) => {
                if (args.type === "aws:s3/bucket:Bucket") {
                    const acl = args.props.acl;
                    if (acl === "public-read" || acl === "public-read-write") {
                        reportViolation("Buckets S3 não podem ter ACL pública.");
                    }
                }
            },
        },
    ],
});

6. Integração contínua e pipelines com Pulumi

Exemplo de pipeline com GitHub Actions:

name: Deploy Infrastructure
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install
      - uses: pulumi/actions@v4
        with:
          command: up
          stack: dev
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Comandos essenciais para automação:

# Deploy sem confirmação
pulumi up --yes

# Preview com diff detalhado
pulumi preview --diff

# Rollback para versão anterior
pulumi stack select dev
pulumi stack revert <stack-tag>

Estratégias de implantação podem ser implementadas usando tags de stack e múltiplos stacks para blue/green:

pulumi stack select blue
pulumi up
# Validar
pulumi stack select green
pulumi up
# Trocar DNS

7. Boas práticas, debugging e próximos passos

Estrutura de projeto modular:

projeto/
├── infra/
│   ├── networking/
│   │   └── index.ts
│   ├── compute/
│   │   └── index.ts
│   └── database/
│       └── index.ts
├── config/
│   └── index.ts
├── Pulumi.yaml
└── package.json

Debugging:

# Visualizar logs de recursos
pulumi logs --resource minha-instancia

# Visualizar grafo de dependências
pulumi stack graph | dot -Tpng > grafo.png

# Logs detalhados
pulumi up --logtostderr --verbose=9

Próximos passos:
- Explore o Pulumi Registry para encontrar componentes prontos
- Estude a Automation API para criar ferramentas personalizadas
- Migre projetos Terraform existentes usando pulumi convert
- Aprofunde-se em testes com pulumi-aws e @pulumi/policy

Referências