Coursework from COMM065 – Cloud Security, part of my MSc Cyber Security at the University of Surrey (CW1: Cloud Automation, 40% of the module). The brief: work as part of a team to fully automate the deployment of a monitored application stack — Grafana running on Kubernetes (via Minikube) on an AWS EC2 server — using Infrastructure-as-Code, with no manual steps in the AWS console after the automation is kicked off.
Scope & disclaimer: This was a 4-person group project (individual marks were adjusted by peer-assessed contribution), run on the AWS Academy Learner Lab. My own contribution covered the technical automation end-to-end — the network and EC2 deployment automation, the Kubernetes installation automation, and the Grafana installation automation — while the team's shared written documentation was produced by teammates. Since that documentation was a group deliverable, and the CloudFormation/Kubernetes templates are themselves the graded artifact for a coursework brief that's reused across cohorts, I've kept this write-up to an architecture-level description of what the automation does rather than reproducing the full working templates or the team's submission files.
flowchart TD
subgraph NET["Network stack"]
VPC["VPC (dedicated /16)"]
IGW["Internet Gateway"]
RT["Public route table"]
SUB["Public subnet"]
VPC --> IGW --> RT --> SUB
end
subgraph APP["Application stack"]
EC2["EC2 instance\nAmazon Linux 2023, t3.medium"]
SG["Security group\n(SSH, Grafana, K8s dashboard)"]
UD["cloud-init user-data script"]
SG --> EC2 --> UD
end
subgraph BOOT["Automated first-boot provisioning"]
DOCKER["Install Docker"]
MK["Install & start Minikube"]
K8S["Apply Kubernetes manifest"]
GRAF["Grafana + datasource + dashboard"]
DOCKER --> MK --> K8S --> GRAF
end
SUB -->|cross-stack import| EC2
UD --> BOOT
GRAF -->|port-forward| USER["Browser: Grafana dashboard"]
GRAF -->|port-forward| DASH["Browser: Kubernetes dashboard"]
Network layer (CloudFormation stack 1): provisions a dedicated VPC with DNS support/hostnames enabled, an Internet Gateway, a public route table with a default route out to the internet, and a public subnet configured to auto-assign public IPs. This stack exports its VPC ID and subnet ID so the application stack can import them, keeping the network and application layers cleanly separated.
Application layer (CloudFormation stack 2): provisions the EC2 instance (sized to comfortably run a single-node Minikube cluster) into the subnet from stack 1, attaches a security group scoped to just the ports the lab needs, and — critically — embeds a cloud-init/user-data script that runs automatically, as root, on first boot. That script is the whole point of the exercise: no one ever logs in by hand to install anything.
First-boot provisioning (cloud-init): the user-data script installs and starts Docker, downloads and starts Minikube, then applies a Kubernetes manifest that deploys Grafana with a persistent volume claim (so dashboards/settings survive pod restarts), a pre-configured external data source, and a ready-made dashboard — followed by backgrounded kubectl port-forward processes so both Grafana and the Kubernetes dashboard are reachable from a browser without any manual kubectl commands.
End-to-end result: create the network stack, then the application stack, wait for CREATE_COMPLETE, and a fully working, monitored Kubernetes deployment is live — verified via the EC2 instance's OS/CPU/memory, the running Minikube control plane, the deployed pods, and the live dashboard in a browser.
Alongside the automation build above, COMM065 also required each team member to individually work through a hands-on Kubernetes fundamentals lab on their own AWS Academy Learner Lab session — with the results counted as an individual contribution toward the same CW1 group mark, ahead of the concepts being fully automated in the pipeline above.
What this covered, done on my own EC2 instance:
- Provisioned a fresh EC2 instance and opened the security-group ports needed for the Kubernetes dashboard and a demo service
- Installed Docker as the container runtime, then installed Minikube and started a single-node Kubernetes cluster on top of it
- Enabled the Kubernetes Dashboard and port-forwarded it to inspect Deployments, Pods, and cluster resource usage from a browser
- Deployed and exposed the
hello-minikubedemo service (kicbase/echo-server) via a NodePort service and confirmed it was reachable - Reviewed pod-level metadata and resource information (namespace, node, status, IP, QoS class, restart count) directly from the dashboard
- Practiced Kubernetes' self-healing behaviour — scaling a deployment's replica count up, then deleting a pod to watch the scheduler automatically recreate it and restore the desired replica count
This part is deliberately manual and exploratory — the point is to build fluency with core Kubernetes building blocks (Deployments, Pods, Services, self-healing) before those same concepts get wired into the fully unattended CloudFormation/cloud-init pipeline described above.
- Infrastructure-as-Code with AWS CloudFormation — multi-stack design, cross-stack resource sharing via
Fn::ImportValue, parameterization cloud-init/EC2 user-data scripting for fully unattended first-boot provisioning- Container orchestration with Kubernetes (via Minikube) — Deployments, Services, ConfigMaps, PersistentVolumeClaims
- Hands-on Kubernetes cluster operations via the Kubernetes Dashboard — deployment/pod inspection, resource monitoring, and scaling/self-healing behaviour
- Security-group design and least-privilege thinking for a cloud lab environment
- Monitoring/observability tooling (Grafana) wired to an external data source
- Debugging unattended automation via boot logs (
/var/log/cloud-init-output.log)
One thing worth calling out from this project, since it's a genuinely useful takeaway: the credential used for Grafana's external data-source integration ended up embedded directly in the CloudFormation template (and, as a knock-on effect, in the instance's boot log too) rather than being pulled in from a secrets manager at deploy time. It's a good real-world illustration of why secrets don't belong in Infrastructure-as-Code templates — they should come from something like AWS Secrets Manager, SSM Parameter Store (SecureString), or a Kubernetes Secret injected at runtime, not be hardcoded into a file that can end up in version control. No credentials of any kind appear anywhere in this repo.
README.md— this write-up.
(This was a 4-person group project; the full submission — CloudFormation templates, Word documentation and PowerPoint evidence — was produced collectively and submitted privately via the university's VLE. It isn't reproduced here, both because the written documentation was a team deliverable and because the templates themselves are the graded artifact for a coursework brief reused across cohorts.)