Contour Ingress Controller
Contour is an open source Kubernetes ingress controller providing the control plane for the Envoy edge and service proxy. Contour supports dynamic configuration updates and multi-team ingress delegation out of the box while maintaining a lightweight profile.
Contour was recently (July 2020) added as a CNCF incubator project.
Installation
Installation was literally as simple as applying a single manifest directly from the Contour site:
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
The supplied installation guide was very helpful, and provided some 'toy' examples to help understand the basic concepts. One of the more useful documents on their website, however, was the guide to connecting Contour with cert-manager to provide automatic Let's Encrypt certificate management for ingresses. Cert-manager is a package of its own with multiple ways to install it -- I'm inclined to avoid helm packages at this time because of my bad experiences with them on the MediaWiki installation (a story for another day), so a simple kubernetes deployment was available and worked well.
The installation of cert-manager was also very straightforward, but the Contour guide was a few minor versions off and caused some consternation until I went directly to the cert-manager GitHub site and read their directions. in the end, all I really had to do was use a new manifest:
kubectl create namespace cert-manager kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.15.1/cert-manager.yaml
The fact that they require you to turn off YAML validation bothers me ... but it did seem to work properly.
At this point you have the core functionality present and ready for use ... but each package requires some CRDs to be created in order to use the functionality.
cert-manager ClusterIssuer
The CRD required for cert-manager is the definition of where and how to get the certificates. Using the ClusterIssuer CRD enables a single source for certificates across all namespaces; if there is a need for special certificate process for individual namespaces, the Issuer CRD can be used.
Let's Encrypt has placed rate limits on issuing certificates for a given CN to prevent abuse; but the also recognize that you need to test your installation, which can end up requesting lots of certificates. They have set up a staging request server that doesn't implement rate limits, but also doesn't issue 'valid' certificates -- so you can get your process down, then switch over to their production server when you're ready to go live. The guide has you create both a staging and production ClusterIssuer for this purpose, saved as 'letsencrypt-staging.yaml' and 'letsencrypt-prod.yaml' in the repository. The only thing you need to modify in these manifests is the email address -- as that is the key that the let's encrypt servers use to identify certificate ownership. These CRDs are installed by applying their manifests:
kubectl apply -f letsencrypt-staging.yaml -f letsencrypt-prod.yaml
Creating Ingresses to publish access to Services
The whole point of a kubernetes ingress (and the newer HTTPProxy that is part of the Contour package) is to enable access to kubernetes services outside of the cluster. The Service can be of any type.
All that needs to be done is to create an Ingress in the same namespace as the Service, referencing the service by name and matching the port as specified in the Service. A simple example Service:
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: httpbin
... and the corresponding Ingress:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: httpbin
spec:
rules:
- host: httpbin.davecheney.com
http:
paths:
- backend:
serviceName: httpbin
servicePort: 8080
Note that the serviceName and servicePort match those values in the Service specification. The 'spec.rules.host' parameter is the hostname that will trigger this ingress when a HTTP/HTTPS request arrives. This definition of an ingress will support unencrypted HTTP.
Creatng Certificates
To create and use a certificate for an Ingress, we have to make two changes to the Ingress"
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: httpbin
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
ingress.kubernetes.io/force-ssl-redirect: "true"
kubernetes.io/ingress.class: contour
kubernetes.io/tls-acme: "true"
spec:
tls:
- secretName: httpbin
hosts:
- httpbin.davecheney.com
rules:
- host: httpbin.davecheney.com
http:
paths:
- backend:
serviceName: httpbin
servicePort: 8080
The two changes are in the 'annotations' and in the 'spec.tls' sections. The 'annotations' give Contour the instructions to:
- get the certificate from lets-encrypt (staging or prod)
- tell the ingress to force all connections to this ingress to be HTTPS (redirecting HTTP as needed)
- identify Contour as the ingress controller
- Use the 'acme' source for getting let's encrypt certificates
The spec.tls parameters specify how and where the certificate is created and stored:
- kubernetes secrets in the current namespace (not the cert-manager namespace) are used to store certificates ... the name of the secret can be whatever is needed to identify the certificate
- the hostnames provided are used to create the certificate
The staging ClusterIssuer can be used initially, but make sure the production ClusterIssuer is specified in the final manifests when deploying. Create the Ingress by applying the manifest:
kubectl apply -f <ingress manifest>.yaml
As soon as the ingress is created, cert-manager will see the request and obtain the certificate as specified in the spec.tls section. It creates a new Certificate CRD to hold all the details needed to support and renew the certificates. To verify the certificate is created successfully, look at the certificate CRD (which is named the same as the secret):
kubectl describe certificate <secret name> | tail -n 12
You will see the messages from the exchange with the certificate servers -- and within a few seconds, should see that the certificate was issued. At that point, accesses to the service should be forced to HTTPS and show a valid certificate issued by Let's Encrypt.