🎯 Objective

In this lab, we will apply what we have learned about Kubernetes (K8s) to create a cluster in cloud environments.

🧭 Kubernetes lab overview
Let's get started! Let's go through a few exercises to practice commands of Kubernetes, specifically kubectl and minikube.
1. Recommended reading: Kubernetes basics
![Screenshot illustrating 1. [Recommended Reading] Learning the basic of Kubernetes from Google](../assets/tutorials/week-10/page-002-image-01.png)
One of the best materials to start learning Kubernetes is from Google.
Open this link: https://kubernetes.io/docs/tutorials/kubernetes-basics/. Please read through six of these basic Kubernetes modules!
Create a Kubernetes cluster
Deploy an app
Explore your app
Expose your app publicly
Scale up your app
Update your app

After completing the reading, let's up our game and set up some projects like a to-do list app or a voting app examples with Kubernetes in the next lab section.
2. Deploy a to-do application with Minikube

This second section will focus around minikube , which is a single-node Kubernetes local cluster, focusing on making it easy to learn and practice Kubernetes.
Create the Minikube server on EC2
Minikube has specific system requirements . At a minimum, it requires 2 CPUs and 2GB of memory , but for more comfortable operation and to run actual workloads, you should aim for at least 2 CPUs and 4GB of memory.

Let’s set up the EC2 instance for “minikube" Server like so. Here are the settings of the Minikube for EC2:
Name: Minikube
OS: default Amazon Linux AMI (Free-tier)
Instance Type: t3.medium (2 vCPU + 4GB RAM)
Important: This instance type is required to meet Minikube's system requirements but is not part of the
AWSFree Tier. Running this EC2 instance will incur costs on your AWS account.
Key pair: select and reuse your old key in other labs (
devops_project_key)Storage: Default 8 GB is plenty enough!
Security Group:
Optional Name:
Minikube_Security_GroupSource Type: Anywhere
Open two ports:
22 for SSH
3000-32767:
30000-32767 for Minikube (In
Kubernetes, aNodePortserviceis a way to expose a service on a port of the node (a machine running Kubernetes). The default port range for NodePortservicesis 30000 -
This means any service you want to access directly can be set
to a port within this range. Minikube, being a local Kubernetes environment, follows to the same standards)
However, keep in mind, in the lab, we only use three ports 3000, 3001 and 3002 only.
Install Minikube, kubectl, and dependencies
[Optional] For some general ideas how we can get these installation done, please read through these guides:
https://minikube.sigs.k8s.io/docs/start/ (
Minikubeinstallation)https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/ (
kubectlinstallation)https://minikube.sigs.k8s.io/docs/drivers/docker/ (docker installation)
To keep things simple without dealing with permission, let’s login as a root user and change our current directory to /root:
sudo su -Now, you are now a root user with the highest permission privilege to install anything you want. You can check your current directory, which should tell you that you are at root directory:

