Skip to content

Commit

Permalink
Add authentication to jupyterhub
Browse files Browse the repository at this point in the history
  • Loading branch information
chiayi committed Sep 18, 2023
1 parent 8f9260e commit c4d6c33
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 72 deletions.
71 changes: 51 additions & 20 deletions jupyter-on-gke/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ We've also included some example notebooks (`ai-on-gke/ray-on-gke/example_notebo
`ai-on-gke/ray-on-gke/README.md` to install a Ray cluster.

This module deploys the following resources, once per user:

* JupyterHub deployment
* User namespace
* Kubernetes service accounts

## Installation

Preinstall the following on your computer:

* Kubectl
* Terraform
* Terraform
* Helm
* Gcloud

Expand Down Expand Up @@ -49,6 +51,30 @@ If not, set `enable_create_namespace` to `true` so a new k8s namespace is create

7. Run `terraform apply`

### Authentication (Enabled by Default)

> **_NOTE:_** To disable GCP IAP authentication, set the `add_auth` boolean in variables.tf to `false` and change the image Jupyterhub is using.
1. After installing Jupyterhub, you will need to retrieve the name of the backend-service from GCP using the following command:

```cmd
gcloud compute backend-services list --project=%PROJECT_ID%
```

You can describe the service to check which service is connected to `proxy-public` using:

```cmd
gcloud compute backend-services describe SERVICE_NAME --project=%PROJECT_ID% --global
```

