This guide explains how to deploy Ingress-NGINX with dynamically (hostname-based) assigned Let’s Encrypt certificates using Flux GitOps. The steps are based on a working example and provide instructions for configuration, deployment, and testing.

Prerequisites

  1. Flux Installed: Ensure Flux is installed and running in your Kubernetes cluster.
  2. Let’s Encrypt Certificate: Provisioned for FQDN. Follow the instructions in the Let’s Encrypt guide.
  3. Git Repository: A Git repository structured for Flux GitOps, e.g.:
.
├── apps/
└── ingress-nginx/
    └── base/
├── clusters/
│   └── production/
│       ├── flux-system/
│       │   └── sources/
│       └── apps/
├── infrastructure/
    └── networking/
        ├── metallb/
        └── ingress-nginx/
  1. Kubernetes Cluster: A Kubernetes cluster with MetalLB-compatible networking.

1. Deploying Ingress-NGINX via Flux

Step 1: Create the Ingress-NGINX Namespace

Create a namespace for Ingress-NGINX in your Git repository:

File: clusters/production/flux-system/sources/ingress-nginx-namespace.yaml:

# File: clusters/production/flux-system/sources/ingress-nginx-namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx

Step 2: Create the Ingress-NGINX HelmRepository

Define the Helm repository for Ingress-NGINX:

File: clusters/production/flux-system/sources/ingress-nginx-helmrepository.yaml:

# File: clusters/production/flux-system/sources/ingress-nginx-helmrepository.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 1h
  url: https://kubernetes.github.io/ingress-nginx

Step 3: Create the Ingress-NGINX HelmRelease

Define the HelmRelease to install Ingress-NGINX:

File: clusters/production/flux-system/sources/ingress-nginx-helmrelease.yaml:

# File: clusters/production/flux-system/sources/ingress-nginx-helmrelease.yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  releaseName: ingress-nginx
  chart:
    spec:
      chart: ingress-nginx
      version: 4.12.0
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  interval: 10m
  values:
    controller:
      service:
        type: LoadBalancer
      admissionWebhooks:
        enabled: true

Add these files to your kustomization.yaml file:

File: clusters/production/flux-system/sources/kustomization.yaml:

# File: clusters/production/flux-system/sources/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # ...existing resources...
  - ingress-nginx-namespace.yaml
  - ingress-nginx-helmrepository.yaml
  - ingress-nginx-helmrelease.yaml

Step 4: Configure Ingress-NGINX

Create the Ingress-NGINX configuration:

File: infrastructure/networking/ingress-nginx/ingress-nginx-config.yaml:

# File: infrastructure/networking/ingress-nginx/ingress-nginx-config.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./infrastructure/networking/ingress-nginx
  prune: true
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: ingress-nginx-controller
      namespace: ingress-nginx
  dependsOn: []

Add this file to your kustomization.yaml:

File: infrastructure/networking/ingress-nginx/kustomization.yaml:

# File: infrastructure/networking/ingress-nginx/kustomization.yaml
---
resources:
  - ingress-nginx-config.yaml

Step 5: Add Ingress-NGINX in Flux

Add the configuration to your Flux kustomization.yaml:

File: clusters/production/flux-system/ingress-nginx.yaml:

# File: clusters/production/flux-system/ingress-nginx.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./infrastructure/networking/ingress-nginx
  prune: true
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: ingress-nginx-controller
      namespace: ingress-nginx

Add this entry to your clusters/production/flux-system/kustomization.yaml:

File: clusters/production/flux-system/kustomization.yaml:

# File: clusters/production/flux-system/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # ...existing entries...
  - ingress-nginx.yaml

Step 6: Create Test Ingress with Certificate

Create a test ingress resource. This test ensures that the NGINX controller is properly configured to serve the Let’s Encrypt certificate and validates that TLS is working correctly for the specified hostname.

File: apps/ingress-nginx/base/nginx-test-ingress.yaml:

# File: apps/ingress-nginx/base/nginx-test-ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-production
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - <hostname FQDN>  # Set to your test hostname
    secretName: letsencrypt-production-cert-tls
  rules:
  - host: <hostname FQDN>  # Set to your test hostname
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              number: 80

Add this file to the apps/ingress-nginx/base/kustomization.yaml:

File: apps/ingress-nginx/base/kustomization.yaml:

# File: apps/ingress-nginx/base/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - nginx-test-ingress.yaml

Include the entire folder in clusters/production/kustomization.yaml:

File: clusters/production/kustomization.yaml:

# File: clusters/production/kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # ...existing resources...
  - ../../../apps/ingress-nginx/base

2. Commit and Push the Changes

git add -A
git commit -m "Deploy Ingress-NGINX with Let's Encrypt"
git push

Reconcile Flux to apply the changes:

flux reconcile kustomization apps -n flux-system
flux reconcile kustomization ingress-nginx -n flux-system

3. Test the Deployment

Verify the Setup

  1. Check all kustomizations are in a READY state:
