Until now, every piece of Kilometre Zero has run on specific machines: the three Cassandra nodes that Ansible configured in 07-05, the Kafka brokers that are restarted one at a time while respecting the ISR, the Patroni cluster with its etcd, the MinIO with its disks, the Kubernetes cluster on which the six services are deployed. Somebody has bought those machines, installed them in a data centre, fitted them with disks and network, and replaces them when they fail. And when 07-03 settled on a pilot light disaster recovery plan with a "secondary region", it left open the question of where that region is and who operates it. This lesson answers that by changing the premise: the infrastructure becomes an API that a provider offers on demand, with regions and availability zones, virtual networks, load balancers, autoscaling and, above all, managed services that do for us what we have assembled by hand throughout the course. We will look at the service and deployment models, the concepts that change the design (regions and zones, VPCs, elasticity), the mapping between each Kilometre Zero component and its managed equivalent on AWS and GCP, the advantages and challenges (including the one that requires a legal review), infrastructure as code with Terraform, the pillars of a well-architected design, and an introduction to FinOps with the platform's approximate monthly cost. The hands-on part deploys Kilometre Zero on AWS with Terraform and the k8s/ manifests from 07-05. Serverless, edge and CDN are left for 08-04.

Contents

  1. Service models: IaaS, PaaS, SaaS and shared responsibility
  2. Deployment models: public, private, hybrid and multi-cloud
  3. Regions, availability zones and disaster recovery
  4. Cloud networking: VPCs, subnets, load balancers and autoscaling
  5. Managed services: what replaces each piece of Kilometre Zero
  6. Advantages and challenges: elasticity, pay-as-you-go, lock-in, egress and compliance
  7. Infrastructure as code with Terraform
  8. Well-architected design: the pillars
  9. FinOps: tags, reservations, spot, budgets and the cost of Kilometre Zero
  10. Hands-on: Kilometre Zero on AWS with Terraform and EKS
  11. Common Mistakes and Tips
  12. Exercises
  13. Conclusion

  1. Service models: IaaS, PaaS, SaaS and shared responsibility

The cloud is defined by what the provider takes on and what remains the customer's responsibility. That line moves depending on the service model, and knowing where it lies in each case is what prevents both paying for what is not used and assuming that "the cloud takes care of it" when it does not.

Layer Own data centre (what Kilometre Zero has today) IaaS (virtual machines: EC2, Compute Engine) PaaS (platform: RDS, EKS, App Engine, Cloud Run) SaaS (application: managed Keycloak, Confluent Cloud, Datadog) FaaS (functions: Lambda; covered in 08-04)
Building, power, physical network Customer Provider Provider Provider Provider
Hardware, hypervisor Customer Provider Provider Provider Provider
Operating system, patches Customer Customer Provider Provider Provider
Runtime, middleware (PostgreSQL, Kafka, Kubernetes) Customer Customer Provider (installation, patches, failover) Provider Provider
Service configuration (parameters, replicas, schemas) Customer Customer Customer Partial Customer
The application and its code Customer Customer Customer Provider Customer (only the function)
Data: classification, encryption, access, logical backups Customer Customer Customer Customer Customer
Identities and permissions (IAM) Customer Customer Customer Customer Customer

Two rows never change column: data and identities always belong to the customer. That is the shared responsibility model every provider publishes: the provider is accountable for the security of the cloud (nobody gets physical access to the disk, the hypervisor isolates), the customer for security in the cloud (the bucket is not public, the IAM role does not have *, personal data is encrypted and in the right region). A km0-invoices bucket reachable without authentication is the customer's responsibility under any model.

For Kilometre Zero, the natural split is: PaaS for everything that has a managed equivalent (databases, Kafka, Kubernetes, cache, objects), IaaS only where there is none or where the control pays off (the Kubernetes worker nodes are IaaS underneath, but managed as a group), and SaaS for whatever makes no difference (identity, observability if the decision is taken to outsource it).

  1. Deployment models: public, private, hybrid and multi-cloud

Model What it is When it makes sense Hidden cost
Public cloud A provider's resources (AWS, GCP, Azure...) shared between customers, logically isolated Variable workloads, small operations teams, a need for managed services, uncertain growth Lock-in, egress, pay-as-you-go costs that grow with success
Private cloud The same abstraction (API, self-service, elasticity) over your own hardware: OpenStack, VMware, on-premise Kubernetes Strict regulation, latency to in-house systems, a stable volume that amortises the hardware You have to operate the cloud as well as the application; no real elasticity beyond the hardware purchased
Hybrid Part private, part public, connected (VPN or dedicated link) Gradual migration (the strangler approach of 08-01 applied to infrastructure); data that must stay inside with elastic compute outside Latency and cost of the link; two security and observability models
Multi-cloud Workloads spread across several public providers Avoiding dependence on one, exploiting each one's unique services, customer demands The most expensive to operate: two IAMs, two networks, two sets of managed services; egress between clouds; portability forces you to give up what is managed

Kilometre Zero, which today has its own hardware, will move to the public cloud with a hybrid period during the migration (the VPN link between the data centre and the VPC lets Kafka replicate with MirrorMaker and lets Patroni have a replica in the cloud before the cutover). Multi-cloud is explicitly ruled out: with four teams, operating two providers would cost more than the risk it mitigates, and portability is preserved another way, with Kubernetes as the deployment layer and containers for whatever is not managed (section 6).

  1. Regions, availability zones and disaster recovery

