The previous lesson ended with an uncomfortable question: if the Auto Scaling group creates and destroys MercadoFresco storefront instances according to Friday's traffic, where do the data live? The product photos that today sit in /var/www/fotos on the office server cannot live inside a disk that vanishes when the ASG decides one machine is surplus.

This lesson answers that question from two complementary angles. Amazon EBS (Elastic Block Store) gives each instance disks that outlive the machine, are extended while running and back themselves up. Amazon EFS (Elastic File System) gives several instances at once a shared file system, which is exactly what Auto Scaling needs so that Friday's four machines all see the same photos.

And along the way we close MercadoFresco's problem 2: the unreliable backups. The external hard drive Marta plugs in "when she remembers" is replaced by incremental snapshots automated with Data Lifecycle Manager, with a defined retention and a copy in another region.

Contents

  1. The three storage models: block, file and object
  2. What an EBS volume is and what it guarantees
  3. EBS volume types and how to choose
  4. Sizing gp3: IOPS and throughput independent of size
  5. Creating, attaching, formatting and mounting a volume
  6. Extending a volume while running, without stopping the shop
  7. Snapshots: what they are, how they are billed and how they are restored
  8. Data Lifecycle Manager: the end of problem 2
  9. Volume encryption
  10. Instance store: fast, local and ephemeral
  11. Amazon EFS: a file system for several instances
  12. Mounting EFS on the storefront instances
  13. Final table: EBS, EFS, instance store and S3

The three storage models: block, file and object

Before touching any service we need to be clear about a distinction that organises the whole cloud storage chapter. These are not three competing products: they are three different models of keeping data, and each one solves a different problem.

Block (EBS) File (EFS) Object (S3)
Unit Raw disk block File inside a hierarchy Object with a key and metadata
How it is accessed Like a hard disk: /dev/xvdf NFS network protocol: mount HTTPS API: GET/PUT
Who puts the file system on it You (mkfs) The service There is no file system
Modifying part of a piece of data Yes, block by block Yes No: the whole object is replaced
Concurrency 1 instance (or a few, with multi-attach) Thousands of instances at once Unlimited, over HTTPS
Capacity Fixed, whatever you provision Automatically elastic Practically unlimited
Typical latency Sub-millisecond Low milliseconds Tens of milliseconds
Relative cost per GB Medium High (≈3× EBS) Low
Clear-cut case System disk, database Directory shared between servers Photos, backups, static files

The mental rule worth fixing:

  • Block: when the software expects a disk. An operating system, PostgreSQL, a database engine. None of them knows how to speak HTTP.
  • File: when several servers have to share the same files and the software expects file system paths (/var/www/fotos). It is the minimum-change route when you are migrating a legacy application.
  • Object: when the piece of data is a whole file written once and read many times. It is the cheapest, the most durable and the most scalable, but it requires the application to use its API.

For MercadoFresco, the product photos could go to EFS (without touching the code) or to S3 (changing the code, cheaper and better in the long run). We will look at EFS here and S3 in lesson 02-03, where it will become clear why that is the definitive decision.

What an EBS volume is and what it guarantees

An EBS volume is a virtual disk attached over the network to an EC2 instance, but which the operating system sees exactly as a local disk: it shows up in lsblk, it is formatted with mkfs and it is mounted with mount.

Its essential properties:

  • It is independent of the instance. If you terminate the instance, the volume can survive (it depends on the DeleteOnTermination attribute). You can detach it and attach it elsewhere.
  • It lives in a single Availability Zone. A volume in eu-west-1a cannot be attached to an instance in eu-west-1b. To move it you have to snapshot it and restore it in the other AZ.
  • It is replicated inside its AZ. AWS keeps several copies on different servers: a physical disk failure does not affect you. But a failure of the whole AZ does. That is why snapshots matter.
  • You pay per provisioned GB, not per used GB. A 500 GiB volume holding 3 GiB of data costs the same as 500 GiB. This is one of the most common hidden costs.
  • It can be extended while running, but not shrunk. Size it sensibly.
flowchart LR
    subgraph AZ1["Availability Zone eu-west-1a"]
        I1["Instance<br/>mercadofresco-tienda-01"]
        V1["Root EBS volume<br/>8 GiB gp3"]
        V2["Data EBS volume<br/>20 GiB gp3<br/>/var/www/fotos"]
        I1 --- V1
        I1 --- V2
    end
    subgraph AZ2["Availability Zone eu-west-1b"]
        I2["Instance<br/>mercadofresco-tienda-02"]
        V3["Root EBS volume<br/>8 GiB gp3"]
        I2 --- V3
    end
    V2 -.->|"snapshot"| S["Snapshots in S3<br/>(regional, multi-AZ)"]
    S -.->|"restore"| V3