kubectl get kustomizations -A
# Example output
# NAMESPACE      NAME            AGE     READY   STATUS
# cert-manager   cert-manager    6d19h   True    Applied revision: main@sha1:c830e277a93d0c73fb4941b6f2e5b6aea15f20cb
# flux-system    flux-system     8d      True    Applied revision: main@sha1:c830e277a93d0c73fb4941b6f2e5b6aea15f20cb
# flux-system    ingress-nginx   4h6m    True    Applied revision: main@sha1:c830e277a93d0c73fb4941b6f2e5b6aea15f20cb
# flux-system    metallb         2d20h   True    Applied revision: main@sha1:c830e277a93d0c73fb4941b6f2e5b6aea15f20cb
# flux-system    pull-secrets    8d      True    Applied revision: main@sha1:c830e277a93d0c73fb4941b6f2e5b6aea15f20cb
  1. Check the namespace:
kubectl get namespace ingress-nginx
# Example output
# NAME              STATUS   AGE
# ingress-nginx     Active   4h8m
  1. Verify the Helm repository:
kubectl get helmrepository ingress-nginx -n flux-system
# Example output
# NAME            URL                                          AGE     READY   STATUS
# ingress-nginx   https://kubernetes.github.io/ingress-nginx   4h9m    True    stored artifact: revision 'sha256:79f424dbbc344395f9cf19d4ade457e9de14af43a337bd76ee593fa1ad920330'
  1. Verify the Helm release:
kubectl get helmrelease ingress-nginx -n ingress-nginx
# Example output
# NAME            AGE     READY   STATUS
# ingress-nginx   4h10m   True    Helm install succeeded for release ingress-nginx/ingress-nginx.v1 with chart [email protected]
  1. Check the deployment and pods:
kubectl get deployments -n ingress-nginx
# Example output
# NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
# ingress-nginx-controller   1/1     1            1           4h12m

kubectl get pods -n ingress-nginx
# Example output
# NAME                                        READY   STATUS    RESTARTS   AGE
# ingress-nginx-controller-6bf449f988-kns7n   1/1     Running   0          3h38m
  1. Check for the external IP of the ingress-nginx-controller:
kubectl get svc -n ingress-nginx
# Example output
# NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
# ingress-nginx-controller             LoadBalancer   10.109.225.4    xxx.xxx.xxx.91   80:32680/TCP,443:30597/TCP   4h13m
# ingress-nginx-controller-admission   ClusterIP      10.101.140.93   <none>         443/TCP                      4h13m

Test the Ingress

Ensure your hostname resolves to the external IP. If DNS is not configured, you can manually update your /etc/hosts file by adding an entry mapping the external IP to the hostname, e.g., xxx.xxx.xxx.91 <FQDN>. Once resolved, test using curl:

curl -v -k https://<FQDN> 2>&1 | grep issuer
# Example output
# *  issuer: C=US; O=Let's Encrypt; CN=R11
# *  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.

This completes the deployment of Ingress-NGINX with Let’s Encrypt certificates using Flux. If you encounter any issues, review the Flux logs running flux events and ensure all resources are correctly configured.

kubectl get namespace ingress-nginx
# Example output
# NAME              STATUS   AGE
# ingress-nginx     Active   4h8m
  1. Verify the Helm repository:
kubectl get helmrepository ingress-nginx -n flux-system
# Example output
# NAME            URL                                          AGE     READY   STATUS
# ingress-nginx   https://kubernetes.github.io/ingress-nginx   4h9m    True    stored artifact: revision 'sha256:79f424dbbc344395f9cf19d4ade457e9de14af43a337bd76ee593fa1ad920330'
  1. Verify the Helm release:
kubectl get helmrelease ingress-nginx -n ingress-nginx
# Example output
# NAME            AGE     READY   STATUS
# ingress-nginx   4h10m   True    Helm install succeeded for release ingress-nginx/ingress-nginx.v1 with chart [email protected]
  1. Check the deployment and pods:
kubectl get deployments -n ingress-nginx
# Example output
# NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
# ingress-nginx-controller   1/1     1            1           4h12m
#
kubectl get pods -n ingress-nginx
# Example output
# NAME                                        READY   STATUS    RESTARTS   AGE
# ingress-nginx-controller-6bf449f988-kns7n   1/1     Running   0          3h38m
  1. Check for the external IP of the ingress-nginx-controller :
kubectl get svc -n ingress-nginx
# Example output
# NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
# ingress-nginx-controller             LoadBalancer   10.109.225.4    xxx.xxx.xxx.91   80:32680/TCP,443:30597/TCP   4h13m
# ingress-nginx-controller-admission   ClusterIP      10.101.140.93   <none>         443/TCP                      4h13m

Test the Ingress:

Ensure your hostname resolves to the external IP. If DNS is not configured, you can manually update your /etc/hosts file by adding an entry mapping the external IP to the hostname, e.g., xxx.xxx.xxx.91 . Once resolved, test using curl:

curl -v -k https://<FQDN>  2>&1 | grep issuer
# Example output
# *  issuer: C=US; O=Let's Encrypt; CN=R11
# *  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.

This completes the deployment of Ingress-NGINX with Let’s Encrypt certificates using Flux. If you encounter any issues, review the Flux logs running flux events and ensure all resources are correctly configured.