|
1 | | -# studies-lab-multicloud-terraform |
| 1 | +<h1 align="center"> |
| 2 | + Lab Multicloud com Terraform: site estático com load balancer na AWS e na Azure |
| 3 | +</h1> |
| 4 | + |
| 5 | +<p align="center"> |
| 6 | + <img src="docs/demo.webp" alt="Página do site estático: a cada recarga o load balancer entrega uma instância diferente (web-01 a web-04), com tema claro e escuro" /> |
| 7 | +</p> |
| 8 | + |
| 9 | +<p align="center"> |
| 10 | + <a href="https://skillicons.dev"> |
| 11 | + <img src="https://skillicons.dev/icons?i=terraform,aws,azure,githubactions,bash,html,css,docker,nginx" alt="Stacks" /> |
| 12 | + </a> |
| 13 | +</p> |
| 14 | + |
| 15 | +## Qual a finalidade do projeto? |
| 16 | + |
| 17 | +Laboratório de estudo de **Terraform multicloud**, que nasceu dos estudos na graduação em Cloud da FIAP. A ideia é subir **a mesma aplicação nas duas nuvens**: um site estático servido por **4 servidores web atrás de um load balancer**, uma vez na **AWS** e outra na **Azure**, com o código organizado do mesmo jeito nas duas. |
| 18 | + |
| 19 | +Cada nuvem tem a sua raiz Terraform com **três módulos simétricos** (`network`, `compute` e `lb`). O site é uma página só, que cada servidor preenche no primeiro boot com os próprios dados (nome, região, zona, hostname e IP), então dá para ver o balanceamento acontecendo: a cada recarga, outra instância responde. |
| 20 | + |
| 21 | +## Arquitetura |
| 22 | + |
| 23 | +<p align="center"> |
| 24 | + <img src="docs/arch.gif" alt="Arquitetura: usuários acessam o ALB na AWS (4 EC2 em duas subnets públicas) e o Load Balancer na Azure (4 VMs em duas subnets); GitHub Actions dispara o Terraform" /> |
| 25 | +</p> |
| 26 | + |
| 27 | +## O que foi construído |
| 28 | + |
| 29 | +### Módulos (iguais nas duas nuvens) |
| 30 | + |
| 31 | +| Módulo | AWS (`terraform/aws`) | Azure (`terraform/azure`) | |
| 32 | +|---|---|---| |
| 33 | +| `network` | VPC, Internet Gateway, 2 subnets públicas (us-east-1a e us-east-1c), route table | VNet, 2 subnets, NSG com HTTP 80 (e SSH opcional) | |
| 34 | +| `compute` | Security group, 4 EC2 Amazon Linux 2023 com Apache, IMDSv2 obrigatório | Availability set, 4 NICs, 4 VMs Ubuntu 22.04 com Apache, login só por chave SSH | |
| 35 | +| `lb` | Application Load Balancer, target group com health check, listener HTTP 80 | IP público e Load Balancer Standard, probe HTTP, regra de entrada e regra de saída | |
| 36 | + |
| 37 | +### Entradas principais |
| 38 | + |
| 39 | +| AWS | Azure | Padrão | |
| 40 | +|---|---|---| |
| 41 | +| `instance_count` | `vm_count` | `4` | |
| 42 | +| `instance_type` | `vm_size` | `t3.micro` / `Standard_B1s` | |
| 43 | +| `public_subnets` | `subnets` | 2 subnets `/24` | |
| 44 | +| `ssh_allowed_cidrs` | `ssh_allowed_cidrs` | `[]` (SSH fechado) | |
| 45 | +| `key_name` | `admin_ssh_public_key` | opcional / obrigatório | |
| 46 | +| · | `dns_label` | obrigatório (nome DNS do IP público) | |
| 47 | + |
| 48 | +### Saídas |
| 49 | + |
| 50 | +| Output | O que traz | |
| 51 | +|---|---| |
| 52 | +| `site_url` | URL do site pelo load balancer (DNS do ALB ou FQDN do IP público da Azure) | |
| 53 | +| `instances` / `vms` | Mapa `nome => IP privado` dos 4 servidores | |
| 54 | + |
| 55 | +### Segurança |
| 56 | + |
| 57 | +| Ponto | Como ficou | |
| 58 | +|---|---| |
| 59 | +| Senha das VMs | Não existe: a Azure usa só chave SSH (`disable_password_authentication = true`) | |
| 60 | +| SSH | Fechado por padrão nas duas nuvens; abre só para os CIDRs de `ssh_allowed_cidrs` | |
| 61 | +| HTTP nas EC2 | Liberado só a partir do security group do ALB | |
| 62 | +| Metadados da EC2 | IMDSv2 obrigatório (`http_tokens = "required"`) | |
| 63 | +| Estado | Backend remoto com configuração parcial (`backend.hcl`, fora do git); `*.tfvars` e `*.tfstate` no `.gitignore` | |
| 64 | +| Pipeline | `apply` e `destroy` só por disparo manual | |
| 65 | + |
| 66 | +### Pipelines |
| 67 | + |
| 68 | +| Workflow | Quando roda | O que faz | |
| 69 | +|---|---|---| |
| 70 | +| `ci.yml` | todo push e PR | `terraform fmt -check`, `init -backend=false` e `validate` na AWS e na Azure; `shellcheck` do script do site; sobe a simulação local e confere que as 4 instâncias respondem | |
| 71 | +| `deploy.yml` | só `workflow_dispatch` | Escolhe a nuvem e a ação (`plan`, `apply` ou `destroy`) | |
| 72 | + |
| 73 | +## Tecnologias utilizadas |
| 74 | + |
| 75 | +- **Terraform** (`>= 1.5`) com os providers **AWS** (`>= 5.70`) e **AzureRM** (`>= 4.5`); |
| 76 | +- **AWS:** VPC, EC2, Application Load Balancer; |
| 77 | +- **Azure:** Virtual Network, NSG, Linux Virtual Machines, Load Balancer Standard; |
| 78 | +- **Apache httpd** nas instâncias, configurado por **user data / cloud-init**; |
| 79 | +- **HTML e CSS** puros no site (sem dependências externas, com tema escuro automático); |
| 80 | +- **GitHub Actions** para CI e deploy manual; |
| 81 | +- **Docker Compose + nginx** para a simulação local do balanceamento. |
| 82 | + |
| 83 | +## Estrutura do repositório |
| 84 | + |
| 85 | +```text |
| 86 | +studies-lab-multicloud-terraform/ |
| 87 | +├── terraform/ |
| 88 | +│ ├── aws/ |
| 89 | +│ │ ├── versions.tf # Providers e backend S3 (parcial) |
| 90 | +│ │ ├── variables.tf · main.tf · outputs.tf |
| 91 | +│ │ ├── terraform.tfvars.example · backend.hcl.example |
| 92 | +│ │ └── modules/ |
| 93 | +│ │ ├── network/ # VPC, IGW, subnets, route table |
| 94 | +│ │ ├── compute/ # SG, EC2 e user-data.sh.tftpl |
| 95 | +│ │ └── lb/ # ALB, target group, listener |
| 96 | +│ └── azure/ |
| 97 | +│ ├── versions.tf # Provider e backend azurerm (parcial) |
| 98 | +│ ├── variables.tf · main.tf · outputs.tf |
| 99 | +│ ├── terraform.tfvars.example · backend.hcl.example |
| 100 | +│ └── modules/ |
| 101 | +│ ├── network/ # VNet, subnets, NSG |
| 102 | +│ ├── compute/ # Availability set, NICs, VMs e cloud-init.sh.tftpl |
| 103 | +│ └── lb/ # IP público, LB, probe, regras |
| 104 | +├── site/ |
| 105 | +│ ├── index.html # Página com marcadores {{...}} |
| 106 | +│ └── render.sh # Preenche os marcadores no boot |
| 107 | +├── local/ |
| 108 | +│ ├── docker-compose.yml # 4 Apache + nginx em round-robin |
| 109 | +│ └── nginx.conf |
| 110 | +├── .github/workflows/ # ci.yml e deploy.yml |
| 111 | +└── docs/ # arch.gif e demo.webp |
| 112 | +``` |
| 113 | + |
| 114 | +## Fluxo de funcionamento |
| 115 | + |
| 116 | +1. O Terraform lê `site/index.html` e `site/render.sh` e embute os dois (em base64) no user data de cada EC2 e no `custom_data` de cada VM. |
| 117 | +2. No primeiro boot, o script instala o Apache, consulta o serviço de metadados da nuvem (IMDSv2 na AWS, IMDS na Azure) para saber região e zona, e roda o `render.sh`, que troca os marcadores pelo nome, hostname e IP da máquina. |
| 118 | +3. O load balancer checa a saúde de cada servidor com `GET /` na porta 80 e só manda tráfego para os saudáveis. |
| 119 | +4. O usuário abre o `site_url`; a cada requisição o balanceador escolhe um servidor, e a página mostra qual respondeu. |
| 120 | +5. Na Azure as VMs não têm IP público: entram pelo LB e saem para a internet (para o `apt`) pela regra de outbound do próprio LB. |
| 121 | + |
| 122 | +## Como rodar |
| 123 | + |
| 124 | +### Simulação local (sem conta em nuvem) |
| 125 | + |
| 126 | +```bash |
| 127 | +docker compose -f local/docker-compose.yml up -d --wait |
| 128 | +# abra http://localhost:8080 e recarregue a página |
| 129 | +docker compose -f local/docker-compose.yml down |
| 130 | +``` |
| 131 | + |
| 132 | +Sobe 4 containers Apache com a mesma página, preenchida pelo mesmo `render.sh`, atrás de um nginx em round-robin fazendo o papel do load balancer. É daí que vem a demo do topo. A porta muda com `LOCAL_PORT=9090`. |
| 133 | + |
| 134 | +### AWS |
| 135 | + |
| 136 | +```bash |
| 137 | +cd terraform/aws |
| 138 | +cp backend.hcl.example backend.hcl # bucket e tabela de lock do tfstate |
| 139 | +cp terraform.tfvars.example terraform.tfvars |
| 140 | +terraform init -backend-config=backend.hcl |
| 141 | +terraform apply |
| 142 | +terraform output site_url |
| 143 | +``` |
| 144 | + |
| 145 | +### Azure |
| 146 | + |
| 147 | +```bash |
| 148 | +az login |
| 149 | +export ARM_SUBSCRIPTION_ID="<id-da-assinatura>" |
| 150 | +cd terraform/azure |
| 151 | +cp backend.hcl.example backend.hcl # storage account do tfstate |
| 152 | +cp terraform.tfvars.example terraform.tfvars # dns_label único |
| 153 | +export TF_VAR_admin_ssh_public_key="$(cat ~/.ssh/id_ed25519.pub)" |
| 154 | +terraform init -backend-config=backend.hcl |
| 155 | +terraform apply |
| 156 | +terraform output site_url |
| 157 | +``` |
| 158 | + |
| 159 | +Para só testar sem backend remoto: `terraform init -backend=false`. Ao terminar: `terraform destroy`. |
| 160 | + |
| 161 | +### Pelo GitHub Actions |
| 162 | + |
| 163 | +O `deploy.yml` roda pela aba **Actions → Deploy (manual)**, escolhendo a nuvem e a ação. Ele usa o environment com o nome da nuvem e espera estes secrets: |
| 164 | + |
| 165 | +| Secret / variável | Nuvem | |
| 166 | +|---|---| |
| 167 | +| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | AWS | |
| 168 | +| `ARM_CLIENT_ID`, `ARM_CLIENT_SECRET`, `ARM_TENANT_ID`, `ARM_SUBSCRIPTION_ID` | Azure | |
| 169 | +| `ADMIN_SSH_PUBLIC_KEY` (secret) e `AZURE_DNS_LABEL` (variável) | Azure | |
| 170 | +| `BACKEND_HCL`: conteúdo do `backend.hcl` da nuvem | as duas | |
| 171 | + |
| 172 | +## Como validar a entrega |
| 173 | + |
| 174 | +Sem credenciais (é o que o CI faz): |
| 175 | + |
| 176 | +```bash |
| 177 | +TF="docker run --rm -v $PWD:/w -w /w hashicorp/terraform:1.9" |
| 178 | +$TF fmt -check -recursive |
| 179 | +for c in aws azure; do |
| 180 | + docker run --rm -v "$PWD":/w -w /w/terraform/$c hashicorp/terraform:1.9 init -backend=false |
| 181 | + docker run --rm -v "$PWD":/w -w /w/terraform/$c hashicorp/terraform:1.9 validate |
| 182 | +done |
| 183 | +``` |
| 184 | + |
| 185 | +Simulação local: recarregar `http://localhost:8080` 8 vezes deve mostrar `web-01`, `web-02`, `web-03` e `web-04`. |
| 186 | + |
| 187 | +Com a infraestrutura no ar: |
| 188 | + |
| 189 | +- `terraform output site_url` devolve o endereço do ALB (AWS) ou `<dns_label>.brazilsouth.cloudapp.azure.com` (Azure); |
| 190 | +- `curl -s <site_url> | grep -o 'web-<em>[0-9]*'` (ou `vm-`) muda de instância entre as chamadas. O ALB distribui por requisição; o Load Balancer da Azure distribui por conexão (hash de 5 tuplas), então no navegador, que reaproveita a conexão, a troca aparece melhor com `curl` ou numa aba anônima; |
| 191 | +- o target group da AWS e o probe da Azure mostram os 4 servidores saudáveis; |
| 192 | +- a página mostra a região e a zona reais lidas do serviço de metadados; |
| 193 | +- `terraform destroy` remove tudo ao final. |
| 194 | + |
| 195 | +## Autor |
| 196 | + |
| 197 | +**William Coelho** · RM 556336 · [@willtechdev](https://github.com/willtechdev) |
0 commit comments