How to Set up Nginx Ingress Controller with SSL on VKE

How to Set up Nginx Ingress Controller with SSL on VKE header image

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-worldding-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:

  • Deploy a Vultr Kubernetes Engine (VKE) Cluster with at least 2 nodes.
  • Install Kubectl to access your VKE cluster.
  • Install the Helm Package Manager on your computer.

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-controllerYour 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.yamlVisit the official CertManager releases page to get the latest version.
  2. Inspect the CertManager Kubernetes resources. $ kubectl get all -n cert-managerYour 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: 80Save 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: 80Save 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 deploymentsYour 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-worldSave 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-worldSave 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 servicesYour 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 ingressYour 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 10m6sIn 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.comBoth 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-managerYour 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:05ZAs 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: nginxThe 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.
  3. Apply the issuer resource to your cluster. $ kubectl apply -f cert-issuer.yaml
  4. Verify that the issuer resource is available and ready to use. $ kubectl get issuerYour output should look like the one below. NAME READY AGE letsencrypt-nginx True 2m
  5. 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.
  6. Check the state of your Ingress resources. $ kubectl get ingress
  7. Edit the hello-world-host.yaml file you created earlier. $ nano hello-world-host.yaml
  8. 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: nginxSave and close the file.
  9. Edit the ding-world-host.yaml file. $ nano hello-world-host.yaml
  10. 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: nginxSave and close the file.To avoid any Kubernetes manifest styling errors. Delete the existing files and recreate them with the above configurations.
  11. 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
  12. Verify that your Ingress resources now have the TLS port 443 activated under the PORTS column.$ kubectl get ingressOutput: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
  13. Verify that the certificate resources are available.$ kubectl get certificatesYour 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 5mIf 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 solvers: - dns01: vultr: tokenSecretRef: name: vultr-dns key: api-keyEnter 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.
  10. Apply the certificate resource.$ kubectl apply -f wcard-certificate.yaml
  11. Check the certificate status.$ kubectl get certificate example.comOutput:NAME READY SECRET AGE example.com True example.com 8m18sAs 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.
  12. Check the Kubernetes secret that contains your TLS certificate.$ kubectl describe secret example-wcard
  13. 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
  14. 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: nginxVerify that the secretName: field references the Kubernetes secret containing your SSl certificate. Then, save and close the file.
  15. Verify that the Ingress resource is available.$ kubectl get ingressYour 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 59sAs 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.pemReplace /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: 80Verify 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.

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…

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…

Leave a Reply

Your email address will not be published. Required fields are marked *