Article

Table of Contents
Theme:
Was this article helpful?

0  out of  1 found this helpful

Try Vultr Today with

$50 Free on Us!

Want to contribute?

You could earn up to $600 by adding new articles.

How to Set up Nginx Ingress Controller with SSL on VKE

Author: Christian Kintu

Last Updated: Thu, Jun 22, 2023
Kubernetes Load Balancer

Introduction

Nginx Ingress Controller is a popular Kubernetes Ingress controller that uses Nginx as a reverse proxy and load balancer to securely route external traffic to services in a cluster. It works as the single access point for underlying services while offering SSL/TLS termination, load balancing, session handling, and path-based routing for services in the Kubernetes cluster.

In this article, you get to set up Nginx Ingress Controller with SSL in a Vultr Kubernetes Engine (VKE) cluster. Using the controller, you will issue Let’s Encrypt certificates with Cert Manager and import commercial SSL certificates for your existing domain name to secure underlying services with HTTPS.

The example services, hello-world, ding-world used in this article represent your Kubernetes services, and the example IP Address 192.0.2.1 represents your cluster's external IP. Replace all occurrences with your actual Kubernetes cluster details.

Prerequisites

Before you begin, make sure you:

Install the Nginx Ingress Controller

  1. Add the Nginx Ingress repository.

    $ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
    
  2. Update Helm.

    $ helm repo update
    
  3. Install the Nginx Ingress Controller.

    $ helm install ingress-nginx ingress-nginx/ingress-nginx
    
  4. After installation, a Vultr Load Balancer is automatically added to your cluster. Verify that the assigned external IP Address matches your load balancer IP in the VKE dashboard.

    $ kubectl get services ingress-nginx-controller
    

    Your output should look like the one below:

    NAME                       TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)                      AGE
    
    ingress-nginx-controller   LoadBalancer   10.108.201.180   192.0.2.1   80:31125/TCP,443:31287/TCP   39h
    

Install CertManager

  1. Install the latest CertManager version.

    $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.1/cert-manager.yaml
    

    Visit the official CertManager releases page to get the latest version.

  2. Inspect the CertManager Kubernetes resources.

    $ kubectl get all -n cert-manager
    

    Your output should look like the one below:

    NAME                                           READY   STATUS    RESTARTS   AGE
    
    pod/cert-manager-5f68c9c6dd-stmp6              1/1     Running   0          35h
    
    pod/cert-manager-cainjector-57d6fc9f7d-gwqr5   1/1     Running   0          35h
    
    pod/cert-manager-webhook-5b7ffbdc98-sq8kg      1/1     Running   0          35h
    
    
    
    NAME                           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    
    service/cert-manager           ClusterIP   10.102.38.47   <none>        9402/TCP   35h
    
    service/cert-manager-webhook   ClusterIP   10.97.255.91   <none>        443/TCP    35h
    
    
    
    NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
    
    deployment.apps/cert-manager              1/1     1            1           35h
    
    deployment.apps/cert-manager-cainjector   1/1     1            1           35h
    
    deployment.apps/cert-manager-webhook      1/1     1            1           35h
    
    
    
    NAME                                                 DESIRED   CURRENT   READY   AGE
    
    replicaset.apps/cert-manager-5f68c9c6dd              1         1         1       35h
    
    replicaset.apps/cert-manager-cainjector-57d6fc9f7d   1         1         1       35h
    
    replicaset.apps/cert-manager-webhook-5b7ffbdc98      1         1         1       35h
    

Deploy Backend Applications

In this section, deploy example applications to test your Nginx Ingress controller. For purposes of this article, deploy two example applications, hello-world and ding-world using the NginxDemos image that outputs a simple webpage with the server hostname, IP, and port.

  1. Using a text editor such as Nano, create a new file named hello-world-deploy.yaml with the following configurations.

    apiVersion: apps/v1
    
    kind: Deployment
    
    metadata:
    
      name: hello-world
    
    spec:
    
     replicas: 2
    
    selector:
    
     matchLabels:
    
       app: hello-world
    
    template:
    
     metadata:
    
       labels:
    
         app: hello-world
    
     spec:
    
       containers:
    
       - name: hello-world
    
         image: nginxdemos/hello
    
         ports:
    
         - containerPort: 80
    

    Save and close the file.

  2. Create the second manifest file named ding-world-deploy.yaml with the following configurations.

    apiVersion: apps/v1
    
    kind: Deployment
    
    metadata:
    
      name: ding-world
    
    spec:
    
     replicas: 2
    
    selector:
    
     matchLabels:
    
       app: ding-world
    
    template:
    
     metadata:
    
       labels:
    
         app: ding-world
    
     spec:
    
       containers:
    
       - name: ding-world
    
         image: nginxdemos/hello
    
         ports:
    
         - containerPort: 80
    

    Save and close the file.

  3. Apply the hello-world deployment.

    $ kubectl apply -f hello-world-deploy.yaml
    
  4. Apply the ding-world deployment.

    $ kubectl apply -f ding-world-deploy.yaml
    
  5. Verify that your deployments are successful.

    $ kubectl get deployments
    

    Your output should look like the one below.

    NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
    
    ding-world                 2/2     2            2           36h
    
    hello-world                2/2     2            2           36h
    
    ingress-nginx-controller   1/1     1            1           39h
    
  6. Create a new service-hello-world.yaml service manifest and add the following configurations to the file.

    apiVersion: v1
    
    kind: Service
    
    metadata:
    
     name: hello-world
    
    spec:
    
      ports:
    
        - name: http
    
          port: 80
    
          targetPort: 8080
    
      selector:
    
        app:hello-world
    

    Save and close the file.

  7. Create the service-ding-world.yaml file with the following contents.

    apiVersion: v1
    
    kind: Service
    
    metadata:
    
     name: ding-world
    
    spec:
    
      ports:
    
        - name: http
    
          port: 80
    
          targetPort: 8080
    
      selector:
    
        app:ding-world
    

    Save and close the file.

  8. Deploy the hello-world service.

    $ kubectl apply -f service-hello-world.yaml
    
  9. Deploy the ding-world service.

    $ kubectl apply -f ding-hello-world.yaml
    
  10. Verify that all services are running.

    $ kubectl get services
    

    Your output should look like the one below.

    NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
    
    echo    ClusterIP   10.106.22.216   <none>        80/TCP    1m
    
    quote   ClusterIP   10.100.202.158    <none>        80/TCP    1m
    

Setup DNS Records

  1. Login to your DNS Provider account. For example, Vultr DNS.

  2. Access your domain.

  3. Set up a new domain A subdomain record with the value hello-world that points to your LoadBalancer’s external IP Address.

  4. Set up another A subdomain record with the value ding-world that points to the same IP Address.

    Setup a Domain A Record on Vultr

  5. In a web browser, visit your new subdomains and verify that they propagate to your Ingress Controller which returns a 404 Unknown error.

    hello-world.example.com
    

Configure the Nginx Ingress Controller to Expose backend Applications

When the Nginx Ingress controller is successfully installed, services running, and the DNS records correctly pointing to the Loadbalancer’s external IP. You need to create Ingress resources to define how external traffic routes to services in the cluster.

In this section, create Ingress resources for both applications in the cluster and test that each host maps to the correct service as described below.

  1. Create a new Ingress resource named hello-world-host.yaml with the following contents.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
     name: ingress-hello-world
    
     annotations:
    
      cert-manager.io/issuer: letsencrypt-nginx
    
    spec:
    
      ingressClassName: nginx
    
      rules:
    
      - host: hello-world.example.com
    
        http:
    
          paths:
    
          - pathType: Prefix
    
          path: "/"
    
          backend:
    
            service:
    
              name: hello-world
    
              port:
    
                number: 80
    
  2. Create the ding-world-host.yaml manifest with the following contents.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
     name: ingress-ding-world
    
     annotations:
    
      cert-manager.io/issuer: letsencrypt-nginx
    
    spec:
    
      ingressClassName: nginx
    
      rules:
    
      - host: ding-world.example.com
    
        http:
    
          paths:
    
          - pathType: Prefix
    
          path: "/"
    
          backend:
    
            service:
    
              name: ding-world
    
              port:
    
                number: 80
    
  3. Deploy the host manifests using the following command.

    $ kubectl apply -f hello-world-host.yaml
    
    $ kubectl apply -f ding-world-host.yaml
    
  4. Verify that the Ingress resources are available.

    $ kubectl get ingress
    

    Your output should look like the one below.

    NAME            CLASS   HOSTS                        ADDRESS          PORTS   AGE
    
    ingress-hello-world    nginx   hello-world.example.com   192.0.2.1  80      10m1s
    
    ingress-ding-world   nginx   ding-world.example.com   192.0.2.1  80      10m6s
    

    In the above output. Verify that the Address column matches the Nginx load balancer IP Address you created earlier.

Test

  1. Using a web browser such as Google Chrome, visit each of your configured domains as below.

    http://hello-world.example.com
    
    http://ding-world.example.com
    

    Both domains should load correctly with a different Server address and name returned by each of the applications.

    Example hello-world Kubernetes application

Setup Nginx Ingress Controller to use Production-Ready SSL Certificates

You can set up the Nginx Ingress controller to use trusted production-ready SSL certificates for your cluster services using an Issuer resource for CertManager. By default, CertManager creates Custom Resource Definitions (CRDs) to handle the certificate issuance process from a Certificate Authority (CA) such as Let’s Encrypt, and these include the following.

  • Issuer: Defines an issuer configuration such as the type of issuer like ACME, method of certificate issuance like DNS01 or HTTP01, and the necessary credentials in a single namespace.

  • Cluster Issuer: Functions like Issuer but can issue certificates to any namespace within the Kubernetes cluster. It’s important when using the same issuer configuration across multiple namespaces.

  • Certificate: Defines a namespaced resource with the desired properties of a TLS/SSL certificate such as the domain names, associated secrets to store the certificate, and the trusted CA issuer to use. The Certificate must reference an Issuer or Cluster Issuer resource.

In this section, set up the Nginx Ingress Controller to use trusted SSL certificates issued by CertManager.

  1. First, inspect the available CRDs by running the following command.

    $ kubectl get crd -l app.kubernetes.io/name=cert-manager
    

    Your output should look like the one below.

    NAME                                  CREATED AT
    
    certificaterequests.cert-manager.io   2023-06-12T23:01:59Z
    
    certificates.cert-manager.io          2023-06-12T23:02:00Z
    
    challenges.acme.cert-manager.io       2023-06-12T23:02:01Z
    
    clusterissuers.cert-manager.io        2023-06-12T23:02:03Z
    
    issuers.cert-manager.io               2023-06-12T23:02:04Z
    
    orders.acme.cert-manager.io           2023-06-12T23:02:05Z
    

    As displayed in the above output, the Issuer, ClusterIssuer, and Certificate CRDs must be available.

