Lab práctico · Semana 3: Almacenamiento: S3 a fondo, EFS, FSx, Backup e híbrido

EFS compartido entre dos AZ y copias con AWS Backup

⏱ 90-120 minDificultad: mediaTask statements: 3.11.32.24.1

Qué vas a construir

Un sistema de ficheros Amazon EFS (Regional, Elastic throughput, cifrado) montado a la vez por dos instancias EC2 en dos AZ distintas, con una política de ciclo de vida hacia Infrequent Access y Archive, y protegido por AWS Backup con un plan asignado por etiqueta, una copia bajo demanda y una restauración.

flowchart LR
    subgraph AZa["AZ a"]
      E1["EC2 lab06-a"] --> MT1["Mount target"]
    end
    subgraph AZb["AZ b"]
      E2["EC2 lab06-b"] --> MT2["Mount target"]
    end
    MT1 --> FS["EFS Regional (Elastic, cifrado)"]
    MT2 --> FS
    FS -->|"plan por etiqueta backup=diaria"| V["Backup vault lab06-vault"]

Antes de empezar

  • Usuario de IAM Identity Center o IAM con MFA, nunca el raíz. CloudShell en eu-south-2 (España).
  • Usaremos la VPC por defecto (subredes públicas con IP pública automática, necesaria para que las instancias lleguen a Systems Manager sin NAT). Si la borraste, crea una con aws ec2 create-default-vpc.
  • Lo que cobra mientras exista: las instancias (t3.micro, unos 0,0114 USD/h cada una en eu-south-2), sus IPv4 públicas (0,005 USD/h cada una), el almacenamiento EFS y las copias de AWS Backup (céntimos para unos KB).
  • No accederemos por SSH: usaremos Systems Manager Run Command, sin abrir puertos.

Paso 0: variables y red

export AWS_REGION=eu-south-2
VPC=$(aws ec2 describe-vpcs --filters Name=is-default,Values=true \
  --query 'Vpcs[0].VpcId' --output text)
read SUB1 SUB2 <<< $(aws ec2 describe-subnets \
  --filters Name=vpc-id,Values=$VPC Name=default-for-az,Values=true \
  --query 'Subnets[0:2].SubnetId' --output text)
aws ec2 describe-subnets --subnet-ids $SUB1 $SUB2 \
  --query 'Subnets[].{Subred:SubnetId,AZ:AvailabilityZone}' --output table

Comprueba que las dos subredes están en AZ distintas.

Paso 1: security groups

Uno para los clientes (sin reglas de entrada) y otro para EFS que solo admite NFS (TCP 2049) desde el primero. Es el patrón de «security group como origen» que el examen adora.

SG_CLI=$(aws ec2 create-security-group --group-name lab06-clientes \
  --description "Clientes EFS lab06" --vpc-id $VPC --query GroupId --output text)
