You've successfully created an EKS cluster, but it's currently empty. This lesson will guide you through deploying your first containerized application. You'll learn to write a Deployment manifest, a YAML file that declaratively tells Kubernetes how to run your application. This manifest specifies the container image, the number of copies to run, resource allocation, and health checks.
In Kubernetes, you don't run containers manually. Instead, you declare your desired state in a manifest, and Kubernetes works to maintain that state. If a container crashes, Kubernetes automatically restarts it. This declarative approach is what makes Kubernetes powerful and reliable for production applications. By the end of this lesson, you'll have a web application running on your EKS cluster, and you'll understand the fundamental workflow for deploying any containerized application.
A Deployment is a Kubernetes resource that manages a set of identical Pods. Before we go further, let's clarify what a Pod is. A Pod is the smallest deployable unit in Kubernetes, and it contains one or more containers that share networking and storage. In most cases, a Pod contains just one container, which is your application. When you create a Deployment, Kubernetes creates Pods based on the template you provide and ensures that the desired number of Pods are always running.
The reason we use Deployments instead of creating Pods directly is that Deployments provide several critical features. First, they handle replication, meaning you can easily run multiple copies of your application for high availability and load distribution. Second, they provide self-healing capabilities. If a Pod crashes or a node fails, the Deployment automatically creates replacement Pods. Third, they enable rolling updates, which means you can update your application to a new version without downtime by gradually replacing old Pods with new ones. Fourth, they maintain a history of your deployments, allowing you to roll back to a previous version if something goes wrong.
When you create a Deployment, you specify how many replicas you want. A replica is simply a copy of your Pod. If you set replicas to 3, Kubernetes will ensure that exactly three Pods are running at all times. If one Pod crashes, Kubernetes immediately creates a new one to maintain the desired count. If you manually delete a Pod, Kubernetes replaces it. This automatic management is what makes Kubernetes reliable for production workloads.
The Deployment also acts as a controller that continuously monitors the actual state of your Pods and compares it to the desired state you declared in your manifest. If there's a difference, the Deployment takes action to reconcile them. This control loop is fundamental to how Kubernetes operates. You declare what you want, and Kubernetes figures out how to make it happen and keep it that way.
Let's look at the structure of a Deployment manifest and understand each section. Here's the complete manifest you'll use to deploy your web application:
Every Kubernetes manifest is composed of several key fields that define the resource. Let's break down the main sections of this Deployment manifest.
apiVersionandkind: These required fields tell Kubernetes which API to use (apps/v1) and what type of resource is being defined (Deployment).metadata: This section contains information about the Deployment itself, such as itsname(my-web-deploy) and thenamespace(apps) where it will be created. Namespaces help organize resources within a cluster.spec: This is the most important section, as it defines the desired state of the Deployment.replicas: Inside thespec, this field specifies how many identical Pods to run. Here, we're asking for1.selector: This tells the Deployment how to find the Pods it manages. It must match the labels defined in the Pod template.template: This section defines the Pod that the Deployment will create. It has its ownmetadata(with labels that match the selector) andspec(which defines the containers).
Together, these fields form a complete declaration of your application's desired state. The selector and the template's labels are particularly important, as they create the link that allows the Deployment to manage its Pods. If these labels don't match, the Deployment controller won't know which Pods belong to it, and your application won't run as expected.
The template.spec section defines the containers that will run in each Pod. Here you specify everything from the container image to its resource requirements and health checks. Let's examine the key fields for configuring your container.
image: This is the most critical field, specifying the container image to pull from a registry. In this example, we use an Amazon ECR URI:123456789012.dkr.ecr.us-east-1.amazonaws.com/my-web-app:1.0.0. This is the standard pattern for EKS deployments, where your account ID is123456789012, your region isus-east-1, your repository ismy-web-app, and your image tag is1.0.0. ECR is the natural choice for EKS because it integrates seamlessly with AWS IAM for authentication and provides fast, private image storage within your AWS account. For the practice exercises using kind, you'll substitute this withnginx:alpinesince kind doesn't have access to your ECR repositories.imagePullPolicy: Set toAlways, this forces Kubernetes to pull the image every time a Pod is created. This is useful in development to ensure you're always running the latest code.ports: This declares that the container listens on a specific port (containerPort: 80). While this is informational and doesn't open the port on the network, it's a best practice and is used by other Kubernetes objects like Services.env: This allows you to set environment variables inside the container. Environment variables are commonly used to pass configuration to containers, such as logging levels, feature flags, database connection strings, or application metadata likeAPP_NAME: "my-web-app". This is a standard way to pass configuration to applications without rebuilding container images.
These configuration options provide a powerful way to define how your application runs. By externalizing configuration through environment variables and clearly declaring ports, you create a portable and well-documented application that is easy for both Kubernetes and other developers to understand.
The resources section is where you specify how much CPU and memory your container needs, which is crucial for cluster stability and application performance. This section is divided into two parts that serve different purposes.
requests: This specifies the minimum amount of resources Kubernetes guarantees for your container. Kubernetes uses this value for scheduling, ensuring the Pod is placed on a node with enough available capacity. For example,cpu: "100m"requests one-tenth of a CPU core, andmemory: "128Mi"requests 128 mebibytes of memory.limits: This specifies the maximum amount of resources your container is allowed to use. If your container exceeds its CPU limit, it will be throttled. If it exceeds its memory limit, it will be terminated and restarted (an OOMKilled event). Limits prevent a single faulty application from consuming all node resources and impacting other applications.
Setting appropriate requests and limits is a balancing act. Requests ensure your application has what it needs to start and run reliably, while limits protect the cluster from misbehaving applications. In a production environment, you would monitor your application's performance and resource consumption over time to fine-tune these values for optimal efficiency and stability.
Health checks are essential for running reliable applications, as they allow Kubernetes to automatically detect and recover from failures. There are two types of probes, and they serve different purposes.
readinessProbe: This probe determines if the container is ready to start accepting traffic. If the probe fails, Kubernetes stops sending traffic to the Pod but does not restart it. This is useful during application startup when the process is running but not yet fully initialized (e.g., warming up a cache).livenessProbe: This probe determines if the container is still healthy. If the probe fails repeatedly, Kubernetes will kill the container and restart it. This helps recover from application deadlocks or other unrecoverable states where the process is running but not functional.
In this manifest, both probes are configured to send an HTTP GET request to the / path on port 80. The initialDelaySeconds field provides a grace period after the container starts before the first probe is run, and periodSeconds defines how often the check is performed. By using both readiness and liveness probes, you create a robust, self-healing system. The readiness probe ensures that traffic is only sent to healthy, fully-initialized application instances, while the liveness probe acts as a safety net, automatically restarting a failed application to restore service without manual intervention.
Now that you understand the Deployment manifest, let's deploy it to your cluster. Before you can apply the Deployment, you need to create the apps namespace because the Deployment specifies namespace: apps in its metadata. Creating a namespace is straightforward:
When you run this command, you'll see output confirming the namespace was created:
Namespaces in Kubernetes provide a way to organize and isolate resources. By creating a dedicated namespace for your applications, you're separating them from system components that run in the kube-system namespace. This makes it easier to manage permissions, apply policies, and view resources. You only need to create the namespace once — it will continue to exist until you explicitly delete it.
Now you can apply your Deployment manifest. Save the manifest shown earlier to a file named deployment.yaml. For the kind practice environment, replace the ECR image URI with nginx:alpine since kind doesn't have access to ECR. In a production EKS cluster, you would keep the ECR URI. Then run:
The -f flag tells kubectl to read the manifest from a file. When you run this command, you'll see output indicating that the Deployment was created:
The apply command is idempotent, which means you can run it multiple times safely. If the Deployment doesn't exist, kubectl creates it. If it already exists, kubectl updates it to match your manifest. This makes apply the preferred command for managing Kubernetes resources because you can use the same command whether you're creating or updating.
When you apply the Deployment, several things happen behind the scenes. First, Kubernetes creates the Deployment resource in the apps namespace. Then, the Deployment controller sees the new Deployment and creates a ReplicaSet, which is an intermediate resource that manages the Pods. The ReplicaSet then creates the Pod based on the template you provided. The Pod is scheduled to one of your worker nodes, and the kubelet on that node pulls the container image and starts the container. The readiness probe begins checking if the container is ready, and once it succeeds, the Pod is marked as ready.
You can check the status of your Deployment with this command:
The -n apps flag tells kubectl to look in the apps namespace. You should see output like this:
This output shows that your Deployment has 1 out of 1 replicas ready, which means your Pod is running and passed its readiness probe. The UP-TO-DATE column shows how many Pods are running the latest version of your template, and the AVAILABLE column shows how many Pods are ready to accept traffic.
You can also check the Pods directly:
You should see output showing your Pod:
The Pod name is generated automatically by combining the Deployment name, the ReplicaSet hash, and a random suffix. The READY column shows that 1 out of 1 containers in the Pod are ready. The STATUS column shows that the Pod is running. The RESTARTS column shows how many times the container has been restarted, which should be 0 for a healthy Pod.
If you want to see more details about your Pod, you can use the describe command:
This command shows detailed information about the Pod, including its events, which tell you what happened during the Pod's lifecycle. You'll see events like "Scheduled," "Pulling," "Pulled," and "Started," which show the progression from scheduling to running. If there were any problems, such as image pull errors or probe failures, they would appear in the events section.
In the upcoming practice exercises, we'll use kind (Kubernetes in Docker) to simulate an EKS cluster environment. kind is a tool for running local Kubernetes clusters using Docker containers as cluster nodes. It provides the exact same kubectl experience as EKS, allowing you to practice all the commands you've learned without waiting for AWS provisioning or incurring costs.
The cluster setup runs automatically in the background when you start each exercise and takes about 30 seconds. Once ready, you'll have a fully functional Kubernetes cluster where all the kubectl commands work identically to how they would on Amazon EKS. Since kind runs locally, you'll use publicly available images like nginx:alpine for practice, but the manifest structure and deployment workflow are identical to what you'd use with ECR images in production. This hands-on practice will solidify your understanding of deployments and prepare you for working with production EKS clusters.
In this lesson, you deployed your first application to a Kubernetes cluster by writing and applying a Deployment manifest. You learned how this manifest defines your application's desired state, from the container image and replica count to resource allocation and health checks. This workflow—writing a manifest, applying it, and verifying the result—is fundamental to managing applications on Kubernetes.
The Deployment you created incorporates several best practices, including using namespaces for organization, setting resource requests and limits for stability, and configuring readiness and liveness probes for automated health management. In the upcoming practice exercises, you'll build on these skills by customizing manifests and troubleshooting common issues, preparing you for the next unit on operating your applications with kubectl commands.