A provider organises its infrastructure into regions (a geographical area: Ireland, Frankfurt, Paris...) and, within each region, into several availability zones (AZs): physically separate data centres (kilometres apart, with independent power and network) connected with 1-2 ms latency. This structure maps directly onto the failure domains of 07-03:

  • An AZ is a data-centre failure domain: fire, power cut, building network failure. Spreading replicas across AZs (the three Cassandra nodes in three AZs; the Patroni primary and its synchronous replica in different AZs; the Kafka brokers with broker.rack = AZ so that each partition's replicas are spread out) means that losing an AZ is not a disaster, just a failover. It is what topologySpreadConstraints did in 07-05 with the zone label.
  • A region is a geographical (and regulatory) failure domain: a natural disaster, a failure of a provider's regional service, or a legal change. The secondary region of 07-03 is another region of the same provider, hundreds or thousands of kilometres away, with 10-80 ms latency, which rules out synchronous replication between them.

With that, the three DR strategies of 07-03 take concrete form in the cloud:

Strategy What exists in the secondary region RPO RTO Relative cost How it is done on AWS
Pilot light Replicated data (RDS cross-region read replica, S3 replication, MSK MirrorMaker); a minimal or non-existent EKS cluster; Terraform ready to create the rest Minutes (asynchronous replication) 30-60 min (create nodes, promote the replica, switch DNS) ~15-20% of the primary RDS cross-region read replica; S3 CRR; Route 53 with manual failover
Warm standby Everything deployed at reduced scale (EKS with few nodes, services with 1 replica) receiving data Minutes Minutes (scale up and switch DNS) ~40-50% As above + an active EKS with the HPA ready; Route 53 with health checks
Active-active multi-region Everything at full scale in both, serving traffic simultaneously Seconds or zero (depending on the data) Seconds (DNS or the global load balancer stops sending) ~200% + data complexity Aurora Global Database or multi-DC Cassandra; Route 53 latency routing; the multi-leader design of 03-04

Kilometre Zero keeps the 07-03 decision (pilot light) on cost grounds, with one important difference from the in-house data centre: in the cloud, the secondary region costs nothing while it is empty. Terraform can create the secondary region's infrastructure in 20 minutes from the same code; the only thing that has to exist beforehand is the replicated data. Active-active is left as a next step in 08-05, because it involves resolving write conflicts between regions (03-04) and would change the consistency model for stock.

flowchart TB
    subgraph R1[Region eu-west-1, primary]
        direction TB
        ALB[Application load balancer<br/>ALB, public]
        subgraph AZa[AZ a]
            EKSa[EKS nodes<br/>orders, catalog...]
            RDSa[(RDS primary<br/>km0_inventory)]
            MSKa[MSK broker 1]
            CASa[Cassandra 1]
        end
        subgraph AZb[AZ b]
            EKSb[EKS nodes]
            RDSb[(RDS standby<br/>Multi-AZ, synchronous)]
            MSKb[MSK broker 2]
            CASb[Cassandra 2]
        end
        subgraph AZc[AZ c]
            EKSc[EKS nodes]
            MSKc[MSK broker 3]
            CASc[Cassandra 3]
            REDc[(ElastiCache<br/>replica)]
        end
        S3[(S3 km0-photos<br/>regional, 3+ AZ)]
        ALB --> EKSa & EKSb & EKSc
    end
    subgraph R2[Region eu-central-1, pilot light]
        RDSr[(Read replica<br/>asynchronous)]
        S3r[(Replicated S3)]
        MSKr[MirrorMaker → minimal MSK]
        EKSr[EKS: only defined<br/>in Terraform]
    end
    RDSa -. "asynchronous replication" .-> RDSr
    S3 -. "CRR" .-> S3r
    MSKa -. "MirrorMaker" .-> MSKr
    DNS[Route 53<br/>km0.example] --> ALB
    DNS -. "manual failover" .-> R2

  1. Cloud networking: VPCs, subnets, load balancers and autoscaling

A VPC (Virtual Private Cloud) is an isolated private network inside the region, with its own address range (10.0.0.0/16), divided into subnets, each of which belongs to one AZ. The convention Kilometre Zero adopts, and the usual one, is three tiers per AZ:

Subnet What it contains Access from the internet Outbound to the internet
Public Load balancers, NAT gateway, bastion (if there is one) Yes (they have a public IP and a route to the Internet Gateway) Direct
Private, application EKS nodes with the six services, Kong No Through the NAT gateway (to call the payment gateway, to pull images)
Private, data RDS, MSK, ElastiCache, Cassandra nodes No None (neither inbound nor outbound to the internet)

Security groups are stateful firewalls per resource, and they express the same rules as the Kubernetes network policies of 07-05 but at the VPC layer: RDS only accepts 5432 from the EKS nodes' security group; MSK only 9094 (TLS) from EKS; nothing from outside. Managed services such as S3 are reached through VPC endpoints, which keep traffic to km0-photos from going out to the internet (and avoid the NAT cost).

Managed load balancers replace the Ingress + Kong of 07-05 at its outer layer: an application load balancer (ALB on AWS, HTTP(S) Load Balancer on GCP) terminates TLS with managed, automatically renewed certificates, spreads traffic across AZs, performs health checks and exposes an IP in several AZs. Kong still exists behind it, as the gateway with its plugins (06-05); the load balancer is only the door into the region. The WebSocket of 08-02 requires the load balancer to support Upgrade and to have a long idle timeout (the ALB allows it; a layer-4 network load balancer passes it through without looking).

Autoscaling operates at two levels that must be kept apart: the HPA of 07-05 adds Pods based on CPU or Kafka lag, but Pods need nodes; the cluster autoscaler (Cluster Autoscaler or Karpenter on EKS, node pool autoscaling on GKE) adds and removes virtual machines when there are pending Pods or empty nodes. Underneath, an Auto Scaling Group of virtual machines replaces them if they fail a health check and spreads them across AZs. Grape Harvest Week with k6 (07-06) thus becomes: the HPA scales orders from 3 to 12 Pods → 6 Pods pending for lack of CPU → Karpenter starts 2 nodes in 90 s → the Pods are scheduled. Those 90 s are the reason for keeping headroom (nodes with spare capacity) ahead of an announced campaign, or for scaling on a schedule.

  1. Managed services: what replaces each piece of Kilometre Zero

This table is the mapping between what was built in the course and what the provider offers as a managed service. In every row, what is gained is what the provider takes on (installation, patches, failover, backups, scaling) and what is lost is control and portability.

Kilometre Zero piece Lesson AWS GCP What the provider manages What remains ours
PostgreSQL + Patroni (km0_inventory, km0_analytics) 03-04, 07-03 RDS PostgreSQL Multi-AZ (or Aurora) Cloud SQL with high availability Failover between AZs (~60 s), automatic backups, PITR, patches, read replicas Schema, indexes, parameters, connection pooling, restore tests
Kafka (orders.events, delivery.*) 02-04 MSK (or Confluent Cloud) Confluent Cloud or Pub/Sub (a different model) Brokers, ZooKeeper/KRaft, patches, replicas across AZs, storage scaling Topics, partitions, retention, schemas, consumers
Cassandra (km0_orders) 04-04 Keyspaces (compatible API, serverless) or Cassandra on EC2 Cassandra on GCE or Astra (DataStax) Nodes, compactions, repairs, scaling Data model, consistency levels (Keyspaces does not support all of CQL: lightweight transactions and some types need review)
Redis (cache, rate limiter, pub/sub) 04-05 ElastiCache for Redis (replicas across AZs) Memorystore Failover, patches, replicas Caching strategy, TTLs, keys
MinIO (km0-photos, km0-invoices, km0-backups, km0-audit) 04-03 S3 GCS Durability (11 nines), versioning, lifecycle, cross-region replication Access policies, encryption with our own keys, key structure
HDFS + Spark + Airflow 04-02, 05-03, 05-05 EMR (Spark on S3, no persistent HDFS) + MWAA (Airflow) Dataproc + Cloud Composer Ephemeral clusters, Spark versions, managed Airflow The jobs (daily_sales.py), the DAGs (km0_daily_sales), the partitioning of the lake on S3
Flink (stock_alerts.py) 05-04 Managed Service for Apache Flink Dataflow (Beam model) or Flink on GKE Checkpoints, scaling, versions The job code and its state
Kubernetes 07-05 EKS GKE Control plane (API server, etcd) with an SLA, upgrades Nodes (managed groups), manifests, operators, mesh
Vault 06-04 Secrets Manager + KMS Secret Manager + Cloud KMS Storage, scheduled rotation, auditing Which secrets, who reads them (IAM), integration into the Pods
Keycloak (realm km0) 06-03 Cognito Identity Platform High availability, scaling, federation Realm, clients, roles, flows (with less flexibility than Keycloak)
Prometheus + Grafana + Loki + Tempo 07-01, 07-02 CloudWatch (metrics, logs) + X-Ray, or Amazon Managed Prometheus/Grafana Cloud Monitoring + Cloud Logging + Cloud Trace Storage, retention, alerts Instrumentation (services/common/), dashboards, SLOs
Kong 06-05 Kong on EKS (or managed API Gateway, with fewer plugins) Kong on GKE (or Apigee) If managed: scaling and availability Routes, plugins
Mosquitto (08-02) 08-02 IoT Core (retired at some providers) or EMQX on EKS EMQX/HiveMQ on GKE — Everything, if self-managed
km0-ca certificates, mTLS 06-04 ACM (public) + mesh for mTLS Certificate Manager + mesh Issuing and renewing public certificates Internal mTLS remains the mesh's job

Three notes on this table. First: GCP Pub/Sub is not Kafka: it has no Kafka-style partitions or offsets (although it offers ordering by key and replay), and consumer code with groups and commits changes; if you want Kafka on GCP, it is Confluent Cloud. Second: Keyspaces is CQL-compatible but it is not Cassandra: consistency levels and lightweight transactions have differences that must be verified against the 04-04 repository before migrating. Third: managed does not eliminate operations, it changes them: PostgreSQL is no longer patched by us, but the instance still has to be sized, the maintenance window chosen (RDS restarts to apply major patches), restores tested and connections watched.

  1. Advantages and challenges: elasticity, pay-as-you-go, lock-in, egress and compliance

Advantage What it brings to Kilometre Zero Condition for it to be real
Elasticity Grape Harvest Week (×15) is absorbed with nodes that exist only that week; the rest of the year you pay for 1/15 The application must scale horizontally (Modules 4 and 7 made that possible) and the autoscaling must be tested (07-06)
Pay-as-you-go No upfront investment; an empty secondary region costs nothing The discipline to switch off what is not in use (section 9)
Managed services Patroni, Cassandra repairs, Kafka patches stop being on-call duties for the platform team Accepting less control and some functional differences
Global reach Regions close to new markets; DR in another region with the same API Latency-aware design between regions
Baseline security A certified data centre, encryption at rest by default, granular IAM, integrated auditing (CloudTrail) Configuring it: the shared responsibility of section 1
Challenge What it consists of How Kilometre Zero handles it
Lock-in The more managed, the more provider-specific: Keyspaces instead of Cassandra, Cognito instead of Keycloak, Step Functions (08-04) instead of order_saga.py. Switching providers becomes a months-long project Selective portability: Kubernetes and containers for the services; standard APIs (PostgreSQL, Kafka, S3, Redis, CQL) for the data, so that the managed service can be swapped for a self-managed one; accept lock-in where the benefit is large (RDS, S3) and avoid it where it is small (Cognito versus Keycloak on EKS)
Egress cost Data that leaves the region is charged per GB (inbound is usually free): the photos served to Anna, the replica to the secondary region, traffic between AZs (also charged, though less) A CDN in front of km0-photos (04-03, 08-04); Kafka consumers in the same AZ as their leader broker whenever possible (client.rack); lake and compute in the same region
Cost that grows with success At a stable, high volume, the cloud can cost more than amortised in-house hardware Reservations and spot (section 9); review every year with numbers
Compliance and data residency Personal data (Anna's name, address and position; payment data) is subject to regulation (in the EU, the GDPR) that constrains where it may be stored and processed, who may access it (including the provider) and which contracts are needed. Choosing a region outside the EU, or a service that replicates metadata to another region, may breach it Warning: this decision requires a legal review, not just a technical one. Kilometre Zero chooses EU regions (eu-west-1, eu-central-1), encrypts personal data with its own keys in KMS (06-02) and documents the sub-processors; but which data may leave the EU, under which contractual clauses and with which impact assessment is decided by the data protection officer, not the architect
IAM complexity Hundreds of roles and policies; an s3:* on * in a service role is the most common data leak Least privilege per service (IAM Roles for Service Accounts in the hands-on part); automated review (Access Analyzer)
Dependence on a third party Regions fail (it happens several times a year at every provider) Multi-AZ for the everyday, cross-region DR for the exceptional (section 3)

  1. Infrastructure as code with Terraform

In 07-05 a distinction was drawn between configuration tools (Ansible: bringing an existing machine to a given state) and provisioning tools (Terraform: creating the infrastructure itself), and the latter was deferred to this lesson. When the infrastructure is an API, describing it as code is what makes it possible to review it in a pull request, reproduce it in another region and know what exists. Terraform (and its open-source fork OpenTofu) is declarative: the desired state is described in .tf files (the HCL language) and the tool works out and executes the API calls needed to get there, in the order the dependencies dictate.

Four concepts are enough to read and write the hands-on part in section 10:

  1. Providers: plugins that translate resources into API calls (aws, google, kubernetes, helm, kafka...). They are declared with a pinned version.
  2. Resources and data sources: resource "aws_db_instance" "inventory" { ... } creates; data "aws_availability_zones" "available" {} queries. They reference each other (aws_vpc.km0.id), and those references are the dependency graph.
  3. Remote state: Terraform keeps in a state file the correspondence between what is declared and what exists (real ids). That file is critical and shared: it is stored in a remote backend (S3 with versioning, and a DynamoDB table as a lock so that two people do not apply at the same time; it is the same mutual exclusion problem as in 03-03, solved with a managed service), it is encrypted and it is never committed to Git. It contains secrets (initial RDS passwords), which reinforces the point.
  4. Modules: reusable folders of .tf files with input variables and outputs (module "vpc" { source = "terraform-aws-modules/vpc/aws" ... }). Kilometre Zero uses community modules for the VPC and EKS (hundreds of resources not worth writing by hand) and its own modules for each environment.

The cycle is terraform init (downloads providers and modules, connects to the state), terraform plan (computes the diff between what is declared and the state and shows it: + create, ~ modify, - destroy, -/+ replace), and terraform apply (executes it after confirmation). The plan is the most valuable piece: it runs in the pipeline (07-05) and is pasted into the pull request; a -/+ on aws_db_instance.inventory (replacing the database) is what a review must stop. In GitOps, apply is only ever run by the pipeline after the merge, never by a person from their laptop.

  1. Well-architected design: the pillars

Providers publish "well-architected" frameworks with pillars that serve as a checklist. The six AWS pillars (GCP has an equivalent list) match course modules almost one to one:

Pillar The question it asks Where Kilometre Zero has worked on it
Operational excellence Is it operated with code, observed, and are incidents learned from? IaC (07-05, this lesson), observability (07-01/07-02), postmortems and runbooks (07-03), GitOps
Security Identity, least privilege, encryption, auditing, defence in depth? The whole of Module 6; IAM and security groups in this lesson
Reliability Does it survive component, AZ and region failures? Has that been tested? Redundancy and failover (07-03), resilience (07-04), chaos (07-06), Multi-AZ and DR (section 3)
Performance efficiency Are the right resources used for each workload, and are they measured? Storage per workload (Module 4), cache (04-05), autoscaling, p99 SLO (07-01)
Cost optimisation Do you pay for what you use, measure it and review it? FinOps (section 9)
Sustainability Is consumption minimised: switching off idle resources, regions with clean energy, code efficiency? Scaling down, ephemeral Spark clusters, data retention (04-02)

Their practical use is the periodic review: every six months, walk through the framework's questions against the platform and note what is missing. It is an exercise in the final project (08-05).

  1. FinOps: tags, reservations, spot, budgets and the cost of Kilometre Zero

In the cloud, cost is one more metric of the system, and like any metric it has to be measured, attributed and alerted on. The discipline is called FinOps and, at its basic level, it boils down to five practices:

  1. Tagging: every resource carries team, service, environment and cost_centre tags. Without them, the bill is a number; with them, "the delivery team spent €1,840 in September, 60% of it on MSK". Terraform applies them to everything by default (default_tags).
  2. Reservations and savings plans: committing to usage for 1 or 3 years in exchange for a 30-60% discount. Applied to the baseline that is always on (the minimum EKS nodes, RDS, MSK, ElastiCache), never to the peak.
  3. Spot instances: the provider's spare capacity at a 60-90% discount, which can be reclaimed with 2 minutes' notice. Ideal for whatever tolerates interruptions and restarts on its own: Spark executors (05-03 already tolerated losing executors by recomputing partitions), EKS nodes for stateless workloads with several replicas. Never for RDS, Cassandra, Kafka or the control plane.
  4. Budgets and alerts: a budget per team and environment, with alerts at 80% and 100% of forecast, and anomaly detection (a NAT gateway that suddenly moves 2 TB because someone served the photos without a CDN). They are alerts like those of 07-01, sent to the same channel.
  5. Switching off idle resources: staging environments switched off at night and at weekends (with Terraform or with schedules), ephemeral Spark clusters (EMR starts up for the km0_daily_sales DAG and is destroyed when it finishes), orphaned volumes and snapshots.

Approximate monthly cost of Kilometre Zero on AWS

The prices below are fictional and indicative, of the right order of magnitude for a European region, and serve the reasoning, not as a budget. The useful exercise is to see where the cost is, not the exact figure.

Component Sizing Base monthly cost (€) With reservations / spot (€) Note
EKS control plane 1 cluster 70 70 Fixed
EKS nodes (services, Kong, mesh, observability) 6 × 4 vCPU/16 GB permanent + up to 12 during a campaign 900 + 300 (average of peaks) 550 + 120 (reserved baseline, spot for peaks) Largest compute item
RDS PostgreSQL Multi-AZ (km0_inventory) 2 vCPU/8 GB × 2 (standby), 200 GB 380 250 Multi-AZ doubles it
RDS PostgreSQL (km0_analytics) 2 vCPU/8 GB, 500 GB 210 140 No Multi-AZ: tolerates an RTO of hours
MSK 3 brokers × 2 vCPU/8 GB, 1 TB total 650 450 Cost per broker-hour + storage
Cassandra on EC2 (3 nodes) 3 × 4 vCPU/16 GB + 3 × 500 GB SSD 480 320 Keyspaces would be per read/write; with 1.2 M orders/month it comes out similar
ElastiCache Redis 2 nodes, 2 GB (primary + replica) 90 60
S3 (km0-photos 30 GB, km0-invoices, km0-backups 800 GB, km0-audit) ~1 TB + requests 35 35 Storage is cheap
Egress to the internet (photos, API) 3 TB/month without a CDN 240 240 → ~40 with a CDN (08-04) The item the CDN reduces
Traffic between AZs ~2 TB/month (replicas, consumers) 40 40 Always forgotten
NAT gateway 2 (one per active AZ) + 500 GB 110 110 VPC endpoints for S3 reduce it
ALB 1 + traffic 45 45
Ephemeral EMR (Spark) 2 h/day × 6 spot nodes 180 60 Spot: −70%
MWAA (Airflow) Small environment 320 320 Expensive for what it does: a candidate for Airflow on EKS (~40)
Managed Flink 4 KPU 420 420 Alternative: Flink on EKS
Secrets Manager + KMS 60 secrets, 3 keys 45 45
CloudWatch (logs 200 GB/month, metrics) 180 180 Or Loki/Tempo/Prometheus on EKS (cost in nodes)
Secondary region (pilot light) RDS replica, S3 CRR, minimal MSK 350 350 ~10% of the primary
Provider support Business plan 250 250
Approximate total ≈ 5,300 ≈ 3,900

What the table teaches: compute (EKS + databases + Kafka) is two thirds of the total; reservations save 25-30% on the baseline; egress is the item most sensitive to design (the CDN of 08-04 divides it by six); and two managed services (MWAA, managed Flink) cost more than their self-managed equivalents on the cluster, which illustrates that "managed" does not always win: the decision depends on how much the time of the team that would operate it is worth.

  1. Hands-on: Kilometre Zero on AWS with Terraform and EKS

The structure of the infrastructure code, added to the km0/ project:

km0/infra/
├── aws/
│   ├── main.tf            # providers, state backend, VPC, EKS, RDS, MSK, ElastiCache, S3, IAM
│   ├── variables.tf       # region, environment, sizes
│   ├── outputs.tf         # endpoints the k8s/ manifests need
│   └── environments/
│       ├── prod.tfvars
│       └── staging.tfvars
└── gcp/                   # the GCP equivalent (table only in this lesson)

What follows is main.tf explained block by block. It is simplified (some security and networking parameters that a real module includes are missing), but every block is real and corresponds to a decision from the previous sections.

Block 1: providers and remote state

# km0/infra/aws/main.tf
terraform {
  required_version = ">= 1.6"
  required_providers {
    aws        = { source = "hashicorp/aws",        version = "~> 5.40" }
    kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.27" }
    helm       = { source = "hashicorp/helm",       version = "~> 2.12" }
  }
  # Remote state: S3 with versioning (to recover a previous state) and
  # DynamoDB as a lock (two simultaneous 'apply' runs exclude each other).
  backend "s3" {
    bucket         = "km0-terraform-state"
    key            = "prod/infra.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "km0-terraform-lock"
    encrypt        = true
  }
}

provider "aws" {
  region = var.region                       # "eu-west-1": an EU region (compliance, section 6)
  default_tags {                            # FinOps: every piece carries these tags
    tags = { project = "km0", environment = var.environment, managed_by = "terraform" }
  }
}

data "aws_availability_zones" "available" { state = "available" }
locals {
  azs = slice(data.aws_availability_zones.available.names, 0, 3)   # three AZs: a, b, c
}

Block 2: the VPC with three tiers per AZ

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.5"

  name = "km0-${var.environment}"
  cidr = "10.0.0.0/16"
  azs  = local.azs

  public_subnets   = ["10.0.0.0/24",  "10.0.1.0/24",  "10.0.2.0/24"]    # ALB, NAT
  private_subnets  = ["10.0.10.0/23", "10.0.12.0/23", "10.0.14.0/23"]   # EKS nodes (512 IPs per AZ: Pods consume IPs)
  database_subnets = ["10.0.20.0/24", "10.0.21.0/24", "10.0.22.0/24"]   # RDS, MSK, ElastiCache, Cassandra

  enable_nat_gateway     = true
  one_nat_gateway_per_az = true          # without this, a NAT in a single AZ is a single point of failure for outbound traffic
  create_database_subnet_group = true

  # Tags EKS needs to discover which subnets to create load balancers in
  public_subnet_tags  = { "kubernetes.io/role/elb" = 1 }
  private_subnet_tags = { "kubernetes.io/role/internal-elb" = 1 }
}

# VPC endpoint for S3: traffic to km0-photos neither goes out to the internet nor through the NAT (cost and security)
resource "aws_vpc_endpoint" "s3" {
  vpc_id          = module.vpc.vpc_id
  service_name    = "com.amazonaws.${var.region}.s3"
  route_table_ids = module.vpc.private_route_table_ids
}

Block 3: EKS with permanent and spot node groups

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.8"

  cluster_name    = "km0-${var.environment}"
  cluster_version = "1.29"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnets       # nodes never in public subnets

  cluster_endpoint_public_access = true              # kubectl from the pipeline (restrict by CIDR in prod)
  enable_irsa                    = true              # IAM Roles for Service Accounts (block 6)

  eks_managed_node_groups = {
    base = {                                         # permanent capacity: this is where the reservations go
      instance_types = ["m6i.xlarge"]                # 4 vCPU / 16 GB
      min_size = 6, desired_size = 6, max_size = 9
      labels = { "km0/pool" = "base" }
    }
    campaign = {                                     # elastic capacity on spot: stateless Pods only
      instance_types = ["m6i.xlarge", "m5.xlarge", "m6a.xlarge"]  # several types: better odds of getting spot
      capacity_type  = "SPOT"
      min_size = 0, desired_size = 0, max_size = 12
      labels = { "km0/pool" = "spot" }
      taints = [{ key = "km0/spot", value = "true", effect = "NO_SCHEDULE" }]  # only what explicitly tolerates it
    }
  }
}

The orders and catalog Pods carry in k8s/orders-deployment.yaml a toleration for km0/spot and an affinity that prefers base, so that during a campaign they overflow onto spot; Cassandra, Kafka (if it were self-managed) and the databases do not tolerate the taint and never land on spot. The cluster autoscaler (Karpenter, installed with the helm provider) is what takes the campaign group from 0 to 12 according to the pending Pods.

Block 4: RDS PostgreSQL Multi-AZ for km0_inventory

resource "aws_db_subnet_group" "data" {
  name       = "km0-data"
  subnet_ids = module.vpc.database_subnets
}

resource "aws_security_group" "rds" {
  name   = "km0-rds"
  vpc_id = module.vpc.vpc_id
  ingress {                                          # only 5432 and only from the EKS nodes
    from_port = 5432, to_port = 5432, protocol = "tcp"
    security_groups = [module.eks.node_security_group_id]
  }
}

resource "aws_db_instance" "inventory" {
  identifier        = "km0-inventory-${var.environment}"
  engine            = "postgres"
  engine_version    = "16"
  instance_class    = var.environment == "prod" ? "db.m6g.large" : "db.t4g.medium"
  allocated_storage = 200
  storage_encrypted = true
  kms_key_id        = aws_kms_key.data.arn            # our own key (06-02): we control rotation and access

  db_name  = "km0_inventory"
  username = "km0_admin"
  manage_master_user_password = true                 # the password is generated and stored by Secrets Manager

  multi_az               = var.environment == "prod" # synchronous standby in another AZ: replaces Patroni (07-03)
  backup_retention_period = 14                       # daily backups + continuous WAL: 14-day PITR
  backup_window          = "02:00-03:00"
  maintenance_window     = "sun:03:30-sun:04:30"     # RDS restarts for major patches: a known window
  deletion_protection    = var.environment == "prod" # an accidental 'terraform destroy' does not delete production
  performance_insights_enabled = true

  db_subnet_group_name   = aws_db_subnet_group.data.name
  vpc_security_group_ids = [aws_security_group.rds.id]
}

