I was happy with Helm when a far-more-experienced-Kubernetes-guy told me I should not use Helm because Tiller is unsafe and some other reasons. Now I follow the method of Tobias Bradtke, with the advantage of declarative application management; while I keep one cluster-definition in one Git repository.
update: The most apparent change of Helm 3 is the removal of Tiller, which makes the templating as shown here less relevant
When you want to route HTTP and HTTPS traffic from outside your Kubernetes cluster to services inside your cluster, this can be done with Nginx-Ingress. Let’s Encrypt is a non-profit certificate authority run by Internet Security Research Group to provide free TLS certificates and cert-manager is a Kubernetes addon to automate the management and issuance of TLS certificates from various issuing sources.
First we will fetch all the resources into a Git repository; then we will render the manifests and apply them to our cluster. This Github repository contains an exampe of the files you’ll end up with.
Before you continue, make sure you have:
- access to a Kubernetes cluster (v1.6+) with kubectl;
- a domain or subdomain with DNS records ready to point to an IP;
- initialized Helm locally (not on the cluster) with
helm init --client-only
. Do not forget to update Helm (helm update repo
).
Fetching cert-manager and nginx-ingress
Make the following nginx-ingress/apply.sh
file:
#!/bin/bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
mkdir -p $SCRIPTPATH/values
# FETCH
echo 'FETCH CHARTS'
## CERT-MANAGER
curl https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml -s --create-dirs -o $SCRIPTPATH/00-crds.yaml # cert-manager CRDs
helm fetch \
--repo https://charts.jetstack.io \
--version v0.10.0 \
--untar \
--untardir $SCRIPTPATH/charts \
cert-manager
FILE=$SCRIPTPATH/values/cert-manager.yaml
if ! test -f "$FILE"; then
cp -p $SCRIPTPATH/charts/cert-manager/values.yaml $FILE
fi
## NGINX-INGRESS
helm fetch \
--version 1.19.1 \
--untar \
--untardir $SCRIPTPATH/charts \
stable/nginx-ingress
FILE=$SCRIPTPATH/values/nginx-ingress.yaml
if ! test -f "$FILE"; then
cp -p $SCRIPTPATH/charts/nginx-ingress/values.yaml $FILE
fi
The file fetches the cert-manager and the Nginx-ingress Helm charts, makes copies of the value files and gets the required cert-manager CRDs yaml file. Note that Let’s Encrypt announced to block all traffic from cert-manager versions less than 0.8.0 as of November 1, 2019.
Now fetch the resources:
./nginx-ingress/fetch.sh
The value files are copied to the nginx-ingress/values
folder, but they don’t need any adjustments for now.
Create a namespace definition file
The following nginx-ingress/nginx-ingress-ns.yaml
simply defines the namespace:
apiVersion: v1
kind: Namespace
metadata:
name: nginx-ingress
Create a ClusterIssuer
Make a nginx-ingress/cluster-issuers
folder and include the following definition of a nginx-ingress/cluster-issuers/letsencrypt-prod.yaml
file:
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
labels:
name: letsencrypt-prod
name: letsencrypt-prod
spec:
acme:
email: EMAIL-ADDRESS
http01: {}
privateKeySecretRef:
name: letsencrypt-prod
server: https://acme-v02.api.letsencrypt.org/directory
Don’t forget to replace EMAIL-ADRESS with your email address. You can read more about setting up ClusterIssuers here.
Render and apply the manifests to the cluster
Create the following nginx-ingress/apply.sh
file:
#!/bin/bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
rm -rf $SCRIPTPATH/manifests
mkdir -p $SCRIPTPATH/manifests
# RENDER
echo 'RENDER MANIFESTS'
## CERT-MANAGER
cp -r $SCRIPTPATH/charts/00-crds.yaml $SCRIPTPATH/manifests/ # cert-manager CRDs
helm template \
--name cert-manager \
--namespace nginx-ingress \
--values $SCRIPTPATH/values/cert-manager.yaml \
--output-dir $SCRIPTPATH/manifests \
$SCRIPTPATH/charts/cert-manager
## NGINX-INGRESS
helm template \
--name nginx-ingress \
--namespace nginx-ingress \
--values $SCRIPTPATH/values/nginx-ingress.yaml \
--output-dir $SCRIPTPATH/manifests \
$SCRIPTPATH/charts/nginx-ingress
# APPLY
echo 'APPLY MANIFESTS'
## NAMESPACE
kubectl apply -f $SCRIPTPATH/nginx-ingress-ns.yaml
## CERT-MANAGER
kubectl label namespace nginx-ingress --overwrite certmanager.k8s.io/disable-validation=true
kubectl apply -f $SCRIPTPATH/00-crds.yaml # cert-manager CRDs
kubectl apply -R -f $SCRIPTPATH/manifests/cert-manager
## CLUSTER ISSUERS
kubectl apply -n nginx-ingress -R -f $SCRIPTPATH/cluster-issuers
## NGINX-INGRESS
kubectl apply -n nginx-ingress -R -f $SCRIPTPATH/manifests/nginx-ingress
echo ''
echo ''
echo 'Now watch the status and the external IP by running:'
echo ' kubectl --namespace nginx-ingress get services -o wide -w nginx-ingress-controller'
The file will first render all the manifests, then applies the namespace and both the manifests to the cluster:
./nginx-ingress/apply.sh
Wait for all the pods to spin up; you can check this with:
$ kubectl get po -n nginx-ingress
NAME READY STATUS RESTARTS AGE
cert-manager-5cb87887d8-sbtgk 1/1 Running 0 47s
cert-manager-cainjector-5d448d76df-6wm6f 1/1 Running 0 52s
cert-manager-webhook-6f75cfcbc8-9h9zf 1/1 Running 1 45s
nginx-ingress-controller-94bd8fd99-mdhqc 1/1 Running 0 43s
nginx-ingress-default-backend-576b86996d-5lc8j 1/1 Running 0 41s
When you received an Internal error occured: failed calling webhook… you need to wait for the cert-manager-webhook to spin-up before re-applying the ClusterIssuers:
kubectl apply -n nginx-ingress -R -f nginx-ingress/cluster-issuers
All the objects are created; including a LoadBalancer (depending on your Kubernetes provider). Wait for the Load Balance to be created and then set your DNS records at this external IP.
$ kubectl --namespace nginx-ingress get services -o wide -w nginx-ingress-controller
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-ingress-controller LoadBalancer 10.245.89.89 <pending> 80:32104/TCP,443:31353/TCP 12s app=nginx-ingress,component=controller,release=nginx-ingress
nginx-ingress-controller LoadBalancer 10.245.89.89 206.189.241.190 80:32104/TCP,443:31353/TCP 2m29s app=nginx-ingress,component=controller,release=nginx-ingress
Example deployment
We are done setting up cert-manager and nginx-ingress! We can test the setup with the following example-deployment.yaml
:
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: example
namespace: dev
spec:
selector:
matchLabels:
app: example
replicas: 2
template:
metadata:
labels:
app: example
spec:
containers:
- name: nginx
image: stenote/nginx-hostname:latest
---
apiVersion: v1
kind: Service
metadata:
name: example
namespace: dev
spec:
selector:
app: example
ports:
- protocol: TCP
port: 80
name: http
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-prod-ingress
namespace: dev
annotations:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- secretName: letsencrypt-prod
hosts:
- test.example.com
rules:
- host: test.example.com
http:
paths:
- path: /
backend:
serviceName: example
servicePort: 80
Replace the example.com domain with your own; where the DNS is set to the ExternalIp of your LoadBalancer; and apply the deployment file to your cluster:
$ kubectl apply -f example-deployment.yaml
namespace/dev created
deployment.apps/example created
service/example created
ingress.extensions/example-prod-ingress created
You can check the status of the certificate:
$ kubectl get certificates -n dev
NAMESPACE NAME READY SECRET AGE
dev letsencrypt-prod True letsencrypt-prod 65s
Navigate to test.YOURDOMAIN.COM and you should have successfully configured HTTPS using a Let’s Encrypt certificate for your Nginx Ingress.