Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Deploying a MVP BinderHub #1404

Merged
merged 24 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2656cc7
Add binderhub helm chart to deployer's validation step
sgibson91 Jun 8, 2022
ec6f0ae
Generate different config for binderhub
sgibson91 Jun 8, 2022
a79a58e
Create basic binderhub config
sgibson91 Jun 8, 2022
45dabd1
Setup secret binderhub config with registry credentials
sgibson91 Jun 8, 2022
ec6a0f8
Add binder-staging deployment to 2i2c's cluster.yaml file
sgibson91 Jun 8, 2022
6eb401d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 8, 2022
3a266f8
Set jupyterhub API token for dask-gateway in BinderHub chart
sgibson91 Jun 8, 2022
69f712d
Temporarily skip validation when deploying
sgibson91 Jun 8, 2022
694b21f
Comment out all the custom stuff
sgibson91 Jun 8, 2022
00da27e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 8, 2022
6543a9c
Remove singleuser.serviceAccountName
sgibson91 Jun 8, 2022
54bfd7b
Revert "Temporarily skip validation when deploying"
sgibson91 Jun 15, 2022
bbc5a55
Update values to resemble the original Pangeo Binder's
sgibson91 Jun 15, 2022
4d9f26d
Merge branch 'master' into deploy-test-binder
sgibson91 Jun 15, 2022
2d46bc2
Set dummy dask api token for binderhub too
sgibson91 Jun 15, 2022
a31a2d5
Update deployer/config_validation.py
sgibson91 Jun 16, 2022
6c86fc6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2022
0ffba14
remove dind config, inherit from upstream binderhub chart
sgibson91 Jun 16, 2022
fe9cd96
Don't pin traefik image
sgibson91 Jun 16, 2022
fb45859
Merge branch 'master' into deploy-test-binder
sgibson91 Jun 20, 2022
c0eefeb
Add userServiceAccount to binderhub
yuvipanda Jun 21, 2022
461f08a
Build user images only on user nodes
yuvipanda Jun 21, 2022
a87bd38
Merge pull request #197 from yuvipanda/no-user
sgibson91 Jun 22, 2022
cdcdd0e
Merge pull request #196 from yuvipanda/deploy-test-binder
sgibson91 Jun 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/clusters/2i2c/binder.values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
binderhub:
config:
BinderHub:
image_prefix: "sgibson91/2i2c-binder-staging-"
11 changes: 11 additions & 0 deletions config/clusters/2i2c/cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ hubs:
# that you intend for these files to be applied in this order.
- enc-dask-staging.secret.values.yaml
- dask-staging.values.yaml
- name: binder-staging
display_name: "2i2c binder staging"
domain: binder-staging.2i2c.cloud
helm_chart: binderhub
auth0:
# connection update? Also ensure the basehub Helm chart is provided a
# matching value for jupyterhub.custom.2i2c.add_staff_user_ids_of_type!
enabled: false
helm_chart_values_files:
- binder.values.yaml
- enc-binder.secret.values.yaml
- name: demo
display_name: "2i2c demo"
domain: demo.2i2c.cloud
Expand Down
18 changes: 18 additions & 0 deletions config/clusters/2i2c/enc-binder.secret.values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
binderhub:
registry:
username: ENC[AES256_GCM,data:ZT0NjJUYveh4,iv:yE2QK4amLcb84piGH2NN7PRKbQirgSNT4EqVMqdBwM4=,tag:7Q5/PHMpUmqT5DDvnxXoKw==,type:str]
password: ENC[AES256_GCM,data:meUMXRwimr26XYEQkv0TwSRSI+M1rzQ6WU8mg+CGpog=,iv:sc0LwsXkVxN2kAuHBv4+D4rbBUv0ONgqWLZzyXDw+6A=,tag:kpAfDEB8oP0Qntev277E2w==,type:str]
sops:
kms: []
gcp_kms:
- resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs
created_at: "2022-06-08T13:12:00Z"
enc: CiQA4OM7eMnD5wD0zQuaFMYYgrJY0qzNtTGK8cwo9OdHtJWx3C0SSQBq6cPrQ+8VfocZn3cxRYZ3RXFwa8XvJ7SI/4hZIaHhlfEbqMvDXArnQ5R0f5iFiw0fNfHC6S+htAJr0usI+QUGT5OoGt9tv1Y=
azure_kv: []
hc_vault: []
age: []
lastmodified: "2022-06-08T13:12:00Z"
mac: ENC[AES256_GCM,data:EhJwhwNOU5JuAHgUo+sRHktsacO3WRB3iLe25T3D/UESHiEAlwv8X1OSmGgV3ybcUn/dBbTFGep6yBCpCMCCXhhEw9IpTTZlss3zxiEoPzGHg0EzW7TWQdU3EirySQq879Y5REbEb8gx01dx7fW/Yu6rFVUgb/ePc0iZMZD3Wi0=,iv:PkSkQCxA3lb9EiOM8g7HkM48wd4Gj/1YDgio/WkRx6M=,tag:2L5GofYPi8hOtvTS2pZtlA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.1
6 changes: 5 additions & 1 deletion deployer/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def _prepare_helm_charts_dependencies_and_schemas():
_generate_values_schema_json(daskhub_dir)
subprocess.check_call(["helm", "dep", "up", daskhub_dir])

binderhub_dir = helm_charts_dir.joinpath("binderhub")
_generate_values_schema_json(binderhub_dir)
subprocess.check_call(["helm", "dep", "up", binderhub_dir])

support_dir = helm_charts_dir.joinpath("support")
_generate_values_schema_json(support_dir)
subprocess.check_call(["helm", "dep", "up", support_dir])
Expand Down Expand Up @@ -109,7 +113,7 @@ def validate_hub_config(cluster_name, hub_name):
# Workaround the current requirement for dask-gateway 0.9.0 to have a
# JupyterHub api-token specified, for updates if this workaround can be
# removed, see https://github.com/dask/dask-gateway/issues/473.
if hub.spec["helm_chart"] == "daskhub":
if hub.spec["helm_chart"] in ("daskhub", "binderhub"):
cmd.append("--set=dask-gateway.gateway.auth.jupyterhub.apiToken=dummy")
try:
subprocess.check_output(cmd, text=True)
Expand Down
222 changes: 131 additions & 91 deletions deployer/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,90 +31,127 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key):

