Sunday, 4 May 2025

Learning Microservices in C++

Microservices can feel abstract until you actually build and run them yourself.
In this guide, you'll build a simple three-service C++ application — an API Gateway, a Greeter Service, and a Time Service — and deploy it to a local Kubernetes cluster running on your Windows 11 laptop using Docker Desktop. No cloud account needed.

What You'll Build Three independent C++ services — an API Gateway, a Greeter Service, and a Time Service — each running in its own Docker container and Kubernetes pod. A client hits the gateway on port 8080, which forwards to the right backend service.
Architecture
Client → HTTP → API Gateway (port 8080, NodePort)
API Gateway → GET /greet → Greeter Service (port 9001, ClusterIP)
API Gateway → GET /time  → Time Service   (port 9002, ClusterIP)
Each service runs in its own Kubernetes Pod inside a local Docker Desktop cluster.

Step 1 — Install Prerequisites Install these three tools on your Windows 11 machine before writing any code. 

  1. Docker Desktop — download from docker.com. During setup go to Settings → Kubernetes → Enable Kubernetes and click Apply. This gives you a single-node cluster locally. 

  2. WSL2 — Docker Desktop uses it as its Linux backend. Enable it via PowerShell (run as Administrator):
wsl --install
  3. Verify everything works by opening PowerShell and running:
docker version
kubectl get nodes
You should see one node named docker-desktop in Ready state.
Step 2 — Project Structure Create this folder layout at C:\projects\microservices-demo:
microservices-demo/
├── gateway/
│   ├── main.cpp
│   └── Dockerfile
├── greeter/
│   ├── main.cpp
│   └── Dockerfile
├── time-svc/
│   ├── main.cpp
│   └── Dockerfile
└── k8s/
    ├── gateway.yaml
    ├── greeter.yaml
    └── time-svc.yaml






Step 3 — The C++ Services Each service is a tiny TCP/HTTP server using only the C++ standard library and POSIX sockets. No third-party HTTP libraries needed. greeter/main.cpp
time-svc/main.cpp
#include <iostream>
#include <string>
#include <ctime>
#include <cstring>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    sockaddr_in address{};
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(9002);

    bind(server_fd, (sockaddr*)&address, sizeof(address));
    listen(server_fd, 5);
    std::cout << "Time service listening on port 9002\n";

    while (true) {
        int client_fd = accept(server_fd, nullptr, nullptr);
        char buf[1024] = {};
        read(client_fd, buf, sizeof(buf));

        time_t now = time(nullptr);
        std::string body = std::string("Current time: ") + ctime(&now);
        std::string response =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain\r\n"
            "Content-Length: " + std::to_string(body.size()) + "\r\n"
            "\r\n" + body;
        write(client_fd, response.c_str(), response.size());
        close(client_fd);
    }
}
gateway/main.cpp The gateway reads the URL path and forwards the request to the correct backend. It uses Kubernetes DNS — the service name greeter-svc resolves automatically inside the cluster.
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

std::string forwardRequest(const std::string& host, int port) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
    connect(sock, (sockaddr*)&addr, sizeof(addr));

    std::string req = "GET / HTTP/1.0\r\nHost: " + host + "\r\n\r\n";
    write(sock, req.c_str(), req.size());

    char buf[4096] = {};
    read(sock, buf, sizeof(buf));
    close(sock);
    return std::string(buf);
}

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    sockaddr_in address{};
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    bind(server_fd, (sockaddr*)&address, sizeof(address));
    listen(server_fd, 5);
    std::cout << "Gateway listening on port 8080\n";

    while (true) {
        int client_fd = accept(server_fd, nullptr, nullptr);
        char buf[2048] = {};
        read(client_fd, buf, sizeof(buf));
        std::string request(buf);

        std::string backend_response;
        if (request.find("GET /greet") != std::string::npos) {
            // "greeter-svc" resolves via Kubernetes DNS
            backend_response = forwardRequest("greeter-svc", 9001);
        } else if (request.find("GET /time") != std::string::npos) {
            backend_response = forwardRequest("time-svc", 9002);
        } else {
            backend_response = "HTTP/1.1 200 OK\r\nContent-Length: 19\r\n\r\nTry /greet or /time";
        }

        write(client_fd, backend_response.c_str(), backend_response.size());
        close(client_fd);
    }
}