Setup Let’s Encrypt Certificates

  1. Create a new issuer manifest. For example cert-issuer.yaml.

    $ nano cert-issuer.yaml
    
  2. Add the following configuration to the file.

    apiVersion: cert-manager.io/v1
    
    kind: Issuer
    
    metadata:
    
     name: letsencrypt-nginx
    
    spec:
    
      acme:
    
        email: hello@example.com
    
        server: https://acme-v02.api.letsencrypt.org/directory
    
        privateKeySecretRef:
    
          name: letsencrypt-nginx-prod
    
        solvers:
    
         - http01:
    
            ingress:
    
              class: nginx
    

    The above configuration uses the ACME issuer with the following fields:

  • email: Active email address to associate with the ACME account.

  • server:: URL to access the ACME Let’s Encrypt server endpoint.

  • privateKeySecretRef: name: Kubernetes secret to store the generated ACME account private key.

  • solvers: Defines the certificate issuer challenge. You can either use the http01 challenge or the dns01 challenge. It’s recommended to use the http01 challenge unless issuing wildcard certificates.

    Enter your email address to replace the pre-filled value, then save and close the file.

  1. Apply the issuer resource to your cluster.

    $ kubectl apply -f cert-issuer.yaml
    
  2. Verify that the issuer resource is available and ready to use.

    $ kubectl get issuer
    

    Your output should look like the one below.

    NAME                      READY   AGE
    
    letsencrypt-nginx         True    2m
    
  3. To configure the Ingress controller to map hosts with TLS. Edit each of the host manifests you created earlier, update the annotations section, and add a new tls section as described below.

  4. Check the state of your Ingress resources.

    $ kubectl get ingress
    
  5. Edit the hello-world-host.yaml file you created earlier.

    $ nano hello-world-host.yaml
    
  6. Update the file with the following configurations.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
      name: ingress-hello-world
    
      namespace: cert-manager
    
    annotations:
    
      cert-manager.io/issuer: letsencrypt-nginx
    
    spec:
    
      tls:
    
      - hosts:
    
       - hello-world.example.com
    
       secretName: letsencrypt-nginx-hello
    
    rules:
    
      - host: hello-world.example.com
    
        http:
    
          paths:
    
            - path: /
    
              pathType: Prefix
    
              backend:
    
               service:
    
                name: echo
    
                port:
    
                  number: 80
    
    ingressClassName: nginx
    

    Save and close the file.

  7. Edit the ding-world-host.yaml file.

    $ nano hello-world-host.yaml
    
  8. Update the file with the following configurations.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
      name: ingress-ding-world
    
      namespace: cert-manager
    
    annotations:
    
      cert-manager.io/issuer: letsencrypt-nginx
    
    spec:
    
      tls:
    
      - hosts:
    
       - ding-world.example.com
    
       secretName: letsencrypt-nginx-ding
    
    rules:
    
      - host: ding-world.example.com
    
        http:
    
          paths:
    
            - path: /
    
              pathType: Prefix
    
              backend:
    
               service:
    
                name: echo
    
                port:
    
                  number: 80
    
    ingressClassName: nginx
    

    Save and close the file.

    To avoid any Kubernetes manifest styling errors. Delete the existing files and recreate them with the above configurations.

  9. Apply the configurations to enable TLS on each of the hosts.

    $ kubectl apply -f hello-world-host.yaml
    
    $ kubectl apply -f ding-world-host.yaml
    
  10. Verify that your Ingress resources now have the TLS port 443 activated under the PORTS column.

    $ kubectl get ingress
    

    Output:

    NAME             CLASS    HOSTS                 ADDRESS         PORTS     AGE
    
    ingress-ding     nginx    ding-world.example.com   192.0.2.1   80,443   10m
    
    ingress-hello    nginx    hello-world.example.com   192.0.2.1   80,443   10m
    
  11. Verify that the certificate resources are available.

    $ kubectl get certificates
    

    Your output should look like the one below.

    NAME                     READY   SECRET                   AGE
    
    letsencrypt-nginx-hello        True   letsencrypt-nginx-hello      5m
    
    letsencrypt-nginx-ding  True    letsencrypt-nginx-ding   5m
    

    If the READY column returns True, your Let’s Encrypt certificates are successfully propagated. If False, please check your Ingress resource configuration and reapply changes to request for a new certificate.

Wildcard Certificates

To set up the Nginx Ingress controller to use Wildcard certificates, you need to create an Issuer or ClusterIssuer resource that uses the DNS-01 challenge. Depending on your DNS provider, you need to enter your account API key for CertManager to create necessary DNS TXT records required to pass the challenge.

In this section, you'll use Vultr DNS with your Vultr API key to create the necessary DNS records during the DNS-01 challenge. If your domain is with a different DNS provider such as Cloudflare, please use your provider API key to pass the challenge as described below.

Since Wildcard certificates cover your full domain *.example.com together with any subdomains. It’s important to cover all namespaces using a Cluster Issuer with a separate subdomain for every service.

  1. Create a new Kubernetes secret to store your Vultr API credentials.

    $ nano vultr-secret.yaml
    
  2. Add the following configurations to the file.

    apiVersion: v1
    
    kind: Secret
    
    metadata:
    
     name: vultr-dns
    
     namespace: cert-manager
    
    type: Opaque
    
    stringData:
    
      apiKey: <your-vultr-api-key>
    

    Save and close the file.

  3. Apply the secret.

    $ kubectl apply -f vultr-secret.yaml
    
  4. Create a Cluster Issuer manifest, for example cluster-issuer.yaml.

    $ nano cluster-issuer.yaml
    
  5. Add the following configurations to the file.

    apiVersion: cert-manager.io/v1
    
    kind: ClusterIssuer
    
    metadata:
    
     name: letsencrypt-prod-wcard
    
    spec:
    
     acme:
    
     email: hello@example.com
    
     server: https://acme-v02.api.letsencrypt.org/directory
    
     privateKeySecretRef:
    
       name: letsencrypt-prod-wcard-private
    
    domains.
    
       solvers:
    
         - dns01:
    
             vultr:
    
               tokenSecretRef:
    
                 name: vultr-dns
    
         key: api-key
    

    Enter your email address, then save and close the file.

  6. Create the Cluster Issuer.

    $ kubectl apply -f cluster-issuer.yaml
    
  7. Verify that the Cluster Issuer is successfully created.

    $ kubectl get issuer
    
  8. Create a certificate resource that references the Cluster Issuer using the following command.

    $ nano wcard-certificate.yaml
    
  9. Add the following configurations to the file.

    apiVersion: cert-manager.io/v1
    
    kind: Certificate
    
    metadata:
    
     name: example-wcard
    
    spec:
    
     secretName: example-wcard
    
     issuerRef:
    
       name: letsencrypt-prod-wcard
    
       kind: ClusterIssuer
    
       group: cert-manager.io
    
     commonName: "*.example.com"
    
     dnsNames:
    
       - "example.com"
    
       - "*.example.com"
    

    The certificate manifest contains the following important fields:

  • secretName: Defines the secret name under which Cert-Manager stored the certificate and private key.

  • Issuer Ref: name: The Issuer resource to use for certificate issuance.

  • commonName: The Common Name (CN) to use on the SSL certificate.

  • dnsNames: List of DNS SANs (Subject Alternative Names) to include on the SSL certificate.

  1. Apply the certificate resource.

    $ kubectl apply -f wcard-certificate.yaml
    
  2. Check the certificate status.

    $ kubectl get certificate example.com
    

    Output:

    NAME                    READY   SECRET                  AGE
    
    example.com             True    example.com             8m18s
    

    As displayed in the above output, the READY column should return True if the certificate is successfully issued. If False, check your certificate configurations, and verify that you used the correct DNS configurations in your ClusterIssuer resource.

  3. Check the Kubernetes secret that contains your TLS certificate.

    $ kubectl describe secret example-wcard
    
  4. To configure Nginx to use the Wildcard certificates, create an Ingress manifest that combines multiple hosts with different paths, but the same SSL certificate.

    $ nano wcard-host-ingress.yaml
    
  5. Add the following configurations to the file.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
     name: ingress-wcard-apps
    
    spec:
    
    tls:
    
      - hosts:
    
       - "*.example.com"
    
      secretName: example-wcard
    
    rules:
    
      - host: example.com
    
        http:
    
         paths:
    
          - path: /
    
            pathType: Prefix
    
            backend:
    
            service:
    
             name: ding-world
    
             port:
    
              number: 80
    
     - host: hello-world.example.com
    
       http:
    
        paths:
    
         - path: /
    
           pathType: Prefix
    
           backend:
    
             service:
    
               name: hello-world
    
               port:
    
                 number: 80
    
    ingressClassName: nginx
    

    Verify that the secretName: field references the Kubernetes secret containing your SSl certificate. Then, save and close the file.

  6. Verify that the Ingress resource is available.

    $ kubectl get ingress
    

    Your output should look like the one below.

    NAME              CLASS  HOSTS                                                   ADDRESS          PORTS     AGE
    
    ingress-wcard-apps nginx  hello-world.example.com,ding-world.example.com          192.0.2.1          80, 443   59s
    

    As displayed in the above output, verify that the hosts are TLS terminated on port 443.

