Skip to content

Commit 8abcc14

Browse files
committed
fix: add driver-ops and docs warning
1 parent 95dd6a7 commit 8abcc14

10 files changed

Lines changed: 188 additions & 31 deletions

docker/common-services.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ services:
180180
FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1
181181
FIFTYONE_PLUGINS_DIR: /opt/plugins
182182
FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379}
183+
# Flush stdout per line so `docker logs` shows registration/heartbeat
184+
# output in near-real-time instead of block-buffered chunks.
185+
PYTHONUNBUFFERED: "1"
183186
TELEMETRY_SOCKET: /tmp/telemetry/agent.sock
184187
restart: always
185188
volumes:

docker/docs/upgrading.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [From FiftyOne Enterprise Version 2.0.0 and Later](#from-fiftyone-enterprise-version-200-and-later)
2222
- [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars)
2323
- [Host Requirements](#host-requirements)
24+
- [Volume ownership (upgrading from v2.18 or earlier)](#volume-ownership-upgrading-from-v218-or-earlier)
2425
- [Opting out of Telemetry](#opting-out-of-telemetry)
2526
- [Scaling delegated-operator workers](#scaling-delegated-operator-workers)
2627
- [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes)
@@ -149,6 +150,59 @@ Use an existing Redis instance instead of the bundled one by setting
149150
(e.g. `redis://my-managed-redis.example.com:6379`) and scaling
150151
`telemetry-redis` to `replicas: 0` as below.
151152

153+
##### Volume ownership (upgrading from v2.18 or earlier)
154+
155+
The telemetry sidecars and workload containers now run as UID/GID 1000
156+
(previously root). Fresh deploys are unaffected — the
157+
`telemetry-socket{,-2,-3,-gpu}` named volumes are declared with
158+
`driver_opts: type: tmpfs, o: "uid=1000,gid=1000,mode=0775"`, so Docker
159+
creates them already owned by UID 1000.
160+
161+
Existing named volumes from a v2.18-or-earlier deploy retain their
162+
original `root:root` ownership across `up`/`down` cycles. Once the
163+
sidecar image flips to UID 1000, the first restart crash-loops with
164+
`PermissionError: [Errno 13]` when the sidecar tries to bind
165+
`agent.sock`. Pick one of the two paths below to remediate.
166+
167+
**Option 1 — Drop and recreate all project volumes (simplest):**
168+
169+
```shell
170+
docker compose -p <project> -f compose.yaml -f compose.delegated-operators.yaml down --volumes
171+
docker compose -p <project> -f compose.yaml -f compose.delegated-operators.yaml up -d
172+
```
173+
174+
`--volumes` only removes volumes declared in this project's compose
175+
files; other Docker projects on the host are untouched.
176+
`telemetry-redis-data` is also wiped — telemetry archives in MongoDB
177+
are unaffected, but the un-archived ~10 minute dashboard backscroll is
178+
lost (same trade-off as `telemetry.redis.persistence.enabled: false`
179+
in the helm chart).
180+
181+
**Option 2 — Selective wipe of only the socket volumes (preserves
182+
redis backscroll and plugins):**
183+
184+
```shell
185+
docker compose -p <project> -f compose.yaml -f compose.delegated-operators.yaml down
186+
docker volume rm <project>_telemetry-socket <project>_telemetry-socket-2 <project>_telemetry-socket-3
187+
docker compose -p <project> -f compose.yaml -f compose.delegated-operators.yaml up -d
188+
```
189+
190+
Recreated socket volumes come up 1000-owned via `driver_opts`. Persistent
191+
volumes (`plugins-vol`, `telemetry-redis-data`) are preserved as-is —
192+
the workload Dockerfile pre-chowns `/opt/plugins` and the upstream redis
193+
image owns its `/data`, so existing data continues to be readable by the
194+
new UID 1000 / UID 999 containers.
195+
196+
If you maintain custom overrides that declare additional named volumes
197+
that non-root workloads write to, chown each manually before `up -d`:
198+
199+
```shell
200+
docker run --rm -v <project>_<volume>:/v alpine chown 1000:1000 /v
201+
```
202+
203+
Use UID `999` (not `1000`) when chowning `telemetry-redis-data` — the
204+
upstream `redis:7-alpine` image runs as the built-in `redis` user.
205+
152206
##### Opting out of Telemetry
153207

154208
Telemetry is enabled by default.

docker/internal-auth/compose.delegated-operators.gpu.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ services:
9191
condition: service_started
9292
restart: always
9393

94+
# Tmpfs-backed named volume with uid/gid set at mount time so the
95+
# non-root sidecar can bind agent.sock.
9496
volumes:
9597
plugins-vol:
9698
telemetry-socket-gpu:
99+
driver_opts:
100+
type: tmpfs
101+
device: tmpfs
102+
o: "uid=1000,gid=1000,mode=0755"

docker/internal-auth/compose.delegated-operators.yaml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@
2929
# invisible to the telemetry sidecar. The cap of 3 is a deliberate
3030
# trade-off — for higher worker counts prefer the helm chart. If
3131
# docker compose is required, the slot-2/3 blocks below are templates:
32-
# duplicate them as `teams-do-4` / `teams-do-4-telemetry` etc.,
33-
# bumping the service name, `pid`, `POD_NAME`, `-n teams-do-N`, and
34-
# `telemetry-socket-N` volume on each copy. See
35-
# docker/docs/configuring-telemetry.md for details.
32+
# duplicate them as `teams-do-4` / `teams-do-4-telemetry`, bumping the
33+
# service name, `pid`, `POD_NAME`, `-n teams-do-N`, and
34+
# `telemetry-socket-N` volume on each copy, then add
35+
# `telemetry-socket-N: *telemetry-socket-spec` to the volumes block.
36+
# See docker/docs/configuring-telemetry.md for details.
3637
#
3738
# For Proxy Server instructions please see
3839
# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies
@@ -186,8 +187,15 @@ services:
186187
condition: service_started
187188
restart: always
188189

190+
# Tmpfs-backed named volumes with uid/gid set at mount time so the
191+
# non-root sidecar can bind agent.sock without any init container.
192+
# Add new slots with `telemetry-socket-N: *telemetry-socket-spec`.
189193
volumes:
190194
plugins-vol:
191-
telemetry-socket:
192-
telemetry-socket-2:
193-
telemetry-socket-3:
195+
telemetry-socket: &telemetry-socket-spec
196+
driver_opts:
197+
type: tmpfs
198+
device: tmpfs
199+
o: "uid=1000,gid=1000,mode=0755"
200+
telemetry-socket-2: *telemetry-socket-spec
201+
telemetry-socket-3: *telemetry-socket-spec

docker/legacy-auth/compose.delegated-operators.gpu.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ services:
9191
condition: service_started
9292
restart: always
9393

94+
# Tmpfs-backed named volume with uid/gid set at mount time so the
95+
# non-root sidecar can bind agent.sock.
9496
volumes:
9597
plugins-vol:
9698
telemetry-socket-gpu:
99+
driver_opts:
100+
type: tmpfs
101+
device: tmpfs
102+
o: "uid=1000,gid=1000,mode=0755"

docker/legacy-auth/compose.delegated-operators.yaml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@
2929
# invisible to the telemetry sidecar. The cap of 3 is a deliberate
3030
# trade-off — for higher worker counts prefer the helm chart. If
3131
# docker compose is required, the slot-2/3 blocks below are templates:
32-
# duplicate them as `teams-do-4` / `teams-do-4-telemetry` etc.,
33-
# bumping the service name, `pid`, `POD_NAME`, `-n teams-do-N`, and
34-
# `telemetry-socket-N` volume on each copy. See
35-
# docker/docs/configuring-telemetry.md for details.
32+
# duplicate them as `teams-do-4` / `teams-do-4-telemetry`, bumping the
33+
# service name, `pid`, `POD_NAME`, `-n teams-do-N`, and
34+
# `telemetry-socket-N` volume on each copy, then add
35+
# `telemetry-socket-N: *telemetry-socket-spec` to the volumes block.
36+
# See docker/docs/configuring-telemetry.md for details.
3637
#
3738
# For Proxy Server instructions please see
3839
# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies
@@ -188,8 +189,15 @@ services:
188189
condition: service_started
189190
restart: always
190191

192+
# Tmpfs-backed named volumes with uid/gid set at mount time so the
193+
# non-root sidecar can bind agent.sock without any init container.
194+
# Add new slots with `telemetry-socket-N: *telemetry-socket-spec`.
191195
volumes:
192196
plugins-vol:
193-
telemetry-socket:
194-
telemetry-socket-2:
195-
telemetry-socket-3:
197+
telemetry-socket: &telemetry-socket-spec
198+
driver_opts:
199+
type: tmpfs
200+
device: tmpfs
201+
o: "uid=1000,gid=1000,mode=0755"
202+
telemetry-socket-2: *telemetry-socket-spec
203+
telemetry-socket-3: *telemetry-socket-spec

helm/docs/upgrading.md

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [From FiftyOne Enterprise Version 2.0.0 or Higher](#from-fiftyone-enterprise-version-200-or-higher)
2222
- [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars)
2323
- [Cluster Requirements](#cluster-requirements)
24+
- [Volume ownership (upgrading from v2.18 or earlier)](#volume-ownership-upgrading-from-v218-or-earlier)
2425
- [Opting out of Telemetry](#opting-out-of-telemetry)
2526
- [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes)
2627
- [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes)
@@ -169,17 +170,16 @@ opt into a `PersistentVolumeClaim` with
169170
170171
1. **`shareProcessNamespace: true`** is set on all four workloads so
171172
the sidecar can read `/proc/<pid>/fd/1` of the target container.
172-
1. **The teams-do sidecar runs as root with `SYS_PTRACE`** (required for
173-
`py-spy` and `/proc` access).
174-
Clusters that enforce
175-
[Pod Security Admission][psa]
176-
`restricted`, or admission policies (OPA/Gatekeeper, Kyverno) that
177-
block `runAsUser: 0` or capability adds, will reject these pods at
178-
admission.
179-
On such clusters, either:
180-
- allow the chart's `namespace.name` namespace at PSA `baseline`
181-
(or relax the relevant admission policy), or
182-
- disable telemetry with `telemetry.enabled: false`.
173+
1. **Sidecars run as UID/GID 1000** (matching the workload image's
174+
user) with `cap_drop: ALL`. The delegated-operator sidecar
175+
additionally has `SYS_PTRACE` added (required for `py-spy` stack
176+
sampling); the other three sidecars run with no capabilities. This
177+
posture is compatible with
178+
[Pod Security Admission][psa] `restricted` out of the box.
179+
Clusters enforcing additional admission policies (OPA/Gatekeeper,
180+
Kyverno) that explicitly block `SYS_PTRACE` on the
181+
delegated-operator namespace can either allow that single capability
182+
or disable telemetry with `telemetry.enabled: false`.
183183

184184
**External Redis:**
185185
To point at a managed Redis (ElastiCache, MemoryStore, an existing
@@ -195,6 +195,62 @@ telemetry:
195195
The chart skips the bundled Redis' `Deployment` and `Service` and wires
196196
every consumer at this URL.
197197
198+
##### Volume ownership (upgrading from v2.18 or earlier)
199+
200+
In v2.19+ all workloads and their telemetry sidecars run as UID/GID
201+
1000 (UID/GID 999 for the bundled Redis, matching the upstream
202+
`redis:7-alpine` image). The chart sets `podSecurityContext.fsGroup`
203+
on each pod so Kubernetes group-chowns the mounted volumes at attach
204+
time. For most CSI drivers this is automatic on chart upgrade — no
205+
operator action required.
206+
207+
**Two edge cases need manual remediation:**
208+
209+
1. **CSI drivers that ignore `fsGroup`** (notably NFS, some Filestore
210+
configurations, certain `ReadWriteMany` volumes): the recursive
211+
chown is skipped, leaving pre-existing data owned by whatever UID
212+
the prior root-running containers wrote it as. The new UID-1000
213+
container can't write back. Symptom: workload pod crash-loops with
214+
`PermissionError: [Errno 13]` against `/opt/plugins` or
215+
`/data` shortly after a chart upgrade.
216+
217+
Remediate by chowning the volume contents once, from a debug pod
218+
with elevated privileges:
219+
220+
```shell
221+
kubectl run -n <namespace> chown-fix --rm -i --restart=Never \
222+
--image=alpine \
223+
--overrides='{"spec":{"containers":[{"name":"chown-fix","image":"alpine","command":["chown","-R","1000:1000","/v"],"volumeMounts":[{"name":"v","mountPath":"/v"}],"securityContext":{"runAsUser":0}}],"volumes":[{"name":"v","persistentVolumeClaim":{"claimName":"<your-pvc>"}}]}}'
224+
```
225+
226+
Use UID `999` (not `1000`) for the Redis data PVC if you opted into
227+
`telemetry.redis.persistence.enabled: true`.
228+
229+
2. **Very large pre-populated PVs** (e.g. multi-GB `plugins-vol`): the
230+
recursive chown on first attach can stall pod startup for several
231+
minutes — readiness probes may fail long enough for the rollout to
232+
look stuck before recovering. Mitigate by setting:
233+
234+
```yaml
235+
apiSettings:
236+
podSecurityContext:
237+
fsGroupChangePolicy: OnRootMismatch
238+
fiftyoneAppSettings:
239+
podSecurityContext:
240+
fsGroupChangePolicy: OnRootMismatch
241+
pluginsSettings:
242+
podSecurityContext:
243+
fsGroupChangePolicy: OnRootMismatch
244+
```
245+
246+
With `OnRootMismatch` the recursive walk is skipped when the
247+
volume's root directory already matches the target GID — effectively
248+
a one-time cost rather than per-rollout. Requires Kubernetes 1.23+.
249+
250+
Fresh installs are unaffected: empty PVs come up correctly via
251+
`fsGroup` regardless of CSI driver, and there is no pre-existing
252+
root-owned data to chown.
253+
198254
##### Opting out of Telemetry
199255
200256
Telemetry is enabled by default.

tests/unit/compose/common_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
package unit
55

6+
import "github.com/compose-spec/compose-go/v2/types"
7+
68
const (
79
envFixtureFilePath = "../../fixtures/docker/.env"
810
)
11+
12+
var telemetrySocketDriverOpts = types.Options{
13+
"device": "tmpfs",
14+
"o": "uid=1000,gid=1000,mode=0755",
15+
"type": "tmpfs",
16+
}

tests/unit/compose/docker-compose-internal-auth_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() {
614614
"FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1",
615615
"FIFTYONE_PLUGINS_DIR=/opt/plugins",
616616
"FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379",
617+
"PYTHONUNBUFFERED=1",
617618
"TELEMETRY_SOCKET=/tmp/telemetry/agent.sock",
618619
},
619620
},
@@ -1111,17 +1112,20 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestVolumes() {
11111112
Name: "fiftyone-compose-test_telemetry-redis-data",
11121113
},
11131114
"telemetry-socket": {
1114-
Name: "fiftyone-compose-test_telemetry-socket",
1115+
Name: "fiftyone-compose-test_telemetry-socket",
1116+
DriverOpts: telemetrySocketDriverOpts,
11151117
},
11161118
// telemetry-socket-{2,3} are declared at the project level
11171119
// so they appear in project.Volumes regardless of which
11181120
// `do-N` profile is active. Slot 2/3 services only start
11191121
// when the matching profile is selected.
11201122
"telemetry-socket-2": {
1121-
Name: "fiftyone-compose-test_telemetry-socket-2",
1123+
Name: "fiftyone-compose-test_telemetry-socket-2",
1124+
DriverOpts: telemetrySocketDriverOpts,
11221125
},
11231126
"telemetry-socket-3": {
1124-
Name: "fiftyone-compose-test_telemetry-socket-3",
1127+
Name: "fiftyone-compose-test_telemetry-socket-3",
1128+
DriverOpts: telemetrySocketDriverOpts,
11251129
},
11261130
},
11271131
},

tests/unit/compose/docker-compose-legacy-auth_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() {
696696
"FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1",
697697
"FIFTYONE_PLUGINS_DIR=/opt/plugins",
698698
"FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379",
699+
"PYTHONUNBUFFERED=1",
699700
"TELEMETRY_SOCKET=/tmp/telemetry/agent.sock",
700701
},
701702
},
@@ -1198,17 +1199,20 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestVolumes() {
11981199
Name: "fiftyone-compose-test_telemetry-redis-data",
11991200
},
12001201
"telemetry-socket": {
1201-
Name: "fiftyone-compose-test_telemetry-socket",
1202+
Name: "fiftyone-compose-test_telemetry-socket",
1203+
DriverOpts: telemetrySocketDriverOpts,
12021204
},
12031205
// telemetry-socket-{2,3} are declared at the project level
12041206
// so they appear in project.Volumes regardless of which
12051207
// `do-N` profile is active. Slot 2/3 services only start
12061208
// when the matching profile is selected.
12071209
"telemetry-socket-2": {
1208-
Name: "fiftyone-compose-test_telemetry-socket-2",
1210+
Name: "fiftyone-compose-test_telemetry-socket-2",
1211+
DriverOpts: telemetrySocketDriverOpts,
12091212
},
12101213
"telemetry-socket-3": {
1211-
Name: "fiftyone-compose-test_telemetry-socket-3",
1214+
Name: "fiftyone-compose-test_telemetry-socket-3",
1215+
DriverOpts: telemetrySocketDriverOpts,
12121216
},
12131217
},
12141218
},

0 commit comments

Comments
 (0)