Restarting Kubernetes Pods When There Are New Secrets With Reloader
Table of Contents
I will tell you a secret—no, a story. Say, at some point, I had a Kubernetes webhook admission controller that I wrote and deployed, and then the TLS certificate was automatically (nice!) renewed by cert-manager, but the pod wasn’t restarted, so it still had the old certificate, and now all Kubernetes deployments failed. That is indeed a story, perhaps a sad one. I had this shiny new cert, but no one was using it. Say I wanted to fix that. One way would be with Reloader.
Reloader
Reloader can watch changes in ConfigMap and Secret and do rolling upgrades on Pods with their associated DeploymentConfigs, Deployments, Daemonsets Statefulsets and Rollouts. - Reloader
Install Reloader
First add the repo.
$ helm repo add stakater https://stakater.github.io/stakater-charts
$ helm repo update
Create a namespace.
$ k create ns reloader
namespace/reloader created
$ kn reloader
✔ Active namespace is "reloader"
Install reloader.
$ helm install reloader stakater/reloader
NAME: reloader
LAST DEPLOYED: Thu Nov 23 09:36:22 2023
NAMESPACE: reloader
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
- For a `Deployment` called `foo` have a `ConfigMap` called `foo-configmap`. Then add this annotation to main metadata of your `Deployment`
configmap.reloader.stakater.com/reload: "foo-configmap"
- For a `Deployment` called `foo` have a `Secret` called `foo-secret`. Then add this annotation to main metadata of your `Deployment`
secret.reloader.stakater.com/reload: "foo-secret"
- After successful installation, your pods will get rolling updates when a change in data of configmap or secret will happen.
Now we’ve got pods.
$ k get pods
NAME READY STATUS RESTARTS AGE
reloader-reloader-64df699b8d-tm5rn 1/1 Running 0 3m4s
Nice and easy. Thanks Helm!
Simple Test
Create a secret.
kubectl create secret generic foo-secret --from-literal=key1=bar
Create a cert-manager certificate. (Of course you need cert-manager installed.)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: foo-certs
namespace: foo
spec:
secretName: foo-certs
issuerRef:
name: kubeadm-ca
kind: ClusterIssuer
duration: 24h # Validity period of the certificate
renewBefore: 12h
commonName: foo.foo.svc.cluster.local
dnsNames:
- foo.foo.svc.cluster.local
- foo.foo.svc
EOF
Use that secret in a deployment. Note the annotation for Reloader. We’re mounting the secret in /etc/foo
and certificates /etc/certs
.
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
labels:
app: foo
annotations:
secret.reloader.stakater.com/reload: "foo-secret,foo-certs"
spec:
replicas: 1
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: my-container
image: nginx
volumeMounts:
- name: secret-volume
mountPath: "/etc/foo"
readOnly: true
- name: certs
mountPath: "/etc/certs"
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: foo-secret
- name: certs
secret:
secretName: foo-certs
EOF
Recreate the secret and check the logs of reloader.
$ kubectl create secret generic foo-secret --from-literal=key1=foo --dry-run=client -o yaml | kubectl apply -f -
Warning: resource secrets/foo-secret is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
secret/foo-secret configured
Reloader logs. It has noticed the secret update and restarted the pod.
$ k logs -n reloader reloader-reloader-64df699b8d-tm5rn
time="2023-11-23T14:36:25Z" level=info msg="Environment: Kubernetes"
time="2023-11-23T14:36:25Z" level=info msg="Starting Reloader"
time="2023-11-23T14:36:25Z" level=warning msg="KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces."
time="2023-11-23T14:36:25Z" level=info msg="created controller for: configMaps"
time="2023-11-23T14:36:25Z" level=info msg="Starting Controller to watch resource type: configMaps"
time="2023-11-23T14:36:25Z" level=info msg="created controller for: secrets"
time="2023-11-23T14:36:25Z" level=info msg="Starting Controller to watch resource type: secrets"
time="2023-11-23T15:18:53Z" level=info msg="Changes detected in 'foo-secret' of type 'SECRET' in namespace 'foo', Updated 'foo' of type 'Deployment' in namespace 'foo'"
New pod should be starting.
$ k get pods
NAME READY STATUS RESTARTS AGE
foo-5c67d96557-s6cj2 1/1 Running 0 18s
foo-75cb458f7d-xcszx 1/1 Terminating 0 2m30s
Now it’s got the new secret.
$ k exec -it foo-5c67d96557-s6cj2 -- cat /etc/foo/key1
foo
Boom.
Certificates
Above we crated a certificate with only 24 hours of validity that should renew after 12 hours. So when it’s renewed, there will be a new version of the secret, and reloader will restart the pod. Let’s see.
$ k logs -n reloader reloader-reloader-7f4859f649-6cvqt
time="2023-11-23T16:03:57Z" level=info msg="Environment: Kubernetes"
time="2023-11-23T16:03:57Z" level=info msg="Starting Reloader"
time="2023-11-23T16:03:57Z" level=warning msg="KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces."
time="2023-11-23T16:03:57Z" level=info msg="created controller for: configMaps"
time="2023-11-23T16:03:57Z" level=info msg="Starting Controller to watch resource type: configMaps"
time="2023-11-23T16:03:57Z" level=info msg="created controller for: secrets"
time="2023-11-23T16:03:57Z" level=info msg="Starting Controller to watch resource type: secrets"
time="2023-11-23T16:06:18Z" level=info msg="Changes detected in 'foo-secret' of type 'SECRET' in namespace 'foo', Updated 'foo' of type 'Deployment' in namespace 'foo'"
time="2023-11-24T04:44:56Z" level=info msg="Changes detected in 'foo-certs' of type 'SECRET' in namespace 'foo', Updated 'foo' of type 'Deployment' in namespace 'foo'"
Looking at cert-manager logs we see:
I1124 04:44:56.006536 1 trigger_controller.go:194] "cert-manager/certificates-trigger: Certificate must be re-issued" key="foo/foo-certs" reason="Renewing" message="Renewing certificate as renewal was scheduled at 2023-11-24 04:44:56 +0000 UTC"
SNIP!
I1124 04:44:56.636293 1 conditions.go:263] Setting lastTransitionTime for CertificateRequest "foo-certs-jk5sq" condition "Ready" to 2023-11-24 04:44:56.636261134 +0000 UTC m=+4380108.430326366
Right, so the secret was updated. Let’s see if the pod was restarted.
$ k get pods
NAME READY STATUS RESTARTS AGE
foo-746699dd7-kr99d 1/1 Running 0 6h43m
$ k describe pod foo-746699dd7-kr99d | grep -i started
Started: Thu, 23 Nov 2023 23:44:59 -0500
That time converts to 04:44:59 UTC, which is when the secret was updated. So it was restarted. This is great, so when a new certificate is issued, the pod will be restarted and mount the new secret and have access to the new certificate and key.
There’s a reloader annotation as well.
$ k get pods -oyaml | grep reloader
reloader.stakater.com/last-reloaded-from: '{"type":"SECRET","name":"foo-certs","namespace":"foo","hash":"94af434fda756e922affdd1c43d723b26f196f3e","containerRefs":["my-container"],"observedAt":1700801096}'
Conclusion
Personally, I would think that this kind of thing would be automatic, but it’s not. So this is a good way to make sure that your pods are restarted when there are new secrets.
Kubernetes is a framework, and you have to pull in a lot of “libraries,” such as Reloader.