Repository navigation
Add opt-in selector labels to adopt existing workloads - #1007
Merged
Merged
Conversation
2 of 3 tasks
marcleblanc2
marked this pull request as ready for review
November 11, 2026 11:59
marcleblanc2
force-pushed
the
marc/selector-labels
branch
from
November 11, 2026 17:11
f35a406 to
7c41209
Compare
marcleblanc2
added a commit
that referenced
this pull request
Nov 11, 2026
A list in `frontend.command` or `migrator.command` rendered as one element, so the container could not start: ```yaml # values frontend: command: ["sh", "-c", "/start.sh"] # rendered before command: [sh -c /start.sh] # rendered now command: - sh - -c - /start.sh ``` A customer moving from Kustomize to this chart wraps these commands in `sh -c` scripts that export credentials read from mounted files. ### Changes - `frontend.command` and `migrator.command` are rendered with `toYaml`, like `args` on the next line, and are now documented in `values.yaml`. - A string holding a YAML list (`command: '["sh", "-c", "/start.sh"]'`) was the only form that worked before. It still renders unchanged, so nobody relying on it breaks. - New `openTelemetry.gateway.command` replaces the `otel-collector` container's command, which was hard-coded. The same customer wraps it in `sh -c` to export an exporter API key from a mounted file. When set, it also replaces the `--config` flag the chart selects, so the wrapper has to pass its own. No other service has a `command` value. Their `args` values already render with `toYaml`. Default values render the same manifests as `main`. ### Checklist - [x] Follow the [manual testing process](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/TEST.md): on an EKS cluster instead of kind, see "Tested on a cluster" below - [x] Update [changelog](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/charts/sourcegraph/CHANGELOG.md) - [ ] Update [Kubernetes update doc](https://docs.sourcegraph.com/admin/updates/kubernetes): not needed, no upgrade steps ### Test plan - New `charts/sourcegraph/tests/command_test.yaml`: 3 of its 5 tests fail on `main` and all pass here. It covers a list, a multi-line script, the string form, the unset default, and the `otel-collector` override. - `helm unittest charts/sourcegraph`: 239 tests pass. - `helm lint charts/sourcegraph` passes. - `helm template` with default values, and with `jaeger.enabled=true` plus `openTelemetry.gateway.args`, is byte-identical to `main`. - Rendered a multi-line `sh -c` script for `frontend` and a `/bin/sh -c` wrapper for `otel-collector`, and read the commands back with `yq`. - Deployed `sh -c` wrappers for `frontend`, `migrator`, and `otel-collector` to a cluster. Details below. ### Tested on a cluster All three wrappers start their container, and the variable each one exports reaches the Sourcegraph process. **Setup** - EKS Auto Mode, Kubernetes v1.37.1, Helm v4.3.1, Sourcegraph 8.1.4873. - Chart: `main` at `0868743` plus this PR and #1007 (the only conflict between them is `CHANGELOG.md`). Lint passes, 252 unit tests pass, and the default render equals `main`. - Applied with `helm upgrade` to a release that had adopted a Kustomize install. #1007 describes that adoption. - Each wrapper reads a mounted file into a variable, logs its arguments, and hands over to the image's entrypoint: ```yaml frontend: command: - /bin/sh - -c - | export COMMAND_WRAPPER_TEST="from-file:$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)" echo "command wrapper: COMMAND_WRAPPER_TEST=$COMMAND_WRAPPER_TEST arguments: $*" >&2 exec /sbin/tini -- /frontend "$@" - -- ``` `migrator` used the same script ending in `exec /sbin/tini -- /migrator "$@"`. `openTelemetry.gateway.command` used one ending in `exec /bin/otelcol-sourcegraph "$@"`, with `--config=/etc/otel-collector/configs/logging.yaml` as a fifth list item. **Results** | Check | `main` | This PR | |---|---|---| | `helm template` with these values | Fails: `YAML parse error on sourcegraph/templates/frontend/sourcegraph-frontend.Deployment.yaml: ... did not find expected ',' or ']'` | `frontend` and `migrator` render a 4-item `command`, `otel-collector` a 5-item one | Deployed with this PR: - `migrator` init container: the wrapper logged `arguments: up`, the migrator reported `Schema(s) are up-to-date!`, exit code 0. - `frontend`: the wrapper logged `arguments: serve`. PID 1 is `/sbin/tini -- /frontend serve`, and the variable is set in PID 1 and in the `frontend` process. The pod is Ready with 0 restarts. - `otel-collector`: PID 1 is `/bin/otelcol-sourcegraph --config=/etc/otel-collector/configs/logging.yaml`, with the variable set. - Search still returns the same 5 matches as before the change. ### Lessons learned: check these when writing a wrapper 1. **The chart still appends `args`.** `frontend` gets `serve` and `migrator` gets `up`, and both wrappers logged them. A `sh -c` wrapper must end its list with a placeholder item such as `--` and pass `"$@"` to the real binary. `sh -c` uses the first item after the script as `$0`, so without the placeholder the binary would not get `serve`. The run without a placeholder was not tested. 2. **`command` replaces the image's entrypoint.** The wrapper has to start it again: `/sbin/tini -- /frontend` and `/sbin/tini -- /migrator` in 8.1.4873. With `exec`, the entrypoint ran as PID 1, the same as without a wrapper. 3. **`openTelemetry.gateway.command` also replaces the `--config` flag.** Without it, the chart picks `configs/logging.yaml`, `configs/jaeger.yaml` when `jaeger.enabled` is set, or `config.yaml` when `openTelemetry.gateway.config.traces.exporters` is set. The wrapper has to pass the flag itself. The binary is `/bin/otelcol-sourcegraph`. 4. **`openTelemetry.gateway` has no `extraVolumes` or `extraVolumeMounts`.** `frontend` has both, and `migrator`, an init container in the same pod, has `extraVolumeMounts`. An `otel-collector` wrapper can only read a file that something else mounts into the pod, such as an injected sidecar. This PR does not add those values. Not tested: - Reading a real credential. The test read the service account's namespace file, which is mounted into every pod. - A file mounted with `frontend.extraVolumes`, or injected by a sidecar. - Helm 3. All runs used Helm 4.3.1. - `jaeger.enabled` together with `openTelemetry.gateway.command`, on a cluster. It is covered by the render tests only. - The string form of `command` on a cluster. It is covered by a unit test only. Co-authored-by: Amp <amp@ampcode.com>
marcleblanc2
force-pushed
the
marc/selector-labels
branch
from
November 11, 2026 17:45
7c41209 to
7fbf7c2
Compare
1 of 3 tasks
michaellzc
added a commit
that referenced
this pull request
Nov 11, 2026
Revert the selector label merge and the indexedSearch.storageLabels.deploy check with their tests. Both only affect custom values that conflict with labels the chart sets, and render consistently with Helm. Keep the golden test. Amp-Thread-ID: https://ampcode.com/threads/T-de6ef54a-e04e-4aeb-9ed5-101ed382c9bf Co-authored-by: Amp <amp@ampcode.com>
michaellzc
approved these changes
Nov 11, 2026
michaellzc
left a comment
Member
There was a problem hiding this comment.
can you pull in before merging it? #1014
marcleblanc2
pushed a commit
that referenced
this pull request
Nov 11, 2026
…#1014) Follow-up to #1007 from review. #1007 changes the helper behind every selector, and its tests would still pass if a default selector changed, which would break `helm upgrade` of every existing release. This adds a golden test recorded on `33c9ef8`, before #1007, that snapshots every workload selector, pod label set, volume claim template, and Service and PodDisruptionBudget selector. ### Checklist - [x] Follow the [manual testing process](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/TEST.md): test-only change - [ ] Update [changelog](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/charts/sourcegraph/CHANGELOG.md): not needed, test only - [ ] Update [Kubernetes update doc](https://docs.sourcegraph.com/admin/updates/kubernetes): not needed ### Test plan - The golden test (108 snapshots, with jaeger, syntactic-code-intel, all 10 PDBs, 2 worker replicas, and a `podLabels` case) passes unchanged on #1007. - It fails on mutations that change default selectors or labels, such as the frontend selector losing `app.kubernetes.io/name`/`instance`, which #1007's own tests miss. --------- Co-authored-by: Amp <amp@ampcode.com>
marcleblanc2
force-pushed
the
marc/selector-labels
branch
from
November 11, 2026 22:59
76eeccd to
caaa558
Compare
Kubernetes does not allow changing a workload's spec.selector or a StatefulSet's volumeClaimTemplates, so a Helm release could not take over workloads created another way, such as with Kustomize, without deleting them. - sourcegraph.selectorLabelsOverride replaces the default app.kubernetes.io/name and app.kubernetes.io/instance selector labels on every component - <serviceName>.extraSelectorLabels adds selector labels to one component - storageLabels on gitserver, grafana, indexedSearch, and searcher adds labels to the volume claim template All default to empty, and the default render is unchanged. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ca07-0283-75f1-837f-d0eb0aa7ee1c Co-authored-by: Amp <amp@ampcode.com>
…#1014) Follow-up to #1007 from review. #1007 changes the helper behind every selector, and its tests would still pass if a default selector changed, which would break `helm upgrade` of every existing release. This adds a golden test recorded on `33c9ef8`, before #1007, that snapshots every workload selector, pod label set, volume claim template, and Service and PodDisruptionBudget selector. ### Checklist - [x] Follow the [manual testing process](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/TEST.md): test-only change - [ ] Update [changelog](https://github.com/sourcegraph/deploy-sourcegraph-helm/blob/main/charts/sourcegraph/CHANGELOG.md): not needed, test only - [ ] Update [Kubernetes update doc](https://docs.sourcegraph.com/admin/updates/kubernetes): not needed ### Test plan - The golden test (108 snapshots, with jaeger, syntactic-code-intel, all 10 PDBs, 2 worker replicas, and a `podLabels` case) passes unchanged on #1007. - It fails on mutations that change default selectors or labels, such as the frontend selector losing `app.kubernetes.io/name`/`instance`, which #1007's own tests miss. --------- Co-authored-by: Amp <amp@ampcode.com>
marcleblanc2
force-pushed
the
marc/selector-labels
branch
from
November 13, 2026 05:34
caaa558 to
0054b5d
Compare
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kubernetes does not allow changing a workload's
spec.selectoror a StatefulSet'svolumeClaimTemplates. The chart hard-codes both, so a Helm release cannot take over workloads that were created another way, such as with Kustomize, unless they are deleted first. A customer moving from Kustomize to this chart has extra labels in every selector and volume claim template.Changes
Three opt-in values, all empty by default:
sourcegraph.selectorLabelsOverridereplaces the defaultapp.kubernetes.io/nameandapp.kubernetes.io/instanceselector labels on every component.<serviceName>.extraSelectorLabelsadds selector labels to one component.storageLabelsongitserver,grafana,indexedSearch, andsearcheradds labels to the volume claim template.How it works:
sourcegraph.selectorLabelsnow takes the component's values,(list . .Values.gitserver), so one helper renders a component's workload selector, pod labels, and Service and PodDisruptionBudget selectors, and they cannot drift apart. Most of the diff is that argument change at 70 call sites.sourcegraph.labels) keepapp.kubernetes.io/nameandapp.kubernetes.io/instancewhen the selector labels are replaced.app,deploy,group,type,worker-replica), because that would stop a selector from matching its pods.grafanaStatefulSet selector is onlyapp: grafana. It now also gets the custom labels when they are set, so an existinggrafanacan be adopted too.Default values render the same manifests as
main. Out of scope:jaeger, which has its own selector helper, and the migrator and executor charts.This touches most workload, Service, and PodDisruptionBudget templates, so it will conflict with other open PRs that edit the same lines.
Checklist
Test plan
charts/sourcegraph/tests/selectorLabels_test.yaml: 9 of its 13 tests fail onmainand all pass here. The 4 that pass onmaincheck that defaults are unchanged.helm unittest charts/sourcegraph: 247 tests pass.helm lint charts/sourcegraphpasses.helm templatewith default values, and with every optional component, all PodDisruptionBudgets, and worker replicas enabled, is byte-identical tomain.spec.selector, and for StatefulSetsvolumeClaimTemplates,serviceName, andpodManagementPolicy, are equal in all 48 fields. The same comparison againstmaindiffers in 28.Tested on a cluster
A Helm release took over a running Kustomize install. No object was deleted or recreated, and the data survived.
Setup
mainat0868743plus this PR and sourcegraph: render frontend and migrator command as a list #1005 (the only conflict between them isCHANGELOG.md). Lint passes, 252 unit tests pass, and the default render equalsmain.deploy-sourcegraph-k8sv8.1.4873, applied withkubectl apply -k, with three extra labels in every workload selector, pod template, Service selector, and volume claim template. Two labels are the same everywhere. The third is per service, and its value is not always the component name: both Redis Deployments share one value, and so do theotel-agentDaemonSet andotel-collectorDeployment.grafana,prometheus, and OpenTelemetry, with 11 PersistentVolumeClaims (7 standalone, 4 fromvolumeClaimTemplates). One replica each. One repository was cloned, indexed, and searched before the adoption.Results
mainvolumeClaimTemplates,serviceName,podManagementPolicy)volumeClaimTemplateskubectl apply --dry-run=serverof the render over the live objectsspec.selectoris immutable on all 17, andvolumeClaimTemplateson 4 StatefulSetsThe adoption itself, with this PR:
deployed.index:only) searches return the same 5 matches as before, and the access token created before the adoption still works, so the database, the cloned repository, and the search index survived.helm upgrade(revision 3, the values from sourcegraph: render frontend and migrator command as a list #1005) applied cleanly.sourcegraph-executor/k8schart, which this PR does not change, installed next to the adopted release. Its executor registered on thebatchesandcodeintelqueues, a server-side batch spec completed (1 workspace, 1 line added), and ascip-typescriptauto-indexing job completed.Lessons learned: check these before adopting a Kustomize install
Each one was observed on the test cluster unless it says otherwise. The selector labels in this PR are needed, but they were not enough: the first two attempts failed for the reasons in 1 and 2.
--server-side=false. Helm 4 uses server-side apply for a new release. With only--take-ownershipit stopped on conflicts with thekubectl-client-side-applyfield manager on 38 objects (15 Services, 9 Deployments, 7 StatefulSets, 6 ConfigMaps, 1 DaemonSet). Client-side apply worked. Helm 3 always applies client-side, and has--take-ownershipsince 3.17.valueon one side andvalueFromon the other blocks the upgrade. Kustomize setsPOSTGRES_PASSWORDandPOSTGRES_USERoncodeinsights-dbwithvalue; the chart sets them withvalueFrom.secretKeyRef. Helm's merge keeps the oldvalueand addsvalueFrom, and the API server rejects that:valueFrom: may not be specified when `value` is not empty. I patched the live StatefulSet's two variables to the chart'ssecretKeyReffirst, then the upgrade passed. The same applies to any variable a Kustomize patch sets one way and the chart sets the other.syntactic-code-intel-worker). The failed client-side attempt had already patched every other workload, and their pods restarted at that point. Plan the first attempt as the real change. No single dry run predicted it:helm template | kubectl apply --dry-run=server -f -caught the immutable selector andvolumeClaimTemplateserrors onmain, but acceptedcodeinsights-db, becausekubectlremoves the oldvalueand Helm does not.helm upgrade --install --dry-run=serverreportedDry run complete30 seconds before the real run failed on the 38 conflicts in 1.helm template | kubectl apply --server-side --force-conflicts --dry-run=server -f -caught the error in 2.storageClass.nameand everystorageSizehave to equal the existing claims, because they are part of the immutablevolumeClaimTemplates.PRECISE_CODE_INTEL_UPLOAD_BACKENDandPRECISE_CODE_INTEL_UPLOAD_AWS_ENDPOINTonworkerandprecise-code-intel-worker,DATA_SOURCE_NAMEon the threepgsql-exportersidecars,USE_ROCKSKIPandROCKSKIP_MIN_REPO_SIZE_MBonsearcher), port 8888 onotel-agent, and Kustomize's object labels. They keep working, but they are not in the values file, so a later rebuild from the chart alone would not have them. Comparekubectl get -o yamlwithhelm get manifestafterwards, and move what is still needed into values.syntactic-code-intelDeployment and Service (the chart createssyntactic-code-intel-workernext to them, so both ran until I deleted the old pair), and the ConfigMapsotel-collector,prometheus-rbac,sourcegraph-frontend-env,sourcegraph-upload, andsourcegraph-kustomize-build-config. The 4 PersistentVolumeClaims created fromvolumeClaimTemplatesare also unowned, which is normal for a StatefulSet. List what the release does not own afterwards and delete what is no longer used.prometheusClusterRole and ClusterRoleBinding. Deleting the namespace does not delete them.helm upgrade --installagain moved the release from failed revision 1 to deployed revision 2.Not tested:
--force-conflicts. Only akubectl apply --server-side --force-conflicts --dry-run=serverof the render was run: it accepted 65 objects and rejectedcodeinsights-dbfor the reason in 2.helm uninstallorhelm rollbackon an adopted release. Expecthelm uninstallto delete the adopted objects, including the 7 standalone PersistentVolumeClaims.jaeger, and external databases or object storage.