voila on jupyterhub (or on a public cloud instance)

Created on 13 Apr 2019  Â·  45Comments  Â·  Source: voila-dashboards/voila

It would be great if we could document a way to do something like the following:

  • A deployer sets up a jupyterhub in the cloud, installs voila as a server extension
  • A user of that jupyterhub creates a notebook meant for voilafication
  • The user shares a link with another user of the jupyterhub

    • Q: would this be an nbgitpuller link? or a voila-specific link?

  • Clicking the link authenticates, then automatically redirects to the voila URL where it starts the voila process

    • Q: The notebook could either be taken from the original user's .ipynb file, or it could be pulled using nbgitpuller first if the file was on github

Another usecase:

  • A deployers sets up a cloud instance that's publicly available
  • They start a Jupyter server and expose it on a public port in some controlled fashion
  • They share links to their public instance so that others can quickly interact with their self-published dashboard

It feels like these use-cases are already quite close to being possible, maybe without any new tech dev. It would be great to document these patterns to provide some guidance for folks

Most helpful comment

@choldgraf

It feels like these use-cases are already quite close to being possible, maybe without any new tech dev.

Yes the current state is already quite good. For example we have been using the following setup for the past months (for internal use):

jupyterhub-voila

  • JupyterHub is deployed on Kubernetes using the Helm chart
  • Voila is deployed as a separate single instance notebook server with its own custom Helm chart. It has the voila server extension enabled and the correct CSP headers to allow IFrames from other tools.
  • Both Voila and the single user JHub pods share the same Docker image (automatically deployed to keep them in sync)
  • In terms of workflow, the dashboard authors can work on their app and save the .ipynb file. It will automatically be picked up by the Voila instance since they share the same storage for the notebooks. For public facing deployments a more sophisticated git workflow might be more suitable.
* The user shares a link with another user of the jupyterhub

  * Q: would this be an nbgitpuller link? or a voila-specific link?

This could also be a shared drive, for example using NFS, EFS or even a S3 bucket.

Overall it works quite well for a small number of concurrent users.
It becomes a bit more tricky when more users access the voila endpoints at the same time. Depending on what the dashboards are doing, it can lead to high memory consumption and OOMs. It might be worth looking into the Entreprise Gateway for remote kernel execution and how this could be used in Voila.

All 45 comments

Also stuff like this where you embed https://github.com/timkpaine/dashboard

@choldgraf

It feels like these use-cases are already quite close to being possible, maybe without any new tech dev.

Yes the current state is already quite good. For example we have been using the following setup for the past months (for internal use):

jupyterhub-voila

  • JupyterHub is deployed on Kubernetes using the Helm chart
  • Voila is deployed as a separate single instance notebook server with its own custom Helm chart. It has the voila server extension enabled and the correct CSP headers to allow IFrames from other tools.
  • Both Voila and the single user JHub pods share the same Docker image (automatically deployed to keep them in sync)
  • In terms of workflow, the dashboard authors can work on their app and save the .ipynb file. It will automatically be picked up by the Voila instance since they share the same storage for the notebooks. For public facing deployments a more sophisticated git workflow might be more suitable.
* The user shares a link with another user of the jupyterhub

  * Q: would this be an nbgitpuller link? or a voila-specific link?

This could also be a shared drive, for example using NFS, EFS or even a S3 bucket.

Overall it works quite well for a small number of concurrent users.
It becomes a bit more tricky when more users access the voila endpoints at the same time. Depending on what the dashboards are doing, it can lead to high memory consumption and OOMs. It might be worth looking into the Entreprise Gateway for remote kernel execution and how this could be used in Voila.

whoah, that is awesome! you should write it up in a blog post or something!

whoah, that is awesome! you should write it up in a blog post or something!

At some point yes. Along with a sample Helm chart for the deployment would also be useful :)

Hi, this would also be very useful for my teams here! We were using the "appmode" extension and someone mentioned voila, and I have been trying to get that running. The issue being we are using JupyterHub on Kubernetes and have been having troubles with the routing through the proxy. I like the simplicity of your solution of running Voila as a single-instance server, though agree it could run into issues with memory.

In addition, we love the user management that JupyterHub provides, and would like to keep that with Voila as well, which we can't really do as a single instance.

Any suggestions? Again, documentation would be great!! We want to use your product but there is very little info on how to use/configure it, especially for JupyterHub.

Do you have a public-facing (DockerHub) image of the single-instance server you are using?

In addition, we love the user management that JupyterHub provides, and would like to keep that with Voila as well, which we can't really do as a single instance.

Right. The reason for having a single instance running voila only is to allow non JupyterHub users to have access to the dashboards. So without going through the Hub auth flow.

What are the requirements in your case? Voila can be installed in the single user Docker image, so that each user can also use it on their own server (as a server extension for example).

Do you have a public-facing (DockerHub) image of the single-instance server you are using?

Not yet. Although it wouldn't be too difficult to create one by adding voila to an environment.yml file, and start it with CMD ["voila"] in the right folder.

This is related to the initial question of the issue: I was trying to install voila for the users of our JupyterHub. As we don't want the voila-rendered notebooks to be available for users other than the currently logged in hub-user, I went ahead and added it to the singleuser Docker image we are running, alas I'm running into an issue when accessing the voila endpoints, e.g. when going to https://notebooks.openhumans.org/user/gedankenstuecke/voila I get an error 403 Forbidden, with the log files saying:

[W 2019-05-14 22:19:28.116 SingleUserNotebookApp handlers:435] Blocking request with non-local 'Host' notebooks.openhumans.org (notebooks.openhumans.org). If the server should be accessible at that name, set ServerApp.allow_remote_access to disable the check.

We're deploying to Google Cloud with Kubernetes, so that might be related to the issue @afeiszli mentions too.

Maybe someone has an idea for a work around for this? 😄

Maybe someone has an idea for a work around for this? smile

This is interesting. I am not sure we would want the same running instance of the jupyter server to be used for

  • the notebook, which should only be accessible to the current user
  • voila, which is meant to be accesible to any jupyterhub user.

Maybe @yuvipanda or @minrk has some idea of how we could share voila dashboards amongs jupyterhub users.

In our case we actually want a given voila visualization be limited to a given user, as a user's private notebook server is getting access to private data that is being used in a notebook. E.g. user A is logging in and starts their notebook server and then wants to see a dashboard based on a notebook from their server. This dashboard should then be limited to user A.

I thought the easiest way to achieve this would be to run voila as a server extension on each user pod.

@gedankenstuecke @SylvainCorlay The approach I used was a custom server extension for JupyterHub which will route calls to :8866/voila, and which bypasses regular authentication, essentially making it "public". This allows users to have self-managed voila. But it is not password protected. This could be modified slightly to allow for authentication: https://github.com/afeiszli/jupyter-voila-extension

@afeiszli thanks so much for sharing this, that looks super useful for my use case. I'm looping in @betatim, as he helped us in getting our setup off the ground, maybe he has some additional advice for how to best allowing the authentication to also cover the voila setup.

I think what we should do is have a 'voila-singleuser' similar to https://github.com/jupyterhub/jupyterhub/blob/master/jupyterhub/singleuser.py. This will allow running voila instances securely under JupyterHub without allowing arbitrary code execution. This enables a few use cases:

  1. Public facing multi user voila dashboards! You can participate in all the JupyterHub fun - spawners, auth, etc
  2. You can mix notebook server and voila in the same JupyterHub, with nmaed servers.

I'm sure there is more. What do y'all think?

Would love to hear more ideas for setups at the workshop

For running it on a JupyterHub setup I'd use jupyter-server-proxy and start voila in stand-alone mode. Simple solutions for simple minds :)

@betatim voila prevents arbitrary code execution I believe - only allows execution of code already present in the notebook. With jupyter-server-proxy you still have all the notebook endpoints which can be used for arbitrary code execution

Should have referenced the question I was replying to: https://github.com/QuantStack/voila/issues/112#issuecomment-492437979 there users already have a notebook server (or at least are allowed to have one). So I think it isn't a problem that they can switch interface to then also execute code.

Right, what @betatim was saying. In our case people already get their own notebook server to play around with the code. For us the issue was more that we want to allow less-technical users to also see the output of a given notebook that remains dynamic capabilities but without having them run cells individually and see/edit the code.

@betatim's idea of using jupyter-server-proxy did do the trick for us.

voila prevents arbitrary code execution I believe - only allows execution of code already present in the notebook.

yes, the right way to think about it (I think) is that voila runs/keeps the notebook server side, while notebook/lab has it client side (and thus requires 'execute' requests from the front end).