# Read replica in the secondary region: the basis of the pilot light (07-03, section 3)
resource "aws_db_instance" "inventory_dr" {
  provider            = aws.secondary                 # a second provider with region = "eu-central-1"
  identifier          = "km0-inventory-dr"
  replicate_source_db = aws_db_instance.inventory.arn
  instance_class      = "db.t4g.medium"               # small: resized on promotion
  kms_key_id          = aws_kms_key.data_dr.arn
  skip_final_snapshot = true
}

What replaces the hand-built pieces here: multi_az = true is Patroni + etcd + synchronous replica + automatic failover (RDS switches the endpoint's DNS to the standby in ~60 s; the application only needs to reconnect, which is what the pool with retries from 07-04 already does); backup_retention_period is the pgBackRest and WAL archiving of 07-03. What it does not replace: the weekly restore test, which remains an Airflow job that restores the latest snapshot onto a temporary instance and runs checks.

Block 5: MSK, ElastiCache and S3

resource "aws_msk_cluster" "events" {
  cluster_name           = "km0-events-${var.environment}"
  kafka_version          = "3.6.0"
  number_of_broker_nodes = 3                                  # one per AZ: partition replicas spread out
  broker_node_group_info {
    instance_type   = "kafka.m5.large"
    client_subnets  = module.vpc.database_subnets
    security_groups = [aws_security_group.msk.id]
    storage_info { ebs_storage_info { volume_size = 350 } }   # 3 × 350 GB ≈ 1 TB: the retention from 02-04
  }
  encryption_info {
    encryption_in_transit { client_broker = "TLS", in_cluster = true }   # mandatory TLS (06-02)
  }
  configuration_info {
    arn      = aws_msk_configuration.events.arn
    revision = aws_msk_configuration.events.latest_revision
  }
}

resource "aws_msk_configuration" "events" {
  name              = "km0-events"
  server_properties = <<-EOT
    default.replication.factor=3
    min.insync.replicas=2
    auto.create.topics.enable=false
  EOT                                                          # the same values as in 02-04 and 07-03
}

resource "aws_elasticache_replication_group" "cache" {
  replication_group_id = "km0-cache-${var.environment}"
  description          = "Catalog cache, rate limiter, delivery pub/sub"
  engine               = "redis"
  node_type            = "cache.t4g.small"
  num_cache_clusters   = 2                                     # primary + replica in another AZ
  automatic_failover_enabled = true
  multi_az_enabled     = true
  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
  subnet_group_name    = aws_elasticache_subnet_group.data.name
  security_group_ids   = [aws_security_group.redis.id]
}

resource "aws_s3_bucket" "photos" {
  bucket = "km0-photos-${var.environment}"
}
resource "aws_s3_bucket_versioning" "photos" {                 # versioning, as in MinIO (04-03)
  bucket = aws_s3_bucket.photos.id
  versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_public_access_block" "photos" {        # never public: served via presigned URL or CDN
  bucket = aws_s3_bucket.photos.id
  block_public_acls = true, block_public_policy = true, ignore_public_acls = true, restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "photos" {
  bucket = aws_s3_bucket.photos.id
  rule {
    id     = "old-versions"
    status = "Enabled"
    noncurrent_version_expiration { noncurrent_days = 30 }     # the lifecycle rule from 04-03
  }
}
resource "aws_s3_bucket_replication_configuration" "photos_dr" {  # CRR to the secondary region (pilot light)
  bucket = aws_s3_bucket.photos.id
  role   = aws_iam_role.s3_replication.arn
  rule {
    id     = "dr"
    status = "Enabled"
    destination { bucket = aws_s3_bucket.photos_dr.arn, storage_class = "STANDARD_IA" }
  }
}

Block 6: IAM roles for service accounts (IRSA)

The catalog Pods need to write to km0-photos; the orders Pods need to read their secrets. Instead of long-lived credentials in a Kubernetes Secret (which 06-04 avoided with Vault), EKS allows an IAM role to be bound to a Kubernetes service account: the Pod obtains temporary credentials automatically, with least privilege, and CloudTrail records who did what. It is the workload identity of 06-04 (SPIFFE) with the provider's IAM.

data "aws_iam_policy_document" "catalog_photos" {
  statement {
    actions   = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"]   # no s3:* and no ListAllMyBuckets
    resources = ["${aws_s3_bucket.photos.arn}/photos/*"]               # only the photos prefix
  }
}

module "irsa_catalog" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.39"
  role_name = "km0-catalog-${var.environment}"
  role_policy_arns = { photos = aws_iam_policy.catalog_photos.arn }
  oidc_providers = {
    eks = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["km0-prod:catalog"]          # ONLY this service account can assume the role
    }
  }
}

