In Part 1, we used Kubernetes Ingress to route traffic to two Go-based microservices based on specific paths. In this second part, we’ll explore advanced Ingress features.
Project Overview
In this part, we’ll build and deploy two updated Go-based services (API and Web) in the ~/k8s-learning/ingress/ingress-different-route
directory. Unlike Part 1, where services handled specific paths (/api
and /web
), here both services respond to the root path (/
). We’ll use Kubernetes Ingress with a rewrite rule to route /api
to the API service and /
to the Web service, ensuring clean URL handling.
Project Structure
We will build two services using Go:
- An API service that responds to HTTP GET requests at the root path (
/
) with a JSON response. - A Web service that responds to HTTP GET requests at the root path (
/
) with an HTML response.
Then, we’ll containerize these services with Docker, set up a Kubernetes cluster using Kind, deploy the services, and configure Ingress with path rewriting to route traffic appropriately.
Tree Structure
Here’s the directory structure of the project:
k8s-learning/ └── ingress/ └── ingress-different-route/ ├── api-service/ │ ├── Dockerfile │ └── main.go ├── web-service/ │ ├── Dockerfile │ └── main.go └── k8s/ ├── api-deployment.yaml ├── api-service.yaml ├── web-deployment.yaml ├── web-service.yaml ├── go-app-ingress.yaml └── kind-cluster-config-with-ingress.yaml
API Service
To Initialize the Go Module for This Service, Run:
cd ~/k8s-learning/ingress/ingress-different-route/api-service go mod init ingress-api-service-different
This command sets up the go module.
Developing the API with Go
The API service is a Go application that responds to HTTP GET requests at the root path (/
) with a JSON response.
package main import ( "encoding/json" "fmt" "log" "net/http" ) func main() { router := http.NewServeMux() router.HandleFunc("GET /", apiHandler) fmt.Println("API Service started at :8080") log.Fatal(http.ListenAndServe(":8080", router)) } func apiHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ "message": "API Service", }) }
Changes from Part 1:
- The API now uses
http.NewServeMux
for routing, explicitly handlingGET /
instead of/api
. - It returns a JSON response (
{"message": "API Service"}
) instead of HTML.
Dockerizing the API Service
The Dockerfile
remains similar to Part 1 but uses the updated image name.
# Use the official Golang image to build the app FROM golang:1.24-alpine as builder # Set the Current Working Directory inside the container WORKDIR /app # Copy the Go Modules files and download dependencies COPY go.mod ./ RUN go mod tidy # Copy the rest of the source code into the container COPY . . # Build the Go app RUN go build -o api-service . # Start a new stage from the official Alpine image FROM alpine:latest # Install necessary dependencies RUN apk --no-cache add ca-certificates # Set the Current Working Directory inside the container WORKDIR /root/ # Copy the pre-built binary file from the builder stage COPY --from=builder /app/api-service . # Expose port 8080 for the API service EXPOSE 8080 # Command to run the executable CMD ["./api-service"]
Build and push the image:
cd ~/k8s-learning/ingress/ingress-different-route/api-service docker build -t olymahmudmugdho/ingress-api-service-different:latest . docker push olymahmudmugdho/ingress-api-service-different:latest
Web Service
To Initialize the Go Module for This Service, Run:
cd ~/k8s-learning/ingress/ingress-different-route/web-service go mod init ingress-web-service-different
I am using Go 1.24.2.
Developing the Web Service with Go
The Web service responds to HTTP GET requests at the root path (/
) with an HTML response.
package main import ( "fmt" "net/http" ) func webHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) return } fmt.Fprintf(w, "<h1>Web Service</h1>") } func main() { router := http.NewServeMux() router.HandleFunc("GET /", webHandler) fmt.Println("Web Service started at :8080") http.ListenAndServe(":8080", router) }
Changes from Part 1:
- The Web service now handles
GET /
instead of/web
, usinghttp.NewServeMux
. - The response remains HTML (
<h1>Web Service</h1>
).
Dockerizing the Web Service
The Dockerfile
is identical to Part 1’s structure but uses the updated image name.
# Use the official Golang image to build the app FROM golang:1.24-alpine as builder # Set the Current Working Directory inside the container WORKDIR /app # Copy the Go Modules files and download dependencies COPY go.mod ./ RUN go mod tidy # Copy the rest of the source code into the container COPY . . # Build the Go app RUN go build -o web-service . # Start a new stage from the official Alpine image FROM alpine:latest # Install necessary dependencies RUN apk --no-cache add ca-certificates # Set the Current Working Directory inside the container WORKDIR /root/ # Copy the pre-built binary file from the builder stage COPY --from=builder /app/web-service . # Expose port 8080 for the Web service EXPOSE 8080 # Command to run the executable CMD ["./web-service"]
Build and push:
cd ~/k8s-learning/ingress/ingress-different-route/web-service docker build -t olymahmudmugdho/ingress-web-service-different:latest . docker push olymahmudmugdho/ingress-web-service-different:latest
Setting Up Kubernetes with KIND
The Kind cluster configuration remains identical to Part 1.
kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: kind-ingress nodes: - role: control-plane image: kindest/node:v1.31.2 kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "ingress-ready=true" extraPortMappings: - containerPort: 80 hostPort: 80 protocol: TCP - containerPort: 443 hostPort: 443 protocol: TCP - role: worker image: kindest/node:v1.31.2 - role: worker image: kindest/node:v1.31.2
Create the cluster:
kind create cluster --config k8s/kind-cluster-config-with-ingress.yaml
Verify:
kubectl get nodes
Installing the Ingress Controller
Deploy the NGINX Ingress controller, as in Part 1:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/kind/deploy.yaml
Check status:
kubectl get pods -n ingress-nginx
API Deployment Configuration
The API Deployment is similar to Part 1 but uses the new image.
apiVersion: apps/v1 kind: Deployment metadata: name: api-deployment spec: replicas: 1 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: olymahmudmugdho/ingress-api-service-different:latest ports: - containerPort: 8080
Explanation: Creates a Deployment for the API service, ensuring one replica runs with the new Docker image, exposing port 8080.
Apply:
kubectl apply -f k8s/api-deployment.yaml
API Service Definition
The API Service is unchanged from Part 1.
apiVersion: v1 kind: Service metadata: name: api-service spec: selector: app: api ports: - protocol: TCP port: 80 targetPort: 8080 type: ClusterIP
Explanation: Exposes the API pods internally via a ClusterIP Service, mapping external port 80 to the container’s port 8080.
Apply:
kubectl apply -f k8s/api-service.yaml
Web Deployment Configuration
The Web Deployment is similar to Part 1 but uses the new image.
apiVersion: apps/v1 kind: Deployment metadata: name: web-deployment spec: replicas: 1 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: olymahmudmugdho/ingress-web-service-different:latest ports: - containerPort: 8080
Explanation: Creates a Deployment for the Web service, ensuring one replica runs with the new Docker image.
Web Service Definition
The Web Service is unchanged from Part 1.
apiVersion: v1 kind: Service metadata: name: web-service spec: selector: app: web ports: - protocol: TCP port: 80 targetPort: 8080 type: ClusterIP
Explanation: Exposes the Web service internally.
Creating Ingress Resource (go-app-ingress.yaml)
The Ingress resource introduces a rewrite rule to handle the new routing logic.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: localhost http: paths: - pathType: Prefix path: /api backend: service: name: api-service port: number: 80 - pathType: Prefix path: / backend: service: name: web-service port: number: 80
Detailed Explanation:
- Metadata: The
name: example-ingress
identifies the Ingress resource. Consider renaming togo-app-ingress
for consistency with Part 1. - Annotations: The
nginx.ingress.kubernetes.io/rewrite-target: /
rewrites incoming paths to/
before forwarding to the backend services. This is critical since both services expect requests at/
, but external requests use/api
or/
. - Spec.Rules: Defines routing rules for
host: localhost
. - Host: Set to
localhost
, suitable for local testing with Kind. In production, this would be a domain (e.g.,example.com
). - HTTP.Paths:
-
/api
: Matches URLs starting with/api
. The rewrite rule strips/api
, forwarding the request as/
toapi-service
on port 80. -
/
: Matches all other URLs (root path). The request is forwarded as/
toweb-service
on port 80.
-
- Backend: Specifies the target Service (
api-service
orweb-service
) and port (80
). - pathType: Prefix: Ensures paths like
/api/health
match/api
, but the rewrite rule normalizes them to/
.
Changes from Part 1:
- The Web service is now routed at
/
instead of/web
. - The
rewrite-target
annotation handles path rewriting, allowing both services to operate at/
internally. - This setup simplifies external URLs (e.g.,
localhost/
instead oflocalhost/web
).
Apply:
kubectl apply -f k8s/go-app-ingress.yaml
Verifying Ingress Functionality
Ensure all components are running:
kubectl get deployments kubectl get services kubectl get ingress kubectl describe ingress example-ingress kubectl get pods -n ingress-nginx
Testing with cURL
Test the routing:
curl http://localhost/api # Expected output: {"message": "API Service"} curl http://localhost/ # Expected output: "<h1>Web Service</h1>" curl http://localhost/invalid # Expected output: 404 Not Found
If you face any problem, troubleshoot:
- Check Ingress controller logs:
kubectl logs -n ingress-nginx <pod-name>
. - Ensure
localhost
resolves correctly in/etc/hosts
or network settings. - Verify the rewrite rule is applied (
kubectl describe ingress example-ingress
).
Thanks for reading
Top comments (0)