You have configured Nginx Ingress Controller to use Wildcard SSL certificates, for more information on using Vultr DNS, visit the CertManager Webhook page for Vultr.

Import Commercial SSL Certificates

To import commercial SSL certificates purchased from a trusted certificate authority (CA), convert the certificate and private key to the base64 format, add them to a Kubernetes secret, then configure your Ingress hosts to use the certificates.

  1. Convert the commercial SSL certificate and private key to base64. Copy the resultant values to your clipboard.

    $ base64 -w 0 /path/ssl-certificate.pem
    
    $ base64 -w 0 /path/cert-private-key.pem
    

    Replace /path with the actual directory path to your commercial SSL certificate and private key.

  2. Create a new Kubernetes secrets manifest.

    $ nano ssl-secret.yaml
    
  3. Add the following configurations to the file.

    apiVersion: v1
    
    kind: Secret
    
    metadata:
    
     name: prod-ssl-secret
    
    type: kubernetes.io/tls
    
    data:
    
      tls.crt: <paste-base64-values>
    
      tls.key: <paste-base64-values>
    

    Paste your base64 encoded values to the respective fields as follows:

    • tls-crt: SSL certificate in base64

    • tls.key: Certificate private key in base64

    Save and close the file.

  4. To configure Nginx to use commercial SSL certificates. Create a new Ingress manifest.

    $ nano ingress-ssl.yaml
    
  5. Add the following configurations to the file.

    apiVersion: networking.k8s.io/v1
    
    kind: Ingress
    
    metadata:
    
     name: ingress-prod
    
    spec:
    
     tls:
    
       - hosts:
    
       - example.com
    
    secretName: prod-ssl-secret
    
    rules:
    
      - host: example.com
    
        http:
    
          paths:
    
            - path: /
    
              pathType: Prefix
    
              backend:
    
                service:
    
                name: hello-world
    
                port:
    
                  number: 80
    

    Verify that the secretName: matches your Kubernetes secret. Then, save and close the file.

You have set up the Nginx Ingress Controller to use your commercial SSL certificates.

Test the SSL Configuration

Depending on your SSL certificate deployment method, verify that you can securely access your services over HTTPS using a web browser of your choice.

    https://hello-world.example.com

    https://ding-world.example.com

If you try to access your hosts over HTTP, the Ingress controller automatically redirects your request to HTTPS.

Troubleshooting

  1. Nginx 502 Gateway Error:

    Verify that your Ingress configuration is correct and the referenced services are available and running.

  2. Unable to generate Let’s Encrypt Certificates, READY column remains False:

    Verify that your Issuer, and Certificate configurations are correct. Or, check the CertManager Logs for further troubleshooting.

  3. Error from server (InternalError): error when creating "ingress-myapp.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io":

    To fix the error, run the following command, then reapply your configuration.

    $ kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
    

Conclusion

In this article, you have installed and set up Nginx Ingress Controller with SSL on a Vultr Kubernetes Engine (VKE) cluster. You can use the controller to correctly route external requests to cluster services securely over HTTPS. For more information about your cluster, visit the VKE reference page.

Next Steps

With the Nginx Ingress Controller correctly routing requests to your cluster services, you can set up multiple applications and services depending on your needs. For more information, visit the following resources.

Want to contribute?

You could earn up to $600 by adding new articles.