I just received a request for a feature along these lines for my institution's Jupyterhub installation, and am hoping for some guidance or documentation on this. We have Jupyterhub running on a large compute node attached to an HPC cluster (no k8s or anything, just a single systemd service on a single host).

Ideally we would like a setup along the lines of what @gedankenstuecke described above, where a user logs in to Jupyterhub and can start a voila instance which runs as their user account on the cluster (with access to their private data), but uses the Jupyterhub proxy for auth/auth so that the voila instance is "private". Any guidance here would be very welcome!

@technivore I solved it by using @betatim's suggestion of using jupyter-server-proxy and starting voila in stand-alone mode in the user image. It's here in our image between L29 & L36. I ran into minor issues when the original voila extensions where still loaded, that's why those are being turned off after the installation.

@gedankenstuecke it'd be awesome if you made a little "how to" post in the community forum :-)

@choldgraf sure, do you have a link to where you'd like me to put it? :)

Not sure which category but https://discourse.jupyter.org is the forum :)

Thanks for the clarification! We are using the LocalProcessSpawner and it's not clear to me how we would start the voila process as well. Perhaps edit c.Spawner.cmd to run a shell script which starts both jupyterhub-singleuser and voila?

--
Matthew Rich
[email protected]

On Wed, Jul 17, 2019, at 11:54 AM, Bastian Greshake Tzovaras wrote:

@technivore https://github.com/technivore I solved it by using @betatim https://github.com/betatim's suggestion of using jupyter-server-proxy and starting voila in stand-alone mode in the user image. It's here in our image https://github.com/wildtreetech/ohjh/blob/master/images/user/Dockerfile#L29 between L29 & L36. I ran into minor issues when the original voila extensions where still loaded, that's why those are being turned off after the installation.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/QuantStack/voila/issues/112?email_source=notifications&email_token=AACQZVNJTV3U5PKLWXLHUJDP75FCLA5CNFSM4HFY2QN2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2FBAVI#issuecomment-512364629, or mute the thread https://github.com/notifications/unsubscribe-auth/AACQZVPSASNRUIZUCZ5WN4DP75FCLANCNFSM4HFY2QNQ.

@technivore I assume you'll also need the jupyter-server-proxy to properly route the standalone voila server forward. I'm not sure how the LocalProcessSpawner influences that though.

@betatim Thanks!

Could someone share how to make the default voila page look nicer? Is there a jinja template somewhere? I keep prod notebooks in one directory and then startup voila in that directory. Then the users just hit http://myserver:8866 and pick what dashboards they want to run. I'd like that picker screen to be a nice experience.

@marketneutral probably something that can be discussed over at https://github.com/QuantStack/voila-vuetify/issues/14?

This is a great thread. The security around jupyterhub, notebook servers, and voila is important to think through. It could go as complicated as forcing users to log into Jupyterhub and providing access to the ipynb files based on file access, and managing whether they are voila only or full notebook, or self managed (as mentioned above) or anonymous access. There are tons of use cases here.

