Skip to content

Instantly share code, notes, and snippets.

@snormore
Last active February 29, 2024 06:29
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save snormore/c7c2935d746531ed0d75064a6ad6058e to your computer and use it in GitHub Desktop.
Save snormore/c7c2935d746531ed0d75064a6ad6058e to your computer and use it in GitHub Desktop.
Deploying a web app to Kubernetes with SSL using Let's Encrypt via cert-manager and nginx-ingress.

Deploying a web app to Kubernetes with SSL using Let's Encrypt via cert-manager and nginx-ingress

Spin up a Kubernetes cluster

You can do this with MiniKube for development and testing, or Google Cloud's GKE for the real thing.

MiniKube

# Make sure you have MiniKube installed and it's the latest
brew update
minikube delete || echo "You don't have MiniKube installed yet."
brew cask reinstall minikube

# Start it up
minikube start --memory=4096 --cpus=2

# Grab a hostname from ngrok with forwarding to MiniKube
brew cask install ngrok
ngrok http $(minikube ip):80

Google Kubernetes Engine

# Head over to https://console.cloud.google.com/ and create a project
gcloud config set project foobar-app
gcloud services enable container.googleapis.com
gcloud container clusters create foobar --num-nodes 1 --machine-type g1-small
gcloud container clusters get-credentials foobar

Install Helm and Tiller

kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-binding --clusterrole=cluster-admin --serviceaccount kube-system:tiller
helm init --service-account tiller --wait --upgrade

Install nginx-ingress and cert-manager using Helm

export CERT_ISSUER="letsencrypt-staging"  # this can also be letsencrypt-prod
# Don't `--set controller.hostNetwork=true` on minikube
helm install stable/nginx-ingress --namespace kube-system --name ingress --set rbac.create=true --set controller.hostNetwork=true
helm install stable/cert-manager --name cert --namespace kube-system --wait --set ingressShim.extraArgs=\{--default-issuer-name=${CERT_ISSUER},--default-issuer-kind=ClusterIssuer\}

Install the Let's Encrypt cert issuers

# Before running this you should replace instances of "foo@example.com" with your own email
kubectl apply -f cert-issuers.yaml

# Before running this you should replace instances of
# 1. "foobar" with your own app name
# 2. "foo.example.com" with your own hostname or the ngrok hostname if you're using that
# 3. "letsencrypt-staging" with "letsencrypt-prod" if that's the issuer you're using
kubectl apply -f app-deployment.yaml

Update DNS with the ingress IP

If you're using a real domain and not ngrok, at this point you need to grab the ingress IP using the following command and update your DNS with an A record pointing to it. You might have to wait a few minutes for everything to be ready and the IP to show up.

kubectl get ingress

Test it

If your tests fail, keep trying for a few minutes until cert-manager has gone through the motions and provisioned your cert with Let's Encrypt. You can always check the cert-manager pod container logs with kubectl logs to see if there's a problem.

First notice that a request to the HTTP endpoint will result in a redirect to the HTTPS version.

$ curl -v 64c3c5b3.ngrok.io
* Rebuilt URL to: 64c3c5b3.ngrok.io/
...
< Location: https://64c3c5b3.ngrok.io/
<
<html>
<head><title>308 Permanent Redirect</title></head>
<body bgcolor="white">
<center><h1>308 Permanent Redirect</h1></center>
<hr><center>nginx/1.13.12</center>
</body>
</html>
* Connection #0 to host 64c3c5b3.ngrok.io left intact

Then try the same thing but using curl -L so that we follow the redirect.

$ curl -L 64c3c5b3.ngrok.io
Hello, world!
Version: 1.0.0
Hostname: foobar-666c49cb6d-b7hdp

And similarly if you hit the HTTPS endpoint directly.

$ curl https://64c3c5b3.ngrok.io
Hello, world!
Version: 1.0.0
Hostname: foobar-666c49cb6d-b7hdp

Clean up

MiniKube

minikube delete

GKE

gcloud container clusters delete foobar
gcloud projects delete foobar-app  # if you don't want it anymore

Conclusion

There's a bit too much manual work happening here, this is where https://github.com/ksonnet/ksonnet and https://github.com/ksonnet/kubecfg can play a role, or even just a custom / local Helm package.

---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: foobar
labels:
app: foobar
spec:
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app: foobar
template:
metadata:
labels:
app: foobar
spec:
containers:
- name: foobar
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: foobar-service
spec:
selector:
app: foobar
ports:
- name: http
protocol: 'TCP'
port: 80
targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: foobar-ingress
annotations:
certmanager.k8s.io/cluster-issuer: letsencrypt-staging
ingress.kubernetes.io/rewrite-target: /
kubernetes.io/tls-acme: "true"
spec:
tls:
- hosts:
- foo.example.com
secretName: foobar
rules:
- host: foo.example.com
http:
paths:
- path: "/"
backend:
serviceName: foobar-service
servicePort: http
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: foo@example.com
privateKeySecretRef:
name: letsencrypt-staging
http01: {}
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: foo@example.com
privateKeySecretRef:
name: letsencrypt-prod
http01: {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment