DEV Community

Cover image for Deploying and Exposing Go Apps with Kubernetes Ingress, Part 2
M. Oly Mahmud
M. Oly Mahmud

Posted on

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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", }) } 
Enter fullscreen mode Exit fullscreen mode

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"] 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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) } 
Enter fullscreen mode Exit fullscreen mode

Changes from Part 1:

  • The Web service now handles GET / instead of /web, using http.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"] 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Create the cluster:

kind create cluster --config k8s/kind-cluster-config-with-ingress.yaml 
Enter fullscreen mode Exit fullscreen mode

Verify:

kubectl get nodes 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Check status:

kubectl get pods -n ingress-nginx 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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)