SG_EFS=$(aws ec2 create-security-group --group-name lab06-efs \
  --description "EFS lab06 solo NFS desde clientes" --vpc-id $VPC --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SG_EFS \
  --protocol tcp --port 2049 --source-group $SG_CLI

Paso 2: crear el sistema de ficheros y los mount targets

FS=$(aws efs create-file-system \
  --performance-mode generalPurpose \
  --throughput-mode elastic \
  --encrypted \
  --no-backup \
  --tags Key=Name,Value=lab06-efs \
  --query FileSystemId --output text)

until [ "$(aws efs describe-file-systems --file-system-id $FS \
  --query 'FileSystems[0].LifeCycleState' --output text)" = "available" ]; do sleep 5; done

aws efs create-mount-target --file-system-id $FS --subnet-id $SUB1 --security-groups $SG_EFS
aws efs create-mount-target --file-system-id $FS --subnet-id $SUB2 --security-groups $SG_EFS

Hemos puesto --no-backup para gestionar nosotros las copias con un plan; al crear desde la consola, las copias automáticas vienen activadas por defecto.

Espera a que los dos mount targets estén available:

aws efs describe-mount-targets --file-system-id $FS \
  --query 'MountTargets[].{AZ:AvailabilityZoneName,IP:IpAddress,Estado:LifeCycleState}' --output table

Paso 3: ciclo de vida de EFS

Ficheros sin acceso 30 días → Infrequent Access; 90 días → Archive (requiere Elastic throughput); vuelta a Standard al primer acceso.

aws efs put-lifecycle-configuration --file-system-id $FS --lifecycle-policies \
  '[{"TransitionToIA":"AFTER_30_DAYS"},{"TransitionToArchive":"AFTER_90_DAYS"},{"TransitionToPrimaryStorageClass":"AFTER_1_ACCESS"}]'
aws efs describe-lifecycle-configuration --file-system-id $FS

Paso 4: rol para Systems Manager

cat > /tmp/confianza-ec2.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }
  ]
}
EOF
aws iam create-role --role-name lab06-ec2-ssm \
  --assume-role-policy-document file:///tmp/confianza-ec2.json
aws iam attach-role-policy --role-name lab06-ec2-ssm \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name lab06-ec2-ssm
aws iam add-role-to-instance-profile --instance-profile-name lab06-ec2-ssm \
  --role-name lab06-ec2-ssm
sleep 15

Paso 5: dos instancias que montan EFS

El user data instala el cliente amazon-efs-utils, monta el sistema de ficheros con TLS (cifrado en tránsito) y escribe una línea con el nombre de la máquina.

cat > /tmp/userdata.sh <<EOF
#!/bin/bash
dnf install -y amazon-efs-utils
mkdir -p /mnt/efs
echo "${FS}:/ /mnt/efs efs _netdev,tls 0 0" >> /etc/fstab
until mount -a; do sleep 10; done
echo "Hola desde \$(hostname) a las \$(date +%T)" >> /mnt/efs/saludos.txt
EOF

lanzar() {
  aws ec2 run-instances \
    --image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
    --instance-type t3.micro \
    --subnet-id "$1" \
    --security-group-ids $SG_CLI \
    --iam-instance-profile Name=lab06-ec2-ssm \
    --metadata-options HttpTokens=required \
    --user-data file:///tmp/userdata.sh \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$2}]" \
    --query 'Instances[0].InstanceId' --output text
}
I1=$(lanzar $SUB1 lab06-a)
I2=$(lanzar $SUB2 lab06-b)
aws ec2 wait instance-status-ok --instance-ids $I1 $I2

Fíjate en HttpTokens=required: obliga a IMDSv2 (módulo 02).

Paso 6: comprobar que ambas ven lo mismo

Ejecuta un comando en las dos instancias con Run Command:

CMD=$(aws ssm send-command --instance-ids $I1 $I2 \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["df -hT /mnt/efs","cat /mnt/efs/saludos.txt"]' \
  --query 'Command.CommandId' --output text)
sleep 10
for i in $I1 $I2; do
  echo "== $i"
  aws ssm get-command-invocation --command-id $CMD --instance-id $i \
    --query StandardOutputContent --output text
done

Las dos instancias, en AZ distintas, muestran el mismo saludos.txt con dos líneas: una de cada máquina. Si una instancia aún no aparece en Systems Manager, espera un minuto y repite.

Paso 7: AWS Backup con plan por etiquetas

7.1 Rol de servicio, vault y plan

cat > /tmp/confianza-backup.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Principal": { "Service": "backup.amazonaws.com" }, "Action": "sts:AssumeRole" }
  ]
}
EOF
ROL_BK=$(aws iam create-role --role-name lab06-backup \
  --assume-role-policy-document file:///tmp/confianza-backup.json \
  --query Role.Arn --output text)
aws iam attach-role-policy --role-name lab06-backup \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
aws iam attach-role-policy --role-name lab06-backup \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores

