How to set-up an NGINX Ingress Controller on PMKFT
Tags: kubernetes, tutorial, devops
Published: 2020-04-30 20:56:58

The vast majority of Kubernetes clusters are used to host containers that process incoming requests from microservices to full web applications. Having these incoming requests come into a central location, then get handed out via services in Kubernetes, is the most secure way to configure a cluster. That central incoming point is an ingress controller.

The most common product used as an ingress controller for privately-hosted Kubernetes clusters is NGINX. NGINX has most of the features enterprises are looking for, and will work as an ingress controller for Kubernetes regardless of which cloud, virtualization platform, or Linux operating system Kubernetes is running on.

In the following tutorial, the Platform9 technical team presents a how-to, step-by-step guide for setting up an NGINX Ingress Controller on the [free version of Platform9 Managed Kubernetes](https://platform9.com/signup/) - a SaaS-managed solution that allows anyone to instantly deploy open-source Kubernetes on-premises, AWS, or Azure.

##First Steps##
The first step required to use NGINX as an Ingress controller on a Platform9 managed Kubernetes cluster, is to have a running Kubernetes cluster.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nzmofibzm6dg7eij2zp4.png)

In this case the cluster we will be using is called “ingress-test” and it is listed as healthy. It is a single node cluster running on an Ubuntu 16.04 server.

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% ssh root@64.227.56.189
Welcome to Ubuntu 16.04.6 LTS <span class="o">(</span>GNU/Linux 4.4.0-173-generic x86_64<span class="o">)</span>
root@pmkft:~# kubectl get nodes
NAME            STATUS   ROLES    AGE   VERSION
64.227.56.189   Ready    master   10h   v1.14.8
root@pmkft:~# kubectl get namespaces
NAME              STATUS   AGE
default           Active   11h
kube-node-lease   Active   11h
kube-public       Active   11h
kube-system       Active   11h
</code></pre></div></div>

Running “kubectl get nodes” and “kubectl get namespaces” confirm that authentication is working, the cluster nodes are ready, and there are no NGINX Ingress controllers configured.

##Mandatory Components for an NGINX Ingress Controller##
An ingress controller, because it is a core component of Kubernetes, requires configuration to more moving parts of the cluster than just deploying a pod and a route.

In the case of NGINX, its recommended configuration has three ConfigMaps:

 - Base Deployment
 - TCP configuration
 - UDP configuration

A service account to run the service is within the cluster, and that service account will be assigned a couple of roles.

A cluster role is assigned to the service account, which allows it to get, list, and read the configuration of all services and events. This could be limited if you were to have multiple ingress controllers. But in most cases, that is overkill.

A namespace-specific role is assigned to the service account to read and update all the ConfigMaps and other items that are specific to the NGINX Ingress controller’s own configuration.

The last piece is the actual pod deployment into its own namespace to make it easy to draw boundaries around it for security and resource quotas.

The deployment specifies which ConfigMaps will be referenced, the container image and command line that will be used, and any other specific information around how to run the actual NGINX Ingress controller.

NGINX has a single file they maintain in GitHub linked to from the [Kubernetes documentation](https://kubernetes.github.io/ingress-nginx/) that has all this configuration spelled out in YAML and ready to deploy.

To apply this configuration, the command to run is:

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.28.0/deploy/static/mandatory.yaml
</code></pre></div></div>

Which will generate the following output:

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
</code></pre></div></div>

##Exposing the NGINX Ingress Controller##
Once the base configuration is in place, the next step is to expose the NGINX Ingress Controller to the outside world to allow it to start receiving connections. This could be through a load-balancer like on AWS, GCP, or Azure. The other option when deploying on your own infrastructure, or a cloud provider with less capabilities, is to create a service with a NodePort to allow access to the Ingress Controller.

Using the NGINX-provided service-nodeport.yaml file, which is located in GitHub, will define a service that runs on ports 80 and 443. It can be applied using a single command line, as done before.

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pmkft:~# kubectl apply <span class="nt">-f</span> https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.28.0/deploy/static/provider/baremetal/service-nodeport.yaml
service/ingress-nginx created
</code></pre></div></div>

##Validate the NGINX Ingress Controller##
The final step is to make sure the Ingress controller is running.

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pmkft:~# kubectl get pods <span class="nt">--all-namespaces</span> <span class="nt">-l</span> app.kubernetes.io/name<span class="o">=</span>ingress-nginx
NAMESPACE       NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-6c7686c6b4-stnq7   1/1     Running   0          6m36s
root@pmkft:~# kubectl get services ingress-nginx <span class="nt">--namespace</span><span class="o">=</span>ingress-nginx
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT<span class="o">(</span>S<span class="o">)</span>                      AGE
ingress-nginx   NodePort   10.21.83.193   &lt;none&gt;        80:30757/TCP,443:31353/TCP   34m
</code></pre></div></div>

**Install via Helm**
Platform9 supports helm3 and is available to anyone who wants to deploy using that method, which is often much easier to manage.

To install an <strong>NGINX Ingress controller</strong> using Helm, use the chart <code class="language-plaintext highlighter-rouge">stable/nginx-ingress</code>, which is available in the official repository. To install the chart with the release name ingress-nginx:
<code class="language-plaintext highlighter-rouge">helm install stable/nginx-ingress --name ingress-nginx</code>

If the kubernetes cluster has RBAC enabled, then run:
<code class="language-plaintext highlighter-rouge">helm install stable/nginx-ingress --name ingress-nginx --set rbac.create=true</code>

##Exposing Services using NGINX Ingress Controller##
Now that an ingress controller is running in the cluster, you will need to create services that leverage it using either host, URI mapping, or even both.

Sample of a host-based service mapping through an ingress controller using the type “Ingress”:

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">extensions/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">hello-world</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="s">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">ingress-nginx</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">host1.domain.ext</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">backend</span><span class="pi">:</span>
          <span class="na">serviceName</span><span class="pi">:</span> <span class="s">hello-world</span>
          <span class="na">servicePort</span><span class="pi">:</span> <span class="m">80</span>
</code></pre></div></div>

Using a URI involves the same basic layout, but specifying more details in the “paths” section of the yaml file. When TLS encryption is required, then you will need to have certificates stored as secrets inside Kubernetes. This can be done manually or with an open source tool like cert-manager. The yaml file needs a little extra information to enable TLS (mapping from port 443 to port 80 is done in the ingress controller):

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">extensions/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">hello-world</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="s">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">ingress-nginx</span>
    <span class="s">cert-manager.io/cluster-issuer</span><span class="pi">:</span> <span class="s">letsencrypt-prod</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">tls</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">host1.domain.ext</span>
    <span class="pi">-</span> <span class="s">host2.domain.ext</span>
    <span class="na">secretName</span><span class="pi">:</span> <span class="s">hello-kubernetes-tls</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">host1.domain.ext</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">backend</span><span class="pi">:</span>
          <span class="na">serviceName</span><span class="pi">:</span> <span class="s">hello-world</span>
          <span class="na">servicePort</span><span class="pi">:</span> <span class="m">80</span>
</code></pre></div></div>

##Next Steps##
WIth a fully-functioning cluster and ingress controller, even a single node one, you are ready to start building and testing applications just like you would in your production environment, with the same ability to test your configuration files and application traffic routing. You just have some capacity limitations that won’t happen on true multi-node clusters.