Deploying and Exposing Go Apps with Kubernetes Ingress, Part 2

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 handling GET / 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, "Web Service") } 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, using http.NewServeMux. The response remains HTML (Web Service). 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

May 10, 2025 - 16:17
 0
Deploying and Exposing Go Apps with Kubernetes Ingress, Part 2

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 handling GET / 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, "

Web Service

"
) } 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, using http.NewServeMux.
  • The response remains HTML (

    Web Service

    ).

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 to go-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 / to api-service on port 80.
    • /: Matches all other URLs (root path). The request is forwarded as / to web-service on port 80.
  • Backend: Specifies the target Service (api-service or web-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 of localhost/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: "

Web Service

"
curl http://localhost/invalid # Expected output: 404 Not Found

If you face any problem, troubleshoot:

  • Check Ingress controller logs: kubectl logs -n ingress-nginx .
  • Ensure localhost resolves correctly in /etc/hosts or network settings.
  • Verify the rewrite rule is applied (kubectl describe ingress example-ingress).

Thanks for reading