In my testing, I tried to just append /voila to the jupyterhub url (instead of host:8000/user/myuser/lab? I did host:8000/user/myuser/voila and that worked, I got a tree view (unsorted) and was able to find a notebook in the context of my user and run it. That was pretty cool. Now, since I was authed to JupyterHub, it wasn't anonymous access, but things seemed to be happy/healthy.

Unfortunately, I had some cells that took to long to execute, so I got a timeout and I don't know how to manage that timeout when going through JupyterHub in my DockerSpawned notebook server.

So I tried to run Voila standalone, and now I am finding that it's not running my notebook startup folder like it does for notebook server. I think Voila could be a huge win in organizations where you have folks creating visual notebooks, and others who would "use" them but not create them. This is where some TLC here could go a long way.

Now, since I was authed to JupyterHub, it wasn't anonymous access, but things seemed to be happy/healthy.

@JohnOmernik could you please share your solution configs or algos or as blog post? I'm also interested in the use case when JupyterLab user can create voila dashboards and share it inside JupyterLab secure zone.

Posting here for visibility.

@danlester has been working on a nice project recently, which lets run arbitrary web apps with JupyterHub, but without actually running a notebook server next to it:

https://github.com/ideonate/jhsingle-native-proxy

There is even a Binder example for Voila!

https://github.com/danlester/binderhub-voila-native

It was originally shared on the Jupyter discourse: https://discourse.jupyter.org/t/new-package-to-run-arbitrary-web-service-in-jupyterhub-jhsingle-native-proxy/3493

Thanks @jtpio for mentioning jhsingle-native-proxy here.

There is even a Binder example for Voila!

https://github.com/danlester/binderhub-voila-native

As I discuss here there isn't actually any need to use jhsingle-native-proxy in a Binder context since the auth isn't implemented, so you could just run the service e.g. voila directly instead.

At the moment, jhsingle-native-proxy only really makes sense in an authenticated JupyterHub.

You still need something that takes care of notifying a BinderHub's JupyterHub about user activity so that your session doesn't get shut down.

Is the dependency on notebook a big deal for voila? I was expecting it would depend on that any way?

Yes, Voila depends on notebook via ipywidgets.

My binderhub-voila-direct instance on mybinder.org still seems to be going strong without interacting with it manually! Voila must be keeping it alive; in other examples (e.g. streamlit) it's possible jhsingle-native-proxy would be required after all to keep it open in binder, but not sure.

Dear @danlester ,
this looks exciting, and I hope your work can continue.

I'd have a very basic question, it would be great if you could point me to the right documentation to understand how it works.

Does it substitute jupyterhub and its configuration entirely or does it substitute the jupyter-single-user command?

Is there a possibility to have a "complete" example with authentication for dummies?

Many thanks

@sntgluca Great to hear you find my work exciting!

To answer your question directly, I guess jhsingle-native-proxy replaces the jupyter-singleuser command. jhsingle-native-proxy acts as the server (just like the notebook server would usually) listening for web traffic. It either takes care of the authentication part itself (using OAuth provided by the JupyterHub that launched our server in the first place) OR once authenticated correctly it just proxies the web traffic to whatever underlying server (e.g. Voila) you are really wanting to run.

A full Docker example is here:

https://github.com/ideonate/jhsingle-native-proxy/tree/master/docker-examples/jupyterhub-singleuser-voila-native

Even if you're not really using Docker, hopefully the CMD line of the Dockerfile can be instructive to you. It's basically running this command:

jhsingle-native-proxy --destport 8505 voila /home/jovyan/Presentation.ipynb {--}port={port} {--}no-browser {--}Voila.base_url={base_url}/ {--}Voila.server_url=/

Ignoring some of the funny syntax, you can see that the bit from 'voila' onwards is really just the command line to run Voila itself. So that's what we're telling jhsingle-native-proxy to run as the 'real' server once it's happy with the authentication part.

(The base_url and server_url stuff is just because JupyterHub runs its servers under subfolders e.g. /users/dan)

All of this depends on what you're trying to achieve and how your JupyterHub is configured. I would be really pleased to talk/chat through your situation directly (it will help inform my own work too, I'm sure) so do get in touch: [email protected].

Thanks,

Dan

Dear @danlester ,

thanks for your explanation, I followed through the example in there, and I wish to share how I was able to use it _within JupyterHub_.
I hope this makes sense, and if correct, may help others.
The snippet below isn't a complete jupyterhub configuration, but I think it contains enough to follow through.

from jupyterhub.spawner import SimpleLocalProcessSpawner

c.JupyterHub.spawner_class = SimpleLocalProcessSpawner
c.JupyterHub.authenticator_class = 'jupyterhub.auth.PAMAuthenticator'

c.Spawner.cmd = 'jhsingle-native-proxy'
c.Spawner.args = '--destport 0 --debug --authtype none -- voila {--}port={port} {--}no-browser {--}Voila.server_url=/ {--}Voila.base_url={base_url}/ {--}debug'.split()

Is this a valid, intended usage?

That's really interesting. I have always been using jhsingle-native-proxy within Docker, but there is no reason something like this can't work.

I haven't tried it yet, but:

If you are using --authtype none then really there is no point using jhsingle-native-proxy as far as I can see - you might as well just have c.Spawner.cmd = 'voila' I think! (Unless there is something else blocking direct Voila usage like that?) So in my mind it only makes sense with the default authtype of 'oauth'.

I'm not sure about --destport 0, certainly if every spawner is using that same number. It means every server will be running Voila on that same port, so I presume that the second one that starts will find the port is not available. So I think we need some way of giving each spawner a different 'destport' number (unless I'm missing something). In Docker, it's fine if every container uses the same port number internally (i.e. destport) since they are all isolated containers so don't conflict. JupyterHub itself will give each Docker container an external port number to avoid conflicts.

