Kubernetes Ingress de-mystified
Just finished a weekend of being totally confused about k8s ingress. I'm glad to say that I finally managed (using my OCD powers and lots of snacks/coffee) to work out what's going on. Here's what I learned. (NOTE: I'm doing this on Minikube with no add-ons)
The confusion I experienced was made worse by several guides that skipped over the key detail that was confusing me, i.e the ingress-controller, because they either:
- Assumed I was running on GKE which handles the ingress-controller automatically
- Assumed I had enabled ingress in Minikube as an add-on which also handles the ingress-controller automatically
Now, call me old fashioned, but when I'm learning something I like to do everything myself so that I fully understand every piece.
Oddly enough, a couple of guides were plain wrong and exposed the services they were publishing via ingress as NodePort. This really confused me because the ingress controller is inside the cluster, along with the services it's routing to and therefore can contact the services "natively" on their ClusterIP. The point here (and this sounds obvious to me now but before the penny dropped it wasn't) is that the only service that needs to be exposed to the outside world when using ingress is the service that sits in front of the ingress controller itself.
The aim of this guide is to:
- Help me not forget what I learned (always good to write things down)
- Maybe help other out people that might find ingress confusing.
So here goes...
The diagram below set's out the target system:
Here's the yaml for each component, starting from the top down:
ingress-controller-service.yaml apiVersion: v1 kind: Service metadata: name: ingress-controller spec: type: NodePort ports: - name: http port: 80 targetPort: http nodePort: 32000 selector: app: ingress-controller
ingress-controller-deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: ingress-controller namespace: default spec: replicas: 1 selector: matchLabels: app: ingress-controller template: metadata: labels: app: ingress-controller spec: containers: - name: nginx-ingress-controller image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.11.0 args: - /nginx-ingress-controller - --default-backend-service=$(POD_NAMESPACE)/default-backend - --annotations-prefix=nginx.ingress.kubernetes.io env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - name: http containerPort: 80
An interesting point to note from the yaml below is that I've set the host to minikube. I originally left this blank which means that any hostname or IP specified in the URL would be acceptable. however when I did this the nginx controller kept trying to redirect me (via a 308) to https. I googled around a bit and found out that this is expected behavior for the nginx controller and that specifying a host stops this from happening. So I tried putting my minikube IP address in, kubectl complained about this stating that hostnames and not IP's had to be used in the ingress host field. This being the case I just placed an entry in my local /etc/hosts
ingress-resource.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: cluster-ingress-rules spec: rules: - host: minikube http: paths: - path: /hello-world backend: serviceName: hello-world servicePort: 8080
hello-world-service.yaml apiVersion: v1 kind: Service metadata: name: hello-world labels: run: hello-world spec: ports: - port: 8080 protocol: TCP targetPort: 8080 selector: run: hello-world
hello-world-deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: hello-world labels: run: hello-world spec: replicas: 1 selector: matchLabels: run: hello-world template: metadata: creationTimestamp: null labels: run: hello-world spec: containers: - image: gcr.io/google-samples/node-hello:1.0 name: hello-world ports: - containerPort: 8080
default-backend-service.yaml apiVersion: v1 kind: Service metadata: name: default-backend labels: app: default-backend spec: ports: - port: 80 targetPort: 8080 selector: app: default-backend
default-backend-deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: default-backend labels: app: default-backend spec: replicas: 1 selector: matchLabels: app: default-backend template: metadata: labels: app: default-backend spec: terminationGracePeriodSeconds: 60 containers: - name: default-backend image: gcr.io/google_containers/defaultbackend:1.4 ports: - containerPort: 8080
Applying this lot to my cluster results in the following:
----- Ingress ------------------------------------------------------------------ NAME HOSTS ADDRESS PORTS AGE cluster-ingress-rules minikube 80 20m ----- Services ----------------------------------------------------------------- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default-backend ClusterIP 10.100.69.250 <none> 80/TCP 20m hello-world ClusterIP 10.110.199.3 <none> 8080/TCP 20m ingress-controller NodePort 10.111.99.142 <none> 80:32000/TCP 20m kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16d ----- Deployments -------------------------------------------------------------- NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE default-backend 1 1 1 1 20m hello-world 1 1 1 1 20m ingress-controller 1 1 1 1 20m ----- Pods --------------------------------------------------------------------- NAME READY STATUS RESTARTS AGE default-backend-79f984dc75-2bbbf 1/1 Running 0 20m hello-world-554998f545-wjj49 1/1 Running 0 20m ingress-controller-5cddb96544-2k9w7 1/1 Running 0 20m
As can be seen from the above, the only service exposed to the outside world is the ingress-controller service
Running minikube service list produces the following result:
|-------------|----------------------|-----------------------------| | NAMESPACE | NAME | URL | |-------------|----------------------|-----------------------------| | default | default-backend | No node port | | default | hello-world | No node port | | default | ingress-controller | http://192.168.99.100:32000 | | default | kubernetes | No node port | | kube-system | kube-dns | No node port | | kube-system | kubernetes-dashboard | http://192.168.99.100:30000 | | kube-system | tiller-deploy | No node port | |-------------|----------------------|-----------------------------|
and putting http://minikube:32000/hello-world into a browser results in the message Hello Kubernetes!
And there you have it, hopefully I haven't missed anything.