Skip to content

Commit 6c227e6

Browse files
authored
Single Response Address Support (#877)
* Encapsulate response in ResponseManager (DRAFT) * Use key pair for crypto * Process responses via periodic callback * Minor touch ups - remove commented-out blocks * Use AES & RSA algs, convey version from launcher * Adjust connection poll interval, add some docstrings * Fix lint issues * Refactor response event framework * Add support for legacy (version 0) launchers * Update Python/Scala kernelspecs, revert R launcher changes * Missed some kernelspec updates previously * Update R kernel-launcher and specs to Version 1 * Update container-based configurations * Add response-port to helm deployment * Enable ability to handle multiple response IPs * Apply doc updates
1 parent 273a5f5 commit 6c227e6

File tree

51 files changed

+770
-380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+770
-380
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ install: ## Make a conda env with dist/*.whl and dist/*.tar.gz installed
8484
pip uninstall -y jupyter_enterprise_gateway
8585
conda env remove -y -n $(ENV)-install
8686

87-
bdist:
87+
bdist: lint
8888
make $(WHEEL_FILE)
8989

9090
$(WHEEL_FILE): $(WHEEL_FILES)

docs/source/config-options.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,16 @@ The following environment variables can be used to influence functionality and a
432432
Attempts to launch a kernel where KERNEL_UID's value is in this list will result
433433
in an exception indicating error 403 (Forbidden). See also EG_PROHIBITED_GIDS.
434434
435+
EG_RESPONSE_IP=None
436+
Experimental. The IP address to use to formulate the response address (with
437+
`EG_RESPONSE_PORT`). By default, the server's IP is used. However, we may find
438+
it necessary to use a different IP in cases where the target kernels are external
439+
to the Enterprise Gateway server (for example). It's value may also need to be
440+
set in cases where the computed (default) is not correct for the current topology.
441+
442+
EG_RESPONSE_PORT=8877
443+
The single response port used to receive connection information from launched kernels.
444+
435445
EG_SHARED_NAMESPACE=False
436446
Kubernetes only. This value indicates whether (True) or not (False) all kernel pods
437447
should reside in the same namespace as Enterprise Gateway. This is not a recommended
@@ -504,8 +514,8 @@ The following environment variables are managed by Enterprise Gateway and listed
504514
This value is set during each kernel launch and resides in the environment of
505515
the kernel launch process. Its value represents the address to which the remote
506516
kernel's connection information should be sent. Enterprise Gateway is listening
507-
on that socket and will close the socket once the remote kernel launcher has
508-
conveyed the appropriate information.
517+
on that socket and will associate that connnection information with the responding
518+
kernel.
509519
```
510520

511521
### Per-kernel Configuration Overrides

docs/source/docker.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Custom kernel images require some support files from the Enterprise Gateway repo
7171
Enterprise Gateway provides a single [bootstrap-kernel.sh](https://github.com/jupyter/enterprise_gateway/blob/master/etc/kernel-launchers/bootstrap/bootstrap-kernel.sh) script that handles the three kernel languages supported out of the box - Python, R, and Scala. When a kernel image is started by Enterprise Gateway, parameters used within the bootstrap-kernel.sh script are conveyed via environment variables. The bootstrap script is then responsible for validating and converting those parameters to meaningful arguments to the appropriate launcher.
7272

7373
##### Kernel Launcher
74-
The kernel launcher, as discussed [here](system-architecture.html#kernel-launchers) does a number of things. In paricular, it creates the connection ports and conveys that connection information back to Enterprise Gateway via the socket identified by the response address parameter. Although not a requirement for container-based usage, it is recommended that the launcher be written in the same language as the kernel. (This is more of a requirement when used in applications like YARN.)
74+
The kernel launcher, as discussed [here](system-architecture.html#kernel-launchers) does a number of things. In particular, it creates the connection ports and conveys that connection information back to Enterprise Gateway via the socket identified by the response address parameter. Although not a requirement for container-based usage, it is recommended that the launcher be written in the same language as the kernel. (This is more of a requirement when used in applications like YARN.)
7575

7676
#### About Jupyter Docker-stacks Images
7777
Most of what is presented assumes the base image for your custom image is derived from the [Jupyter Docker-stacks](https://github.com/jupyter/docker-stacks) repository. As a result, it's good to cover what makes up those assumptions so you can build your own image independently from the docker-stacks repository.
@@ -189,7 +189,9 @@ cp -r python_kubernetes python_myCustomKernel
189189
"--RemoteProcessProxy.kernel-id",
190190
"{kernel_id}",
191191
"--RemoteProcessProxy.response-address",
192-
"{response_address}"
192+
"{response_address}",
193+
"--RemoteProcessProxy.public-key",
194+
"{public_key}"
193195
]
194196
}
195197
```

docs/source/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ In that case, the image should ensure the availability of the kernel libraries a
124124
The launch of containerized kernels via Enterprise Gateway is two-fold.
125125

126126
1. First, there's the argv section in the kernelspec that is processed by the server. In these cases, the command that is invoked is a python script using the target container's api (kubernetes, docker, or docker swarm) that is responsible for converting any necessary "parameters" to environment variables, etc. that are used during the actual container creation.
127-
2. The command that is run in the container is the actual kernel launcher script. This launcher is responsible for taking the response address (which is now an env variable) and returning the kernel's connection information back on that response address to Enterprise Gateway. The kernel launcher does additional things - but primarily listens for interrupt and shutdown requests, which it then passes along to the actual (embedded) kernel.
127+
2. The command that is run in the container is actually the kernel launcher script. This script is responsible for taking the response address (which is now an env variable) and returning the kernel's connection information back on that response address to Enterprise Gateway. The kernel launcher does additional things - but primarily listens for interrupt and shutdown requests, which it then passes along to the actual (embedded) kernel.
128128

129129
So container environments have two launches - one to launch the container itself, the other to launch the kernel (within the container).
130130

docs/source/kernel-docker.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ As always, kernels are launched by virtue of the `argv:` stanza in their respect
9292
"{kernel_id}",
9393
"--RemoteProcessProxy.response-address",
9494
"{response_address}",
95-
"--RemoteProcessProxy.spark-context-initialization-mode",
96-
"none"
95+
"--RemoteProcessProxy.public-key",
96+
"{public_key}"
9797
]
9898
}
9999
```
@@ -119,8 +119,8 @@ Running containers in Docker Swarm versus traditional Docker are different enoug
119119
"{kernel_id}",
120120
"--RemoteProcessProxy.response-address",
121121
"{response_address}",
122-
"--RemoteProcessProxy.spark-context-initialization-mode",
123-
"none"
122+
"--RemoteProcessProxy.public-key",
123+
"{public_key}"
124124
]
125125
}
126126
```

