diff --git a/docs/book/Makefile b/docs/book/Makefile index 828764467..a23018360 100644 --- a/docs/book/Makefile +++ b/docs/book/Makefile @@ -24,7 +24,12 @@ MDBOOK_TOC := $(TOOLS_BIN_DIR)/mdbook-toc $(MDBOOK_TOC): $(CRATE_INSTALL) --git badboy/mdbook-toc --tag 0.7.0 --to $(TOOLS_BIN_DIR) --force -DEPS := $(MDBOOK) $(MDBOOK_TOC) +MDBOOK_MERMAID := $(TOOLS_BIN_DIR)/mdbook-mermaid +$(MDBOOK_MERMAID): + $(CRATE_INSTALL) --git badboy/mdbook-mermaid --tag v0.8.3 --to $(TOOLS_BIN_DIR) --force + $(MDBOOK_MERMAID) install + +DEPS := $(MDBOOK) $(MDBOOK_TOC) $(MDBOOK_MERMAID) .PHONY: build build: $(DEPS) diff --git a/docs/book/book.toml b/docs/book/book.toml index 9415d1bc0..748aa11e7 100644 --- a/docs/book/book.toml +++ b/docs/book/book.toml @@ -8,6 +8,10 @@ title = "Secrets Store CSI Driver" [output.html] curly-quotes = true git-repository-url = "https://sigs.k8s.io/secrets-store-csi-driver" +additional-js = ["mermaid.min.js", "mermaid-init.js"] [preprocessor.toc] command = "bin/mdbook-toc" + +[preprocessor.mermaid] +command = "bin/mdbook-mermaid" \ No newline at end of file diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 68073797e..7cd36b2d3 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -13,6 +13,7 @@ - [Sync as Kubernetes Secret](./topics/sync-as-kubernetes-secret.md) - [Set as ENV var](./topics/set-as-env-var.md) - [Best Practices](./topics/best-practices.md) + - [Auto Restart on Rotation](./topics/auto-restart-on-rotation.md) - [Providers](./providers.md) - [Troubleshooting](./troubleshooting.md) - [Load tests](./load-tests.md) diff --git a/docs/book/src/topics/auto-restart-on-rotation.md b/docs/book/src/topics/auto-restart-on-rotation.md new file mode 100644 index 000000000..c20f2d5da --- /dev/null +++ b/docs/book/src/topics/auto-restart-on-rotation.md @@ -0,0 +1,122 @@ +# Auto restart of pods when secret is rotated + +> NOTE: This is a suggested implementation that can be used alongside the Secrets Store CSI Driver. This solution is neither supported nor tested by the Secrets Store CSI Driver project. + +When [auto rotation of secrets](./secret-auto-rotation.md) is enabled, workloads that depend on secrets will need to either: +- watch for updates to the secrets and reload these in their runtime, or +- be restarted to pick up the latest secrets when they change + +A solution like [Reloader](https://github.com/stakater/Reloader) can watch updates to Kubernetes Secrets or ConfigMaps and restart pods when a change is detected. However, if secret values are mounted as volumes in the pods, that solution is not suitable. + +Using custom resources created by the Secrets Store CSI Driver, a Kubernetes controller can detect when secrets are updated by the driver and restart the associated pods. + +> NOTE: The suggested implementation will result in an increase in secret store reads and secret writes (k8s mounts) by the Secrets Store CSI Driver. Each time the driver updates a mounted secret and the controller subsequently restarts the associated pod, the driver will then read and mount the secret _again_ for the newly created pod. This undesirable consequence ahould be weighed against the convenience of enabling workloads to be reloaded with updated secrets, without code changes. + +## SecretProviderClassPodStatus custom resource + +The relevant custom resource is [`SecretProviderClassPodStatus`](../concepts#secretproviderclasspodstatus). + +Each `SecretProviderClassPodStatus` custom resource (CR) has a one-to-one relationship with a pod that references secrets using a Secrets Store CSI Driver [SecretProviderClass](../concepts#secretproviderclass). The CR includes the pod name, namespace and other attributes. The driver manages the lifecycle of `SecretProviderClassPodStatus` which is tied to the lifecycle of the associated pod. + +```mermaid +stateDiagram-v2 + state "SecretProviderClassPodStatus\nGeneration: 1" as g1 + state "SecretProviderClassPodStatus\nGeneration: n" as gn + + [*] --> g1: pod create with secret from csi + g1 --> gn: secret updated in pod + gn --> [*]: pod restart +``` + +When the driver sets a secret value for a new pod, a `SecretProviderClassPodStatus` CR is created with the `Generation` attribute set to `1`. + +Whenever the driver updates the secret value, the value of the `Generation` attribute is incremented. + +If a pod is restarted, the CR is deleted and a new CR created with `Generation: 1`. + +`SecretProviderClassPodStatus` CRs persist across lifetimes of the secrets-store-csi-driver. + +## Outline of Controller function + +1. Reconcile + + The controller reconciles instances of the `SecretProviderClassPodStatus` CR and deletes (to restart) the associated pod if required. + + If a `SecretProviderClassPodStatus` has `Generation: 1`, it is linked to a newly created pod. The pod should not be restarted. + + If a `SecretProviderClassPodStatus` has `Generation` > 1, it is linked to a pod in which the secrets-store-csi-driver has updated a secret. The pod should be restarted (if it has opted-in for automatic restarting). + +1. Rolling restart + + On reconciling a pod which should be updated, check `metadata.ownerReferences` and walk up to a Deployment (or similar) if present. + + If a `Deployment` is found: + + - Do not restart pod + - Update the Deployment to trigger a rolling restart + - If the number of replicas > 1, update the Deployment once only + + To restart a deployment, the controller sets a timestamped annotation in the deployment + + ``` + template: + metadata: + annotations: + my.controller/restartedAt: "2024-09-05T14:06:29Z" + ``` + + Else: delete pod. + +1. Opt-in to automatic pod restarting + + Automatic restarting of pods when secrets are updated could be an opt-in behaviour. Unless the pod declares its opt-in, it should not be restarted by the controller. + + The opt-in could be indicated via an optional annotation set on the pod: + ``` + kind: pod + metadata: + annotations: + my.controller/restartOnChange: true + ``` + +## Implementation notes + +The [operator-sdk](https://github.com/operator-framework/operator-sdk) can be used to scaffold an implementation project. + +1. Scaffolding the project + + ``` + operator-sdk init --repo= + operator-sdk create api --version v1alpha1 --kind SecretProviderClassPodStatus --resource=false --controller=true + ``` + +1. Custom resources + + The controller does not manage custom resources of its own. It simply watches a custom resource provided by the Secrets Store CSI Driver. + +1. Permissions required + + The controller requires RBAC permissions to operate on various k8s resources. + + To watch `SecretProviderClassPodStatus` + ``` + // +kubebuilder:rbac:groups=secrets-store.csi.x-k8s.io,resources=secretproviderclasspodstatuses,verbs=get;list;watch + ``` + + To lookup and if necessary delete `Pod` + ``` + // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;delete + ``` + + To lookup possible owner of `Pod` + ``` + // +kubebuilder:rbac:groups="apps",resources=daemonsets,verbs=get;list;watch + // +kubebuilder:rbac:groups="apps",resources=replicasets,verbs=get;list;watch + // +kubebuilder:rbac:groups="apps",resources=statefulsets,verbs=get;list;watch + // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch + ``` + + To lookup and if necessary trigger update of `Deployment` + ``` + // +kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;update + ```