Emmanuel Akinfulubi

Cloud fundamentals all 3 written

IaaS, PaaS, SaaS and FaaS; regions and blast radius; the Shared Responsibility Model. Vendor-neutral, before any one vendor.

15 IaaS, PaaS, SaaS, FaaS: what you manage vs what they manage

Every layer of a running system -- hardware, virtualization, OS, runtime, application code, data -- has to be managed by someone. IaaS, PaaS, SaaS and FaaS are not four different technologies; they are four different places to draw the same line. Moving up the list trades control for less operational work, and neither direction is "better" -- it is a decision about which parts of the stack your team wants to own.

The same layers, four different lines

IaaS Infrastructure as a Service

You get a virtual machine. Everything above the hypervisor is yours.

  • Provider manages: physical hardware, the hypervisor, the network fabric
  • You manage: the OS, patching, the runtime, the application, scaling it yourself
  • Example: an EC2 instance
PaaS Platform as a Service

You get a place to put code. The OS and runtime are the provider's problem.

  • Provider manages: OS, patching, the runtime, autoscaling infrastructure
  • You manage: your application code, its configuration, its data
  • Example: AWS Elastic Beanstalk, Heroku
SaaS Software as a Service

You get the finished application. Nothing below the login screen is yours.

  • Provider manages: everything -- infrastructure, runtime, and the application itself
  • You manage: your own data and configuration inside it
  • Example: Gmail, Salesforce, GitHub itself
FaaS Functions as a Service

You get to run one function, on demand. No server, ever, is visible to you.

  • Provider manages: everything IaaS and PaaS cover, plus starting and stopping your code per request
  • You manage: one function's code, and what it is triggered by
  • Example: AWS Lambda -- covered properly in Part 8

Drawing part of the line with a real command, not a diagram

"Who manages the OS" sounds abstract until you actually query a real IaaS resource and see that the OS is a choice you made, not a given -- and that nothing about the service tells the provider what is running inside it.

IaaS: you chose the OS image, so patching it is your job

ran at build time
# AWS_ENDPOINT_URL is already set, pointing at this build's own moto instance --
# every aws command below is real aws CLI, talking to a real (local, fake) AWS.
echo "an IaaS resource: a real EC2 instance, launched against a chosen AMI (a specific OS image):"
instance=$(aws ec2 run-instances --image-id ami-03cf127a \
  --instance-type t2.micro --count 1 --output json)
instance_id=$(echo "$instance" | grep -o '"InstanceId": "[^"]*"' | head -1 | cut -d'"' -f4)
echo "launched: $instance_id"

echo
echo "what AWS itself reports for it -- it knows the AMI ID, NOT what OS is inside it:"
aws ec2 describe-instances --instance-ids "$instance_id" \
  --query 'Reservations[0].Instances[0].{ImageId:ImageId,InstanceType:InstanceType,State:State.Name}' \
  --output json

check "an EC2 instance really was created" "$([ -n "$instance_id" ] && echo yes)" "yes"
check "the instance ID has the real AWS i-xxxx shape" "$(echo "$instance_id" | grep -Ec '^i-[0-9a-f]+$')" "1"

What it printed, when the build ran it

an IaaS resource: a real EC2 instance, launched against a chosen AMI (a specific OS image):
launched: i-19cb0e5afd8ef103f

what AWS itself reports for it -- it knows the AMI ID, NOT what OS is inside it:
{
    "ImageId": "ami-03cf127a",
    "InstanceType": "t2.micro",
    "State": "running"
}
ok   an EC2 instance really was created
ok   the instance ID has the real AWS i-xxxx shape

What that command just proved. AWS's own API answer for the instance contains an image ID and an instance type -- infrastructure facts -- and nothing about what is running inside it, because AWS genuinely does not manage that. If that instance's OS has an unpatched vulnerability next month, that is not a ticket you can raise with AWS support; it is EC2's whole business model, working exactly as designed.

Contrasting with a service where that question does not even apply

S3 is not IaaS. There is no instance to patch, no OS to choose, because there is no virtual machine anywhere in what you are renting -- you are renting storage, addressed by a key, and the "server" question is meaningless for it in a way it is not for EC2.

Where the IaaS question ('which OS') stops making sense at all

ran at build time
aws s3 mb s3://course-service-model-bucket > /dev/null
echo "hello" | aws s3 cp - s3://course-service-model-bucket/file.txt > /dev/null