In the k8s/catalog-deployment.yaml manifest all that is needed is serviceAccountName: catalog and the eks.amazonaws.com/role-arn: <role arn> annotation on the ServiceAccount; the AWS SDK in the Pod (boto3, the same photos.py from 04-03 pointed at S3 instead of MinIO) finds the credentials on its own.

Block 7: outputs for the manifests

# km0/infra/aws/outputs.tf
output "rds_inventory_endpoint" { value = aws_db_instance.inventory.address }
output "msk_bootstrap_tls"      { value = aws_msk_cluster.events.bootstrap_brokers_tls }
output "redis_endpoint"         { value = aws_elasticache_replication_group.cache.primary_endpoint_address }
output "bucket_photos"          { value = aws_s3_bucket.photos.bucket }
output "eks_cluster_name"       { value = module.eks.cluster_name }

Deploying the k8s/ manifests from 07-05 on EKS

The manifests do not change in shape: what changes are the configuration values pointing at the dependencies. In 07-05, orders-config had KAFKA_BOOTSTRAP=kafka-0.kafka:9092; now the pipeline generates it from the Terraform outputs:

# km0/infra/aws/deploy.sh — run by the pipeline after 'terraform apply'
set -euo pipefail
cd km0/infra/aws
terraform init -input=false
terraform plan -var-file=environments/prod.tfvars -out=plan.bin      # the plan is attached to the PR
terraform apply -input=false plan.bin

