Reference

Configuration

Everything the driver reads: The S3A_* variables on its container, the volumeAttributes on a volume, and the rules for combining them.

Environment (driver container)#

VariableDefaultMeaning
CSI_ENDPOINTunix:///csi/csi.sockSocket the driver listens on. Unix domain sockets only; a tcp:// value is rejected at startup.
S3A_STATE_DIR/var/lib/kubelet/plugins/…/stateWhere per-volume state records and upload manifests live. Must be on a host path that outlives the driver container, or a restart re-uploads everything.
NODE_NAMEhostnameThe node id reported by NodeGetInfo. Set it from the downward API; the hostname fallback exists for local runs and logs a warning.
S3A_LOG_LEVELinfoGlobal log threshold: trace, debug, info, warn, error, off.
S3A_LOG_LEVELSunsetPer-logger overrides, comma-separated prefix=level (for example io.grpc=warn,de.trion.csi=debug). Longest matching prefix wins.
S3A_NODE_CONCURRENCY4Concurrent uploads across all volumes on the node. A file is never uploaded concurrently with itself.
S3A_RESCAN_SECONDS60Interval of the full rescan that backs up the inotify watcher.
S3A_LOG_FORMATtexttext for one human-readable line per record, json for one JSON object per line. JSON carries the full logger name and renders a throwable as fields, because a record that spans lines is not a JSON record.
S3A_METRICS_PORTunset (off)Serve Prometheus metrics at /metrics. Off by default: A node plugin running as root should not open a listening port because someone upgraded it. Not 9808, which the livenessprobe sidecar already holds.
S3A_WEBUI_PORTunset (off)Serve the read-only statistics page. Setting the port is what enables it; there is no separate switch.
S3A_WEBUI_RETENTION_DAYS7How much hourly history each node keeps for the statistics page. 1 to 90.
S3A_WEBUI_PEERSunset (this node only)Bare DNS name of a headless Service resolving to every driver pod, so any pod can render the whole cluster. Plain DNS rather than the Kubernetes API, which is what keeps the driver without permissions.
S3A_CONTROLLERfalseAlso serve the Controller service on the node socket. For csi-sanity; in a cluster the controller runs in its own pod.

volumeAttributes#

Set on the pod's inline CSI volume. Every attribute is validated at publish time: An unknown name, a malformed value or a contradictory combination fails the pod with a message on the pod's events, rather than producing a bucket that quietly stays empty.