docs/source/kernel-kubernetes.md

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ spec:
3636
- name: http
3737
port: 8888
3838
targetPort: 8888
39+
- name: response
40+
port: 8877
41+
targetPort: 8877
3942
selector:
4043
gateway-selector: enterprise-gateway
4144
sessionAffinity: ClientIP
@@ -69,6 +72,12 @@ spec:
6972
serviceAccountName: enterprise-gateway-sa
7073
containers:
7174
- env:
75+
- name: EG_PORT
76+
value: "8888"
77+
78+
- name: EG_RESPONSE_PORT
79+
value: "8877"
80+
7281
# Created above.
7382
- name: EG_NAMESPACE
7483
value: "enterprise-gateway"
@@ -105,6 +114,7 @@ spec:
105114
args: ["--gateway"]
106115
ports:
107116
- containerPort: 8888
117+
- containerPort: 8877
108118
```
109119
#### Namespaces
110120
A best practice for Kubernetes applications running in an enterprise is to isolate applications via namespaces. Since Enterprise Gateway also requires isolation at the kernel level, it makes sense to use a namespace for each kernel, by default.
@@ -348,41 +358,50 @@ There are essentially two kinds of kernels (independent of language) launched wi
348358

349359
When _vanilla_ kernels are launched, Enterprise Gateway is responsible for creating the corresponding pod. On the other hand, _spark-on-kubernetes_ kernels are launched via `spark-submit` with a specific `master` URI - which then creates the corresponding pod(s) (including executor pods). Images can be launched using both forms provided they have the appropriate support for Spark installed.
350360

351-
Here's the yaml configuration used when _vanilla_ kernels are launched. As noted in the `KubernetesProcessProxy` section below, this file ([kernel-pod.yaml](https://github.com/jupyter/enterprise_gateway/blob/master/etc/kernel-launchers/kubernetes/scripts/kernel-pod.yaml)) serves as a template where each of the tags surrounded with `${}` represent variables that are substituted at the time of the kernel's launch. All `${kernel_xxx}` parameters correspond to `KERNEL_XXX` environment variables that can be specified from the client in the kernel creation request's json body.
361+
Here's the yaml configuration used when _vanilla_ kernels are launched. As noted in the `KubernetesProcessProxy` section below, this file ([kernel-pod.yaml.j2](https://github.com/jupyter/enterprise_gateway/blob/master/etc/kernel-launchers/kubernetes/scripts/kernel-pod.yaml.j2)) serves as a template where each of the tags surrounded with `{{` and `}}` represent variables that are substituted at the time of the kernel's launch. All `{{ kernel_xxx }}` parameters correspond to `KERNEL_XXX` environment variables that can be specified from the client in the kernel creation request's json body.
352362
```yaml
353363
apiVersion: v1
354364
kind: Pod
355365
metadata:
356-
name: ${kernel_username}-${kernel_id}
357-
namespace: ${kernel_namespace}
366+
name: "{{ kernel_pod_name }}"
367+
namespace: "{{ kernel_namespace }}"
358368
labels:
359-
kernel_id: ${kernel_id}
369+
kernel_id: "{{ kernel_id }}"
360370
app: enterprise-gateway
361371
component: kernel
362372
spec:
363373
restartPolicy: Never
364-
serviceAccountName: ${kernel_service_account_name}
374+
serviceAccountName: "{{ kernel_service_account_name }}"
375+
{% if kernel_uid is defined or kernel_gid is defined %}
365376
securityContext:
366-
runAsUser: ${kernel_uid}
367-
runAsGroup: ${kernel_gid}
377+
{% if kernel_uid is defined %}
378+
runAsUser: {{ kernel_uid | int }}
379+
{% endif %}
380+
{% if kernel_gid is defined %}
381+
runAsGroup: {{ kernel_gid | int }}
382+
{% endif %}
383+
fsGroup: 100
384+
{% endif %}
368385
containers:
369386
- env:
370387
- name: EG_RESPONSE_ADDRESS
371-
value: ${eg_response_address}
388+
value: "{{ eg_response_address }}"
389+
- name: EG_PUBLIC_KEY
390+
value: "{{ eg_public_key }}"
372391
- name: KERNEL_LANGUAGE
373-
value: ${kernel_language}
392+
value: "{{ kernel_language }}"
374393
- name: KERNEL_SPARK_CONTEXT_INIT_MODE
375-
value: ${kernel_spark_context_init_mode}
394+
value: "{{ kernel_spark_context_init_mode }}"
376395
- name: KERNEL_NAME
377-
value: ${kernel_name}
396+
value: "{{ kernel_name }}"
378397
- name: KERNEL_USERNAME
379-
value: ${kernel_username}
398+
value: "{{ kernel_username }}"
380399
- name: KERNEL_ID
381-
value: ${kernel_id}
400+
value: "{{ kernel_id }}"
382401
- name: KERNEL_NAMESPACE
383-
value: ${kernel_namespace}
384-
image: ${kernel_image}
385-
name: ${kernel_username}-${kernel_id}
402+
value: "{{ kernel_namespace }}"
403+
image: "{{ kernel_image }}"
404+
name: "{{ kernel_pod_name }}"
386405
```
387406
There are a number of items worth noting:
388407
1. Kernel pods can be identified in three ways using `kubectl`:
@@ -393,7 +412,7 @@ There are a number of items worth noting:
393412
Note that since kernels run in isolated namespaces by default, it's often helpful to include the clause `--all-namespaces` on commands that will span namespaces. To isolate commands to a given namespace, you'll need to add the namespace clause `--namespace <namespace-name>`.
394413
1. Each kernel pod is named by the invoking user (via the `KERNEL_USERNAME` env) and its kernel_id (env `KERNEL_ID`). This identifier also applies to those kernels launched within `spark-on-kubernetes`.
395414
1. Kernel pods use the specified `securityContext`. If env `KERNEL_UID` is not specified in the kernel creation request a default value of `1000` (the jovyan user) will be used. Similarly for `KERNEL_GID`, whose default is `100` (the users group). In addition, Enterprise Gateway enforces a lists of prohibited UID and GID values. By default, this list is initialized to the 0 (root) UID and GID. Administrators can configure the `EG_PROHIBITED_UIDS` and `EG_PROHIBITED_GIDS` environment variables via the enterprise-gateway.yaml file with comma-separated values to alter the set of user and group ids to be prevented.
396-
1. As noted above, if `KERNEL_NAMESPACE` is not provided in the request, Enterprise Gateway will create a namespace using the same naming algorithm for the pod. In addition, the `kernel-controller` cluster role will be bound to a namespace-scoped role binding of the same name using the namespace's default service account as its subject. Users wishing to use their own kernel namespaces must provide **both** `KERNEL_NAMESPACE` and `KERNEL_SERVICE_ACCOUNT_NAME` as these are both used in the `kernel-pod.yaml` as `${kernel_namespace}` and `${kernel_service_account_name}`, respectively.
415+
1. As noted above, if `KERNEL_NAMESPACE` is not provided in the request, Enterprise Gateway will create a namespace using the same naming algorithm for the pod. In addition, the `kernel-controller` cluster role will be bound to a namespace-scoped role binding of the same name using the namespace's default service account as its subject. Users wishing to use their own kernel namespaces must provide **both** `KERNEL_NAMESPACE` and `KERNEL_SERVICE_ACCOUNT_NAME` as these are both used in the `kernel-pod.yaml.j2` as `{{ kernel_namespace }}` and `{{ kernel_service_account_name }}`, respectively.
397416
1. Kernel pods have restart policies of `Never`. This is because the Jupyter framework already has built-in logic for auto-restarting failed kernels and any other restart policy would likely interfere with the built-in behaviors.
398417
1. The parameters to the launcher that is built into the image are communicated via environment variables as noted in the `env:` section above.
399418

@@ -514,8 +533,8 @@ As always, kernels are launched by virtue of the `argv:` stanza in their respect
514533
"{kernel_id}",
515534
"--RemoteProcessProxy.response-address",
516535
"{response_address}",
517-
"--RemoteProcessProxy.spark-context-initialization-mode",
518-
"none"
536+
"--RemoteProcessProxy.public-key",
537+
"{public_key}"
519538
]
520539
}
521540
```
@@ -536,6 +555,8 @@ For these invocations, the `argv:` is nearly identical to non-kubernetes configu
536555
"{kernel_id}",
537556
"--RemoteProcessProxy.response-address",
538557
"{response_address}",
558+
"--RemoteProcessProxy.public-key",
559+
"{public_key}",
539560
"--RemoteProcessProxy.spark-context-initialization-mode",
540561
"lazy"
541562
]

docs/source/kernel-spark-standalone.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ After that, you should have a kernel.json that looks similar to the one below:
6767
"--RemoteProcessProxy.kernel-id",
6868
"{kernel_id}",
6969
"--RemoteProcessProxy.response-address",
70-
"{response_address}"
70+
"{response_address}",
71+
"--RemoteProcessProxy.public-key",
72+
"{public_key}"
7173
]
7274
}
7375
```

docs/source/kernel-yarn-client-mode.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ After that, you should have a kernel.json that looks similar to the one below:
6464
"--RemoteProcessProxy.kernel-id",
6565
"{kernel_id}",
6666
"--RemoteProcessProxy.response-address",
67-
"{response_address}"
67+
"{response_address}",
68+
"--RemoteProcessProxy.public-key",
69+
"{public_key}"
6870
]
6971
}
7072
```

docs/source/kernel-yarn-cluster-mode.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ After that, you should have a `kernel.json` that looks similar to the one below:
6464
"--RemoteProcessProxy.kernel-id",
6565
"{kernel_id}",
6666
"--RemoteProcessProxy.response-address",
67-
"{response_address}"
67+
"{response_address}",
68+
"--RemoteProcessProxy.public-key",
69+
"{public_key}"
6870
]
6971
}
7072
```

0 commit comments

Comments
 (0)