1- import type { ApiException , V1Job } from "@kubernetes/client-node" ;
1+ import type {
2+ ApiException ,
3+ V1Job ,
4+ V1PodAffinity ,
5+ } from "@kubernetes/client-node" ;
26import crypto , { randomBytes } from "node:crypto" ;
37import { setTimeout } from "node:timers/promises" ;
48import type { AppRepo } from "../db/repo/app.ts" ;
@@ -124,6 +128,45 @@ export class FileBrowserService {
124128 }
125129 }
126130
131+ // In RWO mode, the file browser pod needs to be on the same node as the user's pod (since it already has the volume mounted).
132+ // It seems like K8s isn't smart enough to do this automatically while scheduling, so we need to explicitly request that the
133+ // file browser pod is scheduled on the same node as the user's pod.
134+ const affinityRequired = volume . spec . accessModes ?. includes ( "ReadWriteOnce" ) ;
135+
136+ let affinity : V1PodAffinity = { } ;
137+
138+ if ( affinityRequired ) {
139+ const pods = await this . kubernetesService . listNamespacedPod ( {
140+ namespace,
141+ labelSelector : "anvilops.rcac.purdue.edu/deployment-id" ,
142+ } ) ;
143+
144+ const matchingPod = pods . items . find ( ( pod ) =>
145+ pod . spec . volumes . some (
146+ ( it ) => it . persistentVolumeClaim ?. claimName === volumeClaimName ,
147+ ) ,
148+ ) ;
149+
150+ if ( matchingPod ) {
151+ // This is the pod we're looking for, since it mounts the volume we want to mount in our new file browser pod
152+ affinity = {
153+ requiredDuringSchedulingIgnoredDuringExecution : [
154+ {
155+ topologyKey : "kubernetes.io/hostname" ,
156+ labelSelector : {
157+ matchLabels : matchingPod . metadata . labels ,
158+ } ,
159+ } ,
160+ ] ,
161+ } ;
162+ } else {
163+ logger . warn (
164+ { namespace, volumeName : volume . spec . volumeName } ,
165+ "Affinity required for file browser pod, but target pod not found" ,
166+ ) ;
167+ }
168+ }
169+
127170 let job : V1Job ;
128171 try {
129172 job = await this . kubernetesService . createNamespacedJob ( {
@@ -138,6 +181,7 @@ export class FileBrowserService {
138181 activeDeadlineSeconds : 30 * 60 , // Kill after 30 minutes
139182 template : {
140183 spec : {
184+ affinity : { podAffinity : affinity } ,
141185 containers : [
142186 {
143187 name : "file-browser" ,
0 commit comments