Git workspace reference¶
How the operator and the pods manage the workspace of a
git-sourced CodeLocation: trees, the volume,
retention, outages. Read this when you size RWX storage, write
NetworkPolicies, create Run objects yourself, or debug a build.
Trees¶
A tree is one checkout and its venv, under a key made of the commit, the
runtime image, path and the dependencies setting (timeoutSeconds
excluded). A change to any of these gives a new tree: in shared mode the
code-location pod builds it, in fallback mode every pod does.
spec.env is not part of the key, even a variable that the install
uses, such as UV_INDEX_URL. After you change it, the code-location pods
roll out on the same tree. There is no way to rebuild a tree on request:
to install with the new value, give the CodeLocation a new commit, for
example an empty commit (git commit --allow-empty) on its branch.
In shared mode, run and step pods cannot write Python's compiled files
(__pycache__) into the tree, so the build compiles the files that these
pods import from the repository: the project directory (path) and
editable installs from the repository, such as uv workspace members. If
your code imports other files of the repository, for example through
sys.path, each process compiles them again.
Workspace size¶
| Value | Default | Applies to |
|---|---|---|
codeLocation.workspace.shared.size (chart) |
20Gi |
Shared mode: the request of the PVC the operator creates per CodeLocation |
codeLocation.workspace.sizeLimit (chart) |
2Gi |
Fallback mode: the emptyDir limit per pod |
spec.git.workspaceSize (CodeLocation) |
unset | Replaces either value for one CodeLocation |
All three are Kubernetes quantities above zero, like 10Gi, 500M or
1e9. 5GB is not a quantity: write 5G (10^9 bytes) or 5Gi (2^30
bytes). The API server and the webhook reject any other workspaceSize,
and the operator does not start with any other chart value. The operator
does not resize a PVC that is already there.
In fallback mode the limit must hold the checkout and the venv. During
an install, uv's temporary copy of the wheels is in the container's
/tmp, outside this limit, so /tmp must be writable: a policy that
sets readOnlyRootFilesystem on the pods makes these installs fail.
Volume permissions¶
The runtime image runs as user and group 65532, and every git-mode pod
sets fsGroup: 65532, so that this user can read the git Secret and
write the tree.
In shared mode, group 65532 must also be able to write to the RWX
volume, and not every storage driver applies fsGroup to it: a CSI
driver with the default fsGroupPolicy, ReadWriteOnceWithFSType,
skips RWX volumes, and in-tree nfs volumes ignore fsGroup. On such
storage, give the volume's root directory group 65532 and mode 2775
(setgid), or make it writable for all users. Else the code-location
pod's build fails with Permission denied.
On OpenShift, the default restricted-v2 SCC rejects pods with this
fixed fsGroup, as it rejects the chart's operator and UI pods, which
set a fixed user and group too.
File locks¶
In shared mode, the code-location pods lock files on the RWX volume
(flock), so that two pods do not build the same tree or delete old
trees at the same time, and uv does not prune its cache while another
pod installs. These locks must work across nodes. If your storage keeps
them on one node, two pods on different nodes can build the same tree at
the same time and break it.
uv cache¶
In shared mode, uv's cache is on the PVC (cache/). After each
successful build, the code-location pod runs uv cache prune --ci: only
the wheels that uv built from source stay, and pre-built wheels download
again on the next build. A runtime image you build yourself needs uv
0.10.0 or later, for uv workspace dir and for a uv cache prune that
waits for the installs of other pods instead of deleting files that they
use.
In fallback mode, pods install with UV_NO_CACHE=1 and keep no cache.
Old trees¶
In shared mode, each time a code-location pod starts, it deletes old trees from the PVC. It keeps:
- the trees that the CodeLocation and its unfinished runs use,
- the
codeLocation.workspace.keepRevisionsnewest trees (default 3), - all trees younger than
codeLocation.workspace.minTreeAge(default1h), - its own tree.
Write minTreeAge as a whole number with s, m or h, for example
90m or 24h. The operator does not start if it cannot read
minTreeAge (for example 1d, 1h30m or 1.5h) or keepRevisions.
If the pod cannot read these limits, for example because spec.env sets
RIVERS_WORKSPACE_MIN_AGE_SECONDS or RIVERS_WORKSPACE_KEEP_REVISIONS
to a value that is not a whole number, it deletes no tree, and its init
container log gives the reason.
Before the pod deletes a tree, it renames it to .deleting-<key>-…. If
the pod stops during a deletion, no part of the tree stays under its
key, and the next pod start deletes the rest. The pod deletes old trees
and prunes the uv cache in its init container, after its own tree is
ready, so its main container starts only after that. On NFS or EFS, the
deletion of a tree with many files can take minutes. Other pods that
wait for the same tree do not wait for the deletion or the prune.
Back to image mode¶
When you remove spec.git, the operator rolls the code-location pods
out on spec.image, clears status.runSource, resolvedCommit,
resolvedRef and lastFetchedAt, and new runs get the image. If the
operator cannot resolve spec.image, the CodeLocation is Failed but
keeps these fields, because its pods still run the git source.
In shared mode, the operator then deletes the workspace PVC and its keep
ConfigMap, but only when no pod can mount the PVC: every code-location
pod runs the image, and no Run of the CodeLocation has a git
spec.source. A finished run counts too: its pods stay until you delete
the Run, and Kubernetes does not delete a PVC that a pod mounts. Until
then, the WorkspaceKept condition gives the reason:
| Reason | Meaning |
|---|---|
RollingOut |
The code-location pods still roll out on the image. |
RunsNotListed |
The operator has not listed the runs yet. |
RunsUseWorkspace |
The pods of the named runs mount the PVC. Delete the finished ones to free it. |
The operator checks again every minute. It deletes only a PVC and a
ConfigMap that the CodeLocation owns, not others with the same name. If
you add spec.git again before the PVC is deleted, the pods use it
again. If the PVC is being deleted, the operator waits until it is gone,
and then creates a new one.
Custom run and step images¶
In git mode, RunBackendConfig.kubernetes(image=...) and
Executor.kubernetes(worker_image=...) replace only the image of the
main container of run and step pods. These pods still run the tree that
the runtime image built: the checkout and the venv. The run records that
image in spec.source.runtimeImage, and in fallback mode the init
container that builds the pod's tree runs it. The main container starts
/workspace/venv/bin/rivers, so its image must have the same Python
version at the same path as the runtime image: build it FROM the
runtime image.
Runs you create yourself¶
A Run you create without image gets the rolled-out tree: the webhook
copies status.runSource into the run's spec.source, and its runtime
image into spec.image. Just after you add spec.git to an image-mode
CodeLocation, status.runSource is empty and the webhook rejects such
runs; try again when the first tree has rolled out.
A Run with your own spec.source follows the rules of a CodeLocation:
spec.source.git.commit is a full lowercase SHA, and
spec.source.git.path and spec.source.git.url pass the
path and
url rules. The webhook accepts the
url and Secret if they are those of spec.git, or, while a change of
them rolls out, those of status.runSource. A Run with a digest
image must set spec.source.runtimeImage to a digest in the
CodeLocation's runtime image repository: that of spec.image (or of the
chart's default runtime image), or, while a change of spec.image rolls
out, that of status.resolvedImage. The webhook rejects other images.
Egress¶
In shared mode only the code-location pod needs outbound access to the
git host and the package index; run and step pods fetch nothing. In
fallback mode every pod does. Adjust NetworkPolicies accordingly. The
operator's requests to git hosts and registries carry
User-Agent: rivers-operator/<version>, for firewalls that filter on it.
Outages¶
A git host that does not answer (connection error, HTTP 5xx, or no
complete answer within operator.git.timeoutSeconds, 30 seconds by
default) does not take the CodeLocation down. It stays Ready on the
commit it serves, with SourceResolved False, reason GitUnreachable,
and the error, also in status.message. In shared mode, runs keep using
that commit's tree. In fallback mode, each run pod fetches the commit
itself, so a run that starts while the host is down fails.
The operator asks the host again after 1, 2 and 4 minutes, then every 5
minutes, and picks up new commits when the host answers. A host that
rate-limits the operator (HTTP 429, or 503 with Retry-After) has the
same result, with reason GitRateLimited. The operator then sends no
request to that host, for any CodeLocation, until the host's
Retry-After time is over: at most one hour, and not sooner than the
steps above. The message gives the time of the next request.
Before the first commit has rolled out there is nothing to serve, and
the CodeLocation is Failed until the host answers.
The registry of the runtime image works the same way. If it does not
answer (connection error or HTTP 5xx) or rate-limits the operator (HTTP
429), a CodeLocation that serves a commit stays Ready on it, with
ImageResolved False, reason RegistryError or RateLimited, and
the error, also in status.message. The operator asks the registry
again later, for a 429 when its Retry-After time is over. A tag that
the registry does not have (TagNotFound) or a registry login that
fails (AuthenticationFailed) sets the phase to Failed.
Apply failures¶
If the API server refuses an object of a git CodeLocation (its
Deployment, Service, workspace PVC or keep ConfigMap), for example
because of a policy, a quota or a value that it does not take,
DeploymentAvailable has reason ApplyFailed and the API server's
error, after applying Deployment 'analytics' failed: (or the object
that failed). The same error is in status.message. The pods that run
keep running, so a CodeLocation that serves a commit stays Ready on it
and runs keep using it. Before the first commit has rolled out there is
nothing to serve, and the CodeLocation is Failed. The operator tries
again when you change the CodeLocation, else after 5 minutes, or after 1
minute for a server error (HTTP 5xx), a conflict or throttling.
Status fields¶
| Field | Meaning |
|---|---|
status.source |
The SOURCE column of kubectl get rcl: main@9f3c1ab in git mode, repo@sha256:abc1234 in image mode. Moves only when a rollout finishes. |
status.resolvedCommit |
The full commit of runSource. |
status.resolvedRef |
The ref that commit came from, such as refs/heads/main. Absent for a pinned commit. |
status.lastFetchedAt |
The last fetch of spec.git.ref from the git host. Absent for a pinned commit. |
status.runSource |
The source runs get: url, commit, path, Secret, dependencies and the runtime image that built the tree. Moves only when a rollout finishes. |
status.resolvedImage |
The digest of the image every pod runs and is ready on, which runs get; runSource.runtimeImage in git mode. Moves only when a rollout finishes. |
status.message |
The current error, if any. |