Syncing secrets and configmaps in Kubernetes with Reflector
Reflector is a nice and small, easy-to-use K8s tool to sync secrets and configmaps between namespaces inside a K8s cluster - a feature which is quite useful if you dont want to - or simply cannot rely on externally managed secrets. It does not introduce new CRDs, but works solely by watching resource annotations on existing secrets and configmaps.
#Kubernetes #K8s #devops
Author: Thomas HikadeEmberstack's Reflector
Pros
- A lightweight custom controller installed via a nicely configurable Helm chart.
- No CRDs, only resource annotations on secrets & configmaps required.
- push-sync: annotate a source-secret and automatically push it - and subsequent updates - into target namespace(s).
- pull-sync: create empty destination secrets and annotate them to sync from a central master secret
- Master secret can define list of namespaces by regex or enumeration. Only these are allowed to sync from master secret.
Cons
- You need to be able to annotate your secrets and configmaps. In case these are managed by some other controller or operator, annotations on secrets/configmaps will most probably get removed by that operator.
- proven to work for KafkaUsers and Keycloak user resources
- Supports Secrets and Configmaps only
Docs
Installation
Install via Helm chart
helm repo add emberstack https://emberstack.github.io/helm-charts
helm repo update
helm repo list
helm search repo emberstack/reflector [-l]
helm upgrade --install reflector emberstack/reflector -n reflector
# --------------------------------------------------------------
# on Openshift 4.x, delete the default Helmchart values for the securityContext
# --------------------------------------------------------------
helm upgrade --install reflector emberstack/reflector -n reflector \
--set securityContext.runAsUser=null \
--set podSecurityContext.fsGroup=null
Usecases and examples
Note: all testcases were evaluated using reflector v7.1.238 on Openshift 4.12.
I. Manual reflection (pull-sync)
II. Auto-reflection (push-sync)
I. Manual reflection (pull-sync)
Annotations used:
reflection-allowed="true"
reflection-allowed-namespaces="slave-.*"
-
Create the master secret in namespace
master
:$ oc new-project master $ oc create secret generic foo-secret --from-literal=foo=bar --dry-run=client -o yaml | \ oc annotate -f - --local --dry-run=client -o yaml \ reflector.v1.k8s.emberstack.com/reflection-allowed="true" \ reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces="slave-.*" \ > fooSecret.yaml
# cat fooSecret.yaml apiVersion: v1 kind: Secret metadata: annotations: reflector.v1.k8s.emberstack.com/reflection-allowed: "true" reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "slave-.*" name: foo-secret namespace: master data: foo: YmFy
$ oc -n master apply -f fooSecret.yaml
-
Create the reflected secret without data in target namespace
slave-01
:$ oc new-project slave-01 $ oc create secret generic slave-foo-secret --dry-run=client -o yaml | \ oc annotate -f - --local --dry-run=client -o yaml \ my-custom-annotation="foobar" \ reflector.v1.k8s.emberstack.com/reflects="master/foo-secret" \ > slave-fooSecret.yaml
# cat slave-fooSecret.yaml # NOTICE THE MISSING .data SECTION!! apiVersion: v1 kind: Secret metadata: annotations: my-custom-annotation: foobar reflector.v1.k8s.emberstack.com/reflects: master/foo-secret name: slave-foo-secret
$ oc -n slave-01 apply -f slave-fooSecret.yaml
-
Validate slave-secret data reflection:
# verify that slave secret data was indeed synced! $ oc -n slave-01 get secret slave-foo-secret -o yaml | yq -e .data foo: YmFy
II. Auto-reflection (push-sync)
uses 2 additional annotations to auto-magically copy the entire source-secret to all namespaces in reflection-auto-namespaces
annotation:
reflection-auto-enabled: "true"
reflection-auto-namespaces: slave-02
In this setup, a new target secret will be automatically created in - or pushed out to - namespace slave-02
!
-
Create another source secret in namespace
master
:$ oc project master $ oc create secret generic foo-push-secret --from-literal=foo2=bar2 --dry-run=client -o yaml | \ oc annotate -f - --local --dry-run=client -o yaml \ reflector.v1.k8s.emberstack.com/reflection-allowed="true" \ reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces="slave-.*" \ reflector.v1.k8s.emberstack.com/reflection-auto-enabled="true" \ reflector.v1.k8s.emberstack.com/reflection-auto-namespaces="slave-02" \ > foo-push-secret.yaml
# cat foo-push-secret apiVersion: v1 kind: Secret metadata: annotations: reflector.v1.k8s.emberstack.com/reflection-allowed: "true" reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "slave-.*" reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "slave-02" name: foo-push-secret namespace: master data: foo2: YmFyMg==
$ oc -n master apply -f foo-push-secret.yaml
-
Create namespace
slave-02
and do not create any target secret!
Be sure to tail reflector pod logs to verify what happends immediately after you create the new namespace!$ oc -n reflector logs deploy/reflector -f & $ oc new-project slave-02 ... 2024-01-29 15:55:52.112 +00:00 [INF] (SecretMirror) Created slave-02/foo-push-secret as a reflection of master/foo-push-secret
-
Validate auto-reflection of secret
# verify that a new secret `foo-push-secret` was created! oc -n slave-02 get secret foo-push-secret -o yaml | yq -e .data foo2: YmFyMg==
Troubleshooting
- If things do not sync as expected, have a look the logs of the reflector controller pod. You might see hints like these to find the root-cause:
$ oc -n reflector logs deploy/reflector 2024-01-29 14:12:44.429 +00:00 [INF] () Starting host 2024-01-29 15:18:08.001 +00:00 [WRN] (ES.Kubernetes.Reflector.Core.SecretMirror) Could not update slave-01/slave-foo-secret - Source master/foo-secret does not permit it.
- In pull-sync mode, if you had a typo in the
reflection-allowed-namespaces
annotation value, reflector wont try to update the target secret again, even if you fix the typo in the master secret. You will have to recreate the empty target secret to trigger a sync.
Summary
Reflector has got a quite specific and narrow use-case - but that it does pretty well!
You no longer need to (ab-)use Helm lookup
function to copy secrets - which won't work if you use Helm via ArgoCD anyway!
So if you want to keep a central secret / configmap namespace and replicate those resources inside the cluster, give reflector a try!