aws eks update-kubeconfig --name "$(terraform output -raw eks_cluster_name)" --region eu-west-1

# ConfigMaps with the real endpoints (the 07-05 manifests reference them by name)
kubectl -n km0-prod create configmap platform-endpoints \
  --from-literal=KAFKA_BOOTSTRAP="$(terraform output -raw msk_bootstrap_tls)" \
  --from-literal=PG_INVENTORY_HOST="$(terraform output -raw rds_inventory_endpoint)" \
  --from-literal=REDIS_URL="rediss://$(terraform output -raw redis_endpoint):6379" \
  --from-literal=S3_BUCKET_PHOTOS="$(terraform output -raw bucket_photos)" \
  --dry-run=client -o yaml | kubectl apply -f -

# The same manifests as in 07-05: Deployments, Services, HPA, PDB, NetworkPolicies, canary
kubectl apply -k km0/k8s/overlays/aws-prod       # kustomize: the 07-05 base + patches (serviceAccountName, spot tolerations)
kubectl -n km0-prod rollout status deployment/orders --timeout=300s

What disappears from k8s/ when moving to managed services: the StatefulSets for PostgreSQL with Patroni, for Kafka and for Redis, and their operators. What stays: the six services, Kong, the mesh, Cassandra (if Keyspaces is not adopted), Prometheus/Loki/Tempo (if CloudWatch is not adopted) and Mosquitto. The HPA of 07-05 works the same; what is new is Karpenter underneath. And the tests of 07-06 are repeated against the staging environment deployed with staging.tfvars (small instances, no Multi-AZ, no DR), which is switched off at night.

