Setting up Ingress in a Local Kind Cluster

August 13, 2025 ยท View on GitHub

This document walks through setting up Ingress in a local Kind cluster. There are many examples of how to do this online but the intention of this document is so that when writing future ToolHive content, we can refer back to this guide when needing to setup Ingress in a local Kind cluster without polluting future content with the additional steps.

Prerequisites

  • A kind cluster running locally. Follow our Setup a Local Kind Cluster to do this.
  • Optional: Task to run automated steps with a cloned copy of the ToolHive repository (git clone https://github.com/stacklok/toolhive)

TL;DR

We have also automated the installation of the Nginx Ingress Controller using a Task.

To use, run:

task kind-ingress-setup

It will install the Nginx Ingress Controller and fix the secret inconsistencies. It does nothing with the cloud-provider-kind Load Balancer, so you will still need to run that yourself. But by the end of the task run, the controller will be waiting for an assigned IP.

Manual Install of Nginx Ingress Controller

To install the Nginx Ingress Controller manually, run the following:

kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml

There are known issues around inconsistencies between the secret and the webhook caBundle resulting in the Nginx Ingress Controller not being fully running and operational.

To fix these inconsistencies run:

CA=$(kubectl -n ingress-nginx get secret ingress-nginx-admission -ojsonpath='{.data.ca}')
kubectl patch validatingwebhookconfigurations ingress-nginx-admission --type='json' --patch='[{"op":"add","path":"/webhooks/0/clientConfig/caBundle","value":"'$CA'"}]'

We should now be able to confirm that the Nginx Ingress Controller is running and healthy by running:

$ kubectl get --namespace=ingress-nginx pod --selector=app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/component=controller
NAME                                       READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-76666fb69-5bshr   1/1     Running   0          2m41s

Now, although the Nginx Ingress Controller is running, we need to hook with an IP so we can access it from our local terminal. Automatically, this won't be possible by default, as there is nothing to provide an ExternalIP.

To confirm there is no IP, run:

kubectl get svc/ingress-nginx-controller -n ingress-nginx -o=jsonpath='{.status.loadBalancer.ingress[0].ip}'

Follow the next section to learn how to assign an ExternalIP to an Ingress Controller in Kind.

Run a Local Kind Load Balancer

When running local Kind cluster, the issue is normally being able to run a Load Balancer that assigns an IP to an Ingress Controllers. In the Cloud, this functionality is provided by the Cloud Load Balancers (AWS LB etc). However, the Kind authors have been kind enough (pun intended) to provide a local Kind Load Balancer called cloud-provider-kind. This acts as a small LoadBalancer to assign IPs to Ingress Controllers in a Kind cluster - it essentially mimics the functionality of a Cloud provider's Load Balancer.

To install and run, follow the install documentation found on the Github repository for your preferred method of installation.

After following the documentation, it should now be installed and running and quickly detect that it needs to provide an IP address to our pending Ingress Controllers in our local Kind cluster.

To confirm that it has provided an IP address, you should now see an IP returned when you run:

kubectl get svc/ingress-nginx-controller -n ingress-nginx -o=jsonpath='{.status.loadBalancer.ingress[0].ip}'

Test Nginx Ingress Controller and Kind Load Balancer Setup

After following the two previous sections, we should now be able to confirm if we can connect to the Ingress Controller with our local terminal. Inside of a local terminal run:

$ LB_IP=$(kubectl get svc/ingress-nginx-controller -n ingress-nginx -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ curl -I $LB_IP/healthz
HTTP/1.1 200 OK
Date: Wed, 30 Apr 2025 12:34:43 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive

If you receive an OK response, then you have successfully confirmed that you have an Ingress setup working for your cluster.

To add Ingress for your applications, this can be done using the standard Ingress resource.

We won't be applying it as its beyond the scope of this document, but the below is an example:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mcp-ingress
  namespace: toolhive-system
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /\$2
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /fetch(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: mcp-fetch-proxy
                port:
                  number: 8080
          - path: /yardstick(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: mcp-yardstick-proxy
                port:
                  number: 8080

Ingress with a Local Hostname

If you prefer to use a friendly hostname instead of an IP address, modify your /etc/hosts file to include a mapping for the load balancer IP.

This example creates the hostname my-kind-cluster.dev:

$ LB_IP=$(kubectl get svc/ingress-nginx-controller -n ingress-nginx -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ sudo sh -c "echo '$LB_IP my-kind-cluster.dev' >> /etc/hosts"

Now, when you curl that endpoint, it should connect as it did with the IP:

$ curl -I my-kind-cluster.dev/healthz
HTTP/1.1 200 OK
Date: Wed, 30 Apr 2025 12:37:16 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive