Microservices: Building a City of Apps
“Think of your software as a bustling city — each service is a building, and Kubernetes is the city planner ensuring everything runs smoothly.”
1. Introduction: From Monoliths to Microservices
In the past, apps were like massive skyscrapers: a single structure containing everything. These “monoliths” worked well initially, but as the app grew, adding features or fixing bugs became like trying to renovate a 50-story building while people still lived in it.
Enter microservices — a way to break down that skyscraper into smaller, self-contained buildings, each responsible for one function. Each building (or service) can be updated, scaled, or repaired independently. Kubernetes (K8s) is like the city planner ensuring all buildings stay connected, powered, and operational.
2. How Kubernetes Fits In
Managing dozens (or hundreds) of services manually is impossible. Kubernetes acts as the city planner:
Deployment Management: Ensures every service (building) is deployed in the right place.
Scaling: Adds more replicas of a service during high traffic (like hiring more cashiers during a sale).
Self-Healing: Restarts services (containers) if they crash.
Networking: Connects all the buildings through defined “roads” (APIs).
3. Understanding Microservices with Kubernetes
The Problem with Monolithic Architectures
Imagine an e-commerce platform where authentication, user management, and order processing are part of a single monolithic application. What happens if one part crashes? The whole system goes down. Scaling is also difficult — if the order system is slow, we can’t scale it separately.
Microservices solve this by separating concerns into independent services. Each service:
Runs in its own container (Docker).
Communicates via REST APIs or message queues.
Can be scaled independently.
Kubernetes (K8s) takes this further by:
Deploying services automatically.
Handling failures by restarting crashed services.
Scaling services based on traffic.
4. Microservices: A Real-World Analogy
Imagine an e-commerce platform like Amazon. Instead of bundling every functionality — login, search, cart, payment — into one monolith, microservices break them into smaller, independent services.
For instance:
The Authentication Service handles user logins.
The Profile Service manages user details.
The Payment Service processes transactions.
These services communicate with each other via APIs, making the system flexible and scalable.
5. Designing Microservices: A Simple Example
5.1 Architecture of Our Microservices System
Our system consists of:
API Gateway: Central entry point for handling requests.
Auth Service: Manages login and JWT authentication.
User Service: Handles user data storage.
Order Service: Processes and manages orders.
Here’s a high-level architecture diagram:
The API Gateway routes incoming user requests.
The Auth Service issues JWT tokens for authentication.
The User Service manages profiles and retrieves user details.
The Order Service fetches user orders after validating authentication.
5.2 Implementing Microservices with Spring Boot and Kotlin
Service 1: Authentication Service (JWT Token Generation)
This service handles user login and generates JWT tokens.
Controller:
@RestController
@RequestMapping("/auth")
class AuthController(private val authService: AuthService) {
@PostMapping("/login")
fun login(@RequestBody request: LoginRequest): ResponseEntity<String> {
val token = authService.authenticate(request.username, request.password)
return ResponseEntity.ok(token)
}
}
data class LoginRequest(val username: String, val password: String)
Service:
@Service
class AuthService {
fun authenticate(username: String, password: String): String {
if (username == "admin" && password == "password") {
return "mock-jwt-token"
}
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials")
}
}
This service returns a JWT token upon successful authentication.
Service 2: User Service (Fetching User Profile)
Once authenticated, users can retrieve their profiles.
Controller:
@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
@GetMapping("/{id}")
fun getUserById(@PathVariable id: Long): ResponseEntity<User> {
val user = userService.getUserById(id)
return ResponseEntity.ok(user)
}
}
data class User(val id: Long, val name: String, val email: String)
Service:
@Service
class UserService {
fun getUserById(id: Long): User {
return User(id, "John Doe", "john.doe@example.com")
}
}
Service 3: Order Service (Fetching User Orders)
The Order Service retrieves all orders placed by a user.
Controller:
@RestController
@RequestMapping("/orders")
class OrderController(private val orderService: OrderService) {
@GetMapping("/{userId}")
fun getUserOrders(@PathVariable userId: Long): ResponseEntity<List<Order>> {
val orders = orderService.getOrdersByUserId(userId)
return ResponseEntity.ok(orders)
}
}
data class Order(val orderId: Long, val userId: Long, val totalAmount: Double)
Service:
@Service
class OrderService {
fun getOrdersByUserId(userId: Long): List<Order> {
return listOf(
Order(1, userId, 49.99),
Order(2, userId, 29.99)
)
}
}
6. Deploying Microservices with Kubernetes
6.1 Kubernetes Deployment for Authentication Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service
spec:
replicas: 2
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- name: auth-service
image: your-auth-service-image:latest
ports:
- containerPort: 8080
The User Service and Order Service would have similar configurations.
6.2 Kubernetes Service for Authentication
apiVersion: v1
kind: Service
metadata:
name: auth-service
spec:
selector:
app: auth-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
Each microservice is deployed inside Kubernetes as a container, and Kubernetes manages them.
7. Understanding Kubernetes Structure: How Microservices Fit into the Big Picture
Now that we’ve deployed our microservices in Kubernetes, let’s break down the core components of Kubernetes and how they work together to manage our services.
7.1 Key Kubernetes Components Explained
Kubernetes is designed to manage, scale, and maintain containerized applications. Here’s how the key components fit into our system:
1. Pods (The Smallest Unit in Kubernetes)
A pod is the smallest deployable unit in Kubernetes.
Each pod contains one or more containers.
In our example, the Auth Service, User Service, and Order Service each run inside their own pods.
If traffic increases, Kubernetes spins up more pods automatically.
Example:
Pod 1 → Auth Service (1 container)
Pod 2 → User Service (1 container)
Pod 3 → Order Service (1 container)
If traffic increases, Kubernetes might create 3 more replicas of each service..
2. Deployments (Managing Scaling and Updates)
A Deployment ensures that a specified number of replicas of a pod are running at all times.
If a pod crashes, the deployment restarts it automatically.
Example: If we set
replicas: 3
, Kubernetes ensures that 3 instances of each microservice are always running.
Deployment Example (for Auth Service):
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service
spec:
replicas: 3
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- name: auth-service
image: your-auth-service-image:latest
ports:
- containerPort: 8080
Here, Kubernetes ensures three pods are always running for the Auth Service.
3. Services (Networking & Load Balancing)
Kubernetes Services expose our pods to the outside world.
Since pods are ephemeral (they can die and restart with a new IP), a Service ensures they can always be accessed via a stable endpoint.
Load Balancing: If three instances (pods) of our Order Service are running, the service distributes traffic between them.
Service Example (for User Service):
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 80
targetPort: 8081
This ensures that no matter which pod is running, requests to user-service
are correctly routed.
4. API Gateway (Entry Point for Requests)
Instead of calling microservices directly, we use an API Gateway as the central entry point.
The API Gateway routes requests based on paths.
Example:
/auth/login
→ Routes to Auth Service/users/{id}
→ Routes to User Service/orders/{userId}
→ Routes to Order Service
7.2 How Do Microservices Run in Multiple Pods?
Deployment starts multiple replicas of each service.
Pods are distributed across different nodes (physical or virtual machines).
Services act as stable entry points, ensuring traffic is routed correctly.
The API Gateway orchestrates communication between services.
Final Flow:
The user sends a request to
api.myapp.com/orders/1
.The API Gateway forwards the request to the Order Service.
The Order Service Pod processes the request and fetches user data from the User Service.
Kubernetes ensures that if one pod fails, another takes over.
7.3 Kubernetes in Action: Scaling Under Load
Let’s say the number of users doubles overnight.
Kubernetes detects increased load and automatically creates new pods.
Instead of 3 instances per service, it scales up to 6.
When traffic decreases, it scales down automatically, saving resources.
8. Key Takeaways: Why Use Microservices?
Scalability → Scale individual services independently.
Fault Tolerance → If one service fails, others remain functional.
Technology Freedom → Different services can use different languages or frameworks.
9. Conclusion: The Future of Microservices
Microservices are not just a trend — they’re a necessity in modern applications. By splitting services into independent, scalable components, you create resilient, flexible applications that can evolve with time. Kubernetes further simplifies deployment, making it an essential tool for backend developers.
“The best architectures are those that allow seamless change.”
Are you working with microservices? What challenges have you faced? Share your thoughts in the comments below!