The GCP equivalent

Resource on AWS (main.tf) GCP equivalent (google provider) Relevant difference
VPC with subnets per AZ google_compute_network + google_compute_subnetwork (subnets are regional, spanning every zone) Fewer subnets; zone distribution is done by GKE
EKS + node groups GKE (regional google_container_cluster + google_container_node_pool, with Autopilot as a nodeless option) Regional GKE spreads the control plane and the nodes across 3 zones by default
RDS Multi-AZ Cloud SQL with availability_type = "REGIONAL" Similar failover (~60 s); PITR with point_in_time_recovery_enabled
MSK Confluent Cloud (Kafka) or Pub/Sub (a different model: no Kafka-style partitions or offsets) Pub/Sub requires rewriting consumers; Confluent keeps the code
ElastiCache Memorystore for Redis (tier = "STANDARD_HA") No cluster mode in the basic tiers
S3 + CRR GCS with a dual-region or multi-region bucket Cross-region replication is a property of the bucket, not a rule
IRSA Workload Identity (Kubernetes service account ↔ Google service account) Same concept
Secrets Manager + KMS Secret Manager + Cloud KMS Equivalent
EMR / MWAA / managed Flink Dataproc / Cloud Composer / Dataflow Dataflow uses the Beam model, not the Flink API
CloudWatch Cloud Monitoring + Cloud Logging + Cloud Trace Equivalent
Remote state on S3 + DynamoDB gcs backend (the bucket itself provides the lock) Simpler

Common Mistakes and Tips

  • Assuming that "managed" means "no operations". RDS still restarts in the maintenance window, MSK still needs partitions sized, and nobody tests the restores for us. The work changes; it does not disappear.
  • A single AZ "to save money". One NAT, one subnet, RDS without Multi-AZ: the provider's first AZ incident is a total outage. Multi-AZ for everything on the path of an order.
  • Forgetting egress and inter-AZ traffic in the budget. They are the two items that surprise on the first bill. A CDN for public content, VPC endpoints for S3, client.rack on Kafka consumers.
  • IAM roles with *. A service role with s3:* over every bucket turns a compromised Pod into a total leak. One policy per service account, with specific actions and resources (block 6).
  • Terraform state in Git or on a laptop. It contains secrets and is the only map of what exists. An encrypted remote backend with versioning and a lock, always.
  • Applying without reading the plan. A -/+ on a database is a destroy-and-recreate. The plan goes in the pull request and deletion_protection on production.
  • Choosing the region only on price or latency. Personal data has residency restrictions; the region is a decision with a legal review, and a documented one.
  • Buying reservations for the peak. Reservations cover the baseline that is always on; the peak goes to spot or on-demand. Over-buying is paying for capacity that is not used.
  • Tip: deploy staging first with the same Terraform configuration and small sizes; 90% of the networking, IAM and security group mistakes show up there at minimal cost.
  • Tip: put cost in Grafana next to the other metrics (providers export daily spend per tag). A chart of euros per order is the business metric that generates the most useful conversations.

Exercises

Exercise 1: failure domains in the VPC

Review the main.tf from section 10 and answer: (a) if AZ b disappears entirely, which Kilometre Zero components are affected, which mechanism recovers each one and in roughly how long; (b) what would have happened with one_nat_gateway_per_az = false; (c) which component of main.tf would not survive the loss of the region, and what would have to be run to recover it.

Exercise 2: the bill for changing a decision

Using the cost table from section 9 (fictional prices), calculate the approximate monthly saving or extra cost of these three decisions and reason about whether they are worth it: (a) replacing MWAA with Airflow on EKS (estimate: €40 in nodes); (b) moving km0_analytics on RDS to Multi-AZ; (c) moving the permanent EKS nodes from on-demand to a 3-year reservation (estimated discount 55% instead of 40%), knowing that the team plans to reduce to 4 permanent nodes within a year.

Exercise 3: selective lock-in

For each of these components, decide whether Kilometre Zero should use the provider's managed service or deploy it on EKS, applying the "selective lock-in" criterion from section 6 (benefit of the managed service versus cost of the dependency): PostgreSQL, Kafka, Cassandra, Keycloak, Vault, Prometheus/Grafana. Justify each one in two sentences.

Solutions

Exercise 1.

(a) With AZ b down: RDS km0_inventory: the standby was in b (or the primary; RDS decides); if it was the standby, nothing visible happens and RDS creates a new one in another AZ; if it was the primary, automatic failover to the standby in ~60 s, during which stock reservations fail and the saga retries (07-04). MSK: broker 2 goes down; the partitions whose leader was there elect a new leader from among the ISR (03-03) within seconds; with min.insync.replicas=2 and RF=3, writes continue. Cassandra: one node out of three; with QUORUM (2 of 3), reads and writes continue. ElastiCache: if the primary was in b, automatic failover to the replica (~30 s) and the cache refills; if it was the replica, nothing. EKS: the nodes in b disappear; Kubernetes reschedules their Pods onto a and c (07-05: topologySpreadConstraints guaranteed that no service had all its replicas in b) in 1-3 minutes, and Karpenter adds nodes if capacity is short. ALB: it stops sending to b once the health checks fail (seconds). NAT: the one in b goes down, but so do the nodes in b, so nobody needs it.

