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 Gradle. After determining your desired image name, you can build a new image with:
# Run test suite
./gradlew test
# Create local Docker image
./gradlew dockerBuildImage
This will build an image locally with the tag
tazjin/letsencrypt-controller:${VERSION}
, where ${VERSION}
is the
one specified in build.gradle.kts
.
Feel free to contribute pull requests, file bugs and open issues with feature suggestions!
Please follow the code of conduct.