Notice the important detail in the diagram: mercadofresco-tienda-02 cannot attach to the photo volume in the other AZ. That limit is precisely what pushes you towards EFS or S3.

EBS volume types and how to choose

There are two categories: SSD, optimised for operations per second (IOPS) and random access, and HDD, optimised for sequential throughput (MB/s) and cost per GB.

Type Technology Max. IOPS per volume Max. throughput Indicative cost (USD/GB-month, eu-west-1) Use case
gp3 SSD 16,000 1,000 MB/s 0.088 (+ extra IOPS and MB/s billed apart) The default option: system, applications, medium-sized databases
gp2 SSD 16,000 (tied to size) 250 MB/s 0.11 Previous generation; do not choose it for anything new
io2 / io2 Block Express SSD 64,000 / 256,000 4,000 MB/s 0.138 + 0.065 per IOPS Critical databases, guaranteed IOPS, 99.999 % durability
st1 HDD 500 500 MB/s 0.048 Logs, big data, massive sequential reads
sc1 HDD 250 250 MB/s 0.018 Cold archive that is rarely accessed

The prices are indicative and they change; always check them in the AWS calculator. What does not change is the order of magnitude between them.

Four decision criteria:

  1. Always start with gp3. It is around 20 % cheaper than gp2 and gives 3,000 baseline IOPS even if the volume is only 1 GiB.
  2. Move up to io2 only if you have measured that you need more than 16,000 IOPS or if the business demands five-nines durability. It is expensive.
  3. HDD (st1/sc1) only for sequential access. If the workload does many small, random reads, an HDD will give appalling performance however tempting the price per GB.
  4. HDDs cannot be boot volumes. The root disk is always SSD.

Migrating from gp2 to gp3 is free, done while running and saves money. If you inherit an account with gp2 volumes, this is the easiest saving there is:

aws ec2 modify-volume --volume-id vol-0123456789abcdef0 --volume-type gp3 \
  --profile mercadofresco-dev --region eu-west-1

Sizing gp3: IOPS and throughput independent of size

Here is gp3's conceptual improvement over gp2, and it is worth pausing on because it explains a lot of architectural decisions.

With gp2, performance was tied to size: 3 IOPS per GiB. If you wanted 3,000 IOPS you had to provision 1,000 GiB, even if you only used 40. You bought space in order to buy speed.

With gp3, the three parameters are bought separately:

Parameter Included in the base price Maximum Cost of the extra
Size Whatever you provision 16 TiB 0.088 USD/GB-month
IOPS 3,000 16,000 ~0.006 USD per IOPS-month above 3,000
Throughput 125 MB/s 1,000 MB/s ~0.048 USD per MB/s-month above 125

A comparison for MercadoFresco's case, which needs 100 GiB and 3,000 IOPS:

Option Configuration Approximate monthly cost
gp2 1,000 GiB (to reach 3,000 IOPS) ~110 USD
gp3 100 GiB + 3,000 baseline IOPS ~8.8 USD

A factor of 12 in difference just for knowing the right volume type. And if later on the database needed 6,000 IOPS, they are added without touching the size:

aws ec2 modify-volume \
  --volume-id vol-0123456789abcdef0 \
  --iops 6000 --throughput 250 \
  --profile mercadofresco-dev --region eu-west-1

One practical limit you need to know: the instance has a ceiling too. A t3.micro will not be able to take advantage of 16,000 IOPS however many you buy, because its bandwidth towards EBS is capped. For disk-intensive workloads you use "EBS-optimised" instances of a sufficient size. A blisteringly fast volume hanging off a tiny instance is not much use.

Creating, attaching, formatting and mounting a volume

We are going to give mercadofresco-tienda-01 a dedicated 20 GiB disk for the product photos, separate from the system disk. Keeping data and system apart is good practice: you can reinstall the machine without touching the data, and take snapshots only of what matters.

Step 1: create the volume (in the same AZ as the instance)

# The AZ must match the instance's EXACTLY. If it does not, it cannot be attached.
VOL_ID=$(aws ec2 create-volume \
  --availability-zone eu-west-1a \
  --size 20 \
  --volume-type gp3 \
  --encrypted \
  --tag-specifications 'ResourceType=volume,Tags=[
      {Key=Name,Value=mercadofresco-fotos-01},
      {Key=Proyecto,Value=mercadofresco},
      {Key=Entorno,Value=desarrollo},
      {Key=Componente,Value=catalogo},
      {Key=Propietario,Value=luis},
      {Key=CentroCoste,Value=operaciones}]' \
  --query 'VolumeId' --output text \
  --profile mercadofresco-dev --region eu-west-1)