Installing kubectl is essential for learning Minikube because it provides the necessary command line tool to interact with your Minikube
Kubernetescluster, allowing you to deploy and manage applications effectively.Let’s Install kubectl binary with curl. It does so by first retrieving the version number from the stable release URL, then using it to compose the download URL for the specific kubectl binary needed for Linux:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
Installs the kubectl program with owner and group set to root, and permissions set to 0755, placing it in the
/usr/local/bindirectory for system-wide use:
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectlTest if the installation is done correctly by checking the version information of the kubectl:
kubectl version --client
Minikube is a tool that creates a local Kubernetes cluster on your machine, and it requires
Dockerto run the containers that make up the nodes of the cluster. Therefore, we need to install docker first!
yum install docker -yStart docker!
service docker start
Check if docker has started:
service docker status
Uses curl to fetch the latest Minikube release for Linux and saves it with its original name:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
Install the Minikube binary as 'minikube' in the '
/usr/local/bin' directory, granting it system-wide access for all users:
sudo install minikube-linux-amd64 /usr/local/bin/minikubeNow, we are ready! Let's start a local Kubernetes cluster (Minikube):
minikube start --force
The reason we use “--force” is because we want to run minikube as a root user!
minikube status:
minikube status
[Explanation] The Control Plane, host, kubelet, and apiserver components are all active, and the kubectl configuration is properly set up, meaning the cluster is ready to be used for Kubernetes operations.
Lists all the
podsin allnamespacesof a Kubernetes cluster. :
kubectl get po -A
[Explanation] The -A flag (short for --all-namespaces) tells kubectl to list pods from all namespaces in the cluster. In a new Minikube cluster, the only pods running are the essential system components, which reside in the kube-system namespace. The output confirms that core components like CoreDNS, etcd, the API server, and the scheduler are all in a healthy Running state, with their containers ready (1/1). This means our cluster is operational.
You can see all components of Kube master or control plan above there!
Let’s initiates a new Kubernetes
podnamed 'todolistapp' using the 'tomhuynhsg/react-todo-list-app'Docker Hubimage to deploy a React-based to-do list application:
kubectl run todolistapp --image=tomhuynhsg/react-todo-list-app
View the Pod:
kubectl get pods
A Kubernetes Pod is a group of one or more Containers, tied together for the purposes of administration and networking. The Pod in this tutorial has only one Container.
View cluster events:
kubectl get events
This shows a list of system pods in the 'kube-system' namespace, all of which are currently in a 'Running' status, indicating a healthy state for these core components of the cluster.
By default, the Pod is only accessible by its internal IP address within the Kubernetes cluster. To make the our to-do list app Container accessible from outside the Kubernetes virtual network, you have to expose the Pod as a Kubernetes
Service.Expose the Pod using the kubectl expose command. Let’s create a new service named 'todolistapp-service' that exposes the 'todolistapp' pod on a network, accessible via a
NodePortatport 80, allowing external access to the application:
kubectl expose pod todolistapp --type=NodePort --port=80 --name=todolistapp-service
[Explanation] NodePort: This type makes your service accessible on a static port on each node's IP in the Kubernetes cluster. It allocates a port from a range (default is 30000-32767), and any traffic sent to this port on any node is forwarded to the service. In short, NodePort is a simple way to get external traffic into your cluster, typically for development or small-scale environments.
Let’s retrieve the list of
servicesyou created:
kubectl get services
[Explanation] It shows 80:32449/TCP , meaning the service is listening on port 80 within the cluster , and it has been mapped to port 32449 on all nodes' IP addresses . External traffic that hits any node on port 32449 will be forwarded to this service on port 80.
Similarly, you can use “kubectl get svc” instead of “kubectl get services” as “svc” is simply an alias or shorthand for services in the kubectl command:
kubectl get svc
Let’s focus on the todolistapp-service only!
Let's list the service of todolistapp-service:
kubectl get services todolistapp-service
Let’s retrieve the URL of the 'todolistapp-service' service running in your local Minikube environment, allowing you to interact with your app via a URL:
minikube service todolistapp-service --url
Let’s make an HTTP request to a web service running on a Minikube cluster to test if the website is actually working:
curl <the-website-url>
The HTML code displayed is the actual content of the homepage that a web browser would render for a user.
The Problem : Even though you can fetch the website content via curl command internally, you would not be able to access the website via the public IP address of EC2 via the expose port 32449 on the browser. You can try to do that by opening the URL: <your-public-ip-of-ec2>:<export-port-of-service> on your browser then you will realize it will not respond to any website unlike the result of curl.
The Explanation :
Minikube Environment : Minikube is designed to be a local Kubernetes environment. When you expose a service in Minikube, it's typically only accessible within the VM or the environment where Minikube is running. The service todolistapp-service is exposed on a NodePort within the Minikube's virtual network, which is not directly accessible from the public internet.
Public IP Address : Your EC2 instance has a public IP, but the Minikube cluster and its services have their own set of private IPs that are not directly exposed to the internet. Even though you've opened the appropriate ports in your security group, the traffic has nowhere to go because the Minikube service isn't listening on the EC2 instance's network interface.
The Solution : By using kubectl port-forward, you are creating a tunnel from your EC2 instance's network (which has the public IP) to the Minikube network. The --address 0.0.0.0 flag is crucial here because it tells kubectl to listen on all network interfaces, including the public one. This effectively exposes the Minikube service to the internet via your EC2's public IP address and makes your application accessible.
Let’s forwards requests from local
port 3000toport 80on the todolistapp-service, accessible from all network interfaces:
kubectl port-forward svc/todolistapp-service 3000:80 --address 0.0.0.0 &
You can see the website via the port number 3000 from the public IP EC2 instance:

Please spend some time playing around with this simple To Do List App to celebrate your progress so 🎉🥳 far!
Let's list all events happening inside
hello-nodepod which provides details about the specific events associated with the Kubernetes pod :
kubectl describe pod todolistapp
[Explanation] From the events, it shows the pod 'todolistapp' was successfully scheduled, pulled its required Docker image, created a container from that image, and started it without errors
Optional Minikube Dashboard Setup: The Minikube Dashboard is a graphical interface for managing and monitoring Kubernetes clusters, included with Minikube to simplify local development.
Open the Kubernetes dashboard in a browser:
minikube dashboard --url
[Explanation] Minikube starts up the Kubernetes Dashboard and prints out a URL like “http://127.0.0.1:43149/ … ” .This indicates that the dashboard is listening on port 43149 on the remote VM/EC2 instance. Minikube randomly chooses this port every time the dashboard is started.
Since the dashboard is only exposed on 127.0.0.1 inside the remote VM (the EC2 instance), you can’t just open your local browser and go to http://<EC2 IP>:43149. To access the K8s dashboard, you need to choose either option A (kubectl proxy method) or option B (SSH port forwarding method)
Option A: Exposing the Dashboard with kubectl proxy
⚠ Important Security Warning: The following method is easier for a lab environment but is highly insecure. It exposes your entire Kubernetes cluster's control API to the network where the EC2 instance resides. Never use this method in a production environment. We are using it here strictly for learning purposes to avoid the complexity of SSH tunnels.
The kubectl proxy command can start a web server that allows us to access the Kubernetes API, including the dashboard. By default, it only listens on localhost, but we will tell it to listen on all network interfaces.
Start kubectl proxy on the EC2 Instance
--address='0.0.0.0' makes the proxy listen on all network interfaces, not just localhost.--accept-hosts='.*' allows your browser's hostname to connect.The & at the end will run it in the background so you can continue using the terminal.
kubectl proxy --address='0.0.0.0' --accept-hosts='.*' &Open Port 8001 in your EC2 Security Group
The proxy runs on
port 8001by default. You must open this port to access it from your browser.If you follow the instruction carefully, you should have already opened this
port 8081(within 3000-32767 range)
Access the Dashboard
Now, you can access the dashboard by combining your EC2 instance's public IP with the special dashboard URL:
http://<Your-EC2-Public-IP>:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/This will open the Kubernetes Dashboard directly. Note that the URL in your browser will now show your public IP, not localhost or
127.0.0.1.

Option B: Exposing the Dashboard with SSH Port Forwarding
Alternatively, you can use an SSH tunnel to “forward” a local port on your own computer to that remote port on the VM.
[Open another terminal window] Try to create a SSH Tunnel using this command:
ssh -i [private key.pem] -L [local port]:localhost:[remote port of minikube dashboard] ec2-user@[ec2-public ip]![Screenshot illustrating ssh -i [private key.pem] -L [local port]:localhost:[remote port of minikube dashboard] ec2-user@[ec2-public ip]](../assets/tutorials/week-10/page-012-image-02.png)
[Explanation] This command will forward any connections made to localhost:8081 (on your laptop) over SSH to localhost:43149 (on the remote EC2 instance).
Now, you can access to the dashboard by pasting the full dashboard url in your browser:

[Explanation] Everything you do on localhost:8081 will be securely tunneled over SSH and mapped to port 43149 on the remote EC2 instance, which is where the Minikube Dashboard is actually running.
[After do option A or option B to access to K8s dashboard]
Here are quite a lot of information you can learn more about your Minikube or Kubernetes nodes, services and deployments:


Please spend your time playing around with the dashboard before proceeding to the next steps!
Time to clean up everything to move on to the next project!
[Switching back to the first terminal] Now you can clean up the resources you created in your cluster:
kubectl delete service todolistapp-service
kubectl delete pod todolistapp
Stop the Minikube virtual machine (VM):
minikube stop
You can delete the whole cluster of Minikube by running this command:
minikube delete
🗳
3. Deploy the voting application with Kubernetes manifests


I hope you have fun so far! Let's recreate the voting application which we have practiced with Docker swarm in the previous lab. This time, we're gonna use Kubernetes manifest files to deploy this application.
The manifest is a specification of a Kubernetes API object in JSON or YAML format. A Kubernetes Manifest file is the heart of Kubernetes. These plaintext configuration files describe how a pod’s containers should be run and connected to other objects, such as services or replication controllers. You can learn about it here.
Let's start a local Kubernetes cluster with minikube:
minikube start --force
Next, let's git clone the voting application from my Github:
yum install git -y
git clone https://github.com/TomHuynhSG/simple-voting-application.git
Let's change the current directory to the “kubernetes" directory in the Github repo:
cd simple-voting-application/kubernetesLet's list all manifest YAML files which are needed to setup the voting app with Kubernetes:
ls

