Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions entregas/aula-05/6325149/entrega.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Entrega — Aula 05: RDS e Remote State

**Aluno:** Gabriel Reis Cunha
**RA:** 6325149
**Data:** 11/09/2026

## Repositório

- URL: https://github.com/gabrielreis354/unifaat-devops-portfolio
- Infra principal: [`aula-05-rds/`](https://github.com/gabrielreis354/unifaat-devops-portfolio/tree/main/aula-05-rds)
- Backend do state: [`aula-05-backend/`](https://github.com/gabrielreis354/unifaat-devops-portfolio/tree/main/aula-05-backend)
- Spec (SDD) e evidências completas: [`specs/001-aula05-rds-remote-state/`](https://github.com/gabrielreis354/unifaat-devops-portfolio/tree/main/specs/001-aula05-rds-remote-state)

## Evidências

- [x] VPC com subnets públicas e privadas em 2 AZs
- [x] RDS PostgreSQL (db.t3.micro) nas subnets privadas
- [x] EC2 t2.micro na subnet pública, conectando ao RDS
- [x] Security Groups corretos (porta 5432 apenas da VPC)
- [x] Remote State configurado (S3 + DynamoDB)
- [x] State armazenado no S3 (evidência abaixo)
- [x] Conexão EC2 → RDS via psql (evidência abaixo)
- [x] `terraform destroy` executado após evidências

## Nota sobre o bucket S3 do Remote State

O SCP do AWS Academy Learner Lab nega explicitamente
`s3:GetBucketObjectLockConfiguration`, chamada que o recurso `aws_s3_bucket` do
provider `hashicorp/aws` executa em toda leitura/criação (testado em `5.100.0` e
`5.42.0` — falha nas duas). As demais chamadas necessárias (create-bucket,
versioning, encryption, public-access-block) funcionam normalmente. Por isso o
bucket é criado por [`aula-05-backend/bootstrap.sh`](https://github.com/gabrielreis354/unifaat-devops-portfolio/blob/main/aula-05-backend/bootstrap.sh)
(AWS CLI puro, idempotente) enquanto a tabela DynamoDB permanece 100% gerenciada
pelo Terraform (`aula-05-backend/dynamodb.tf`). Detalhes em
[`specs/001-aula05-rds-remote-state/plan.md`](https://github.com/gabrielreis354/unifaat-devops-portfolio/blob/main/specs/001-aula05-rds-remote-state/plan.md#8-addendum--execução-real-2026-09-1011).

## Evidência do State no S3

```
$ aws s3 ls s3://technova-terraform-state-54600b3e83155696/aula-05/ --recursive
2026-09-10 22:26:35 34843 aula-05/terraform.tfstate
```

Bucket com versionamento habilitado, criptografia SSE-KMS e Block Public Access
(4/4). Tabela DynamoDB `technova-terraform-locks` (`LockID`, String) usada como
lock do state.

## Evidência da Conexão EC2 → RDS

```
$ ssh -i ~/.ssh/technova-key ec2-user@54.227.232.230
$ psql -h technova-db.cxhwj2zlyovj.us-east-1.rds.amazonaws.com -U technova_admin -d technova -c "SELECT version();"

version
---------------------------------------------------------------------------------------------------
PostgreSQL 15.17 on x86_64-pc-linux-gnu, compiled by x86_64-pc-linux-gnu-gcc (GCC) 12.4.0, 64-bit
(1 row)
```

## Evidência de Dados Persistentes

```
CREATE TABLE
INSERT 0 3

technova=> SELECT * FROM orders;
id | customer_name | product | quantity | total | created_at
----+---------------+---------------------+----------+---------+----------------------------
1 | Maria Silva | Laptop TechNova Pro | 1 | 4599.90 | 2026-09-11 01:28:18.231431
2 | Joao Santos | Monitor 27" | 2 | 2398.00 | 2026-09-11 01:28:18.231431
3 | Ana Costa | Teclado Mecanico | 3 | 897.00 | 2026-09-11 01:28:18.231431
(3 rows)
```

## Evidência de `terraform plan` limpo (pós-apply)

```
$ terraform plan
...
No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
```

## Evidência: RDS não exposto à internet

```
$ timeout 8 bash -c "echo > /dev/tcp/technova-db.cxhwj2zlyovj.us-east-1.rds.amazonaws.com/5432"
(timeout — conexão recusada/não roteada a partir de fora da VPC)
```

## Teardown

Toda a infraestrutura foi destruída na mesma sessão, imediatamente após a captura
das evidências:

```
$ terraform destroy # aula-05-rds -> Destroy complete! Resources: 13 destroyed.
$ ./teardown.sh # aula-05-backend -> bucket esvaziado e removido
$ terraform destroy # aula-05-backend -> Destroy complete! Resources: 1 destroyed.
```

Verificação pós-teardown: nenhuma instância RDS, bucket S3, tabela DynamoDB, EC2
ou VPC com tag `Project=technova` restante na conta.
153 changes: 153 additions & 0 deletions entregas/aula-05/6325149/trabalho-em-aula.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Trabalho em Aula — Aula 05: RDS e Remote State

**Aluno:** Gabriel Reis Cunha
**RA:** 6325149
**Data:** 03/09/2026

## Parte 1 — Análise dos Incidentes

### Cenário A: Perda de Dados

1. **Por que os dados foram perdidos:** a API não usava nenhum banco de dados
persistente — os pedidos ficavam guardados só na **memória (RAM)** do processo
Node.js rodando no EC2 (um array/objeto em memória, sem gravação em disco nem em
um serviço externo). Ao reiniciar a instância, o processo é finalizado e recriado
do zero: a memória é limpa e não existe nenhum lugar de onde recuperar os 5
pedidos. Um simples reboot programado já basta, nem precisa terminar a instância.

2. **Outros cenários que causariam a mesma perda (mín. 3):**
- Deploy de uma nova versão da aplicação (o processo é reiniciado para carregar
o novo código).
- Crash da aplicação por uma exceção não tratada (o processo morre e sobe de
novo vazio).
- Auto Scaling substituindo a instância por uma nova (a memória da instância
antiga simplesmente não existe na nova).
- Manutenção de host da própria AWS migrando a instância para outro hardware
físico (o que aconteceu no cenário descrito).
- Alguém rodando `terraform apply` com uma mudança que força replace do EC2
(ex.: trocar a AMI ou o `user_data`).

3. **Por que "não reiniciar o EC2" não é solução válida:** reinícios não são uma
escolha do time — a AWS reinicia hosts por manutenção, hardware falha, deploys
precisam reiniciar o processo, Auto Scaling substitui instâncias. Tentar evitar
reinícios ataca o sintoma, não a causa raiz (falta de armazenamento persistente).
Além disso, é operacionalmente insustentável: qualquer atualização de código ou
patch de segurança do sistema também exigiria manter o processo no ar para
sempre, o que é impraticável.

4. **Dados em memória vs. dados persistentes:** dados em memória existem apenas
enquanto o processo que os criou está rodando — são voláteis, rápidos de acessar,
mas desaparecem em qualquer reinício, crash ou substituição da instância. Dados
persistentes são gravados em um armazenamento duradouro (disco, ou melhor ainda,
um banco de dados gerenciado como o RDS) que sobrevive independentemente do que
acontece com o servidor de aplicação. É exatamente a separação implementada no
Lab: o EC2 (camada de processamento, descartável) fica isolado do RDS (camada de
dados, persistente) — reiniciar, substituir ou até terminar o EC2 não afeta uma
linha sequer do banco.

### Cenário B: Perda do State

1. **O que acontece com `terraform plan` sem o state, e por quê:** o Terraform
decide o que fazer comparando três fontes: o código `.tf` (desejado), o state
(o que ele *acha* que já existe) e a API da AWS. Sem o state, a segunda fonte
fica vazia — o Terraform não tem nenhuma memória do que já provisionou. Ele
então trata **cada recurso do código como se não existisse**, e o `plan` mostra
tudo como "to add" (criar do zero), mesmo que a VPC, o RDS e o EC2 estejam rodando
normalmente na AWS.

2. **Risco de rodar `terraform apply` nessa situação:** o Terraform tentaria criar
recursos duplicados. Alguns falhariam por conflito de nome único (ex.: bucket S3),
mas outros (VPC, subnets, EC2, RDS) seriam criados **de novo, em paralelo** aos
que já existem — dobrando o custo, criando uma segunda VPC desconectada da
aplicação real, e deixando a infraestrutura em um estado confuso e caro até
alguém perceber e limpar manualmente.

3. **`terraform import` como solução de emergência:** sim, existe — permite
"adotar" um recurso que já existe na AWS de volta para dentro de um state novo
(`terraform import aws_instance.api i-0123...`), recurso por recurso. É viável,
mas lento e arriscado: cada import exige saber o ID exato do recurso, e o
Terraform não preenche sozinho os atributos do `.tf` — se o código não bater
exatamente com a configuração real, o próximo `plan` mostra mudanças indevidas
(drift) que podem alterar ou destruir o recurso sem querer.

4. **Como essa situação poderia ter sido prevenida:** exatamente com o que foi
implementado no Lab Parte 2 — um **backend remoto** (bucket S3 versionado +
trava no DynamoDB). O state nunca vive só no laptop de uma pessoa; ele fica
centralizado na nuvem, acessível por toda a equipe, e com versionamento
habilitado é possível voltar a uma versão anterior se algo corromper. Perder o
laptop do Rafael deixaria de ser um incidente de infraestrutura.

## Parte 2 — Design da Arquitetura

```
Internet
┌────────┴────────┐
│ Internet Gateway │
└────────┬────────┘
VPC 10.0.0.0/16 │
┌───────────────────────────────────────────────────────────┐
│ Subnet PÚBLICA (us-east-1a) │
│ ┌───────────────┐ SG: entrada 22 (SSH) e 3000 (API) │
│ │ EC2 t2.micro │ de 0.0.0.0/0 │
│ │ (API Node.js) │ │
│ └───────┬───────┘ │
│ │ porta 5432, só de dentro da VPC │
│ ─ ─ ─ ─ ─ ─┼─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ Subnet PRIVADA (us-east-1a) ┐ │
│ Subnet PRIVADA (us-east-1b) ┼─ DB Subnet Group │
│ ┌──────────────────────┐ ┘ SG: entrada 5432 só do │
│ │ RDS PostgreSQL │ CIDR da VPC/SG do EC2 │
│ │ (subnets privadas) │ sem rota para a internet │
│ └──────────────────────┘ │
└───────────────────────────────────────────────────────────┘

Fora da VPC (state do Terraform, não é dado de aplicação):
┌───────────────────┐ ┌───────────────────────┐
│ S3 Bucket │ │ DynamoDB Table │
│ terraform.tfstate │ │ lock (LockID) │
│ versionado + SSE │ │ │
└───────────────────┘ └───────────────────────┘
```

- **Componentes acessíveis pela internet:** só o **EC2**, e só nas portas 22 (SSH)
e 3000 (API) — porque ele está na subnet pública com IP público e o Security
Group libera essas duas portas de `0.0.0.0/0`.
- **Componentes isolados:** o **RDS** está totalmente isolado da internet — vive
em subnets privadas (sem rota para o Internet Gateway), tem `publicly_accessible
= false`, e o Security Group só aceita a porta 5432 vindo de dentro da própria
VPC. Mesmo que alguém descobrisse o endpoint do banco, não haveria como rotear
até ele de fora. O **S3** e o **DynamoDB** do state ficam fora da VPC (são
serviços regionais, não "dentro" de uma rede), mas também não são públicos:
Block Public Access (4/4) no bucket e permissões IAM controlam quem acessa.
- **Por que o RDS precisa de subnets em 2 AZs mesmo sem Multi-AZ:** é uma exigência
estrutural do **DB Subnet Group** da AWS — ele só aceita ser criado com subnets
cobrindo pelo menos duas Availability Zones, mesmo que a instância use só uma
delas no dia a dia. Isso deixa a porta aberta para ativar Multi-AZ depois sem
precisar reconstruir a rede, e a AWS também usa essa segunda AZ durante
manutenção da instância (pode mover o RDS para lá temporariamente).

## Parte 3 — Discussão: Conflito Simultâneo

- **Cenários reais onde isso ocorreria:** um pipeline de CI/CD rodando
`terraform apply` automaticamente a cada merge na `main`, enquanto um
desenvolvedor roda `apply` manualmente do laptop para testar uma mudança local;
dois integrantes do mesmo squad mexendo na mesma stack ao mesmo tempo sem avisar
um ao outro (exatamente o cenário do Dev A / Dev B); ou um job agendado de
correção de drift disparando junto com uma mudança manual de emergência.

- **Impacto de um state corrompido:** o Terraform perde a correspondência
confiável entre o que ele "acha" que existe e o que de fato está provisionado.
Os próximos `plan`/`apply` deixam de ser confiáveis — podem tentar recriar
recursos que já existem (duplicando custo), ou "esquecer" recursos que ficam
órfãos (não gerenciados, gerando custo invisível), ou na pior hipótese, destruir
algo em produção por engano ao tentar reconciliar um estado inconsistente.

- **Como o locking resolve:** a tabela DynamoDB funciona como um **mutex
distribuído**. Antes de ler ou escrever o state, o Terraform tenta gravar um item
de lock (`LockID`) na tabela; se o item já existir (outra operação está em
andamento), a segunda tentativa fica bloqueada — espera ou falha com "Error
acquiring the state lock" — em vez de seguir em frente com um state
desatualizado. Isso serializa as operações: Dev A aplica, libera o lock, só então
Dev B consegue aplicar sua mudança em cima do state já atualizado. Nenhuma das
duas mudanças é perdida.
Loading