WARNING: MIGHT CONTAINS SECRET VALUES!
"""

generated_config = {
"jupyterhub": {
"proxy": {"https": {"hosts": [self.spec["domain"]]}},
"ingress": {
"hosts": [self.spec["domain"]],
"tls": [
{"secretName": "https-auto-tls", "hosts": [self.spec["domain"]]}
],
},
"singleuser": {
# If image_repo isn't set, just have an empty image dict
"image": {"name": self.cluster.spec["image_repo"]}
if "image_repo" in self.cluster.spec
else {},
},
"hub": {
"config": {},
"initContainers": [
{
"name": "templates-clone",
"image": "alpine/git",
"args": [
"clone",
"--",
"https://github.com/2i2c-org/default-hub-homepage",
"/srv/repo",
],
"securityContext": {
"runAsUser": 1000,
"allowPrivilegeEscalation": False,
"readOnlyRootFilesystem": True,
},
"volumeMounts": [
{"name": "custom-templates", "mountPath": "/srv/repo"}
if self.spec["helm_chart"] == "binderhub":
generated_config = {
"binderhub": {
"config": {
"BinderHub": {"hub_url": f"https://hub.{self.spec['domain']}"}
},
"ingress": {
"hosts": [self.spec["domain"]],
"tls": [
{
"secretName": "https-auto-tls-binder",
"hosts": [self.spec["domain"]],
}
],
},
"jupyterhub": {
"ingress": {
"hosts": [f"hub.{self.spec['domain']}"],
"tls": [
{
"secretName": "https-auto-tls-hub",
"hosts": [f"hub.{self.spec['domain']}"],
}
],
}
],
"extraContainers": [
{
"name": "templates-sync",
"image": "alpine/git",
"workingDir": "/srv/repo",
"command": ["/bin/sh"],
"args": [
"-c",
dedent(
f"""\
while true; do git fetch origin;
if [[ $(git ls-remote --heads origin {self.cluster.spec["name"]}-{self.spec["name"]} | wc -c) -ne 0 ]]; then
git reset --hard origin/{self.cluster.spec["name"]}-{self.spec["name"]};
else
git reset --hard origin/master;
fi
sleep 5m; done
"""
),
],
"securityContext": {
"runAsUser": 1000,
"allowPrivilegeEscalation": False,
"readOnlyRootFilesystem": True,
},
}
}
else:
generated_config = {
"jupyterhub": {
"proxy": {"https": {"hosts": [self.spec["domain"]]}},
"ingress": {
"hosts": [self.spec["domain"]],
"tls": [
{
"secretName": "https-auto-tls",
"hosts": [self.spec["domain"]],
}
],
},
"singleuser": {
# If image_repo isn't set, just have an empty image dict
"image": {"name": self.cluster.spec["image_repo"]}
if "image_repo" in self.cluster.spec
else {},
},
"hub": {
"config": {},
"initContainers": [
{
"name": "templates-clone",
"image": "alpine/git",
"args": [
"clone",
"--",
"https://github.com/2i2c-org/default-hub-homepage",
"/srv/repo",
],
"securityContext": {
"runAsUser": 1000,
"allowPrivilegeEscalation": False,
"readOnlyRootFilesystem": True,
},
"volumeMounts": [
{
"name": "custom-templates",
"mountPath": "/srv/repo",
}
],
}
],
"extraContainers": [
{
"name": "templates-sync",
"image": "alpine/git",
"workingDir": "/srv/repo",
"command": ["/bin/sh"],
"args": [
"-c",
dedent(
f"""\
while true; do git fetch origin;
if [[ $(git ls-remote --heads origin {self.cluster.spec["name"]}-{self.spec["name"]} | wc -c) -ne 0 ]]; then
git reset --hard origin/{self.cluster.spec["name"]}-{self.spec["name"]};
else
git reset --hard origin/master;
fi
sleep 5m; done
"""
),
],
"securityContext": {
"runAsUser": 1000,
"allowPrivilegeEscalation": False,
"readOnlyRootFilesystem": True,
},
"volumeMounts": [
{
"name": "custom-templates",
"mountPath": "/srv/repo",
}
],
}
],
"extraVolumes": [{"name": "custom-templates", "emptyDir": {}}],
"extraVolumeMounts": [
{
"mountPath": "/usr/local/share/jupyterhub/custom_templates",
"name": "custom-templates",
"subPath": "templates",
},
"volumeMounts": [
{"name": "custom-templates", "mountPath": "/srv/repo"}
],
}
],
"extraVolumes": [{"name": "custom-templates", "emptyDir": {}}],
"extraVolumeMounts": [
{
"mountPath": "/usr/local/share/jupyterhub/custom_templates",
"name": "custom-templates",
"subPath": "templates",
},
{
"mountPath": "/usr/local/share/jupyterhub/static/extra-assets",
"name": "custom-templates",
"subPath": "extra-assets",
},
],
{
"mountPath": "/usr/local/share/jupyterhub/static/extra-assets",
"name": "custom-templates",
"subPath": "extra-assets",
},
],
},
},
},
}
}
#
# Allow explicilty ignoring auth0 setup
if self.spec["auth0"].get("enabled", True):
Expand Down Expand Up @@ -156,16 +193,6 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key):
Ideally, these would be done declaratively. Until then, let's put all of
them in this function.
"""
hub_helm_chart = self.spec["helm_chart"]

