Package: jupyterhub-systemdspawner
Features|Requirements|Installation|Configuration|Getting help|LicensesystemdspawnerThesystemdspawnerenables JupyterHub to spawn single-user
notebook servers usingsystemd.FeaturesIf you want to use Linux Containers (Docker, rkt, etc) for isolation and
security benefits, but don't want the headache and complexity of
container image management, then you should use the SystemdSpawner.With thesystemdspawner, you get to use the familiar, traditional system
administration tools, whether you love or meh them, without having to learn an
extra layer of container related tooling.The following features are currently available:Limit maximum memory permitted to each user.If they request more memory than this, it will not be granted (mallocwill fail, which will manifest in different ways depending on the
programming language you are using).Limit maximum CPU available to each user.Provide fair scheduling to users independent of the number of processes they
are running.For example, if User A is running 100 CPU hogging processes, it will usually
mean User B's 2 CPU hogging processes will never get enough CPU time as scheduling
is traditionally per-process. With Systemd Spawner, both these users' processes
will as a whole get the same amount of CPU time, regardless of number of processes
being run. Good news if you are User B.Accurate accounting of memory and CPU usage (via cgroups, which systemd uses internally).You can check this out withsystemd-cgtop./tmpisolation.Each user gets their own/tmp, to prevent accidental information
leakage.Spawn notebook servers as specific local users on the system.This can replace the need for using SudoSpawner.Restrict users from being able to sudo to root (or as other users) from within the
notebook.This is an additional security measure to make sure that a compromise of
a jupyterhub notebook instance doesn't allow root access.Restrict what paths users can write to.This allows making/read only and only granting write privileges to
specific paths, for additional security.Automatically collect logs from each individual user notebook intojournald, which also handles log rotation.Dynamically allocate users with Systemd'sdynamic usersfacility. Very useful in conjunction withtmpauthenticator.RequirementsSystemd and Linux distributionsSystemdSpawner 1 is recommended to be used with systemd version 245 or higher,
butmaywork with systemd version 243-244 as well. Below are examples of Linux
distributions that use systemd and has a recommended version.Ubuntu 20.04+Debian 11+Rocky 9+ / CentOS 9+The commandsystemctl --versioncan be used to verify that systemd is used,
and what version is used.Kernel ConfigurationCertain kernel options need to be enabled for the CPU / Memory limiting features
to work. If these are not enabled, CPU / Memory limiting will just fail
silently. You can check if your kernel supports these features by running
thecheck-kernel.bashscript.Root accessCurrently, JupyterHub must be run as root to use Systemd Spawner.systemd-runneeds to be run as root to be able to set memory & cpu limits. Simple sudo rules
do not help, since unrestricted access tosystemd-runis equivalent to root. We
will explore hardening approaches soon.Local UsersIf running withc.SystemdSpawner.dynamic_users = False(the default), each user's
server is spawned to run as a local unix user account. Hence this spawner
requires that all users who authenticate have a local account already present on the
machine.If running withc.SystemdSpawner.dynamic_users = True, no local user accounts
are required. Systemd will automatically create dynamic users as required.
Seethis blog postfor
details.InstallationYou can install it from PyPI with:pipinstalljupyterhub-systemdspawnerYou can enable it for your jupyterhub with the following lines in yourjupyterhub_config.pyfilec.JupyterHub.spawner_class="systemd"Note that to confirm systemdspawner has been installed in the correct jupyterhub
environment, a newly generated config file should listsystemdspawneras one of the
available spawner classes in the comments above the configuration line.ConfigurationLots of configuration options for you to choose! You should put all of these
in yourjupyterhub_config.pyfile:mem_limitcpu_limituser_workingdirusername_templatedefault_shellextra_pathsunit_name_templateunit_extra_propertiesisolate_tmpisolate_devicesdisable_user_sudoreadonly_pathsreadwrite_pathsdynamic_usersmem_limitSpecifies the maximum memory that can be used by each individual user. It can be
specified as an absolute byte value. You can use the suffixesK,M,GorTto
mean Kilobyte, Megabyte, Gigabyte or Terabyte respectively. Setting it toNonedisables
memory limits.Even if you want individual users to use as much memory as possible, it is still good
practice to set a memory limit of 80-90% of total physical memory. This prevents one
user from being able to single handedly take down the machine accidentally by OOMing it.c.SystemdSpawner.mem_limit='4G'Defaults toNone, which provides no memory limits.This info is exposed to the single-user server as the environment variableMEM_LIMITas integer bytes.cpu_limitA float representing the total CPU-cores each user can use.1represents one
full CPU,4represents 4 full CPUs,0.5represents half of one CPU, etc.
This value is ultimately converted to a percentage and rounded down to the
nearest integer percentage, i.e.1.5is converted to 150%,0.125is
converted to 12%, etc.c.SystemdSpawner.cpu_limit=4.0Defaults toNone, which provides no CPU limits.This info is exposed to the single-user server as the environment variableCPU_LIMITas a float.Note: there isa bugin
systemd v231 which prevents the CPU limit from being set to a value greater
than 100%.CPU fairnessCompletely unrelated tocpu_limitis the concept of CPU fairness - that each
user should have equal access to all the CPUs in the absense of limits. This
does not entirely work in the normal case for Jupyter Notebooks, since CPU
scheduling happens on a per-process level, rather than per-user. This means
a user running 100 processes has 100x more access to the CPU than a user running
one. This is far from an ideal situation.Since each user's notebook server runs in its own Systemd Service, this problem
is mitigated - all the processes spawned from a user's notebook server are run
in one cgroup, and cgroups are treated equally for CPU scheduling. So independent
of how many processes each user is running, they all get equal access to the CPU.
This works out perfect for most cases, since this allows users to burst up and
use all CPU when nobody else is using CPU & forces them to automatically yield
when other users want to use the CPU.user_workingdirThe directory to spawn each user's notebook server in. This directory is what users
see when they open their notebooks servers. Usually this is the user's home directory.{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.c.SystemdSpawner.user_workingdir='/home/{USERNAME}'Defaults to the home directory of the user. Not respected ifdynamic_usersis true.username_templateTemplate for unix username each user should be spawned as.{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.This user should already exist in the system.c.SystemdSpawner.username_template='jupyter-{USERNAME}'Not respected ifdynamic_usersis set to Truedefault_shellThe default shell to use for the terminal in the notebook. Sets theSHELLenvironment
variable to this.c.SystemdSpawner.default_shell='/bin/bash'Defaults to whatever the value of theSHELLenvironment variable is in the JupyterHub
process, or/bin/bashifSHELLisn't set.extra_pathsList of paths that should be prepended to thePATHenvironment variable for the spawned
notebook server. This is easier than setting theenvproperty, since you want to
add to PATH, not completely replace it. Very useful when you want to add a virtualenv
or conda install onto the user'sPATHby default.c.SystemdSpawner.extra_paths=['/home/{USERNAME}/conda/bin']{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.Defaults to[]which doesn't add any extra paths toPATHunit_name_templateTemplate to form the Systemd Service unit name for each user notebook server. This
allows differentiating between multiple jupyterhubs with Systemd Spawner on the same
machine. Should contain only [a-zA-Z0-9_-].c.SystemdSpawner.unit_name_template='jupyter-{USERNAME}-singleuser'{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.Defaults tojupyter-{USERNAME}-singleuserunit_extra_propertiesDict of key-value pairs used to add arbitrary properties to the spawned Jupyerhub units.c.SystemdSpawner.unit_extra_properties={'LimitNOFILE':'16384'}Readman systemd-runfor details on per-unit properties available in transient units.{USERNAME}and{USERID}in each parameter value will be expanded to the
appropriate values for the user being spawned.Defaults to{}which doesn't add any extra properties to the transient scope.isolate_tmpSetting this to true provides a separate, private/tmpfor each user. This is very
useful to protect against accidental leakage of otherwise private information - it is
possible that libraries / tools you are using create /tmp files without you knowing and
this is leaking info.c.SystemdSpawner.isolate_tmp=TrueDefaults to false.isolate_devicesSetting this to true provides a separate, private/devfor each user. This prevents the
user from directly accessing hardware devices, which could be a potential source of
security issues./dev/null,/dev/zero,/dev/randomand the ttyp pseudo-devices will
be mounted already, so most users should see no change when this is enabled.c.SystemdSpawner.isolate_devices=TrueDefaults to false.disable_user_sudoSet to true, this prevents users from being able to usesudo(or any other means) to
become other users (including root). This helps contain damage from a compromise of a user's
credentials if they also have sudo rights on the machine - a web based exploit will now only
be able to damage the user's own stuff, rather than have complete root access.c.SystemdSpawner.disable_user_sudo=TrueDefaults to True.readonly_pathsList of filesystem paths that should be mounted readonly for the users' notebook server. This
will override any filesystem permissions that might exist. Subpaths of paths that are mounted
readonly can be marked readwrite withreadwrite_paths. This is useful for marking/as
readonly & only whitelisting the paths where notebook users can write. If paths listed here
do not exist, you will get an error.c.SystemdSpawner.readonly_paths=['/']{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.Defaults toNonewhich disables this feature.readwrite_pathsList of filesystem paths that should be mounted readwrite for the users' notebook server. This
only makes sense ifreadonly_pathsis used to make some paths readonly - this can then be
used to make specific paths readwrite. This doesnotoverride filesystem permissions - the
user needs to have appropriate rights to write to these paths.c.SystemdSpawner.readwrite_paths=['/home/{USERNAME}']{USERNAME}and{USERID}in this configuration value will be expanded to the
appropriate values for the user being spawned.Defaults toNonewhich disables this feature.dynamic_usersAllocate system users dynamically for each user.Uses the DynamicUser= feature of Systemd to make a new system user
for each hub user dynamically. Their home directories are set up
under /var/lib/{USERNAME}, and persist over time. The system user
is deallocated whenever the user's server is not running.Seehttp://0pointer.net/blog/dynamic-users-with-systemd.htmlfor more
information.sliceRun the spawned notebook in a given systemd slice. This allows aggregate configuration that
will apply to all the units that are launched. This can be used (for example) to control
the total amount of memory that all of the notebook users can use.Seehttps://samthursfield.wordpress.com/2015/05/07/running-firefox-in-a-cgroup-using-systemd/for
an example of how this could look.For detailed configuration see themanpageGetting helpWe encourage you to ask questions in theJupyter Discourse forum.LicenseWe use a shared copyright model that enables all contributors to maintain the
copyright on their contributions.All code is licensed under the terms of the revised BSD license.