Skip to content

Conversation

harshad16
Copy link

@harshad16 harshad16 commented Jul 29, 2025

related: #37

This PR adds spec.podTemplate.ports[] to workspaceKind CRD, which lets users include ports httpproxy setting for their workspaces.

WorkspaceKind CRD changes

spec:
  podTemplate:
    ports:
      - id: "jupyterlab"
        protocol: HTTP
        displayname: "Jupyterlab"
        httpProxy: {}

Following changes are included:

  • moved protocol from imageconfig.spec.ports to podtemplates.ports
  • included the podtemplates.ports with defaultdisplayname
  • add validation webhook for podtemplate.ports
  • update the sample workspacekind with ports reference
  • referencing same id for portid in imageconfig and podtemplate.ports

These changes would be consider while setting the routing for proper traffic controller/routing to the pods.

@thesuperzapper
Copy link
Member

/ok-to-test

Copy link
Contributor

@andyatmiami andyatmiami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// ports that the container listens on
// +kubebuilder:validation:Optional
HTTPProxy *HTTPProxy `json:"httpProxy,omitempty"`
Ports []WorkspaceKindPort `json:"ports,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity - what is the practical purpose of defining a WorkspaceKind with no Ports ? Why would someone want to do that ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great question , we should rethink this 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should require at least one port, just so that the frontend does not have to deal with the possibility of a workspace with no ports.

+kubebuilder:validation:MinItems:=1

@harshad16 harshad16 force-pushed the port-in-wsk-podtemplate branch from 5170f6e to 438e485 Compare August 6, 2025 18:56
@google-oss-prow google-oss-prow bot added the area/backend area - related to backend components label Aug 6, 2025
@harshad16 harshad16 force-pushed the port-in-wsk-podtemplate branch from 438e485 to 3239dde Compare August 6, 2025 19:40
@andyatmiami
Copy link
Contributor

/ok-to-test

@andyatmiami
Copy link
Contributor

/lgtm

testing these changes on a cluster and was able to:

  • create a workspacekind (using samples/)
  • create a workspace referencing the workspacekind (using samples/)
  • view the YAML representation of workspacekind and see the ports: changes
 $ kubectl get workspacekinds.kubeflow.org/jupyterlab -o yaml
apiVersion: kubeflow.org/v1beta1
kind: WorkspaceKind
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"kubeflow.org/v1beta1","kind":"WorkspaceKind","metadata":{"annotations":{},"name":"jupyterlab"},"spec":{"podTemplate":{"containerSecurityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"runAsNonRoot":true},"culling":{"activityProbe":{"jupyter":{"lastActivity":true}},"enabled":true,"maxInactiveSeconds":86400},"extraEnv":[{"name":"NB_PREFIX","value":"{{ httpPathPrefix \"jupyterlab\" }}"}],"extraVolumeMounts":[{"mountPath":"/dev/shm","name":"dshm"}],"extraVolumes":[{"emptyDir":{"medium":"Memory"},"name":"dshm"}],"options":{"imageConfig":{"spawner":{"default":"jupyterlab_scipy_190"},"values":[{"id":"jupyterlab_scipy_180","redirect":{"message":{"level":"Info","text":"This update will change..."},"to":"jupyterlab_scipy_190"},"spawner":{"description":"JupyterLab, with SciPy Packages","displayName":"jupyter-scipy:v1.8.0","hidden":true,"labels":[{"key":"python_version","value":"3.11"}]},"spec":{"image":"ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0","imagePullPolicy":"IfNotPresent","ports":[{"displayName":"JupyterLab","id":"jupyterlab","port":8888,"protocol":"HTTP"}]}},{"id":"jupyterlab_scipy_190","spawner":{"description":"JupyterLab, with SciPy Packages","displayName":"jupyter-scipy:v1.9.0","labels":[{"key":"python_version","value":"3.11"}]},"spec":{"image":"ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0","imagePullPolicy":"IfNotPresent","ports":[{"displayName":"JupyterLab","id":"jupyterlab","port":8888,"protocol":"HTTP"}]}}]},"podConfig":{"spawner":{"default":"tiny_cpu"},"values":[{"id":"tiny_cpu","spawner":{"description":"Pod with 0.1 CPU, 128 Mb RAM","displayName":"Tiny CPU","labels":[{"key":"cpu","value":"100m"},{"key":"memory","value":"128Mi"}]},"spec":{"resources":{"requests":{"cpu":"100m","memory":"128Mi"}}}},{"id":"small_cpu","spawner":{"description":"Pod with 1 CPU, 2 GB RAM","displayName":"Small CPU","hidden":false,"labels":[{"key":"cpu","value":"1000m"},{"key":"memory","value":"2Gi"}]},"spec":{"affinity":{},"nodeSelector":{},"resources":{"requests":{"cpu":"1000m","memory":"2Gi"}},"tolerations":[]}},{"id":"big_gpu","spawner":{"description":"Pod with 4 CPU, 16 GB RAM, and 1 GPU","displayName":"Big GPU","hidden":false,"labels":[{"key":"cpu","value":"4000m"},{"key":"memory","value":"16Gi"},{"key":"gpu","value":"1"}]},"spec":{"affinity":{},"nodeSelector":{},"resources":{"limits":{"nvidia.com/gpu":1},"requests":{"cpu":"4000m","memory":"16Gi"}},"tolerations":[{"effect":"NoSchedule","key":"nvidia.com/gpu","operator":"Exists"}]}}]}},"podMetadata":{"annotations":{"my-workspace-kind-annotation":"my-value"},"labels":{"my-workspace-kind-label":"my-value"}},"ports":[{"httpProxy":{"removePathPrefix":false,"requestHeaders":{}},"portId":"jupyterlab"}],"probes":null,"securityContext":{"fsGroup":100},"serviceAccount":{"name":"default-editor"},"volumeMounts":{"home":"/home/jovyan"}},"spawner":{"deprecated":false,"deprecationMessage":"This WorkspaceKind will be removed on 20XX-XX-XX, please use another WorkspaceKind.","description":"A Workspace which runs JupyterLab in a Pod","displayName":"JupyterLab Notebook","hidden":false,"icon":{"url":"https://jupyter.org/assets/favicons/apple-touch-icon-152x152.png"},"logo":{"url":"https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg"}}}}
  creationTimestamp: "2025-08-06T21:02:15Z"
  finalizers:
  - notebooks.kubeflow.org/workspacekind-protection
  generation: 2
  name: jupyterlab
  resourceVersion: "31521"
  uid: 22488214-4fa7-410d-b41c-50d8624c5134
spec:
  podTemplate:
    containerSecurityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
      runAsNonRoot: true
    culling:
      activityProbe:
        jupyter:
          lastActivity: true
      enabled: true
      maxInactiveSeconds: 86400
    extraEnv:
    - name: NB_PREFIX
      value: '{{ httpPathPrefix "jupyterlab" }}'
    extraVolumeMounts:
    - mountPath: /dev/shm
      name: dshm
    extraVolumes:
    - emptyDir:
        medium: Memory
      name: dshm
    options:
      imageConfig:
        spawner:
          default: jupyterlab_scipy_190
        values:
        - id: jupyterlab_scipy_180
          redirect:
            message:
              level: Info
              text: This update will change...
            to: jupyterlab_scipy_190
          spawner:
            description: JupyterLab, with SciPy Packages
            displayName: jupyter-scipy:v1.8.0
            hidden: true
            labels:
            - key: python_version
              value: "3.11"
          spec:
            image: ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.8.0
            imagePullPolicy: IfNotPresent
            ports:
            - displayName: JupyterLab
              id: jupyterlab
              port: 8888
              protocol: HTTP
        - id: jupyterlab_scipy_190
          spawner:
            description: JupyterLab, with SciPy Packages
            displayName: jupyter-scipy:v1.9.0
            hidden: false
            labels:
            - key: python_version
              value: "3.11"
          spec:
            image: ghcr.io/kubeflow/kubeflow/notebook-servers/jupyter-scipy:v1.9.0
            imagePullPolicy: IfNotPresent
            ports:
            - displayName: JupyterLab
              id: jupyterlab
              port: 8888
              protocol: HTTP
      podConfig:
        spawner:
          default: tiny_cpu
        values:
        - id: tiny_cpu
          spawner:
            description: Pod with 0.1 CPU, 128 Mb RAM
            displayName: Tiny CPU
            hidden: false
            labels:
            - key: cpu
              value: 100m
            - key: memory
              value: 128Mi
          spec:
            resources:
              requests:
                cpu: 100m
                memory: 128Mi
        - id: small_cpu
          spawner:
            description: Pod with 1 CPU, 2 GB RAM
            displayName: Small CPU
            hidden: false
            labels:
            - key: cpu
              value: 1000m
            - key: memory
              value: 2Gi
          spec:
            affinity: {}
            resources:
              requests:
                cpu: "1"
                memory: 2Gi
        - id: big_gpu
          spawner:
            description: Pod with 4 CPU, 16 GB RAM, and 1 GPU
            displayName: Big GPU
            hidden: false
            labels:
            - key: cpu
              value: 4000m
            - key: memory
              value: 16Gi
            - key: gpu
              value: "1"
          spec:
            affinity: {}
            resources:
              limits:
                nvidia.com/gpu: "1"
              requests:
                cpu: "4"
                memory: 16Gi
            tolerations:
            - effect: NoSchedule
              key: nvidia.com/gpu
              operator: Exists
    podMetadata:
      annotations:
        my-workspace-kind-annotation: my-value
      labels:
        my-workspace-kind-label: my-value
    ports:
    - httpProxy:
        removePathPrefix: false
        requestHeaders: {}
      portId: jupyterlab
    securityContext:
      fsGroup: 100
    serviceAccount:
      name: default-editor
    volumeMounts:
      home: /home/jovyan
  spawner:
    deprecated: false
    deprecationMessage: This WorkspaceKind will be removed on 20XX-XX-XX, please use
      another WorkspaceKind.
    description: A Workspace which runs JupyterLab in a Pod
    displayName: JupyterLab Notebook
    hidden: false
    icon:
      url: https://jupyter.org/assets/favicons/apple-touch-icon-152x152.png
    logo:
      url: https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg
status:
  podTemplateOptions:
    imageConfig:
    - id: jupyterlab_scipy_180
      workspaces: 0
    - id: jupyterlab_scipy_190
      workspaces: 1
    podConfig:
    - id: tiny_cpu
      workspaces: 1
    - id: small_cpu
      workspaces: 0
    - id: big_gpu
      workspaces: 0
  workspaces: 1

@andyatmiami
Copy link
Contributor

/lgtm

Testing Methodology

  1. using a kind cluster created by a script I have

  2. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ gmake

  3. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ gmake install

  4. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ gmake test

  5. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ DOCKER_BUILDKIT=0 gmake docker-build IMG=quay.io/rh-ee-astonebe/kubeflow-notebooks-v2:controller-ports-harshad-review

  6. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ KIND_EXPERIMENTAL_PROVIDER=podman kind load docker-image quay.io/rh-ee-astonebe/kubeflow-notebooks-v2:controller-ports-harshad-review --name kubeflow

  7. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ kubectl -n workspace-controller-system edit deployment.apps/workspace-controller-controller-manager

    • replace container image with one built off this branch
  8. ➜ controller/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ kubectl -n workspace-controller-system get deployment.apps/workspace-controller-controller-manager -o yaml

    • verify desired image in place and deployment healthy
  9. ➜ workspaces/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ cd controller/config/samples/common

  10. ➜ common/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ kubectl -n kubeflow-default-profile apply -f workspace_home_pvc.yaml

  11. ➜ common/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ kubectl -n kubeflow-default-profile apply -f workspace_data_pvc.yaml

  12. ➜ common/ git:((HEAD detached at harshad/port-in-wsk-podtemplate)) $ cd ~/Development/Test/notebooks-v2/manifests/harshad-ports

    • directory with manifests:
      • /samples for valid workspacekind + workspace
      • modified samples/ workspacekind that has:
        • unreferenced ports element (i.e. no image using it)
        • invalid ports.id reference in imageConfig
  13. ➜ harshad-ports/ $ kubectl -n kubeflow-default-profile apply -f jupyterlab_v1beta1_workspacekind.yaml

  14. ➜ harshad-ports/ $ kubectl -n kubeflow-default-profile apply -f jupyterlab_v1beta1_workspace.yaml

  15. ➜ harshad-ports/ $ kubectl get all -n kubeflow-default-profile

    • confirm pod created from Workspace CR creation
  16. ➜ harshad-ports/ $ kubectl -n kubeflow-default-profile apply -f jupyterlab_v1beta1_workspacekind-bad.yaml

    • confirm rejected by API server
    The WorkspaceKind "jupyterlab" is invalid: spec.podTemplate.ports: Invalid value: "does-not-exist": port ID "does-not-exist" is referenced in imageConfig but not defined in ports
    

@google-oss-prow google-oss-prow bot added the lgtm label Sep 11, 2025
Copy link
Member

@thesuperzapper thesuperzapper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @harshad16 here are comments.

// ports that the container listens on
// +kubebuilder:validation:Optional
HTTPProxy *HTTPProxy `json:"httpProxy,omitempty"`
Ports []WorkspaceKindPort `json:"ports,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should require at least one port, just so that the frontend does not have to deal with the possibility of a workspace with no ports.

