Kubernetes Ingress with Cert-Manager
This example demonstrates the use of Istio as a secure Kubernetes Ingress controller with TLS certificates issued by Let’s Encrypt.
You will start with a clean Istio installation, create an example service, expose it using the Kubernetes Ingress
resource and get it secured by instructing cert-manager (bundled with Istio) to manage issuance and renewal of TLS certificates that will be further delivered to the Istio ingress gateway and hot-swapped as necessary via the means of Secrets Discovery Service (SDS).
Before you begin
Install Istio making sure to enable ingress gateway with Kubernetes Ingress support, SDS. Here’s an example of how to do it:
$ istioctl manifest apply \ --set values.gateways.istio-ingressgateway.sds.enabled=true \ --set values.global.k8sIngress.enabled=true \ --set values.global.k8sIngress.enableHttps=true \ --set values.global.k8sIngress.gatewayName=ingressgateway
Install cert-manager to manage certificates automatically.
Configuring DNS name and gateway
Take a note of the external IP address of the istio-ingressgateway
service:
$ kubectl -n istio-system get service istio-ingressgateway
Configure your DNS zone so that the domain you’d like to use for this example is resolving to the external IP address of istio-ingressgateway
service that you’ve captured in the previous step. You will need a real domain name for this example in order to get a TLS certificate issued. Let’s store the configured domain name into an environment variable for further use:
$ INGRESS_DOMAIN=mysubdomain.mydomain.edu
Your Istio installation contains an automatically generated gateway resource configured to serve the routes defined by the Kubernetes Ingress
resources. By default it does not use SDS, so you need to modify it in order to enable the delivery of the TLS certificates to the istio-ingressgateway
via SDS:
$ kubectl -n istio-system edit gateway
…and modify the tls
section corresponding to the https-default
port as follows:
$ kubectl -n istio-system \
patch gateway istio-autogenerated-k8s-ingress --type=json \
-p='[{"op": "replace", "path": "/spec/servers/1/tls", "value": {"credentialName": "ingress-cert", "mode": "SIMPLE", "privateKey": "sds", "serverCertificate": "sds"}}]'
Now it’s time to setup a demo application.
Setting up a demo application
You will be using a simple helloworld
application for this example. The following command will spin up the Deployment
and Service
for the demo application and expose the service using an Ingress
resource that will be handled by istio-ingressgateway
.
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
spec:
ports:
- port: 5000
name: http
selector:
app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld
spec:
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: istio/examples-helloworld-v1
resources:
requests:
cpu: "100m"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: istio
name: helloworld-ingress
spec:
rules:
- host: "$INGRESS_DOMAIN"
http:
paths:
- path: /hello
backend:
serviceName: helloworld
servicePort: 5000
---
EOF
Now you should be able to access your demo application via HTTP:
$ curl http://$INGRESS_DOMAIN/hello
Hello version: v1, instance: helloworld-5d498979b6-jp2mf
HTTPS access still won’t work as you don’t have any TLS certificates. Let’s fix that.
Getting a Let’s Encrypt certificate issued using cert-manager
At this point your Istio installation should have cert-manager up and running with two ClusterIssuer
resources configured (for production and staging ACME-endpoints provided by Let’s Encrypt). You will be using staging endpoint for this example (feel free to try swapping letsencrypt-staging
for letsencrypt
to get a browser-trusted certificate issued).
In order to have a certificate issued and managed by cert-manager you need to create a Certificate
resource:
$ cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: ingress-cert
namespace: istio-system
spec:
secretName: ingress-cert
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
commonName: $INGRESS_DOMAIN
dnsNames:
- $INGRESS_DOMAIN
acme:
config:
- http01:
ingressClass: istio
domains:
- $INGRESS_DOMAIN
---
EOF
Notice that the secretName
matches the credentialName
attribute value that you previously used while configuring the gateway resource. The Certificate
resource will be processed by cert-manager and a new certificate will eventually be issued. Consult the status of the Certificate
resource to check the progress:
$ kubectl -n istio-system describe certificate ingress-cert
-> status should eventually flip to 'Certificate issued successfully'
At this point the service should become available over HTTPS as well:
$ curl --insecure https://$INGRESS_DOMAIN/hello
Hello version: v1, instance: helloworld-5d498979b6-jp2mf
Note that you have to use the --insecure
flag as certificates issued by the “staging” ACME-endpoints aren’t trusted.
Moving to production from staging
Now to switch to the production letsencrypt
issuer. First we’ll reapply the certificate.
$ cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: ingress-cert
namespace: istio-system
spec:
secretName: ingress-cert
issuerRef:
name: letsencrypt
kind: ClusterIssuer
commonName: $INGRESS_DOMAIN
dnsNames:
- $INGRESS_DOMAIN
acme:
config:
- http01:
ingressClass: istio
domains:
- $INGRESS_DOMAIN
---
EOF
certificate.certmanager.k8s.io/ingress-cert configured
Now delete the secret to force cert-manager to request a new certificate from the production issuer:
$ kubectl delete secret -n istio-system ingress-cert
And watch that cert for a successful issuance:
$ watch -n1 kubectl describe cert ingress-cert -n istio-system
you should see something like:
Normal CertIssued 13m cert-manager Certificate issued successfully