cd ..
LINUX

Da VM à EC2: três desafios práticos de Linux para Cloud Native

Da VM à EC2: três desafios práticos de Linux para Cloud Native

Estudar Linux de verdade vai muito além de decorar comandos. A parte mais importante é entender onde cada comando se encaixa em um fluxo real de trabalho.

Nos três primeiros desafios da formação Linux para Cloud Native, montei um pequeno ambiente híbrido com uma VM local e uma instância EC2, organizei a aplicação seguindo uma estrutura inspirada no FHS, transferi arquivos com rsync, editei código diretamente no servidor e acompanhei logs em tempo real.

Neste post, registro os principais passos, os erros que apareceram e o que aprendi em cada etapa.

Nota de segurança: endereços IP e outras informações de infraestrutura foram censurados nas imagens antes da publicação. Chaves privadas, tokens e senhas nunca devem aparecer em screenshots ou repositórios.

Desafio 1 — Setup híbrido com VM, EC2 e SSH

O primeiro objetivo foi preparar dois ambientes Linux:

A ideia era evitar comandos enormes como:

ssh -i ~/.ssh/minha-chave.pem usuario@endereco-do-servidor

Com aliases configurados, o acesso ficou mais simples:

ssh vm
ssh ec2

Um exemplo de configuração segura seria:

Host vm
    HostName ENDERECO_DA_VM
    User usuario-da-vm
    IdentityFile ~/.ssh/id_ed25519

Host ec2
    HostName ENDERECO_DA_EC2
    User ubuntu
    IdentityFile ~/.ssh/chave-ec2.pem

Os valores reais de HostName não devem ser publicados.

Validando os ambientes

Depois de conectar, usei comandos simples para confirmar em qual máquina estava:

whoami
hostname
pwd

Também executei a atualização dos pacotes nos dois terminais:

sudo apt update

VM local e EC2 acessadas por SSH, com informações sensíveis censuradas

O que aprendi

O arquivo ~/.ssh/config não é apenas uma conveniência. Ele centraliza os parâmetros de conexão e reduz erros ao acessar diferentes máquinas.

Também ficou mais claro que:

Desafio 2 — Shell, FHS, diretórios e transferência com rsync

No segundo desafio, preparei uma estrutura para a aplicação giropops-status em /opt.

A estrutura criada foi:

/opt/giropops-status/
├── app/
├── backups/
├── config/
└── logs/

O comando utilizado foi:

sudo mkdir -p /opt/giropops-status/{app,backups,config,logs}

A opção -p faz o mkdir criar os diretórios intermediários quando necessário e não retornar erro caso eles já existam.

Depois, ajustei o proprietário dos diretórios:

sudo chown -R "$USER":"$USER" /opt/giropops-status

Conferindo com ls -lah

Para validar a estrutura, utilizei:

ls -lah /opt/giropops-status

Cada opção tem uma função:

Transferindo a aplicação

Usei rsync para transferir os arquivos entre os ambientes:

rsync -av origem/ destino/

As opções principais foram:

Em outra etapa, empacotei os arquivos e transferi o arquivo compactado:

tar -czf /tmp/giropops-app.tar.gz .
rsync -avz /tmp/giropops-app.tar.gz ec2:/tmp/

Estrutura da aplicação e transferência com rsync

O detalhe da barra final no rsync

Um aprendizado importante foi a diferença entre:

rsync -av pasta/ destino/

e:

rsync -av pasta destino/

Com a barra final, o rsync copia o conteúdo de pasta. Sem a barra, ele copia o próprio diretório para dentro do destino.

É um caractere pequeno com potencial para criar uma árvore de diretórios digna de filme de ficção científica.

Desafio 3 — Vim, VS Code Remote SSH e logs em tempo real

No terceiro desafio, trabalhei com edição de arquivos diretamente no servidor.

As principais tarefas foram:

Para abrir o arquivo:

less app.py

Dentro do less, procurei uma função digitando:

/def check_service

Os atalhos utilizados foram:

Também listei as variáveis de ambiente esperadas pela aplicação:

grep '^[A-Z_]* = os.environ' app.py

Alterando a versão com Vim

Abri o arquivo diretamente no servidor:

vim app.py

No Vim, alterei o valor padrão de APP_VERSION, salvei e saí com:

:wq

