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:
- uma VM Ubuntu local;
- uma instância Ubuntu na AWS EC2;
- acesso aos dois ambientes por SSH;
- aliases no arquivo
~/.ssh/config; - autenticação por chave;
- atualização dos pacotes nos dois servidores.
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

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:
- a VM local e a EC2 são ambientes independentes;
- cada máquina possui usuários, processos e filesystem próprios;
- uma chave privada deve permanecer apenas no computador autorizado;
- o arquivo da chave deve ter permissões restritas, normalmente
chmod 600; - nomes de hosts, IPs públicos, tokens e conteúdo de chaves não devem aparecer em posts ou screenshots.
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:
-l: exibe a listagem detalhada;-a: inclui arquivos ocultos;-h: apresenta tamanhos em formato legível, como4Ke32M.
Transferindo a aplicação
Usei rsync para transferir os arquivos entre os ambientes:
rsync -av origem/ destino/
As opções principais foram:
-a: modo arquivo, preservando atributos importantes;-v: modo verboso, mostrando os arquivos processados.
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/

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:
- navegar pelo código com
less; - procurar funções dentro do arquivo;
- alterar
APP_VERSIONusando Vim; - instalar um
.vimrc; - editar o
<title>pelo VS Code Remote SSH; - criar um ambiente virtual Python;
- instalar e iniciar o Redis;
- executar a aplicação Flask;
- acompanhar os logs com
tail -f.
Navegando com less
Para abrir o arquivo:
less app.py
Dentro do less, procurei uma função digitando:
/def check_service
Os atalhos utilizados foram:
n: próxima ocorrência;N: ocorrência anterior;q: sair.
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

Editando remotamente com VS Code
No computador local, instalei a extensão Remote - SSH, da Microsoft.
Depois:
- abri a paleta com
Ctrl+Shift+P; - selecionei
Remote-SSH: Connect to Host; - escolhi o alias
vm; - abri
/opt/giropops-status/app; - editei
templates/index.html; - 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

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:
- SSH e autenticação por chave;
- VM local e infraestrutura em nuvem;
- filesystem Linux e diretórios em
/opt; - transferência de arquivos com
rsync; - edição com Vim e VS Code Remote SSH;
- isolamento de dependências com
venv; - Redis como serviço do sistema;
- logs configuráveis;
- diagnóstico de processos e portas.
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.