echo "Volume created: $VOL_ID"
  • --encrypted turns on encryption at rest with the AWS-managed key. It costs the same as leaving it unencrypted and it cannot be added afterwards, so you always set it from the start.
  • The Componente is catalogo, because these are the product photos, not the storefront itself. The tagging scheme then lets you attribute the cost to the right team.

Step 2: attach it to the instance

aws ec2 attach-volume \
  --volume-id "$VOL_ID" \
  --instance-id i-0123456789abcdef0 \
  --device /dev/sdf \
  --profile mercadofresco-dev --region eu-west-1

One detail that throws people: you ask for /dev/sdf but Amazon Linux with a modern kernel and NVMe will show it as /dev/nvme1n1. The name you ask for is a label; the one you see inside depends on the system. That is why you never mount by device name in /etc/fstab, but by UUID.

Step 3: format and mount (inside the instance)

# Look at the available disks. The new one shows up with no mount point.
lsblk
# NAME          MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# nvme0n1       259:0    0   8G  0 disk
# └─nvme0n1p1   259:1    0   8G  0 part /
# nvme1n1       259:2    0  20G  0 disk          <-- the new one, empty

# Check whether it already has a file system. If it answers "data", it is empty.
sudo file -s /dev/nvme1n1
# /dev/nvme1n1: data

# Format with XFS (the standard on Amazon Linux; ext4 is just as valid).
# WARNING: mkfs DESTROYS everything on the volume. Only on brand-new disks.
sudo mkfs -t xfs /dev/nvme1n1

# Create the mount point and mount it
sudo mkdir -p /var/www/fotos
sudo mount /dev/nvme1n1 /var/www/fotos

# Check
df -h /var/www/fotos
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/nvme1n1     20G  175M   20G   1% /var/www/fotos

Step 4: make it permanent in /etc/fstab

Without this step, the mount is lost at the next reboot and the shop starts up with an empty photo directory.

# Get the file system's UUID (stable, unlike /dev/nvme1n1)
sudo blkid /dev/nvme1n1
# /dev/nvme1n1: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="xfs"

# Add the line to /etc/fstab
echo 'UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890  /var/www/fotos  xfs  defaults,nofail  0  2' \
  | sudo tee -a /etc/fstab

# ALWAYS VERIFY before rebooting. This command re-reads fstab and mounts whatever is missing.
sudo umount /var/www/fotos
sudo mount -a
df -h /var/www/fotos

The nofail option is not decorative. Without it, if the volume is unavailable at boot the instance hangs during start-up and you will not be able to get in, not even over SSH. It is one of the most frequent ways of "losing" an instance in EC2. Always run sudo mount -a before rebooting to check that the fstab line is correct.

Extending a volume while running, without stopping the shop

In November, with the Christmas campaign, the catalogue photos fill up the 20 GiB. Extending is an operation in two halves: first the volume in AWS, then the file system inside the instance. If you do only the first one, df -h will still be showing 20 GiB and you will think it has not worked.

# Half 1: extend the volume in AWS (the instance keeps running)
aws ec2 modify-volume --volume-id "$VOL_ID" --size 50 \
  --profile mercadofresco-dev --region eu-west-1

# Follow the progress of the optimisation
aws ec2 describe-volumes-modifications --volume-id "$VOL_ID" \
  --query 'VolumesModifications[].{State:ModificationState,Progress:Progress}' \
  --output table \
  --profile mercadofresco-dev --region eu-west-1
# Half 2: inside the instance, extend the partition and the file system
lsblk                                  # the disk already shows 50G, the file system does not

# If the volume has partitions, the partition is extended first:
sudo growpart /dev/nvme1n1 1           # (not applicable if you formatted the whole disk, as here)

# Extend the file system WHILE RUNNING, with the volume mounted:
sudo xfs_growfs -d /var/www/fotos      # for XFS
# sudo resize2fs /dev/nvme1n1          # for ext4

df -h /var/www/fotos                   # now it is right: 50G

Important restrictions:

  • A volume cannot be shrunk. The only route is to create a smaller one, copy the data across and delete the big one.
  • At least 6 hours must pass between two modifications of the same volume.
  • The extension is transparent to the application: there is no need to stop nginx or unmount.

Snapshots: what they are, how they are billed and how they are restored

A snapshot is a point-in-time copy of a volume stored in Amazon S3 (in a space managed by AWS, not in a bucket of yours). It is the basis of backups in EC2 and the answer to MercadoFresco's problem 2.

Three properties to understand properly, because almost everyone misreads them:

  1. They are incremental, but each one is complete. The first snapshot copies every used block. The second copies only the blocks that have changed since the first. Restoring the second, however, gives you back the whole volume, not a differential. AWS manages the references internally.
  2. Deleting an intermediate snapshot is safe. If you delete snapshot 2, AWS keeps the blocks that snapshot 3 still needs. A chain is never corrupted by deleting a link.
  3. They are billed per GB of unique data stored (≈0.05 USD/GB-month), not by the size of the volume. A 50 GiB volume holding 8 GiB of data that barely changes produces very cheap snapshots.