AttributeDefault (env override)Meaning
bucketrequired (S3A_BUCKET)Target bucket, from either place.
prefix{namespace}/{podName}/ (S3A_PREFIX)Key prefix template. See placeholders below.
endpointAWS (S3A_ENDPOINT)S3 endpoint override for Ceph, SeaweedFS and the like.
regionus-east-1 (S3A_REGION)Region.
pathStylefalse (S3A_PATH_STYLE)Force path-style addressing; most non-AWS stores need it.
appendOnlyfalse (S3A_APPEND_ONLY)Periodically sync growing files instead of waiting for them to go quiet.
deleteAfterUploadfalse (S3A_DELETE_AFTER_UPLOAD)Delete the local file after a successful upload. Mutually exclusive with appendOnly.
quiescenceSeconds30 (S3A_QUIESCENCE_SECONDS)How long a file must be unmodified before it is uploaded.
appendSyncSeconds60 (S3A_APPEND_SYNC_SECONDS)Sync interval in appendOnly mode.
appendStrategyrewrite (S3A_APPEND_STRATEGY)rewrite re-uploads the whole file per sync; segments uploads only new tail bytes and compacts at pod end.
segmentTargetBytes8388608 (S3A_SEGMENT_TARGET_BYTES)segments: Flush the pending tail at this size.
include / excludeall / none (S3A_INCLUDE, S3A_EXCLUDE)Comma-separated glob filters on paths inside the volume.
storageClassbucket default (S3A_STORAGE_CLASS)For example STANDARD_IA.
sweepTimeoutSecondsunlimited (S3A_SWEEP_TIMEOUT_SECONDS)Bound for the final sweep at pod termination.
presignEndpointunset (S3A_PRESIGN_ENDPOINT)Signer service URL; switches the volume to presigned-upload mode.
presignMultipartfalse (S3A_PRESIGN_MULTIPART)Presigned mode: Upload large files in parts, lifting the 5 GiB single-PUT cap. Needs a signer that signs the multipart operations, and is opt-in because a putObject-only signer answers them plausibly and wrongly.
compressionnone (S3A_COMPRESSION)gzip compresses each object and appends .gz to the key. zstd is refused with a reason: It needs a per-architecture native library, which a static binary does not have.
serverSideEncryptionnone (S3A_SERVER_SIDE_ENCRYPTION)AES256 or aws:kms, with sseKmsKeyId. A KMS key without KMS mode is a hard error rather than a silently ignored setting.
sseKmsKeyIdnone (S3A_SSE_KMS_KEY_ID)The KMS key id or ARN to encrypt with. Only meaningful alongside serverSideEncryption: aws:kms; setting it without that mode is a hard error rather than a silently ignored value.
sizeLimitBytesunlimited (S3A_SIZE_LIMIT_BYTES)Reported as the volume capacity by NodeGetVolumeStats, and an overrun is logged. Not enforced by deletion: The driver will not delete a workload's files to stay under a limit.
appendUploadauto (S3A_APPEND_UPLOAD)How a growing object is extended: copy uses UploadPartCopy, offset uses x-amz-write-offset-bytes and needs an S3 Express directory bucket, off re-uploads the whole file. Only in appendOnly mode.
durablefalse (S3A_DURABLE)The volume outlives its pod: Restored from the archive at publish, archived and mirrored at unpublish. PVC-declared volumes only, and credential mode only.
restoreTimeoutSecondsunlimited (S3A_RESTORE_TIMEOUT_SECONDS)Bounds how long a pod start may block on a durable restore. Past it the pod gets what arrived and the driver logs that the volume is incomplete.

appendSyncSeconds, appendStrategy and segmentTargetBytes only mean anything with appendOnly: "true", so setting one on a volume without it is rejected. As a driver-global S3A_* default they are fine: They simply apply to the volumes that do use appendOnly.

Prefix placeholders#

prefix may contain {namespace}, {podName}, {podUid}, {serviceAccount}, {nodeName}, {volumeId} and {date} (UTC yyyy-MM-dd at upload time). Anything else is an error rather than a literal:

a typo, caught at publish time
$ kubectl describe pod heapdump-example
Events:
  Type     Reason       Message
  ----     ------       -------
  Warning  FailedMount  MountVolume.SetUp failed for volume "dumps" :
           rpc error: code = InvalidArgument desc = volumeAttributes: unknown placeholder
           "{namesapce}" in prefix; known placeholders are {namespace}, {podName}, {podUid},
           {serviceAccount}, {nodeName}, {volumeId}, {date}

Keys are capped at S3's 1024 UTF-8 bytes. A file whose key would exceed that is skipped and logged, never silently truncated, because a truncated key would overwrite an unrelated object.

Choosing an append strategy#

rewrite re-uploads the whole file every sync: The object always equals the file and only PutObject is needed, but transfer grows quadratically with file size. segments uploads only new tail bytes as immutable objects under {key}.parts/: Transfer is linear and data reaches S3 promptly, at the cost of a mid-flight reader needing to concatenate segments in sequence order.

Both end up as one clean object at the canonical key after the pod terminates, because the sweep compacts. Rule of thumb: rewrite for files that stay small, segments for a log that runs for hours.

rewritesegments
Transfer for an n-byte fileO(n²) across syncsO(n)
S3 operationsPutObject onlyPutObject plus DeleteObject at compaction
Object during the runalways a complete filesegments under {key}.parts/
Object after terminationcomplete filecomplete file (compacted)
Reading mid-flightjust read the keyconcatenate a generation's segments in seq order

Before compaction the data lives at {key}.parts/{generation}/{seq}-{startOffset}. A new generation appears when the file is truncated or rotated.