Note: make deploy is HTTP-only and does not install cert-manager or apply cert-manager.yaml. Use this document when you want to add TLS manually.
This optional flow provisions SSL certificates using Let's Encrypt and cert-manager.
Still required before TLS routing works (install once per cluster):
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90sInstall cert-manager on the cluster (not done by make deploy):
Manual install:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yamlDeploy the application with your domain (install ingress first; cert-manager is handled by deploy when needed):
# If you SSH into the same hostname that users use in the browser:
make deploy HOST=iammanager.it.eu-cloud.mirantis.net USER=your-linux-user
# If you SSH to a different machine than the public app URL (recommended when sharing an ingress):
make deploy HOST=k8s-bastion.internal DOMAIN=iammanager.it.eu-cloud.mirantis.net USER=ubuntuImportant:
- HOST is the
ssh/scptarget (bastion or control node withkubectl). - DOMAIN (optional) is the DNS name on the certificate and Ingress. It defaults to HOST when omitted.
- Shared port 80: The nginx ingress controller listens on port 80 for all
Ingressresources. Other apps use differenthost:values on the same controller. This project’s Ingress only matches your DOMAIN, so it does not take over traffic for unrelated hostnames.
DNS: Create an A (or AAAA) record for DOMAIN pointing at the ingress controller’s external load balancer IP (same as your other app if it already uses that ingress).
“Connection refused” on https://your-domain/
Usually nothing is listening on :443 at the IP your DNS resolves to.
-
Confirm DNS (must be the IP where HTTP(S) terminates, often the node running ingress):
dig +short your.domain.example
-
Ingress controller must exist and expose 80/443. On bare metal / k0s the default cloud
LoadBalancernever gets anEXTERNAL-IP; the stock bare-metal ingress manifest uses NodePort instead, so :443 on the node is closed unless you use hostNetwork or an external LB. -
Install nginx ingress and bind host ports (single-node k0s + DNS → that node IP):
make install-ingress-nginx HOST=YOUR_NODE_IP USER=ubuntu INGRESS_HOSTNETWORK=1
-
Firewall: allow 80 and 443 on that host (Let’s Encrypt HTTP-01 needs 80).
-
From your laptop:
curl -vkI --resolve your.domain.example:443:THAT_IP https://your.domain.example/to test before relying on public DNS.
make deploy applies an Ingress with cert-manager.io/cluster-issuer; cert-manager’s ingress-shim creates the Certificate and writes cloud-manager-tls. (The file k8s/certificate.yaml is optional/manual only—not applied by deploy—to avoid duplicate Certificates fighting the same secret.)
- Certificate: Created by cert-manager from the Ingress
tlssection - HTTP-01 Challenge: cert-manager serves the challenge (needs port 80 to the ingress)
- Let’s Encrypt issues the cert → Secret
cloud-manager-tls - Nginx terminates TLS using that secret
Check certificate status:
# Check certificate status
kubectl get certificates -n cloud-manager
# Check certificate details (name may be cloud-manager-tls-… from ingress-shim)
kubectl describe certificate -n cloud-manager -l app.kubernetes.io/name=cloud-manager 2>/dev/null || kubectl describe certificate -n cloud-manager
# Check certificate secret
kubectl get secret cloud-manager-tls -n cloud-managerA 503 from nginx usually means no ready backends for that Ingress host (not a TLS failure).
-
Pods and endpoints
kubectl get pods,svc,endpoints -n cloud-manager kubectl describe pod -n cloud-manager -l app.kubernetes.io/component=app kubectl logs -n cloud-manager -l app.kubernetes.io/component=app --tail=80
-
SQLite on PVC: The app opens
DB_PATH(default/data/iam-manager.db). If the pod runs as non-root and the volume is root-owned, startup can fail and the pod never becomes Ready → 503. The Deployment setsrunAsUser/fsGroup1000 to match the image user; rebuild/push the image after the Dockerfile uid/gid change, then redeploy. -
Image pull:
ImagePullBackOffalso yields no endpoints → 503. Confirmghcr.io/mirantis/cloud-manager:latestexists and is pullable from the cluster (image pull secrets if the registry is private).
Until cloud-manager-tls exists and is bound on the Ingress, nginx often serves the controller default certificate (looks self-signed or “fake”). That is expected until cert-manager marks the Certificate Ready.
kubectl get certificate,secret -n cloud-manager
kubectl describe certificate cloud-manager-cert -n cloud-manager
kubectl get challenges,orders -n cloud-managerFix ACME (DNS to this ingress, port 80 open for HTTP-01, ClusterIssuer email) first. If you previously had two Certificate objects or a stuck invalid order, delete them and let cert-manager recreate from the Ingress:
kubectl delete certificate --all -n cloud-manager
kubectl delete secret cloud-manager-tls -n cloud-manager --ignore-not-found
kubectl delete challenge,order,certificaterequest -n cloud-manager --all --ignore-not-foundThen make deploy ... again (or only re-apply the Ingress). If Let’s Encrypt rate-limited you, deploy with CERT_ISSUER=letsencrypt-staging once, confirm staging cert works, then switch back to letsencrypt-prod after the limit window.
If certificate status shows "Pending":
# Check cert-manager logs
kubectl logs -n cert-manager -l app=cert-manager
# Check certificate events
kubectl describe certificate cloud-manager-cert -n cloud-manager
# Check challenge status
kubectl get challenges -n cloud-manager- DNS not pointing to ingress: Ensure your domain points to the ingress controller's external IP
- Firewall blocking port 80: HTTP-01 challenge requires port 80 to be accessible
- Rate limiting: Let's Encrypt has rate limits; use staging issuer for testing
- Invalid email: Ensure the email in cert-manager.yaml is valid
For testing, use the staging issuer to avoid rate limits:
# Edit certificate.yaml to use staging issuer
# issuerRef:
# name: letsencrypt-staging
# kind: ClusterIssuer- Certificates auto-renew 15 days before expiry
- HTTP traffic is automatically redirected to HTTPS
- HTTPS is enforced for all routes
- Application authentication (admin username/password) applies over SSL