flowchart LR
    S1["Snapshot 1<br/>Monday<br/>8 GiB (complete)"] --> S2["Snapshot 2<br/>Tuesday<br/>+0.3 GiB new"]
    S2 --> S3["Snapshot 3<br/>Wednesday<br/>+0.5 GiB new"]
    S3 --> R["Restore snapshot 3<br/>= complete 8.8 GiB volume"]
    S1 -.-> C["Total billed cost:<br/>8 + 0.3 + 0.5 = 8.8 GiB"]

Creating a snapshot

SNAP_ID=$(aws ec2 create-snapshot \
  --volume-id "$VOL_ID" \
  --description "MercadoFresco catalogue photos - before the Christmas campaign" \
  --tag-specifications 'ResourceType=snapshot,Tags=[
      {Key=Name,Value=snap-mercadofresco-fotos},
      {Key=Proyecto,Value=mercadofresco},
      {Key=Entorno,Value=desarrollo},
      {Key=Componente,Value=catalogo},
      {Key=Propietario,Value=luis},
      {Key=CentroCoste,Value=operaciones}]' \
  --query 'SnapshotId' --output text \
  --profile mercadofresco-dev --region eu-west-1)

# Wait for it to finish (it can take minutes the first time)
aws ec2 wait snapshot-completed --snapshot-ids "$SNAP_ID" \
  --profile mercadofresco-dev --region eu-west-1
echo "Snapshot $SNAP_ID completed"

Data consistency. A snapshot captures what is on the disk, not what the application holds in memory. For static files such as the photos that is enough. For a database you have to freeze the file system or, far better, use the engine's native backups —which is exactly what RDS does and what we will see in lesson 02-04.

Restoring

A snapshot is not restored over the original volume: it creates a new one. This is the mechanism that lets you move data between Availability Zones, something an EBS volume cannot do on its own.

