Automatización de AWS CLI con Bash
Automatiza el aprovisionamiento de recursos AWS con bash y AWS CLI.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
El AWS CLI envuelve llamadas a la API de AWS en una herramienta de línea de comandos. Combinado con bash, puedes automatizar aprovisionamiento de infraestructura, operaciones S3, gestión de IAM y monitoreo de costos. Esta recipe cubre patrones comunes de automatización de AWS usando scripts bash.
Cuándo Usar
- Necesitas automatizar aprovisionamiento y teardown de instancias EC2
- Quieres gestionar buckets S3 y sincronizar archivos programáticamente
- Necesitas auditar permisos IAM en tu cuenta de AWS
- Estás construyendo scripts de deploy que interactúan con recursos AWS
Solución
Aprovisionar una instancia EC2
#!/bin/bash
set -euo pipefail
INSTANCE_NAME="web-server-01"
AMI_ID="ami-0c55b159cbfafe1f0"
INSTANCE_TYPE="t3.micro"
KEY_NAME="my-key-pair"
SECURITY_GROUP="sg-12345678"
SUBNET_ID="subnet-12345678"
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" \
--instance-type "$INSTANCE_TYPE" \
--key-name "$KEY_NAME" \
--security-group-ids "$SECURITY_GROUP" \
--subnet-id "$SUBNET_ID" \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=${INSTANCE_NAME}}]" \
--query 'Instances[0].InstanceId' \
--output text)
echo "Launched instance: $INSTANCE_ID"
# Wait for instance to be running
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
# Get public IP
PUBLIC_IP=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "Instance running at: $PUBLIC_IP"
Listar todas las instancias EC2 con detalles
#!/bin/bash
aws ec2 describe-instances \
--query 'Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,PublicIpAddress,Tags[?Key==`Name`].Value | [0]]' \
--output table
Gestión de buckets S3
#!/bin/bash
BUCKET_NAME="my-app-backups-$(date +%Y%m%d)"
REGION="us-east-1"
# Create bucket
if [ "$REGION" = "us-east-1" ]; then
aws s3api create-bucket --bucket "$BUCKET_NAME"
else
aws s3api create-bucket \
--bucket "$BUCKET_NAME" \
--region "$REGION" \
--create-bucket-configuration LocationConstraint="$REGION"
fi
echo "Created bucket: $BUCKET_NAME"
# Enable versioning
aws s3api put-bucket-versioning \
--bucket "$BUCKET_NAME" \
--versioning-configuration Status=Enabled
# Set lifecycle policy (delete after 90 days)
cat > /tmp/lifecycle.json << 'EOF'
{
"Rules": [
{
"ID": "DeleteOldObjects",
"Status": "Enabled",
"Expiration": { "Days": 90 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"Filter": {}
}
]
}
EOF
aws s3api put-bucket-lifecycle-configuration \
--bucket "$BUCKET_NAME" \
--lifecycle-configuration file:///tmp/lifecycle.json
echo "Versioning and lifecycle policy configured"
Sincronizar directorio local a S3
#!/bin/bash
LOCAL_DIR="/var/backups/app"
S3_BUCKET="my-app-backups"
S3_PREFIX="daily"
# Sync with delete and exclude temp files
aws s3 sync "$LOCAL_DIR" "s3://${S3_BUCKET}/${S3_PREFIX}/" \
--delete \
--exclude "*.tmp" \
--exclude "*.lock" \
--storage-class STANDARD_IA
echo "Synced $LOCAL_DIR to s3://${S3_BUCKET}/${S3_PREFIX}/"
Auditoría de usuarios IAM
#!/bin/bash
echo "=== IAM User Audit ==="
echo "Date: $(date)"
echo ""
# List all users
USERS=$(aws iam list-users --query 'Users[*].UserName' --output text)
for user in $USERS; do
echo "--- User: $user ---"
# Get access keys
KEYS=$(aws iam list-access-keys --user-name "$user" --output text 2>/dev/null)
if [ -n "$KEYS" ]; then
echo "Access keys:"
echo "$KEYS" | tr '\t' '\n'
else
echo "No access keys"
fi
# Get attached policies
POLICIES=$(aws iam list-attached-user-policies --user-name "$user" \
--query 'AttachedPolicies[*].PolicyName' --output text 2>/dev/null)
if [ -n "$POLICIES" ]; then
echo "Attached policies: $POLICIES"
fi
# Get groups
GROUPS=$(aws iam list-groups-for-user --user-name "$user" \
--query 'Groups[*].GroupName' --output text 2>/dev/null)
if [ -n "$GROUPS" ]; then
echo "Groups: $GROUPS"
fi
echo ""
done
Detener e iniciar instancias EC2 por tag
#!/bin/bash
# Stop all instances tagged Environment=dev
INSTANCE_IDS=$(aws ec2 describe-instances \
--filters "Name=tag:Environment,Values=dev" "Name=instance-state-name,Values=running" \
--query 'Reservations[*].Instances[*].InstanceId' \
--output text)
if [ -z "$INSTANCE_IDS" ]; then
echo "No running dev instances found"
exit 0
fi
echo "Stopping instances: $INSTANCE_IDS"
aws ec2 stop-instances --instance-ids $INSTANCE_IDS
# Wait for stop
aws ec2 wait instance-stopped --instance-ids $INSTANCE_IDS
echo "All dev instances stopped"
Monitoreo de costos con AWS Cost Explorer
#!/bin/bash
START_DATE=$(date -d "30 days ago" +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
aws ce get-cost-and-usage \
--time-period Start="$START_DATE",End="$END_DATE" \
--granularity DAILY \
--metrics UnblendedCost \
--group-by Type=SERVICE \
--query 'ResultsByTime[*].[TimePeriod.Start,Groups[*].[Keys[0],Metrics.UnblendedCost.Amount]]' \
--output table
Limpieza de volúmenes EBS no usados
#!/bin/bash
# Find unattached volumes
VOLUMES=$(aws ec2 describe-volumes \
--filters "Name=status,Values=available" \
--query 'Volumes[*].[VolumeId,Size,Tags[?Key==`Name`].Value | [0]]' \
--output text)
if [ -z "$VOLUMES" ]; then
echo "No unused volumes found"
exit 0
fi
echo "Unused EBS volumes:"
echo "$VOLUMES"
# Delete each volume
for vol_id in $(echo "$VOLUMES" | awk '{print $1}'); do
echo "Deleting volume: $vol_id"
aws ec2 delete-volume --volume-id "$vol_id"
done
echo "Cleanup complete"
Backup de snapshot RDS
#!/bin/bash
DB_INSTANCE="my-database"
SNAPSHOT_ID="${DB_INSTANCE}-snapshot-$(date +%Y%m%d-%H%M%S)"
aws rds create-db-snapshot \
--db-instance-identifier "$DB_INSTANCE" \
--db-snapshot-identifier "$SNAPSHOT_ID"
echo "Creating snapshot: $SNAPSHOT_ID"
# Wait for snapshot to complete
aws rds wait db-snapshot-available \
--db-snapshot-identifier "$SNAPSHOT_ID"
echo "Snapshot created: $SNAPSHOT_ID"
# Clean up snapshots older than 30 days
OLD_SNAPSHOTS=$(aws rds describe-db-snapshots \
--db-instance-identifier "$DB_INSTANCE" \
--query "DBSnapshots[?SnapshotCreateTime<\`$(date -d '30 days ago' -I)\`].DBSnapshotIdentifier" \
--output text)
for snapshot in $OLD_SNAPSHOTS; do
echo "Deleting old snapshot: $snapshot"
aws rds delete-db-snapshot --db-snapshot-identifier "$snapshot"
done
Explicación
El AWS CLI devuelve JSON por defecto. Usar --query con expresiones JMESPath y --output text|table hace el output amigable para scripts. La sintaxis --query filtra y da forma a la respuesta antes de que llegue a bash.
Patrones clave:
--query: Expresión JMESPath para extraer campos específicos de la respuesta de la API.--output text: Devuelve texto plano, un valor por línea. Ideal para capturar en variables.--output table: Formato legible para humanos para reportes y auditorías.- Comandos
wait: Bloquean hasta que una operación long-running completa (instance running, snapshot available, etc.). set -euo pipefail: Fails fast en errores, variables undefined y pipe failures.
Variantes
| Enfoque | Herramienta | Complejidad | Usar Cuando |
|---|---|---|---|
| AWS CLI + bash | aws cli | Baja | Scripts rápidos, automatización simple |
| AWS SDK (Python) | boto3 | Media | Lógica compleja, manejo de errores |
| AWS SDK (JavaScript) | aws-sdk | Media | Entornos Node.js |
| Terraform | terraform | Alta | Infrastructure as code |
| CloudFormation | aws cloudformation | Alta | IaC declarativo |
Pautas
- Siempre setea
set -euo pipefailal inicio de los scripts. Fail fast en errores. - Usa
--querypara extraer solo los campos que necesitas. Evita parsear JSON en bash. - Usa comandos
waiten vez de pollear consleep. Son más confiables. - Taguea todos los recursos. Los tags facilitan la limpieza y asignación de costos.
- Usa IAM roles para instancias EC2 en vez de embeber access keys en scripts.
- Testea scripts contra una cuenta no productiva primero.
Errores Comunes
- Embeber credenciales AWS en scripts. Usa variables de entorno o IAM roles.
- No manejar rate limits de la API. AWS throttlear peticiones; agrega retries con
--retry-attempts. - Olvidar esperar operaciones async. El script continúa antes de que el recurso esté listo.
- No limpiar recursos. Volúmenes EBS huérfanos y snapshots viejos generan costos.
- Usar
--output jsony parsear conjqcuando--querypuede hacerlo nativamente.
Preguntas Frecuentes
¿Cómo manejo errores de AWS CLI en bash?
Verifica el exit code y captura stderr:
if ! aws s3 ls "s3://my-bucket" 2>/dev/null; then
echo "Bucket does not exist or access denied"
exit 1
fi
¿Cómo asumo un rol antes de ejecutar comandos?
ROLE_ARN="arn:aws:iam::123456789012:role/MyRole"
CREDENTIALS=$(aws sts assume-role \
--role-arn "$ROLE_ARN" \
--role-session-name "my-script" \
--query 'Credentials' \
--output json)
export AWS_ACCESS_KEY_ID=$(echo "$CREDENTIALS" | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDENTIALS" | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo "$CREDENTIALS" | jq -r '.SessionToken')
# Now run AWS commands as the assumed role
aws s3 ls
¿Cómo ejecuto el mismo comando en múltiples regiones?
REGIONS=("us-east-1" "us-west-2" "eu-west-1")
for region in "${REGIONS[@]}"; do
echo "=== Region: $region ==="
aws ec2 describe-instances --region "$region" \
--query 'Reservations[*].Instances[*].[InstanceId,State.Name]' \
--output table
done
¿Cómo hago dry-run de operaciones destructivas?
Usa el flag --dry-run. AWS valida la petición sin hacer cambios:
aws ec2 terminate-instances --instance-ids "i-12345678" --dry-run
Si el output dice DryRunOperation, la petición tendría éxito. Si dice UnauthorizedOperation, te faltan permisos.
Recursos Relacionados
AWS CLI Automation with Bash
Automate AWS resource provisioning, tagging, and cleanup using Bash scripts wrapped around the AWS CLI.
DocAuto-Scaling Policy Template
A template for documenting scale-up and scale-down rules for cloud infrastructure.
RecipeBackup Rotation Script in Bash
Automated backup with retention policies using bash and find.
RecipeMonitor Disk Usage with Bash
Alert when disk space crosses thresholds with bash scripts
RecipeLog Rotation and Compression in Bash
Rotate and compress application logs automatically with bash scripts