Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deployment example #87

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ See the [Plasma documentation on user environments](https://docs.plasmabio.org/e

See: https://repo2docker.readthedocs.io/en/latest/howto/jupyterhub_images.html

## Deploy on Kubernetes cluster with Zero to JupyterHub

Check out the instructions in [DEPLOYMENT.md](./example/DEPLOYMENT.md) to set up the deployment.

## Run Locally

Check out the instructions in [CONTRIBUTING.md](./CONTRIBUTING.md) to set up a local environment.
92 changes: 92 additions & 0 deletions example/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Deploy `tljh_repo2docker` as a hub-managed service

A guide to help you add `tljh_repo2docker` service to a JupyterHub deployment from scratch.

> [!NOTE]
> In this guide, we assume you have experience with setting up JupyterHub on Kubernetes. We will use [Zero to JupyterHub](https://z2jh.jupyter.org/en/latest/index.html#zero-to-jupyterhub-with-kubernetes) as the base JupyterHub deployment.

## Create a new JupyterHub docker image.

Since we are going to use `tljh_repo2docker` as a hub-managed service, the python package needs to be installed into the base JupyterHub image. We will build a custom image based on the [zero-to-jupyterhub image](https://quay.io/repository/jupyterhub/k8s-hub). Here is a minimal docker file to build the custom image:

```docker
FROM quay.io/jupyterhub/k8s-hub:3.3.7

USER root
RUN python3 -m pip install "tljh-repo2docker>=2.0.0a1"

USER jovyan
```

Then you can build and push the image to the registry of your choice:

```bash
docker build . -t k8s-hub-tljh:xxx
```

## Installing JupyterHub and tljh_repo2docker with Helm chart.

With a Kubernetes cluster available and Helm installed, we can install our custom JupyterHub image in the Kubernetes cluster using the JupyterHub Helm chart. You can find a chart template at [example/tljh_r2d](./tljh_r2d).

### Using local build-backend via `repo2docker`.

To build images using the local docker engine, modify the `value.yaml` file with the following content:

```yaml
# values.yaml
jupyterhub:
enabled: true
hub:
image:
name: k8s-hub-tljh
tag: xxx
extraFiles:
my_jupyterhub_config:
mountPath: /usr/local/etc/jupyterhub/jupyterhub_config.d/jupyterhub_config.py
```

Now install the chart with the JupyterHub config from `jupyterhub_config_local.py`:

```bash
helm upgrade --install tljh . --wait --set-file jupyterhub.hub.extraFiles.my_jupyterhub_config.stringData=./jupyterhub_config_local.py
```

### Using binderhub service as build-backend.

To use binderhub as the build-backend, you need to deploy [binderhub service](https://binderhub-service.readthedocs.io/en/latest/tutorials/install.html) and config `tljh-repo2docker` to use this service. Here is an example of the configuration:

```yaml
#values.yaml

jupyterhub:
enabled: true
hub:
image:
name: k8s-hub-binhderhub-tljh
tag: xxx
config:
BinderSpawner:
auth_enabled: true
extraFiles:
my_jupyterhub_config:
mountPath: /usr/local/etc/jupyterhub/jupyterhub_config.d/jupyterhub_config.py

binderhub-service:
config:
BinderHub:
base_url: /services/binder
use_registry: true
image_prefix: ''
enable_api_only_mode: true

buildPodsRegistryCredentials:
server: 'https://index.docker.io/v1/' # Set image registry for pushing images
```

Now install the chart with the JupyterHub config from `jupyterhub_config_binderhub.py` and with image registry credentials passed via command line:

```bash
helm upgrade --install tljh . --wait --set-file jupyterhub.hub.extraFiles.my_jupyterhub_config.stringData=./jupyterhub_config_binderhub.py --set binderhub-service.buildPodsRegistryCredentials.password=xxx --set binderhub-service.buildPodsRegistryCredentials.username=xxx
```

Now you should have a working instance of JupyterHub with `tljh_repo2docker` service deployed.
6 changes: 6 additions & 0 deletions example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM quay.io/jupyterhub/k8s-hub:3.3.7

USER root
RUN python3 -m pip install "tljh-repo2docker>=2.0.0a1"

USER jovyan
23 changes: 23 additions & 0 deletions example/tljh_r2d/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
9 changes: 9 additions & 0 deletions example/tljh_r2d/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dependencies:
- name: jupyterhub
repository: https://hub.jupyter.org/helm-chart/
version: 3.3.7
- name: binderhub-service
repository: https://2i2c.org/binderhub-service
version: 0.1.0-0.dev.git.201.h161a088
digest: sha256:db05fc18bdb7cbcf06ea8881ed72391383ed0524def10d76b62b9d1880abac51
generated: "2024-05-02T20:34:11.347162518+02:00"
34 changes: 34 additions & 0 deletions example/tljh_r2d/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: v2
name: tljh_r2d
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

dependencies:
- name: jupyterhub
version: "3.3.7"
repository: https://hub.jupyter.org/helm-chart/
condition: jupyterhub.enabled
# Optional binderhub build backend
- name: binderhub-service
version : "0.1.0-0.dev.git.201.h161a088"
repository: https://2i2c.org/binderhub-service
182 changes: 182 additions & 0 deletions example/tljh_r2d/jupyterhub_config_binderhub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""
This file is only used for local development
and overrides some of the default values from the plugin.
"""

from kubespawner import KubeSpawner
from pathlib import Path
from tljh_repo2docker import TLJH_R2D_ADMIN_SCOPE
from tornado import web
from traitlets import Bool, Unicode
from traitlets.config import Configurable
import sys


"""
Helpers for creating BinderSpawners
This file is defined in binderhub/binderspawner_mixin.py and is copied to here
"""


class BinderSpawnerMixin(Configurable):
"""
Mixin to convert a JupyterHub container spawner to a BinderHub spawner

Container spawner must support the following properties that will be set
via spawn options:
- image: Container image to launch
- token: JupyterHub API token
"""

def __init__(self, *args, **kwargs):
# Is this right? Is it possible to having multiple inheritance with both
# classes using traitlets?
# https://stackoverflow.com/questions/9575409/calling-parent-class-init-with-multiple-inheritance-whats-the-right-way
# https://github.com/ipython/traitlets/pull/175
super().__init__(*args, **kwargs)

auth_enabled = Bool(
False,
help="""
Enable authenticated binderhub setup.

Requires `jupyterhub-singleuser` to be available inside the repositories
being built.
""",
config=True,
)

cors_allow_origin = Unicode(
"",
help="""
Origins that can access the spawned notebooks.

Sets the Access-Control-Allow-Origin header in the spawned
notebooks. Set to '*' to allow any origin to access spawned
notebook servers.

See also BinderHub.cors_allow_origin in binderhub config
for controlling CORS policy for the BinderHub API endpoint.
""",
config=True,
)

def get_args(self):
if self.auth_enabled:
args = super().get_args()
else:
args = [
"--ip=0.0.0.0",
f"--port={self.port}",
f"--NotebookApp.base_url={self.server.base_url}",
f"--NotebookApp.token={self.user_options['token']}",
"--NotebookApp.trust_xheaders=True",
]
if self.default_url:
args.append(f"--NotebookApp.default_url={self.default_url}")

if self.cors_allow_origin:
args.append("--NotebookApp.allow_origin=" + self.cors_allow_origin)
# allow_origin=* doesn't properly allow cross-origin requests to single files
# see https://github.com/jupyter/notebook/pull/5898
if self.cors_allow_origin == "*":
args.append("--NotebookApp.allow_origin_pat=.*")
args += self.args
# ServerApp compatibility: duplicate NotebookApp args
for arg in list(args):
if arg.startswith("--NotebookApp."):
args.append(arg.replace("--NotebookApp.", "--ServerApp."))
return args

def start(self):
if not self.auth_enabled:
if "token" not in self.user_options:
raise web.HTTPError(400, "token required")
if "image" not in self.user_options:
raise web.HTTPError(400, "image required")
if "image" in self.user_options:
self.image = self.user_options["image"]
return super().start()

def get_env(self):
env = super().get_env()
if "repo_url" in self.user_options:
env["BINDER_REPO_URL"] = self.user_options["repo_url"]
for key in (
"binder_ref_url",
"binder_launch_host",
"binder_persistent_request",
"binder_request",
):
if key in self.user_options:
env[key.upper()] = self.user_options[key]
return env


class BinderSpawner(BinderSpawnerMixin, KubeSpawner):
pass


HERE = Path(__file__).parent

c.JupyterHub.spawner_class = BinderSpawner

c.JupyterHub.allow_named_servers = True

c.JupyterHub.services.extend(
[
{"name": "binder", "url": "http://tljh-binderhub-service"},
{
"name": "tljhrepo2docker",
"url": "http://r2d-svc:6789",
"command": [
sys.executable,
"-m",
"tljh_repo2docker",
"--ip",
"0.0.0.0",
"--port",
"6789",
"--TljhRepo2Docker.binderhub_url",
"http://tljh-binderhub-service/services/binder",
],
"oauth_no_confirm": True,
"oauth_client_allowed_scopes": [
TLJH_R2D_ADMIN_SCOPE,
],
},
]
)

c.JupyterHub.custom_scopes = {
TLJH_R2D_ADMIN_SCOPE: {
"description": "Admin access to myservice",
},
}

c.JupyterHub.load_roles = [
{
"description": "Role for tljh_repo2docker service",
"name": "tljh-repo2docker-service",
"scopes": [
"read:users",
"read:roles:users",
"admin:servers",
"access:services!service=binder",
],
"services": ["tljhrepo2docker"],
},
{
"name": "tljh-repo2docker-service-admin",
"users": [], # List of users having admin right on tljh-repo2docker
"scopes": [TLJH_R2D_ADMIN_SCOPE],
},
{
"name": "user",
"scopes": [
"self",
# access to the env page
"access:services!service=tljhrepo2docker",
],
},
]
Loading
Loading