-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 8: Kubernetes Configuration
Welcome to Chapter 8! In our journey so far, we've built our three microservices: the Shopfront, Product Catalogue, and Stock Manager. We learned how they talk to each other using REST APIs and exchange DTOs. We also mastered packaging each service into portable Docker Images using Dockerfiles, and how to run and manage this multi-container application easily on a single machine using Docker Compose.
Docker Compose is fantastic for local development and testing. But what about running our application in a real-world production environment? This usually involves:
- Running our services on multiple servers, not just one.
- Automatically scaling up or down the number of copies (instances) of each service based on demand.
- Automatically restarting services if they crash or become unhealthy.
- Rolling out updates to services without downtime.
- Ensuring services can still find each other reliably even if they are running on different machines and new copies are constantly starting or stopping.
Doing all of this manually across a cluster of machines is incredibly complex. This is where Kubernetes comes in.
Imagine managing a vast warehouse filled with thousands of those standardized Docker boxes (containers) we talked about. You need to:
- Decide which boxes go onto which shelf (server).
- Make sure there are always enough copies of the popular boxes.
- Replace any box that breaks immediately.
- Connect boxes that need to talk to each other, even if they are on different shelves.
- Receive new versions of boxes and swap them out without stopping the whole operation.
This task quickly becomes impossible for a human to do efficiently or reliably at scale.
Kubernetes (often shortened to K8s because there are 8 letters between the 'K' and the 's') is a powerful platform for automating the deployment, scaling, and management of containerized applications across a cluster of machines.
Think of Kubernetes as the sophisticated automated warehouse management system. You tell the system what you want the state of your warehouse to be (e.g., "I need 10 copies of the 'Shopfront' box running," "the 'Shopfront' box needs to talk to the 'Product Catalogue' box"), and Kubernetes figures out how to make that happen and continuously works to maintain that desired state.
Instead of giving Kubernetes step-by-step instructions ("start container A, then start container B, then connect them"), you give it a declarative configuration describing the end result you want. This configuration is typically written in YAML files.
These YAML files are like the instruction manuals for the automated warehouse system. They tell K8s about the different parts of your application and how they should behave in the cluster.
Kubernetes uses several core building blocks, configured via YAML, to manage your applications:
-
Pods: The smallest deployable unit in Kubernetes. A Pod is a group of one or more containers that are deployed together on the same machine and share resources like network and storage. For most simple microservices, a Pod contains just one container (your application). Think of a Pod as the minimal unit that must stick together – like putting the main application box and any tiny required accessories box together on a single hand truck within the warehouse.
# This is NOT a full file, just showing a Pod definition structure apiVersion: v1 kind: Pod # We are defining a Pod metadata: name: my-app-pod spec: containers: - name: my-app-container image: my-docker-image:latest # Which Docker image to run ports: - containerPort: 8080 # Which port the container listens on
-
Deployments: This is how you manage stateless applications (like our microservices). A Deployment tells Kubernetes how to create and update Pods. You specify which Docker image to use, how many replicas (copies) of the Pod you want running, and update strategies. Deployments ensure that the desired number of Pods are always running and handle replacing Pods if they fail. Think of a Deployment as the instruction for the warehouse manager: "Always keep 5 'Shopfront' hand trucks (Pods) ready to go using this specific blueprint (Image). If one breaks, replace it automatically."
# This is NOT a full file, just showing a Deployment structure apiVersion: apps/v1 kind: Deployment # We are defining a Deployment metadata: name: my-app-deployment spec: replicas: 3 # I want 3 copies (Pods) of my application running selector: matchLabels: app: my-app # How this Deployment finds the Pods it manages template: # This is the blueprint for the Pods this Deployment creates metadata: labels: app: my-app # Labels applied to the Pods spec: containers: - name: my-app-container image: my-docker-image:latest ports: - containerPort: 8080
Notice the
template
section looks like a Pod definition nested inside the Deployment. Theselector
matches thelabels
in thetemplate
to connect the Deployment to its Pods. -
Services: Pods are ephemeral – they can be created, killed, and recreated with new IP addresses. How do other Pods (or external users) find them reliably? Kubernetes Services solve this. A Service provides a stable way to access a set of Pods (usually defined by matching labels). It gives them a consistent IP address and DNS name within the cluster, even as the underlying Pods change. Think of a Service as the stable internal phone number or public loading dock for a specific department (a group of Pods). You call the number/go to the dock, and the Service routes you to one of the available team members/hand trucks (Pods).
# This is NOT a full file, just showing a Service structure apiVersion: v1 kind: Service # We are defining a Service metadata: name: my-app-service # The stable name for this service (used for discovery) spec: selector: app: my-app # This Service targets Pods with the label 'app: my-app' ports: - protocol: TCP port: 80 # The port this Service exposes targetPort: 8080 # The port the Pods are listening on
The
selector
connects the Service to the relevant Pods (those managed by the Deployment, because their labels match). Theport
is the port the Service itself listens on, andtargetPort
is the port inside the Pods that the Service should forward traffic to.
Now let's see how these concepts apply to our specific project. We need YAML files to define a Deployment and a Service for each of our three microservices. These files are typically stored in a kubernetes
directory.
Let's look at the kubernetes/stockmanager-service.yaml
file. It actually defines two things: a Service and a Deployment for the Stock Manager. Kubernetes YAML files can often contain definitions for multiple resources separated by ---
.
# kubernetes/stockmanager-service.yaml
--- # This separates the Service definition from the Deployment definition
apiVersion: v1
kind: Service # First, define the Service for Stock Manager
metadata:
name: stockmanager # The stable name: 'stockmanager'
labels:
app: stockmanager # Labels for organizing and selecting
spec:
type: NodePort # How the Service is exposed (NodePort for external access)
selector:
app: stockmanager # This Service targets Pods with label 'app: stockmanager'
ports:
- protocol: TCP
port: 8030 # The Service port (what other services/users call)
name: http
# targetPort defaults to port if not specified, so it will be 8030 too
# nodePort is automatically assigned if type is NodePort and nodePort isn't set
---
apiVersion: apps/v1 # Version for Deployment kind
kind: Deployment # Second, define the Deployment for Stock Manager
metadata:
name: stockmanager # The name of the Deployment
spec:
selector:
matchLabels:
app: stockmanager # This Deployment manages Pods with label 'app: stockmanager'
replicas: 1 # Start with 1 copy (Pod) of the Stock Manager
template: # The blueprint for the Pods
metadata:
labels:
app: stockmanager # Apply the label 'app: stockmanager' to Pods created
spec:
containers:
- name: stockmanager # Name of the container inside the Pod
image: danielbryantuk/djstockmanager:latest # Use this Docker image
ports:
- containerPort: 8030 # The container listens on port 8030 (internal to Pod)
livenessProbe: # Configuration for health checks
httpGet: # How to check health: make an HTTP GET request
path: /health # The health check endpoint path
port: 8030 # The port to check on
initialDelaySeconds: 30 # Wait 30 seconds before the first check
timeoutSeconds: 1 # Consider the check failed if no response after 1 second
-
Service: Creates a stable name (
stockmanager
) and IP address for the Stock Manager. It uses theapp: stockmanager
label selector to find the correct Pods. It exposes port8030
.type: NodePort
means K8s will also expose this service on a high, static port across all nodes in the cluster, potentially allowing external access (though typically an Ingress is used for production). -
Deployment: Ensures that one Pod (
replicas: 1
) with the labelapp: stockmanager
is running. The Pod template specifies to use thedanielbryantuk/djstockmanager:latest
Docker image and that the container inside listens on port8030
. ThelivenessProbe
tells K8s how to check if a running Stock Manager Pod is still healthy; if the/health
endpoint doesn't respond on port 8030, K8s will consider the Pod unhealthy and restart it.
The kubernetes/productcatalogue-service.yaml
is very similar:
# kubernetes/productcatalogue-service.yaml
---
apiVersion: v1
kind: Service # Define the Service for Product Catalogue
metadata:
name: productcatalogue # Stable name: 'productcatalogue'
labels:
app: productcatalogue
spec:
type: NodePort # Exposed via NodePort
selector:
app: productcatalogue # Targets Pods with label 'app: productcatalogue'
ports:
- protocol: TCP
port: 8020 # Service port
name: http
---
apiVersion: apps/v1
kind: Deployment # Define the Deployment for Product Catalogue
metadata:
name: productcatalogue
spec:
selector:
matchLabels:
app: productcatalogue # Manages Pods with label 'app: productcatalogue'
replicas: 1 # Start with 1 copy
template:
metadata:
labels:
app: productcatalogue # Apply label to Pods
spec:
containers:
- name: productcatalogue # Container name
image: danielbryantuk/djproductcatalogue:latest # Docker image
ports:
- containerPort: 8020 # Container listens on 8020
livenessProbe: # Health check config
httpGet:
path: /healthcheck # Note: Product Catalogue health check is on /healthcheck
port: 8025 # Note: Product Catalogue health check is on admin port 8025
initialDelaySeconds: 30
timeoutSeconds: 1
-
Service: Provides the stable name
productcatalogue
, targets Pods with the matching label, and exposes port8020
. -
Deployment: Ensures one Pod with the correct label is running, using the specified Docker image and exposing container port
8020
. Note thelivenessProbe
path (/healthcheck
) and port (8025
) match the Dropwizard admin port setup we saw in Chapter 2.
Finally, the kubernetes/shopfront-service.yaml
for the user-facing service:
# kubernetes/shopfront-service.yaml
---
apiVersion: v1
kind: Service # Define the Service for Shopfront
metadata:
name: shopfront # Stable name: 'shopfront'
labels:
app: shopfront
spec:
type: NodePort # Exposed via NodePort (allows external access)
selector:
app: shopfront # Targets Pods with label 'app: shopfront'
ports:
- protocol: TCP
port: 8010 # Service port (what users call, and other services *could* call)
name: http
# nodePort will be assigned automatically for external access
---
apiVersion: apps/v1
kind: Deployment # Define the Deployment for Shopfront
metadata:
name: shopfront
spec:
selector:
matchLabels:
app: shopfront # Manages Pods with label 'app: shopfront'
replicas: 1 # Start with 1 copy
template:
metadata:
labels:
app: shopfront # Apply label to Pods
spec:
containers:
- name: djshopfront # Container name
image: danielbryantuk/djshopfront:latest # Docker image
ports:
- containerPort: 8010 # Container listens on 8010
livenessProbe: # Health check config
httpGet:
path: /health # Health check endpoint
port: 8010 # Health check port
initialDelaySeconds: 30
timeoutSeconds: 1
-
Service: Provides the stable name
shopfront
, targets Pods with the matching label, and exposes port8010
. Since this is the user-facing service,type: NodePort
is important here as it makes the Service accessible from outside the Kubernetes cluster on a specific port of the cluster nodes. -
Deployment: Ensures one Pod with the correct label is running, using the specified Docker image and exposing container port
8010
. Includes a health check on/health
at port 8010.
Remember the Shopfront's configuration from Chapter 1 and Chapter 4:
# shopfront/src/main/resources/application.properties
productCatalogueUri = http://productcatalogue:8020
stockManagerUri = http://stockmanager:8030
Just like with Docker Compose, the Shopfront needs to find the other services using these names. Kubernetes provides DNS-based Service Discovery automatically.
When you create a Kubernetes Service with metadata.name: productcatalogue
, Kubernetes automatically creates a DNS entry within the cluster for productcatalogue
. Any Pod in the same namespace (a way to organize resources in K8s) can resolve the hostname productcatalogue
to the IP address of the productcatalogue
Service. The Service then routes the traffic to a healthy Pod managed by the productcatalogue
Deployment.
sequenceDiagram
participant ShopfrontPod as Shopfront Pod
participant K8sDNS as Kubernetes DNS
participant K8sServicePC as Product Catalogue Service
participant ProductCataloguePod as Product Catalogue Pod(s)
ShopfrontPod->>K8sDNS: Lookup hostname "productcatalogue"
K8sDNS-->>ShopfrontPod: Return IP address of Product Catalogue Service
ShopfrontPod->>K8sServicePC: Send HTTP request to Service IP on port 8020
K8sServicePC->>ProductCataloguePod: Forward request to one of the Product Catalogue Pods on port 8020
ProductCataloguePod-->>K8sServicePC: Send HTTP response
K8sServicePC-->>ShopfrontPod: Forward HTTP response
The same flow happens when the Shopfront Pod calls stockmanager:8030
. This seamless internal routing means the Shopfront code doesn't need to change whether it's running locally via Docker Compose or in a Kubernetes cluster!
With these YAML files ready, deploying the application to a Kubernetes cluster is straightforward using the kubectl
command-line tool (the main way to interact with K8s clusters).
Assuming you have kubectl
configured to point to your cluster (like a local Minikube or a cloud cluster), you would simply apply the configuration files:
kubectl apply -f kubernetes/stockmanager-service.yaml
kubectl apply -f kubernetes/productcatalogue-service.yaml
kubectl apply -f kubernetes/shopfront-service.yaml
Output:
service/stockmanager created
deployment.apps/stockmanager created
service/productcatalogue created
deployment.apps/productcatalogue created
service/shopfront created
deployment.apps/shopfront created
When you run these commands, you tell Kubernetes:
- "Create a Service and Deployment for Stock Manager as described in this file."
- "Create a Service and Deployment for Product Catalogue as described in this file."
- "Create a Service and Deployment for Shopfront as described in this file."
Kubernetes then takes over:
- It creates the Service objects, setting up internal DNS entries.
- It creates the Deployment objects.
- Each Deployment sees it needs
replicas: 1
and starts creating a Pod using the specified Docker image. - It pulls the Docker images (e.g.,
danielbryantuk/djshopfront:latest
) from a registry if they aren't already on the cluster node. - It schedules the Pods onto available nodes in the cluster.
- Once the Pods start, the Deployments monitor them. If a Pod becomes unhealthy (fails the
livenessProbe
), the Deployment automatically kills it and starts a replacement. - The Services automatically detect the healthy Pods matching their label selectors and start routing traffic to them.
You can then check the status using commands like kubectl get pods
, kubectl get deployments
, and kubectl get services
.
To access the Shopfront from outside the cluster (because we used type: NodePort
), you would typically find the external IP of a node and the specific nodePort
assigned to the shopfront
Service (using kubectl get services shopfront
).
Using YAML files to configure Kubernetes provides major advantages for managing applications in a cluster:
- Declarative: You describe the desired state, and K8s makes it happen.
- Automated Management: K8s handles deployment, scaling, self-healing, and updates.
- Service Discovery: Services provide stable internal access via DNS names.
- Abstraction: You deploy applications without needing to know the specifics of the underlying machines.
-
Scalability: Easily change the
replicas
count in the Deployment YAML or via commands to scale the number of Pods.
While setting up the initial YAML files can seem complex, they provide a robust and repeatable way to define and manage your application's presence in a distributed cluster environment, far exceeding the capabilities of Docker Compose for production use cases.
In this chapter, we transitioned from managing containers on a single machine with Docker Compose to orchestrating them across a cluster using Kubernetes. We learned that Kubernetes uses declarative YAML configuration to define the desired state of our application. We were introduced to key K8s concepts: Pods (the smallest unit, containing one or more containers), Deployments (managing stateless applications, ensuring desired replicas, and handling updates/self-healing), and Services (providing stable network access and service discovery via DNS).
We examined the YAML files used in the project to define a Service and a Deployment for each microservice (Shopfront, Product Catalogue, Stock Manager), highlighting how labels
and selectors
connect Services to Deployments/Pods, how container images and ports are specified, and how health checks (livenessProbe
) are configured. Crucially, we saw how Kubernetes' built-in DNS service discovery allows the Shopfront to find the other services using their stable Service names (productcatalogue
, stockmanager
), just like in our Docker Compose setup. Finally, we touched upon how using kubectl apply
brings this configuration to life in a Kubernetes cluster.
We now have our application built, containerized, and configured for deployment in a powerful, distributed environment. The final step in our tutorial is to verify that our entire application works correctly from end-to-end when deployed in this manner.
Next Chapter: End-to-End Tests
Doc by Reyas Khan. References: [1], [2], [3]
TO Begin: Start here