minikube makes it easy to spin up a local Kubernetes cluster on macOS, and adding an Ingress is convenient with its built-in Addons.
In this article, I want to take it one step further and show how to expose the Ingress via TLS (secure https) using a custom key/certificate chain.
Prerequisites
- MacOS
- Brew package manager
- QEMU virtual machine emulator/virtualizer
- ‘step‘ utility for generating custom CA and TLS certificate
Brew
Install Brew as described on its documentation page. We will use the brew package manager to install the other components needed.
# install brew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # add brew environment variables to current shell eval "$(/opt/homebrew/bin/brew shellenv)"
To persist the proper brew shell settings, add the following values to ~/.zprofile
# brew variables and PATH eval "$(/opt/homebrew/bin/brew shellenv)" command -v brew >/dev/null 2>&1 || export PATH=/opt/homebrew/bin:$PATH
QEMU
Install QEMU the open-source virtual machine emulator and virtualizer using brew.
brew install qemu
Then enable the socket_vmnet library for QEMU, which enables the minikube “service” command.
brew install socket_vmnet brew tap homebrew/services HOMEBREW=$(which brew) && sudo ${HOMEBREW} services start socket_vmnet
Certificate creation utility
Install the step utility, which makes it convenient to generate certificates.
brew install step
Install minikube
brew install minikube
Start minikube, enable Ingress
# start minikube, override default docker engine and use qemu instead minikube start --driver=qemu --network socket_vmnet # validate access to cluster $ which kubectl /opt/homebrew/bin/kubectl $ kubectl get nodes NAME STATUS ROLES AGE VERSION minikube Ready control-plane 6m42s v1.28.3 # enable ingress-nginx using Addon minikube addons enable ingress # wait for deployment readiness kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx --timeout=90s
Test Ingress with non-secure http
Before we move forward with secure Ingress over TLS, let’s prove out simple http. We will use similar instructions as described in these tutorials at k8-sdocs and w3cubdocs, however we do need to use an image with an ARM64 target to support Apple Silicon.
Simple example deployed at NodePort
# simple multi-arch deployment that supports ARM64 and AMD64 kubectl create deployment web --image=ghcr.io/fabianlee/google-hello-app-multiarch:1.0.0 --port=8080 kubectl expose deployment web --type=NodePort --port=8080 # wait for full readiness kubectl rollout status deployment web -n default --timeout=90s # get NodePort info kubectl get service web web_url=$(minikube service web --url) echo "about to insecurely pull from deployment at NodePort: $web_url" # use http to pull content from service at NodePort $ curl $web_url Hello, world! Version: 1.0.0 Hostname: web-5d76dc856d-6xhh6
Exposed via Ingress
# set variable used for domain name domain=hello-world.example # create Ingress using host 'hello-world.info' wget https://k8s.io/examples/service/networking/example-ingress.yaml kubectl apply -f example-ingress.yaml # wait for ingress to get assigned IP address (will match 'minikube ip') kubectl get ingress sleep 60 lb_ip=$(kubectl get ingress -o=jsonpath="{.items[].status.loadBalancer.ingress[0].ip}") echo "loadbalancer IP for ingress: $lb_ip" # first, use curl resolve flag so that no DNS entries are necessary curl --resolve "hello-world.example:80:$lb_ip" -i http://hello-world.example # add loadbalancer IP to local hosts file # this way we do not need minikube tunnel echo $lb_ip $domain | sudo tee -a /etc/hosts # prove that service is available at Ingress $ curl http://$domain Hello, world! Version: 1.0.0 Hostname: web-5d76dc856d-6xhh6
Create CA cert and TLS certificate
Using the step utility, we can create a custom root CA, intermediate, and leaf certificate for secure TLS at the URL “https://hello-world.example”.
# set variable domain=hello-world.example # create root CA step certificate create --no-password --insecure --profile root-ca "Example Root CA" root_ca.crt root_ca.key # intermediate cert step certificate create "Example Intermediate CA 1" intermediate_ca.crt intermediate_ca.key --profile intermediate-ca --ca ./root_ca.crt --ca-key ./root_ca.key --no-password --insecure # leaf cert step certificate create $domain $domain.crt $domain.key --profile leaf --not-after=8760h --ca ./intermediate_ca.crt --ca-key ./intermediate_ca.key --bundle --no-password --insecure # show results step certificate inspect $domain.crt --short
Load TLS cert and key into cluster
# load key+cert into secret in kube-system namespace kubectl -n kube-system create secret tls ingress-tls-cert --key $domain.key --cert $domain.crt # do direct patch to set default TLS certificate (had problem setting with 'minikube addons configure ingress') kubectl patch deployment ingress-nginx-controller -n ingress-nginx --type "json" --patch '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--default-ssl-certificate=kube-system/ingress-tls-cert"}]' # wait for readiness kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx --timeout=90s # ensure that secret was loaded by looking at log $ kubectl logs deployment/ingress-nginx-controller -n ingress-nginx | grep ingress-tls-cert ... backend_ssl.go:67] "Adding secret to local store" name="kube-system/ingress-tls-cert"
Validate ingress with secure TLS
With the TLS key+cert now loaded, we should be able to pull from the service using secure https.
# pull using TLS (trusted CA cert required) curl --cacert root_ca.crt https://$domain # show leaf certificate step certificate inspect --roots=root_ca.crt --short https://$domain
Component versions used in this article
I have seen issues on a Mac M1 where the versions of qemu or socket_vmnet cause unexpected errors. Here are the component versions I used when testing for this article:
- ‘sw_vers’ on Mac M1 = Sequoia 15.1.1
- ‘brew –version’ = 4.4.6
- ‘brew info qemu’ = 9.1.1
- ‘brew info libvirt’ = 10.9.0
- ‘brew info socket_vmnet’ = 1.1.7
- minikube kubernetes = 1.31.0
- ‘/nginx-ingress-controller –version’ nginx ingress version = v1.11.2
REFERENCES
minikube, custom TLS cert with ingress
devopscube.com, minikube on Mac
minikube.sigs.k8s.io, explains QEMU networking modes and reason for socket_vmnet
github.com, google hello-app but with multiple target architectures
stackoverflow, patch command to set ingress-nginx default certificate
k8s-docs, minikube with ingress nginx
kubernetes.io, minikube and Ingress
github container-hello-app, source code for image gcr.io/google-samples/hello-app
kubernetes.github.io, TLS and default cert
NOTES
If you have a problem where the ingress components never show up, try starting fresh and enabling the ‘ingress-dns’ addon first.
minikube delete minikube start minikube addons enable ingress-dns minikube addons enable ingress
If you have another virtual machine manager (like KVM), you can start minikube like:
minikube start --driver=kvm2
The GoogleCloudPlatform/kubernetes-engine-samples is only for architecture amd64, I modified for multiple target archs
# cannot use on arch64 (apple silicon) kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0 --port=8080 # but I have created a multi-arch distribution kubectl create deployment web --image=ghcr.io/fabianlee/google-hello-app-multiarch:1.0.0 --port=8080
upgrade minikube using brew
minikube version brew update && brew outdated minikube delete brew upgrade qemu socket_vmnet kubernetes-cli brew upgrade minikube minikube version minikube start minikube logs | grep -i "container runtime version" minikube logs | grep -i "creating cni manager"