[docs] Document default fsGroup for Kubernetes persistent volumes - #1502
[docs] Document default fsGroup for Kubernetes persistent volumes#1502aspire-repo-bot[bot] wants to merge 1 commit into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Frontend HTML artifact readyThe latest frontend build uploaded the This comment updates automatically when a new frontend build artifact is uploaded. |
There was a problem hiding this comment.
Pull request overview
Updates the Kubernetes persistent volumes documentation to describe the new default pod-level securityContext applied by WithPersistentVolume(...) so non-root workloads can write to newly provisioned volumes without hitting permission errors.
Changes:
- Added a new “Default pod security context” section documenting the default
fsGroup: 2000andfsGroupChangePolicy: OnRootMismatch. - Documented how to override the default
fsGroupor remove the generated pod security context viaPublishAsKubernetesService. - Added cautions about CSI/storage driver support and cluster admission policy constraints.
Suppressed comments (1)
src/frontend/src/content/docs/deployment/kubernetes/persistent-volumes.mdx:239
- This second example has the same issues as the previous one: it references an undefined
datavariable and usesresource.Workload?.PodTemplate.Spec. Consider casting toStatefulSet(since PV-bound workloads are promoted) and clearingSpec.Template.Spec.SecurityContextthere, while reusing the volume variable from the preceding example (media).
builder.AddProject<Projects.WebFrontend>("webfrontend")
.WithPersistentVolume(data, "/data")
.PublishAsKubernetesService(resource =>
{
var podSpec = resource.Workload?.PodTemplate.Spec
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| builder.AddProject<Projects.WebFrontend>("webfrontend") | ||
| .WithPersistentVolume(data, "/data") | ||
| .PublishAsKubernetesService(resource => | ||
| { | ||
| var podSpec = resource.Workload?.PodTemplate.Spec | ||
| ?? throw new InvalidOperationException("The Kubernetes workload was not generated."); | ||
|
|
||
| podSpec.SecurityContext ??= new(); | ||
| podSpec.SecurityContext.FsGroup = 3000; | ||
| }); |
David Pine (IEvangelist)
left a comment
There was a problem hiding this comment.
Automated docs-accuracy review
Phase A source of truth: microsoft/aspire release/13.5 @ 8ab6999850d96e0023670799edbd4bdc245ad63c (branch matched to this PR's target, aspire.dev release/13.5). Version context read from microsoft/aspire main @ 002abcf250.
Claims extracted: 12 non-narrative (plus several narrative statements, not blocking).
| Verdict | Count | Claims |
|---|---|---|
| ✅ verified | 6 | overload count, PublishAsKubernetesService, Workload?.PodTemplate.Spec, SecurityContext, FsGroup, YAML aliases |
| 0 | — | |
| ❔ unverifiable | 6 | the automatic fsGroup: 2000 / OnRootMismatch default and everything that depends on it |
| ❌ contradicted | 0 | — |
Phase B (doc-tester): 1 route exercised — /deployment/kubernetes/persistent-volumes/ (#default-pod-security-context). Served from PR head e2d11bd2 via pnpm dev at http://localhost:4321/. 0 critical, 1 warning, plus knowledge gaps. Page renders cleanly (0 expressive-code errors, 0 console errors).
Verdict: COMMENT. No claim is contradicted and Phase B found no critical issues, but the headline behavior is unverifiable on the branch this PR targets (see the inline comment on the "…add the following pod security context…automatically" line), and there's a language-tab consistency warning.
Phase A — Claim verification
The six unverifiable claims all share one root cause: the automatic pod-security-context default is a 13.6 feature (source PR microsoft/aspire#19374, milestone 13.6) that is not present on release/13.5, which is the source-of-truth branch for this PR. See the inline comment for the evidence and the affected lines (L192, L196‑197, L200, L202, L206, L227).
✅ verified claims (6) — the override/removal API surface is valid on release/13.5
| # | Claim | Evidence (microsoft/aspire release/13.5) |
|---|---|---|
| C2 | There are two WithPersistentVolume(...) overloads |
KubernetesPersistentVolumeExtensions.cs:253 (volume) and :293 (volume, mountPath, isReadOnly = false) |
| C7 | PublishAsKubernetesService takes an Action<KubernetesResource> callback |
KubernetesServiceExtensions.cs:34 |
| C8 | resource.Workload?.PodTemplate.Spec resolves to PodSpecV1 |
KubernetesResource.Workload is Workload? (KubernetesResource.cs:60); Workload.PodTemplate is abstract PodTemplateSpecV1 (Workload.cs); PodTemplateSpecV1.Spec is PodSpecV1 (PodTemplateSpecV1.cs:33); same chain used by the publisher (KubernetesPublishingContext.cs:406) |
| C9 | podSpec.SecurityContext is nullable get/set; ??= new() and = null are valid |
PodSpecV1.SecurityContext is PodSecurityContextV1? (PodSpecV1.cs:301); PodSecurityContextV1 is a sealed class with an implicit parameterless ctor |
| C10 | podSpec.SecurityContext.FsGroup = 3000 is assignable |
PodSecurityContextV1.FsGroup is long? (PodSecurityContextV1.cs:52) |
| C12 | FsGroup/FsGroupChangePolicy serialize to YAML fsGroup/fsGroupChangePolicy |
[YamlMember(Alias = "fsGroup")] (PodSecurityContextV1.cs:51), [YamlMember(Alias = "fsGroupChangePolicy")] (:116) |
❔ unverifiable claims (6) — absent on release/13.5, present on main (13.6)
| # | Claim | Finding |
|---|---|---|
| C1 | Both overloads add fsGroup: 2000 + fsGroupChangePolicy: OnRootMismatch automatically |
On release/13.5 both overloads add only annotations (KubernetesPersistentVolumeExtensions.cs:253,293); no PodSecurityContextV1 is constructed anywhere in src/Aspire.Hosting.Kubernetes; the literal 2000 is absent; OnRootMismatch appears only in a doc comment (PodSecurityContextV1.cs:114). Present on main: KubernetesResource.cs:24‑25. |
| C3 | Aspire always uses the fixed group id 2000 |
No such constant on release/13.5. main: DefaultPersistentVolumeFsGroup = 2000. |
| C4 | Default change policy is OnRootMismatch |
Not applied as a default on release/13.5. main: DefaultPersistentVolumeFsGroupChangePolicy = "OnRootMismatch". |
| C5 | The default only applies to WithPersistentVolume-bound workloads; default-storage PVCs unaffected |
Depends on the default, which is absent on release/13.5. |
| C6 | The default is applied before the PublishAsKubernetesService callback runs |
No default is applied on release/13.5, so the ordering can't be evidenced there. |
| C11 | The generated OnRootMismatch policy is retained unless the callback replaces it |
Depends on the default, which is absent on release/13.5. |
Narrative statements (general Kubernetes background — access mode vs. filesystem permissions, root:root ownership, non‑root first‑write failures, CSI/admission caveats) are noted but not blocking.
Phase B — Doc-tester results
Blind-user perspective — the rendered docs site was the only window into behavior; no source code was consulted for this phase.
Route exercised: /deployment/kubernetes/persistent-volumes/ → section #default-pod-security-context (the only route touched by this PR's diff). Served from PR head e2d11bd2 via pnpm dev (http://localhost:4321/), navigated with Playwright.
Summary
| Category | Passed | Failed | Warnings |
|---|---|---|---|
| Content accuracy (as presented) | 1 | 0 | 0 |
| Rendering (page/section/code/asides) | 5 | 0 | 0 |
| Code examples (readability) | 2 | 0 | 0 |
| Language-tab consistency | 0 | 0 | 1 |
| Links | n/a | 0 | 0 |
Critical issues
None.
Warnings
W1 — Language-tab inconsistency in the override examples.
Location: #default-pod-security-context → "Overriding or removing the default". The two <Tabs syncKey="aspire-lang"> groups in the new section expose only a C# tab, whereas the four other <Tabs> groups on this same page each offer C# and TypeScript. Because all groups share the aspire-lang sync key, a reader who selected TypeScript earlier on the page arrives at these examples and sees a C#-only block with no TypeScript equivalent and no explanation. Observed tab labels on the page: [C#, TypeScript] × 4, then [C#] × 2.
Suggestion: add a TypeScript tab, or a one-line note that the override/removal callback is C#-only.
Passed checks
- Page loads (
Persistent volumes on Kubernetes | Aspire); new## Default pod security contextH2 renders with a TOC entry and anchor link (#default-pod-security-context). - The
securityContextYAML block renders correctly. - Both C# examples render with the
AppHost.cstitle and are readable/copyable; 0 expressive-code error markers. - The
cautionaside renders. - No browser console errors.
Knowledge gaps
- KG1 — TypeScript support unclear. The page teaches in both C# and TypeScript, but the override/removal pattern is shown only in C#. From the docs alone a polyglot user can't tell whether TypeScript AppHosts can't do this or the example is simply missing. (Ties to W1.)
- KG2 (minor, expected). A blind reader can't verify the runtime claims (the
2000/OnRootMismatchdefault and "applied before the callback runs") from the docs alone — they're taken on faith. Normal for behavior docs; noted for completeness. Phase A found these are not evidenced on therelease/13.5source.
Recommendations
- Resolve the branch/version mismatch surfaced in Phase A before this lands on the 13.5 docs (the documented default ships in 13.6, not 13.5).
- Add a TypeScript tab or an explicit C#-only note for the override/removal examples (W1 / KG1).
|
|
||
| A Kubernetes access mode such as `ReadWriteOnce` controls how a volume can be attached and mounted; it doesn't grant the container's Linux process permission to write to the mounted filesystem. Without further configuration, a freshly provisioned volume is typically owned by `root:root`, so a workload that runs as a non-root user fails on its first write even though the PVC is `Bound` and the pod is `Running`. | ||
|
|
||
| To avoid that out-of-the-box failure, both `WithPersistentVolume(...)` overloads add the following pod security context to the workload automatically: |
There was a problem hiding this comment.
Phase A — unverifiable on the source-of-truth branch (version skew).
This PR targets aspire.dev release/13.5, so the matching source of truth is microsoft/aspire release/13.5 (@ 8ab6999850). On that branch the automatic fsGroup: 2000 / fsGroupChangePolicy: OnRootMismatch default does not exist:
- Both
WithPersistentVolume(...)overloads add only annotations — no security context (KubernetesPersistentVolumeExtensions.cs:253and:293). - No
PodSecurityContextV1is ever constructed insrc/Aspire.Hosting.Kubernetes, the literal2000appears nowhere there, andOnRootMismatchappears only in a doc comment (PodSecurityContextV1.cs:114).
The behavior is present on microsoft/aspire main (13.6): KubernetesResource.cs:24-25 define DefaultPersistentVolumeFsGroup = 2000 and DefaultPersistentVolumeFsGroupChangePolicy = "OnRootMismatch" — matching the source PR's 13.6 milestone (microsoft/aspire#19374).
So every claim about the automatic default — the 2000/OnRootMismatch values (L196‑197, L200), "this default only applies to WithPersistentVolume" (L202), "applied before any PublishAsKubernetesService callback runs" (L206), and "the generated OnRootMismatch change policy is retained" (L227) — can't be verified against release/13.5. As written, a 13.5 reader is told about a default that doesn't ship in 13.5. Please confirm the intended target branch (e.g. wait for a release/13.6 docs branch, or ensure the source change is present on 13.5) before this lands on the 13.5 docs.
Note: the override/removal API used below (PublishAsKubernetesService, resource.Workload?.PodTemplate.Spec, podSpec.SecurityContext, FsGroup) is valid on release/13.5 — see the ✅ list in the review summary.
|
|
||
| The default security context is applied before any `PublishAsKubernetesService` callback runs, so a workload or cluster that needs a different group can replace it: | ||
|
|
||
| <Tabs syncKey="aspire-lang"> |
There was a problem hiding this comment.
Phase B (doc-tester, blind to source) — warning + knowledge gap.
This <Tabs syncKey="aspire-lang"> (and the one at L231) exposes only a C# tab. The four other <Tabs> groups on this page each offer both C# and TypeScript. Because they share the aspire-lang sync key, a reader who selected TypeScript earlier on the page arrives here and sees a C#-only block with no TypeScript equivalent and no explanation.
Knowledge gap: the page doesn't state whether overriding/removing the pod security context is available to TypeScript AppHosts. Please add a TypeScript tab, or a one-line note that this callback is C#-only.
|
|
||
| A Kubernetes access mode such as `ReadWriteOnce` controls how a volume can be attached and mounted; it doesn't grant the container's Linux process permission to write to the mounted filesystem. Without further configuration, a freshly provisioned volume is typically owned by `root:root`, so a workload that runs as a non-root user fails on its first write even though the PVC is `Bound` and the pod is `Running`. | ||
|
|
||
| To avoid that out-of-the-box failure, both `WithPersistentVolume(...)` overloads add the following pod security context to the workload automatically: |
There was a problem hiding this comment.
Could we also update the generated StatefulSet excerpt below to include this securityContext? This section says every WithPersistentVolume(...) workload gets it automatically, but the documented output still omits it.
Documents changes from microsoft/aspire#19374
@mitchdennyTargeting
release/13.5— the latest release branch onmicrosoft/aspire.dev— becauserelease/13.6(from the source PR milestone13.6) does not exist there.Why
Workloads bound through either
WithPersistentVolume(...)overload onAspire.Hosting.Kubernetesnow automatically receive a pod security context (fsGroup: 2000,fsGroupChangePolicy: OnRootMismatch) so first writes to a mounted volume don't fail with a permission error when the container runs as non-root. This is a new default behavior for a documented, first-class API (WithPersistentVolume), so the existing persistent-volumes page needed to describe it, including how to override or remove it viaPublishAsKubernetesService.What changed
src/frontend/src/content/docs/deployment/kubernetes/persistent-volumes.mdx: added a new "Default pod security context" section explaining thefsGroup/fsGroupChangePolicydefault, why2000andOnRootMismatchare used, how to override the group or remove the security context entirely via aPublishAsKubernetesServicecallback, and caveats about storage-driver/CSI support and admission policies.Page updated (no new pages created).