Publicly expose a website via HTTP with Ingress Nginx and Kubernetes
by Alex Arica

In this blog post we are going to publicly expose a website via HTTP with Ingress Nginx controller in a bare-metal Kubernetes cluster.

Install Ingress Nginx controller

We are going to install the Ingress Nginx controller with the command available in the Kubernetes official doc.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v[version]/deploy/static/provider/baremetal/deploy.yaml
                    

Since we are installing in a bare-metal Kubernetes, we can replace the variable [version] by the latest version shown in the "bare-metal" installation section.

Alternatively, the latest version is available in the official Kubernetes GitHub page. Check the latest release version for "NGINX:[version]" and extract its version number.

Once installed, Ingress controller creates a namespace "ingress-nginx" where it deploys its resources. Wait until all Nginx components' status indicate as running or completed:

kubectl get all -n ingress-nginx
                    

Let's check the ports on which the Ingress controller listens for HTTP and HTTPS requests:

kubectl get svc ingress-nginx-controller -n ingress-nginx
                    

Output example:

NAME                       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   NodePort   10.103.241.100   none          80:30271/TCP,443:32174/TCP   3m18s
                    

The most important information above are:

  • "80:30271/TCP": Ingress controller listens on all Kubernetes nodes on port 30271 for HTTP requests and it routes those requests to services configured with custom "Ingress" resources.
  • "443:32174/TCP": Ingress controller listens on all Kubernetes nodes on port 32174 for HTTPS requests and it routes those requests to services configured with custom "Ingress" resources.

For example, if we have 2 nodes in a Kubernetes cluster, with IP addresses 192.168.0.2 and 192.168.0.3, then the services configured with Ingress resources are accessible:

  • for HTTP requests: via 192.168.0.2:30271 and 192.168.0.3:30271
  • for HTTPS requests: via 192.168.0.2:32174 and 192.168.0.3:32174

When configuring a load balancer, we use those IP:port details (e.g. 230.70.191:30271 ...) depending on whether the TCP requests received by the load balancer are on port 80 or 443. For example, for TCP requests received on port 80 (=HTTP) of the load balancer, I configured it so that it routes the requests to the most available IP ranges 192.168.0.2:30271 and 192.168.0.3:30271. And for TCP requests received on port 443 (=HTTPS), the IP ranges would be 192.168.0.2:32174 and 192.168.0.3:32174 .

To resume, the routing would be:

User -> calls www.reactive-tech.io on port 80 (HTTP) -> external load balancer IP -> calls either node 192.168.0.2:30271 or 192.168.0.3:30271
                    
User -> calls www.reactive-tech.io on port 443 (HTTPS) -> external load balancer IP -> calls either node 192.168.0.2:32174 or 192.168.0.3:32174
                    

Create a namespace

It is a good practice to create a custom namespace for our custom resources. In our case, the idea is to have a namespace in which all static websites' resources are located. Let's create the namespace "static-websites".

In a new file:

vi static-websites-namespace.yaml
                    

Add:

apiVersion: v1
kind: Namespace
metadata:
  name: static-websites
                    

Apply it to Kubernetes:

kubectl apply -f static-websites-namespace.yaml
                    

Deploy the website

The website's name is "reactive-tech-website" and we are using the docker image "nginx" which listens on container's port 80.

In a new file:

vi reactive-tech-website-deployment.yaml
                    

Add:

apiVersion: apps/v1
kind: Deployment
metadata:First
  name: reactive-tech-website
  namespace: static-websites
spec:
  replicas: 2
  selector:
    matchLabels:
      app: reactive-tech-website
  template:
    metadata:
      labels:
        app: reactive-tech-website
    spec:
      containers:
        - image: nginx
          name: reactive-tech-website
          ports:
            - containerPort: 80
              protocol: TCP
                    

Deploy it:

kubectl apply -f reactive-tech-website-deployment.yaml
                    

Deploy a service

We are going to deploy a service allowing to access to our website within the cluster. Kubernetes will assign a clusterIP to our service and expose it on port 80. For convenience, we called this service "reactive-tech-website" as well:

In a new file:

vi reactive-tech-website-service.yaml
                    

Add:

apiVersion: v1
kind: Service
metadata:
  name: reactive-tech-website
  namespace: static-websites
spec:
  selector:
    app: reactive-tech-website
  ports:
    - protocol: "TCP"
      port: 80
                    

Deploy it:

kubectl apply -f reactive-tech-website-service.yaml
                    

Our website is accessible within the cluster. Next, let's make it accessible via Ingress.

Deploy a custom Ingress configuration

Ingress Nginx controller is looking for any resources of type "Ingress" in order to configure the routing rules. We are going to route the domain name "www.reactive-tech.io" to our service "reactive-tech-website".

In a new file:

vi reactive-tech-website-ingress.yaml
                    

Add:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: reactive-tech-website
  namespace: static-websites
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
spec:
  rules:
    - host: "www.reactive-tech.io"
      http:
        paths:
          - pathType: Prefix
          - pathType: Prefix
            path: "/"
            backend:
              service:
                name: reactive-tech-website
                port:
                  number: 80

                    

Note the usage of the annotation "nginx.ingress.kubernetes.io/from-to-www-redirect" which means that any requests for "reactive-tech.io" will be redirected to "www.reactive-tech.io".

Deploy it:

kubectl apply -f reactive-tech-website-service.yaml
                    

At this stage, the website reactive-tech-website is accessible on all Kubernetes nodes via port 30271 for HTTP requests and via port 32174 for HTTPS requests. Inside our cluster, we should be able to access to the website by using a CURL command:

curl -H "Host: www.reactive-tech.io" 192.168.0.2:30271
OR
curl -H "Host: www.reactive-tech.io" 192.168.0.3:30271
...
                    

Configure www.reactive-tech.io to route to the load balancer's IP address

The last step is to change the "A" DNS record of the domain name "reactive-tech.io" to the IP address of the external load balancer. Our website should be accessible publicly via "www.reactive-tech.io".

For a bare-metal Kubernetes installation, we are using an external load balancer which is configured to route TCP traffics to a list of Kubernetes nodes IP:port (e.g. via 192.168.0.2:30271, 192.168.0.3:30271, ...).

Next step: issue a SSL certificate

Our website is accessible via HTTP on "www.reactive-tech.io". However, if we call it using HTTPS it does not issue a valid SSL certificate and therefore the browser displays an error message. In the next blog post we are going to explain how to issue a valid SSL certificate using Let's encrypt.