Architecture#

Default setup with remote system#

This is the default architecture designed to start a single-user notebook server. It uses a JupyterHub configured with an OutpostSpawner and running on a Kubenernetes cluster as well as a JupyterHub Outpost configured with a remote Spawner and running on a remote system (here a remote Kubernetes cluster, but can be another kind of system). The packages between the user and the single-user notebook server will be tunneled through the hub container on the JupyterHub Kubernetes cluster.

JupyterHub default architecture

Cf. here

1. Send Request#

The OutpostSpawner takes on a user’s start request for a notebook server. Instead of starting the server directly, it collects all information relevant for the start of a single-user server. In general these are name, environment and selected user_options. Optional information like certificates or trust_bundles (in case of internal_ssl) is sent to the JupyterHub Outpost if necessary.

2. Spawner.start#

The JupyterHub Outpost will use the configured JupyterHub Spawner to start the single-user server. This is the process which is usually handled by JupyterHub directly and it uses the same functions JupyterHub would use during a start process (run_pre_spawn_hook, move_certs, start). Events generated by _generate_progress() will be forwarded to JupyterHub, so users will not miss any important information.

3. Send service address#

JupyterHub needs the service address (usually a combination of port and ip) to create the SSH port forwarding. SSH port forwarding enables the user to reach the remote single-user notebook server, even when it is running in an otherwise isolated environment.

4. Port forwarding#

JupyterHub uses a random available local port (random_port) to forward traffic for this single-user server to the JupyterHub Outpost. It uses SSH multiplexing to reduce the number of connections. In this setup, the JupyterHub Outpost must be able to reach the notebook server’s under its ip (service_address) and port (single-user_port). Simplified port forward command: ssh -L 0.0.0.0:[random_port]:[service_address]:[single-user_port] jhuboutpost@[outpost-ip].

It is also possible to define a customized port forward function (e.g. to outsource port-forwarding to an external pod, see external tunneling). Once could also tunnel directly to the system where the notebook server is running without going through a JupyterHub Outpost, see delayed tunneling.

5. Create service#

At this step the JupyterHub OutpostSpawner will create a Kubernetes Service, allowing the Configurable HTTP Proxy to communicate with the single-user server via this Kubernetes service.

In the default configuration, the Hub pod is targeted by the Kubernetes service since it handles the SSH connections. All packages between the client and the single-user server will therefore be forwarded through the hub container. It is also possible to alter the Kubernetes service selector, or to define a customized service creation function (e.g. to outsource port-forwarding to an external pod).

Default setup with remote and local system#

This is the same architecture as described in the previous section but with the addition of a local JupyterHub Outpost service running on the same Kubernetes cluster as JupyterHub. It simply serves to demonstrate that in the case of a local Outpost service, there is no need to enable ssh port-forwarding as the notebook servers will be directly reachable thanks to Kubernetes’ internal DNS name resolution.

JupyterHub delayed architecture

Steps A to C correspond to steps 1 to 3 in the previous section.

External Tunneling#

In this scenario, an additional pod was created to manage the port forwarding. This means that the management of SSH tunnels to single-user notebook servers is delegated from the JupyterHub pod to the external pod forwarding pod.

With this setup, single-user server are reachable even if JupyterHub itself is currently offline. Instead of tunneling through the Hub pod, packages between the client and the single-user server travel through the port forward pod. The Kubernetes service for the single-user server therefore targets the port forwarding pod and not the Hub pod.

JupyterHub external architecture

Cf. here

Delayed Tunneling#

This setup shows the start of a single-user server on an external system where the service address of the single-user server cannot be determined during the start process. This may for example be the case if you have configured a Spawner which spawns single-user server on remote Batch systems. The single-user server has to contact the OutpostSpawner itself, once its location is settled.

The use of the additional port forward pod is optional and SSH tunnels to the single-user servers may be managed by the Hub pod itself. In any case, when one tunnels directly to the remote system where the notebook server runs, port 22 of the system (or whichever port is used for ssh) must be reachable for the pod which manages the tunnels.

JupyterHub delayed architecture

Cf. here