Skip to content

Hands-on Exercises with Kubernetes 💻🚀

Kubernetes is the go-to platform for container orchestration, and mastering it is essential for modern DevOps and cloud-native development. One fundamental aspect of Kubernetes is managing resources using either imperative or declarative methods. In this hands-on blog post, we'll dive into both approaches with practical examples and real-time use cases.

Kubernetes syntax 🧩

Let's dive into the basic Kubernetes syntax for four key fields: apiVersion, kind, metadata, and spec:

  1. apiVersion:

    Purpose: The apiVersion field specifies the version of the Kubernetes API that the resource definition uses. It ensures compatibility between the resource and the Kubernetes cluster.

    Format: Typically, the apiVersion field consists of two parts separated by a forward slash ("/"). The first part is the API group, and the second part is the API version within that group.

    Examples: Common API versions include v1 (core API for basic resources), apps/v1 (for application-related resources), and networking.k8s.io/v1 (for network policies).

    Usage: It tells the Kubernetes cluster how to interpret the resource definition. It's essential to specify the correct apiVersion to create resources successfully.

  2. kind:

    Purpose: The kind field defines the type of Kubernetes resource you are creating or managing. It determines the behavior and attributes of the resource.

    Examples: Common resource kinds include Pod, Service, Deployment, ConfigMap, Secret, Namespace, and more.

    Usage: The kind field ensures that Kubernetes understands the type of resource you intend to create or modify.

  3. metadata:

    Purpose: The metadata field provides information about the resource, such as its name, labels, and annotations.

    Format: It is a YAML dictionary that includes various metadata fields like name, labels, annotations, and more.

    Examples: Setting name specifies the name of the resource, while labels allow you to add key-value pairs for categorization and selection.

    Usage: Metadata helps in identifying, organizing, and managing resources within the Kubernetes cluster.

  4. spec:

    Purpose: The spec field describes the desired state of the resource. It specifies the configuration settings, properties, and behaviors that the resource should have.

    Format: It depends on the resource type and its attributes. For example, in a Pod, the spec field may include details about containers, volumes, and networking.

    Usage: The spec field is where you define how you want the resource to function within the Kubernetes cluster. It represents the resource's desired state.

Here's an example of how these fields come together in a Kubernetes YAML file for creating a Pod:

# Kubernetes YAML file for a basic Pod

#The 'apiVersion' field specifies the Kubernetes API version. 
apiVersion: v1 # (1)!

# The 'kind' field defines the type of Kubernetes resource.
kind: Pod # (2)!

# Metadata section provides information about the resource.
metadata:
  # 'name' specifies the name of the Pod.
  name: my-pod

  # 'labels' are key-value pairs used to organize and select resources.
  # Here, we label the Pod as 'app: my-app.'
  labels:
    app: my-app 

# The 'spec' section describes the desired state of the resource.
spec:
  # 'containers' is a list of containers to run within the Pod.
  containers:
    - name: my-container
      # 'image' specifies the Docker image to use for this container.
      # In this case, we use the 'nginx:latest' image.
      image: nginx:latest

      # 'ports' section defines the ports to be exposed by the container.
      ports:
        - 
          # 'containerPort' specifies the port number (80) on which the container listens.
          containerPort: 80
  1. Common API versions include:

    • v1: Core resources like Pods, Services, ConfigMaps, and more.

    • apps/v1: Resources like Deployments, StatefulSets, and DaemonSets.

    • extensions/v1beta1: Older version for resources like Ingress.

    • networking.k8s.io/v1: Resources like NetworkPolicies.

  2. In this case, we are creating a Pod, but other common resource types include:

    • Deployment

    • Service

    • ConfigMap

    • Secret

    • PersistentVolume

    • PersistentVolumeClaim

    • StatefulSet

    • Namespace

In this example, we have:

  • apiVersion: v1 indicating the core API version.
  • kind: Pod specifying that we are defining a Pod resource.
  • metadata providing metadata like the Pod's name and labels.
  • spec describing the desired state of the Pod, including container details and port configuration.

Understanding and correctly using these basic Kubernetes syntax elements is crucial for defining and managing Kubernetes resources effectively.

Imperative 🆚 Declarative Methods

Imperative Method:

Example

Imperative commands directly manipulate Kubernetes resources in real-time. They are ideal for quick, one-off tasks. Let's explore some imperative examples:

1. Create a Namespace:

Command Line
kubectl create namespace my-namespace 

2. Run a Pod:

Command Line
kubectl run my-pod --image=nginx --port=80

3. Scale a Deployment:

Command Line
kubectl scale deployment my-deployment --replicas=3 # (1)

  1. 🖐️ Modify the quantity of replicas here

Real-time Use Case:

Suppose you have a sudden spike in traffic to your website, and you need to quickly scale up your application. Imperative commands allow you to react swiftly without worrying about YAML configuration files.

Declarative Method:

Example

Declarative Kubernetes management relies on YAML files to define the desired state of resources. Here are some declarative examples:

1. Create a Namespace

namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-namespace

2. Run a Pod

pod.yaml
apiVersion: v1  # Kubernetes API version 🌐
kind: Pod  # Type of resource: a Pod 🚀
metadata:
  name: my-pod  # Name of the Pod 🏷️
spec:
  containers:
    - name: nginx-container  # Container name 🐳
      image: nginx  # Docker image used 📦
      ports:
        - containerPort: 80  # Container's port configuration 🔌

3. Scale a Deployment

deployment.yaml
apiVersion: apps/v1  # Kubernetes API version for Deployments 🌐
kind: Deployment  # Type of resource: a Deployment 🚀
metadata:
  name: my-deployment  # Name of the Deployment 🏷️