+kubebuilder:validation:MinItems:=1

@google-oss-prow google-oss-prow bot removed the lgtm label Sep 11, 2025
@harshad16 harshad16 force-pushed the port-in-wsk-podtemplate branch 3 times, most recently from 5d88a27 to 30d1fac Compare September 16, 2025 19:26
Copy link
Contributor

@andyatmiami andyatmiami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple (minor) last tweaks being suggested here. However, none of them are really functionality impacting - so I will note that I also tested this code against a kind cluster (making sure I updated both the controller AND backend deployments - and I can happily confirm:

  • creating workspace kinds + workspaces through kubectl works
  • creating workspace kinds + workspaces through frontend UI also work

Additionally confirmed common negative scenarios as well as ensuring "falling back" to the DefaultDisplayName is handled appropriately.

@harshad16 harshad16 force-pushed the port-in-wsk-podtemplate branch from 30d1fac to 689e7bc Compare September 25, 2025 06:31
 - moved protocal from imageconfig.spec.ports to podtemplates.ports
 - included the podtemplates.ports with defaultdisplayname
 - add validation webhook for podtemplate.ports
 - update the sample workspacekind with ports reference
 - referencing same id for portid in imageconfig and podtemplate.ports

Signed-off-by: Harshad Reddy Nalla <hnalla@redhat.com>
@harshad16 harshad16 force-pushed the port-in-wsk-podtemplate branch from 689e7bc to dffa3a7 Compare September 25, 2025 06:43
@andyatmiami
Copy link
Contributor

/lgtm

@google-oss-prow google-oss-prow bot added the lgtm label Sep 25, 2025
@google-oss-prow google-oss-prow bot removed the lgtm label Sep 25, 2025
- adjusted method buildService with details from ports

Signed-off-by: Harshad Reddy Nalla <hnalla@redhat.com>
Copy link
Member

@thesuperzapper thesuperzapper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small changes, then good to go.

Signed-off-by: Harshad Reddy Nalla <hnalla@redhat.com>
@thesuperzapper thesuperzapper changed the title feat(ws): add spec.podTemplate.ports[] to WorkspaceKind feat(ws): add spec.podTemplate.ports[] to WorkspaceKind Oct 2, 2025
Copy link
Member

@thesuperzapper thesuperzapper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harshad16 thanks for bearing with us while we went back and forward on these reviews, great to have this merged.

/approve
/lgtm

@google-oss-prow google-oss-prow bot added the lgtm label Oct 2, 2025
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: thesuperzapper

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow google-oss-prow bot merged commit 12c994f into kubeflow:notebooks-v2 Oct 2, 2025
15 checks passed
@github-project-automation github-project-automation bot moved this from Needs Triage to Done in Kubeflow Notebooks Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved area/backend area - related to backend components area/controller area - related to controller components area/v2 area - version - kubeflow notebooks v2 lgtm ok-to-test size/XL
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants