Technical Theory

Primitives for Robust Deployments

Introduction

This tutorial will guide you through the fundamental Kubernetes primitives that are crucial for deploying and managing robust, self-healing applications. We’ll cover Pods, ReplicaSets, Deployments, and Services, demonstrating how they work together to ensure your application’s availability and scalability.

Prerequisites:

  • A working Kubernetes cluster (e.g., Minikube, Kind, or a cloud provider cluster)
  • kubectl installed and configured to connect to your cluster
  • Basic understanding of containerization concepts (e.g., Docker)

Task 1: Understanding Pods

Pods are the smallest deployable units in Kubernetes. They represent a single instance of a running process in your cluster. A pod can contain one or more containers that share network and storage resources.

  1. Create a Pod definition file named pod-definition.yaml:

    NODE_TYPE // yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: my-first-pod
      labels:
        app: my-app
    spec:
      containers:
      - name: my-container
        image: nginx:latest
        ports:
        - containerPort: 80
    This YAML file defines a Pod named my-first-pod running a single container based on the nginx:latest image. The container exposes port 80.
  2. Deploy the Pod using kubectl:

    NODE_TYPE // bash
    kubectl apply -f pod-definition.yaml
    NODE_TYPE // output
    pod/my-first-pod created
  3. Verify the Pod is running:

    NODE_TYPE // bash
    kubectl get pods
    NODE_TYPE // output
    NAME           READY   STATUS    RESTARTS   AGE
    my-first-pod   1/1     Running   0          10s
  4. Access the nginx default page:

    First, expose the pod using kubectl port-forward:

    NODE_TYPE // bash
    kubectl port-forward pod/my-first-pod 8080:80

    Then, open a web browser and navigate to http://localhost:8080. You should see the default nginx welcome page.

    kubectl port-forward should only be used for local debugging or quick access. For production deployments, use Services (explained later). Interrupt the kubectl port-forward by pressing Ctrl+C after verifying the nginx page.

Task 2: Introducing ReplicaSets

ReplicaSets ensure that a specified number of Pod replicas are running at any given time. If a Pod fails, the ReplicaSet automatically creates a new one to maintain the desired replica count.

  1. Create a ReplicaSet definition file named replicaset-definition.yaml:

    NODE_TYPE // yaml
    apiVersion: apps/v1
    kind: ReplicaSet
    metadata:
      name: my-replicaset
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          containers:
          - name: my-container
            image: nginx:latest
            ports:
            - containerPort: 80
    This YAML defines a ReplicaSet named my-replicaset that manages 3 replicas of a Pod with the label app: my-app. The selector field ensures that the ReplicaSet only manages Pods with this label.
  2. Deploy the ReplicaSet:

    NODE_TYPE // bash
    kubectl apply -f replicaset-definition.yaml
    NODE_TYPE // output
    replicaset.apps/my-replicaset created
  3. Verify the ReplicaSet and its Pods:

    NODE_TYPE // bash
    kubectl get replicasets
    kubectl get pods -l app=my-app
    NODE_TYPE // output
    NAME             DESIRED   CURRENT   READY   AGE
    my-replicaset   3         3         3       20s
    
    NAME                 READY   STATUS    RESTARTS   AGE
    my-replicaset-4lm9n   1/1     Running   0          20s
    my-replicaset-klbrx   1/1     Running   0          20s
    my-replicaset-q7n2z   1/1     Running   0          20s

    You should see that the ReplicaSet has created and is managing three Pods.

  4. Simulate a Pod failure:

    NODE_TYPE // bash
    kubectl delete pod my-replicaset-4lm9n
    NODE_TYPE // output
    pod "my-replicaset-4lm9n" deleted
  5. Observe the ReplicaSet recreate the Pod:

    NODE_TYPE // bash
    kubectl get pods -l app=my-app --watch
    NODE_TYPE // output
    NAME                 READY   STATUS    RESTARTS   AGE
    my-replicaset-klbrx   1/1     Running   0          5m30s
    my-replicaset-q7n2z   1/1     Running   0          5m30s
    my-replicaset-zxb5w   1/1     Running   0          4s

    The --watch flag allows you to see the changes in real-time. You’ll notice that the deleted Pod is replaced by a new one, demonstrating the self-healing capability of ReplicaSets. Press Ctrl+C to stop watching.

Task 3: Introducing Deployments

Deployments provide declarative updates for Pods and ReplicaSets. They manage the rollout and rollback of changes to your application, ensuring minimal downtime and easy version management. Deployments use ReplicaSets under the hood to manage the underlying Pods.

  1. Create a Deployment definition file named deployment-definition.yaml:

    NODE_TYPE // yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-deployment
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          containers:
          - name: my-container
            image: nginx:1.23  # Specify the nginx version
            ports:
            - containerPort: 80
    This YAML defines a Deployment named my-deployment that manages 3 replicas of a Pod with the label app: my-app. The image is set to nginx:1.23.
  2. Deploy the Deployment:

    NODE_TYPE // bash
    kubectl apply -f deployment-definition.yaml
    NODE_TYPE // output
    deployment.apps/my-deployment created
  3. Verify the Deployment and its Pods:

    NODE_TYPE // bash
    kubectl get deployments
    kubectl get pods -l app=my-app
    NODE_TYPE // output
    NAME            READY   UP-TO-DATE   AVAILABLE   AGE
    my-deployment   3/3     3            3           25s
    
    NAME                             READY   STATUS    RESTARTS   AGE
    my-deployment-7d66f98b76-6686z   1/1     Running   0          25s
    my-deployment-7d66f98b76-8f8mv   1/1     Running   0          25s
    my-deployment-7d66f98b76-97r5l   1/1     Running   0          25s

    You should see the Deployment is managing three Pods. Note that the Pod names are prefixed with the Deployment name and a generated hash.

  4. Update the Deployment (e.g., change the nginx version):

    NODE_TYPE // bash
    kubectl set image deployment/my-deployment my-container=nginx:1.24
    NODE_TYPE // output
    deployment.apps/my-deployment image updated
  5. Monitor the rollout:

    NODE_TYPE // bash
    kubectl rollout status deployment/my-deployment
    NODE_TYPE // output
    deployment.apps/my-deployment successfully rolled out

    This command shows the progress of the update. Kubernetes performs a rolling update, gradually replacing the old Pods with new ones.

  6. Verify the updated Pods:

    NODE_TYPE // bash
    kubectl get pods -l app=my-app

    The output should show that the Pods are running the new nginx:1.24 image.

Task 4: Exposing Applications with Services

Services provide a stable IP address and DNS name for accessing your application. They abstract away the underlying Pods, allowing you to scale and update your application without affecting clients.

  1. Create a Service definition file named service-definition.yaml:

    NODE_TYPE // yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      selector:
        app: my-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      type: LoadBalancer
    This YAML defines a Service named my-service that selects Pods with the label app: my-app. It exposes port 80 on the Service and forwards traffic to port 80 on the selected Pods. The type: LoadBalancer will provision a cloud load balancer (if supported by your Kubernetes environment). If you’re using Minikube or Kind, you’ll need to use NodePort type instead.
  2. Deploy the Service:

    NODE_TYPE // bash
    kubectl apply -f service-definition.yaml
    NODE_TYPE // output
    service/my-service created
  3. Verify the Service:

    NODE_TYPE // bash
    kubectl get service my-service
    NODE_TYPE // output
    NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
    my-service   LoadBalancer   10.100.5.123   <pending>     80:30000/TCP   15s

    The EXTERNAL-IP will be provisioned by your cloud provider (if using a cloud provider). If you are using Minikube, run minikube service my-service to get the URL of the service.

  4. Access the application through the Service:

    Once the EXTERNAL-IP is available (or you have the Minikube URL), you can access your application through the Service. Open a web browser and navigate to the provided IP address or URL. You should see the default nginx welcome page.

    If you’re using Minikube, you can access the service by running minikube service my-service --url. This will output the URL you can use to access your application.

Conclusion

In this tutorial, you learned about the core Kubernetes primitives: Pods, ReplicaSets, Deployments, and Services. You saw how they work together to create a robust, self-healing application deployment. You created Pods, used ReplicaSets to ensure high availability, leveraged Deployments for rolling updates, and exposed your application with Services. Mastering these primitives is essential for building and managing applications in Kubernetes.

Next Topic