🇬🇧 Kubernetes Part 2
 🇧🇷 para ler este artigo em portuguĂŞs clique aquiÂ
In this article series, we will explore Kubernetes in three parts. This is the second part, where I will explain tools and present exercises for you to put your acquired knowledge into practice. This will allow you to observe the pods running within the cluster and understand how each part of this system connects to create scalable and resilient applications.
Throughout the article, I will introduce various components and exercises separately, but it’s very important to understand that, together, they create a robust and scalable infrastructure. Kubernetes organizes resources using YAML files, which describe configurations such as pods, the basic units of execution. Elements like ReplicaSets and Deployments ensure high availability and consistent updates. Namespaces help isolate and organize resources, while Services and the Networking Infrastructure connect components to each other and to the external world. Features like Liveness Probes, Volumes, and Resource Management ensure monitoring, storage, and efficiency in complex environments.
With this in mind, let’s get started!
MINIKUBE:
As we will be learning and performing exercises, we need to prepare the Kubernetes environment to run our tests. If you don’t yet have Kubernetes and Minikube installed, I’ll provide a tutorial to help you set them up. But first, what is Minikube?
Minikube is a tool that allows you to create a local Kubernetes cluster on your computer. It is ideal for testing, development, and learning because you can run the entire Kubernetes setup without needing complex infrastructure or cloud services.
In practice, Minikube creates a virtual machine or container (depending on your system) and installs a basic Kubernetes cluster within it. This way, you can test your applications as you would in a “real” Kubernetes environment, but all locally. It also offers easy commands to manage the cluster, such as starting, stopping, adding extensions, and accessing the Kubernetes dashboard.
This video from the “CODIGO FLUENTE” channel will teach you how to set up your environment:
https://www.youtube.com/watch?v=Tr3l-cRIlmI
Watch it, and then come back here.
With Minikube installed, run the command:
minikube start
Now we are ready to get started.
YAML
YAML (Yet Another Markup Language, or currently YAML Ain’t Markup Language) is a data serialization format that is easy for humans to read and write. It is widely used for application configuration because it is simple and straightforward, yet powerful enough to support more complex structures like nested lists and maps.
The main idea of YAML is to be minimalist. Instead of using lots of braces and brackets like JSON, it uses indentation to organize data. This makes the file cleaner and easier to understand. Here’s a basic example:
name: Gustavo
age: 25
hobbies:
– watching series
– programming
– reading
work:
role: Data Engineer
experience: 6.5 years
In Kubernetes, YAML is the backbone. Everything you configure there—pods, deployments, services, volumes—is described in YAML files. These files tell Kubernetes what you want, and it makes it happen.
How does it work in Kubernetes?
YAML is used to define manifests that describe the desired state of resources. You write what you need, apply it with kubectl, and Kubernetes handles the rest.
Basic structure of a Kubernetes YAML file:
A file typically includes:
apiVersion: The API version you are using (e.g., apps/v1, v1).
kind: The type of resource you are creating (e.g., Pod, Deployment, Service).
metadata: Information like name and labels.
spec: The details of what you want to configure.
Basic example: Creating a Pod
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: my-app
spec:
containers:
– name: nginx-container
image: nginx:latest
ports:
– containerPort: 80
What it does:
kind: Pod: Specifies that you are creating a pod.
metadata: Provides the pod’s name and labels for identification.
spec: Configures the container, defines the image (nginx), and the port to be exposed.
Key points to be careful about:
YAML is unforgiving with indentation errors. A single misplaced space can break the file.
kubectl helps with validation: Use kubectl apply -f to check for errors before getting frustrated.
Be mindful of API versions—they change over time (e.g., extensions/v1beta1 is deprecated).
You can chain multiple resources in a single file by separating them with —.
With YAML in Kubernetes, you essentially describe what you want, and the cluster does the heavy lifting.
Later, we will create some pods and resources using YAML.
Pods:
Creating Pods – Imperative Approach:
Creating pods imperatively in Kubernetes is straightforward, quick, and great for testing something without creating a YAML file.
1. Simple Command:
Use kubectl run on the Minikube cluster we just started to create a pod directly:
kubectl run my-pod –image=nginx:latest
Pod created and running.
What happens:
Creates a pod named my-pod.
Uses the nginx:latest image from Docker Hub.
2. Adding a Port:
If your container exposes a port, you can specify it in the command:
kubectl run my-pod –image=nginx:latest –port=80
This is useful, for instance, if you want to connect via a service later.
3. Creating Pods with Environment Variables:
You can pass variables to the container:
kubectl run my-pod –image=nginx:latest –env=”ENV=production” –env=”DEBUG=false”
4. Generating the Pod’s YAML:
If you want to see the equivalent YAML and learn what it would look like:
kubectl run my-pod –image=nginx:latest –dry-run=client -o yaml
This only prints the YAML without applying anything. If you want to save it to a file:
kubectl run my-pod –image=nginx:latest –dry-run=client -o yaml > my-pod.yaml
Then you can edit it and apply it to the Minikube cluster:
kubectl apply -f my-pod.yaml
5. Listing Created Pods:
To check if your pod is running:
kubectl get pods
To view the details:
kubectl describe pod my-pod
6. Deleting the Pod:
When you no longer need it:
kubectl delete pod my-pod
Why Use the Imperative Approach?
- Quick Tests: No need to create and edit YAML for something simple.
- Learning: Helps you understand the available options in Kubernetes.
- Debugging: Quick to run and test containers directly on the cluster.
However, for more serious use cases, YAML is the best practice.
Creating Pods – Manifest Files:
Creating pods with manifest files is the declarative and organized way in Kubernetes.
1. Create the Pod YAML File
You can use any IDE to create a file named pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: my-app
spec:
containers:
– name: nginx-container
image: nginx:latest
ports:
– containerPort: 80
What each part does:
apiVersion: Defines the API version. For Pods, it’s v1.
kind: Tells Kubernetes you’re creating a Pod.
metadata: Includes information like the name (my-pod) and labels (app: my-app).
spec: Defines what the Pod will contain, such as its containers.
containers: A list of containers in the Pod.
name: The name of the container.
image: The Docker image to use.
ports: The port the container will expose.
2. Apply the File to the Cluster
Use the kubectl apply command to create the Pod:
kubectl apply -f pod.yaml
If everything works correctly, Kubernetes will create the Pod.
Why Use Manifest Files?
Organization: Easy to version control with Git.
Reusable: Can use the same YAML across clusters.
Scalable: It’s the foundation for creating more advanced resources like Deployments and Services.
This approach is essential for production and understanding Kubernetes in daily operations.
ReplicaSets:
ReplicaSets are like the “guardians” of Pods in Kubernetes. They ensure that you always have the desired number of replicas running, even if a Pod dies or fails.
What is a ReplicaSet?
Purpose: Maintain a fixed number of active Pods.
How it works: If a Pod dies, the ReplicaSet creates another. If there are extra Pods, it removes the surplus.
Difference from a Deployment: ReplicaSets don’t manage version updates of Pods; that’s a Deployment’s job.
When to Use ReplicaSets?
In practice, you’ll almost always use Deployments because they already create and manage ReplicaSets for you. Only use a ReplicaSet directly for something very basic or for learning purposes.
Let’s Test It in Practice!
Creating a ReplicaSet
Create a file named replicaset.yaml with the following content:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: my-replicaset
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
– name: nginx-container
image: nginx:latest
ports:
– containerPort: 80
What it does:
replicas: 3: Ensures 3 Pods are running.
selector: Specifies that it only manages Pods with the label app: my-app.
template: Defines how the Pods should be created.
Applying the ReplicaSet
Run the following command on the Minikube cluster to create the ReplicaSet:
kubectl apply -f replicaset.yaml
Check if it was created:
kubectl get replicasets
You will see something like this:
NAME DESIRED CURRENT READY AGE
my-replicaset 3 3 3 10s
Checking the Pods
The ReplicaSet automatically generated the Pods. Verify them:
kubectl get pods
You’ll see that the following Pods were created (the names after “replicaset-” might differ for you):
my-replicaset-abc12
my-replicaset-def34
my-replicaset-ghi56
Testing the ReplicaSet
Let’s test the ReplicaSet in action.
Delete a Pod manually:
kubectl delete pod my-replicaset-abc12
Now, see what happens:
kubectl get pods
The ReplicaSet will recreate the Pod you deleted. It ensures that there are always 3 Pods running.
Final Tip
You almost never create ReplicaSets directly in production. Instead, you use Deployments, which handle all the ReplicaSet tasks and also manage updates. However, understanding ReplicaSets is an excellent starting point.
Deployments:
Deployments in Kubernetes act as a “manual” that you provide to the cluster, instructing it on how to run and manage your Pods. With a Deployment, you define how your application should be deployed and kept operational.
What is it?
A resource used to create and manage Pod replicas.
Ensures your application is always running with the number of instances you define.
What do you configure?
In a Deployment YAML, you specify:
Container image (e.g., nginx:latest).
Number of replicas (e.g., 3 Pods).
Update strategies (e.g., “replace one at a time” or “take down all and start fresh”).
Labels and selectors to identify which Pods belong to the Deployment.
What does it do for you?
If a Pod fails, the Deployment automatically creates another.
For updates, it rolls out changes gradually (or as configured), to avoid downtime.
In Practice
Deployments run your application and ensure stability. However, if something goes wrong (e.g., a Pod gets stuck in CrashLoopBackOff), you’ll need to debug using kubectl logs or check Deployment events.
Creating a Deployment
Save the following YAML as deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
– name: my-app
image: nginx:1.23
ports:
– containerPort: 80
Quick Explanation
apiVersion and kind: Defines that this is a Deployment using apps/v1.
metadata: Specifies the name of the Deployment.
spec.replicas: Number of Pod replicas.
spec.selector.matchLabels: Determines which Pods belong to the Deployment (based on app: my-app).
spec.template: The Pod template.
metadata.labels: Labels for the Pod.
spec.containers: Container configuration:
name: Name of the container.
image: Container image to use.
ports: Port exposed by the container.
Apply the Deployment
Use the kubectl apply command to create the Deployment:
kubectl apply -f deployment.yaml
Verify the Deployment
Check if the Deployment is running:
kubectl get deployments
You’ll see something like:
NAME READY UP-TO-DATE AVAILABLE AGE
my-app 2/2 2 2 10s
Deployment Rollout
Deployment rollout is the process of releasing a new version of an application to production gradually and in a controlled manner to avoid widespread issues in case something goes wrong. Essentially, it replaces the old version with a new one cautiously, testing changes as they are applied.
Why Perform a Rollout?
If you simply update the entire application at once (the infamous “big bang deployment”) and there’s a critical bug, it can bring everything down. With a rollout, issues can be caught in smaller stages, making it easier to roll back to the previous version.
Rollout Examples
Rolling Update (Continuous Update):
Imagine you have 10 replicas of your application running in Kubernetes. With a rolling update, you update one or two replicas at a time. During the process, some replicas will still be running the old version, ensuring stability while you verify that the new version works as expected.
Example in Kubernetes:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
This ensures that no more than one replica will ever be unavailable.
Canary Deployment:
This approach deploys the new version to a small percentage of users at first. If it performs well, you gradually increase the percentage until it reaches 100%.
It’s like testing a bridge with one car before opening it to full traffic.
Practical example: You have an application serving 100% of traffic. Configure a load balancer to send 10% of requests to the new version (v2), while the remaining 90% continue to the stable version (v1).
Rollout History
Rollout history tracks the versions deployed to your application. It’s helpful to understand what changed, identify when something went wrong, and even revert to a previous version. Kubernetes makes this process easy to manage and track.
In Practice with Kubernetes:
View rollout history:
To see the deployment history:
kubectl rollout history deployment <deployment-name>
Example:
kubectl rollout history deployment my-app
Expected output:
deployment.apps/my-app
REVISION CHANGE-CAUSE
1 Initial deployment created
2 Added new feature X
3 Bug fix in the API
Describe a specific revision:
To get details about a specific revision:
kubectl rollout history deployment <deployment-name> –revision=<number>
Example:
kubectl rollout history deployment my-app –revision=2
Output:
deployment.apps/my-app with revision #2
Containers:
– Name: my-app
Image: my-image:2.0
Port: 8080
Environment Variables:
– ENV: production
Rollback to a previous version:
If the current version is breaking, revert to the last stable version:
kubectl rollout undo deployment <deployment-name>
Example:
kubectl rollout undo deployment my-app
Or to a specific revision:
kubectl rollout undo deployment my-app –to-revision=2
Summary
Use rollout history to track changes.
Add reasons (–record) for easier tracking.
Quickly rollback in case of issues.
Rollout Pause and Rollout Resume
Rollout Pause:
This Kubernetes command halts the deployment update process. It’s useful when you need to:
Analyze if the new version is stable before proceeding.
Make manual adjustments to the deployment while it’s paused.
How to pause a rollout:
kubectl rollout pause deployment <deployment-name>
Example:
kubectl rollout pause deployment my-app
What happens?
The deployment stops updating Pods to the new version.
Already created Pods continue running, but no new replicas are updated.
Rollout Resume:
Once adjustments are complete or the new version is confirmed stable, you can resume the paused rollout. Kubernetes will continue the process from where it left off.
How to resume a rollout:
kubectl rollout resume deployment <deployment-name>
Example:
kubectl rollout resume deployment my-app
What happens?
Kubernetes resumes updating the remaining Pods.
The rollout continues following the defined strategy (e.g., RollingUpdate).
Deployment Scale
Deployment Scale refers to the ability to dynamically adjust the number of replicas of an application running in a Kubernetes cluster. In simple terms, it’s like scaling the “size” of your application to handle more (or less) traffic.
When to Use?
High Demand: Scale up to add more replicas when application traffic increases.
Cost Efficiency: Scale down during low-usage periods to save resources.
Resilience: More replicas help keep the application available even if some pods fail.
How It Works in Practice
Manual Scaling:
Adjust the number of replicas manually using the kubectl scale command.
Basic Command:
kubectl scale deployment <deployment-name> –replicas=<desired-number>
Example:
kubectl scale deployment my-app –replicas=5
Kubernetes ensures that 5 pods are running for the my-app deployment.
Check the Status After Scaling:
After scaling, verify the status:
kubectl get pods
This will show new pods being created or deleted.
Automatic Scaling (Horizontal Pod Autoscaler):
Instead of manual scaling, Kubernetes can automatically adjust the number of replicas based on metrics like CPU or memory usage.
Create an Autoscaler:
Use the kubectl autoscale command:
kubectl autoscale deployment <deployment-name> –min=<min-replicas> –max=<max-replicas> –cpu-percent=<target-percentage>
Example:
kubectl autoscale deployment my-app –min=2 –max=10 –cpu-percent=80
Kubernetes maintains between 2 and 10 replicas.
It scales up if a replica’s CPU usage exceeds 80%.
Monitor the Autoscaler:
Check the status:
kubectl get hpa
Output example:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
my-app Deployment/my-app 75%/80% 2 10 4
Here, 75%/80% indicates the current CPU usage is 75%, near the 80% limit.
Recreate Strategy Type
Recreate is a Kubernetes deployment strategy where all existing pods of an application are removed before creating new ones. It’s straightforward: shut down the old and bring up the new, with no overlap.
When to Use Recreate?
Incompatibility: When the old and new versions of the application cannot run together.
Unique Dependencies: If the application depends on a single resource (e.g., a database connection) that multiple instances might conflict over.
Simplicity: For quick updates in systems that can tolerate downtime.
How to Configure Recreate:
Set this in the deployment configuration file under spec.strategy.type.
Example YAML with Recreate:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: Recreate
template:
metadata:
labels:
app: my-app
spec:
containers:
– name: my-app
image: my-app:2.0
ports:
– containerPort: 8080
When you apply a new version, Kubernetes deletes all old pods first and then creates the new ones.
Kubernetes Networking Infrastructure
Kubernetes Networking is like a system of “streets and addresses” ensuring everything in the cluster can communicate. Each Pod gets its own IP address, and communication is open by default (which can be risky).
Key Concepts:
Pods Communicate with Pods: They can reach each other regardless of location.
Services Act as Street Signs: Provide stable addresses to access pods, even as pods are created or deleted.
Ingress Acts as a Doorman: Manages external access to the cluster.
Quick Examples:
ClusterIP (Internal):
Used for services that only applications within the cluster can access.
NodePort (Fixed Port):
Opens a fixed port on each node for local testing.
LoadBalancer (Public):
Exposes a service to the internet through a cloud provider, like AWS or GCP.
Example Service YAML for an App on Port 8080:
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
– port: 80
targetPort: 8080
Ingress (HTTP Routing):
Direct traffic like mysite.com/api to the backend and mysite.com to the frontend.
Example YAML:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
– host: mysite.com
http:
paths:
– path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
– path: /api
pathType: Prefix
backend:
service:
name: backend
port:
number: 80
NetworkPolicy (Traffic Control):
Control access. For example, only allow the frontend to access the backend.
Example YAML:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: policy-backend
spec:
podSelector:
matchLabels:
app: backend
ingress:
– from:
– podSelector:
matchLabels:
app: frontend
Summary:
Pod: A house.
Service: An address plate.
Ingress: A doorman.
NetworkPolicy: A gate with a password.
Namespaces
Namespaces in Kubernetes act as “partitions” within the cluster. They help organize and isolate resources. Think of them as folders in a file system: you can separate environments (production, staging, development) or teams (frontend, backend).
Why Use Namespaces?
Organization: Separate resources for environments or teams.
Isolation: Restrict access to resources using RBAC and NetworkPolicies (covered in part 3).
Quotas: Set limits on CPU, memory, or specific resources.
Default Namespaces in Kubernetes:
default: Where resources go if no namespace is specified.
kube-system: Internal Kubernetes components (e.g., DNS).
kube-public: Data accessible to everyone.
How to Create a Namespace:
Using the Command Line (kubectl):
kubectl create namespace my-namespace
Using YAML:
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
Apply it with:
kubectl apply -f namespace.yaml
Using Namespaces:
Create Resources in a Specific Namespace: Add -n <namespace> to the command:
kubectl create deployment nginx –image=nginx -n my-namespace
Specify Namespace in YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: my-namespace
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
– name: nginx
image: nginx
Change the Default Namespace: Avoid typing -n repeatedly:
kubectl config set-context –current –namespace=my-namespace
Tip: Resource Quotas in Namespaces
You can limit resource usage within a namespace using ResourceQuota.
Example: Limit CPU and Memory Usage
apiVersion: v1
kind: ResourceQuota
metadata:
name: quotas
namespace: my-namespace
spec:
hard:
requests.cpu: “2”
requests.memory: 4Gi
limits.cpu: “4”
limits.memory: 8Gi
Apply it with:
kubectl apply -f resourcequota.yaml
Services
In Kubernetes, Services are used to expose a set of Pods for communication within or outside the cluster. They provide an abstraction layer, ensuring applications can locate Pods even as their IPs change.
Why Use Services?
Service Discovery: Enable locating Pods without relying on dynamic IPs.
Load Balancing: Distribute traffic among associated Pods.
External Connectivity: Expose applications to the outside world.
Types of Services:
ClusterIP (Default):
Exposes the service within the cluster.
Used for communication between Pods.
NodePort:
Opens a fixed port on each node for external access.
Example: Local development or testing.
LoadBalancer:
Creates a public IP (requires cloud provider support).
Ideal for production environments.
ExternalName:
Redirects traffic to an external DNS name.
Example: Connecting to external APIs.
Creating a Service:
ClusterIP (Internal Communication):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
– protocol: TCP
port: 80
targetPort: 8080
port: Port exposed by the Service.
targetPort: Port of the Pod.
selector: Defines the Pods associated with the Service.
Apply it with:
kubectl apply -f service-clusterip.yaml
NodePort (External Access):
apiVersion: v1
kind: Service
metadata:
name: my-nodeport
spec:
type: NodePort
selector:
app: my-app
ports:
– protocol: TCP
port: 80
targetPort: 8080
nodePort: 30007
Access it via:
http://<Node_IP>:30007
LoadBalancer (Public IP):
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer
spec:
type: LoadBalancer
selector:
app: my-app
ports:
– protocol: TCP
port: 80
targetPort: 8080
Requires a cloud provider (e.g., AWS, GCP, Azure) to automatically create a public IP.
ExternalName (External DNS):
apiVersion: v1
kind: Service
metadata:
name: my-externalname
spec:
type: ExternalName
externalName: api.example.com
Access the service at:
api.example.com
Check Services:
Use the command:
kubectl get services
Summary:
ClusterIP: Internal communication.
NodePort: Fixed external access.
LoadBalancer: Public IP for production.
ExternalName: Redirects to an external DNS.
With Services, you can connect components seamlessly and scalably in Kubernetes. Ready to practice and start using them?
Consuming a ClusterIP Service
To consume a ClusterIP Service, ensure that the Pods consuming the service are in the same cluster (or namespace, depending on the context). Here’s a practical example:
Scenario:
We have a backend application running in Pods, exposed by a ClusterIP Service called backend-service.
A frontend application consumes this service.
Backend Configuration:
Backend Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
– name: backend
image: hashicorp/http-echo
args:
– “-text=Hello from Backend!”
ports:
– containerPort: 8080
ClusterIP Service:
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
– protocol: TCP
port: 80
targetPort: 8080
Frontend Configuration:
Frontend Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
– name: frontend
image: curlimages/curl
command: [ “sh”, “-c”, “while true; do curl http://backend-service; sleep 5; done” ]
Steps to Deploy:
Apply the manifests:
kubectl apply -f backend-deployment.yaml
kubectl apply -f backend-service.yaml
kubectl apply -f frontend-deployment.yaml
Verify the Pods and Services:
kubectl get pods
kubectl get services
Check the frontend (consumer) logs:
kubectl logs -l app=frontend -f
You will see output like:
Hello from Backend!
Hello from Backend!
Hello from Backend!
How It Works:
The backend-service is a ClusterIP Service, creating an internal IP accessible only within the cluster.
The frontend makes calls to http://backend-service (internal DNS generated by Kubernetes).
The ClusterIP Service routes traffic to the backend Pods.
Tip:
Use Kubernetes’ internal DNS to reference the service:
The service name (backend-service) automatically works as a DNS name within the cluster, allowing seamless communication without worrying about Pod IPs.
Consuming a NodePort Service
When using a NodePort Service, the service exposes a fixed port on each node in the cluster, enabling external access via any node’s IP and the designated port.
Scenario:
A backend service is exposed as a NodePort Service.
The service is accessed from outside the cluster using a node’s IP.
Backend Configuration:
Backend Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
– name: backend
image: hashicorp/http-echo
args:
– “-text=Hello from Backend (NodePort)!”
ports:
– containerPort: 8080
NodePort Service:
apiVersion: v1
kind: Service
metadata:
name: backend-service-nodeport
spec:
type: NodePort
selector:
app: backend
ports:
– protocol: TCP
port: 80
targetPort: 8080
nodePort: 30007
Steps to Deploy:
Apply the manifests:
kubectl apply -f backend-deployment.yaml
kubectl apply -f backend-service-nodeport.yaml
Check the service:
kubectl get service backend-service-nodeport
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend-service-nodeport NodePort 10.96.0.234 <none> 80:30007/TCP 5m
Here, 30007 is the open port on all nodes.
Find a node’s IP:
kubectl get nodes -o wide
This will display the nodes’ external/internal IPs.
Consume the service:
Use a browser, cURL, or an HTTP tool to access:
http://<NODE_IP>:30007
Example using cURL:
curl http://<NODE_IP>:30007
Expected output:
Hello from Backend (NodePort)!
Testing with Minikube:
If using Minikube, get the node’s IP with:
minikube ip
Then access:
http://<Minikube_IP>:30007
How It Works:
A NodePort Service creates a fixed port (nodePort) on all cluster nodes.
Any request to http://<Node_IP>:30007 is routed to the backend Pods.
Kubernetes automatically load balances traffic among the Pods.
Tip:
Avoid NodePort in production; use LoadBalancer or Ingress for external exposure.
Use NodePort for development or testing in local environments or without cloud providers.
Liveness Probes
Liveness Probes are how Kubernetes checks if your application is still “alive” and functioning. If the probe fails, the container is restarted. This is useful when your application crashes or enters a state where it cannot recover by itself.
How It Works:
You define a rule for Kubernetes to check the container’s state. There are three main types of probes:
HTTP: Makes an HTTP request to an endpoint.
Command: Executes a command inside the container.
TCP: Checks if it can connect to a specific port.
Basic Example:
Liveness Probe HTTP
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-liveness
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
– name: app
image: nginx
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Explanation:
httpGet: Makes an HTTP request to the / path on port 80.
initialDelaySeconds: Waits for 5 seconds before starting the checks.
periodSeconds: Checks every 10 seconds.
Liveness Probe with Command
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-liveness-command
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
– name: app
image: busybox
command:
– sh
– -c
– echo “Starting app”; sleep 1000
livenessProbe:
exec:
command:
– cat
– /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 10
Explanation:
The container must create the /tmp/healthy file to be considered “alive.”
If the file disappears, the probe fails, and Kubernetes will restart the container.
Liveness Probe TCP
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-liveness-tcp
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
– name: app
image: nginx
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Explanation:
Verifies if port 80 is open and accepting connections.
Testing:
Apply the YAML:
kubectl apply -f <file.yaml>
Check the status of the Pods:
kubectl get pods
Check the events:
kubectl describe pod <pod-name>
If the Liveness Probe fails, you will see something like:
Warning Unhealthy Liveness probe failed: HTTP probe failed with statuscode: 500
When to Use:
App Crashes: If your app can freeze, use liveness probes to automatically restart it.
External Dependencies: If your app relies on something external and might enter a “broken” state.
Quick Tip:
Combine liveness probes with readiness probes to ensure the app only receives traffic when it’s truly ready.
Resource Management
Managing resources in Kubernetes is essentially about ensuring your Pods do not overconsume (or run out of) CPU and memory. You control this by setting limits and requests directly on the containers.
Why Is This Important?
Avoid Conflicts: If a container uses too much CPU/memory, it could crash other containers or even the node.
Ensure Performance: Reserve what the application needs to run smoothly.
Load Balancing: Kubernetes uses this information to intelligently distribute Pods across nodes.
How It Works:
Requests: The minimum amount of CPU/memory that the container needs. Kubernetes uses this to schedule the Pod on a node with enough capacity.
Limits: The maximum amount of CPU/memory the container can use. If it exceeds the limit, the container will be throttled (for CPU) or even terminated (in case of memory).
Simple Example:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
– name: my-app
image: nginx
resources:
requests:
memory: “128Mi”
cpu: “250m”
limits:
memory: “256Mi”
cpu: “500m”
What Does This Mean?
The container requires 128Mi of memory and 250m (0.25 vCPU) to start.
At most, it can use 256Mi of memory and 500m (0.5 vCPU).
If it tries to use more memory, the container will be terminated due to OOM (Out of Memory).
If it tries to use more CPU, it will be throttled.
Testing Resource Usage:
Run a container that uses CPU:
apiVersion: v1
kind: Pod
metadata:
name: cpu-tester
spec:
containers:
– name: cpu-tester
image: busybox
command: [“sh”, “-c”, “while true; do :; done”]
resources:
limits:
cpu: “100m”
Check the usage:
kubectl top pod
You will see it is limited to around 10% of a CPU core.
Tips:
Always set both requests and limits on containers.
For critical apps, use Pod Priority and Preemption to ensure they run.
Monitor the cluster with tools like Metrics Server, Prometheus, or Grafana.
Summary:
Managing resources in Kubernetes is about ensuring everyone gets what they need without anyone taking more than they should.
Volumes
Volumes in Kubernetes solve a simple problem: the lifecycle of containers. When a container restarts, its data is lost, but sometimes you need to store something for longer periods. A Volume connects persistent or shared storage to your Pods.
Types of Volumes
emptyDir
A blank directory created when the Pod is started.
Everything is deleted when the Pod is deleted.
Good for temporary storage between containers within the same Pod.
volumes:
– name: my-volume
emptyDir: {}
hostPath
Uses a directory on the node where the Pod is running.
Can be risky in production because it depends on the node.
volumes:
– name: my-volume
hostPath:
path: /data
persistentVolumeClaim (PVC)
Links the Pod to persistent storage (e.g., cloud disks).
Survives even if the Pod or cluster restarts.
volumes:
– name: my-volume
persistentVolumeClaim:
claimName: my-pvc
configMap and secret
We’ll discuss in Part 3, but it’s used to store configurations and sensitive data.
nfs
Connects your Pod to an NFS server.
Useful for sharing data between Pods.
volumes:
– name: my-nfs
nfs:
server: 192.168.1.1
path: /export
Complete Example: emptyDir
apiVersion: v1
kind: Pod
metadata:
name: pod-with-volume
spec:
containers:
– name: app
image: busybox
command: [“sh”, “-c”, “echo ‘data’ > /data/test.txt && sleep 3600”]
volumeMounts:
– name: my-volume
mountPath: /data
volumes:
– name: my-volume
emptyDir: {}
What Happens?
The /data directory in the container is linked to the emptyDir volume.
Any file created there is shared between containers within the same Pod.
Example with PVC
Create a PersistentVolume (PV):
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 1Gi
accessModes:
– ReadWriteOnce
hostPath:
path: /mnt/data
Create a PersistentVolumeClaim (PVC):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
– ReadWriteOnce
resources:
requests:
storage: 500Mi
Use the PVC in the Pod:
apiVersion: v1
kind: Pod
metadata:
name: pod-with-pvc
spec:
containers:
– name: app
image: nginx
volumeMounts:
– name: my-volume
mountPath: /data
volumes:
– name: my-volume
persistentVolumeClaim:
claimName: my-pvc
Practical Tips
Persistent Storage: Use PVC and PV for important data (e.g., databases, logs).
Temporary Files: Use emptyDir.
Security: Store sensitive information with secrets.
Caution with hostPath: Avoid using it in production, as it ties your app to a specific node.
In Summary: Volumes are Kubernetes’ flexible way of handling storage. You just choose the right type for your problem and go ahead!
This concludes Part 2 of our Kubernetes series. Was it clear? Don’t worry if you didn’t understand everything completely. The most important thing is to practice the examples to gain a deeper understanding. Thank you for reading!