2. Once you get the name of the backend-service, replace the variable in the [variables.tf](https://github.com/GoogleCloudPlatform/ai-on-gke/blob/main/jupyter-on-gke/variables.tf) file.

3. Re-run `terraform apply`

4. Navigate to the [GCP IAP Cloud Console](https://pantheon.corp.google.com/security/iap) and select your backend-service checkbox.

5. Click on `Add Principal`, insert the new principle and select under `Cloud IAP` with role `IAP-secured Web App User`

## Using Ray with Jupyter

1. Run `kubectl get services -n <namespace>`. The namespace is the user name that you specified above.
Expand All @@ -62,46 +88,49 @@ If not, set `enable_create_namespace` to `true` so a new k8s namespace is create
4. The Ray cluster is available at `ray://example-cluster-kuberay-head-svc:10001`. To access the cluster, you can open one of the sample notebooks under `example_notebooks` (via `File` -> `Open from URL` in the Jupyter notebook window and use the raw file URL from GitHub) and run through the example. Ex url: https://raw.githubusercontent.com/GoogleCloudPlatform/ai-on-gke/main/ray-on-gke/example_notebooks/gpt-j-online.ipynb

5. To use the Ray dashboard, run the following command to port-forward:
```

```cmd
kubectl port-forward -n <namespace> service/example-cluster-kuberay-head-svc 8265:8265
```

And then open the dashboard using the following URL:
```

```cmd
http://localhost:8265
```

## Securing Your Cluster Endpoints

For demo purposes, this repo creates a public IP for the Jupyter notebook with basic dummy authentication. To secure your cluster, it is *strong recommended* to replace
this with your own secure endpoints.
this with your own secure endpoints.

For more information, please take a look at the following links:

* https://cloud.google.com/iap/docs/enabling-kubernetes-howto
* https://cloud.google.com/endpoints/docs/openapi/get-started-kubernetes-engine
* https://jupyterhub.readthedocs.io/en/stable/tutorial/getting-started/authenticators-users-basics.html


## Running GPT-J-6B

This example is adapted from Ray AIR's examples [here](https://docs.ray.io/en/master/ray-air/examples/gptj_serving.html).

1. Open the `gpt-j-online.ipynb` notebook under `ai-on-gke/ray-on-gke/example_notebooks`.

2. Open a terminal in the Jupyter session and install Ray AIR:
```
pip install ray[air]
```

```cmd
pip install ray[air]
```

3. Run through the notebook cells. You can change the prompt in the last cell:
```
prompt = (
## Input your own prompt here
)
```

4. This should output a generated text response.
```cmd
prompt = (
## Input your own prompt here
)
```

4. This should output a generated text response.

## Logging and Monitoring

Expand All @@ -110,14 +139,16 @@ and Managed Prometheus for monitoring. To see your Ray cluster logs:

1. Open Cloud Console and open Logging
2. If using Jupyter notebook for job submission, use the following query parameters:
```
resource.type="k8s_container"
resource.labels.cluster_name=%CLUSTER_NAME%
resource.labels.pod_name=%RAY_HEAD_POD_NAME%
resource.labels.container_name="fluentbit"
```

```log explorer
resource.type="k8s_container"
resource.labels.cluster_name=%CLUSTER_NAME%
resource.labels.pod_name=%RAY_HEAD_POD_NAME%
resource.labels.container_name="fluentbit"
```

To see monitoring metrics:

1. Open Cloud Console and open Metrics Explorer
2. In "Target", select "Prometheus Target" and then "Ray".
3. Select the metric you want to view, and then click "Apply".
Expand Down
24 changes: 24 additions & 0 deletions jupyter-on-gke/iap_module/deployments/backend-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2023 Google LLC
#
# 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.

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: config-default
namespace: ray
spec:
iap:
enabled: true
oauthclientCredentials:
secretName: my-secret
22 changes: 22 additions & 0 deletions jupyter-on-gke/iap_module/deployments/managed-cert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2023 Google LLC
#
# 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.

apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
namespace: ray
spec:
domains:
- ${ip_addr}
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,4 @@ spec:
service:
name: proxy-public
port:
number: 80
---
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
namespace: ray
spec:
domains:
- ${ip_addr}
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: config-default
namespace: ray
spec:
iap:
enabled: true
oauthclientCredentials:
secretName: my-secret
number: 80
65 changes: 45 additions & 20 deletions jupyter-on-gke/iap_module/iap.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,76 @@
# See the License for the specific language governing permissions and
# limitations under the License.

provider "kubernetes" {
config_path = pathexpand("~/.kube/config")
}
# provider "kubernetes" {
# config_path = pathexpand("~/.kube/config")
# }

# provider "kubectl" {
# config_path = pathexpand("~/.kube/config")
# }

# provider "google" {
# config_path = pathexpand("~/.kube/config")
# project = var.project_id
# region = var.location
# }

# provider "google-beta" {
# config_path = pathexpand("~/.kube/config")
# project = var.project_id
# region = var.location
# }

provider "kubectl" {
config_path = pathexpand("~/.kube/config")
data "local_file" "backend_config_yaml" {
filename = "${path.module}/deployments/backend-config.yaml"
}

provider "google" {
config_path = pathexpand("~/.kube/config")
project = var.project_id
region = var.location
data "local_file" "managed_cert_yaml" {
filename = "${path.module}/deployments/managed-cert.yaml"
}

provider "google-beta" {
config_path = pathexpand("~/.kube/config")
project = var.project_id
region = var.location
data "local_file" "static_ingress_yaml" {
filename = "${path.module}/deployments/static-ingress.yaml"
}

data "google_compute_backend_service" "jupyter-ingress" {
name = var.service_name
project = var.project_id

depends_on = [ kubectl_manifest.deployments ]
depends_on = [ kubectl_manifest.backend_config ]
}

# Reserve IP Address
resource "google_compute_global_address" "default" {
project = var.project_id
provider = google-beta
project = var.project_id
name = "jupyter-address"
address_type = "EXTERNAL"
ip_version = "IPV4"
}

resource "kubectl_manifest" "deployments" {
resource "kubectl_manifest" "backend_config" {
override_namespace = var.namespace
yaml_body = templatefile("${path.module}/deployments.yaml", {
ip_addr = "${resource.google_compute_global_address.default.address}.nip.io"
static_addr_name = "${resource.google_compute_global_address.default.name}"
})
yaml_body = templatefile("${path.module}/deployments/backend-config.yaml", {})
depends_on = [ kubectl_manifest.static_ingress ]
}

resource "kubectl_manifest" "managed_cert" {
override_namespace = var.namespace
yaml_body = templatefile("${path.module}/deployments/managed-cert.yaml", {
ip_addr = "${google_compute_global_address.default.address}.nip.io"
})
depends_on = [ google_compute_global_address.default ]
}

resource "kubectl_manifest" "static_ingress" {
override_namespace = var.namespace
yaml_body = templatefile("${path.module}/deployments/static-ingress.yaml", {
static_addr_name = "${google_compute_global_address.default.name}"
})
depends_on = [ google_compute_global_address.default, kubectl_manifest.managed_cert ]
}

resource "kubernetes_secret" "my-secret" {
metadata {
name = "my-secret"
Expand Down
6 changes: 3 additions & 3 deletions jupyter-on-gke/iap_module/output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

output "service_id" {
description = "Service ID"
value = data.google_compute_backend_service.jupyter-ingress.generated_id
output "backend_service_id" {
description = "Backend Service"
value = data.google_compute_backend_service.jupyter-ingress.generated_id == null ? "no-id-yet" : data.google_compute_backend_service.jupyter-ingress.generated_id
}
6 changes: 6 additions & 0 deletions jupyter-on-gke/iap_module/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ variable "project_id" {
default = "<your project>"
}

variable "service_name" {
type = string
description = "Name of the backend service"
default = "default"
}

variable "location" {
type = string
description = "GCP project location"
Expand Down
10 changes: 9 additions & 1 deletion jupyter-on-gke/jupyterhub.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ provider "helm" {
}
}

provider "google-beta" {
project = var.project_id
region = var.location
}

resource "kubernetes_namespace" "namespace" {
count = var.create_namespace ? 1 : 0
metadata {
Expand All @@ -46,6 +51,9 @@ module "iap_auth" {
namespace = var.namespace
client_id = var.client_id
client_secret = var.client_secret
service_name = "k8s-be-32570--b8029e393bc6d64a"

depends_on = [ kubernetes_namespace.namespace ]
}

resource "helm_release" "jupyterhub" {
Expand All @@ -57,7 +65,7 @@ resource "helm_release" "jupyterhub" {

values = [
templatefile("${path.module}/jupyter_config/config-selfauth.yaml", {
service_id = "${module.iap_auth.service_id}"
service_id = "${module.iap_auth[0].backend_service_id}"
project_number = "${var.project_number}"
})
]
Expand Down
Loading

0 comments on commit c4d6c33

Please sign in to comment.