# Create a NEW volume in the OTHER AZ from the snapshot
NEW_VOL=$(aws ec2 create-volume \
  --snapshot-id "$SNAP_ID" \
  --availability-zone eu-west-1b \
  --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[
      {Key=Name,Value=mercadofresco-fotos-restaurado},
      {Key=Proyecto,Value=mercadofresco},
      {Key=Entorno,Value=pruebas},
      {Key=Componente,Value=catalogo},
      {Key=Propietario,Value=luis},
      {Key=CentroCoste,Value=operaciones}]' \
  --query 'VolumeId' --output text \
  --profile mercadofresco-dev --region eu-west-1)

Copying to another region (disaster recovery)

# Copy the snapshot from Ireland to Frankfurt. Note: --source-region is the SOURCE
# and --region is the DESTINATION where the copy is run.
aws ec2 copy-snapshot \
  --source-region eu-west-1 \
  --source-snapshot-id "$SNAP_ID" \
  --description "DR copy of MercadoFresco photos" \
  --encrypted \
  --profile mercadofresco-dev --region eu-central-1

Copying snapshots between regions costs money in data transfer and in storage at the destination, but it is the real protection against the failure of an entire region.

Data Lifecycle Manager: the end of problem 2

Up to now MercadoFresco's backups were "Marta plugs in the external drive when she remembers": no schedule, no verification and no copy outside the office. That is problem 2. A manual snapshot like the one above does not fix it, because it also depends on somebody remembering.

Amazon Data Lifecycle Manager (DLM) runs snapshots according to a policy: which resources it applies to (selected by tags), how often, how many to keep and where to copy them. This is where you see why MercadoFresco's tagging scheme was not bureaucracy: it is the policy's selection mechanism.

First, the role DLM needs in order to act on your behalf (IAM is covered in 04-01):

aws dlm create-default-role --resource-type snapshot \
  --profile mercadofresco-dev --region eu-west-1

And the policy, saved in politica-dlm.json:

{
  "ResourceTypes": ["VOLUME"],
  "TargetTags": [
    {"Key": "Proyecto", "Value": "mercadofresco"}
  ],
  "Schedules": [
    {
      "Name": "Diario-7-dias",
      "CreateRule": {
        "CronExpression": "cron(0 2 * * ? *)"
      },
      "RetainRule": {"Count": 7},
      "CopyTags": true,
      "TagsToAdd": [
        {"Key": "TipoCopia", "Value": "diaria-automatica"}
      ]
    },
    {
      "Name": "Semanal-4-semanas-con-copia-DR",
      "CreateRule": {
        "CronExpression": "cron(0 3 ? * SUN *)"
      },
      "RetainRule": {"Count": 4},
      "CopyTags": true,
      "CrossRegionCopyRules": [
        {
          "TargetRegion": "eu-central-1",
          "Encrypted": true,
          "RetainRule": {"Interval": 4, "IntervalUnit": "WEEKS"}
        }
      ]
    }
  ]
}
aws dlm create-lifecycle-policy \
  --description "Automated backups of MercadoFresco volumes" \
  --state ENABLED \
  --execution-role-arn arn:aws:iam::111122223333:role/AWSDataLifecycleManagerDefaultRole \
  --policy-details file://politica-dlm.json \
  --profile mercadofresco-dev --region eu-west-1

What this policy does exactly, line by line:

  • TargetTags: it applies to every volume tagged Proyecto=mercadofresco. Any new volume Luis creates with the right tag automatically joins the backup plan, without anyone having to remember. That is the cultural change.
  • Daily schedule: cron(0 2 * * ? *) = every day at 02:00 UTC. It keeps the last 7; when the eighth is created it deletes the oldest, so the cost stabilises.
  • Weekly schedule: Sundays at 03:00 UTC, keeps 4 and copies each one to eu-central-1 encrypted. One month of history outside the main region.
  • CopyTags: true: the snapshots inherit the volume's tags, so the cost of the backups also ends up attributed to its CentroCoste.

With this, problem 2 is solved as far as disks are concerned:

Before (office server) Now (AWS with DLM)
Who does it Marta, when she remembers Automatic, every day at 02:00
Where it is kept External drive in the same building Managed S3 + a copy in another region
Retention Overwritten 7 daily + 4 weekly, defined
Verification None State visible; tested by restoring
Restore time Hours or days Minutes
Cost The external drive + Marta's time Pennies a month

One honest caveat is missing: an untested backup is not a backup. Marta puts a quarterly restore test in the calendar: create a volume from the latest snapshot, mount it on a test instance and check that the photos are there. The database side of this problem will be closed in lesson 02-04, with RDS's automated backups and PITR.

Volume encryption

EBS encryption is transparent: it encrypts the data at rest, the data in transit between the instance and the volume, and every snapshot derived from it. Nothing has to change in the application and the impact on performance is negligible.

Practical points:

  • An existing volume cannot be encrypted. The procedure is: snapshot → copy of the snapshot with --encrypted → create a new volume from that copy → swap them over.
  • You can turn on encryption by default for the whole region, and it is highly recommended:
aws ec2 enable-ebs-encryption-by-default \
  --profile mercadofresco-dev --region eu-west-1

From then on, every new volume in eu-west-1 is born encrypted even if nobody asks for it.

  • The keys are managed by AWS KMS. You can use the AWS-managed key (aws/ebs, free) or your own key, which gives you control over rotation and over who can decrypt. KMS is lesson 04-02; for now it is enough to know that encryption is turned on with a checkbox.

Instance store: fast, local and ephemeral

Some instance families (the ones with a d in the name, such as m6id.large, and the i and d families) include NVMe disks physically attached to the host server. That is the instance store.

EBS Instance store
Location Network, inside the AZ Physically on the host
Latency Sub-millisecond Lower still, with no network hop
Persistence Survives stop and terminate Lost on stop or terminate
Also lost if… The host hardware fails
Snapshots Yes No
Cost Separate, per GB-month Included in the instance price
Can be detached Yes No

It is ephemeral by design: when you stop the instance, AWS will move it to another physical host when you start it again, and that host has different disks. A reboot does keep the data, because the host does not change; a stop/start does not.

Legitimate uses: caches, temporary files, intermediate data from a computation, build scratch space. It does not apply to MercadoFresco today, but it is worth recognising so that you do not pick a d instance by mistake thinking the included disk is a saving.

Amazon EFS: a file system for several instances

Back to the problem that opened the lesson. The Auto Scaling group brings up 4 instances on Friday. Each one has its own EBS volume. If Luis uploads a new product photo to instance 1, the other three do not see it. And no instance in eu-west-1b can even attach to the photo volume that lives in eu-west-1a.

Amazon EFS solves exactly this: a managed NFS v4.1 file system that is mounted simultaneously on thousands of instances, across several AZs, and that grows and shrinks by itself.

Its distinguishing characteristics:

  • Genuinely elastic: you do not provision a size. You pay for the GB you actually store (≈0.30 USD/GB-month in Standard, some 3.4 times more expensive than EBS gp3).
  • Regional: it is reached from any Availability Zone in the region through mount targets, one per subnet.
  • Shared: concurrent reads and writes from every mounted instance.
  • POSIX semantics: permissions, owners, links. To the application it is an ordinary directory.

Storage classes

Class Description Relative cost When it is used
Standard Data replicated across several AZs Active data that must survive an AZ going down
Standard-IA Infrequent access, multi-AZ ~0.15× + an access charge Files unopened for more than 30 days
One Zone A single AZ ~0.53× Reproducible data, development environments
One Zone-IA One AZ, infrequent access ~0.08× + an access charge Cheap, non-critical archive

Lifecycle management is automatic: you configure "move anything untouched for 30 days to IA" and EFS does it on its own. It fits MercadoFresco's catalogue perfectly, because the photos of seasonal products stop being looked at outside their season.

Performance modes and throughput modes

Performance mode Latency IOPS When
General Purpose Lower Up to 35,000 Default: web servers, CMSs, home directories
Max I/O Higher Practically unlimited Hundreds of instances in parallel, massive analysis
Throughput mode How it works When
Elastic (recommended) Scales automatically with demand, you pay per use Variable workloads: MercadoFresco's case
Bursting Throughput depends on the size stored, with credits Large systems with proportional load
Provisioned You buy a fixed throughput independent of size Little data but a lot of traffic

In practice: General Purpose + Elastic unless you measure that it is not enough.

Mounting EFS on the storefront instances

# 1. Create the file system
EFS_ID=$(aws efs create-file-system \
  --performance-mode generalPurpose \
  --throughput-mode elastic \
  --encrypted \
  --tags Key=Name,Value=efs-mercadofresco-fotos \
         Key=Proyecto,Value=mercadofresco \
         Key=Entorno,Value=desarrollo \
         Key=Componente,Value=catalogo \
         Key=Propietario,Value=luis \
         Key=CentroCoste,Value=operaciones \
  --query 'FileSystemId' --output text \
  --profile mercadofresco-dev --region eu-west-1)

echo "EFS created: $EFS_ID"

# 2. One mount target PER SUBNET (one per AZ). Without this, the instances
#    in that AZ cannot reach the file system.
aws efs create-mount-target --file-system-id "$EFS_ID" \
  --subnet-id subnet-aaa11111 --security-groups sg-efs-mercadofresco \
  --profile mercadofresco-dev --region eu-west-1

aws efs create-mount-target --file-system-id "$EFS_ID" \
  --subnet-id subnet-bbb22222 --security-groups sg-efs-mercadofresco \
  --profile mercadofresco-dev --region eu-west-1

The sg-efs-mercadofresco security group must allow port 2049 (NFS) from the storefront instances' security group. It is the number one failure when mounting EFS and it is explained in depth in lesson 03-02.

Inside the instance:

# Install the client AWS recommends (it handles encryption in transit and retries)
sudo dnf install -y amazon-efs-utils

sudo mkdir -p /var/www/fotos-compartidas

# Mount with encryption in transit (-o tls)
sudo mount -t efs -o tls "$EFS_ID":/ /var/www/fotos-compartidas

# Permanent in /etc/fstab
echo "$EFS_ID:/ /var/www/fotos-compartidas efs _netdev,tls,noresvport 0 0" \
  | sudo tee -a /etc/fstab

sudo mount -a
df -h /var/www/fotos-compartidas
# Filesystem      Size  Used Avail Use% Mounted on
# 127.0.0.1:/     8.0E  0    8.0E   0% /var/www/fotos-compartidas

That size of "8.0E" (8 exabytes) is the way NFS has of saying "unlimited": there is no size to provision at all.

The corresponding line would go into the user data of the launch template we created in 02-01, so that every new ASG instance mounts the photos automatically at start-up. With that, the diagram from the beginning stops having the problem:

flowchart TD
    subgraph AZ1["eu-west-1a"]
        I1["tienda-01"]
        I2["tienda-02"]
    end
    subgraph AZ2["eu-west-1b"]
        I3["tienda-03"]
        I4["tienda-04"]
    end
    EFS["EFS efs-mercadofresco-fotos<br/>/var/www/fotos-compartidas<br/>regional, elastic"]
    I1 --> EFS
    I2 --> EFS
    I3 --> EFS
    I4 --> EFS

Cost warning and technical honesty. EFS solves the problem without touching MercadoFresco's code, and that is why it is the right answer when you are migrating a legacy application in a hurry. But for serving product photos to browsers it is expensive (≈3.4× EBS, ≈13× S3 Standard), it still consumes CPU and bandwidth on the instances, and it makes no use of any content delivery network. In lesson 02-03 we will move /var/www/fotos to S3, which is MercadoFresco's definitive decision, and in 03-04 we will put CloudFront in front of it.

Final table: EBS, EFS, instance store and S3

EBS EFS Instance store S3
Model Block File (NFS) Block Object
Scope One AZ Regional (multi-AZ) One physical host Regional, multi-AZ
Simultaneous instances 1 (multi-attach on io2: up to 16) Thousands 1 N/A (HTTPS access)
Persists on terminate Yes (configurable) Yes No Yes
Capacity Provisioned, up to 16 TiB Elastic, unlimited Fixed by the type Unlimited
Billed by Provisioned GB Stored GB Included in the instance Stored GB + requests + egress
Indicative cost GB-month 0.088 USD (gp3) 0.30 USD (Standard) 0 USD 0.023 USD (Standard)
Latency < 1 ms Low ms The lowest Tens of ms
Backups Snapshots + DLM AWS Backup, replication None Versioning + replication
Case at MercadoFresco System disk, database data Sharing files between instances without touching the code Not used Product photos, backups, static files (02-03)

Common Mistakes and Tips

  • Creating the volume in a different AZ from the instance. The attach-volume will fail. Always check the instance's Placement.AvailabilityZone before creating the disk.
  • Forgetting /etc/fstab and discovering after a reboot that the shop is serving an empty directory. And the other way round: putting a bad line in fstab without nofail and leaving the instance unable to start. Run sudo mount -a before every reboot.
  • Extending the volume in AWS and not extending the file system. df -h does not change and it looks as if the extension had not worked. growpart and/or xfs_growfs/resize2fs are missing.
  • Believing a volume can be shrunk. It cannot. Provision it sensibly and extend it when you need to.
  • Orphaned volumes. When instances are terminated, volumes are left in the available state, still being paid for and looked at by nobody. Review this every month:
    aws ec2 describe-volumes --filters Name=status,Values=available \\
      --query 'Volumes[].{ID:VolumeId,GB:Size,Created:CreateTime}' --output table \\
      --profile mercadofresco-dev --region eu-west-1
    
  • Snapshots that pile up forever. Hundreds of manual snapshots with no retention policy are a constant drip on the bill. That is why everything must go through DLM.
  • Trusting a backup that has never been restored. Test the restore every quarter.
  • Carrying on with gp2. Migrating to gp3 is free, done while running and cheaper.
  • Mounting EFS without opening port 2049. The mount just hangs with no clear message. Check the EFS security group.
  • Using EFS for everything out of convenience. It is the expensive option. Use it when you need shared POSIX semantics; for files served over HTTP, S3.
  • Not encrypting from the start. It costs the same and it cannot be added later without recreating the volume. Turn on enable-ebs-encryption-by-default in the region and forget about it.

Exercises

Exercise 1: choosing the volume and calculating the cost

MercadoFresco's orders database needs 200 GiB of space and, measured during the Friday peak, 5,000 sustained IOPS with a throughput of 200 MB/s.

  1. What type of volume would you choose, and with exactly what configuration?
  2. Work out the approximate monthly cost with the prices from the table (0.088 USD/GB-month, 0.006 USD per extra IOPS, 0.048 USD per extra MB/s).
  3. How much would the same thing cost with gp2? And why would gp2 also force another decision?

Exercise 2: designing the complete backup policy

Marta wants a backup policy that meets these business requirements:

  • A backup every 12 hours of every production volume, keeping 14 days.
  • A monthly backup kept for 12 months and replicated to eu-central-1.
  • Development volumes only need a daily backup with 3 days of retention.
  • The backups must be attributable to the right cost centre on the bill.

Explain how you would structure the DLM policies and which tags make each selection possible.

Exercise 3: choosing the storage for four cases

For each MercadoFresco situation, choose between EBS, EFS, instance store or S3, and justify it in two sentences:

  • A) The boot disk of the storefront instances.
  • B) A /var/www/uploads directory where customers upload photos of problems with their order, and which the 4 ASG instances must all see alike, without being able to touch the PHP code.
  • C) The temporary files of the overnight process that recompresses 40,000 catalogue photos.
  • D) The 40,000 catalogue photos served to customers' browsers.

