| title | Drive9 CSI Lite |
|---|
This repository provides a minimal Kubernetes integration for
github.com/mem9-ai/drive9.
It intentionally ships a small stable surface first:
- PVCs mount the Drive9 workspace root selected by the per-PVC API key by default.
- Optional managed directory volumes backed by Drive9 remote paths.
ReadWriteOnceby default.SINGLE_NODE_MULTI_WRITERsupported for same-node multi-pod access.ReadWriteManyadds noprofileordurabilityrequirements or defaults; configured mount parameters are forwarded todrive9 mount.- Credentials are resolved from PVC annotation
drive9.ai/secret-name→ Kubernetes Secret. - Default workspace-root volumes do not create or delete Drive9 workspace data.
- Managed directory volumes write a marker file.
DeleteVolumedetaches CSI ownership only: it removes CSI metadata (marker, index, name index) but never deletes Drive9 workspace data.NodeStageVolumerunsdrive9 mount --supervise-foreground --mode=fuse --direct-mount-strictthrough a host systemd service.NodePublishVolumebind-mounts the staged path into the pod.- No snapshots, expansion, or automatic tenant provisioning.
Customers usually already run business workloads in Kubernetes pods and want normal PVCs. CSI Lite gives them that path without requiring application-level changes.
The driver does not reimplement Drive9 FUSE. It installs the official drive9
CLI from the node-plugin image to a content-addressed host path and keeps CSI
focused on Kubernetes lifecycle, idempotency, mount orchestration, and secret
handling. New mounts require Drive9's direct mount(2) path and never fall back
to fusermount3 or fusermount. The Drive9 process owned by systemd is the
in-binary supervisor; its replaceable child owns the FUSE connection.
Put Drive9 credentials in a Kubernetes Secret:
apiVersion: v1
kind: Secret
metadata:
name: drive9-workspace-secret
type: Opaque
stringData:
server: https://api.drive9.ai
apiKey: drive9_api_key_redactedCreate this Secret in the workload namespace before creating the matching PVC.
Each PVC specifies which Secret to use via the drive9.ai/secret-name
annotation. The PVC and Secret must be in the same namespace. This lets one
namespace create many PVCs backed by different Drive9 API keys or workspaces
without requiring one cluster-scoped StorageClass per workspace.
The optional PVC annotation drive9.ai/remote-root can mount an existing
subpath of that workspace instead of /. Omit it for the normal workspace-root
behavior.
Credentials are never stored in StorageClass parameters, PV attributes, pod env, or volume parameters. The driver resolves them at runtime:
CreateVolume: reads PVC annotation → fetches Secret via K8s clientNodeStageVolume: reads Secret reference from PV volumeAttributes (fixated during CreateVolume, contains only Secret name/namespace — not the API key)DeleteVolume: looks up PV by volumeHandle → reads Secret reference from volumeAttributes → fetches Secret
If the required drive9.ai/secret-name annotation is missing, CreateVolume
fails closed with InvalidArgument — there is no implicit fallback.
The sidecar fallback necessarily injects the secret into the mounter sidecar environment. Use CSI for production when the customer can install a node plugin.
The node plugin needs privileged FUSE access, SYS_ADMIN, /dev/fuse, the host
mount namespace, and host systemd. Treat it like other node storage plugins:
restrict who can modify its DaemonSet and workload namespace Secrets. The image
does not install a FUSE mount helper on the node.
Both the controller and node service accounts need get access to Secrets so
the driver can resolve per-PVC credentials at provision and mount time. The
default RBAC does not grant list or watch beyond what is needed.
The default workspace-root mode does not write CSI metadata into the Drive9
workspace and DeleteVolume is a no-op for Drive9 data. If you opt into managed
directory mode with remoteRootPrefix, the driver stores CSI metadata under
/k8s/.drive9-csi/volumes. A scoped Drive9 token for that mode must cover both
the volume prefix, for example /k8s/pvc, and the metadata index path
/k8s/.drive9-csi/volumes.
The checked-in Kubernetes manifests are a fail-closed base. Their CSI image is
registry.invalid/drive9-csi:unpublished, so applying the base cannot silently
run an older, incompatible driver. The manually triggered validation-image
workflow resolves the latest complete Drive9 CLI release, builds and pushes the
image, then reports its trace tag, manifest-list digest, and immutable reference
in the workflow summary. It does not generate deployment manifests. Each target
architecture must execute
drive9 mount --supervise-foreground --direct-mount-strict --help
successfully during the image build, and its help must expose
--gvisor-compat, --local-only, --remote-only, --append-log, and the
corresponding DRIVE9_MOUNT_* environment names used by the fallback sidecar. The minimum
compatible version is the first published Drive9 release containing those
contracts; do not use the older dac2d62 minimum for this driver version. The
runtime image installs neither fuse3 nor an /etc/fuse.conf dependency.
Non-production validation must inject
that immutable reference through a local, environment-specific overlay.
Validation images have a traceable tag:
ghcr.io/drive9-ai/drive9-csi:drive9-<drive9-short-sha>-csi-<csi-short-sha>
The publish workflow does not publish :latest or 0.1.0. Use a traceable tag
or a digest.
A manually published image is validation-only and is not release-admitted. Do not promote it to production until the N/N-1 bidirectional cache/writeback compatibility gate in the mount-survival design has landed and passed.
To build a validation image with the latest published Drive9 CLI:
gh workflow run publish-image.yml \
-R drive9-ai/k8s-csi \
--ref <csi-branch>To build a local image using the Dockerfile's public-release downloader:
make image \
IMAGE=ghcr.io/drive9-ai/drive9-csi:localInstall that preloaded local image with the local overlay:
kubectl apply -k deploy/overlays/localFor a published validation image, read the immutable reference from the workflow summary and use it only in a non-production validation overlay. The tag or digest is traceability evidence, not release-admission evidence. Do not apply the fail-closed base directly.
Create a Drive9 Secret in the workload namespace before creating each PVC:
kubectl -n default create secret generic drive9-workspace-secret \
--from-literal=server=https://api.drive9.ai \
--from-literal=apiKey=drive9_api_key_redacted \
--dry-run=client -o yaml | kubectl apply -f -Create a PVC with the drive9.ai/secret-name annotation pointing to the Secret:
kubectl apply -f deploy/examples/kubernetes/volumeattributesclass.example.yaml
kubectl -n default apply -f deploy/examples/kubernetes/pvc.example.yamlBecause the default StorageClass uses WaitForFirstConsumer, a PVC can remain
Pending until the first Pod uses it.
Mount the PVC in a normal workload Pod:
kubectl -n default apply -f deploy/examples/kubernetes/pod.example.yaml
kubectl -n default wait --for=condition=Ready \
pod/drive9-workspace-smoke --timeout=180s
kubectl -n default logs drive9-workspace-smoke
kubectl -n default exec drive9-workspace-smoke -- cat /workspace/hello.txtExpected output:
hello-drive9
The shared-PVC example is a single-file application bundle containing its own
Namespace, StorageClass, Secret, PVC, writer Deployment, and two writer
replicas. Replace replace-with-drive9-api-key in the file, then apply it once:
kubectl apply -f deploy/examples/kubernetes/shared-pvc.example.yaml
kubectl -n drive9-shared-pvc-example rollout status \
deployment/drive9-workspace-writer --timeout=180s
kubectl -n drive9-shared-pvc-example exec \
deployment/drive9-workspace-writer -- ls -1 /workspaceBoth writer replicas update a file named after their Pod every five seconds.
Kubernetes may place the replicas on the same node or different nodes. The PVC
uses ReadWriteMany without RWX-specific mount-parameter defaults; visibility
semantics follow the Drive9 arguments configured by the user. See
deploy/examples/kubernetes/README.md for the scenario, resource roles,
credential flow, verification steps, consistency limits, and retention
behavior.
RWX does not provide distributed file locks. It does not merge concurrent writes to the same file. Applications that require those semantics must supply their own coordination.
Use the PVC from application Pods the same way:
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
persistentVolumeClaim:
claimName: drive9-workspaceClean up the examples:
kubectl -n default delete pod drive9-workspace-smoke
kubectl -n default delete pvc drive9-workspace-tuned
kubectl delete -f deploy/examples/kubernetes/volumeattributesclass.example.yaml
kubectl delete -f deploy/examples/kubernetes/shared-pvc.example.yamlThe default example StorageClass uses Retain, so deleting the PVC keeps the
PV and Drive9 workspace data for safety. Even with reclaimPolicy: Delete, the
default workspace-root mode does not delete Drive9 workspace data.
Example StorageClass, VolumeAttributesClass, Secret, PVC, and workload manifests
live under deploy/examples/kubernetes/ so that applying deploy/kubernetes/
does not create placeholder credentials or demo workloads in production
clusters.
Apply the example Secret with
kubectl -n <workload-namespace> apply -f deploy/examples/kubernetes/secret.example.yaml
after replacing the API key. Each PVC references its Secret via the
drive9.ai/secret-name annotation — multiple PVCs can share a Secret or use
different ones.
The checked-in manifests separate provisioning identity from mount behavior.
The drive9-rwo and drive9-rwx StorageClasses have empty parameters:
provisioner: csi.drive9.ai
parameters: {}
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: falsePVCs opt into mount behavior with spec.volumeAttributesClassName. The base
installs the optional drive9-coding-agent VolumeAttributesClass; the separate
tuned example installs drive9-coding-agent-tuned:
apiVersion: storage.k8s.io/v1
kind: VolumeAttributesClass
metadata:
name: drive9-coding-agent-tuned
driverName: csi.drive9.ai
parameters:
profile: coding-agent
gvisorCompat: "false"
attrTTL: 30s
entryTTL: 30s
dirTTL: 30s
perfEnabled: "false"
readdirPrefetch: "true"
readdirPrefetchMaxFiles: "64"
readdirPrefetchMaxFileBytes: "50000"
readdirPrefetchMaxBytes: "4194304"
writebackBatchWindow: 20msspec:
storageClassName: drive9-rwo
volumeAttributesClassName: drive9-coding-agent-tunedStorageClass mount parameters remain supported for compatibility. At volume
creation, VolumeAttributesClass parameters override matching StorageClass
parameters. The driver stores the effective values in PV volumeAttributes so
the node uses a fixed mount configuration for that volume.
This VAC support is creation-time only. The driver advertises CSI
MODIFY_VOLUME because external-provisioner requires that capability when
provisioning a PVC with a VAC. After validating the requested keys and values,
ControllerModifyVolume returns Unimplemented for a valid dynamic update.
Changing spec.volumeAttributesClassName on an existing PVC does not remount or
reconfigure it; recreate the volume to apply different mount parameters.
Parameter ownership is:
| Parameter | Preferred source | Behavior |
|---|---|---|
remoteRootPrefix |
StorageClass | Creates a CSI-managed directory and affects volume identity |
remoteRoot |
PVC annotation; legacy StorageClass fallback | Selects an existing workspace path and affects volume identity |
profile, durability |
VolumeAttributesClass | Forwarded to drive9 mount when explicitly set |
gvisorCompat |
VolumeAttributesClass | Strict boolean; always forwarded as `--gvisor-compat=<true |
localOnlyPatterns |
VolumeAttributesClass | Newline-delimited additional local-only rules |
remoteOnlyPatterns |
VolumeAttributesClass | Newline-delimited remote-persistent overrides; wins over local routing |
appendLogPatterns |
VolumeAttributesClass | Newline-delimited append-log optimization rules for remote-persistent files; empty by default |
attrTTL, entryTTL, dirTTL |
VolumeAttributesClass | Positive Go durations; each defaults to 30s |
perfEnabled |
VolumeAttributesClass | Boolean; defaults to false |
| Read-directory and writeback tuning | VolumeAttributesClass | Optional; no CSI defaults |
Credentials are not valid StorageClass or VolumeAttributesClass parameters.
They are resolved from the PVC's drive9.ai/secret-name annotation, which
keeps Secret binding explicit and auditable.
Retain is the default because this is customer data. To use managed-directory
mode, create a separate StorageClass containing only the identity parameter:
parameters:
remoteRootPrefix: /k8s/pvcIn managed-directory mode, CreateVolume creates a unique child directory
under that prefix and writes CSI metadata. Even with reclaimPolicy: Delete,
the driver removes only the metadata and refuses cleanup without both a matching
index entry and .drive9-csi-volume.json marker. It never deletes user data.
The optional attrTTL, entryTTL, and dirTTL values control the matching
drive9 mount --attr-ttl, --entry-ttl, and --dir-ttl flags. Each uses Go
duration syntax, for example 500ms, 1s, 30s, or 2m.
gvisorCompat and the three pattern lists are fixed per volume. The driver
normalizes each policy list by trimming lines, removing blanks, and preserving
the first occurrence of exact duplicates. It persists the canonical values in
PV volumeAttributes, then reconstructs deterministic mount arguments during
staging and recovery. Native CSI users configure these options through a VAC;
the CSI Node DaemonSet does not forward the matching DRIVE9_MOUNT_*
environment variables.
localOnlyPatterns and remoteOnlyPatterns require an overlay profile;
profile: none and profile: interactive are rejected when either routing
list is non-empty. appendLogPatterns alone also supports those two profiles.
The combined raw values of all three lists and their expanded mount arguments
are each limited to 64 KiB; their JSON-encoded arguments are limited to 256 KiB.
Oversized policies are rejected during volume creation before the driver
accesses the PVC or creates remote volume metadata.
For example, apply
deploy/examples/kubernetes/volumeattributesclass-path-policy.example.yaml
and reference drive9-gvisor-persistent-tmp from a new PVC. Its effective
arguments include:
--gvisor-compat=true
--local-only=**/local-build-cache/**
--remote-only=**/tmp/**
--remote-only=**/.tmp/**
The equals form keeps each pattern in one argv entry. A local/remote overlap is valid; Drive9 applies remote-only precedence. An explicit empty VAC policy value clears the complete matching legacy StorageClass value.
To opt into append-log, apply
deploy/examples/kubernetes/volumeattributesclass-append-log.example.yaml
and reference drive9-append-log from a new PVC. The example uses
profile: none and emits --append-log=data/app.db-wal and
--append-log=logs/events.log. Patterns refer to paths inside the mounted
filesystem, not the host mountpoint or a path prefixed with remoteRoot.
Append-log does not change local/remote routing or require a WAL filename suffix. A matching local-only file stays local. Drive9 decides whether a remote-persistent file and its writes qualify for the optimization, including server capability checks and fallback behavior. Empty lists emit no append-log flags. Changing an existing PVC's VAC remains unsupported; recreate the volume to apply different patterns.
When perfEnabled is "true", NodeStageVolume passes --perf-dir with a
driver-generated path under /var/lib/drive9-csi/perf/<volume-id>. The driver
does not accept a user-provided perf path or automatically delete perf output.
The optional tuning parameters have no CSI defaults:
| Parameter | drive9 mount flag |
|---|---|
readdirPrefetch |
--readdir-prefetch |
readdirPrefetchMaxFiles |
--readdir-prefetch-max-files |
readdirPrefetchMaxFileBytes |
--readdir-prefetch-max-file-bytes |
readdirPrefetchMaxBytes |
--readdir-prefetch-max-bytes |
writebackBatchWindow |
--writeback-batch-window |
readdirPrefetch accepts "true" or "false". Integer values must be
positive. writebackBatchWindow uses a positive Go duration such as 20ms and
requires a compatible Drive9 CLI.
If you use reclaimPolicy: Delete, keep the per-PVC workload namespace Secret
until Kubernetes has deleted the PV. If the Secret is removed first,
DeleteVolume cannot resolve credentials and metadata cleanup requires manual
intervention.
If a customer cannot install a CSI driver yet, use
deploy/sidecar/deployment.yaml as a fallback. The checked-in manifest uses the
fail-closed registry.invalid/drive9-csi:unpublished image; override it with a
strict-capable immutable image reference before applying it. It runs a
privileged Drive9 mounter sidecar and exposes the mounted directory to the app
container.
This is less clean than CSI because it requires privileged pods and hostPath
mount propagation. Use it for pilots or constrained clusters, not as the default
production path. The fallback example also mounts the Drive9 workspace root by
default; set DRIVE9_REMOTE_ROOT only when a subpath is intentional.
The sidecar uses the same fixed
--supervise-foreground --direct-mount-strict --allow-other contract. Drive9's
in-binary supervisor restarts an unhealthy FUSE worker and performs bounded
TERM/KILL escalation and mount cleanup. Its 30-second stop timeout leaves
cleanup headroom inside the Pod's 60-second termination grace period.
The sidecar fallback exposes the same mount TTL behavior through
DRIVE9_ATTR_TTL, DRIVE9_ENTRY_TTL, and DRIVE9_DIR_TTL. Each defaults to
30s and maps to the corresponding drive9 mount flag.
Set DRIVE9_PERF_ENABLED to "true" to enable drive9 mount --perf-dir in the
sidecar fallback. The path is fixed to /perf; use a Kubernetes volume mount to
choose where /perf is stored. The example manifest mounts it from
/var/lib/drive9-sidecar/demo/perf.
The sidecar fallback also supports explicit mount-option and tuning environment
variables. Compatibility defaults to false; policy and tuning values default
to empty:
| Environment variable | drive9 mount flag |
|---|---|
DRIVE9_MOUNT_GVISOR_COMPAT |
--gvisor-compat |
DRIVE9_MOUNT_LOCAL_ONLY_PATTERNS |
Repeated --local-only rules |
DRIVE9_MOUNT_REMOTE_ONLY_PATTERNS |
Repeated --remote-only rules |
DRIVE9_MOUNT_APPEND_LOG_PATTERNS |
Repeated --append-log rules |
DRIVE9_READDIR_PREFETCH |
--readdir-prefetch |
DRIVE9_READDIR_PREFETCH_MAX_FILES |
--readdir-prefetch-max-files |
DRIVE9_READDIR_PREFETCH_MAX_FILE_BYTES |
--readdir-prefetch-max-file-bytes |
DRIVE9_READDIR_PREFETCH_MAX_BYTES |
--readdir-prefetch-max-bytes |
DRIVE9_WRITEBACK_BATCH_WINDOW |
--writeback-batch-window |
Example:
env:
- name: DRIVE9_MOUNT_GVISOR_COMPAT
value: "true"
- name: DRIVE9_MOUNT_REMOTE_ONLY_PATTERNS
value: |-
**/tmp/**
**/.tmp/**
- name: DRIVE9_PERF_ENABLED
value: "true"
- name: DRIVE9_READDIR_PREFETCH
value: "true"
- name: DRIVE9_READDIR_PREFETCH_MAX_FILES
value: "64"
- name: DRIVE9_READDIR_PREFETCH_MAX_FILE_BYTES
value: "50000"
- name: DRIVE9_READDIR_PREFETCH_MAX_BYTES
value: "4194304"
- name: DRIVE9_WRITEBACK_BATCH_WINDOW
value: 20msCreate the sidecar Secret in the target namespace before applying the fallback deployment:
kubectl create secret generic drive9-sidecar-secret \
--from-literal=server=https://api.drive9.ai \
--from-literal=apiKey=drive9_api_key_redacted \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k deploy/sidecarThe sidecar Secret example lives under deploy/examples/sidecar/ and is
intentionally not part of the sidecar kustomization.
When perfEnabled is "true", the CSI node plugin writes drive9 mount
profiling output under:
/var/lib/drive9-csi/perf/<volume-id>/
CSI does not upload or delete this data automatically. To send it to Drive9
support, use the drive9 CLI already present in the node plugin container with
a short-lived support upload token provided by Drive9 support. Do not use the
workload Drive9 API key for this upload.
Find the node plugin pod on the target node:
kubectl -n drive9-csi get pods -l app=drive9-csi-node -o wideRun the helper inside the drive9-csi container:
kubectl -n drive9-csi exec -it <drive9-csi-node-pod> -c drive9-csi -- \
drive9-csi-upload-perf --case-id <case-id>The helper prompts for the support upload token without echoing it, creates
/var/lib/drive9-csi/perf/<case-id>.tgz, uploads it to:
:/support-inbox/<case-id>/<node-name>/<volume-id>.tgz
and verifies the uploaded bundle with drive9 fs stat. If more than one perf
volume directory exists, rerun with --volume-id <volume-id>.
For non-interactive use, pass the token on stdin:
printf '%s' "${DRIVE9_SUPPORT_UPLOAD_TOKEN}" | \
kubectl -n drive9-csi exec -i <drive9-csi-node-pod> -c drive9-csi -- \
drive9-csi-upload-perf --case-id <case-id> --token-stdinDo not pass the support upload token as a command-line argument. The helper uses the token only for the upload and verification commands.
If the helper is unavailable, create a bundle manually inside the drive9-csi
container:
kubectl -n drive9-csi exec <drive9-csi-node-pod> -c drive9-csi -- \
tar czf /var/lib/drive9-csi/perf/<case-id>.tgz \
-C /var/lib/drive9-csi/perf <volume-id>Then upload and verify the bundle with the support-owned Drive9 token:
kubectl -n drive9-csi exec <drive9-csi-node-pod> -c drive9-csi -- \
env DRIVE9_SERVER=https://api.drive9.ai \
DRIVE9_API_KEY="${DRIVE9_SUPPORT_UPLOAD_TOKEN}" \
drive9 fs cp /var/lib/drive9-csi/perf/<case-id>.tgz \
:/support-inbox/<case-id>/<node-name>/<volume-id>.tgz \
--tag case=<case-id> \
--tag source=k8s-csi \
--description "Drive9 CSI perf bundle"kubectl -n drive9-csi exec <drive9-csi-node-pod> -c drive9-csi -- \
env DRIVE9_SERVER=https://api.drive9.ai \
DRIVE9_API_KEY="${DRIVE9_SUPPORT_UPLOAD_TOKEN}" \
drive9 fs stat :/support-inbox/<case-id>/<node-name>/<volume-id>.tgzFor sidecar fallback, perf output is written to /perf in the mounter container
when DRIVE9_PERF_ENABLED is "true". Bundle and upload the mounted /perf
directory with the same support upload token flow.
After support confirms receipt, remove the local bundle and perf directory manually:
kubectl -n drive9-csi exec <drive9-csi-node-pod> -c drive9-csi -- \
rm -rf /var/lib/drive9-csi/perf/<volume-id> \
/var/lib/drive9-csi/perf/<case-id>.tgz- Linux only.
ReadWriteOnceby default.SINGLE_NODE_MULTI_WRITERsupported for same-node multi-pod access.ReadWriteManydoes not addprofileordurabilitydefaults or validation.- One Drive9 principal per mounted volume lifecycle.
- No volume expansion or quota enforcement.
- RWX does not provide distributed file locks, same-file merge, byte-range write ordering, or a strong POSIX/database-workload guarantee.
- Cross-node visibility may require bounded polling.
drive9 mountmust be present in the driver image.
make test # production Go unit tests
make build-check # binary/ELF acceptance
make manifest-check # deployment manifest contracts
make script-check # shell helper behavior
make e2e-check # non-mutating E2E safety checks
make check # complete local validationStatic checks are not enough for customer distribution. Before calling a build production-safe, run a real Kubernetes cluster against a real Drive9 server and a pre-provisioned Secret containing its API key:
export DRIVE9_CSI_E2E_CONTEXT=dev-dat9-eks-ap-southeast-1
export DRIVE9_CSI_E2E_DRIVER_NAMESPACE=drive9-csi
export DRIVE9_CSI_E2E_SECRET_NAME=drive9-csi-secret-flags-test
export DRIVE9_CSI_E2E_CONFIRM=1
e2e/prepare.sh --image-tag drive9-a53e497-csi-d91bfe3
e2e/basic-lifecycle.sh
e2e/mount-survival.sh
e2e/multi-node-rwx.shprepare.sh idempotently creates or updates the persistent Driver environment
with the completed publishing workflow's bare trace tag. Cases can then run
repeatedly against that environment. They reuse a pre-provisioned namespace and
Secret, create and clean only their own StorageClass, VolumeAttributesClass,
PVC, and Pod resources, and never delete the prepared Driver.
All four scripts require explicit context and Driver namespace values. The current kubectl context is never used implicitly, and production-like context names are rejected.
basic-lifecycle.sh covers provisioning, mount/write/read, readonly Pod access
where reads succeed and writes are denied, workload Pod remount, same-node
multi-Pod access, one-Pod multi-PVC behavior, unpublish, unstage, and deletion.
mount-survival.sh keeps workload I/O active while
replacing the matching CSI node Pod and verifies the host mount identity does
not change. multi-node-rwx.sh requires two eligible nodes, schedules one
writer on each, verifies cross-node visibility through one RWX PVC, and checks
that deleting one writer does not interrupt the other. See e2e/README.md and
e2e/AGENTS.md for the complete safety and execution contract.
Each PVC maps to exactly one Drive9 workspace via its drive9.ai/secret-name
annotation. Different PVCs can point to different Secrets (and therefore
different API keys / workspaces) without requiring multiple StorageClasses.
To mount multiple workspaces in one namespace, create one Secret + PVC pair per workspace:
# Secret for workspace A
kubectl -n myapp create secret generic secret-workspace-a \
--from-literal=server=https://api.drive9.ai \
--from-literal=apiKey=<api-key-for-workspace-a>
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: workspace-a
namespace: myapp
annotations:
drive9.ai/secret-name: secret-workspace-a
spec:
accessModes: [ReadWriteOnce]
storageClassName: drive9-rwo
resources:
requests:
storage: 1Gi
EOF
# Secret for workspace B
kubectl -n myapp create secret generic secret-workspace-b \
--from-literal=server=https://api.drive9.ai \
--from-literal=apiKey=<api-key-for-workspace-b>
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: workspace-b
namespace: myapp
annotations:
drive9.ai/secret-name: secret-workspace-b
spec:
accessModes: [ReadWriteOnce]
storageClassName: drive9-rwo
resources:
requests:
storage: 1Gi
EOFMultiple PVCs can also share the same Secret if they use the same API key.
A single pod can mount both PVCs:
containers:
- name: app
volumeMounts:
- name: ws-a
mountPath: /workspace-a
- name: ws-b
mountPath: /workspace-b
volumes:
- name: ws-a
persistentVolumeClaim:
claimName: workspace-a
- name: ws-b
persistentVolumeClaim:
claimName: workspace-bPVC stuck in Pending with failed to provision volume or
missing required annotation:
The driver reads the drive9.ai/secret-name annotation from the PVC. If your
PVC does not have this annotation, CreateVolume will reject it. Add the
annotation:
metadata:
annotations:
drive9.ai/secret-name: my-drive9-secretThen make sure the named Secret exists in the same namespace:
kubectl -n <namespace> create secret generic my-drive9-secret \
--from-literal=server=https://api.drive9.ai \
--from-literal=apiKey=<your-api-key>Multiple PVCs sharing the same API key:
Multiple PVCs can reference the same Secret by name. Just set the same
drive9.ai/secret-name annotation value on each PVC. No per-PVC Secret naming
convention is required.
The image package must be public before customers can pull it without a GitHub token. After the first successful publish, open:
https://github.com/orgs/drive9-ai/packages/container/package/drive9-csi
Then use package settings to change the package visibility to public. Verify anonymous access against the published trace tag or digest before distributing the release manifests.