diff --git a/dev-support/docker-images/jupyter/Dockerfile b/dev-support/docker-images/jupyter/Dockerfile index 69eb76e881..110ed431b6 100644 --- a/dev-support/docker-images/jupyter/Dockerfile +++ b/dev-support/docker-images/jupyter/Dockerfile @@ -19,7 +19,7 @@ FROM $BASE_IMAGE ARG NB_USER="jovyan" ARG NB_UID="1000" -ARG NB_PREFIX="/jupyter/" +ARG NB_PREFIX="/" ARG NB_PORT=8888 USER root @@ -72,9 +72,7 @@ RUN mv /tini /usr/local/bin/tini && chmod +x /usr/local/bin/tini # Install python package RUN pip uninstall -y enum34 -RUN pip3 --no-cache-dir install \ - jupyterhub \ - jupyterlab +RUN pip3 --no-cache-dir install jupyterlab # Configure container startup EXPOSE $NB_PORT diff --git a/dev-support/k8s/deploy-notebook-controller.sh b/dev-support/k8s/deploy-notebook-controller.sh new file mode 100755 index 0000000000..a672daa7ce --- /dev/null +++ b/dev-support/k8s/deploy-notebook-controller.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +set -e + +readonly NOTEBOOK_CONTROLLER_IMAGE="gcr.io/kubeflow-images-public/notebook-controller:v1.1.0-g253890cb" + +if [ -L "${BASH_SOURCE-$0}" ]; then + PWD=$(dirname "$(readlink "${BASH_SOURCE-$0}")") +else + PWD=$(dirname "${BASH_SOURCE-$0}") +fi +CURRENT_PATH=$(cd "${PWD}">/dev/null; pwd) +export CURRENT_PATH +export SUBMARINE_HOME=${CURRENT_PATH}/../.. +# lib.sh use the ROOT variable +export ROOT="${SUBMARINE_HOME}/submarine-cloud/" +export KUBECONFIG="${HOME}/.kube/kind-config-${clusterName:-kind}" + +# shellcheck source=./../../submarine-cloud/hack/lib.sh +source "${SUBMARINE_HOME}/submarine-cloud/hack/lib.sh" + +########################################### +# Load local docker image into registry +# Globals: +# KIND_BIN +# Arguments: +# image +########################################### +function load_image_to_registry() { + if [[ ! $(docker inspect "$1" > /dev/null) ]] ; then + docker pull "$1" + fi + ${KIND_BIN} load docker-image "$1" +} + + +function main() { + + hack::ensure_kubectl + + load_image_to_registry "${NOTEBOOK_CONTROLLER_IMAGE}" + ${KUBECTL_BIN} apply -k "${CURRENT_PATH}/notebook-controller" + +} + +main "$@" diff --git a/dev-support/k8s/notebook-controller/cluster-role-binding.yaml b/dev-support/k8s/notebook-controller/cluster-role-binding.yaml new file mode 100644 index 0000000000..a1a3945401 --- /dev/null +++ b/dev-support/k8s/notebook-controller/cluster-role-binding.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: role +subjects: +- kind: ServiceAccount + name: service-account diff --git a/dev-support/k8s/notebook-controller/cluster-role.yaml b/dev-support/k8s/notebook-controller/cluster-role.yaml new file mode 100644 index 0000000000..16b6253a8f --- /dev/null +++ b/dev-support/k8s/notebook-controller/cluster-role.yaml @@ -0,0 +1,107 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: role +rules: +- apiGroups: + - apps + resources: + - statefulsets + - deployments + verbs: + - '*' +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - '*' +- apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch + - create +- apiGroups: + - kubeflow.org + resources: + - notebooks + - notebooks/status + - notebooks/finalizers + verbs: + - '*' +- apiGroups: + - networking.istio.io + resources: + - virtualservices + verbs: + - '*' + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kubeflow-notebooks-admin + labels: + rbac.authorization.kubeflow.org/aggregate-to-kubeflow-admin: "true" +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.kubeflow.org/aggregate-to-kubeflow-notebooks-admin: "true" +rules: [] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kubeflow-notebooks-edit + labels: + rbac.authorization.kubeflow.org/aggregate-to-kubeflow-edit: "true" + rbac.authorization.kubeflow.org/aggregate-to-kubeflow-notebooks-admin: "true" +rules: +- apiGroups: + - kubeflow.org + resources: + - notebooks + - notebooks/status + verbs: + - get + - list + - watch + - create + - delete + - deletecollection + - patch + - update + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kubeflow-notebooks-view + labels: + rbac.authorization.kubeflow.org/aggregate-to-kubeflow-view: "true" +rules: +- apiGroups: + - kubeflow.org + resources: + - notebooks + - notebooks/status + verbs: + - get + - list + - watch diff --git a/dev-support/k8s/notebook-controller/crd.yaml b/dev-support/k8s/notebook-controller/crd.yaml new file mode 100644 index 0000000000..b6556bd4cc --- /dev/null +++ b/dev-support/k8s/notebook-controller/crd.yaml @@ -0,0 +1,64 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: notebooks.kubeflow.org +spec: + group: kubeflow.org + names: + kind: Notebook + plural: notebooks + singular: notebook + scope: Namespaced + subresources: + status: {} + versions: + - name: v1alpha1 + served: true + storage: false + - name: v1beta1 + served: true + storage: true + - name: v1 + served: true + storage: false + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + template: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file' + properties: + spec: + type: object + type: object + type: object + status: + properties: + conditions: + description: Conditions is an array of current conditions + items: + properties: + type: + description: Type of the confition/ + type: string + required: + - type + type: object + type: array + required: + - conditions + type: object diff --git a/dev-support/k8s/notebook-controller/deployment.yaml b/dev-support/k8s/notebook-controller/deployment.yaml new file mode 100644 index 0000000000..ded2ad0b8a --- /dev/null +++ b/dev-support/k8s/notebook-controller/deployment.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment +spec: + template: + metadata: + annotations: + sidecar.istio.io/inject: "false" + spec: + containers: + - name: manager + image: gcr.io/kubeflow-images-public/notebook-controller:v1.1.0-g253890cb + command: + - /manager + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + serviceAccountName: service-account diff --git a/dev-support/k8s/notebook-controller/deployment_patch.yaml b/dev-support/k8s/notebook-controller/deployment_patch.yaml new file mode 100644 index 0000000000..a7dfb43349 --- /dev/null +++ b/dev-support/k8s/notebook-controller/deployment_patch.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment +spec: + template: + spec: + containers: + - name: manager + env: + # We use a patch to set the USE_ISTIO because in other patches + # we want to set it to a configMapRef and so if we include the value + # in the base when we do the merge we end up with 2 fields setting the value. + - name: USE_ISTIO + value: "false" \ No newline at end of file diff --git a/dev-support/k8s/notebook-controller/kustomization.yaml b/dev-support/k8s/notebook-controller/kustomization.yaml new file mode 100644 index 0000000000..dbc92d051b --- /dev/null +++ b/dev-support/k8s/notebook-controller/kustomization.yaml @@ -0,0 +1,42 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- cluster-role-binding.yaml +- cluster-role.yaml +- crd.yaml +- deployment.yaml +- service-account.yaml +- service.yaml +namePrefix: notebook-controller- +namespace: default +patchesStrategicMerge: +- deployment_patch.yaml +commonLabels: + app: notebook-controller + kustomize.component: notebook-controller +images: +- name: gcr.io/kubeflow-images-public/notebook-controller + newName: gcr.io/kubeflow-images-public/notebook-controller + newTag: v1.1.0-g253890cb +configMapGenerator: +- name: parameters + literals: + - USE_ISTIO=false + - ISTIO_GATEWAY= +generatorOptions: + disableNameSuffixHash: true +vars: +- fieldref: + fieldPath: data.USE_ISTIO + name: USE_ISTIO + objref: + apiVersion: v1 + kind: ConfigMap + name: parameters +- fieldref: + fieldPath: data.ISTIO_GATEWAY + name: ISTIO_GATEWAY + objref: + apiVersion: v1 + kind: ConfigMap + name: parameters diff --git a/dev-support/k8s/notebook-controller/service-account.yaml b/dev-support/k8s/notebook-controller/service-account.yaml new file mode 100644 index 0000000000..a36cbd800f --- /dev/null +++ b/dev-support/k8s/notebook-controller/service-account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: service-account diff --git a/dev-support/k8s/notebook-controller/service.yaml b/dev-support/k8s/notebook-controller/service.yaml new file mode 100644 index 0000000000..c7368f9703 --- /dev/null +++ b/dev-support/k8s/notebook-controller/service.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Service +metadata: + name: service +spec: + ports: + - port: 443 diff --git a/docs/userdocs/k8s/jupyter-example.yaml b/docs/userdocs/k8s/jupyter-example.yaml new file mode 100644 index 0000000000..35c5fac60c --- /dev/null +++ b/docs/userdocs/k8s/jupyter-example.yaml @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +apiVersion: kubeflow.org/v1alpha1 +kind: Notebook +metadata: + labels: + app: my-nb + name: my-nb + namespace: default +spec: + template: + spec: + containers: + - env: [] + image: apache/submarine:tf2.1.0-jupyter + name: my-nb + resources: + limits: {} + requests: + cpu: "0.5" + memory: 1.0Gi diff --git a/docs/userdocs/k8s/notebook.md b/docs/userdocs/k8s/notebook.md new file mode 100644 index 0000000000..33583d268e --- /dev/null +++ b/docs/userdocs/k8s/notebook.md @@ -0,0 +1,70 @@ + + +# Notebook Guide +This guide describes how to use Kubeflow's notebook-controller to manage jupyter notebook instances. + +## Notebook Controller +The controller creates a StatefulSet to manage the notebook instance, and a Service to expose its port. \ +Please refer to the [link](https://github.com/kubeflow/kubeflow/tree/master/components/notebook-controller) for more info. + + +### Pod Spec +To specify the PodSpec for the jupyter notebook. +```yaml +apiVersion: kubeflow.org/v1alpha1 +kind: Notebook +metadata: + name: {NOTEBOOK_NAME} + namespace: {NAMESPACE} + labels: + app: {NOTEBOOK_NAME} +spec: + template: + spec: + containers: + - image: {IMAGE_NAME} + name: {NOTEBOOK_NAME} + env: [] + resources: + requests: + cpu: "0.5" + memory: "1.0Gi" + volumes: [] + ... + .. +``` +You could refer to this sample [Dockerfile](../../../dev-support/docker-images/jupyter/Dockerfile) for building your own +jupyter docker image and the CR (Notebook) [example](jupyter-example.yaml). + +### Create a notebook instance +``` +kubectl apply -f jupyter-example.yaml +``` + +## Access the notebook locally +The controller creates a Service which will target TCP port 8888 on jupyter pod and be exposed on port 80 internally. \ +You can use the following command to set up port forwarding to the notebook. +``` +kubectl port-forward -n ${NAMESPACE} svc/${NOTEBOOK_NAME} 8888:80 +``` +To access the jupyter notebook, open the following URL in your browser. +``` +http://localhost:8888/notebook/${NAMESPACE}/${NOTEBOOK_NAME} +``` diff --git a/submarine-cloud/hack/deploy-submarine.sh b/submarine-cloud/hack/deploy-submarine.sh index 48d8f1e6ce..900634b8d2 100755 --- a/submarine-cloud/hack/deploy-submarine.sh +++ b/submarine-cloud/hack/deploy-submarine.sh @@ -21,7 +21,6 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd) cd $ROOT SUBMARINE_HOME=${ROOT}/.. SUBMARINE_VERSION="0.4.0" -TF_JUPYTER_IMAGE="apache/submarine:tf2.1.0-jupyter"; source $ROOT/hack/lib.sh @@ -71,11 +70,6 @@ function install_submarine() { fi $KIND_BIN load docker-image apache/submarine:database-${SUBMARINE_VERSION} - if ! docker inspect ${TF_JUPYTER_IMAGE} >/dev/null ; then - docker pull ${TF_JUPYTER_IMAGE} - fi - $KIND_BIN load docker-image ${TF_JUPYTER_IMAGE} >/dev/null - if ! docker inspect apache/submarine:server-${SUBMARINE_VERSION} >/dev/null ; then docker pull apache/submarine:server-${SUBMARINE_VERSION} fi diff --git a/submarine-cloud/hack/integration-test.sh b/submarine-cloud/hack/integration-test.sh index 48fd23da17..51a6723e8c 100755 --- a/submarine-cloud/hack/integration-test.sh +++ b/submarine-cloud/hack/integration-test.sh @@ -31,6 +31,7 @@ export KUBECONFIG=~/.kube/kind-config-${clusterName:-kind} function start() { $ROOT/hack/kind-cluster-build.sh $SUBMARINE_HOME/dev-support/k8s/deploy-kubeflow-operators.sh -a + $SUBMARINE_HOME/dev-support/k8s/deploy-notebook-controller.sh $ROOT/hack/deploy-submarine.sh --test for((i=1;i<=30;i++)); do @@ -75,7 +76,6 @@ function update_docker_images() { $SUBMARINE_HOME/dev-support/docker-images/database/build.sh $SUBMARINE_HOME/dev-support/docker-images/operator/build.sh $SUBMARINE_HOME/dev-support/docker-images/submarine/build.sh - $SUBMARINE_HOME/dev-support/docker-images/jupyter/build.sh docker images } diff --git a/submarine-cloud/hack/kind-cluster-build.sh b/submarine-cloud/hack/kind-cluster-build.sh index f1c743ddde..b252f25272 100755 --- a/submarine-cloud/hack/kind-cluster-build.sh +++ b/submarine-cloud/hack/kind-cluster-build.sh @@ -136,12 +136,9 @@ EOF for ((k=1;k<=${volumeNum};k++)) do mkdir -p ${data_dir}/worker${i}/vol${k} - mkdir -p ${data_dir}/worker${i}/submarine-jupyter cat <> ${configFile} - containerPath: /mnt/disks/vol${k} hostPath: ${data_dir}/worker${i}/vol${k} - - containerPath: /mnt/disks/submarine-jupyter - hostPath: ${data_dir}/worker${i}/submarine-jupyter EOF done done diff --git a/submarine-cloud/manifests/submarine-cluster/jupyter.yaml b/submarine-cloud/manifests/submarine-cluster/jupyter.yaml deleted file mode 100644 index 1015c68e53..0000000000 --- a/submarine-cloud/manifests/submarine-cluster/jupyter.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: jupyter-notebook - labels: - app: jupyter -spec: - selector: - matchLabels: - app: jupyter - replicas: 1 - template: - metadata: - labels: - app: jupyter - spec: - containers: - - name: jupyter - image: apache/submarine:tf2.1.0-jupyter - imagePullPolicy: IfNotPresent - volumeMounts: - - mountPath: /home/jovyan - name: jupyter-storage - volumes: - - name: jupyter-storage - hostPath: - path: /mnt/disks/submarine-jupyter - type: DirectoryOrCreate ---- -kind: Service -apiVersion: v1 -metadata: - name: jupyter-svc -spec: - selector: - app: jupyter - ports: - - port: 8888 diff --git a/submarine-cloud/manifests/submarine-cluster/submarine-server.yaml b/submarine-cloud/manifests/submarine-cluster/submarine-server.yaml index dc566a3ec5..c318eb7635 100644 --- a/submarine-cloud/manifests/submarine-cluster/submarine-server.yaml +++ b/submarine-cloud/manifests/submarine-cluster/submarine-server.yaml @@ -108,10 +108,6 @@ spec: backend: serviceName: submarine-svc servicePort: 8080 - - path: /jupyter - backend: - serviceName: jupyter-svc - servicePort: 8888 --- # You can also access the submarine workbench via port-forward