(b) A single NAT in, say, AZ a: if a goes down, the nodes in b and c stay alive but lose their outbound internet access: payments cannot reach the gateway, the pipeline cannot pull images. A healthy component is rendered useless by someone else's failure domain: exactly what 07-03 called non-independent redundancy.

(c) Everything in the primary region. What exists in the secondary: the RDS replica (inventory_dr), the replicated bucket and the minimal MSK with MirrorMaker. To recover: terraform apply of the same code with region = "eu-central-1" (some 20-30 min for VPC + EKS + ElastiCache), promote the RDS replica (aws rds promote-read-replica, ~5 min), deploy the manifests pointing at the new endpoints, restore Cassandra from the snapshots replicated to S3 (the longest step and the one that sets the RPO for orders), and switch the Route 53 record. It is the pilot light runbook from 07-03, and the real RTO is whatever comes out of the game day, not the one in the table.

Exercise 2.

(a) MWAA €320 → Airflow on EKS €40: saving ≈ €280/month (€3,360/year). It is worth it if the platform team takes on operating Airflow (upgrades, metadata database, on-call); with the Kubernetes operator and a small RDS instance for the metadata (~€30 more), it is still worth it, and it also avoids a provider-specific dependency. It is the example of managed not always winning.

(b) €210 → roughly double, ≈ €420: extra cost ≈ €210/month. For km0_analytics, whose temporary loss does not prevent selling (an RTO of hours in 07-03; it is rebuilt from the lake), it is not worth it; the 07-03 decision still stands.

(c) Node baseline: €900 on-demand; with a 1-year reservation at 40%: €540; with a 3-year reservation at 55%: €405. Difference in favour of 3 years: €135/month. But the 3-year reservation covers 6 nodes and within a year only 4 will be used: 2 reserved nodes would be paid for unused for 2 years (≈ 2 × €67 × 24 months ≈ €3,200), against a saving of 135 × 12 = €1,620 in the first year. Not worth it: a 1-year reservation, renegotiable when the sizing changes. Reservations are bought for the stable baseline, and "stable" includes the foreseeable future.

Exercise 3.

  • PostgreSQL → RDS (managed). High benefit (Multi-AZ, PITR and patches replace the entire Patroni cluster) and low lock-in: it is standard PostgreSQL, a pg_dump moves it anywhere.
  • Kafka → MSK (managed). High benefit (the broker rolling restarts of 07-05 and disk management disappear) and low lock-in: standard Kafka protocol, MirrorMaker replicates it to any Kafka. On GCP, Confluent rather than Pub/Sub for the same reason.
  • Cassandra → on EKS (or EC2), not Keyspaces. Keyspaces changes the semantics (lightweight transactions, some types, a per-request cost model) and the 04-04 repository would need review: high lock-in for a medium benefit. It stays self-managed with the operator from 07-05, and is reconsidered if the platform team cannot keep up.
  • Keycloak → on EKS, not Cognito. Cognito is more limited in flows and customisation, and migrating users and clients away from an identity provider is painful: high lock-in. Keycloak with two replicas and RDS behind it is easy to operate.
  • Vault → Secrets Manager (managed), with a caveat. For static secrets and their rotation, Secrets Manager with IRSA is simpler and integrates with IAM. The dynamic database credentials and the PKI for mTLS that Vault provided in 06-04 have no direct equivalent: if the mesh takes over mTLS (08-01), Vault can be retired; if not, Vault stays on EKS for that alone.
  • Prometheus/Grafana → on EKS. The services/common/ instrumentation is standard (OpenTelemetry, Prometheus exposition) and the 07-01 dashboards and alerts already exist; CloudWatch would cost more and would change the query language (PromQL). Avoidable lock-in at low cost. The provider's managed Prometheus/Grafana services are an acceptable middle ground if metrics storage grows.

Conclusion

The cloud turns infrastructure into an API, and with that it changes three things in the design of Kilometre Zero. The first is responsibility: IaaS, PaaS, SaaS and FaaS move the line between what the provider does and what the customer does, but data and identities always stay on this side of it. The second is geography: regions and availability zones are failure domains with names, AZs absorb everyday failures with Multi-AZ on everything an order touches, and the secondary region of the 07-03 pilot light becomes Terraform code that costs nothing while it is not run. The third is substitution: RDS for Patroni, MSK for the Ansible-managed brokers, S3 for MinIO, EKS for the in-house cluster, with a table that says what is gained and what is lost in every row, and a selective lock-in criterion (managed where the benefit is high and the API standard, self-hosted where the dependency would cost more than the operation). Terraform describes the VPC with three tiers per AZ, EKS with base and spot nodes, Multi-AZ RDS with its replica in another region, MSK, ElastiCache, S3 with versioning and replication, and IRSA so that every Pod has only the permissions it needs; the 07-05 manifests are deployed almost unchanged, and the Terraform plan is reviewed in the pull request like any other code. The well-architected pillars summarise the course as a checklist, and FinOps adds cost as a metric: tags, reservations for the baseline, spot for Spark and the peaks, budgets with alerts, and a table showing that compute is two thirds of the bill, that two managed services cost more than their equivalents on the cluster, and that egress is the item design can reduce the most.

Precisely that item, and two more questions, open the next lesson. Does it take a service on Kubernetes, with its replicas, its probes and its on-call rota, to generate a thumbnail every time Montblanc Dairy uploads a photo, or to produce a PDF when a payment.confirmed arrives? And why do the aged-cheese photos have to travel from Ireland to Anna's phone in Valencia on every visit, when they could be twenty kilometres away from her? Functions that exist only while there is an event to process, and compute and cache at the edge of the network: Serverless and Edge Computing Architectures.

Distributed Architectures Course

Module 1: Introduction to Distributed Systems

Module 2: Communication in Distributed Systems

Module 3: Consistency and Replication

Module 4: Distributed Storage

Module 5: Distributed Computing

Module 6: Security in Distributed Systems

Module 7: Monitoring and Maintenance

Module 8: Case Studies and Applications

© Copyright 2026. All rights reserved