If you get a chance to try further, please let us know. I'll see if I can get this running too.

Hi,
a couple of comments about my use case might help understand my choices. Sorry for the long thread.

I wish to serve __one or more dashboards to multiple users__, each of which should be _authenticated_.

Until voila supports authentication directly, the compromise I could find is to authenticate via JupyterHub and _run a voila server per authenticated user_.

Here is what I need (AFAIU):

  • authentication via PAM or LDAP, I don't have other options
  • voila starting independent dashboards for each authenticated user

And here is what I found:

  • c.Spawner.cmd = 'voila' _never worked for me_. This is the reason I came across this thread in the first place. jhsingle-native-proxy bridges JupyterHub and voila together.
  • --destport 0 actually starts voila on a random free port, allowing more than one voila server to communicate with JupyterHub. If you have another suggestion, happy to follow up.
  • --authtype none is there since authentication, at this point, has been given by JupyterHub. Also, I wasn't able to configure oauth :(.
  • I would like to avoid containers, at this stage, for a number of reasons.

Finally, I am not able to replicate this setup with jhsingle-native-proxy alone. Mainly, I don't know how to setup PAM authentication. I was successful in running without authentication, but this is not what I need.

If you have further hints on what I should learn I would gladly do my homework. But I don't know where to look.

Some more thoughts from my experiments:

I am able to get a basic Voila server running directly using this config:

c.Spawner.cmd = 'voila'
c.Spawner.args = '--no-browser --Voila.base_url=/user/dan/ --debug --Voila.ip=0.0.0.0 /Users/dan/Dev/jhlocalprocess'.split()

(Of course, ideally you'd find a way to get the user's server URL from env vars or similar - I've just hardcoded it as /user/dan here. Not sure if LocalProcessSpawner allows variable substitution, so might need changing, or write a script to wrap voila.)

However, this does not take care of authentication. And as you say, neither does your jhsingle-native-proxy solution so far.

But I also see some errors when trying authtype oauth, so that needs some investigation.

Sorry, I was being a bit slow before about --destport=0 which is really a feature of jhsingle-native-proxy... yes, it picks a random port on which to start voila, which it passes in place of the {port} placeholder in c.Spawner.args.

Note that JupyterHub itself also injects an unseen (in the config) --port argument which is picked up by jhsingle-native-proxy as the port on which it exposes itself. destport is the port to which it proxies (and on which Voila is running).

So the question boils down to fixing the oauth in jhsingle-native-proxy if this is possible in the local process case...

There was one error in the auth code that caused problems in this case. This has now been fixed in jhsingle-native-proxy 0.1.1 released just now.

In any case, you also need to add this to the jupyterhub_config.py:

c.Spawner.environment = {'JUPYTERHUB_ANYONE': '1'}

Actually, adding that should mean you avoid the previous error being thrown, but it would be best to upgrade to 0.1.1 anyway.

This means any authenticated JupyterHub user can view the Voila app. Please let me know if this helps!

Hi @danlester,
I am curious to try over the weekend. Thanks for all the hints!

Good day @danlester ,
I was able to replicate your setup and it indeed works, although I still have to solve the problem you mentioned, to pass the right user urls from JupyterHub to the voila server.

I would like to ask you an off-topic question though.
In my case, I would be happy to have a separate voila server, and have JupyterHub _directly connect_ to it, without starting a new one.
I don't know the work behind it, but I think you do. Is it easy to setup something like that? Do you have any advice?

@sntgluca Great to hear that you have made progress! I would be very happy to find out more and help further, but I think we're going off topic for this thread... please could you email [email protected] and we can set up a chat/call or resolve over email!

To summarise for others, my ContainDS Dashboards project solves many of the use cases discussed here.

A JupyterHub user can create their Jupyter notebooks as normal, and then when ready to publish as a Voila dashboard, they can just 'add new dashboard' from the Dashboards menu and specify the relevant file or folder. A new Voila-only server starts up, which by default is accessible to any authenticated JupyterHub user. (Other users can see a list of published dashboards in their own Dashboards menu.)

Here are the full instructions. Please get in touch directly if you need any help or have any questions!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

choldgraf picture choldgraf  Â·  5Comments

tomnewg picture tomnewg  Â·  7Comments

pingme998 picture pingme998  Â·  5Comments

jeiche picture jeiche  Â·  8Comments

choldgraf picture choldgraf  Â·  8Comments