From b51de8bd9a54defcbee7d88e2d79eb4e4ab15975 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Fri, 20 Dec 2019 14:05:34 +0100
Subject: [PATCH 01/29] Add underscore "_" to forbidden user name characters
---
resources/jupyterhub_config.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index a58262a..151cc45 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -28,7 +28,7 @@
def custom_normalize_username(self, username):
username = original_normalize_username(self, username)
more_than_one_forbidden_char = False
- for forbidden_username_char in [" ", ",", ";", ".", "-", "@"]:
+ for forbidden_username_char in [" ", ",", ";", ".", "-", "@", "_"]:
# Replace special characters with a non-special character. Cannot just be empty, like "", because then it could happen that two distinct user names are transformed into the same username.
# Example: "foo, bar" and "fo, obar" would both become "foobar".
replace_char = "0"
From 505f151765f29d74270540e81f401ca18161f779 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Fri, 20 Dec 2019 14:25:08 +0100
Subject: [PATCH 02/29] Check servername for forbidden characters
---
resources/jupyterhub-mod/template-home.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/resources/jupyterhub-mod/template-home.html b/resources/jupyterhub-mod/template-home.html
index a4627aa..f00dfd3 100644
--- a/resources/jupyterhub-mod/template-home.html
+++ b/resources/jupyterhub-mod/template-home.html
@@ -49,7 +49,8 @@
let row = $(self).parent();
let serverName = row.find(".new-server-name").val();
- if (serverName === '') {
+ // the server name must not be empty and must not contain DNS-incompatible characters
+ if (serverName === '' || !/^[a-zA-Z0-9\-]+$/.test(serverName)) {
alert('Server name cannot be empty');
return;
}
From c80514e98a8e53a4ccad802330266d3a22860c18 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Sat, 21 Dec 2019 15:46:21 +0100
Subject: [PATCH 03/29] - Change path of user name whitelist file - Add
environment variable to enable/disable the cleanup service
---
Dockerfile | 3 ++-
README.md | 2 +-
resources/jupyterhub_config.py | 23 +++++++++++++----------
3 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 1e59e5f..ac5b8b5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -160,7 +160,8 @@ ENV \
EXECUTION_MODE="local" \
HUB_NAME="mlhub" \
CLEANUP_INTERVAL_SECONDS=3600 \
- DYNAMIC_WHITELIST_ENABLED="false"
+ DYNAMIC_WHITELIST_ENABLED="false" \
+ IS_CLEANUP_SERVICE_ENABLED="true"
### END CONFIGURATION ###
diff --git a/README.md b/README.md
index 2437a83..edae07c 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@ Here are the additional environment variables for the hub:
DYNAMIC_WHITELIST_ENABLED |
- Enables each Authenticator to use a file as a whitelist of usernames. The file must contain one whitelisted username per line and must be mounted to /resources/dynamic_whitelist.txt. The file can be dynamically modified. Keep in mind that already logged in users stay authenticated even if removed from the list - they just cannot login again.
+ Enables each Authenticator to use a file as a whitelist of usernames. The file must contain one whitelisted username per line and must be mounted to /resources/users/dynamic_whitelist.txt. The file can be dynamically modified. Keep in mind that already logged in users stay authenticated even if removed from the list - they just cannot login again.
|
false |
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 151cc45..87ff745 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -45,9 +45,10 @@ def custom_normalize_username(self, username):
original_check_whitelist = Authenticator.check_whitelist
def dynamic_check_whitelist(self, username, authentication=None):
- dynamic_whitelist_file = "/resources/dynamic_whitelist.txt"
+ dynamic_whitelist_file = "/resources/users/dynamic_whitelist.txt"
if os.getenv("DYNAMIC_WHITELIST_ENABLED", "false") == "true":
+ # TODO: create the file and warn the user that the user has to go into the hub pod and modify it there
if not os.path.exists(dynamic_whitelist_file):
logger.error("The dynamic white list has to be mounted to '{}'. Use standard JupyterHub whitelist behavior.".format(dynamic_whitelist_file))
else:
@@ -207,12 +208,14 @@ def combine_config_dicts(*configs) -> dict:
# c.JupyterHub.template_paths = []
c.JupyterHub.template_paths.append("{}/templates/".format(os.path.dirname(nativeauthenticator.__file__)))
-c.JupyterHub.services = [
- {
- 'name': 'cleanup-service',
- 'admin': True,
- 'url': 'http://{}:9000'.format(service_host),
- 'environment': service_environment,
- 'command': [sys.executable, '/resources/cleanup-service.py']
- }
-]
+# TODO: add env variable to readme
+if (os.getenv("IS_CLEANUP_SERVICE_ENABLED", "true") == "true"):
+ c.JupyterHub.services = [
+ {
+ 'name': 'cleanup-service',
+ 'admin': True,
+ 'url': 'http://{}:9000'.format(service_host),
+ 'environment': service_environment,
+ 'command': [sys.executable, '/resources/cleanup-service.py']
+ }
+ ]
From 6a830bc7e9dd603a71e19974236f78fb0d49c3da Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Sun, 22 Dec 2019 19:36:57 +0100
Subject: [PATCH 04/29] Fix issue with dynamic whitelist where newline
characters or similar could prevent a match
---
resources/jupyterhub_config.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 87ff745..04b7876 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -53,7 +53,9 @@ def dynamic_check_whitelist(self, username, authentication=None):
logger.error("The dynamic white list has to be mounted to '{}'. Use standard JupyterHub whitelist behavior.".format(dynamic_whitelist_file))
else:
with open(dynamic_whitelist_file, "r") as f:
- whitelisted_users = f.readlines()
+ #whitelisted_users = f.readlines()
+ # rstrip() will remove trailing whitespaces or newline characters
+ whitelisted_users = [line.rstrip() for line in f]
return username.lower() in whitelisted_users
return original_check_whitelist(self, username, authentication)
From 2b90e838985f14de0c2154e6dcef1b69e25bb685 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 7 Jan 2020 15:12:20 +0100
Subject: [PATCH 05/29] Show hub version in the web app
---
Dockerfile | 10 ++++++++--
resources/jupyterhub-mod/template-admin.html | 2 ++
resources/jupyterhub-mod/template-home.html | 2 ++
resources/jupyterhub-mod/version-number-snippet.html | 7 +++++++
4 files changed, 19 insertions(+), 2 deletions(-)
create mode 100644 resources/jupyterhub-mod/version-number-snippet.html
diff --git a/Dockerfile b/Dockerfile
index ac5b8b5..85b79c0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -126,6 +126,9 @@ RUN apt-get update && apt-get install -y pcregrep && clean-layer.sh
### CONFIGURATION ###
+ARG ARG_HUB_VERSION="unknown"
+ENV HUB_VERSION=$ARG_HUB_VERSION
+
COPY resources/nginx.conf /etc/nginx/nginx.conf
COPY resources/scripts $_RESOURCES_PATH/scripts
COPY resources/docker-entrypoint.sh $_RESOURCES_PATH/docker-entrypoint.sh
@@ -135,6 +138,7 @@ COPY resources/jupyterhub-mod/template-home.html /usr/local/share/jupyterhub/tem
COPY resources/jupyterhub-mod/template-admin.html /usr/local/share/jupyterhub/templates/admin.html
COPY resources/jupyterhub-mod/ssh-dialog-snippet.html /usr/local/share/jupyterhub/templates/ssh-dialog-snippet.html
COPY resources/jupyterhub-mod/info-dialog-snippet.html /usr/local/share/jupyterhub/templates/info-dialog-snippet.html
+COPY resources/jupyterhub-mod/version-number-snippet.html /usr/local/share/jupyterhub/templates/version-number-snippet.html
COPY resources/jupyterhub-mod/jsonpresenter /usr/local/share/jupyterhub/static/components/jsonpresenter/
COPY resources/jupyterhub-mod/cleanup-service.py /resources/cleanup-service.py
@@ -144,6 +148,10 @@ RUN \
chmod -R ug+rxw $_RESOURCES_PATH/scripts && \
chmod ug+rwx $_RESOURCES_PATH/docker-entrypoint.sh
+RUN \
+ # Replace the variable with the actual value. There seems to be no direct functionality in ninja-templates
+ sed -i "s/\$HUB_VERSION/$HUB_VERSION/g" /usr/local/share/jupyterhub/templates/version-number-snippet.html
+
# Set python3 to default python. Needed for the ssh-proxy scripts
RUN \
rm /usr/bin/python && \
@@ -169,8 +177,6 @@ ENV \
ARG ARG_BUILD_DATE="unknown"
ARG ARG_VCS_REF="unknown"
-ARG ARG_HUB_VERSION="unknown"
-ENV HUB_VERSION=$ARG_HUB_VERSION
# Overwrite & add common labels
LABEL \
diff --git a/resources/jupyterhub-mod/template-admin.html b/resources/jupyterhub-mod/template-admin.html
index 61fc45d..6278d2f 100644
--- a/resources/jupyterhub-mod/template-admin.html
+++ b/resources/jupyterhub-mod/template-admin.html
@@ -187,4 +187,6 @@
{% include 'info-dialog-snippet.html' %}
+{% include 'version-number-snippet.html' %}
+
{% endblock %}
diff --git a/resources/jupyterhub-mod/template-home.html b/resources/jupyterhub-mod/template-home.html
index f00dfd3..2f01d59 100644
--- a/resources/jupyterhub-mod/template-home.html
+++ b/resources/jupyterhub-mod/template-home.html
@@ -172,4 +172,6 @@
{% include 'info-dialog-snippet.html' %}
+{% include 'version-number-snippet.html' %}
+
{% endblock %}
diff --git a/resources/jupyterhub-mod/version-number-snippet.html b/resources/jupyterhub-mod/version-number-snippet.html
new file mode 100644
index 0000000..fb57329
--- /dev/null
+++ b/resources/jupyterhub-mod/version-number-snippet.html
@@ -0,0 +1,7 @@
+
+
mltooling/ml-hub:$HUB_VERSION
+
From c32f4ce7df386e40de773b787b771326de0df2de Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 7 Jan 2020 17:12:45 +0100
Subject: [PATCH 06/29] Fix way how GPUs are counted. Previously, the command
`nvidia-smi` was not installed and even if it were, it would not have shown
gpus as they would have needed to be made visible to mlhub
---
.../mlhubspawner/mlhubspawner/mlhubspawner.py | 21 +++++--------------
1 file changed, 5 insertions(+), 16 deletions(-)
diff --git a/resources/mlhubspawner/mlhubspawner/mlhubspawner.py b/resources/mlhubspawner/mlhubspawner/mlhubspawner.py
index e58bf59..8ac4339 100644
--- a/resources/mlhubspawner/mlhubspawner/mlhubspawner.py
+++ b/resources/mlhubspawner/mlhubspawner/mlhubspawner.py
@@ -340,22 +340,11 @@ def load_state(self, state):
def get_gpu_info(self) -> list:
count_gpu = 0
try:
- sp = subprocess.Popen(['nvidia-smi', '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out_str = sp.communicate()
- out_list = out_str[0].decode("utf-8").split('\n')
-
- # out_dict = {}
-
- for item in out_list:
- try:
- key, val = item.split(':')
- key, val = key.strip(), val.strip()
- if key == 'Product Name':
- count_gpu += 1
- # gpus.append(val)
- #out_dict[key + "_" + str(count_gpu)] = val
- except:
- pass
+ # NOTE: this approach currently only works for nvidia gpus.
+ ps = subprocess.Popen(('find', '/proc/irq/', '-name', 'nvidia'), stdout=subprocess.PIPE)
+ output = subprocess.check_output(('wc', '-l'), stdin=ps.stdout)
+ ps.wait()
+ count_gpu = int(output.decode("utf-8"))
except:
pass
From efffb2795d8f3c89b1d601f5d5675dbd29409b23 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 9 Jan 2020 16:49:40 +0100
Subject: [PATCH 07/29] With the modified helm chart, the user will make almost
all configurations as usual via the JupyterHub config
---
resources/jupyterhub_config.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 04b7876..5c0eb8c 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -145,8 +145,6 @@ def combine_config_dicts(*configs) -> dict:
# See https://traitlets.readthedocs.io/en/stable/config.html#configuration-files-inheritance
load_subconfig("{}/jupyterhub_user_config.py".format(os.getenv("_RESOURCES_PATH")))
-
-
service_environment = {
ENV_NAME_HUB_NAME: ENV_HUB_NAME,
utils.ENV_NAME_EXECUTION_MODE: ENV_EXECUTION_MODE,
@@ -161,12 +159,8 @@ def combine_config_dicts(*configs) -> dict:
c.JupyterHub.spawner_class = 'mlhubspawner.MLHubKubernetesSpawner'
c.KubeSpawner.pod_name_template = c.Spawner.name_template
- from z2jh import set_config_if_not_none
- set_config_if_not_none(c.KubeSpawner, 'workspace_images', 'singleuser.workspaceImages')
-
c.KubeSpawner.environment = get_or_init(c.KubeSpawner.environment, dict)
- # if not isinstance(c.KubeSpawner.environment, dict):
- # c.KubeSpawner.environment = {}
+
c.KubeSpawner.environment.update(default_env)
# For cleanup-service
From fb54e2e1c4c7b759ac3e35aa64629f68074eae9b Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 9 Jan 2020 17:55:17 +0100
Subject: [PATCH 08/29] Modified documentation based on simplified setup
---
README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 51 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index edae07c..cf788d7 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,19 @@ docker run \
To persist the hub data, such as started workspaces and created users, mount a directory to `/data`.
Any given name (`--name`) will be overruled by the environment variable `HUB_NAME`.
-For Kubernetes deployment, we forked and modified [zero-to-jupyterhub-k8s](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) which you can find [here](https://github.com/ml-tooling/zero-to-mlhub-k8s).
+
+### Start an instance via Kubernetes
+
+Via helm:
+```bash
+RELEASE=mlhub # change if needed
+NAMESPACE=$RELEASE # change if needed
+helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
+```
+
+
+
+
### Configuration
@@ -137,21 +149,54 @@ Here are the additional environment variables for the hub:
#### Jupyterhub Config
-##### Docker-local
-
Jupyterhub itself is configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
Following settings are additional to standard JupyterHub:
- `c.Spawner.workspace_images` - set the images that appear in the dropdown menu when a new named server should be created, e.g. `c.Spawner.workspace_images = [c.Spawner.image, "mltooling/ml-workspace-gpu:0.8.7", "mltooling/ml-workspace-r:0.8.7"]`
Following settings should probably not be overriden:
-- `c.Spawner.environment` - we set default variables there. Instead of overriding it, you can add extra variables to the existing dict, e.g. via `c.Spawner.environment["myvar"] = "myvalue"`.
+- `c.Spawner.environment` - we set default variables there. Instead of overriding it, you can add extra variables to the existing dict, e.g. via `c.Spawner.environment["myvar"] = "myvalue"`.
- `c.DockerSpawner.prefix` and `c.DockerSpawner.name_template` - if you change those, check whether your SSH environment variables permit those names a target. Also, think about setting `c.Authenticator.username_pattern` to prevent a user having a username that is also a valid container name.
- If you override ip and port connection settings, make sure to use Docker images that can handle those.
+An examplary custom config file could look like this:
+
+```python
+# jupyterhub_user_config.py
+c.Spawner.environment = {"FOO": "BAR"}
+c.Spawner.workspace_images = ["mltooling/ml-workspace-r:0.8.7"]
+```
+
+##### Docker-local
+
+In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jupyterhub_user_config.py`.
+
##### Kubernetes
+
+When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml --set-file userConfig=./jupyterhub_user_config.py`.
+
+!!! info
+ The Jupyterhub configurations `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
+
+
+Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the Spawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
+Additionally, you have to use this yaml file for the settings `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` as they have to be shared between services and, thus, have to be known during deployment.
+A `config.yaml` where you set those values would look like following:
+
+```yaml
+proxy:
+ secretToken: <32 characters random string base64 encoded>
+
+hub:
+ baseUrl: "/mlhub"
+```
+
+You can pass the file via `--values config.yaml`. The complete command would look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml`. The `--set-file userConfig=./jupyterhub_user_config.py` flag can additionally be provided.
+
+
+
+
-To make modifications to the config in the Kubernetes setup, checkout the documentation for [Zero to JupyterHub with Kubernetes](https://zero-to-jupyterhub.readthedocs.io/en/latest/reference.html?highlight=service_account#singleuser). Our hub is compatible with their approach and so you can pass a config.yaml to the helm command to set values for the Jupyterhub config. We modified a few default values compared to the original repository.
-[This file](https://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/jupyterhub/values.yaml) contains the default values for the helm deployment. The passed config is used by [the Jupyterhub config](https://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/images/hub/jupyterhub_config.py), which we load subsequently to the Jupyterhub config you find in this repo. Hence, the "Zero to JupyterHub with Kubernetes" config overrides the above described default config as it is loaded after our default config file. In short what happens: This repo's hub config is loaded, then the "Zero to JupyterHub with Kubernetes" config, where values can be modified via a config.yaml.
### Enable SSL/HTTPS
From 29c758cc798f03ab8afe87f361b0ed88df96f70f Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 10:51:31 +0100
Subject: [PATCH 09/29] Update documentation
---
README.md | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index cf788d7..9213b42 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,7 @@ Here are the additional environment variables for the hub:
#### Jupyterhub Config
-Jupyterhub itself is configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
+Jupyterhub and the used spawners are configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
Following settings are additional to standard JupyterHub:
- `c.Spawner.workspace_images` - set the images that appear in the dropdown menu when a new named server should be created, e.g. `c.Spawner.workspace_images = [c.Spawner.image, "mltooling/ml-workspace-gpu:0.8.7", "mltooling/ml-workspace-r:0.8.7"]`
@@ -167,20 +167,19 @@ c.Spawner.workspace_images = ["mltooling/ml-workspace-r:0.8.7"]
```
##### Docker-local
-
-In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jupyterhub_user_config.py`.
+
+In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jupyterhub_user_config.py`. Have a look at the [DockerSpawner properties](https://github.com/jupyterhub/dockerspawner/blob/master/dockerspawner/dockerspawner.py) to see what can be configured.
##### Kubernetes
-
-When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml --set-file userConfig=./jupyterhub_user_config.py`.
-!!! info
- The Jupyterhub configurations `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
+When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured.
+
+> **Info:** The Jupyterhub configurations `c.JupyterHub.base_url`, `c.JupyterHub.proxy_auth_token`, and `c.JupyterHub.debug` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
-
Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the Spawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
Additionally, you have to use this yaml file for the settings `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` as they have to be shared between services and, thus, have to be known during deployment.
-A `config.yaml` where you set those values would look like following:
+
+A `config.yaml` where you set those values could look like following:
```yaml
proxy:
@@ -188,16 +187,19 @@ proxy:
hub:
baseUrl: "/mlhub"
+
+debug:
+ enabled: true
```
-You can pass the file via `--values config.yaml`. The complete command would look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml`. The `--set-file userConfig=./jupyterhub_user_config.py` flag can additionally be provided.
+
+You can pass the file via `--values config.yaml`. The complete command would look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml`. The `--set-file userConfig=./jupyterhub_user_config.py` flag can additionally be set.
+You can find the Helm chart resources in the repository [zero-to-mlhub-with-kubernetes](https://github.com/ml-tooling/zero-to-mlhub-k8s); the [values file](ttps://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/jupyterhub/values.yaml) contains the default values for the deployment.
-
-
### Enable SSL/HTTPS
MLHub will start in HTTP mode by default. Note that in HTTP mode, the ssh tunnel feature does not work.
From 494eca98247fd8944c8e47ede9a1cd65fd4e5d38 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 10:59:51 +0100
Subject: [PATCH 10/29] Update documentation
---
README.md | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 9213b42..d1e5db0 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
Contribution
-MLHub is based on [Jupyterhub](https://github.com/jupyterhub/jupyterhub) with complete focus on Docker and Kubernetes. MLHub allows to create and manage multiple [workspaces](https://github.com/ml-tooling/ml-workspace), for example to distribute them to a group of people or within a team.
+MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with complete focus on Docker and Kubernetes. MLHub allows to create and manage multiple [workspaces](https://github.com/ml-tooling/ml-workspace), for example to distribute them to a group of people or within a team.
## Highlights
@@ -43,7 +43,7 @@ MLHub is based on [Jupyterhub](https://github.com/jupyterhub/jupyterhub) with co
- Kubernetes (for Kubernetes modes)
- Helm (for easy deployment via our [helm chart](https://github.com/ml-tooling/ml-hub/releases/download/0.1.4/mlhub-chart-0.1.4.tgz))
-Most parts will be identical to the configuration of Jupyterhub 1.0.0. One of the things that are different is that ssl will not be activated on proxy or hub-level, but on our nginx proxy.
+Most parts will be identical to the configuration of JupyterHub 1.0.0. One of the things that are different is that ssl will not be activated on proxy or hub-level, but on our nginx proxy.
### Start an instance via Docker
@@ -76,7 +76,7 @@ helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
#### Default Login
-When using the default config - so leaving the Jupyterhub config `c.Authenticator.admin_users` as it is -, a user named `admin` can access the hub with admin rights. If you use the default `NativeAuthenticator` as authenticator, youc must register the user `admin` with a password of your choice first before login in.
+When using the default config - so leaving the JupyterHub config `c.Authenticator.admin_users` as it is -, a user named `admin` can access the hub with admin rights. If you use the default `NativeAuthenticator` as authenticator, youc must register the user `admin` with a password of your choice first before login in.
If you use a different authenticator, you might want to set a different user as initial admin user as well, for example in case of using oauth you want to set `c.Authenticator.admin_users` to a username returned by the oauth login.
#### Environment Variables
@@ -134,22 +134,23 @@ Here are the additional environment variables for the hub:
START_JHUB |
- Start the Jupyterhub hub. This option is built-in to work with
+ | Start the JupyterHub hub. This option is built-in to work with
zero-to-mlhub-k8s, where the image is also used as the CHP image. |
true |
START_CHP |
- Start the Jupyterhub proxy process separately (The hub should not start the proxy itself, which can be configured via the Jupyterhub config file. This option is built-in to work with zero-to-mlhub-k8s, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
+ Start the JupyterHub proxy process separately (The hub should not start the proxy itself, which can be configured via the JupyterHub config file. This option is built-in to work with zero-to-mlhub-k8s, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
false |
> ℹ️ _Via the START\_* environment variables, you can define what is started within the container. It's like this since the mlhub image is used in our Kubernetes setup for both, the hub and the proxy container. We did not want to break those functionalities into different images for now._
-#### Jupyterhub Config
+#### JupyterHub Config
+
+JupyterHub and the used Spawners are configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
-Jupyterhub and the used spawners are configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
Following settings are additional to standard JupyterHub:
- `c.Spawner.workspace_images` - set the images that appear in the dropdown menu when a new named server should be created, e.g. `c.Spawner.workspace_images = [c.Spawner.image, "mltooling/ml-workspace-gpu:0.8.7", "mltooling/ml-workspace-r:0.8.7"]`
@@ -172,14 +173,15 @@ In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jup
##### Kubernetes
-When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured.
+When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured.
-> **Info:** The Jupyterhub configurations `c.JupyterHub.base_url`, `c.JupyterHub.proxy_auth_token`, and `c.JupyterHub.debug` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
+> **Info:** The JupyterHub configurations `c.JupyterHub.base_url`, `c.JupyterHub.proxy_auth_token`, and `c.JupyterHub.debug` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the Spawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
Additionally, you have to use this yaml file for the settings `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` as they have to be shared between services and, thus, have to be known during deployment.
+
-A `config.yaml` where you set those values could look like following:
+A config.yaml where you set those values could look like following: (click to expand...)
```yaml
proxy:
@@ -195,10 +197,7 @@ debug:
You can pass the file via `--values config.yaml`. The complete command would look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml`. The `--set-file userConfig=./jupyterhub_user_config.py` flag can additionally be set.
-You can find the Helm chart resources in the repository [zero-to-mlhub-with-kubernetes](https://github.com/ml-tooling/zero-to-mlhub-k8s); the [values file](ttps://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/jupyterhub/values.yaml) contains the default values for the deployment.
-
-
+You can find the Helm chart resources in the repository [zero-to-mlhub-with-kubernetes](https://github.com/ml-tooling/zero-to-mlhub-k8s); the [values file](https://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/jupyterhub/values.yaml) contains the default values for the deployment.
### Enable SSL/HTTPS
@@ -214,7 +213,7 @@ If you have an own certificate, mount the certificate and key files as `cert.crt
### Spawner
-We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces.
+We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the *Configuration Section*.
#### DockerSpawner
From 7fc4193b0cd860efebff47ad9c5d5827218e54db Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 11:28:25 +0100
Subject: [PATCH 11/29] Add information about ssl deployment here
---
README.md | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index d1e5db0..1eac998 100644
--- a/README.md
+++ b/README.md
@@ -61,14 +61,21 @@ Any given name (`--name`) will be overruled by the environment variable `HUB_NAM
### Start an instance via Kubernetes
-Via helm:
+Via Helm:
+
```bash
RELEASE=mlhub # change if needed
NAMESPACE=$RELEASE # change if needed
+
helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
```
-
+You can find the chart file attached to the [release](https://github.com/ml-tooling/ml-hub/releases).
+
+
+
@@ -209,6 +216,26 @@ You can activate ssl via the environment variable `SSL_ENABLED`. If you don't pr
If you have an own certificate, mount the certificate and key files as `cert.crt` and `cert.key`, respectively, as read-only at `/resources/ssl`, so that the container has access to `/resources/ssl/cert.crt` and `/resources/ssl/cert.key`.
+For Docker, mount a volume at the path like `-v my-ssl-files:/resources/ssl`.
+For Kubernetes, add following lines to the `config.yaml` file (based on [setup-manual-https.](https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/security.html#set-up-manual-https)):
+
+```yaml
+proxy:
+ https:
+ hosts:
+ -
+ type: manual
+ manual:
+ key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ ...
+ -----END RSA PRIVATE KEY-----
+ cert: |
+ -----BEGIN CERTIFICATE-----
+ ...
+ -----END CERTIFICATE-----
+```
+
### Spawner
From 6cc5414a020e72d6797b3f7ff4d86f7e51998c37 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 11:49:44 +0100
Subject: [PATCH 12/29] Enhance documentation about https
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 1eac998..a528a7b 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,7 @@ You can find the chart file attached to the [release](https://github.com/ml-tool
@@ -221,6 +222,8 @@ For Kubernetes, add following lines to the `config.yaml` file (based on [setup-m
```yaml
proxy:
+ extraEnv:
+ SSL_ENABLED: true
https:
hosts:
-
From 8ea06dd0e04e1ae683d152f27a9b352855c5d617 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 15:18:07 +0100
Subject: [PATCH 13/29] Simplified JupyterHub config by documenting that
c.Spawner should be used to configure Spawners
---
README.md | 15 ++++++++-------
resources/jupyterhub_config.py | 17 +++++++++++------
2 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index a528a7b..eb28c64 100644
--- a/README.md
+++ b/README.md
@@ -157,15 +157,16 @@ Here are the additional environment variables for the hub:
#### JupyterHub Config
-JupyterHub and the used Spawners are configured via a `config.py` file. In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
+JupyterHub and the used Spawner are configured via a `config.py` file as stated in the [official documentation](https://jupyterhub.readthedocs.io/en/stable/getting-started/config-basics.html). In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
-Following settings are additional to standard JupyterHub:
+*Important:* When setting properties for the Spawner, please use the general form `c.Spawner.` instead of `c.DockerSpawner.`, `c.KubeSpawner.` etc. so that they are merged with default values accordingly.
+
+Our custom Spawners support the additional configurations:
- `c.Spawner.workspace_images` - set the images that appear in the dropdown menu when a new named server should be created, e.g. `c.Spawner.workspace_images = [c.Spawner.image, "mltooling/ml-workspace-gpu:0.8.7", "mltooling/ml-workspace-r:0.8.7"]`
Following settings should probably not be overriden:
-- `c.Spawner.environment` - we set default variables there. Instead of overriding it, you can add extra variables to the existing dict, e.g. via `c.Spawner.environment["myvar"] = "myvalue"`.
-- `c.DockerSpawner.prefix` and `c.DockerSpawner.name_template` - if you change those, check whether your SSH environment variables permit those names a target. Also, think about setting `c.Authenticator.username_pattern` to prevent a user having a username that is also a valid container name.
-- If you override ip and port connection settings, make sure to use Docker images that can handle those.
+- `c.Spawner.prefix` and `c.Spawner.name_template` - if you change those, check whether your SSH environment variables permit those names a target. Also, think about setting `c.Authenticator.username_pattern` to prevent a user having a username that is also a valid container name.
+- If you override ip and port connection settings, make sure to use Docker images and an overall setup that can handle those.
An examplary custom config file could look like this:
@@ -243,7 +244,7 @@ proxy:
### Spawner
-We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the *Configuration Section*.
+We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](./#configuration).
#### DockerSpawner
@@ -308,7 +309,7 @@ The "Days to live" flag is purely informational currently and can be seen in the
### Cleanup Service
-JupyterHub was originally not created with Docker or Kubernetes in mind, which can result in unfavorable scenarios such as that containers are stopped but not deleted on the host. Furthermore, our custom spawners might create some artifacts that should be cleaned up as well. MLHub contains a cleanup service that is started as a [JupyterHub service](https://jupyterhub.readthedocs.io/en/stable/reference/services.html) inside the hub container. It can be accessed as a REST-API by an admin, but it is also triggered automatically every X timesteps when not disabled (see config for `CLEANUP_INTERVAL_SECONDS`). The service enhances the JupyterHub functionality with regards to the Docker and Kubernetes world. "Containers" is hereby used interchangeably for Docker containers and Kubernetes pods.
+JupyterHub was originally not created with Docker or Kubernetes in mind, which can result in unfavorable scenarios such as that containers are stopped but not deleted on the host. Furthermore, our custom spawners might create some artifacts that should be cleaned up as well. MLHub contains a cleanup service that is started as a [JupyterHub service](https://jupyterhub.readthedocs.io/en/stable/reference/services.html) inside the hub container; both in the Docker and the Kubernetes setup. It can be accessed as a REST-API by an admin, but it is also triggered automatically every X timesteps when not disabled (see config for `CLEANUP_INTERVAL_SECONDS`). The service enhances the JupyterHub functionality with regards to the Docker and Kubernetes world. "Containers" is hereby used interchangeably for Docker containers and Kubernetes pods.
The service has two endpoints which can be reached under the Hub service url `/services/cleanup-service/*` with admin permissions.
- `GET /services/cleanup-service/users`: This endpoint is currently doing anything only in Docker-local mode. There, it will check for resources of deleted users, so users who are not in the JupyterHub database anymore, and delete them. This includes containers, networks, and volumes. This is done by looking for labeled Docker resources that point to containers started by hub and belonging to the specific users.
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 5c0eb8c..8065e69 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -102,7 +102,6 @@ def combine_config_dicts(*configs) -> dict:
# Set default environment variables used by our ml-workspace container
default_env = {"AUTHENTICATE_VIA_JUPYTER": "true", "SHUTDOWN_INACTIVE_KERNELS": "true"}
-c.Spawner.environment = default_env
# Workaround to prevent api problems
#c.Spawner.will_resume = True
@@ -144,6 +143,8 @@ def combine_config_dicts(*configs) -> dict:
# > c.DockerSpawner.extra_create_kwargs.update({'labels': {'foo': 'bar'}})
# See https://traitlets.readthedocs.io/en/stable/config.html#configuration-files-inheritance
load_subconfig("{}/jupyterhub_user_config.py".format(os.getenv("_RESOURCES_PATH")))
+c.Spawner.environment = get_or_init(c.Spawner.environment, dict)
+c.Spawner.environment.update(default_env)
service_environment = {
ENV_NAME_HUB_NAME: ENV_HUB_NAME,
@@ -159,9 +160,9 @@ def combine_config_dicts(*configs) -> dict:
c.JupyterHub.spawner_class = 'mlhubspawner.MLHubKubernetesSpawner'
c.KubeSpawner.pod_name_template = c.Spawner.name_template
- c.KubeSpawner.environment = get_or_init(c.KubeSpawner.environment, dict)
-
- c.KubeSpawner.environment.update(default_env)
+ # Consider the case where the user-config contains c.KubeSpawner.environment instead of c.Spawner.environment
+ # c.KubeSpawner.environment = get_or_init(c.KubeSpawner.environment, dict)
+ # c.Spawner.environment.update(c.KubeSpawner.environment)
# For cleanup-service
## Env variables that are used by the Python Kubernetes library to load the incluster config
@@ -178,8 +179,8 @@ def combine_config_dicts(*configs) -> dict:
# shm_size can only be set for Docker, not Kubernetes (see https://stackoverflow.com/questions/43373463/how-to-increase-shm-size-of-a-kubernetes-container-shm-size-equivalent-of-doc)
c.Spawner.extra_host_config = { 'shm_size': '256m' }
- client_kwargs = {**get_or_init(c.DockerSpawner.client_kwargs, dict), **get_or_init(c.MLHubDockerSpawner.client_kwargs, dict)}
- tls_config = {**get_or_init(c.DockerSpawner.tls_config, dict), **get_or_init(c.MLHubDockerSpawner.tls_config, dict)}
+ client_kwargs = {**get_or_init(c.Spawner.client_kwargs, dict)} # {**get_or_init(c.DockerSpawner.client_kwargs, dict), **get_or_init(c.MLHubDockerSpawner.client_kwargs, dict)}
+ tls_config = {**get_or_init(c.Spawner.tls_config, dict)} # {**get_or_init(c.DockerSpawner.tls_config, dict), **get_or_init(c.MLHubDockerSpawner.tls_config, dict)}
docker_client = utils.init_docker_client(client_kwargs, tls_config)
try:
@@ -193,6 +194,10 @@ def combine_config_dicts(*configs) -> dict:
# For cleanup-service
service_environment.update({"DOCKER_CLIENT_KWARGS": json.dumps(client_kwargs), "DOCKER_TLS_CONFIG": json.dumps(tls_config)})
service_host = "127.0.0.1"
+
+ # Consider the case where the user-config contains c.DockerSpawner.environment instead of c.Spawner.environment
+ # c.DockerSpawner.environment = get_or_init(c.DockerSpawner.environment, dict)
+ # c.Spawner.environment.update(c.DockerSpawner.environment)
#c.MLHubDockerSpawner.hub_name = ENV_HUB_NAME
# Add nativeauthenticator-specific templates
From 60c6d74feed9613af2a77e29b57788a6baa3ac39 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 14 Jan 2020 15:20:01 +0100
Subject: [PATCH 14/29] Fix issue with link in documentation
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index eb28c64..2753104 100644
--- a/README.md
+++ b/README.md
@@ -244,7 +244,7 @@ proxy:
### Spawner
-We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](./#configuration).
+We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](#configuration).
#### DockerSpawner
From aa4cb026ad7318d8da824f31d3029942a4149d77 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:23:24 +0100
Subject: [PATCH 15/29] Update documentation
---
README.md | 127 ++++++++++++++++++++++++++++++++----------------------
1 file changed, 75 insertions(+), 52 deletions(-)
diff --git a/README.md b/README.md
index 2753104..081cbf7 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with co
- Kubernetes (for Kubernetes modes)
- Helm (for easy deployment via our [helm chart](https://github.com/ml-tooling/ml-hub/releases/download/0.1.4/mlhub-chart-0.1.4.tgz))
-Most parts will be identical to the configuration of JupyterHub 1.0.0. One of the things that are different is that ssl will not be activated on proxy or hub-level, but on our nginx proxy.
+Most parts will be identical to the configuration of JupyterHub 1.0.0. One of the things done differently is that ssl will not be activated on proxy or hub-level, but on our nginx proxy.
### Start an instance via Docker
@@ -68,6 +68,10 @@ RELEASE=mlhub # change if needed
NAMESPACE=$RELEASE # change if needed
helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
+
+# In case you just want to use the templating mechanism of Helm without deploying tiller on your cluster
+# 1. Use the "helm template ..." command
+# 2. kubectl apply -f templates/hub && kubectl apply -f templates/proxy
```
You can find the chart file attached to the [release](https://github.com/ml-tooling/ml-hub/releases).
@@ -89,7 +93,7 @@ If you use a different authenticator, you might want to set a different user as
#### Environment Variables
-MLHub is based on [SSH Proxy](https://github.com/ml-tooling/ssh-proxy). Check out SSH Proxy for ssh-related configurations.
+MLHub is based on [SSH Proxy](https://github.com/ml-tooling/ssh-proxy). Check out SSH Proxy for ssh-related configurations. Check the [Configuration Section](#configuration) for details how to pass them, especially in the Kubernetes setup.
Here are the additional environment variables for the hub:
@@ -106,55 +110,31 @@ Here are the additional environment variables for the hub:
mlhub |
- EXECUTION_MODE |
- Defines in which execution mode the hub is running in. Value is one of [docker | k8s] |
- local |
+ SSL_ENABLED |
+ Enable SSL. If you don't provide an ssl certificate as described in Section "Enable SSL/HTTPS", certificates will be generated automatically. As this auto-generated certificate is not signed, you have to trust it in the browser. Without ssl enabled, ssh access won't work as the container uses a single port and has to tell https and ssh traffic apart. |
+ false |
- CLEANUP_INTERVAL_SECONDS |
-
- Interval in which expired and not-used resources are deleted. Set to -1 to disable the automatic cleanup. For more information, see Section Cleanup Service.
- |
- 3600 |
+ EXECUTION_MODE |
+ Defines in which execution mode the hub is running in. Value is one of [local | k8s] |
+ local (If you use the helm chart, the value is already set to k8s) |
DYNAMIC_WHITELIST_ENABLED |
- Enables each Authenticator to use a file as a whitelist of usernames. The file must contain one whitelisted username per line and must be mounted to /resources/users/dynamic_whitelist.txt. The file can be dynamically modified. Keep in mind that already logged in users stay authenticated even if removed from the list - they just cannot login again.
+ Enables each Authenticator to use a file as a whitelist of usernames. The file must contain one whitelisted username per line and must be mounted to /resources/users/dynamic_whitelist.txt. The file can be dynamically modified. The c.Authenticator.whitelist configuration is not considered! If set to true but the file does not exist,the normal whitelist behavior of JupyterHub is used. Keep in mind that already logged in users stay authenticated even if removed from the list - they just cannot login again.
|
false |
- SSL_ENABLED |
- Enable SSL. If you don't provide an ssl certificate as described in Section "Enable SSL/HTTPS", certificates will be generated automatically. As this auto-generated certificate is not signed, you have to trust it in the browser. Without ssl enabled, ssh access won't work as the container uses a single port and has to tell https and ssh traffic apart. |
- false |
-
-
- START_SSH |
- Start the sshd process which is used to tunnel ssh to the workspaces. |
- true |
-
-
- START_NGINX |
- Whether or not to start the nginx proxy. If the Hub should be used without additional tool routing to workspaces, this could be disabled. SSH port 22 would need to be published separately then. This option is built-in to work with zero-to-mlhub-k8s
+ | CLEANUP_INTERVAL_SECONDS |
+
+ Interval in which expired and not-used resources are deleted. Set to -1 to disable the automatic cleanup. For more information, see Section Cleanup Service.
|
- true |
-
-
- START_JHUB |
- Start the JupyterHub hub. This option is built-in to work with
- zero-to-mlhub-k8s, where the image is also used as the CHP image. |
- true |
-
-
- START_CHP |
- Start the JupyterHub proxy process separately (The hub should not start the proxy itself, which can be configured via the JupyterHub config file. This option is built-in to work with zero-to-mlhub-k8s, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
- false |
+ 3600 (currently disabled in Kubernetes) |
-> ℹ️ _Via the START\_* environment variables, you can define what is started within the container. It's like this since the mlhub image is used in our Kubernetes setup for both, the hub and the proxy container. We did not want to break those functionalities into different images for now._
-
#### JupyterHub Config
JupyterHub and the used Spawner are configured via a `config.py` file as stated in the [official documentation](https://jupyterhub.readthedocs.io/en/stable/getting-started/config-basics.html). In case of MLHub, a default config file is stored under `/resources/jupyterhub_config.py`. If you want to override settings or set extra ones, you can put another config file under `/resources/jupyterhub_user_config.py`.
@@ -182,25 +162,24 @@ In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jup
##### Kubernetes
-When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured.
+When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured for the Spawner.
-> **Info:** The JupyterHub configurations `c.JupyterHub.base_url`, `c.JupyterHub.proxy_auth_token`, and `c.JupyterHub.debug` cannot be set in the `jupyterhub_user_config.py`. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below).
+Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the KubeSpawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
-Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the Spawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
-Additionally, you have to use this yaml file for the settings `c.JupyterHub.base_url` and `c.JupyterHub.proxy_auth_token` as they have to be shared between services and, thus, have to be known during deployment.
+> ℹ️ _Some JupyterHub configurations cannot be set in the `jupyterhub_user_config.py` as they have to be shared between services and, thus, have to be known during deployment. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below)._
-A config.yaml where you set those values could look like following: (click to expand...)
+A config.yaml where you can set those values could look like following: (click to expand...)
```yaml
-proxy:
- secretToken: <32 characters random string base64 encoded>
-hub:
- baseUrl: "/mlhub"
+config:
+ baseUrl: "/mlhub" # corresponds to c.JupyterHub.base_url
+ debug: true # corresponds to c.JupyterHub.debug
+ secretToken: <32 characters random string base64 encoded> # corresponds to c.JupyterHub.proxy_auth_token
+ env: # used to set environment variables as described in the Section "Environment Variables"
+ DYNAMIC_WHITELIST_ENABLED: true
-debug:
- enabled: true
```
@@ -222,9 +201,12 @@ For Docker, mount a volume at the path like `-v my-ssl-files:/resources/ssl`.
For Kubernetes, add following lines to the `config.yaml` file (based on [setup-manual-https.](https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/security.html#set-up-manual-https)):
```yaml
-proxy:
- extraEnv:
+
+config:
+ env:
SSL_ENABLED: true
+
+proxy:
https:
hosts:
-
@@ -316,9 +298,50 @@ The service has two endpoints which can be reached under the Hub service url `/s
- `GET /services/cleanup-service/expired`: When starting a named workspace, an expiration date can be assigned to it. This endpoint will delete all containers that are expired. The respective named server is deleted from the JupyterHub database and also the Docker/Kubernetes resource is deleted.
-## Customization
+## FAQ
+
+
+How to change the logo shown in the webapp? (click to expand...)
+
+If you want to have your own logo in the corner, place it at `/usr/local/share/jupyterhub/static/images/jupyter.png` inside the hub container.
+
+
+
+What are the additional environment variables I have seen in the code? (click to expand...)
+
+Via the START\_* environment variables you can define what is started within the container. It's like this since the MLHub image is used in our Kubernetes setup for both, the hub and the proxy container. We did not want to break those functionalities into different images for now. They are probably configured in the provided Helm chart and, thus, do **not** have to be configured by you.
+
+
+
+ Variable |
+ Description |
+ Default |
+
+
+ START_SSH |
+ Start the sshd process which is used to tunnel ssh to the workspaces. |
+ true |
+
+
+ START_NGINX |
+ Whether or not to start the nginx proxy. If the Hub should be used without additional tool routing to workspaces, this could be disabled. SSH port 22 would need to be published separately then. This option is built-in to work with zero-to-mlhub-k8s
+ |
+ true |
+
+
+ START_JHUB |
+ Start the JupyterHub hub. This option is built-in to work with
+ zero-to-mlhub-k8s, where the image is also used as the CHP image. |
+ true |
+
+
+ START_CHP |
+ Start the JupyterHub proxy process separately (The hub should not start the proxy itself, which can be configured via the JupyterHub config file. This option is built-in to work with zero-to-mlhub-k8s, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
+ false |
+
+
-- Logo: if you want to have your own logo in the corner, place it at `/usr/local/share/jupyterhub/static/images/jupyter.png` inside the hub container.
+
## Contribution
From 4d675165914d080a302e3bf4596cee0959332abd Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:26:07 +0100
Subject: [PATCH 16/29] Update documentation
---
README.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/README.md b/README.md
index 081cbf7..b947fc0 100644
--- a/README.md
+++ b/README.md
@@ -197,7 +197,12 @@ You can activate ssl via the environment variable `SSL_ENABLED`. If you don't pr
If you have an own certificate, mount the certificate and key files as `cert.crt` and `cert.key`, respectively, as read-only at `/resources/ssl`, so that the container has access to `/resources/ssl/cert.crt` and `/resources/ssl/cert.key`.
+#### Docker-local
+
For Docker, mount a volume at the path like `-v my-ssl-files:/resources/ssl`.
+
+#### Kubernetes
+
For Kubernetes, add following lines to the `config.yaml` file (based on [setup-manual-https.](https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/security.html#set-up-manual-https)):
```yaml
From 68131ee654970b8867a173d1c379deffa20c4fa5 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:27:32 +0100
Subject: [PATCH 17/29] Update documentation
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index b947fc0..2fb0242 100644
--- a/README.md
+++ b/README.md
@@ -156,11 +156,11 @@ c.Spawner.environment = {"FOO": "BAR"}
c.Spawner.workspace_images = ["mltooling/ml-workspace-r:0.8.7"]
```
-##### Docker-local
+**Docker-local**
In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jupyterhub_user_config.py`. Have a look at the [DockerSpawner properties](https://github.com/jupyterhub/dockerspawner/blob/master/dockerspawner/dockerspawner.py) to see what can be configured.
-##### Kubernetes
+**Kubernetes**
When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured for the Spawner.
@@ -197,11 +197,11 @@ You can activate ssl via the environment variable `SSL_ENABLED`. If you don't pr
If you have an own certificate, mount the certificate and key files as `cert.crt` and `cert.key`, respectively, as read-only at `/resources/ssl`, so that the container has access to `/resources/ssl/cert.crt` and `/resources/ssl/cert.key`.
-#### Docker-local
+**Docker-local**
For Docker, mount a volume at the path like `-v my-ssl-files:/resources/ssl`.
-#### Kubernetes
+**Kubernetes**
For Kubernetes, add following lines to the `config.yaml` file (based on [setup-manual-https.](https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/security.html#set-up-manual-https)):
From e2df9c1d9486cd0e17a3bb5119eb525658f2f4cb Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:33:14 +0100
Subject: [PATCH 18/29] Update documentation
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 2fb0242..d2661d1 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ Here are the additional environment variables for the hub:
EXECUTION_MODE |
Defines in which execution mode the hub is running in. Value is one of [local | k8s] |
- local (If you use the helm chart, the value is already set to k8s) |
+ local (if you use the helm chart, the value is already set to k8s) |
DYNAMIC_WHITELIST_ENABLED |
@@ -131,7 +131,7 @@ Here are the additional environment variables for the hub:
Interval in which expired and not-used resources are deleted. Set to -1 to disable the automatic cleanup. For more information, see Section Cleanup Service.
|
- 3600 (currently disabled in Kubernetes) |
+ 3600 (currently disabled in Kubernetes) |
From 4ff0478625570c3fab06ab8ab8091a48670e6aec Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:45:12 +0100
Subject: [PATCH 19/29] Update documentation. Provide a "Overview in a
Nutshell" Section
---
README.md | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index d2661d1..3721085 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
Contribution
-MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with complete focus on Docker and Kubernetes. MLHub allows to create and manage multiple [workspaces](https://github.com/ml-tooling/ml-workspace), for example to distribute them to a group of people or within a team.
+MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with complete focus on Docker and Kubernetes. MLHub allows to create and manage multiple [workspaces](https://github.com/ml-tooling/ml-workspace), for example to distribute them to a group of people or within a team. The standard configuration allows a setup within seconds.
## Highlights
@@ -35,6 +35,13 @@ MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with co
- 🎛 Tunnel SSH connections to workspace containers.
- 🐳 Focused on Docker and Kubernetes with enhanced functionality.
+## Overview in a Nutshell
+
+- It can be configured like JupyterHub with a normal JupyterHub configuration, with minor adjustments in the Kubernetes scenario.
+- The documentation provides an overview of how to use and configure it in Docker-local and Kubernetes mode.
+- More information about the Helm chart resources for Kubernetes can be found [here](https://github.com/ml-tooling/zero-to-mlhub-k8s).
+- We created two custom Spawners that are based on the official [DockerSpawner](https://github.com/jupyterhub/dockerspawner) and [KubeSpawner](https://github.com/jupyterhub/kubespawner) and, hence, support their configurations set via the JupyterHub config.
+
## Getting Started
### Prerequisites
@@ -70,25 +77,17 @@ NAMESPACE=$RELEASE # change if needed
helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
# In case you just want to use the templating mechanism of Helm without deploying tiller on your cluster
-# 1. Use the "helm template ..." command
+# 1. Use the "helm template ..." command. The template command also excepts flags such as --config and --set-file as described in the respective Sections in this documentation.
# 2. kubectl apply -f templates/hub && kubectl apply -f templates/proxy
```
You can find the chart file attached to the [release](https://github.com/ml-tooling/ml-hub/releases).
-
-
-
-
-
### Configuration
#### Default Login
-When using the default config - so leaving the JupyterHub config `c.Authenticator.admin_users` as it is -, a user named `admin` can access the hub with admin rights. If you use the default `NativeAuthenticator` as authenticator, youc must register the user `admin` with a password of your choice first before login in.
+When using the default config - so leaving the JupyterHub config `c.Authenticator.admin_users` as it is -, a user named `admin` can access the hub with admin rights. If you use the default `NativeAuthenticator` as authenticator, you must register the user `admin` with a password of your choice first before login in.
If you use a different authenticator, you might want to set a different user as initial admin user as well, for example in case of using oauth you want to set `c.Authenticator.admin_users` to a username returned by the oauth login.
#### Environment Variables
@@ -117,7 +116,7 @@ Here are the additional environment variables for the hub:
EXECUTION_MODE |
Defines in which execution mode the hub is running in. Value is one of [local | k8s] |
- local (if you use the helm chart, the value is already set to k8s) |
+ local (if you use the helm chart, the value is already set to k8s) |
DYNAMIC_WHITELIST_ENABLED |
@@ -131,7 +130,7 @@ Here are the additional environment variables for the hub:
Interval in which expired and not-used resources are deleted. Set to -1 to disable the automatic cleanup. For more information, see Section Cleanup Service.
|
- 3600 (currently disabled in Kubernetes) |
+ 3600 (currently disabled in Kubernetes) |
@@ -233,6 +232,8 @@ proxy:
We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](#configuration).
+All resources created by our custom spawners are labeled (Docker / Kubernetes labels) with the labels `mlhub.origin` set to the Hub name `$ENV_HUB_NAME`, `mlhub.user` set to the JupyterHub user the resources belongs to, and `mlhub.server_name` to the named server name. For example, if the hub name is "mlhub" and a user named "foo" has a named server "bar", the labels would be `mlhub.origin=mlhub`, `mlhub.user=foo`, `mlhub.server_name=bar`.
+
#### DockerSpawner
- We create a separate Docker network for each user, which means that (named) workspaces of the same user can see each other but workspaces of different users cannot see each other. Doing so adds another security layer in case a user starts a service within the own workspace and does not properly secure it.
@@ -241,7 +242,6 @@ We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/res
- Create / delete services for a workspace, so that the hub can access them via Kubernetes DNS.
-All resources created by our custom spawners are labeled (Docker / Kubernetes labels) with the labels `mlhub.origin` set to the Hub name `$ENV_HUB_NAME`, `mlhub.user` set to the JupyterHub user the resources belongs to, and `mlhub.server_name` to the named server name. For example, if the hub name is "mlhub" and a user named "foo" has a named server "bar", the labels would be `mlhub.origin=mlhub`, `mlhub.user=foo`, `mlhub.server_name=bar`.
## Support
From ccb6ef0be4730e5ab5a10c4f774c2c5510e064f4 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 14:52:19 +0100
Subject: [PATCH 20/29] Update documentation
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 3721085..03b3024 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with co
## Overview in a Nutshell
-- It can be configured like JupyterHub with a normal JupyterHub configuration, with minor adjustments in the Kubernetes scenario.
+- MLHub can be configured like JupyterHub with a normal JupyterHub configuration, with minor adjustments in the Kubernetes scenario.
- The documentation provides an overview of how to use and configure it in Docker-local and Kubernetes mode.
- More information about the Helm chart resources for Kubernetes can be found [here](https://github.com/ml-tooling/zero-to-mlhub-k8s).
- We created two custom Spawners that are based on the official [DockerSpawner](https://github.com/jupyterhub/dockerspawner) and [KubeSpawner](https://github.com/jupyterhub/kubespawner) and, hence, support their configurations set via the JupyterHub config.
@@ -172,7 +172,7 @@ Additionally to the `jupyterhub_user_config.py`, which can be used to configure
```yaml
-config:
+mlhub:
baseUrl: "/mlhub" # corresponds to c.JupyterHub.base_url
debug: true # corresponds to c.JupyterHub.debug
secretToken: <32 characters random string base64 encoded> # corresponds to c.JupyterHub.proxy_auth_token
From 0a06d222d061f9fc48643b0c3a367328728ebd8a Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Thu, 16 Jan 2020 16:03:59 +0100
Subject: [PATCH 21/29] Update documentation with an example
---
README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/README.md b/README.md
index 03b3024..1d34288 100644
--- a/README.md
+++ b/README.md
@@ -311,6 +311,73 @@ The service has two endpoints which can be reached under the Hub service url `/s
If you want to have your own logo in the corner, place it at `/usr/local/share/jupyterhub/static/images/jupyter.png` inside the hub container.
+
+Do you have an example for Kubernetes? (click to expand...)
+
+Following setup is tested and should work. It uses AzureOAuth as the authenticator and has HTTPS enabled.
+
+*Command*
+
+```
+helm upgrade \
+ --install mlhub \
+ mlhub-chart-2.0.0.tgz \
+ --namespace mlhub \
+ --values config.yaml \
+ --set-file userConfig=./jupyterhub_user_config.py
+```
+
+*Folder structure*
+
+```
+ .
+ /config.yaml
+ /jupyterhub_user_config.yaml
+```
+
+*config.yaml*
+
+```yaml
+
+mlhub:
+ env:
+ SSL_ENABLED: true
+ AAD_TENANT_ID: ""
+c.AzureAdOAuthenticator.client_secret = ""
+c.AzureAdOAuthenticator.admin_users = ["some-user"]
+c.AzureAdOAuthenticator.tenant_id = os.environ.get('AAD_TENANT_ID')
+
+```
+
+
+
What are the additional environment variables I have seen in the code? (click to expand...)
From 37e47d2ef29337978231387381cc069564372cc4 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Fri, 17 Jan 2020 18:02:32 +0100
Subject: [PATCH 22/29] Add the helmchart components to this repository from
https://github.com/ml-tooling/zero-to-mlhub-k8s
---
.gitignore | 4 +-
Dockerfile | 23 +-
README.md | 15 +-
build.py | 23 +-
helmchart/LICENSE | 238 ++++++++
helmchart/README.md | 23 +
helmchart/mlhub/.helmignore | 21 +
helmchart/mlhub/Chart.yaml | 10 +
helmchart/mlhub/schema.yaml | 528 ++++++++++++++++++
helmchart/mlhub/templates/NOTES.txt | 38 ++
helmchart/mlhub/templates/_helpers.tpl | 295 ++++++++++
helmchart/mlhub/templates/hub/configmap.yaml | 17 +
helmchart/mlhub/templates/hub/deployment.yaml | 185 ++++++
.../hub/image-credentials-secret.yaml | 12 +
helmchart/mlhub/templates/hub/netpol.yaml | 34 ++
helmchart/mlhub/templates/hub/pdb.yaml | 13 +
helmchart/mlhub/templates/hub/pvc.yaml | 25 +
helmchart/mlhub/templates/hub/rbac.yaml | 40 ++
helmchart/mlhub/templates/hub/secret.yaml | 18 +
helmchart/mlhub/templates/hub/service.yaml | 35 ++
.../mlhub/templates/hub/user-configmap.yaml | 9 +
helmchart/mlhub/templates/ingress.yaml | 29 +
.../mlhub/templates/proxy/deployment.yaml | 124 ++++
helmchart/mlhub/templates/proxy/netpol.yaml | 65 +++
helmchart/mlhub/templates/proxy/pdb.yaml | 13 +
helmchart/mlhub/templates/proxy/secret.yaml | 15 +
helmchart/mlhub/templates/proxy/service.yaml | 75 +++
.../mlhub/templates/singleuser/netpol.yaml | 40 ++
helmchart/mlhub/validate.py | 9 +
helmchart/mlhub/values.yaml | 146 +++++
resources/jupyterhub_config.py | 3 +-
resources/kubernetes/config.yaml | 15 -
.../kubernetes/jupyterhub_chart_config.py | 108 ++++
.../mlhubspawner/mlhubkubernetesspawner.py | 1 +
34 files changed, 2212 insertions(+), 37 deletions(-)
create mode 100644 helmchart/LICENSE
create mode 100644 helmchart/README.md
create mode 100644 helmchart/mlhub/.helmignore
create mode 100644 helmchart/mlhub/Chart.yaml
create mode 100644 helmchart/mlhub/schema.yaml
create mode 100644 helmchart/mlhub/templates/NOTES.txt
create mode 100644 helmchart/mlhub/templates/_helpers.tpl
create mode 100644 helmchart/mlhub/templates/hub/configmap.yaml
create mode 100644 helmchart/mlhub/templates/hub/deployment.yaml
create mode 100644 helmchart/mlhub/templates/hub/image-credentials-secret.yaml
create mode 100644 helmchart/mlhub/templates/hub/netpol.yaml
create mode 100644 helmchart/mlhub/templates/hub/pdb.yaml
create mode 100644 helmchart/mlhub/templates/hub/pvc.yaml
create mode 100644 helmchart/mlhub/templates/hub/rbac.yaml
create mode 100644 helmchart/mlhub/templates/hub/secret.yaml
create mode 100644 helmchart/mlhub/templates/hub/service.yaml
create mode 100644 helmchart/mlhub/templates/hub/user-configmap.yaml
create mode 100644 helmchart/mlhub/templates/ingress.yaml
create mode 100644 helmchart/mlhub/templates/proxy/deployment.yaml
create mode 100644 helmchart/mlhub/templates/proxy/netpol.yaml
create mode 100644 helmchart/mlhub/templates/proxy/pdb.yaml
create mode 100644 helmchart/mlhub/templates/proxy/secret.yaml
create mode 100644 helmchart/mlhub/templates/proxy/service.yaml
create mode 100644 helmchart/mlhub/templates/singleuser/netpol.yaml
create mode 100755 helmchart/mlhub/validate.py
create mode 100644 helmchart/mlhub/values.yaml
delete mode 100755 resources/kubernetes/config.yaml
create mode 100644 resources/kubernetes/jupyterhub_chart_config.py
diff --git a/.gitignore b/.gitignore
index fba41d0..8ee1a6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,6 @@ hs_err_pid*
*.log
*.swp
temp/*
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+helmchart/*.tgz
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 85b79c0..ea69104 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -106,20 +106,21 @@ RUN \
### INCUBATION ZONE ###
# Kubernetes Support
-ADD https://raw.githubusercontent.com/ml-tooling/zero-to-mlhub-k8s/master/images/hub/z2jh.py /usr/local/lib/python3.6/dist-packages/z2jh.py
ADD https://raw.githubusercontent.com/ml-tooling/zero-to-mlhub-k8s/master/images/hub/cull_idle_servers.py /usr/local/bin/cull_idle_servers.py
+ADD resources/kubernetes/jupyterhub_chart_config.py $_RESOURCES_PATH/jupyterhub_chart_config.py
# Copy the jupyterhub config that has a lot of options to be configured
-ADD https://raw.githubusercontent.com/ml-tooling/zero-to-mlhub-k8s/master/images/hub/jupyterhub_config.py $_RESOURCES_PATH/kubernetes/jupyterhub_chart_config.py
-ADD https://raw.githubusercontent.com/ml-tooling/zero-to-mlhub-k8s/master/images/hub/requirements.txt /tmp/requirements.txt
-RUN PYCURL_SSL_LIBRARY=openssl pip3 install --no-cache-dir \
- -r /tmp/requirements.txt && \
- chmod u+rx /usr/local/bin/cull_idle_servers.py && \
- chmod u+rx /usr/local/lib/python3.6/dist-packages/z2jh.py && \
- # Cleanup
- clean-layer.sh
-
-RUN pip3 install oauthenticator psutil
+RUN chmod u+rx /usr/local/bin/cull_idle_servers.py
+
+RUN pip3 install oauthenticator psutil yamlreader pyjwt \
+ # https://github.com/jupyterhub/kubespawner
+ # https://pypi.org/project/jupyterhub-kubespawner
+ jupyterhub-kubespawner==0.11.* \
+ # https://github.com/kubernetes-client/python
+ # https://pypi.org/project/kubernetes
+ kubernetes==10.0.* \
+ # https://pypi.org/project/pycurl/
+ pycurl==7.43.0.*
RUN apt-get update && apt-get install -y pcregrep && clean-layer.sh
### END INCUBATION ZONE ###
diff --git a/README.md b/README.md
index 1d34288..bad6f9b 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with co
- MLHub can be configured like JupyterHub with a normal JupyterHub configuration, with minor adjustments in the Kubernetes scenario.
- The documentation provides an overview of how to use and configure it in Docker-local and Kubernetes mode.
-- More information about the Helm chart resources for Kubernetes can be found [here](https://github.com/ml-tooling/zero-to-mlhub-k8s).
+- More information about the Helm chart resources for Kubernetes can be found [here](https://github.com/ml-tooling/ml-hub/tree/master/helmchart).
- We created two custom Spawners that are based on the official [DockerSpawner](https://github.com/jupyterhub/dockerspawner) and [KubeSpawner](https://github.com/jupyterhub/kubespawner) and, hence, support their configurations set via the JupyterHub config.
## Getting Started
@@ -74,7 +74,7 @@ Via Helm:
RELEASE=mlhub # change if needed
NAMESPACE=$RELEASE # change if needed
-helm upgrade --install $RELEASE mlhub-chart-1.0.1.tgz --namespace $NAMESPACE
+helm upgrade --install $RELEASE mlhub-chart-2.0.0.tgz --namespace $NAMESPACE
# In case you just want to use the templating mechanism of Helm without deploying tiller on your cluster
# 1. Use the "helm template ..." command. The template command also excepts flags such as --config and --set-file as described in the respective Sections in this documentation.
@@ -184,7 +184,7 @@ mlhub:
You can pass the file via `--values config.yaml`. The complete command would look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --values config.yaml`. The `--set-file userConfig=./jupyterhub_user_config.py` flag can additionally be set.
-You can find the Helm chart resources in the repository [zero-to-mlhub-with-kubernetes](https://github.com/ml-tooling/zero-to-mlhub-k8s); the [values file](https://github.com/ml-tooling/zero-to-mlhub-k8s/blob/master/jupyterhub/values.yaml) contains the default values for the deployment.
+You can find the Helm chart resources, including the values file that contains the default values, in the directory `helmchart`).
### Enable SSL/HTTPS
@@ -206,7 +206,7 @@ For Kubernetes, add following lines to the `config.yaml` file (based on [setup-m
```yaml
-config:
+mlhub:
env:
SSL_ENABLED: true
@@ -396,19 +396,18 @@ Via the START\_* environment variables you can define what is started within the
START_NGINX |
- Whether or not to start the nginx proxy. If the Hub should be used without additional tool routing to workspaces, this could be disabled. SSH port 22 would need to be published separately then. This option is built-in to work with zero-to-mlhub-k8s
+ | Whether or not to start the nginx proxy. If the Hub should be used without additional tool routing to workspaces, this could be disabled. SSH port 22 would need to be published separately then. This option is built-in to work with our Kubernetes Helm chart.
|
true |
START_JHUB |
- Start the JupyterHub hub. This option is built-in to work with
- zero-to-mlhub-k8s, where the image is also used as the CHP image. |
+ Start the JupyterHub hub. |
true |
START_CHP |
- Start the JupyterHub proxy process separately (The hub should not start the proxy itself, which can be configured via the JupyterHub config file. This option is built-in to work with zero-to-mlhub-k8s, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
+ Start the JupyterHub proxy process separately (The hub should not start the proxy itself, which can be configured via the JupyterHub config file. This option is built-in to work with our Kubernetes Helm chart, where the image is also used as the Configurable-Http-Proxy (CHP) image. Additional arguments to the chp-start command can be passed to the container by passing an environment variable ADDITIONAL_ARGS, e.g. --env ADDITIONAL_ARGS="--ip=0.0.0.0 --api-ip=0.0.0.0". |
false |
diff --git a/build.py b/build.py
index 42d220e..6e3d415 100644
--- a/build.py
+++ b/build.py
@@ -84,4 +84,25 @@ def build(module="."):
if "SNAPSHOT" not in args.version:
# do not push SNAPSHOT builds as latest version
- call("docker push " + remote_latest_image)
\ No newline at end of file
+ call("docker push " + remote_latest_image)
+
+# Create the Helm chart resource
+import fileinput
+
+chart_yaml = "./helmchart/mlhub/Chart.yaml"
+values_yaml = "./helmchart/mlhub/values.yaml"
+with fileinput.FileInput(chart_yaml, inplace=True, backup='.bak') as file:
+ for line in file:
+ print(line.replace("$VERSION", str(args.version)), end='')
+
+with fileinput.FileInput(values_yaml, inplace=True, backup='.bak') as file:
+ for line in file:
+ print(line.replace("$VERSION", str(args.version)), end='')
+
+try:
+ call("helm package ./helmchart/mlhub -d helmchart")
+except:
+ print("There was a problem with the helm command")
+
+os.replace(f"{chart_yaml}.bak", chart_yaml)
+os.replace(f"{values_yaml}.bak", values_yaml)
diff --git a/helmchart/LICENSE b/helmchart/LICENSE
new file mode 100644
index 0000000..6c56bf5
--- /dev/null
+++ b/helmchart/LICENSE
@@ -0,0 +1,238 @@
+BSD 3-Clause License
+
+Copyright (c) 2017, Project Jupyter Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2016 Allan Wu
+ Copyright 2016 Derrick Mar
+ Copyright 2016 Jeff Gong
+ Copyright 2016 Peter Veerman
+ Copyright 2016 Ryan Lovett
+ Copyright 2016 Sam Lau
+ Copyright 2016 Yuvi Panda
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/helmchart/README.md b/helmchart/README.md
new file mode 100644
index 0000000..fe487b6
--- /dev/null
+++ b/helmchart/README.md
@@ -0,0 +1,23 @@
+# Zero to MLHub with Kubernetes
+
+This directory contains a *Helm chart* for a default configuration to use MLHub.
+
+## MLHub Modifications
+
+It is inspired and partially based on the great *[Zero to JupyterHub K8s](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) project*.
+However, we made some modifications for two reasons; first, to get it work with ml-hub and and [ml-workspace](https://github.com/ml-tooling/ml-workspace). Second, to simplify the setup. *Zero to Jupyterhub K8s* is very sophisticated but adds an extra layer of complexity by, for example, not directly enabling you to configure KubeSpawner as you are used to by Jupyterhub via it's `c.Spawner` config but via mixing it with infrastructure configuration in the same `values.yaml` file. They have a great documentation about pre-pulling images, auto-scaling the cluster etc. So if this simple version is not enough, we recommend to check out their project and documentation.
+
+Most prominent changes to the "original" project:
+- change of the command fields in hub and proxy yamls
+- modifying ports to make tunnelling of ssh possible
+- changes of default values, e.g. the used images
+- changes of paths, e.g. the ssl secret mount path
+- separated the deployment configuration and the configuration for JupyterHub & the Spawner
+- removed a few Kubernetes resources such as the image puller etc.
+
+We do not push the helm chart to a repository for now, so feel free to download it from the [mlhub releases page](https://github.com/ml-tooling/ml-hub/releases) or to create the package yourself via the `helm package` command.
+
+You can then deploy the chart via `helm upgrade --install mlhub packaged-chart.tgz --namespace $namespace --values config.yaml --set-file userConfig=./jupyterhub_user_config.py`.
+The `config.yaml` can be used to overrride default deployment values, the `userConfig` can be used to configure JupyterHub and the Spawner.
+
+For more details, check out the main [readme](https://github.com/ml-tooling/ml-hub).
diff --git a/helmchart/mlhub/.helmignore b/helmchart/mlhub/.helmignore
new file mode 100644
index 0000000..f0c1319
--- /dev/null
+++ b/helmchart/mlhub/.helmignore
@@ -0,0 +1,21 @@
+# 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
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
diff --git a/helmchart/mlhub/Chart.yaml b/helmchart/mlhub/Chart.yaml
new file mode 100644
index 0000000..80a51b9
--- /dev/null
+++ b/helmchart/mlhub/Chart.yaml
@@ -0,0 +1,10 @@
+name: mlhub
+version: $VERSION
+appVersion: 1.0.0
+description: Multi-user Jupyter installation
+home: https://github.com/ml-tooling/ml-hub
+sources:
+ - https://github.com/ml-tooling/ml-hub/helmchart
+icon: https://jupyter.org/assets/hublogo.svg
+kubeVersion: '>=1.11.0-0'
+tillerVersion: '>=2.11.0-0'
diff --git a/helmchart/mlhub/schema.yaml b/helmchart/mlhub/schema.yaml
new file mode 100644
index 0000000..21cbce0
--- /dev/null
+++ b/helmchart/mlhub/schema.yaml
@@ -0,0 +1,528 @@
+title: Config
+type: object
+properties:
+
+ mlhub:
+ secretToken:
+ type: string
+ description: |
+ A 32-byte cryptographically secure randomly generated string used to secure communications
+ between the hub and the configurable-http-proxy.
+
+ ```sh
+ # to generate a value, run
+ openssl rand -hex 32
+ ```
+
+ Changing this value will cause the proxy and hub pods to restart. It is good security
+ practice to rotate these values over time. If this secret leaks, *immediately* change
+ it to something else, or user data can be compromised
+
+ hub:
+ type: object
+ properties:
+ imagePullPolicy:
+ type: string
+ enum:
+ - IfNotPresent
+ - Always
+ - Never
+ description: |
+ Set the imagePullPolicy on the hub pod.
+
+ See the [Kubernetes docs](https://kubernetes.io/docs/concepts/containers/images/#updating-images)
+ for more info on what the values mean.
+ imagePullSecret:
+ type: object
+ description: |
+ Creates an image pull secret for you and makes the hub pod utilize
+ it, allowing it to pull images from private image registries.
+
+ Using this configuration option automates the following steps that
+ normally is required to pull from private image registries.
+
+ ```sh
+ # you won't need to run this manually...
+ kubectl create secret docker-registry hub-image-credentials \
+ --docker-server= \
+ --docker-username= \
+ --docker-email= \
+ --docker-password=
+ ```
+
+ ```yaml
+ # you won't need to specify this manually...
+ spec:
+ imagePullSecrets:
+ - name: hub-image-credentials
+ ```
+
+ To learn the username and password fields to access a gcr.io registry
+ from a Kubernetes cluster not associated with the same google cloud
+ credentials, look into [this
+ guide](http://docs.heptio.com/content/private-registries/pr-gcr.html)
+ and read the notes about the password.
+ properties:
+ enabled:
+ type: boolean
+ description: |
+ Enable the creation of a Kubernetes Secret containing credentials
+ to access a image registry. By enabling this, the hub pod will also be configured
+ to use these credentials when it pulls its container image.
+ registry:
+ type: string
+ description: |
+ Name of the private registry you want to create a credential set
+ for. It will default to Docker Hub's image registry.
+
+ Examples:
+ - https://index.docker.io/v1/
+ - quay.io
+ - eu.gcr.io
+ - alexmorreale.privatereg.net
+ username:
+ type: string
+ description: |
+ Name of the user you want to use to connect to your private
+ registry. For external gcr.io, you will use the `_json_key`.
+
+ Examples:
+ - alexmorreale
+ - alex@pfc.com
+ - _json_key
+ password:
+ type: string
+ description: |
+ Password of the user you want to use to connect to your private
+ registry.
+
+ Examples:
+ - plaintextpassword
+ - abc123SECRETzyx098
+
+ For gcr.io registries the password will be a big JSON blob for a
+ Google cloud service account, it should look something like below.
+
+ ```yaml
+ password: |-
+ {
+ "type": "service_account",
+ "project_id": "jupyter-se",
+ "private_key_id": "f2ba09118a8d3123b3321bd9a7d6d0d9dc6fdb85",
+ ...
+ }
+ ```
+
+ Learn more in [this
+ guide](http://docs.heptio.com/content/private-registries/pr-gcr.html).
+ image:
+ type: object
+ description: |
+ Set custom image name / tag for the hub pod.
+
+ Use this to customize which hub image is used. Note that you must use a version of
+ the hub image that was bundled with this particular version of the helm-chart - using
+ other images might not work.
+ properties:
+ name:
+ type: string
+ description: |
+ Name of the image, without the tag.
+
+ ```
+ # example names
+ yuvipanda/wikimedia-hub
+ gcr.io/my-project/my-hub
+ ```
+ tag:
+ type: string
+ description: |
+ The tag of the image to pull.
+
+ This is the value after the `:` in your full image name.
+
+ ```
+ # example tags
+ v1.11.1
+ zhy270a
+ ```
+ db:
+ type: object
+ properties:
+ pvc:
+ type: object
+ description: |
+ Customize the Persistent Volume Claim used when `hub.db.type` is `sqlite-pvc`.
+ properties:
+ annotations:
+ type: object
+ description: |
+ Annotations to apply to the PVC containing the sqlite database.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
+ for more details about annotations.
+ selector:
+ type: object
+ description: |
+ Label selectors to set for the PVC containing the sqlite database.
+
+ Useful when you are using a specific PV, and want to bind to
+ that and only that.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims)
+ for more details about using a label selector for what PV to
+ bind to.
+ storage:
+ type: string
+ description: |
+ Size of disk to request for the database disk.
+ labels:
+ type: object
+ description: |
+ Extra labels to add to the hub pod.
+
+ See the [Kubernetes docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
+ to learn more about labels.
+ initContainers:
+ type: list
+ description: |
+ list of initContainers to be run with hub pod. See [Kubernetes Docs](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/)
+
+ ```yaml
+ hub:
+ initContainers:
+ - name: init-myservice
+ image: busybox:1.28
+ command: ['sh', '-c', 'command1']
+ - name: init-mydb
+ image: busybox:1.28
+ command: ['sh', '-c', 'command2']
+ ```
+ extraEnv:
+ type: object
+ description: |
+ Extra environment variables that should be set for the hub pod.
+
+ ```yaml
+ hub:
+ extraEnv:
+ MY_ENV_VARS_NAME: "my env vars value"
+ ```
+
+ **NOTE**: We still support this field being a list of
+ [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#envvar-v1-core)
+ objects as well.
+
+ These are usually used in two circumstances:
+ - Passing parameters to some custom code specified with `extraConfig`
+ - Passing parameters to an authenticator or spawner that can be directly customized
+ by environment variables (rarer)
+ uid:
+ type: integer
+ minimum: 0
+ description:
+ The UID the hub process should be running as.
+
+ Use this only if you are building your own image & know that a user with this uid
+ exists inside the hub container! Advanced feature, handle with care!
+
+ Defaults to 1000, which is the uid of the `jovyan` user that is present in the
+ default hub image.
+ fsGid:
+ type: integer
+ minimum: 0
+ description:
+ The gid the hub process should be using when touching any volumes mounted.
+
+ Use this only if you are building your own image & know that a group with this gid
+ exists inside the hub container! Advanced feature, handle with care!
+
+ Defaults to 1000, which is the gid of the `jovyan` user that is present in the
+ default hub image.
+ service:
+ type: object
+ description: |
+ Object to configure the service the JupyterHub will be exposed on by the Kubernetes server.
+ properties:
+ type:
+ type: string
+ enum:
+ - ClusterIP
+ - NodePort
+ - LoadBalancer
+ - ExternalName
+ description: |
+ The Kubernetes ServiceType to be used.
+
+ The default type is `ClusterIP`.
+ See the [Kubernetes docs](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types)
+ to learn more about service types.
+ loadBalancerIP:
+ type: string
+ description: |
+ The public IP address the hub service should be exposed on.
+
+ This sets the IP address that should be used by the LoadBalancer for exposing the hub service.
+ Set this if you want the hub service to be provided with a fixed external IP address instead of a dynamically acquired one.
+ Useful to ensure a stable IP to access to the hub with, for example if you have reserved an IP address in your network to communicate with the JupyterHub.
+
+ To be provided like:
+ ```
+ hub:
+ service:
+ loadBalancerIP: xxx.xxx.xxx.xxx
+ ```
+ ports:
+ type: object
+ description: |
+ Object to configure the ports the hub service will be deployed on.
+ properties:
+ nodePort:
+ type: integer
+ description: |
+ The nodePort to deploy the hub service on.
+ annotations:
+ type: object
+ description: |
+ Kubernetes annotations to apply to the hub service.
+ pdb:
+ type: object
+ description: |
+ Set the Pod Disruption Budget for the hub pod.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)
+ for more details about disruptions.
+ properties:
+ enabled:
+ type: boolean
+ description: |
+ Whether PodDisruptionBudget is enabled for the hub pod.
+ minAvailable:
+ type: integer
+ description: |
+ Minimum number of pods to be available during the voluntary disruptions.
+
+ proxy:
+ type: object
+ properties:
+ service:
+ type: object
+ description: |
+ Object to configure the service the JupyterHub's proxy will be exposed on by the Kubernetes server.
+ properties:
+ type:
+ type: string
+ enum:
+ - ClusterIP
+ - NodePort
+ - LoadBalancer
+ - ExternalName
+ description: |
+ See `hub.service.type`.
+ labels:
+ type: object
+ description: |
+ Extra labels to add to the proxy service.
+
+ See the [Kubernetes docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
+ to learn more about labels.
+ annotations:
+ type: object
+ description: |
+ Annotations to apply to the service that is exposing the proxy.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
+ for more details about annotations.
+ nodePorts:
+ type: object
+ description: |
+ Object to set NodePorts to expose the service on for http and https.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport)
+ for more details about NodePorts.
+ properties:
+ http:
+ type: integer
+ description: |
+ The HTTP port the proxy-public service should be exposed on.
+ https:
+ type: integer
+ description: |
+ The HTTPS port the proxy-public service should be exposed on.
+ loadBalancerIP:
+ type: string
+ description: |
+ See `hub.service.loadBalancerIP`
+ https:
+ type: object
+ description: |
+ Object for customizing the settings for HTTPS used by the JupyterHub's proxy.
+ For more information on configuring HTTPS for your JupyterHub, see the [HTTPS section in our security guide](https://zero-to-jupyterhub.readthedocs.io/en/stable/security.html?highlight=security#https)
+ properties:
+ enabled:
+ type: boolean
+ description: |
+ Indicator to set whether HTTPS should be enabled or not on the proxy. Defaults to `true` if the https object is provided.
+ type:
+ type: string
+ enum:
+ - letsencrypt
+ - manual
+ - offload
+ - secret
+ description: |
+ The type of HTTPS encryption that is used.
+ Decides on which ports and network policies are used for communication via HTTPS. Setting this to `secret` sets the type to manual HTTPS with a secret that has to be provided in the `https.secret` object.
+ Defaults to `letsencrypt`.
+ letsencrypt:
+ type: object
+ properties:
+ contactEmail:
+ type: string
+ description: |
+ The contact email to be used for automatically provisioned HTTPS certificates by Let's Encrypt. For more information see [Set up automatic HTTPS](https://zero-to-jupyterhub.readthedocs.io/en/stable/security.html?highlight=security#set-up-automatic-https).
+ Required for automatic HTTPS.
+ manual:
+ type: object
+ description: |
+ Object for providing own certificates for manual HTTPS configuration. To be provided when setting `https.type` to `manual`.
+ See [Set up manual HTTPS](https://zero-to-jupyterhub.readthedocs.io/en/stable/security.html?highlight=security#set-up-manual-https)
+ properties:
+ key:
+ type: string
+ description: |
+ The RSA private key to be used for HTTPS.
+ To be provided in the form of
+
+ ```
+ key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ ...
+ -----END RSA PRIVATE KEY-----
+ ```
+ cert:
+ type: string
+ description: |
+ The certificate to be used for HTTPS.
+ To be provided in the form of
+
+ ```
+ cert: |
+ -----BEGIN CERTIFICATE-----
+ ...
+ -----END CERTIFICATE-----
+ ```
+ secret:
+ type: object
+ description: |
+ Secret to be provided when setting `https.type` to `secret`.
+ properties:
+ name:
+ type: string
+ description: |
+ Name of the secret
+ key:
+ type: string
+ description: |
+ Path to the private key to be used for HTTPS.
+ Example: `'tls.key'`
+ crt:
+ type: string
+ description: |
+ Path to the certificate to be used for HTTPS.
+ Example: `'tls.crt'`
+ hosts:
+ type: list
+ description: |
+ You domain in list form.
+ Required for automatic HTTPS. See [Set up automatic HTTPS](https://zero-to-jupyterhub.readthedocs.io/en/stable/security.html?highlight=security#set-up-automatic-https).
+ To be provided like:
+ ```
+ hosts:
+ -
+ ```
+ pdb:
+ type: object
+ description: |
+ Set the Pod Disruption Budget for the proxy pod.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)
+ for more details about disruptions.
+ properties:
+ enabled:
+ type: boolean
+ description: |
+ Whether PodDisruptionBudget is enabled for the proxy pod.
+ minAvailable:
+ type: integer
+ description: |
+ Minimum number of pods to be available during the voluntary disruptions.
+ required:
+ - secretToken
+
+ singleuser:
+ type: object
+ description: |
+ Options for customizing the environment that is provided to the users after they log in.
+ properties:
+
+ ingress:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ description: |
+ Enable the creation of a Kubernetes Ingress to proxy-public service.
+
+ See [Advanced Topics — Zero to JupyterHub with Kubernetes 0.7.0 documentation]
+ (https://zero-to-jupyterhub.readthedocs.io/en/stable/advanced.html#ingress)
+ for more details.
+ annotations:
+ type: object
+ description: |
+ Annotations to apply to the Ingress.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
+ for more details about annotations.
+ hosts:
+ type: list
+ description: |
+ List of hosts to route requests to the proxy.
+ pathSuffix:
+ type: string
+ description: |
+ Suffix added to Ingress's routing path pattern.
+
+ Specify `*` if your ingress matches path by glob pattern.
+ tls:
+ type: list
+ description: |
+ TLS configurations for Ingress.
+
+ See [the Kubernetes
+ documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls)
+ for more details about annotations.
+
+ custom:
+ type: object
+ description: |
+ Additional values to pass to the Hub.
+ JupyterHub will not itself look at these,
+ but you can read values in your own custom config via `hub.extraConfig`.
+ For example:
+
+ ```yaml
+ custom:
+ myHost: "https://example.horse"
+ hub:
+ extraConfig:
+ myConfig.py: |
+ c.MyAuthenticator.host = get_config("custom.myHost")
+ ```
diff --git a/helmchart/mlhub/templates/NOTES.txt b/helmchart/mlhub/templates/NOTES.txt
new file mode 100644
index 0000000..a541739
--- /dev/null
+++ b/helmchart/mlhub/templates/NOTES.txt
@@ -0,0 +1,38 @@
+Thank you for installing JupyterHub!
+
+Your release is named {{ .Release.Name }} and installed into the namespace {{ .Release.Namespace }}.
+
+You can find if the hub and proxy is ready by doing:
+
+ kubectl --namespace={{ .Release.Namespace }} get pod
+
+and watching for both those pods to be in status 'Running'.
+
+You can find the public IP of the JupyterHub by doing:
+
+ kubectl --namespace={{ .Release.Namespace }} get svc proxy-public
+
+It might take a few minutes for it to appear!
+
+Note that this is still an alpha release! If you have questions, feel free to
+ 1. Read the guide at https://z2jh.jupyter.org
+ 2. Chat with us at https://gitter.im/jupyterhub/jupyterhub
+ 3. File issues at https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues
+
+{{- if .Values.hub.extraConfigMap }}
+
+DEPRECATION: hub.extraConfigMap is deprecated in jupyterhub chart 0.8.
+Use top-level `custom` instead:
+
+---
+custom:
+{{- (merge dict .Values.custom .Values.hub.extraConfigMap) | toYaml | nindent 2}}
+---
+{{- end }}
+
+{{- /* if and (not .Values.scheduling.podPriority.enabled) (and .Values.scheduling.userPlaceholder.enabled .Values.scheduling.userPlaceholder.replicas) }}
+
+WARNING: You are using user placeholders without pod priority enabled, either
+enable pod priority or stop using the user placeholders to avoid wasting cloud
+resources.
+{{- end */}}
\ No newline at end of file
diff --git a/helmchart/mlhub/templates/_helpers.tpl b/helmchart/mlhub/templates/_helpers.tpl
new file mode 100644
index 0000000..ce30086
--- /dev/null
+++ b/helmchart/mlhub/templates/_helpers.tpl
@@ -0,0 +1,295 @@
+{{- /*
+ ## About
+ This file contains helpers to systematically name, label and select Kubernetes
+ objects we define in the .yaml template files.
+
+
+ ## How helpers work
+ Helm helper functions is a good way to avoid repeating something. They will
+ generate some output based on one single dictionary of input that we call the
+ helpers scope. When you are in helm, you access your current scope with a
+ single a single punctuation (.).
+
+ When you ask a helper to render its content, one often forward the current
+ scope to the helper in order to allow it to access .Release.Name,
+ .Values.rbac.enabled and similar values.
+
+ #### Example - Passing the current scope
+ {{ include "jupyterhub.commonLabels" . }}
+
+ It would be possible to pass something specific instead of the current scope
+ (.), but that would make .Release.Name etc. inaccessible by the helper which
+ is something we aim to avoid.
+
+ #### Example - Passing a new scope
+ {{ include "demo.bananaPancakes" (dict "pancakes" 5 "bananas" 3) }}
+
+ To let a helper access the current scope along with additional values we have
+ opted to create dictionary containing additional values that is then populated
+ with additional values from the current scope through a the merge function.
+
+ #### Example - Passing a new scope augmented with the old
+ {{- $_ := merge (dict "appLabel" "kube-lego") . }}
+ {{- include "jupyterhub.matchLabels" $_ | nindent 6 }}
+
+ In this way, the code within the definition of `jupyterhub.matchLabels` will
+ be able to access .Release.Name and .appLabel.
+
+ NOTE:
+ The ordering of merge is crucial, the latter argument is merged into the
+ former. So if you would swap the order you would influence the current scope
+ risking unintentional behavior. Therefore, always put the fresh unreferenced
+ dictionary (dict "key1" "value1") first and the current scope (.) last.
+
+
+ ## Declared helpers
+ - appLabel |
+ - componentLabel |
+ - nameField | uses componentLabel
+ - commonLabels | uses appLabel
+ - labels | uses commonLabels
+ - matchLabels | uses labels
+ - podCullerSelector | uses matchLabels
+
+
+ ## Example usage
+ ```yaml
+ # Excerpt from proxy/autohttps/deployment.yaml
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: {{ include "jupyterhub.nameField" . }}
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+ spec:
+ selector:
+ matchLabels:
+ {{- $_ := merge (dict "appLabel" "kube-lego") . }}
+ {{- include "jupyterhub.matchLabels" $_ | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "jupyterhub.labels" $_ | nindent 8 }}
+ hub.jupyter.org/network-access-proxy-http: "true"
+ ```
+
+ NOTE:
+ The "jupyterhub.matchLabels" and "jupyterhub.labels" is passed an augmented
+ scope that will influence the helpers' behavior. It get the current scope
+ "." but merged with a dictionary containing extra key/value pairs. In this
+ case the "." scope was merged with a small dictionary containing only one
+ key/value pair "appLabel: kube-lego". It is required for kube-lego to
+ function properly. It is a way to override the default app label's value.
+*/}}
+
+
+{{- /*
+ jupyterhub.appLabel:
+ Used by "jupyterhub.labels".
+*/}}
+{{- define "jupyterhub.appLabel" -}}
+{{ .Values.nameOverride | default .Chart.Name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+
+{{- /*
+ jupyterhub.componentLabel:
+ Used by "jupyterhub.labels" and "jupyterhub.nameField".
+
+ NOTE: The component label is determined by either...
+ - 1: The provided scope's .componentLabel
+ - 2: The template's filename if living in the root folder
+ - 3: The template parent folder's name
+ - : ...and is combined with .componentPrefix and .componentSuffix
+*/}}
+{{- define "jupyterhub.componentLabel" -}}
+{{- $file := .Template.Name | base | trimSuffix ".yaml" -}}
+{{- $parent := .Template.Name | dir | base | trimPrefix "templates" -}}
+{{- $component := .componentLabel | default $parent | default $file -}}
+{{- $component := print (.componentPrefix | default "") $component (.componentSuffix | default "") -}}
+{{ $component }}
+{{- end }}
+
+
+{{- /*
+ jupyterhub.nameField:
+ Populates the name field's value.
+ NOTE: some name fields are limited to 63 characters by the DNS naming spec.
+
+ TODO:
+ - [ ] Set all name fields using this helper.
+ - [ ] Optionally prefix the release name based on some setting in
+ .Values to allow for multiple deployments within a single namespace.
+*/}}
+{{- define "jupyterhub.nameField" -}}
+{{- $name := print (.namePrefix | default "") (include "jupyterhub.componentLabel" .) (.nameSuffix | default "") -}}
+{{ printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+
+{{- /*
+ jupyterhub.commonLabels:
+ Foundation for "jupyterhub.labels".
+ Provides labels: app, release, (chart and heritage).
+*/}}
+{{- define "jupyterhub.commonLabels" -}}
+app: {{ .appLabel | default (include "jupyterhub.appLabel" .) }}
+release: {{ .Release.Name }}
+{{- if not .matchLabels }}
+chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+heritage: {{ .heritageLabel | default .Release.Service }}
+{{- end }}
+{{- end }}
+
+
+{{- /*
+ jupyterhub.labels:
+ Provides labels: component, app, release, (chart and heritage).
+*/}}
+{{- define "jupyterhub.labels" -}}
+component: {{ include "jupyterhub.componentLabel" . }}
+{{ include "jupyterhub.commonLabels" . }}
+{{- end }}
+
+
+{{- /*
+ jupyterhub.matchLabels:
+ Used to provide pod selection labels: component, app, release.
+*/}}
+{{- define "jupyterhub.matchLabels" -}}
+{{- $_ := merge (dict "matchLabels" true) . -}}
+{{ include "jupyterhub.labels" $_ }}
+{{- end }}
+
+
+# {{- /*
+# jupyterhub.dockersingleuserconfigjson:
+# Creates a base64 encoded docker registry json blob for use in a image pull
+# secret, just like the `kubectl create secret docker-registry` command does
+# for the generated secrets data.dockerconfigjson field. The output is
+# verified to be exactly the same even if you have a password spanning
+# multiple lines as you may need to use a private GCR registry.
+
+# - https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod
+# */}}
+# {{- define "jupyterhub.dockersingleuserconfigjson" -}}
+# {{ include "jupyterhub.dockersingleuserconfigjson.yaml" . | b64enc }}
+# {{- end }}
+
+# {{- define "jupyterhub.dockersingleuserconfigjson.yaml" -}}
+# {{- with .Values.singleuser.imagePullSecret -}}
+# {
+# "auths": {
+# {{ .registry | default "https://index.docker.io/v1/" | quote }}: {
+# "username": {{ .username | quote }},
+# "password": {{ .password | quote }},
+# {{- if .email }}
+# "email": {{ .email | quote }},
+# {{- end }}
+# "auth": {{ (print .username ":" .password) | b64enc | quote }}
+# }
+# }
+# }
+# {{- end }}
+# {{- end }}
+
+{{- /*
+ jupyterhub.dockerhubconfigjson:
+ Creates a base64 encoded docker registry json blob for use in a image pull
+ secret, just like the `kubectl create secret docker-registry` command does
+ for the generated secrets data.dockerhubconfigjson field. The output is
+ verified to be exactly the same even if you have a password spanning
+ multiple lines as you may need to use a private GCR registry.
+
+ - https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod
+*/}}
+{{- define "jupyterhub.dockerhubconfigjson" -}}
+{{ include "jupyterhub.dockerhubconfigjson.yaml" . | b64enc }}
+{{- end }}
+
+{{- define "jupyterhub.dockerhubconfigjson.yaml" -}}
+{{- with .Values.hub.imagePullSecret -}}
+{
+ "auths": {
+ {{ .registry | default "https://index.docker.io/v1/" | quote }}: {
+ "username": {{ .username | quote }},
+ "password": {{ .password | quote }},
+ {{- if .email }}
+ "email": {{ .email | quote }},
+ {{- end }}
+ "auth": {{ (print .username ":" .password) | b64enc | quote }}
+ }
+ }
+}
+{{- end }}
+{{- end }}
+
+{{- /*
+ jupyterhub.resources:
+ The resource request of a singleuser.
+*/}}
+# {{- define "jupyterhub.resources" -}}
+# {{- $r1 := .Values.singleuser.cpu.guarantee -}}
+# {{- $r2 := .Values.singleuser.memory.guarantee -}}
+# {{- $r3 := .Values.singleuser.extraResource.guarantees -}}
+# {{- $r := or $r1 $r2 $r3 -}}
+# {{- $l1 := .Values.singleuser.cpu.limit -}}
+# {{- $l2 := .Values.singleuser.memory.limit -}}
+# {{- $l3 := .Values.singleuser.extraResource.limits -}}
+# {{- $l := or $l1 $l2 $l3 -}}
+# {{- if $r -}}
+# requests:
+# {{- if $r1 }}
+# cpu: {{ .Values.singleuser.cpu.guarantee }}
+# {{- end }}
+# {{- if $r2 }}
+# memory: {{ .Values.singleuser.memory.guarantee }}
+# {{- end }}
+# {{- if $r3 }}
+# {{- range $key, $value := .Values.singleuser.extraResource.guarantees }}
+# {{ $key | quote }}: {{ $value | quote }}
+# {{- end }}
+# {{- end }}
+# {{- end }}
+
+# {{- if $l }}
+# limits:
+# {{- if $l1 }}
+# cpu: {{ .Values.singleuser.cpu.limit }}
+# {{- end }}
+# {{- if $l2 }}
+# memory: {{ .Values.singleuser.memory.limit }}
+# {{- end }}
+# {{- if $l3 }}
+# {{- range $key, $value := .Values.singleuser.extraResource.limits }}
+# {{ $key | quote }}: {{ $value | quote }}
+# {{- end }}
+# {{- end }}
+# {{- end }}
+# {{- end }}
+
+{{- define "jupyterhub.coreAffinity" -}}
+{{- $require := eq .Values.scheduling.corePods.nodeAffinity.matchNodePurpose "require" -}}
+{{- $prefer := eq .Values.scheduling.corePods.nodeAffinity.matchNodePurpose "prefer" -}}
+{{- if or $require $prefer -}}
+affinity:
+ nodeAffinity:
+ {{- if $require }}
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: hub.jupyter.org/node-purpose
+ operator: In
+ values: [core]
+ {{- end }}
+ {{- if $prefer }}
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: hub.jupyter.org/node-purpose
+ operator: In
+ values: [core]
+ {{- end }}
+{{- end }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/configmap.yaml b/helmchart/mlhub/templates/hub/configmap.yaml
new file mode 100644
index 0000000..1e59e6f
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/configmap.yaml
@@ -0,0 +1,17 @@
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: hub-config
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+data:
+{{- $values := pick .Values "custom" "hub" "singleuser" "mlhub" }}
+{{- /* trim secret values. Update here if new secrets are added! */ -}}
+{{- /* make a copy of values.auth to avoid modifying the original */ -}}
+{{- $_ := set $values "hub" (omit $values.hub "extraEnv") -}}
+{{- $_ := set $values "mlhub" (omit $values.mlhub "env") -}}
+{{- /* passthrough subset of Chart / Release */ -}}
+{{- $_ := set $values "Chart" (dict "Name" .Chart.Name "Version" .Chart.Version) }}
+{{- $_ := set $values "Release" (pick .Release "Name" "Namespace" "Service") }}
+ values.yaml: |
+ {{- $values | toYaml | nindent 4 }}
diff --git a/helmchart/mlhub/templates/hub/deployment.yaml b/helmchart/mlhub/templates/hub/deployment.yaml
new file mode 100644
index 0000000..96383ae
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/deployment.yaml
@@ -0,0 +1,185 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+ strategy:
+ {{- .Values.hub.deploymentStrategy | toYaml | trimSuffix "\n" | nindent 4 }}
+ template:
+ metadata:
+ labels:
+ {{- /* Changes here will cause the Deployment to restart the pods. */}}
+ {{- include "jupyterhub.matchLabels" . | nindent 8 }}
+ hub.jupyter.org/network-access-proxy-api: "true"
+ hub.jupyter.org/network-access-proxy-http: "true"
+ hub.jupyter.org/network-access-singleuser: "true"
+ {{- if .Values.hub.labels }}
+ {{- .Values.hub.labels | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ annotations:
+ # This lets us autorestart when the secret changes!
+ checksum/config-map: {{ include (print .Template.BasePath "/hub/configmap.yaml") . | sha256sum }}
+ checksum/secret: {{ include (print .Template.BasePath "/hub/secret.yaml") . | sha256sum }}
+ {{- if .Values.hub.annotations }}
+ {{- .Values.hub.annotations | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- if .Values.scheduling.podPriority.enabled }}
+ priorityClassName: {{ .Release.Name }}-default-priority
+ {{- end }}
+ nodeSelector: {{ toJson .Values.hub.nodeSelector }}
+ {{- include "jupyterhub.coreAffinity" . | nindent 6 }}
+ volumes:
+ - name: config
+ configMap:
+ name: hub-config
+ - name: secret
+ secret:
+ secretName: hub-secret
+ {{- if .Values.hub.extraVolumes }}
+ {{- .Values.hub.extraVolumes | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ {{- if eq .Values.hub.db.type "sqlite-pvc" }}
+ - name: hub-db-dir
+ persistentVolumeClaim:
+ claimName: hub-db-dir
+ {{- end }}
+ - name: user-config
+ configMap:
+ name: hub-user-config
+ {{- if .Values.rbac.enabled }}
+ serviceAccountName: hub
+ {{- end }}
+ securityContext:
+ fsGroup: {{ .Values.hub.fsGid }}
+ {{- if .Values.hub.imagePullSecret.enabled }}
+ imagePullSecrets:
+ - name: hub-image-credentials
+ {{- end }}
+ {{- if .Values.hub.initContainers }}
+ initContainers:
+ {{- .Values.hub.initContainers | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ containers:
+ {{- if .Values.hub.extraContainers }}
+ {{- .Values.hub.extraContainers | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ - name: hub
+ image: {{ .Values.hub.image.name }}:{{ .Values.hub.image.tag }}
+ volumeMounts:
+ - mountPath: /etc/jupyterhub/config/
+ name: config
+ - mountPath: /etc/jupyterhub/secret/
+ name: secret
+ {{- if .Values.hub.extraVolumeMounts }}
+ {{- .Values.hub.extraVolumeMounts | toYaml | trimSuffix "\n" | nindent 12 }}
+ {{- end }}
+ {{- if eq .Values.hub.db.type "sqlite-pvc" }}
+ - mountPath: /data
+ name: hub-db-dir
+ {{- if .Values.hub.db.pvc.subPath }}
+ subPath: {{ .Values.hub.db.pvc.subPath | quote }}
+ {{- end }}
+ {{- end }}
+ - mountPath: /resources/jupyterhub_user_config.py
+ name: user-config
+ subPath: jupyterhub_user_config.py
+ resources:
+ {{- .Values.hub.resources | toYaml | trimSuffix "\n" | nindent 12 }}
+ {{- with .Values.hub.image.pullPolicy }}
+ imagePullPolicy: {{ . }}
+ {{- end }}
+ securityContext:
+ runAsUser: {{ .Values.hub.uid }}
+ # Don't allow any process to execute as root inside the container
+ allowPrivilegeEscalation: false
+ env:
+ - name: ADDITIONAL_ARGS
+ {{- $args := "--config /resources/jupyterhub_config.py" }}
+ {{- if .Values.mlhub.debug }}
+ {{- $args = printf "%s %s" $args "--debug" }}
+ {{- end }}
+ {{- /*
+ We want to do automatic upgrades for sqlite-pvc by default, but
+ allow users to opt out of that if they want. Users using their own
+ db need to 'opt in' Go Templates treat nil and "" and false as
+ 'false', making this code complex. We can probably make this a
+ one-liner, but doing combinations of boolean vars in go templates is
+ very inelegant & hard to reason about.
+ */}}
+ {{- $upgradeType := typeOf .Values.hub.db.upgrade }}
+ {{- if eq $upgradeType "bool" }}
+ {{- /* .Values.hub.db.upgrade has been explicitly set to true or false */}}
+ {{- if .Values.hub.db.upgrade }}
+ {{- $args = printf "%s %s" $args "--upgrade-db" }}
+ {{- end }}
+ {{- else if eq $upgradeType "" }}
+ {{- /* .Values.hub.db.upgrade is nil */}}
+ {{- if eq .Values.hub.db.type "sqlite-pvc" }}
+ {{- $args = printf "%s %s" $args "--upgrade-db" }}
+ {{- end }}
+ {{- end }}
+ value: {{ $args | quote }}
+ - name: START_NGINX
+ value: "false"
+ - name: EXECUTION_MODE
+ value: "k8s"
+ - name: PYTHONUNBUFFERED
+ value: "1"
+ - name: HELM_RELEASE_NAME
+ value: {{ .Release.Name | quote }}
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
+ - name: CONFIGPROXY_AUTH_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: hub-secret
+ key: proxy.token
+ {{- if .Values.hub.extraEnv }}
+ {{- $extraEnvType := typeOf .Values.hub.extraEnv }}
+ {{- /* If we have a list, embed that here directly. This allows for complex configuration from configmap, downward API, etc. */}}
+ {{- if eq $extraEnvType "[]interface {}" }}
+ {{- .Values.hub.extraEnv | toYaml | trimSuffix "\n" | nindent 12 }}
+ {{- else if eq $extraEnvType "map[string]interface {}" }}
+ {{- /* If we have a map, treat those as key-value pairs. */}}
+ {{- range $key, $value := .Values.hub.extraEnv }}
+ - name: {{ $key | quote }}
+ value: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ {{- if .Values.mlhub.env }}
+ {{- range $key, $value := .Values.mlhub.env }}
+ - name: {{ $key | quote }}
+ value: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
+ ports:
+ - containerPort: 8081
+ name: hub
+ - containerPort: 22
+ name: ssh
+ ## livenessProbe notes:
+ ## We don't know how long hub database upgrades could take
+ ## so having a liveness probe could be a bit risky unless we put
+ ## a initialDelaySeconds value with long enough margin for that
+ ## to not be an issue. If it is too short, we could end up aborting
+ ## database upgrades midway or ending up in an infinite restart
+ ## loop.
+ # livenessProbe:
+ # initialDelaySeconds: 30
+ # httpGet:
+ # path: {{ .Values.mlhub.baseUrl }}/hub/health
+ # port: hub
+ readinessProbe:
+ httpGet:
+ path: {{ .Values.mlhub.baseUrl }}/hub/health
+ port: hub
diff --git a/helmchart/mlhub/templates/hub/image-credentials-secret.yaml b/helmchart/mlhub/templates/hub/image-credentials-secret.yaml
new file mode 100644
index 0000000..edafcd4
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/image-credentials-secret.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.hub.imagePullSecret.enabled }}
+kind: Secret
+apiVersion: v1
+metadata:
+ name: hub-image-credentials
+ labels:
+ {{- $_ := merge (dict "componentSuffix" "-image-credentials") . }}
+ {{- include "jupyterhub.labels" $_ | nindent 4 }}
+type: kubernetes.io/dockerconfigjson
+data:
+ .dockerconfigjson: {{ include "jupyterhub.dockerhubconfigjson" . }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/netpol.yaml b/helmchart/mlhub/templates/hub/netpol.yaml
new file mode 100644
index 0000000..04bf64f
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/netpol.yaml
@@ -0,0 +1,34 @@
+{{- if .Values.hub.networkPolicy.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ podSelector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+ policyTypes:
+ - Ingress
+ - Egress
+ ingress:
+ - from:
+ - podSelector:
+ matchLabels:
+ hub.jupyter.org/network-access-hub: "true"
+ ports:
+ - protocol: TCP
+ port: 8081
+ egress:
+ {{- /*
+ The default is to allow all egress for hub If you want to restrict it the
+ following egress is required
+ - proxy:8001
+ - singleuser:8888
+ - Kubernetes api-server
+ */}}
+ {{- if .Values.hub.networkPolicy.egress }}
+ {{- .Values.hub.networkPolicy.egress | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/pdb.yaml b/helmchart/mlhub/templates/hub/pdb.yaml
new file mode 100644
index 0000000..5b0623b
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/pdb.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.hub.pdb.enabled -}}
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ minAvailable: {{ .Values.hub.pdb.minAvailable }}
+ selector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/pvc.yaml b/helmchart/mlhub/templates/hub/pvc.yaml
new file mode 100644
index 0000000..240321a
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/pvc.yaml
@@ -0,0 +1,25 @@
+{{- if eq .Values.hub.db.type "sqlite-pvc" -}}
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+ name: hub-db-dir
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+ {{- if .Values.hub.db.pvc.annotations }}
+ annotations:
+ {{- .Values.hub.db.pvc.annotations | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if .Values.hub.db.pvc.selector }}
+ selector:
+ {{- .Values.hub.db.pvc.selector | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+ {{- if typeIs "string" .Values.hub.db.pvc.storageClassName }}
+ storageClassName: {{ .Values.hub.db.pvc.storageClassName | quote }}
+ {{- end }}
+ accessModes:
+ {{- .Values.hub.db.pvc.accessModes | toYaml | trimSuffix "\n" | nindent 4 }}
+ resources:
+ requests:
+ storage: {{ .Values.hub.db.pvc.storage | quote }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/rbac.yaml b/helmchart/mlhub/templates/hub/rbac.yaml
new file mode 100644
index 0000000..1cf527c
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/rbac.yaml
@@ -0,0 +1,40 @@
+{{- if .Values.rbac.enabled -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+rules:
+ - apiGroups: [""] # "" indicates the core API group
+ resources: ["pods", "persistentvolumeclaims"]
+ verbs: ["get", "watch", "list", "create", "delete"]
+ - apiGroups: [""] # "" indicates the core API group
+ resources: ["events"]
+ verbs: ["get", "watch", "list"]
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["list", "create", "delete"]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+subjects:
+ - kind: ServiceAccount
+ name: hub
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ kind: Role
+ name: hub
+ apiGroup: rbac.authorization.k8s.io
+{{- end }}
diff --git a/helmchart/mlhub/templates/hub/secret.yaml b/helmchart/mlhub/templates/hub/secret.yaml
new file mode 100644
index 0000000..9d3c2c7
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/secret.yaml
@@ -0,0 +1,18 @@
+kind: Secret
+apiVersion: v1
+metadata:
+ name: hub-secret
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+type: Opaque
+data:
+ {{- if .Values.mlhub.secretToken }}
+ proxy.token: {{ (required "Proxy token must be a 32 byte random string generated with `openssl rand -hex 32`!" .Values.mlhub.secretToken) | b64enc | quote }}
+ {{- else }}
+ proxy.token: {{ randAlphaNum 32 | b64enc | quote }}
+ {{- end }}
+ {{- if .Values.hub.db.password }}
+ hub.db.password: {{ .Values.hub.db.password | b64enc | quote }}
+ {{- end }}
+ {{- $values := dict "hub" dict }}
+ values.yaml: {{ $values | toYaml | b64enc | quote }}
diff --git a/helmchart/mlhub/templates/hub/service.yaml b/helmchart/mlhub/templates/hub/service.yaml
new file mode 100644
index 0000000..500eb5a
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/service.yaml
@@ -0,0 +1,35 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: hub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+ annotations:
+ {{- if not (index .Values.hub.service.annotations "prometheus.io/scrape") }}
+ prometheus.io/scrape: "true"
+ {{- end }}
+ {{- if not (index .Values.hub.service.annotations "prometheus.io/path") }}
+ prometheus.io/path: {{ .Values.mlhub.baseUrl }}/hub/metrics
+ {{- end }}
+ {{- if .Values.hub.service.annotations }}
+ {{- .Values.hub.service.annotations | toYaml | nindent 4 }}
+ {{- end }}
+spec:
+ type: {{ .Values.hub.service.type }}
+ {{- if .Values.hub.service.loadBalancerIP }}
+ loadBalancerIP: {{ .Values.hub.service.loadBalancerIP }}
+ {{- end }}
+ selector:
+ {{- include "jupyterhub.matchLabels" . | nindent 4 }}
+ ports:
+ - protocol: TCP
+ port: 8081
+ targetPort: 8081
+ name: hub
+ {{- if .Values.hub.service.ports.nodePort }}
+ nodePort: {{ .Values.hub.service.ports.nodePort }}
+ {{- end }}
+ - protocol: TCP
+ port: 22
+ targetPort: 22
+ name: ssh
\ No newline at end of file
diff --git a/helmchart/mlhub/templates/hub/user-configmap.yaml b/helmchart/mlhub/templates/hub/user-configmap.yaml
new file mode 100644
index 0000000..131c736
--- /dev/null
+++ b/helmchart/mlhub/templates/hub/user-configmap.yaml
@@ -0,0 +1,9 @@
+kind: ConfigMap
+apiVersion: v1
+metadata:
+ name: hub-user-config
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+data:
+ jupyterhub_user_config.py: |
+ {{- .Values.userConfig | nindent 4 }}
diff --git a/helmchart/mlhub/templates/ingress.yaml b/helmchart/mlhub/templates/ingress.yaml
new file mode 100644
index 0000000..f8e287c
--- /dev/null
+++ b/helmchart/mlhub/templates/ingress.yaml
@@ -0,0 +1,29 @@
+{{- if .Values.ingress.enabled -}}
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+ name: jupyterhub
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+ {{- if .Values.ingress.annotations }}
+ annotations:
+ {{- range $key, $value := .Values.ingress.annotations }}
+ {{ $key }}: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
+spec:
+ rules:
+ {{- range $host := .Values.ingress.hosts }}
+ - host: {{ $host | quote }}
+ http:
+ paths:
+ - path: {{ $.Values.mlhub.baseUrl }}{{ $.Values.ingress.pathSuffix }}
+ backend:
+ serviceName: proxy-public
+ servicePort: 80
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- .Values.ingress.tls | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/proxy/deployment.yaml b/helmchart/mlhub/templates/proxy/deployment.yaml
new file mode 100644
index 0000000..4497380
--- /dev/null
+++ b/helmchart/mlhub/templates/proxy/deployment.yaml
@@ -0,0 +1,124 @@
+{{- $manualHTTPS := and .Values.mlhub.env.SSL_ENABLED (eq .Values.proxy.https.type "manual") -}}
+{{- $manualHTTPSwithsecret := and .Values.mlhub.env.SSL_ENABLED (eq .Values.proxy.https.type "secret") -}}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: proxy
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- /* Changes here will cause the Deployment to restart the pods. */}}
+ {{- include "jupyterhub.matchLabels" . | nindent 8 }}
+ hub.jupyter.org/network-access-hub: "true"
+ hub.jupyter.org/network-access-singleuser: "true"
+ {{- if .Values.proxy.labels }}
+ {{- .Values.proxy.labels | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ annotations:
+ # This lets us autorestart when the secret changes!
+ checksum/hub-secret: {{ include (print $.Template.BasePath "/hub/secret.yaml") . | sha256sum }}
+ checksum/proxy-secret: {{ include (print $.Template.BasePath "/proxy/secret.yaml") . | sha256sum }}
+ {{- if .Values.proxy.annotations }}
+ {{- .Values.proxy.annotations | toYaml | trimSuffix "\n" | nindent 8 }}
+ {{- end }}
+ spec:
+ terminationGracePeriodSeconds: 60
+ {{- /* if .Values.scheduling.podPriority.enabled */}}
+ #priorityClassName: {{ .Release.Name }}-default-priority
+ {{- /* end */}}
+ nodeSelector: {{ toJson .Values.proxy.nodeSelector }}
+ {{- include "jupyterhub.coreAffinity" . | nindent 6 }}
+ {{- if $manualHTTPS }}
+ volumes:
+ - name: tls-secret
+ secret:
+ secretName: proxy-manual-tls
+ {{- else if $manualHTTPSwithsecret }}
+ volumes:
+ - name: tls-secret
+ secret:
+ secretName: {{ .Values.proxy.https.secret.name }}
+ {{- end }}
+ containers:
+ - name: chp
+ image: {{ .Values.proxy.chp.image.name }}:{{ .Values.proxy.chp.image.tag }}
+ {{- if or $manualHTTPS $manualHTTPSwithsecret }}
+ volumeMounts:
+ - name: tls-secret
+ # mountPath: /etc/chp/tls
+ mountPath: /resources/ssl
+ readOnly: true
+ {{- end }}
+ resources:
+ {{- .Values.proxy.chp.resources | toYaml | trimSuffix "\n" | nindent 12 }}
+ securityContext:
+ # Don't allow any process to execute as root inside the container
+ allowPrivilegeEscalation: false
+ env:
+ - name: ADDITIONAL_ARGS
+ {{- $args := "--ip=0.0.0.0 --api-ip=0.0.0.0 --api-port=8001 --port=8000 --default-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT) --error-target=http://$(HUB_SERVICE_HOST):$(HUB_SERVICE_PORT)/hub/error" }}
+ {{- if .Values.mlhub.debug }}
+ {{- $args = printf "%s %s" $args "--log-level=debug" }}
+ {{- end }}
+ value: {{ $args | quote }}
+ - name: CONFIGPROXY_AUTH_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: hub-secret
+ key: proxy.token
+ - name: EXECUTION_MODE
+ value: "k8s"
+ - name: SSHD_TARGET
+ value: hub:22
+ - name: START_SSH
+ value: "false"
+ - name: START_JHUB
+ value: "false"
+ - name: START_CHP
+ value: "true"
+ {{- if .Values.proxy.extraEnv }}
+ {{- $extraEnvType := typeOf .Values.proxy.extraEnv }}
+ {{- /* If we have a list, embed that here directly. This allows for complex configuration from configmap, downward API, etc. */}}
+ {{- if eq $extraEnvType "[]interface {}" }}
+ {{- .Values.proxy.extraEnv | toYaml | trimSuffix "\n" | nindent 12 }}
+ {{- else if eq $extraEnvType "map[string]interface {}" }}
+ {{- /* If we have a map, treat those as key-value pairs. */}}
+ {{- range $key, $value := .Values.proxy.extraEnv }}
+ - name: {{ $key | quote }}
+ value: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ {{- if .Values.mlhub.env }}
+ {{- range $key, $value := .Values.mlhub.env }}
+ - name: {{ $key | quote }}
+ value: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
+ {{- with .Values.proxy.chp.image.pullPolicy }}
+ imagePullPolicy: {{ . }}
+ {{- end }}
+ ports:
+ {{- if or $manualHTTPS $manualHTTPSwithsecret }}
+ - containerPort: 8443
+ name: proxy-https
+ {{- end }}
+ - containerPort: 8000
+ name: proxy-public
+ - containerPort: 8001
+ name: api
+ livenessProbe:
+ httpGet:
+ path: /_chp_healthz
+ port: proxy-public
+ readinessProbe:
+ httpGet:
+ path: /_chp_healthz
+ port: proxy-public
diff --git a/helmchart/mlhub/templates/proxy/netpol.yaml b/helmchart/mlhub/templates/proxy/netpol.yaml
new file mode 100644
index 0000000..400487a
--- /dev/null
+++ b/helmchart/mlhub/templates/proxy/netpol.yaml
@@ -0,0 +1,65 @@
+{{- $HTTPS := .Values.mlhub.env.SSL_ENABLED -}}
+{{- $autoHTTPS := and $HTTPS (and (eq .Values.proxy.https.type "letsencrypt") .Values.proxy.https.hosts) -}}
+{{- $manualHTTPS := and $HTTPS (eq .Values.proxy.https.type "manual") -}}
+{{- $manualHTTPSwithsecret := and $HTTPS (eq .Values.proxy.https.type "secret") -}}
+{{- if .Values.proxy.networkPolicy.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: proxy
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ podSelector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+ policyTypes:
+ - Ingress
+ - Egress
+ ingress:
+ - ports:
+ - protocol: TCP
+ port: 80
+ - protocol: TCP
+ port: 443
+ {{- if not $autoHTTPS }}
+ - protocol: TCP
+ port: 8000
+ {{- end }}
+ {{- if or $manualHTTPS $manualHTTPSwithsecret}}
+ - protocol: TCP
+ port: 8443
+ # probably change the port to 8080 as we also changed it within the proxy deployment.yaml
+ {{- end }}
+ # kube-lego /healthz
+ - protocol: TCP
+ port: 8080
+ # nginx /healthz
+ - protocol: TCP
+ port: 10254
+ - from:
+ - podSelector:
+ matchLabels:
+ hub.jupyter.org/network-access-proxy-http: "true"
+ ports:
+ - protocol: TCP
+ port: 8000
+ - from:
+ - podSelector:
+ matchLabels:
+ hub.jupyter.org/network-access-proxy-api: "true"
+ ports:
+ - protocol: TCP
+ port: 8001
+ egress:
+ {{- /*
+ The default is to allow all egress for proxy If you want to restrict it the
+ following egress is required
+ - hub:8081
+ - singleuser:8888
+ - Kubernetes api-server
+ */}}
+ {{- if .Values.proxy.networkPolicy.egress }}
+ {{- .Values.proxy.networkPolicy.egress | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/proxy/pdb.yaml b/helmchart/mlhub/templates/proxy/pdb.yaml
new file mode 100644
index 0000000..83970d5
--- /dev/null
+++ b/helmchart/mlhub/templates/proxy/pdb.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.proxy.pdb.enabled -}}
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+ name: proxy
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ minAvailable: {{ .Values.proxy.pdb.minAvailable }}
+ selector:
+ matchLabels:
+ {{- include "jupyterhub.matchLabels" . | nindent 6 }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/proxy/secret.yaml b/helmchart/mlhub/templates/proxy/secret.yaml
new file mode 100644
index 0000000..6cc903c
--- /dev/null
+++ b/helmchart/mlhub/templates/proxy/secret.yaml
@@ -0,0 +1,15 @@
+{{- $manualHTTPS := and .Values.mlhub.env.SSL_ENABLED (eq .Values.proxy.https.type "manual") -}}
+{{- if $manualHTTPS -}}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: proxy-manual-tls
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+type: kubernetes.io/tls
+data:
+ tls.crt: {{ .Values.proxy.https.manual.cert | b64enc }}
+ tls.key: {{ .Values.proxy.https.manual.key | b64enc }}
+ cert.crt: {{ .Values.proxy.https.manual.cert | b64enc }}
+ cert.key: {{ .Values.proxy.https.manual.key | b64enc }}
+{{- end }}
diff --git a/helmchart/mlhub/templates/proxy/service.yaml b/helmchart/mlhub/templates/proxy/service.yaml
new file mode 100644
index 0000000..5910136
--- /dev/null
+++ b/helmchart/mlhub/templates/proxy/service.yaml
@@ -0,0 +1,75 @@
+{{- $HTTPS := .Values.mlhub.env.SSL_ENABLED -}}
+{{- $autoHTTPS := and $HTTPS (and (eq .Values.proxy.https.type "letsencrypt") .Values.proxy.https.hosts) -}}
+{{- $offloadHTTPS := and $HTTPS (eq .Values.proxy.https.type "offload") -}}
+{{- $manualHTTPS := and $HTTPS (eq .Values.proxy.https.type "manual") -}}
+{{- $manualHTTPSwithsecret := and $HTTPS (eq .Values.proxy.https.type "secret") -}}
+apiVersion: v1
+kind: Service
+metadata:
+ name: proxy-api
+ labels:
+ {{- $_ := merge (dict "componentSuffix" "-api") . }}
+ {{- include "jupyterhub.labels" $_ | nindent 4 }}
+spec:
+ selector:
+ {{- include "jupyterhub.matchLabels" . | nindent 4 }}
+ ports:
+ - protocol: TCP
+ port: 8001
+ targetPort: 8001
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: proxy-public
+ labels:
+ {{- $_ := merge (dict "componentSuffix" "-public") . }}
+ {{- include "jupyterhub.labels" $_ | nindent 4 }}
+ {{- if .Values.proxy.service.labels }}
+ {{- .Values.proxy.service.labels | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+ {{- if .Values.proxy.service.annotations }}
+ annotations:
+ {{- .Values.proxy.service.annotations | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+spec:
+ selector:
+ # TODO: Refactor to utilize the helpers
+ {{- if $autoHTTPS }}
+ component: autohttps
+ {{- else }}
+ component: proxy
+ {{- end }}
+ release: {{ .Release.Name }}
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ {{- if $autoHTTPS }}
+ targetPort: 80
+ {{- else }}
+ targetPort: 8080
+ {{- end }}
+ # allow proxy.service.nodePort for http
+ {{- if .Values.proxy.service.nodePorts.http }}
+ nodePort: {{ .Values.proxy.service.nodePorts.http }}
+ {{- end }}
+ {{- if $HTTPS }}
+ - name: https
+ port: 443
+ protocol: TCP
+ {{- if or $manualHTTPS $manualHTTPSwithsecret }}
+ targetPort: 8080
+ {{- else if $offloadHTTPS }}
+ targetPort: 8000
+ {{- else }}
+ targetPort: 8080
+ {{- end }}
+ {{- if .Values.proxy.service.nodePorts.https }}
+ nodePort: {{ .Values.proxy.service.nodePorts.https }}
+ {{- end }}
+ {{- end }}
+ type: {{ .Values.proxy.service.type }}
+ {{- if .Values.proxy.service.loadBalancerIP }}
+ loadBalancerIP: {{ .Values.proxy.service.loadBalancerIP }}
+ {{- end }}
diff --git a/helmchart/mlhub/templates/singleuser/netpol.yaml b/helmchart/mlhub/templates/singleuser/netpol.yaml
new file mode 100644
index 0000000..2ce534f
--- /dev/null
+++ b/helmchart/mlhub/templates/singleuser/netpol.yaml
@@ -0,0 +1,40 @@
+{{- if and .Values.singleuser.networkPolicy.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: singleuser
+ labels:
+ {{- include "jupyterhub.labels" . | nindent 4 }}
+spec:
+ podSelector:
+ matchLabels:
+ {{- $_ := merge (dict "componentLabel" "singleuser-server") . }}
+ {{- include "jupyterhub.matchLabels" $_ | nindent 6 }}
+ policyTypes:
+ - Ingress
+ - Egress
+ ingress:
+ - from:
+ - podSelector:
+ matchLabels:
+ hub.jupyter.org/network-access-singleuser: "true"
+ ports:
+ - protocol: TCP
+ port: 8080
+ egress:
+ - to:
+ - podSelector:
+ matchLabels:
+ {{- /*
+ Override componentLabel because we need the label of the
+ destination, not the source
+ */}}
+ {{- $_ := merge (dict "componentLabel" "hub") . }}
+ {{- include "jupyterhub.matchLabels" $_ | nindent 14 }}
+ ports:
+ - protocol: TCP
+ port: 8081
+ {{- if .Values.singleuser.networkPolicy.egress }}
+ {{- .Values.singleuser.networkPolicy.egress | toYaml | trimSuffix "\n" | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/helmchart/mlhub/validate.py b/helmchart/mlhub/validate.py
new file mode 100755
index 0000000..777f410
--- /dev/null
+++ b/helmchart/mlhub/validate.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+import jsonschema
+import yaml
+
+# HACK: These files are never closed, but is ok!
+schema = yaml.safe_load(open('schema.yaml'))
+values = yaml.safe_load(open('values.yaml'))
+
+jsonschema.validate(values, schema)
diff --git a/helmchart/mlhub/values.yaml b/helmchart/mlhub/values.yaml
new file mode 100644
index 0000000..b9cbd41
--- /dev/null
+++ b/helmchart/mlhub/values.yaml
@@ -0,0 +1,146 @@
+custom: {}
+
+mlhub:
+ # NOTE: baseUrl has to be set to configure other services during deployment (together with proxy.secretToken only hub-configurations that have to be set here instead of jupyterhub-user config py)
+ baseUrl: /
+ secretToken: ''
+ debug: false
+ env:
+
+hub:
+ service:
+ type: ClusterIP
+ annotations: {}
+ ports:
+ nodePort:
+ loadBalancerIP:
+ initContainers: []
+ uid: 0
+ fsGid: 0
+ nodeSelector: {}
+ deploymentStrategy:
+ # sqlite-pvc backed hub requires Recreate strategy to work
+ type: Recreate
+ # This is required for upgrading to work
+ rollingUpdate:
+ db:
+ # type: sqlite-pvc
+ upgrade:
+ pvc:
+ annotations: {}
+ selector: {}
+ accessModes:
+ - ReadWriteOnce
+ storage: 1Gi
+ subPath:
+ storageClassName:
+ labels: {}
+ annotations: {}
+ extraEnv: {}
+ extraContainers: []
+ extraVolumes: []
+ extraVolumeMounts: []
+ image:
+ name: mltooling/ml-hub
+ tag: $VERSION
+ resources:
+ requests:
+ cpu: 200m
+ memory: 512Mi
+ imagePullSecret:
+ enabled: false
+ registry:
+ username:
+ email:
+ password:
+ pdb:
+ enabled: true
+ minAvailable: 1
+ networkPolicy:
+ enabled: false
+ egress:
+ - to:
+ - ipBlock:
+ cidr: 0.0.0.0/0
+
+rbac:
+ enabled: true
+
+proxy:
+ service:
+ type: LoadBalancer
+ labels: {}
+ annotations: {}
+ nodePorts:
+ http:
+ https:
+ loadBalancerIP:
+ chp:
+ image:
+ name: mltooling/ml-hub
+ tag: $VERSION
+ resources:
+ requests:
+ cpu: 200m
+ memory: 512Mi
+ labels: {}
+ nodeSelector: {}
+ pdb:
+ enabled: true
+ minAvailable: 1
+ https:
+ type: manual
+ #type: manual
+ manual:
+ key:
+ cert:
+ secret:
+ name: ''
+ key: cert.key
+ crt: cert.crt
+ hosts: []
+ networkPolicy:
+ enabled: false
+ egress:
+ - to:
+ - ipBlock:
+ cidr: 0.0.0.0/0
+ extraEnv:
+
+singleuser:
+ networkTools:
+ image:
+ name: jupyterhub/k8s-network-tools
+ tag: '0.8.2'
+ # https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/security.html#audit-cloud-metadata-server-access
+ cloudMetadata:
+ enabled: false
+ ip: 169.254.169.254
+ networkPolicy:
+ enabled: true
+ egress:
+ # Required egress is handled by other rules so it's safe to modify this
+ - to:
+ - ipBlock:
+ cidr: 0.0.0.0/0
+ except:
+ - 169.254.169.254/32
+
+scheduling:
+ podPriority:
+ enabled: false
+ globalDefault: false
+ defaultPriority: 0
+ userPlaceholderPriority: -10
+ corePods:
+ nodeAffinity:
+ matchNodePurpose: prefer
+
+
+
+ingress:
+ enabled: false
+ annotations: {}
+ hosts: []
+ pathSuffix: ''
+ tls: []
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 8065e69..61c5b9f 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -155,7 +155,8 @@ def combine_config_dicts(*configs) -> dict:
# In Kubernetes mode, load the Kubernetes Jupyterhub config that can be configured via a config.yaml.
# Those values will override the values set above, as it is loaded afterwards.
if ENV_EXECUTION_MODE == utils.EXECUTION_MODE_KUBERNETES:
- load_subconfig("{}/kubernetes/jupyterhub_chart_config.py".format(os.getenv("_RESOURCES_PATH")))
+ # NOTE: only load when deployed via helm chart and not manual Kubernetes?
+ load_subconfig("{}/jupyterhub_chart_config.py".format(os.getenv("_RESOURCES_PATH")))
c.JupyterHub.spawner_class = 'mlhubspawner.MLHubKubernetesSpawner'
c.KubeSpawner.pod_name_template = c.Spawner.name_template
diff --git a/resources/kubernetes/config.yaml b/resources/kubernetes/config.yaml
deleted file mode 100755
index 6e32db4..0000000
--- a/resources/kubernetes/config.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-proxy:
- # the secret token should be changed in your deployment ;)
- secretToken: 4bda3bd04f6ae5ia28368bf7b5514712351fd9cf8edb09ce9b26f73500f8aa54b
-
-singleuser:
- storage:
- type: none
-
-hub:
- db:
- type: foo
-
-scheduling:
- userScheduler:
- enabled: false
diff --git a/resources/kubernetes/jupyterhub_chart_config.py b/resources/kubernetes/jupyterhub_chart_config.py
new file mode 100644
index 0000000..38c806d
--- /dev/null
+++ b/resources/kubernetes/jupyterhub_chart_config.py
@@ -0,0 +1,108 @@
+from kubernetes import client
+from yamlreader import yaml_load
+from tornado.httpclient import AsyncHTTPClient
+import os
+
+### Begin config from chart
+# Configure JupyterHub to use the curl backend for making HTTP requests,
+# rather than the pure-python implementations. The default one starts
+# being too slow to make a large number of requests to the proxy API
+# at the rate required.
+AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
+
+# c.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'
+
+# Connect to a proxy running in a different pod
+# The environment variables *_SERVICE_HOST/PORT are injected by Kubernetes automatically
+c.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))
+c.ConfigurableHTTPProxy.should_start = False
+
+c.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']
+c.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])
+
+c.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
+c.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])
+
+# Do not shut down user pods when hub is restarted
+c.JupyterHub.cleanup_servers = False
+
+# Check that the proxy has routes appropriately setup
+c.JupyterHub.last_activity_interval = 60
+
+# Don't wait at all before redirecting a spawning user to the progress page
+c.JupyterHub.tornado_settings = {
+ 'slow_spawn_timeout': 0,
+}
+
+# load the values from the mounted config-map
+files = ["/etc/jupyterhub/config/values.yaml", "/etc/jupyterhub/secret/values.yaml"]
+config = yaml_load(files)
+c.JupyterHub.base_url = config["mlhub"]["baseUrl"]
+
+# add dedicated-node toleration
+for key in (
+ 'hub.jupyter.org/dedicated',
+ # workaround GKE not supporting / in initial node taints
+ 'hub.jupyter.org_dedicated',
+):
+ c.Spawner.tolerations.append(
+ dict(
+ key=key,
+ operator='Equal',
+ value='user',
+ effect='NoSchedule',
+ )
+ )
+
+cloud_metadata = config['singleuser']['cloudMetadata']
+if not cloud_metadata.get('enabled', False):
+ # Use iptables to block access to cloud metadata by default
+ network_tools_image_name = config['singleuser']['networkTools']['image']['name']
+ network_tools_image_tag = config['singleuser']['networkTools']['image']['tag']
+ ip_block_container = client.V1Container(
+ name="block-cloud-metadata",
+ image=f"{network_tools_image_name}:{network_tools_image_tag}",
+ command=[
+ 'iptables',
+ '-A', 'OUTPUT',
+ '-d', cloud_metadata.get('ip', '169.254.169.254'),
+ '-j', 'DROP'
+ ],
+ security_context=client.V1SecurityContext(
+ privileged=True,
+ run_as_user=0,
+ capabilities=client.V1Capabilities(add=['NET_ADMIN'])
+ )
+ )
+
+c.Spawner.init_containers.append(ip_block_container)
+
+# implement common labels
+# this duplicates the jupyterhub.commonLabels helper
+if not isinstance(c.Spawner.common_labels, dict):
+ c.Spawner.common_labels = {}
+
+common_labels = c.Spawner.common_labels
+common_labels['app'] = "mlhub"
+if "nameOverride" in config:
+ common_labels['app'] = config["nameOverride"]
+else:
+ common_labels['app'] = config["Chart"]["Name"]
+
+common_labels['heritage'] = "mlhub"
+chart_name = config['Chart']['Name']
+chart_version = config['Chart']['Version']
+if chart_name and chart_version:
+ common_labels['chart'] = "{}-{}".format(
+ chart_name, chart_version.replace('+', '_'),
+ )
+release = config['Release']['Name']
+if release:
+ common_labels['release'] = release
+
+if config['mlhub']['debug']:
+ c.JupyterHub.log_level = 'DEBUG'
+ c.Spawner.debug = True
+
+
+### End Chart config
\ No newline at end of file
diff --git a/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py b/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py
index 5cd01ef..455c2bf 100644
--- a/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py
+++ b/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py
@@ -35,6 +35,7 @@ def __init__(self, *args, **kwargs):
self.hub_name = utils.ENV_HUB_NAME
self.default_label = {utils.LABEL_MLHUB_ORIGIN: self.hub_name, utils.LABEL_MLHUB_USER: self.user.name, utils.LABEL_MLHUB_SERVER_NAME: self.name, LABEL_POD_NAME: self.pod_name}
self.extra_labels.update(self.default_label)
+ self.extra_labels.update(self.common_labels)
@default('options_form')
def _options_form(self):
From 0329b30618eaf9e054f64e264a0f8c01c022d4dd Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Fri, 17 Jan 2020 18:12:57 +0100
Subject: [PATCH 23/29] Update documentation
---
README.md | 12 +++++++++---
helmchart/README.md | 4 ++++
helmchart/mlhub/values.yaml | 2 --
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index bad6f9b..1039b24 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,11 @@ MLHub is based on [JupyterHub](https://github.com/jupyterhub/jupyterhub) with co
Most parts will be identical to the configuration of JupyterHub 1.0.0. One of the things done differently is that ssl will not be activated on proxy or hub-level, but on our nginx proxy.
-### Start an instance via Docker
+### Quick Start
+
+Following commands will start the hub with the default config.
+
+#### Start an instance via Docker
```bash
docker run \
@@ -66,7 +70,7 @@ To persist the hub data, such as started workspaces and created users, mount a d
Any given name (`--name`) will be overruled by the environment variable `HUB_NAME`.
-### Start an instance via Kubernetes
+#### Start an instance via Kubernetes
Via Helm:
@@ -163,7 +167,9 @@ In Docker, mount a custom config like `-v /jupyterhub_user_config:/resources/jup
When using Helm, you can pass the configuration to the installation command via `--set-file userConfig=./jupyterhub_user_config.py`. So the complete command could look like `helm upgrade --install mlhub mlhub-chart-1.0.1.tgz --namespace mlhub --set-file userConfig=./jupyterhub_user_config.py`. Have a look at the [KubeSpawner properties](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html) to see what can be configured for the Spawner.
-Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the KubeSpawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations.
+Additionally to the `jupyterhub_user_config.py`, which can be used to configure JupyterHub or the KubeSpawner, you can provide a `config.yaml` where you can make some Kubernetes-deployment specific configurations. Check out the *helmchart/* directory for more information.
+
+You can think of it like this: everything that has to be configured for the deployment itself, such as environment variables or volumes for the *hub / proxy* itself, goes to the `config.yaml`. Everything related to JupyterHub's way of working such as how to authenticate or what the spawned user pods will mount goes to the `jupyterhub_user_config.py`.
> ℹ️ _Some JupyterHub configurations cannot be set in the `jupyterhub_user_config.py` as they have to be shared between services and, thus, have to be known during deployment. Instead, if you want to specify them, you have to do it in the `config.yaml` (see below)._
diff --git a/helmchart/README.md b/helmchart/README.md
index fe487b6..de5aa42 100644
--- a/helmchart/README.md
+++ b/helmchart/README.md
@@ -21,3 +21,7 @@ You can then deploy the chart via `helm upgrade --install mlhub packaged-chart.t
The `config.yaml` can be used to overrride default deployment values, the `userConfig` can be used to configure JupyterHub and the Spawner.
For more details, check out the main [readme](https://github.com/ml-tooling/ml-hub).
+
+## Config
+
+You find the default values for the deployment in the *mlhub/values.yaml* file. Some values should be self-explanatory; if not we encourage you to have a look at the chart files or at the *[Zero to JupyterHub K8s](https://github.com/jupyterhub/zero-to-jupyterhub-k8s)* documentation, on which our setup is based.
diff --git a/helmchart/mlhub/values.yaml b/helmchart/mlhub/values.yaml
index b9cbd41..2bca66e 100644
--- a/helmchart/mlhub/values.yaml
+++ b/helmchart/mlhub/values.yaml
@@ -136,8 +136,6 @@ scheduling:
nodeAffinity:
matchNodePurpose: prefer
-
-
ingress:
enabled: false
annotations: {}
From 29646d4e6a1fd8820c5fa79479d7bcb60247c012 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Mon, 20 Jan 2020 10:40:49 +0100
Subject: [PATCH 24/29] Update documentation
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1039b24..79703af 100644
--- a/README.md
+++ b/README.md
@@ -348,7 +348,7 @@ helm upgrade \
mlhub:
env:
SSL_ENABLED: true
- AAD_TENANT_ID: ""
proxy:
https:
From 0f478a794d1a76b9f8a42abe901ddba5eb08b352 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Mon, 20 Jan 2020 15:23:32 +0100
Subject: [PATCH 25/29] Add some thoughts about HTTPS in the documentation
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 79703af..0164746 100644
--- a/README.md
+++ b/README.md
@@ -234,6 +234,9 @@ proxy:
+If you use a (cloud provider) LoadBalancer in your cluster where SSL is already terminated, just do not enable SSL on Hub-level and point the LoadBalancer regularly to the Hub's port.
+If you do not have a certificate, for example from your cloud provider, you can have a look at the [Let's Encrypt project](https://letsencrypt.org/getting-started/) for how to generate one. For that, your domain must be publicly reachable. It is not built-in the MLHub project, but one idea would be to have a pod that creates & renews certificates for your domain, copying them into the proxy pod and re-starting nginx there.
+
### Spawner
We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](#configuration).
From 4c1f58c15b229dc51aa1613d88cf874685f4359a Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Mon, 20 Jan 2020 15:25:14 +0100
Subject: [PATCH 26/29] Update documentation
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 0164746..ab322b2 100644
--- a/README.md
+++ b/README.md
@@ -232,11 +232,11 @@ proxy:
-----END CERTIFICATE-----
```
-
-
-If you use a (cloud provider) LoadBalancer in your cluster where SSL is already terminated, just do not enable SSL on Hub-level and point the LoadBalancer regularly to the Hub's port.
+If you use a (cloud provider) LoadBalancer in your cluster where SSL is already terminated, just do not enable SSL on Hub-level and point the LoadBalancer regularly to the Hub's port.
If you do not have a certificate, for example from your cloud provider, you can have a look at the [Let's Encrypt project](https://letsencrypt.org/getting-started/) for how to generate one. For that, your domain must be publicly reachable. It is not built-in the MLHub project, but one idea would be to have a pod that creates & renews certificates for your domain, copying them into the proxy pod and re-starting nginx there.
+
+
### Spawner
We override [DockerSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubspawner.py) and [KubeSpawner](https://github.com/ml-tooling/ml-hub/blob/master/resources/mlhubspawner/mlhubspawner/mlhubkubernetesspawner.py) for Docker and Kubernetes, respectively. We do so to add convenient labels and environment variables. Further, we return a custom option form to configure the resouces of the workspaces. The overriden Spawners can be configured the same way as the base Spawners as stated in the [Configuration Section](#configuration).
From 3f65f6c19ae1e3a804c0da8fe5069b4f2fc3b168 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 21 Jan 2020 11:24:22 +0100
Subject: [PATCH 27/29] Update documentation for the hub
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index ab322b2..8312dbf 100644
--- a/README.md
+++ b/README.md
@@ -278,6 +278,9 @@ In this scenario, the idea is that just the admin user exists and can access the
Go to the admin panel (1) and create a new user (2).
You can then start the standard workspace for that user or create a new workspace (see second image).
Via the ssh access button (3), you can send the user a command to connect to the started workspace via ssh. For more information about the ssh-feature in the workspace, checkout [this documentation section](https://github.com/ml-tooling/ml-workspace#ssh-access). If you created a workspace for another user, it might be necessary to click access on the workspace and authorize once per user to be able to use the ssh-access button.
+A user can also access the UI via ssh-ing into the workspace, printing the API token via `echo $JUPYTERHUB_API_TOKEN`, and then accessing the url of the hub in the browser under `/user///tree?token=`. The `JUPYTERHUB_API_TOKEN` gives access to *all* named servers of a user, so use different users for different persons in this scenario.
+
+> ℹ️ _Do **not** create different workspaces with the same Hub user and give access to different persons. Via the `$JUPYTERHUB_API_TOKEN` you get access to **all** workspaces of a server. In other words, if you create multiple named workspaces for the user 'admin' and distribute it to different persons, they can access all named workspaces._
From 96442373163e5d3cb011ab396daa550a1d984fa6 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Tue, 21 Jan 2020 11:25:55 +0100
Subject: [PATCH 28/29] Update documentation
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 8312dbf..90a247e 100644
--- a/README.md
+++ b/README.md
@@ -280,7 +280,7 @@ You can then start the standard workspace for that user or create a new workspac
Via the ssh access button (3), you can send the user a command to connect to the started workspace via ssh. For more information about the ssh-feature in the workspace, checkout [this documentation section](https://github.com/ml-tooling/ml-workspace#ssh-access). If you created a workspace for another user, it might be necessary to click access on the workspace and authorize once per user to be able to use the ssh-access button.
A user can also access the UI via ssh-ing into the workspace, printing the API token via `echo $JUPYTERHUB_API_TOKEN`, and then accessing the url of the hub in the browser under `/user///tree?token=`. The `JUPYTERHUB_API_TOKEN` gives access to *all* named servers of a user, so use different users for different persons in this scenario.
-> ℹ️ _Do **not** create different workspaces with the same Hub user and give access to different persons. Via the `$JUPYTERHUB_API_TOKEN` you get access to **all** workspaces of a server. In other words, if you create multiple named workspaces for the user 'admin' and distribute it to different persons, they can access all named workspaces._
+> ℹ️ _Do **not** create different workspaces for the same Hub user and then give access to them to different persons. Via the `$JUPYTERHUB_API_TOKEN` you get access to **all** workspaces of a user. In other words, if you create multiple named workspaces for the user 'admin' and distribute it to different persons, they can access all named workspaces for the 'admin' user._
From d05066bed8fa8d4319f11d093a8f47174a539fc9 Mon Sep 17 00:00:00 2001
From: Benjamin Raethlein
Date: Mon, 3 Feb 2020 16:47:52 +0100
Subject: [PATCH 29/29] The cleanup services runs within the hub container and,
therefore, I reachable from the hub on localhost
---
resources/jupyterhub_config.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/jupyterhub_config.py b/resources/jupyterhub_config.py
index 61c5b9f..a52145c 100644
--- a/resources/jupyterhub_config.py
+++ b/resources/jupyterhub_config.py
@@ -173,7 +173,7 @@ def combine_config_dicts(*configs) -> dict:
SERVICE_HOST_ENV_NAME: os.getenv(SERVICE_HOST_ENV_NAME),
SERVICE_PORT_ENV_NAME: os.getenv(SERVICE_PORT_ENV_NAME)
})
- service_host = "hub"
+ service_host = "127.0.0.1" #"hub"
elif ENV_EXECUTION_MODE == utils.EXECUTION_MODE_LOCAL: