{"id":31558059,"url":"https://github.com/spoditor/spoditor","last_synced_at":"2025-10-05T00:13:46.293Z","repository":{"id":44686391,"uuid":"348109922","full_name":"spoditor/spoditor","owner":"spoditor","description":"A dynamical admission control for Kubernetes to differentiate Pod belonging to a StatefulSet","archived":false,"fork":false,"pushed_at":"2022-01-31T12:03:00.000Z","size":93,"stargazers_count":8,"open_issues_count":1,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-06-19T06:55:59.919Z","etag":null,"topics":["kubernetes","mutatingadmissionwebhook","operator","pod","statefulset"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spoditor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-15T20:15:14.000Z","updated_at":"2024-05-04T16:27:16.000Z","dependencies_parsed_at":"2022-09-05T08:41:51.742Z","dependency_job_id":null,"html_url":"https://github.com/spoditor/spoditor","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/spoditor/spoditor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spoditor%2Fspoditor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spoditor%2Fspoditor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spoditor%2Fspoditor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spoditor%2Fspoditor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spoditor","download_url":"https://codeload.github.com/spoditor/spoditor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spoditor%2Fspoditor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278391592,"owners_count":25979031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kubernetes","mutatingadmissionwebhook","operator","pod","statefulset"],"created_at":"2025-10-05T00:13:45.083Z","updated_at":"2025-10-05T00:13:46.288Z","avatar_url":"https://github.com/spoditor.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![sts.png](sts.png)\n\n# Spoditor\nSpoditor, (StatefulSet Pod Editor), is a Kubernetes dynamical admission controller to differentiate each individual Pod belonging to a StatefulSet.\n\n[StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) is designated to manage stateful workload on Kubernetes. However, developer often has to face the limit that all the Pods in a StatefulSet have to share the same PodSpec. A lot of stateful workload cluster actually requires slightly different specification, such as configuration, storage, etc, on one or more Pods.\n\nSpoditor helps to lift this limitation of StatefulSet, by allowing developer to annotate the PodSpec template of a StatefulSet and apply extra configuration to Pod of different ordinal.\n\n## Quick Example\n```yaml\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: web\nspec:\n  selector:\n    matchLabels:\n      app: nginx # has to match .spec.template.metadata.labels\n  serviceName: \"nginx\"\n  replicas: 3 # by default is 1\n  template:\n    metadata:\n      labels:\n        app: nginx # has to match .spec.selector.matchLabels\n      annotations:\n        spoditor.io/mount-volume: |\n          {\n            \"volumes\": [\n              {\n                \"name\": \"my-volume\",\n                \"secret\": {\n                  \"secretName\": \"my-secret\"\n                }\n              }\n            ],\n            \"containers\": [\n              {\n                \"name\": \"nginx\",\n                \"volumeMounts\": [\n                  {\n                    \"name\": \"my-volume\",\n                    \"mountPath\": \"/etc/secrets/my-volume\"\n                  }\n                ]\n              }\n            ]\n          }\n    spec:\n      terminationGracePeriodSeconds: 10\n      containers:\n      - name: nginx\n        image: k8s.gcr.io/nginx-slim:0.8\n        ports:\n        - containerPort: 80\n          name: web\n        volumeMounts:\n        - name: www\n          mountPath: /usr/share/nginx/html\n  volumeClaimTemplates:\n  - metadata:\n      name: www\n    spec:\n      accessModes: [ \"ReadWriteOnce\" ]\n      resources:\n        requests:\n          storage: 1Gi\n```\nThe annotation `spoditor.io/mount-volume` above will mount secret `my-secret-0` to container `nginx` in Pod `web-0`, secret `my-secret-1` to Pod `web-1`, secret `my-secret-2` to Pod `web-2`, so on and so forth.\n\nThis annotation takes a JSON object as its value, the schema of `volumes` and `containers` fields are the same as corresponding fields in [PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec), though only subset of the schemas are meaningful to this annotation, irrelevant fields are just simply ignored.\n\n**Important**\n\nObviously, Spoditor does not create the _expanded_ secret, configmap, etc resources for each Pod. It is the client's responsibility to provide them. If an _expanded_ resource does not exist, the corresponding Pod will be pending for container creation.\n\n## Annotation Qualifier\n\nThe example above illustrates mounting dedicated secret (potentially, other mountable resources) for each Pod matching the same ordinal.\n\nHowever, in other scenario, developer may only want to argument a subset of Pods in a StatefulSet, for example, Pod 0 being the master node of a stateful workload cluster. Spoditor supports **qualifier** suffix for this purpose.\n\n`spoditor.io/mount-volume[_{lower}-{upper}]`\n\n| With qualifier suffix  | Applicable Pod ordinal |\n| ------------- | ------------- |\n| spoditor.io/mount-volume_0 | Only Pod 0  |\n| spoditor.io/mount-volume_5-  | All Pod with ordinal \u003e= 5 |\n| spoditor.io/mount-volume_-5  | All Pod with ordinal \u003c= 5 |\n| spoditor.io/mount-volume_2-5  | All Pod with ordinal \u003e= 2 AND \u003c= 5 |\n\nMultiple annotations with different qualifier suffix can be applied to the same StatefulSet. For example, we can use both `spoditor.io/mount-volume_0` and `spoditor.io/mount-volume_1-` to give Pod 0 a dedicated configuration while making all the other Pods share a same configuration.\n\n## Editing Existing StatefulSet\n\nSpoditor chooses to use annotations under the `.spec.template.metadata.annotations` field of a StatefulSet. This allows the reconciliation loop of the StatefulSet controller to kick in upon any update to any annotation, which means developer can argument running StatefulSet, and the underlying Pods will be recreated with dedicated configuration applied by Spoditor.\n\n## Supported Annotations\n### mount-volume\nThis annotation allows mounting different `secret` or `configmap` as volume to different Pods. _Other volume source will be supported soon._\n\nThe JSON schema of its value\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"volumes\": {\n      \"type\": \"array\",\n      \"items\":{\n        \"description\": \"refer to https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/volume/#Volume\",\n        \"type\": \"object\"\n      }\n    },\n    \"containers\": {\n      \"type\": \"array\",\n      \"items\":{\n        \"description\": \"refer to https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container\",\n        \"type\": \"object\"\n      }\n    }\n  }\n}\n```\n\n## Installation\n\n### Prerequisites\nSpoditor depends on [Cert-Manager](https://cert-manager.io) to issue TLS certificates. So, please follow its [installation guide](https://cert-manager.io/docs/installation/kubernetes/) to install it to your Kubernetes cluster.\n\n### Install Spoditor\nEach Spoditor release offers an all-in-one YAML manifest `bundle.yaml`\n\n```shell\nkubectl apply -f https://github.com/spoditor/spoditor/releases/download/v0.1.1/bundle.yaml\n```\n\n## Quick Demo\n[![asciicast](https://asciinema.org/a/xmA2TISTPQoMcXryyFnRiRxbI.svg)](https://asciinema.org/a/xmA2TISTPQoMcXryyFnRiRxbI)\n\n## Contributing\nWe welcome pull request to support more ways Spoditor can argument the Pods of Statefulset.\n\nPlease refer to the [mount-volume](internal/annotation/volumes/mount.go) implementation to understand how to implement new annotation. Basically, all an annotation needs to do is to implement the following interfaces:\n```go\ntype Handler interface {\n\tMutate(spec *corev1.PodSpec, ordinal int, cfg interface{}) error\n\tGetParser() Parser\n}\n\ntype Parser interface {\n\tParse(annotations map[QualifiedName]string) (interface{}, error)\n}\n```\n\n## Community\nPlease join [Spoditor](https://join.slack.com/t/spoditor/shared_invite/zt-p6anaij6-07DsggYHlnEktixBWIURMA) on Slack","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspoditor%2Fspoditor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspoditor%2Fspoditor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspoditor%2Fspoditor/lists"}