Step 4 — Dockerfiles Each service uses a two-stage Docker build: compile on a GCC image, then copy only the binary to a slim runtime image. This keeps the final container very small. greeter/Dockerfile
FROM gcc:13 AS builder
WORKDIR /app
COPY main.cpp .
RUN g++ -O2 -o greeter main.cpp

FROM debian:bookworm-slim
WORKDIR /app
COPY --from=builder /app/greeter .
EXPOSE 9001
CMD ["./greeter"]
For time-svc/Dockerfile: same file, change greeter to time-svc and EXPOSE 9001 to EXPOSE 9002.
For gateway/Dockerfile: same file, change greeter to gateway and EXPOSE 9001 to EXPOSE 8080.
Step 5 — Build the Docker Images Open PowerShell inside microservices-demo/ and build all three images:
docker build -t greeter:v1   ./greeter
docker build -t time-svc:v1  ./time-svc
docker build -t gateway:v1   ./gateway
The images stay in Docker Desktop's local registry — Kubernetes can use them directly without pushing to Docker Hub.
Step 6 — Kubernetes YAML Manifests Each service needs two Kubernetes objects: a Deployment (keeps pods running, handles restarts) and a Service (gives pods a stable DNS name inside the cluster). k8s/greeter.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: greeter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: greeter
  template:
    metadata:
      labels:
        app: greeter
    spec:
      containers:
      - name: greeter
        image: greeter:v1
        imagePullPolicy: Never
        ports:
        - containerPort: 9001
---
apiVersion: v1
kind: Service
metadata:
  name: greeter-svc
spec:
  selector:
    app: greeter
  ports:
  - port: 9001
    targetPort: 9001
Create k8s/time-svc.yaml the same way — replace every greeter with time-svc and 9001 with 9002. k8s/gateway.yaml — uses NodePort so your Windows host can reach it on localhost:30080:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gateway
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gateway
  template:
    metadata:
      labels:
        app: gateway
    spec:
      containers:
      - name: gateway
        image: gateway:v1
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: gateway-svc
spec:
  type: NodePort
  selector:
    app: gateway
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30080

Step 7 — Deploy to Kubernetes
kubectl apply -f k8s/greeter.yaml
kubectl apply -f k8s/time-svc.yaml
kubectl apply -f k8s/gateway.yaml
Check that all three pods are running:
kubectl get pods
kubectl get services
You should see three pods all showing Running status.
Step 8 — Test It!
curl http://localhost:30080/greet
# → Hello from the Greeter Service!

curl http://localhost:30080/time
# → Current time: Sun Apr 19 ...

curl http://localhost:30080/
# → Try /greet or /time

Key Concepts You Just Learned
ConceptWhat it is in your app
MicroserviceEach of your 3 C++ programs — independent, single-purpose, separately deployable
ContainerThe Docker image packaging your binary and its Linux dependencies
PodKubernetes' smallest unit — wraps your container and gives it an IP
DeploymentEnsures your pod keeps running; restarts it automatically if it crashes
Service (ClusterIP)Gives greeter and time-svc a stable DNS name inside the cluster
Service (NodePort)Exposes the gateway to your Windows host on localhost:30080
API GatewaySingle entry point that routes requests to the right microservice

What to Explore Next 

  1. Scale a service
      Run kubectl scale deployment greeter --replicas=3 and watch Kubernetes load balance across 3       pods. 

  2. Kill a pod
      Run kubectl delete pod <pod-name> — Kubernetes will immediately restart it. This is self-healing       in action. 

  3. Add a new microservice
      Create a "joke" or "quote" service and wire it into the gateway on a new route such as /joke

  4. Add health checks
      Add livenessProbe and readinessProbe fields to your YAML. Kubernetes uses these to decide if a          pod is healthy enough to receive traffic. 

  5. Use ConfigMaps
      Move hardcoded backend hostnames into Kubernetes ConfigMaps and inject them as environment          variables — so your code doesn't need to change when config does. 

No comments:

Post a Comment