echo "asking S3 for anything resembling 'what OS, what instance type' about this bucket:"
aws s3api get-bucket-location --bucket course-service-model-bucket

echo
echo "there is no 'instance-type' equivalent for a bucket at all -- the question genuinely"
echo "does not apply, because there is no server-shaped thing being rented here."

readback=$(aws s3 cp s3://course-service-model-bucket/file.txt -)
echo
echo "the object still round-trips correctly, with no server ever named or chosen:"
echo "$readback"

check "the object comes back exactly as written, through pure storage, not a machine" \
  "$readback" "hello"

What it printed, when the build ran it

asking S3 for anything resembling 'what OS, what instance type' about this bucket:
{
    "LocationConstraint": null
}

there is no 'instance-type' equivalent for a bucket at all -- the question genuinely
does not apply, because there is no server-shaped thing being rented here.

the object still round-trips correctly, with no server ever named or chosen:
hello
ok   the object comes back exactly as written, through pure storage, not a machine

Choosing a model is choosing what you want to be paged about

  • IaaS: paged for OS-level issues -- disk full, kernel panic, an unpatched CVE in a library you installed.
  • PaaS: paged for application-level issues only -- the platform itself failing is the provider's incident, not yours.
  • SaaS: never paged for the service itself; only for your own configuration or data inside it.
  • FaaS: paged for your function's logic and its triggers, and for nothing resembling "the server," because there genuinely is no persistent one from your side of the API.

This is the practical test for "which model is this, really": read the responsibilities in a real incident, not the marketing name of the product.

16 Regions, Availability Zones and blast radius

A region is a real geographic area (Ireland, Ohio, Singapore). An Availability Zone is one or more physically separate data centres inside that region, with independent power and networking, close enough for low-latency replication between them but far enough apart that one burning down does not take out the others. Both of those facts are queryable, not just asserted -- this lesson queries them.

The real list, from AWS's own API

Every region and every AZ used anywhere in this course so far is a real entry in AWS's actual, current list -- fetched here, not typed from memory.

Real regions and Availability Zones, queried directly

ran at build time
echo "a sample of AWS's actual current regions:"
aws ec2 describe-regions --query 'Regions[].RegionName' --output text | tr '\t' '\n' | sort | head -8

region_count=$(aws ec2 describe-regions --query 'length(Regions)' --output text)
echo
echo "total regions AWS reports right now: $region_count"

echo
echo "the real Availability Zones inside us-east-1:"
aws ec2 describe-availability-zones --region us-east-1 \
  --query 'AvailabilityZones[].ZoneName' --output text | tr '\t' '\n'

az_count=$(aws ec2 describe-availability-zones --region us-east-1 --query 'length(AvailabilityZones)' --output text)

check "AWS reports more than a handful of regions" "$([ "$region_count" -gt 10 ] && echo yes)" "yes"
check "us-east-1 alone has multiple real, distinct AZs" "$([ "$az_count" -ge 3 ] && echo yes)" "yes"

What it printed, when the build ran it

a sample of AWS's actual current regions:
af-south-1
ap-east-1
ap-east-2
ap-northeast-1
ap-northeast-2
ap-northeast-3
ap-south-1
ap-south-2

total regions AWS reports right now: 38

the real Availability Zones inside us-east-1:
us-east-1a
us-east-1b
us-east-1c
us-east-1d
us-east-1e
us-east-1f
ok   AWS reports more than a handful of regions
ok   us-east-1 alone has multiple real, distinct AZs

Blast radius: what "only in one AZ" actually costs you

Launch a resource without specifying a zone, and the provider picks one for you -- silently making a single-AZ decision on your behalf unless you actively spread things out. The panel below launches two EC2 instances the naive way and shows they can land in the same zone purely by default behaviour.

Launching without choosing a zone: the provider decides for you

ran at build time
instance_a=$(aws ec2 run-instances --image-id ami-03cf127a --instance-type t2.micro --count 1 --output json)
instance_b=$(aws ec2 run-instances --image-id ami-03cf127a --instance-type t2.micro --count 1 --output json)

id_a=$(echo "$instance_a" | grep -o '"InstanceId": "[^"]*"' | head -1 | cut -d'"' -f4)
id_b=$(echo "$instance_b" | grep -o '"InstanceId": "[^"]*"' | head -1 | cut -d'"' -f4)

az_a=$(aws ec2 describe-instances --instance-ids "$id_a" \
  --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)
az_b=$(aws ec2 describe-instances --instance-ids "$id_b" \
  --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)

echo "instance A landed in: $az_a"
echo "instance B landed in: $az_b"
echo
echo "neither launch specified a zone -- whatever AZ they land in is whatever the"
echo "provider's default placement decided, not a deliberate choice either way."

check "both instances really were created" \
  "$([ -n "$id_a" ] && [ -n "$id_b" ] && echo yes)" "yes"
check "each instance was actually placed in a real, named AZ" \
  "$(echo "$az_a $az_b" | grep -Ec '^[a-z]+-[a-z]+-[0-9][a-z] [a-z]+-[a-z]+-[0-9][a-z]$')" "1"

What it printed, when the build ran it

instance A landed in: us-east-1a
instance B landed in: us-east-1a

neither launch specified a zone -- whatever AZ they land in is whatever the
provider's default placement decided, not a deliberate choice either way.
ok   both instances really were created
ok   each instance was actually placed in a real, named AZ

Spreading deliberately across zones -- the actual fix

Naming the AvailabilityZone explicitly on each launch is the whole mechanism. There is no separate "high availability" flag; it is just placing resources in different real zones on purpose, so no single data centre failure takes everything down at once.

Choosing distinct AZs on purpose, and confirming the placement stuck

ran at build time
zones=$(aws ec2 describe-availability-zones --region us-east-1 --query 'AvailabilityZones[].ZoneName' --output text)
zone_array=($zones)
zone1="${zone_array[0]}"
zone2="${zone_array[1]}"

echo "deliberately placing one instance in $zone1 and another in $zone2:"

deliberate_a=$(aws ec2 run-instances --image-id ami-03cf127a --instance-type t2.micro \
  --count 1 --placement AvailabilityZone="$zone1" --output json)
deliberate_b=$(aws ec2 run-instances --image-id ami-03cf127a --instance-type t2.micro \
  --count 1 --placement AvailabilityZone="$zone2" --output json)

id_a=$(echo "$deliberate_a" | grep -o '"InstanceId": "[^"]*"' | head -1 | cut -d'"' -f4)
id_b=$(echo "$deliberate_b" | grep -o '"InstanceId": "[^"]*"' | head -1 | cut -d'"' -f4)

placed_a=$(aws ec2 describe-instances --instance-ids "$id_a" \
  --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)
placed_b=$(aws ec2 describe-instances --instance-ids "$id_b" \
  --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)

echo "instance A confirmed in: $placed_a"
echo "instance B confirmed in: $placed_b"
echo
echo "if $placed_a's data centre lost power right now, $placed_b is a physically"
echo "separate facility, on separate power -- unaffected by definition, not by luck."

check "the first instance landed exactly where it was told to" "$placed_a" "$zone1"
check "the second instance landed in a genuinely different zone" "$placed_b" "$zone2"
check "the two zones really are different from each other" "$([ "$zone1" != "$zone2" ] && echo yes)" "yes"

What it printed, when the build ran it

deliberately placing one instance in us-east-1a and another in us-east-1b:
instance A confirmed in: us-east-1a
instance B confirmed in: us-east-1b

if us-east-1a's data centre lost power right now, us-east-1b is a physically
separate facility, on separate power -- unaffected by definition, not by luck.
ok   the first instance landed exactly where it was told to
ok   the second instance landed in a genuinely different zone
ok   the two zones really are different from each other

A region is not automatically a blast-radius boundary either. Some AWS services (IAM, Route 53, S3's control plane) are global or region-spanning by design; others are strictly regional. Multi-AZ protects against one data centre failing. Multi-region protects against an entire region having a bad day -- a much rarer, much more expensive thing to defend against, and rarely worth it for anything but the most critical systems.

17 The Shared Responsibility Model

AWS is responsible for the security of the cloud: the physical data centres, the hypervisor, the network fabric between them. You are responsible for security in the cloud: how you configure what AWS gives you. The line moves depending on the service (less is yours for a managed database than for a raw EC2 instance), but for the services this course covers, the split below is where it actually falls -- checked against a real, running setting, not just stated.

A bucket that defaults to no encryption -- because that default is your call

AWS did not decide your data needs encryption at rest; it gave you the switch and waits. Before that switch is touched, asking for the bucket's encryption setting fails -- not with "off," but with "not configured at all."

Encryption at rest: not the default, and not automatic

ran at build time
aws s3api create-bucket --bucket encryption-test > /dev/null

echo "asking for this brand-new bucket's encryption setting, before touching anything:"
before=$(aws s3api get-bucket-encryption --bucket encryption-test 2>&1)
before_exit=$?
echo "$before"
echo "(exit status: $before_exit -- a real error, not a quiet 'disabled')"

echo
echo "turning it on -- this half of the model is entirely the customer's responsibility:"
aws s3api put-bucket-encryption --bucket encryption-test \
  --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

after=$(aws s3api get-bucket-encryption --bucket encryption-test)
echo "$after"

check "before any configuration, there is no default encryption to find" \
  "$([ "$before_exit" -ne 0 ] && echo yes)" "yes"
check "after explicitly configuring it, AES256 encryption is confirmed active" \
  "$(echo "$after" | grep -c 'AES256')" "1"

What it printed, when the build ran it

asking for this brand-new bucket's encryption setting, before touching anything:

An error occurred (ServerSideEncryptionConfigurationNotFoundError) when calling the GetBucketEncryption operation: The server side encryption configuration was not found
(exit status: 255 -- a real error, not a quiet 'disabled')

turning it on -- this half of the model is entirely the customer's responsibility:
{
    "ServerSideEncryptionConfiguration": {
        "Rules": [
            {
                "ApplyServerSideEncryptionByDefault": {
                    "SSEAlgorithm": "AES256"
                },
                "BucketKeyEnabled": false
            }
        ]
    }
}
ok   before any configuration, there is no default encryption to find
ok   after explicitly configuring it, AES256 encryption is confirmed active

What AWS actually guarantees here, on its side of the line. The physical drives S3 stores your object on, the durability across multiple facilities, and the infrastructure that would encrypt it correctly once told to -- all AWS's job, done whether or not you ever look at it. Whether that switch gets flipped at all is completely outside AWS's side of the line.

Public access: blocked by a setting, not by a wall

The single most common real-world cloud data breach is not a sophisticated attack -- it is a bucket left reachable from the public internet because nobody explicitly blocked it. AWS's answer is a setting with four parts, all of which have to be turned on, by the customer, to matter.

Public access blocking: four settings, all the customer's to set

ran at build time
aws s3api create-bucket --bucket public-block-test > /dev/null

echo "turning on every part of S3's public access block, explicitly:"
aws s3api put-public-access-block --bucket public-block-test \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

echo
echo "confirming all four are really on, not assumed:"
block_config=$(aws s3api get-public-access-block --bucket public-block-test)
echo "$block_config"

check "all four public-access-blocking settings are confirmed on" \
  "$(echo "$block_config" | grep -c 'true')" "4"

What it printed, when the build ran it

turning on every part of S3's public access block, explicitly:

confirming all four are really on, not assumed:
{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": true,
        "IgnorePublicAcls": true,
        "BlockPublicPolicy": true,
        "RestrictPublicBuckets": true
    }
}
ok   all four public-access-blocking settings are confirmed on

This is not a hypothetical risk. Search "S3 bucket exposed" and the result is years of real incidents at real companies -- not a failure of AWS's infrastructure, but of a setting exactly like the one above never being turned on. The Shared Responsibility Model is not a legal disclaimer; it is a precise description of which failures are yours to prevent.

Where the line moves, by service

The split is not identical for every service -- it shifts with how much AWS manages, which is the same line Part 5's first lesson (service models) already drew.

  • EC2 (IaaS): AWS secures the hypervisor and physical hardware. You patch the OS, configure the security group, and manage everything running on it.
  • S3 (managed storage): AWS secures the storage infrastructure and durability. You configure encryption, public access, and bucket policies -- exactly the two settings just demonstrated.
  • RDS (managed database): AWS patches the database engine itself and manages the underlying host. You manage access control, network placement, and your own schema-level permissions.
  • Lambda (FaaS): AWS manages everything except your function's own code and the permissions you grant it -- the least "yours" of any service in this list.

The single question that locates the line for any service: "what did I have to explicitly configure, versus what came already correct." Everything in the second category is AWS's responsibility. Everything in the first is yours -- and Part 13 (Security) returns to this exact idea with real IAM policy evaluation.

Newsletter

New writing, when it lands.

Occasional notes on shipping software with AI agents. No schedule, no filler, unsubscribe in one click.