Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/test-chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,92 @@ jobs:
if: failure() && matrix.setup-postgresql-args
run: |
kubectl logs statefulset/postgresql

test-gateway-ingress:
# The main test configures autohttps, but this conflicts with testing
# ingress/gateway which assume they can directly connect to proxy-public
Copy link
Member Author

@manics manics Apr 30, 2025

Choose a reason for hiding this comment

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

In theory it should be possible to have autohttps working, but that would only be used for internal connections between the traefik ingress/gateway controller and the autohttps/proxy pod which effectively means it's no use, since the certificate is designed for external connections.

The certificate for external connections has to be managed by the gateway or ingress controller, not autohttps.
https://gateway-api.sigs.k8s.io/guides/tls/

I don't think autohttps is useful unless using a TCP LoadBalancer which passes network traffic straight through.

Copy link
Member Author

@manics manics Apr 30, 2025

Choose a reason for hiding this comment

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

Rereading https://gateway-api.sigs.k8s.io/guides/tls/#tls-configuration there's a Passthrough mode, but this is Experimental and I don't see what benefit it offers for autohttps- it adds unnecessary complexity.

# This is a minimal test that checks connections via a gateway is possible
timeout-minutes: 20

strategy:
# Keep running even if one variation of the job fail
fail-fast: false
matrix:
ingress-type:
- gateway
- ingress

runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
with:
# chartpress requires git history to set chart version and image tags
# correctly
fetch-depth: 0

# Enable Gateway API in K3s
# https://github.com/k3s-io/k3s/issues/12183#issuecomment-2818989109
# This will create gateway.networking.k8s.io/v1 kube-system/traefik-gateway
- name: pre-configure k3s installation
if: matrix.ingress-type == 'gateway'
run: |
sudo mkdir -p /var/lib/rancher/k3s/server/manifests
sudo cp ci/traefik-config.yaml /var/lib/rancher/k3s/server/manifests/

# Starts a k8s cluster with NetworkPolicy enforcement and installs both
# kubectl and helm
#
# ref: https://github.com/jupyterhub/action-k3s-helm/
- uses: jupyterhub/action-k3s-helm@v4
with:
k3s-channel: stable
metrics-enabled: false
traefik-enabled: true
docker-enabled: true

- uses: actions/setup-python@v5
with:
python-version: "3.11"

# Build our images if needed and update values.yaml with the tags
- name: Install and run chartpress
run: |
pip3 install -r dev-requirements.txt
chartpress
env:
DOCKER_BUILDKIT: "1"

- name: Install local chart (gateway)
if: matrix.ingress-type == 'gateway'
run: >-
helm upgrade --install jupyterhub ./jupyterhub
--set proxy.service.type=ClusterIP
--set httpRoute.enabled=true
--set httpRoute.gateway.name=traefik-gateway
--set httpRoute.gateway.namespace=kube-system

# K3S traefik listens on host:80/443
- name: Install local chart (ingress)
if: matrix.ingress-type == 'ingress'
run: >-
helm upgrade --install jupyterhub ./jupyterhub
--set proxy.service.type=ClusterIP
--set ingress.enabled=true
--set ingress.hosts[0]=localhost

- name: "Await local chart"
uses: jupyterhub/action-k8s-await-workloads@v3
with:
timeout: 150
max-restarts: 1

- name: Test ingress
run: |
curl -vL http://localhost/
curl -vL http://localhost/ | grep '<title>JupyterHub</title>'

- name: Get traefik logs
if: always()
run: |
kubectl -nkube-system logs deploy/traefik
18 changes: 18 additions & 0 deletions ci/traefik-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
providers:
kubernetesGateway:
enabled: true
gateway:
listeners:
web:
# -- Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.FromNamespaces
namespacePolicy: All
logs:
access:
enabled: true
7 changes: 6 additions & 1 deletion jupyterhub/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@
Try secure HTTPS access: https://{{ $host }}{{ $.Values.hub.baseUrl | trimSuffix "/" }}/
{{- end }}
{{- end }}
{{- else if .Values.httpRoute.enabled }}
{{- range $host := (.Values.httpRoute.hostnames | default (list "localhost")) }}
Try insecure HTTP access: http://{{ $host }}{{ $.Values.hub.baseUrl | trimSuffix "/" }}/
or secure HTTP access: https://{{ $host }}{{ $.Values.hub.baseUrl | trimSuffix "/" }}/ if supported by your Gateway
{{- end }}
{{- else }}
You have not configured a k8s Ingress resource so you need to access the k8s
You have not configured a k8s Ingress or Gateway resource so you need to access the k8s
Service {{ $proxy_service }} directly.
{{- println }}

Expand Down
8 changes: 8 additions & 0 deletions jupyterhub/templates/_helpers-names.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@
{{- end }}
{{- end }}

{{- /* HTTPRoute */}}
{{- define "jupyterhub.httpRoute.fullname" -}}
{{- if (include "jupyterhub.fullname" .) }}
{{- include "jupyterhub.fullname" . }}
{{- else -}}
jupyterhub
{{- end }}
{{- end }}


{{- /*
Expand Down
23 changes: 23 additions & 0 deletions jupyterhub/templates/httproute.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{{- if .Values.httpRoute.enabled -}}
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: {{ include "jupyterhub.httpRoute.fullname" . }}
labels:
{{- include "jupyterhub.labels" . | nindent 4 }}
spec:
parentRefs:
- kind: Gateway
name: {{ .Values.httpRoute.gateway.name }}
{{- with .Values.httpRoute.gateway.namespace }}
namespace: {{ . }}
{{- end }}
{{- with .Values.httpRoute.hostnames }}
hostnames:
{{- . | toYaml | nindent 4 }}
{{- end }}
rules:
- backendRefs:
- name: proxy-public
port: 80
{{- end }}
31 changes: 31 additions & 0 deletions jupyterhub/values.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,37 @@ properties:
See [the Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types)
for more details about paths.

httpRoute:
type: object
additionalProperties: false
required: [enabled]
properties:
enabled:
type: boolean
description: |
Enable the creation of a Kubernetes HTTPRoute to the proxy-public service.

Requires support for the [Gateway API](https://gateway-api.sigs.k8s.io/).

A Gateway must already exist.
hostnames:
type: array
description: |
List of hosts to route requests to the proxy, if empty route all requests regardless of host.
gateway:
type: object
additionalProperties: false
required: [name]
properties:
name:
type: string
description: |
The name of your Gateway for this route.
namespace:
type: string
description: |
The namespace the Gateway is running in.

prePuller:
type: object
additionalProperties: false
Expand Down
7 changes: 7 additions & 0 deletions jupyterhub/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,13 @@ ingress:
tls: []
extraPaths: []

httpRoute:
enabled: false
hostnames: []
gateway:
name: your-gateway
# namespace: if different from Z2JH

# cull relates to the jupyterhub-idle-culler service, responsible for evicting
# inactive singleuser pods.
#
Expand Down