aws backup create-backup-vault --backup-vault-name lab06-vault

cat > /tmp/plan.json <<'EOF'
{
  "BackupPlanName": "lab06-plan",
  "Rules": [
    {
      "RuleName": "diaria-7-dias",
      "TargetBackupVaultName": "lab06-vault",
      "ScheduleExpression": "cron(0 3 * * ? *)",
      "StartWindowMinutes": 60,
      "CompletionWindowMinutes": 180,
      "Lifecycle": { "DeleteAfterDays": 7 }
    }
  ]
}
EOF
PLAN=$(aws backup create-backup-plan --backup-plan file:///tmp/plan.json \
  --query BackupPlanId --output text)

7.2 Asignación de recursos por etiqueta

Todo recurso compatible con la etiqueta backup=diaria entra en el plan, sin tocarlo más:

cat > /tmp/seleccion.json <<EOF
{
  "SelectionName": "etiqueta-diaria",
  "IamRoleArn": "${ROL_BK}",
  "ListOfTags": [
    { "ConditionType": "STRINGEQUALS", "ConditionKey": "backup", "ConditionValue": "diaria" }
  ]
}
EOF
aws backup create-backup-selection --backup-plan-id $PLAN \
  --backup-selection file:///tmp/seleccion.json
aws efs tag-resource --resource-id $FS --tags Key=backup,Value=diaria

7.3 Copia bajo demanda

No vamos a esperar a las 3:00. Lanza una copia ya:

FS_ARN=$(aws efs describe-file-systems --file-system-id $FS \
  --query 'FileSystems[0].FileSystemArn' --output text)
JOB=$(aws backup start-backup-job --backup-vault-name lab06-vault \
  --resource-arn $FS_ARN --iam-role-arn $ROL_BK \
  --lifecycle DeleteAfterDays=1 --query BackupJobId --output text)

until [ "$(aws backup describe-backup-job --backup-job-id $JOB \
  --query State --output text)" = "COMPLETED" ]; do sleep 20; echo -n "."; done; echo
aws backup list-recovery-points-by-backup-vault --backup-vault-name lab06-vault \
  --query 'RecoveryPoints[].{Punto:RecoveryPointArn,Estado:Status}' --output table

Paso 8: simular un desastre y restaurar

Borra el fichero desde una instancia:

aws ssm send-command --instance-ids $I1 --document-name AWS-RunShellScript \
  --parameters 'commands=["rm -f /mnt/efs/saludos.txt","ls -la /mnt/efs"]'

Restaura desde la consola (es más cómodo que la API para EFS):

  1. AWS Backup → Backup vaults → lab06-vault, elige el punto de recuperación y pulsa Restore.
  2. Elige Restore items to directory in source file system (restauración a un directorio del mismo EFS), con el rol lab06-backup.
  3. Espera a que el trabajo de restauración termine (Jobs → Restore jobs).

Comprueba el resultado: AWS Backup crea un directorio aws-backup-restore_… con los ficheros recuperados.

CMD=$(aws ssm send-command --instance-ids $I2 --document-name AWS-RunShellScript \
  --parameters 'commands=["find /mnt/efs -name saludos.txt -exec cat {} +"]' \
  --query 'Command.CommandId' --output text)
sleep 10
aws ssm get-command-invocation --command-id $CMD --instance-id $I2 \
  --query StandardOutputContent --output text

Comprueba que funciona

  • Los dos mount targets están available en AZ distintas.
  • df -hT muestra /mnt/efs de tipo nfs4 en ambas instancias.
  • saludos.txt tenía una línea de cada instancia.
  • La política de ciclo de vida incluye IA, Archive y vuelta a Standard.
  • El trabajo de copia terminó en COMPLETED y restauraste el fichero borrado.

Limpieza

Orden: instancias → puntos de recuperación → plan y vault → mount targets → sistema de ficheros → security groups → IAM.

aws ec2 terminate-instances --instance-ids $I1 $I2
aws ec2 wait instance-terminated --instance-ids $I1 $I2

for rp in $(aws backup list-recovery-points-by-backup-vault --backup-vault-name lab06-vault \
  --query 'RecoveryPoints[].RecoveryPointArn' --output text); do
  aws backup delete-recovery-point --backup-vault-name lab06-vault --recovery-point-arn $rp
done
SEL=$(aws backup list-backup-selections --backup-plan-id $PLAN \
  --query 'BackupSelectionsList[0].SelectionId' --output text)
aws backup delete-backup-selection --backup-plan-id $PLAN --selection-id $SEL
aws backup delete-backup-plan --backup-plan-id $PLAN
sleep 30
aws backup delete-backup-vault --backup-vault-name lab06-vault

for mt in $(aws efs describe-mount-targets --file-system-id $FS \
  --query 'MountTargets[].MountTargetId' --output text); do
  aws efs delete-mount-target --mount-target-id $mt
done
until [ "$(aws efs describe-mount-targets --file-system-id $FS \
  --query 'length(MountTargets)')" = "0" ]; do sleep 10; done
aws efs delete-file-system --file-system-id $FS

aws ec2 delete-security-group --group-id $SG_EFS
aws ec2 delete-security-group --group-id $SG_CLI

aws iam remove-role-from-instance-profile --instance-profile-name lab06-ec2-ssm --role-name lab06-ec2-ssm
aws iam delete-instance-profile --instance-profile-name lab06-ec2-ssm
aws iam detach-role-policy --role-name lab06-ec2-ssm \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam delete-role --role-name lab06-ec2-ssm
aws iam detach-role-policy --role-name lab06-backup \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
aws iam detach-role-policy --role-name lab06-backup \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores
aws iam delete-role --role-name lab06-backup

Si delete-backup-vault falla porque aún quedan puntos de recuperación en borrado, espera un minuto y repítelo. Si restauraste a un sistema de ficheros nuevo en vez de a un directorio, bórralo también (primero sus mount targets, si los creaste).

Preguntas para pensar como arquitecto

1. ¿Por qué no habría servido un volumen EBS para este caso, ni siquiera con Multi-Attach?

EBS vive en una sola AZ y Multi-Attach (solo io1/io2) funciona dentro de esa AZ y exige un sistema de ficheros de clúster. Aquí dos instancias en AZ distintas necesitan leer y escribir los mismos ficheros con semántica NFS: eso es EFS (o FSx si fuera Windows/SMB).

2. La empresa quiere que la copia de EFS sobreviva a la caída de toda la región y que nadie pueda borrarla durante 30 días. ¿Qué añadirías al plan?

Una copy action en la regla del plan hacia un vault de otra región (y, mejor aún, de otra cuenta de la organización), y AWS Backup Vault Lock en ese vault con retención mínima de 30 días. Como alternativa de RPO/RTO de minutos para el propio sistema de ficheros, EFS Replication a otra región.

3. Tu aplicación escribe ficheros que casi nadie vuelve a leer después de un mes, pero cuando se leen deben estar disponibles al momento. ¿Cómo abaratas EFS?

Con la política de ciclo de vida hacia Infrequent Access (latencia de decenas de milisegundos, pero sin restauración) y AFTER_1_ACCESS para devolverlos a Standard si se vuelven a usar. Archive abarata más, pero tiene 90 días de duración mínima; úsalo solo para datos que sabes que casi nunca se leen.

4. Las instancias fueran Windows y usaran rutas \\servidor\compartido con permisos de Active Directory. ¿Qué cambiarías?

EFS no admite SMB ni clientes Windows. Usarías FSx for Windows File Server en despliegue Multi-AZ, unido a AWS Managed Microsoft AD o a tu AD, y AWS Backup seguiría sirviendo para las copias.


Volver al módulo