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

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, "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
, usinghttp.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 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: "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