Skip to content

Solution

CI Week 1

1.1 Google Cloud & GKE - Completed Badges

Completed badges via Google Skills:


1.2 Kubernetes

Assignment 1 - Cluster Installation

The assignment specifies Ubuntu 24.04 LTS minimal. I used Ubuntu 25.10 LTS minimal.

Ubuntu 25.10 ships with sudo-rs (a Rust reimplementation of sudo) version 0.2.8 by default. This version has a known session bug where sudo reboot fails with an unexpected error. Solved via a GCP startup script that installs classic sudo on every boot.

sudo-rs version on a fresh Ubuntu 25.10

sudo-rs bug: reboot fails

GCP startup script replaces sudo-rs

Instances used:

NodeNameZoneTypeOS
Mastermaster-amsterdameurope-west4-a (Netherlands)e2-mediumUbuntu 25.10 LTS minimal
Worker 1worker-brusselseurope-west1-b (Belgium)e2-mediumUbuntu 25.10 LTS minimal
Worker 2worker-londoneurope-west2-b (United Kingdom)e2-mediumUbuntu 25.10 LTS minimal

VM instances in Google Cloud

OS and storage configuration

The cluster was installed with two shell scripts: configure_master.sh for the master node and configure_worker.sh for the worker nodes. These scripts automate kernel module configuration, installing containerd, Kubernetes package installation (v1.35), and cluster initialisation.

Running configure_master.sh on master-amsterdam

Running configure_worker.sh on worker-brussels

Workers joined - all nodes Ready

Explanation of kubeadm init:

kubeadm init sets up the Kubernetes control plane on the master node. It generates TLS certificates, writes kubeconfig files, creates static Pod manifests for core components (kube-apiserver, kube-controller-manager, kube-scheduler, etcd), and generates a bootstrap token for worker nodes.

Explanation of kubectl apply -f kube-flannel.yml:

Installs Flannel as the CNI plugin. Flannel creates a VXLAN overlay network that gives each pod a unique IP, so pods on different nodes can communicate directly. The CIDR 10.244.0.0/16 must match the --pod-network-cidr of kubeadm init.

Other network CNIs:

CNIDescription
FlannelSimple L3 overlay network via VXLAN. No network policy support.
CalicoBGP routing with full NetworkPolicy support. Widely used in production.
CiliumeBPF-based CNI with advanced observability and security.
Weave NetMesh overlay network, simple installation, supports NetworkPolicy.
CanalCombines Flannel (networking) with Calico (network policy).

1a - kubectl get nodes:

NAME               STATUS   ROLES           AGE    VERSION
worker-brussels    Ready    <none>          14m    v1.35.1
worker-london      Ready    <none>          7m     v1.35.1
master-amsterdam   Ready    control-plane   17m    v1.35.1

kubectl get pods -n kube-system

kubectl get pods -n kube-flannel

Explanation of the kube-system pods:

PodRole
kube-apiserverFront-end of the control plane. All kubectl commands and internal components go through this REST API. Master only.
kube-controller-managerRuns all controller loops: correct number of pod replicas, node lifecycle, certificate rotation. Master only.
kube-schedulerWatches for unscheduled pods and assigns them to a suitable node. Master only.
etcdDistributed key-value store holding the complete cluster state. Master only.
kube-proxyManages iptables/nftables rules so Service IPs route correctly to pods. One pod per node.
corednsCluster-internal DNS. Two replicas for redundancy.

Assignment 2 - Containerised Application

Dockerfile: (view on GitHub)

A Dockerfile is a text file with instructions that Docker executes step by step to build an image. Each instruction creates a layer in the image. Docker can cache layers: if an instruction has not changed, Docker reuses the cached layer and does not re-run that step. This makes builds faster.

The Dockerfile for this application works as follows:

FROM nginx:alpine
COPY . /usr/share/nginx/html

FROM nginx:alpine This is the base of the image. nginx:alpine is the official nginx web server on Alpine Linux. Alpine is a minimal Linux distribution of around 5 MB, compared to roughly 180 MB for the Debian variant. Smaller means a smaller attack surface: fewer packages on the system that could contain vulnerabilities. For a web server that only serves static files, Alpine is more than sufficient.

COPY . /usr/share/nginx/html Copies all files from the current directory (so index.html and any other assets) to the nginx document root. This is the directory nginx looks in by default when a request comes in. That way nginx does not need to be configured and the static site runs immediately.

nginx starts automatically in the foreground when the container starts, because that is built into the nginx:alpine image. This keeps the container running as long as nginx is running.

GitHub Actions workflow:

The workflow (ci_week1.yml) builds and pushes the image as stensel8/public-cloud-concepts:latest on every push to main.

deployment.yaml: (view on GitHub)

2b - Pod IPs:

NAME                               IP           NODE
first-deployment-5ffbd9444c-5hkzs  10.244.2.2   worker-london
first-deployment-5ffbd9444c-s4xdb  10.244.1.2   worker-brussels

kubectl apply output - pods in Pending status

Both pods Running

curl and kubectl exec output

The response confirms that the nginx container is running and serving the static site via the internal Flannel IP. External access requires a Kubernetes Service (Week 2).