Let's launch the pod of the voting app website in the cluster using “
voting-app-pod.yml" file:
kubectl create -f voting-app-pod.yml
[Investigation] Please read through this the content of voting-app-pod.yml manifest file for your learning:
apiVersion: v1
kind: Pod
metadata:
name: voting-app-pod
labels:
name: voting-app-pod
app: demo-voting-app
spec:
containers:
- name: voting-app
image: dockersamples/examplevotingapp_vote
ports:
- containerPort: 80List all
podsin the cluster:
kubectl get pods
You can see there is a voting-app-pod which is created and running.
Describe the activity of all pods:
kubectl describe pods
You can see that step-by-step event messages which indicates:
The task of creating pod “
voting-app-pod" is assigned to the minikube.Pulling the dockersamples/
examplevotingapp_votefrom the Dockerhub.Created and started the container
voting-appin the cluster.
List all nodes in the cluster:
kubectl get nodes
As you can see the minikube node is our control-plane/master-node is running and ready.
Let's continue to create a
serviceto expose thevoting-app-pod which we just created to a public port number from the innerport 80of the node:
kubectl create -f voting-app-service.yml
[Investigation] Please read through this the content of voting-app-pod.yml manifest file for your learning:
apiVersion: v1
kind: Service
metadata:
name: voting-service
labels:
name: voting-service
app: demo-voting-app
spec:
ports:
- port: 80
targetPort: 80
type: LoadBalancer
selector:
name: voting-app-pod
app: demo-voting-appLet's list all the services:
kubectl get services
As you can see our new voting-service is a type of Load Balancer and open to the port number
[Extra Info] Service Types: NodePort vs. LoadBalancer
In this lab, you will encounter two types of Kubernetes Services to expose your applications:
NodePort : Exposes the service on a static port on each Node's IP address. It's a straightforward way to get external traffic into your cluster, making it great for development environments like the to-do list app.
LoadBalancer : This is the standard method for exposing a service to the internet on a cloud provider (like
AWSor GCP). When you create a service of this type, the cloud platform automatically provisions an external load balancer and assigns it a public IP.
[Extra Info] Why is the EXTERNAL-IP <pending>?
Minikuberuns as a local, self-contained cluster and is not integrated with a cloud provider's infrastructure. Therefore, it cannot automatically provision an external load balancer. This is why theEXTERNAL-IPstatus remains<pending>.To get around this, Minikube provides a handy command: minikube service
<service-name>. This command creates a tunnel directly to the service inside the cluster, allowing you to access it.
Let’s continue!
Let’s retrieve the URL of the 'todolistapp-service' service running in your local Minikube environment, allowing you to interact with your voting app via a URL:
minikube service voting-service --url
Let’s make an HTTP request to a web service running on a Minikube cluster to test if the voting website is actually working:
curl <the-voting-website-url>
Let’s forwards requests from local
port 3001toport 80on the voting-service, accessible from all network interfaces:
kubectl port-forward svc/voting-service 3001:80 --address 0.0.0.0 &
You can see the voting app website is running, which is hosted inside the
voting-app-pod in our cluster:

However, when you tried to click on the button “Dog" or “Cat" to vote, there would be an error “Internal Service Error" as the
voting-app-node tried to communicate with the redis container and failed to do so. This is just like the exact problem of our previous lab:


Let's resolve that problem so let's create the redis-pod using
redis-pod.ymlfile:
kubectl create -f redis-pod.yml
[Optional] Here is the content of redis-pod.yml manifest file for your learning:
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
labels:
name: redis-pod
app: demo-voting-app
spec:
containers:
- name: redis
image: redis:latest
ports:
- containerPort: 6379Let's create a service for redis-pod to expose the node to an external port number:
kubectl create -f redis-service.yml
[Optional] Here is the content of redis-service.yml manifest file for your learning:
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
name: redis-service
app: demo-voting-app
spec:
ports:
- port: 6379
name: redis-something
targetPort: 6379
selector:
name: redis-pod
app: demo-voting-app
Let's continue the routine and create the database postgres pod using
postgres-pod.yml:
kubectl create -f postgres-pod.yml
[Optional] Here is the content of postgres-pod.yml manifest file for your learning:
apiVersion: v1
kind: Pod
metadata:
name: postgres-pod
labels:
name: postgres-pod
app: demo-voting-app
spec:
containers:
- name: postgres
image: postgres:9.4
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"
- name: POSTGRES_HOST_AUTH_METHOD
value: trust
ports:
- containerPort: 5432Let's create a service for postgres-pod to expose the node to an external port number:
kubectl create -f postgres-service.yml
[Optional] Here is the content of postgres-service.yml manifest file for your learning:
apiVersion: v1
kind: Service
metadata:
name: db
labels:
name: db-service
app: demo-voting-app
spec:
ports:
- port: 5432
targetPort: 5432selector:
name: postgres-pod
app: demo-voting-app
Let's continue the routine and create the worker app pod using
worker-app-pod.yml:
kubectl create -f worker-app-pod.yml
[Optional] Here is the content of worker-app-pod.yml manifest file for your learning:
apiVersion: v1
kind: Pod
metadata:
name: worker-app-pod
labels:
name: worker-app-pod
app: demo-voting-app
spec:
containers:
- name: worker-app
image: dockersamples/examplevotingapp_worker
Let's create the result app pod using
result-app-pod.yml:
kubectl create -f result-app-pod.yml
[Optional] Here is the content of result-app-pod.yml manifest file for your learning:
apiVersion: v1
kind: Pod
metadata:
name: result-app-pod
labels:
name: result-app-pod
app: demo-voting-app
spec:
containers:
- name: result-app
image: dockersamples/examplevotingapp_result
ports:
- containerPort: 80Let's create a service for
result-app-pod to expose the node to an external port number so we can access the result app website via a public port number:
kubectl create -f result-app-service.yml
[Optional] Here is the content of result-app-service.yml manifest file for your learning:
apiVersion: v1
kind: Service
metadata:
name: result-service
labels:
name: result-service
app: demo-result-app
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
name: result-app-pod
app: demo-voting-appLet's list all pods and repeat this command until all of the pods are running:
kubectl get pods
As you can see all five pods are up and running in the cluster.
Let's list all services in the cluster:
kubectl get services
As you can see, we have two services result-service and voting-service which expose the result app website and the voting app website to be accessible via port numbers. We can use those port numbers to access the websites on the browser.
Let’s forwards requests from local
port 3002toport 80on the result-service, accessible from all network interfaces:
kubectl port-forward svc/result-service 3002:80 --address 0.0.0.0 &
You should be see the result app website displaying via the port number:

That is a great success of deploying the voting application in the Kubernetes cluster!
[Question] However, now you might ask if there is any easy way to deploy all of the nodes in one go instead of running the yaml file one by one.
Let’s clean up all pods and services. First, you can delete all pods:
kubectl delete pods --all
Let's list all pods, and you should see if all five pods have been deleted:
kubectl get pods
Then you can delete delete all services:
kubectl delete services --all
Let's list all services, and you should see if all services have been deleted:
kubectl get services
For a multi-component application like this, applying each manifest file individually is inefficient. A much better practice is to apply all manifest files in a directory at once. Let's deploy the entire voting application with a single command:
Now, to quickly deploy all nodes in one go again without running each yaml file, you can use this command to for
kubectlto run all yaml files in the current directory and execute all yaml files in one go to create all five pods and their services:
kubectl create -f .
Let's list all pods, and you should see all five pods up and running:
kubectl get pods
Let's list all pods, and you should see all five services up and running:
kubectl get services
You can try to redo the steps (option A or option B) to launch the Kubernetes dashboard again to investigate more about those pods:



In a similar manner, you can delete all pods by running this command by telling kubectl to find all yaml files in the current directory and remove all pods mentioned in them:
kubectl delete -f .
Let's list all pods again, and you should see all pods has been removed:
kubectl get pods
Let's list all services again, and you should see all services has been removed except kubernetes service:
kubectl get services
Feel free to stop the Minikube virtual machine (VM):
minikube stop
You can also delete the whole cluster of Minikube to wrap up this tutorial lab:
minikube delete
Nicely done! Amazingly good job! You have learned one of the most difficult topics in DevOps! This 🏆🥇🎉👏 is just the basics of Kubernetes with Minikube ! In the next lab, we will up our game and learn about EKS (Elastic Kubernetes Service) in AWS, which will manage our Kubernetes cluster with more ease in the real production!

4. Bonus: Kubernetes online labs
![Screenshot illustrating 4. [Bonus] Kubernetes Online Lab & Lessons from Docker](../assets/tutorials/week-10/page-030-image-03.png)
Play with Kubernetes is a labs site provided by Docker and created by Tutorius.
Open this link: https://labs.play-with-k8s.com/.
You can login with either a Github or Docker account. Then press the “Start” button!
Start the interactive session and follow this Kubernetes tutorial: https://training.play-with-kubernetes.com/kubernetes-workshop/

5. Bonus: Docker Swarm online lab
![Screenshot illustrating 5. [Bonus] Docker Swarm Online Lab from Docker](../assets/tutorials/week-10/page-031-image-02.png)
In case you want to revise and work on your docker swarm skills for assignment 3, please follow this very short fun learning labs for Docker swarm from Docker:
Open this link: https://training.play-with-docker.com/ops-s1-swarm-intro/
Have fun! Always be learning!
Here are some fun memes about Kubernetes to reward yourself after a long day of learning Kubernetes:








