Skip to content

Adding SSL to Local KinD Clusters: Comprehensive Guide

This guide provides detailed instructions for implementing SSL/TLS in your local KinD (Kubernetes in Docker) clusters for secure HTTPS development and testing.

Prerequisites

  • A running KinD cluster
  • kubectl configured to use your cluster
  • Helm v3 installed
  • Basic understanding of Kubernetes networking and certificates

1. Setting Up Ingress Controller

First, we need to install an ingress controller to manage external access to services:

bash
# Install NGINX ingress controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.0/deploy/static/provider/kind/deploy.yaml

# Wait for the controller to be ready
kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=120s

2. Installing cert-manager

cert-manager is a Kubernetes add-on to automate the management and issuance of TLS certificates from various issuing sources.

Step 1: Add the Jetstack Helm repository

bash
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Install cert-manager with CRDs

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.crds.yaml
#kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.17.0

#  --set crds.enabled=true

# Verify the installation
kubectl get pods -n cert-manager

3. Creating a Self-Signed Certificate

Step 1: Create a self-signed issuer

sh
# Create a file named self-signed-issuer.yaml
cat > self-signed-issuer.yaml << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
EOF

# Apply the configuration
kubectl apply -f self-signed-issuer.yaml

# Verify the issuer is ready
kubectl get clusterissuer selfsigned-issuer -o jsonpath='{.status.conditions[0].status}'

Step 2: Installing cert-manager

cert-manager is a Kubernetes addon that automates certificate management:

sh
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Install cert-manager with CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.13.1

# Verify the installation
kubectl get pods -n cert-manager

Step 3: Creating Certificate Issuers

Self-Signed Certificate Issuer For development purposes, a self-signed issuer is sufficient:

sh
# Create a file named self-signed-issuer.yaml
cat > self-signed-issuer.yaml << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
EOF

# Apply the configuration
kubectl apply -f self-signed-issuer.yaml

# Verify the issuer is ready
kubectl get clusterissuer selfsigned-issuer -o jsonpath='{.status.conditions[0].status}'

Let's Encrypt Staging (Optional) For testing with Let's Encrypt without rate limits:

sh
# Create a file named letsencrypt-staging.yaml
cat > letsencrypt-staging.yaml << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    email: kelly.carinola@gmail.com
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
EOF

# Apply the configuration
kubectl apply -f letsencrypt-staging.yaml

Let's Encrypt Production (Optional)

For production certificates (be cautious of rate limits):

sh
# Create a file named letsencrypt-prod.yaml
cat > letsencrypt-prod.yaml << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: kelly.carinola@gmail.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
EOF

# Apply the configuration
kubectl apply -f letsencrypt-prod.yaml

Step 4: Create a certificate resource

sh
# Create a file named certificate.yaml
cat > certificate.yaml << EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: local-tls
  namespace: default
spec:
  secretName: local-tls-cert
  dnsNames:
  - app.local
  - api.local
  - "*.app.local"
  isCA: false
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
EOF

# Apply the configuration
kubectl apply -f certificate.yaml

# Verify the certificate is ready
kubectl get certificate -n default

Step 5: Configuring Ingress with SSL

Create an ingress resource that uses the certificate:

sh
# First, deploy a sample application
kubectl create deployment demo --image=nginx --port=80
kubectl expose deployment demo --port=80

# Create a file named secure-ingress.yaml
cat > secure-ingress.yaml << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-ingress
  annotations:
    spec.ingressClassName: nginx
    cert-manager.io/cluster-issuer: selfsigned-issuer
spec:
  tls:
  - hosts:
    - app.local
    secretName: local-tls-cert
  rules:
  - host: app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo
            port:
              number: 80
EOF

# Apply the configuration
kubectl apply -f secure-ingress.yaml

# Verify the ingress is created
kubectl get ingress

Step 6: Setting Up Local DNS

Configure your local machine to resolve the hostnames:

sh
# Add entries to /etc/hosts
sudo sh -c 'echo "127.0.0.1 app.local api.local" >> /etc/hosts'

Step 7: Testing the Setup

sh
# Using curl with insecure flag (for self-signed certs)
curl -k https://app.local

# Or using wget
wget --no-check-certificate https://app.local

Step 8: Trusting Certificates Locally

Export the Certificate

sh
kubectl get secret local-tls-cert -o jsonpath='{.data.tls\.crt}' | base64 -d > local-cert.pem

Add to System Trust Store Linux (Debian/Ubuntu)

sh
sudo cp local-cert.pem /usr/local/share/ca-certificates/local-cert.crt
sudo update-ca-certificates

macOS

sh
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain local-cert.pem

Windows

powershell
Import-Certificate -FilePath local-cert.pem -CertStoreLocation Cert:\LocalMachine\Root

Step 9: Advanced Configurations

Wildcard Certificates

For covering multiple subdomains:

sh
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
  namespace: default
spec:
  secretName: wildcard-tls-cert
  dnsNames:
  - "*.example.local"
  isCA: false
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer

Using Certificates Across Namespaces

To use a certificate in multiple namespaces, create a Certificate resource in each namespace and reference the same Secret:

sh
# Create certificate in a central namespace
kubectl create namespace cert-central

# Create the certificate
cat > shared-cert.yaml << EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: shared-tls
  namespace: cert-central
spec:
  secretName: shared-tls-cert
  dnsNames:
  - "*.example.local"
  isCA: false
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
EOF

kubectl apply -f shared-cert.yaml

10. Troubleshooting

Certificate Not Being Issued Check cert-manager logs:

sh
kubectl logs -n cert-manager $(kubectl get pods -n cert-manager -l app=cert-manager -o name) -f

Check the status of the certificate:

sh
kubectl describe certificate local-tls-cert -n default

Ingress Controller Issues Check the logs of the ingress controller:

sh
kubectl logs -n ingress-nginx $(kubectl get pods -n ingress-nginx -l app.kubernetes.io/component=controller -o name) -f

How to fix pending on EXTERNAL-IP

Apply MetalLB manifest

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml

Wait for MetalLB to be ready

sh
# Apply MetalLB manifest
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml

# Wait for MetalLB to be ready
kubectl wait --namespace metallb-system \
  --for=condition=ready pod \
  --selector=app=metallb \
  --timeout=90s

# Create IP address pool (using Docker's default network range)
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 172.18.0.200-172.18.0.250
EOF

# Create L2Advertisement
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
  - first-pool
EOF

Conclusion

You now have a fully functional SSL setup in your local KinD cluster. This enables secure HTTPS development and testing without needing to deploy to a cloud environment.

For production environments, consider using Let's Encrypt with appropriate DNS or HTTP challenges instead of self-signed certificates.