This implements a Kubernetes controller that automatically requests and refreshes Letsencrypt certificates based on service annotations.
This controller currently supports Amazon Route 53 and Google Cloud DNS as the DNS targets.
Launch the controller into your cluster using
kubectl apply -f letsencrypt-controller.yaml
This will use a release or snapshot version (depending on your git checkout) hosted on my Docker Hub account.
The pod must run with the permissions required for updating records in the DNS zones that you maintain.
On AWS, consider using a project such as kube2iam to grant permissions to individual pods.
Please refer to the 'Building' section for using your own image.
The controller currently supports three configuration options via environment variables:
ACME_URL
: This can be set to an alternative ACME directory URL, for example the Letsencrypt staging server if you only want to test out the controller.CLOUD_PLATFORM
: This can be set to eitherGCP
orAWS
to override the automatic platform detection. You can use this to for example use Route53 as the DNS backend with a cluster running on Google's Cloud Platform. If you override this option you must provide credentials for the DNS backend, for example via the environment variables for the Google Cloud Java SDK or the AWS Java SDKLOG_LEVEL
: This can be used to set the log level to something other than the default (INFO
).
Simply add an annotation to your services, for example:
---
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
app: my-app
annotations:
acme/certificate: www.yourdomain.com
spec:
type: LoadBalancer
[...]
The controller will notice this and, assuming you have a matching hosted zone, create a certificate
and store it as a secret named www-yourdomain-com-tls
.
You can override the name of the secret by specifying an annotation called acme/secretName
.
You may specify multiple domains to include in a certificate as a JSON array. This requires
setting the acme/secretName
annotation. For example:
[...]
metadata:
annotations:
acme/certificate: '["yourdomain.com", "www.yourdomain.com"]'
acme/secretName: mydomain-certificate
[...]
The certificate secret will contain four files named certificate.pem
, chain.pem
, key.pem
and
fullchain.pem
.
You can mount these into whatever application you use to terminate TLS.
If required, you can configure these file names via the environment variables,
CERTIFICATE_FILENAME
, CHAIN_FILENAME
, KEY_FILENAME
, FULLCHAIN_FILENAME
.
The secret will always be created in the same namespace as your service. Removing the annotation will never remove a secret.
Every secret will be annotated with the certificate expiry date. The controller will refresh the certificate and update the secret once the expiry date is close.
Currently this update happens within 1-2 days of expiry. The reason for the short time-interval is that Letsencrypt has a long-term desire to reduce the certificate lifespans so I am trying to be future-proof here.
The controller first attempts to find a secret in the Kubernetes kube-system
namespace with the
name letsencrypt-keypair
. This secret is expected to contain the key pair used for authentication
with the Letsencrypt service.
If no such key pair is found the controller will create one and store it as a secret.
On startup the controller will check all existing services for an annotation
All build lifecycle steps are handled in Maven. After determining your desired image name, you can build and push a new image with:
# Builds, tests and creates shaded JAR
mvn package
# Creates and pushes Docker image
mvn -Ddocker.imageName='your/image-name' docker:build docker:push