# FIXME: This section is only relevant if we generate any config in this
# function. Currently we only generate a JupyterHub API token for
# dask-gateway to use, but as that is resolved we can cleanup
# this entire function.
#
if hub_helm_chart != "basehub":
generated_config = {"basehub": generated_config}

# FIXME: This section can be removed upon resolution of the below linked issue, where we would
# instead just define a JupyterHub service under hub.services and
# rely on the JupyterHub Helm chart to generate an api token if
Expand All @@ -174,7 +201,9 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key):
# Blocked by https://github.com/dask/dask-gateway/issues/473 and a
# release including it.
#
if hub_helm_chart == "daskhub":
if self.spec["helm_chart"] == "daskhub":
generated_config = {"basehub": generated_config}

gateway_token = hmac.new(
secret_key, b"gateway-" + self.spec["name"].encode(), hashlib.sha256
).hexdigest()
Expand All @@ -185,6 +214,17 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key):
"hub", {}
).setdefault("services", {})["dask-gateway"] = {"apiToken": gateway_token}

elif self.spec["helm_chart"] == "binderhub":
gateway_token = hmac.new(
secret_key, b"gateway-" + self.spec["name"].encode(), hashlib.sha256
).hexdigest()
generated_config["dask-gateway"] = {
"gateway": {"auth": {"jupyterhub": {"apiToken": gateway_token}}}
}
generated_config["binderhub"].setdefault("jupyterhub", {}).setdefault(
"hub", {}
).setdefault("services", {})["dask-gateway"] = {"apiToken": gateway_token}

return generated_config

def deploy(self, auth_provider, secret_key):
Expand Down
7 changes: 7 additions & 0 deletions helm-charts/binderhub/templates/user-sa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{ if .Values.userServiceAccount.enabled -}}
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {{ .Values.userServiceAccount.annotations | toJson}}
name: user-sa
{{- end }}
24 changes: 24 additions & 0 deletions helm-charts/binderhub/values.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,31 @@ required:
- binderhub
- dask-gateway
- global
- userServiceAccount
properties:
userServiceAccount:
type: object
additionalProperties: false
required:
- enabled
properties:
enabled:
type: boolean
description: |
Enables creation of a Service Account for use by notebook & dask pods.

Config must still be set for notebook and dask pods to actually use
this service account, which is named user-sa.
annotations:
type: object
additionalProperties: true
description: |
Dictionary of annotations that can be applied to the service account.

When used with GKE and Workload Identity, you need to set
the annotation with key "iam.gke.io/gcp-service-account" to the
email address of the Google Service Account whose credentials it
should have.
# binderhub is a dependent helm chart, we rely on its schema validation for
# values passed to it and are not imposing restrictions on them in this helm
# chart.
Expand Down
Loading