Truques para escrever scripts de deploy idempotentes e seguros
1. Fundamentos da Idempotência em Deploy
Idempotência é a propriedade de uma operação que pode ser executada múltiplas vezes produzindo sempre o mesmo resultado final. Em scripts de deploy, isso significa que executar o mesmo script duas ou cem vezes deve deixar o sistema no mesmo estado desejado. Scripts idempotentes são críticos para deploys seguros porque permitem repetição sem medo de efeitos colaterais.
Scripts únicos (one-shot) assumem que o estado inicial é sempre o mesmo — uma premissa perigosa em ambientes reais. Scripts não idempotentes podem criar arquivos duplicados, sobrescrever configurações ou deixar serviços em estado inconsistente após uma falha. Rollbacks se tornam complexos porque não há garantia do que foi realmente modificado.
2. Estrutura Condicional e Verificações de Estado
O padrão fundamental para idempotência é verificar antes de agir. Sempre que possível, use o padrão "check-then-act" em vez de "act-and-handle".
# Padrão check-then-act: verifica antes de criar
if [ ! -f /etc/nginx/sites-available/meu-site.conf ]; then
cp /tmp/meu-site.conf /etc/nginx/sites-available/
echo "Arquivo de configuração criado"
else
echo "Arquivo já existe, pulando criação"
fi
# Verificar se imagem Docker existe antes de rebuildar
if ! docker images --format '{{.Repository}}' | grep -q "^meu-app$"; then
docker build -t meu-app:latest .
echo "Imagem construída"
else
echo "Imagem já existe, usando cache"
fi
Para operações que não podem ser verificadas previamente (como chamadas de API), use "act-and-handle" com idempotência no receptor:
# Criação de diretório é naturalmente idempotente
mkdir -p /var/log/meu-app
3. Uso de Flags e Modos Idempotentes em Ferramentas
Muitas ferramentas oferecem flags que tornam operações idempotentes:
# rsync com --ignore-existing para não sobrescrever
rsync -av --ignore-existing ./dist/ /var/www/meu-app/
# docker compose com --no-recreate para não recriar containers existentes
docker compose up -d --no-recreate
# Terraform com -auto-approve combinado com plan prévio
terraform plan -out=tfplan
if [ -f tfplan ]; then
terraform apply -auto-approve tfplan
fi
O padrão --yes ou --non-interactive deve sempre ser combinado com verificações prévias para evitar execuções acidentais.
4. Gerenciamento de Estado com Arquivos de Lock e Marcadores
Arquivos marcadores são uma forma simples e eficaz de rastrear etapas concluídas:
# Arquivo de estado para migrações de banco
MIGRATION_STATE="/var/state/meu-app/migration-2024-01-01.done"
if [ ! -f "$MIGRATION_STATE" ]; then
echo "Executando migração do banco..."
mysql -u root -p$DB_PASSWORD meu_app < /tmp/migration.sql
touch "$MIGRATION_STATE"
echo "Migração concluída"
else
echo "Migração já foi executada, pulando"
fi
Para evitar execuções simultâneas, use flock:
# Lockfile para evitar execuções paralelas
LOCKFILE="/tmp/deploy-meu-app.lock"
exec 200>"$LOCKFILE"
flock -n 200 || { echo "Outro deploy em execução"; exit 1; }
# ... código do deploy ...
flock -u 200
5. Tratamento Robusto de Erros e Rollback Seguro
Use set -e para abortar em qualquer erro e trap para capturar sinais:
#!/bin/bash
set -euo pipefail
ROLLBACK_NEEDED=false
cleanup() {
if [ "$ROLLBACK_NEEDED" = true ]; then
echo "Executando rollback..."
# Rollback idempotente: só desfaz se existir
if [ -f /tmp/backup-config.tar.gz ]; then
tar -xzf /tmp/backup-config.tar.gz -C /etc/meu-app/
echo "Rollback concluído"
fi
fi
}
trap cleanup EXIT
# Backup antes de modificar
tar -czf /tmp/backup-config.tar.gz /etc/meu-app/
ROLLBACK_NEEDED=true
# Operação principal
if ! cp /tmp/nova-config /etc/meu-app/config; then
echo "Falha ao copiar configuração"
exit 1
fi
ROLLBACK_NEEDED=false
6. Separação de Etapas e Ordem de Execução
Divida o script em fases claras:
# Fase 1: Pré-verificação
pre_check() {
echo "Verificando conectividade..."
if ! curl -s --connect-timeout 5 http://api.exemplo.com/health; then
echo "API não está acessível"
exit 1
fi
echo "Pré-verificação OK"
}
# Fase 2: Execução
deploy() {
echo "Iniciando deploy..."
# ... código de deploy ...
}
# Fase 3: Pós-validação
post_validate() {
echo "Validando deploy..."
if curl -s http://localhost:8080/health | grep -q "OK"; then
echo "Deploy validado com sucesso"
else
echo "Falha na validação"
exit 1
fi
}
# Execução principal
pre_check
deploy
post_validate
7. Boas Práticas de Segurança em Scripts de Deploy
Use set -u para detectar variáveis não definidas e evite exposição de credenciais:
#!/bin/bash
set -euo pipefail
# Carregar secrets de forma segura
if [ -f /etc/meu-app/secrets.env ]; then
source /etc/meu-app/secrets.env
# Limpar variáveis após uso
unset DB_PASSWORD
unset API_KEY
fi
# Validar entrada de parâmetros
if [ -z "${1:-}" ]; then
echo "Uso: $0 <ambiente>"
exit 1
fi
# Sanitizar parâmetros para evitar injeção
AMBIENTE="$1"
case "$AMBIENTE" in
dev|staging|production) ;;
*) echo "Ambiente inválido"; exit 1 ;;
esac
8. Testes e Simulação de Deploy (Dry-Run)
Implemente modo --dry-run para simular sem efeitos colaterais:
DRY_RUN=false
if [ "${1:-}" = "--dry-run" ]; then
DRY_RUN=true
fi
execute() {
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
# Uso em todo o script
execute cp /tmp/nova-config /etc/meu-app/config
execute systemctl restart meu-app
# Comparar estado atual com desejado
if [ "$DRY_RUN" = true ]; then
echo "Comparando arquivos..."
diff -r /etc/meu-app/ /tmp/estado-desejado/ || true
fi
Use shellcheck para análise estática e testes unitários com bats:
# Instalar shellcheck
sudo apt-get install shellcheck
shellcheck meu-deploy.sh
# Teste unitário com bats
@test "Verifica se pré-verificação falha sem API" {
run ./meu-deploy.sh
[ "$status" -eq 1 ]
[[ "$output" =~ "API não está acessível" ]]
}
Referências
- Documentação oficial do Bash - Shell Scripts — Guia completo de programação shell, incluindo
set -e,trap, e boas práticas - Terraform - Best Practices for Idempotent Infrastructure — Tutorial oficial sobre como garantir idempotência em infraestrutura como código
- Docker - Best Practices for Writing Dockerfiles — Guia oficial com dicas para builds idempotentes e seguros
- ShellCheck - Static Analysis for Shell Scripts — Ferramenta de análise estática para identificar bugs e problemas de segurança em scripts shell
- Bats - Bash Automated Testing System — Framework de testes unitários para scripts bash, essencial para validar scripts de deploy
- The Twelve-Factor App - Build, Release, Run — Metodologia para deploys seguros e idempotentes em aplicações modernas
- GitLab CI/CD - Idempotent Jobs — Documentação oficial sobre como configurar jobs idempotentes em pipelines CI/CD