Infraestrutura como Código no Azure com Pulumi: deploy de VMs com NGINX e MySQL

Infraestrutura como Código no Azure com Pulumi: deploy de VMs com NGINX e MySQL

29 de Setembro de 2025

Introdução

O Pulumi é uma alternativa moderna ao ARM, Bicep ou Terraform para provisionar infraestrutura no Azure. A grande vantagem é que você pode escrever sua infraestrutura com linguagens como TypeScript, Python ou C#, aproveitando:

  • Intellisense, testes, controle de fluxo
  • Reutilização de código
  • Integração com bibliotecas do ecossistema
  • Deploy automatizado com CI/CD

Neste exemplo prático, vamos criar:

  • Um Resource Group
  • Duas VMs Linux no Azure:
    • Uma rodando o NGINX
    • Outra com o MySQL Server
  • Configuração de networking (VNet + Subnet + IP público + NSG)
  • E tudo usando Pulumi com TypeScript

Pré-requisitos

  • Conta no Azure com permissão de contributor
  • Node.js instalado
  • Pulumi CLI instalado:
curl -fsSL https://get.pulumi.com | sh
  • Login no Azure via CLI:
az login

Estrutura do projeto

azure-pulumi-infra/
├── Pulumi.yaml
├── Pulumi.dev.yaml
├── index.ts
├── package.json
└── tsconfig.json

Inicializando o projeto

pulumi new azure-native-typescript

Escolha um nome para o projeto e a stack (ex: dev)

Código completo (index.ts)

import * as azure from "@pulumi/azure-native";

// 1. Resource Group
const rg = new azure.resources.ResourceGroup("app-rg", {
    location: "brazilsouth",
});

// 2. Virtual Network + Subnet
const vnet = new azure.network.VirtualNetwork("vnet", {
    resourceGroupName: rg.name,
    location: rg.location,
    addressSpace: { addressPrefixes: ["10.0.0.0/16"] },
    subnets: [{ name: "default", addressPrefix: "10.0.1.0/24" }],
});

// 3. Public IPs
const publicIpNginx = new azure.network.PublicIPAddress("nginx-ip", {
    resourceGroupName: rg.name,
    location: rg.location,
    publicIPAllocationMethod: "Dynamic",
});

const publicIpMySQL = new azure.network.PublicIPAddress("mysql-ip", {
    resourceGroupName: rg.name,
    location: rg.location,
    publicIPAllocationMethod: "Dynamic",
});

// 4. Network Interface (NIC)
const nicNginx = new azure.network.NetworkInterface("nic-nginx", {
    resourceGroupName: rg.name,
    location: rg.location,
    ipConfigurations: [{
        name: "ipcfg",
        subnet: { id: vnet.subnets.apply(s => s[0].id!) },
        privateIPAllocationMethod: "Dynamic",
        publicIPAddress: { id: publicIpNginx.id },
    }],
});

const nicMySQL = new azure.network.NetworkInterface("nic-mysql", {
    resourceGroupName: rg.name,
    location: rg.location,
    ipConfigurations: [{
        name: "ipcfg",
        subnet: { id: vnet.subnets.apply(s => s[0].id!) },
        privateIPAllocationMethod: "Dynamic",
        publicIPAddress: { id: publicIpMySQL.id },
    }],
});

// 5. VM com NGINX
const vmNginx = new azure.compute.VirtualMachine("vm-nginx", {
    resourceGroupName: rg.name,
    location: rg.location,
    networkProfile: { networkInterfaces: [{ id: nicNginx.id }] },
    hardwareProfile: { vmSize: "Standard_B1s" },
    osProfile: {
        computerName: "nginxvm",
        adminUsername: "azureuser",
        adminPassword: "SenhaForte@123", // evite isso em prod, use secrets!
        customData: Buffer.from(`#!/bin/bash
        sudo apt-get update
        sudo apt-get install -y nginx
        sudo systemctl start nginx`).toString("base64"),
    },
    storageProfile: {
        osDisk: {
            name: "osdisk-nginx",
            createOption: "FromImage",
        },
        imageReference: {
            publisher: "Canonical",
            offer: "UbuntuServer",
            sku: "18.04-LTS",
            version: "latest",
        },
    },
});

// 6. VM com MySQL
const vmMySQL = new azure.compute.VirtualMachine("vm-mysql", {
    resourceGroupName: rg.name,
    location: rg.location,
    networkProfile: { networkInterfaces: [{ id: nicMySQL.id }] },
    hardwareProfile: { vmSize: "Standard_B1s" },
    osProfile: {
        computerName: "mysqlvm",
        adminUsername: "azureuser",
        adminPassword: "SenhaForte@123",
        customData: Buffer.from(`#!/bin/bash
        sudo apt-get update
        sudo apt-get install -y mysql-server
        sudo systemctl start mysql`).toString("base64"),
    },
    storageProfile: {
        osDisk: {
            name: "osdisk-mysql",
            createOption: "FromImage",
        },
        imageReference: {
            publisher: "Canonical",
            offer: "UbuntuServer",
            sku: "18.04-LTS",
            version: "latest",
        },
    },
});

// 7. Outputs
export const ipNginx = publicIpNginx.ipAddress;
export const ipMySQL = publicIpMySQL.ipAddress;

Deploy da infraestrutura

npm install
pulumi up

Para destruir o ambiente:

pulumi destroy
  

Boas práticas

PráticaJustificativa
Use Pulumi Config para senhas e valores sensíveisEvita hardcoded
Separe lógica em arquivos por recursoMelhor manutenção
Use autoTags, stacks, e environmentsPara ambientes dev/prod/test
Integre com GitHub Actions ou Azure PipelinesCI/CD de IaC completo
Combine com Azure Policy e Key VaultGovernança e segurança

Referências

Conclusão

Pulumi traz agilidade e flexibilidade para equipes de infraestrutura modernas que desejam integrar IaC com as práticas de desenvolvimento. Ao usar TypeScript ou Python, a curva de aprendizado se torna mais acessível e os recursos do Azure podem ser definidos de forma programática e escalável.

Confira mais:

Fique por dentro das novidades

Assine nossa newsletter e receba as últimas atualizações e artigos diretamente em seu email.