Skip to content

Commit

Permalink
Add deployment example
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed May 24, 2024
1 parent 44d7e0f commit 81037b9
Show file tree
Hide file tree
Showing 9 changed files with 444 additions and 0 deletions.
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

0 comments on commit 81037b9

Please sign in to comment.