Overview

Custom Deployments are custom-defined applications that run inside your Saturn installation's Kubernetes cluster. These can be a model, a dashboard, or another application. Custom Deployments use the same libraries, conda environment, and image that your Jupyter instances use, meaning that they can be developed inside of Jupyter Lab.

Creating and Deploying

Custom Deployments are tied to a Project. When a Jupyter instance is created, a Project is created for it. Files within the project directory (/home/jovyan/project) are included in the same place in a Custom Deployment's container, and that directory is set as the starting directory.

Custom Deployments are assiged a public-facing hostname once created. Port 8000 will forwarded from the load balancer to the deployments' container(s). The server or process should also be bound to 0.0.0.0 (the below example does not require this - some frameworks, such as Flask, will bind to 127.0.0.1  by default).

For example, using the following script at project/hello.py:

import http.server
import socketserver
from http import HTTPStatus

class Handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(HTTPStatus.OK)
        self.end_headers()
        self.wfile.write(b'Hello Saturn!')

httpd = socketserver.TCPServer(('', 8000), Handler)
httpd.serve_forever()

As seen in Jupyter Lab:

A Custom Deployment can be created from the Custom Deployments page in your Saturn dashboard.

To run the example hello.py above, the command is simply python hello.py - the container starts in the project (/home/jovyan/project) directory. Once the settings have been set as desired, click Create. The new Custom Deployment will show on the top of the page:

Initially, the status will be "Pending", with zero instances running. This should progress to "Ready" shortly (the first deploy may take several minutes - subsequent deployments should be faster).

Accessing a Custom Deployment

In the deployment's details, a URL is shown. This is the URL for the deployment - clicking it will open the deployment.

Custom Deployments are protected by an internal authentication layer. When accessed, the user's Saturn session is verified. If the user is not signed in, they will be prompted to sign in. Currently, any signed-in Saturn user can access the deployment - finer-grained access controls are coming in a future release.

Token-based authentication is not yet available for custom deployments, but is coming in a future release. If automated (non-browser) access is needed for a custom deployment before token-based authentication is available, the following helper function can be used alongside a requests session:

import requests
from urllib.parse import urlparse, urlunparse

def do_saturn_login(session, deployment_url, username, password):
    session.cookies.clear_session_cookies()
    # step 1 - get the auth URL
    resp = session.get(deployment_url, allow_redirects=False)
    if resp.status_code != 302:
        raise Exception(f"Unexpected response from Saturn step 1: {resp.status_code}")
    location = resp.headers.get("Location")

    # step 2 - get the _xsrf value
    # need to strip the query string off of the auth URL so the token doesn't get used up
    parsed = urlparse(location)
    queryless_location = urlunparse(
        [parsed.scheme, parsed.netloc, parsed.path, parsed.params, "", ""]
    )
    resp = session.get(queryless_location, allow_redirects=False)
    if resp.status_code != 200:
        raise Exception(f"Unexpected response from Saturn step 2: {resp.status_code}")
    # _xsrf is set as a cookie
    _xsrf = resp.cookies.get("_xsrf")

    # step 3 - log in
    resp = session.post(
        location,
        headers={"X-XSRFToken": _xsrf},
        data={"username": username, "password": password},
        allow_redirects=False,
    )
    if resp.status_code != 302:
        raise Exception(f"Unexpected response from Saturn step 3: {resp.status_code}")
    return_url = resp.headers.get("Location")

    # step 4 - consume response token
    resp = session.get(return_url, allow_redirects=False)
    if resp.status_code != 302:
        raise Exception(f"Unexpected response from Saturn step 4: {resp.status_code}")
    # session is now ready to use!

Example usage for the above hello.py example:

DEPLOYMENT_URL = "http://hello-saturn.deploy.example.com/"

s = requests.Session()
do_saturn_login(s, DEPLOYMENT_URL, "my-username", "my-password")
print(s.get(DEPLOYMENT_URL).text)
# prints "Hello, Saturn!"

Sessions prepared in this way have their tokens expire after 60 minutes - the function will need to be called periodically to ensure that it has a valid token.

Updating and Redeploying a Custom Deployment

When a Custom Deployment is created, the current HEAD commit from its project's git repository is used for all containers, no matter how many there are. This means that the project can be freely modified and updated without a need to worry about breaking deployments, but it also means that deployments do not automatically update. To sync a deployment's containers up to the latest code, click the "Sync" button in the deployment's details.

The deployment's command, instance count, and instance size can be modified by clicking the "Update" button. Doing so will also bring the deployment's containers up to the latest code from the project.

Through either of the above methods, the change will be applied as a rolling update - new containers will be created and the old containers will be shut down.

Cost Management

If a Custom Deployment is temporarily not needed, its instance count can be set to zero in an update. This will cause it to not incur any extra infrastructure costs. It can then be changed to whatever count is desired when it is needed again.

Troubleshooting

For general troubleshooting, the deployment's logs can be viewed by clicking the "Logs" button in the deployment details.

The Deployment never gets to "Ready" status

The most likely cause of this is that deployment's containers are either crashing, or exiting too quickly. Kubernetes expects deployments' containers to be long-running processes - if the deployment's code is a simple short task, such as something that pulls work from a queue, it may need to be changed to a loop.

If the containers are crashing, errors should be shown in the deployment's logs.

The Deployment's status is "Ready", but accessing the resource gives a status 502

The most likely cause of this is that nothing is bound to port 8000 within the deployment's containers. 8000 is the only forwarded HTTP port - applications need to bind to it to be accessible. Another possibility is that the server is bound to 127.0.0.1  and not 0.0.0.0  - it needs to listen on all addresses to be accessible from outside of the container.

Other issues

support@saturncloud.io

Example Models and Dashboards

Coming soon!

Did this answer your question?