spec:
  replicas: 3  # Number of desired replicas of the app 🔄
  selector:
    matchLabels:
      app: my-app  # Label selector for matching Pods 🏷️
  template:
    metadata:
      labels:
        app: my-app  # Labels for Pods created by this Deployment 🏷️
    spec:
      containers:
        - name: my-container  # Container name 🐳
          image: my-image  # Docker image used for the container 📦

Real-time Use Case:

Imagine you are setting up a Continuous Integration/Continuous Deployment (CI/CD) pipeline. Declarative manifests are the preferred choice as they clearly define the desired state of your resources, making it easier to track changes and collaborate with your team.

Converting Imperative Kubernetes Commands to Declarative YAML Files

Command Line
kubectl create deployment my-nginx-pod --image=nginx --dry-run=client -o yaml > pod.yaml
This command converts an imperative action (creating a pod) into a declarative file using kubectl.

Sample Pod File - Imperative vs. Declarative:

Let's create a sample Pod file to run an Nginx container using both imperative and declarative methods:

**Imperative **

Command Line
kubectl run my-nginx-pod --image=nginx --port=80 

Declarative

pod.yaml
apiVersion: v1  # Kubernetes API version 🌐
kind: Pod  # Type of resource: a Pod 🚀
metadata:
  name: my-nginx-pod  # Name of the Pod 🏷️
spec:
  containers:
    - name: nginx-container  # Container name 🐳
      image: nginx  # Docker image used 📦
      ports:
        - containerPort: 80  # Port configuration for the container 🔌

Sample Deployment and Service Files - Imperative vs. Declarative:

Now, let's create deployment and service files for deploying Nginx with a NodePort service using both methods:

Imperative (command-line):

kubectl create deployment nginx-deployment --image=nginx
kubectl expose deployment nginx-deployment --port=80 --type=NodePort

Declarative

deployment.yaml
apiVersion: apps/v1  # Kubernetes API version for Deployments 🌐
kind: Deployment  # Type of resource: a Deployment 🚀
metadata:
  name: nginx-deployment  # Name of the Deployment 🏷️
spec:
  replicas: 3  # Number of desired replicas of the app 🔄
  selector:
    matchLabels:
      app: nginx  # Label selector for matching Pods 🏷️
  template:
    metadata:
      labels:
        app: nginx  # Labels for Pods created by this Deployment 🏷️
    spec:
      containers:
        - name: nginx-container  # Container name 🐳
          image: nginx  # Docker image used for the container 📦

Declarative

service.yaml
apiVersion: v1  # Kubernetes API version for Services 🌐
kind: Service  # Type of resource: a Service 🚀
metadata:
  name: nginx-service  # Name of the Service 🏷️
spec:
  selector:
    app: nginx  # Label selector for matching Pods 🏷️
  ports:
    - protocol: TCP  # Protocol used for the port 🔌
      port: 80  # Port exposed by the Service 🌐
      targetPort: 80  # Target port on Pods 🎯
  type: NodePort  # Type of Service: NodePort for external access 🏃‍♂️🏢

Deploying Grafana with LoadBalancer:

To further demonstrate declarative YAML, let's create a deployment and service file to deploy Grafana with a NodePort service. You can customize the Grafana deployment based on your requirements.

deployment.yaml
apiVersion: apps/v1  # Kubernetes API version for Deployments 🌐
kind: Deployment  # Type of resource: a Deployment 🚀
metadata:
  name: grafana-deployment  # Name of the Deployment 🏷️
spec:
  replicas: 1  # Number of desired replicas of the app 🔄
  selector:
    matchLabels:
      app: grafana  # Label selector for matching Pods 🏷️
  template:
    metadata:
      labels:
        app: grafana  # Labels for Pods created by this Deployment 🏷️
    spec:
      containers:
        - name: grafana-container  # Container name 🐳
          image: grafana/grafana:latest  # Docker image used for the container 📦
          ports:
            - containerPort: 3000  # Port configuration for the container 🔌
service.yaml
apiVersion: v1  # Kubernetes API version for Services 🌐
kind: Service  # Type of resource: a Service 🚀
metadata:
  name: grafana-service  # Name of the Service 🏷️
spec:
  selector:
    app: grafana  # Label selector for matching Pods 🏷️
  ports:
    - protocol: TCP  # Protocol used for the port 🔌
      port: 80  # Port exposed by the Service 🌐
      targetPort: 3000  # Target port on Pods 🎯
  type: LoadBalancer  # Type of Service: LoadBalancer link for external access 🏃‍♂️🏢

In this example, we're deploying Grafana using declarative YAML files, allowing for a well-defined and repeatable process.

Website Deployment

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wavecafe  # 🚀 Name of the Deployment
spec:
  replicas: 1  # 🚶 Number of desired replicas
  selector:
    matchLabels:
      app: cafe  # 🏷️ Selector to match pods with the label "app: cafe"
  template:
    metadata:
      labels:
        app: cafe  # 🏷️ Labels applied to pods created by this template
    spec:
      containers:
        - name: my-app-container  # 📦 Name of the container
          image: saitejairrinki/wavecafe:v1  # 🐳 Docker image to use
          ports:
            - name: cafe-port  # 🌐 Name of the port
              containerPort: 80  # 🚪 Port that the container listens on
service.yaml
apiVersion: v1
kind: Service
metadata:
  name: wave-cafe  # ☕ Name of the Service
spec:
  selector:
    app: cafe  # 🏷️ Select pods with the label "app: cafe"
  ports:
    - protocol: TCP  # 🌐 Protocol for the port
      port: 80  # 🚪 Port on the Service
      targetPort: cafe-port  # 🚀 Port on the pods to forward traffic to
  type: NodePort  # 🏢 Expose the Service as a NodePort