Solutions

Solution 1.

  1. gp3, 200 GiB, with 5,000 IOPS and 200 MB/s provisioned. io2 is not needed: gp3's limit is 16,000 IOPS and 1,000 MB/s, well above what is asked for, and io2 is a good deal more expensive. One extra detail: you have to check that the instance supports that throughput towards EBS (a t3.micro does not; an EBS-optimised type of a suitable size would be needed).

  2. gp3 cost:

    Storage:          200 GB × 0.088          = 17.60 USD
    Extra IOPS:       (5,000 − 3,000) × 0.006 = 12.00 USD
    Extra throughput: (200 − 125) × 0.048     =  3.60 USD
    ------------------------------------------------------
    Total                                     ≈ 33.20 USD/month
    
  3. With gp2, performance is tied to size at a rate of 3 IOPS/GiB, so for 5,000 IOPS you would need 1,667 GiB:

    1,667 GB × 0.11 ≈ 183 USD/month
    

    More than 5 times the cost, and with the forced decision of provisioning 1,667 GiB to use 200: you pay for 1,467 GiB of empty space just to buy speed. This is the definitive argument for not using gp2 on anything new.

Solution 2.

You create three DLM policies, all of them selecting by tags, which is what makes it work with no maintenance:

Policy TargetTags Schedule Retention Cross-region copy
produccion-12h Proyecto=mercadofresco + Entorno=produccion cron(0 */12 * * ? *) 28 snapshots (= 14 days at 2/day) No
produccion-mensual Proyecto=mercadofresco + Entorno=produccion cron(0 4 1 * ? *) (day 1 at 04:00) 12 Yes, to eu-central-1, encrypted, 12 months
desarrollo-diaria Proyecto=mercadofresco + Entorno=desarrollo cron(0 2 * * ? *) 3 No

Key design points:

  • TargetTags with two tags acts as a logical AND: only volumes carrying both get in. That is why the project's mandatory tagging scheme is what makes the automation viable: a new, correctly tagged volume ends up protected without anyone stepping in.
  • CopyTags: true propagates CentroCoste, Componente and Propietario to every snapshot, so the cost of the backups shows up correctly split in Cost Explorer (lesson 11-03).
  • Retention is expressed as a number of snapshots, not in days: you have to translate the frequency. At 2 backups a day, 14 days is 28 snapshots.
  • The first two can be merged into a single policy with two Schedules (DLM allows up to four per policy); keeping them separate is just as valid and more readable.

Solution 3.

Case Choice Rationale
A) Boot disk EBS gp3 The root volume has to be an SSD block device and it must persist for as long as the instance exists. It is the only possible candidate.
B) Shared /var/www/uploads EFS Several instances across several AZs have to read and write the same files, and the "without touching the PHP code" constraint rules out S3: the application will carry on using file system paths. EBS is no good because it is not shared between AZs.
C) Temporary files of the overnight process Instance store (or EBS if the chosen type has none) It is reproducible, throwaway data: the volatility is not a drawback, and the local disk gives maximum speed with no extra cost and no snapshots to manage.
D) Catalogue photos served to customers S3 Objects written once and read many times, served over HTTPS. It is around 13 times cheaper than EFS, it has eleven nines of durability, it scales without limit and it goes behind CloudFront. We will see it in 02-03 and 03-04.

Conclusion

You have learned to tell apart the three storage models —block, file and object— and to recognise from the shape of the problem which one applies in each case: if the software expects a disk, block; if several servers share files with POSIX paths, file; if the piece of data is a whole file served over HTTP, object.

In EBS you know that a volume lives in a single AZ, that you pay per provisioned GB and not per used GB, and that it is extended but never shrunk. You know how to choose between gp3, gp2, io2, st1 and sc1, and you have worked out with real numbers why gp3 is the default option: by decoupling size, IOPS and throughput it avoids the gp2 absurdity of buying 1,667 GiB in order to get 5,000 IOPS. You have gone through the full life of a data disk: create-volume, attach-volume, lsblk, mkfs, mount, the /etc/fstab line by UUID and with nofail, and the extension while running in its two halves, modify-volume in AWS and xfs_growfs inside the machine.

You have understood snapshots properly: incremental in how they are billed but complete when restored, safe to delete at any point in the chain, able to move data between AZs and between regions. And with Data Lifecycle Manager you have turned that capability into a backup plan that runs on its own, selects by tags and keeps exactly what it should: seven daily, four weekly replicated to eu-central-1. With that, the disk side of MercadoFresco's problem 2 —the backups that depended on somebody remembering— is closed, with the warning that a backup without a restore test does not count as a backup.

You also know about EBS's transparent encryption and why it is turned on by default for the region, and you know what the instance store is and why its speed does not make up for its volatility except for disposable data. Finally, EFS has given you the first real solution to the problem Auto Scaling left open: a regional, elastic NFS file system mounted at the same time on Friday's four instances, with its storage classes and its performance modes.

But we close with a pending and deliberate decision. EFS solves the photo problem without touching the code, and that is why it is the right answer when you are in a hurry. It is not the right answer in the long run: it is around thirteen times more expensive than the alternative, it forces the image traffic through the instances and it makes no use of AWS's global scale. In lesson 02-03, "Amazon S3", we will really migrate /var/www/fotos with aws s3 sync, we will meet the eleven nines of durability, the storage classes with lifecycle rules that make old catalogue photos cheaper, the versioning that protects you from accidental deletions, the presigned URLs with which Sara will download her reports without credentials, and the events that later on will trigger a Lambda function to generate the thumbnails.

© Copyright 2026. All rights reserved