Depois validei a alteração:

grep -n 'APP_VERSION' app.py

Configurando o .vimrc

O pacote do exercício trouxe um arquivo didático:

cp /opt/giropops-status/app/dotfiles/vimrc.example ~/.vimrc

A configuração habilitou recursos como:

set number
set tabstop=4
set shiftwidth=4
set expandtab
set autoindent
syntax on
filetype plugin indent on

Validação da APP_VERSION, tag title e configurações do vimrc

Editando remotamente com VS Code

No computador local, instalei a extensão Remote - SSH, da Microsoft.

Depois:

  1. abri a paleta com Ctrl+Shift+P;
  2. selecionei Remote-SSH: Connect to Host;
  3. escolhi o alias vm;
  4. abri /opt/giropops-status/app;
  5. editei templates/index.html;
  6. salvei a alteração diretamente na VM.

O terminal integrado do VS Code já estava conectado ao servidor, então validei com:

hostname
pwd
whoami
grep -n '<title>' templates/index.html

Arquivo index.html aberto diretamente na VM pelo VS Code Remote SSH

Python, Redis e ambiente virtual

Criei um ambiente virtual para evitar instalar dependências no Python do sistema:

cd /opt/giropops-status/app
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt

Confirmei o interpretador ativo:

which python
which pip
python --version

Para o armazenamento da aplicação, instalei o Redis:

sudo apt install -y redis-server
sudo systemctl enable --now redis-server
redis-cli ping

O retorno esperado foi:

PONG

Logging configurável e tail -f

A versão atualizada da aplicação aceita as variáveis:

LOG_FILE=/tmp/giropops-logs/app.log
LOG_LEVEL=INFO

Iniciei a aplicação em segundo plano:

mkdir -p /tmp/giropops-logs

LOG_FILE=/tmp/giropops-logs/app.log LOG_LEVEL=INFO python3 app.py &

Em outro terminal, acompanhei o arquivo em tempo real:

tail -f /tmp/giropops-logs/app.log

Para filtrar somente eventos de check:

tail -f /tmp/giropops-logs/app.log |
grep --line-buffered -i 'check'

Também testei as rotas da API:

curl -X POST   -H 'Content-Type: application/json'   -d '{"name":"google","url":"https://google.com"}'   http://localhost:5000/api/services

curl -X POST http://localhost:5000/api/check

A aplicação retornou HTTP 201 no cadastro e HTTP 200 na verificação, marcando o serviço como UP.

Quando terminei, saí do tail com Ctrl+C e parei a aplicação no terminal em que ela foi iniciada:

kill %1

Erros que apareceram no caminho

Nem tudo funcionou de primeira — e essa foi uma das melhores partes do laboratório.

O arquivo vimrc.example não existia na VM

A primeira tentativa retornou:

cp: cannot stat '.../vimrc.example': No such file or directory

O arquivo existia no projeto local, mas não tinha sido sincronizado para a VM. A solução foi localizar o arquivo e copiar novamente a pasta dotfiles.

O ambiente virtual não foi criado

O Python informou que ensurepip não estava disponível. Instalei os pacotes necessários:

sudo apt install -y python3-venv python3-full
rm -rf .venv
python3 -m venv .venv

A porta 5000 já estava ocupada

Ao iniciar uma segunda instância da aplicação, o Flask respondeu:

Address already in use
Port 5000 is in use by another program.

Identifiquei o processo:

sudo ss -ltnp | grep ':5000'
jobs -l

E encerrei a instância anterior:

kill %1

O LOG_FILE não funcionava na versão antiga

A variável LOG_FILE não criava o arquivo porque o app.py inicial ainda não implementava FileHandler.

Confirmei com:

grep -nE 'LOG_FILE|LOG_LEVEL|FileHandler|basicConfig' app.py

Depois de sincronizar a versão atualizada, o código passou a configurar o logging corretamente.

Conclusão

Esses três desafios conectaram vários conceitos que normalmente são estudados de forma isolada:

Mais do que executar comandos, o exercício mostrou uma rotina próxima do trabalho real: preparar servidores, transferir aplicações, editar arquivos remotamente, validar serviços e investigar erros.

É nesse ponto que o terminal deixa de ser apenas uma tela preta e começa a virar uma ferramenta de engenharia.

O que achou?