JupyterHub Traefik Proxy#

An implementation of the JupyterHub proxy api with traefik : an extremely lightweight, portable reverse proxy implementation, that supports load balancing and can configure itself automatically and dynamically.

Why traefik?#

Currently, the default proxy implementation for JupyterHub is configurable-http-proxy (CHP), which stores the routing table in-memory. This might be the best approach in most of the cases, but because you can only run a single copy of the proxy at a time, it has its limitations when used in dynamic large scale systems.

When using a proxy implementation based on traefik, you can run multiple instances of traefik by using a distributed key-value store like etcd or consul to store the routing table. This makes the proxy highly available and improves the scalability and stability of the system. Moreover it offers HTTPS support through a straight-forward ACME (Let’s Encrypt) configuration.

There are three versions for the proxy, depending on how traefik stores the routes:

  • for smaller, single-node deployments:

    • TraefikFileProviderProxy

  • for distributed setups:

    • TraefikEtcdProxy

    • TraefikConsulProxy

Contents#

Installation Guide#

Installation#

Traefik-proxy installation#
  1. Install JupyterHub:

    $ python3 -m pip install jupyterhub
    
  2. Install jupyterhub-traefik-proxy, which is available now as pre-release:

    python3 -m pip install jupyterhub-traefik-proxy
    
  3. In order to be able to launch JupyterHub with traefik-proxy or run the tests, traefik, must first be installed and added to your PATH.

    There are two ways you can install traefik:

    1. Through traefik-proxy’s install utility.

      $ python3 -m jupyterhub_traefik_proxy.install --output=/usr/local/bin
      

      This will install traefik.

      This will install the default versions of traefik, to to /usr/local/bin specified through the --output option.

      If no directory is passed to the installer, a dependencies directory will be created in the traefik-proxy directory. In this case, you must add this directory to PATH, e.g.

      $ export PATH=$PATH:{$PWD}/dependencies
      

      If you want to install other versions of traefik in a directory of your choice, just specify it to the installer through the following arguments:

      • --traefik-version

      • --output

      Example:

      $ python3 -m jupyterhub_traefik_proxy.install --output=dep \
               --traefik-version=2.4.8
      

      If the desired install directory doesn’t exist, it will be created by the installer.

      To get a list of all the available options:

      $ python3 -m jupyterhub_traefik_proxy.install --help
      
    2. From traefik release page:

Installing a key-value store#

If you want to use a key-value store to mediate configuration (mainly for use in distributed deployments, such as containers), you can get etcd or consul via their respective release pages:

Or, more likely, select the appropriate container image. You will also need to install a Python client for the Key-Value store of your choice:

  • etcdpy

  • python-consul2

Enabling traefik-proxy in JupyterHub#

TraefikFileProviderProxy, TraefikEtcdProxy and TraefikConsulProxy are custom proxy implementations that subclass Proxy and can register in JupyterHub config using c.JupyterHub.proxy_class entrypoint.

On startup, JupyterHub will look by default for a configuration file, jupyterhub_config.py, in the current working directory. If the configuration file is not in the current working directory, you can load a specific config file and start JupyterHub using:

$ jupyterhub -f /path/to/jupyterhub_config.py

There is an example configuration file here that configures JupyterHub to run with TraefikEtcdProxy as the proxy and uses dummyauthenticator and simplespawner to enable testing without administrative privileges.

In jupyterhub_config.py:

c.JupyterHub.proxy_class = "traefik_file"
# will configure JupyterHub to run with TraefikFileProviderProxy
c.JupyterHub.proxy_class = "traefik_etcd"
# will configure JupyterHub to run with TraefikEtcdProxy
c.JupyterHub.proxy_class = "traefik_consul"
# will configure JupyterHub to run with TraefikConsulProxy

Getting Started#

Enabling HTTPS with TraefikProxy#

When running JupyterHub, you almost always want to use TLS (HTTPS). Traefik has a few ways to do that. The first tricky bit is that traefik separates dynamic configuration from static configuration, and some configuration needs to go in static, while some goes in dynamic.

Static configuration#

If you are using externally-managed traefik (c.TraefikProxy.should_start = False), you must write the static configuration file yourself. The only static configuration required by juptyerhub-traefik-proxy is the creation of the entrypoints for the api and jupyterhub itself:

# static configuration

# enable API
[api]

[entryPoints.auth_api]
address = "localhost:8099" # should match c.TraefikProxy.traefik_api_url

[entrypoints.https]
address = ":443"

[entrypoints.https.http.tls]
options = "default"

jupyterhub-traefik-proxy can take care of the rest because it will apply its dynamic configuration when JupyterHub starts.

Manual SSL#

Configuring SSL with your own certificates works the same with traefik proxy as any other JupyterHub proxy implementation:

c.JupyterHub.ssl_cert = "/path/to/ssl.cert"
c.JupyterHub.ssl_key = "/path/to/ssl.key"

This will set the traefik dynamic configuration:

# dynamic configuration
[tls.stores.default.defaultCertificate]
certFile = "path/to/cert.crt"
keyFile  = "path/to/cert.key"

If you don’t tell jupyterhub about these files, you will need to set this configuration yourself in dynamic configuration (Traefik ignores TLS configuration in the “static” configuration file). Passing the certificates via JupyterHub configuration assumes the options = "default" static configuration:

# static configuration
[entrypoints.https.http.tls]
options = "default"

If you use your own static and dynamic configuration files, you don’t have to use the ‘default’ TLS options or tell jupyterhub anything about your TLS configuration.

Let’s Encrypt#

Traefik supports using Let’s Encrypt for automatically issuing and renewing certificates. It’s great!

To configure traefik to use let’s encrypt, first we need to register a certificate resolver in static configuration:

# static configuration

# redirect all http requests to https
[entrypoints.httponly]
address = ":80"
[entryPoints.httponly.http.redirections.entryPoint]
to = "https"
scheme = "https"

# configure
[certificatesResolvers.letsencrypt.acme]
email = "you@example.com"
storage = "acme.json" # file where certificates are stored
# use the staging server to test your deployment
# uncomment this when you are ready for production
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"

# tlsChallenge means you don't need an http endpoint
[certificatesResolvers.letsencrypt.acme.tlsChallenge]

And in your extra dynamic configuration, specify the domain(s) you want certificates for:

# dynamic configuration
[tls.stores.default.defaultGeneratedCert]
resolver = "letsencrypt"
[tls.stores.default.defaultGeneratedCert.domain]
main = "hub.example.com"
sans = [
  # if you are serving more than one domain
  "other.service.example.com",
]

If you are using JupyterHub-managed traefik (c.TraefikProxy.should_start = True), you can specify this same configuration via TraefikProxy’s extra_static_config and extra_dynamic_config:

c.TraefikProxy.traefik_entrypoint = "https"
c.TraefikProxy.extra_static_config = {
    "entryPoints": {
        "http": {
            "address": ":80"
        },
        "https": {
            "http": {
                "tls": {
                    "options": "default"
                },
            },
        },
    },
    "certificatesResolvers": {
        "letsencrypt": {
            "acme": {
                "email": "you@example.com",
                "storage": "acme.json",
            },
            "tlsChallenge": {},
        },
    },
}


c.TraefikProxy.extra_dynamic_config = {
    "tls": {
        "stores": {
            "default": {
                "defaultGeneratedCert": {
                    "resolver": "letsencrypt",
                    "domain": {
                        "main": "hub.example.com",
                    },
                },
            },
        },
    },
}

Using TraefikFileProviderProxy#

jupyterhub-traefik-proxy can be used with simple toml or yaml configuration files, for smaller, single-node deployments such as The Littlest JupyterHub.

How-To install TraefikFileProviderProxy#
  1. Install jupyterhub

  2. Install jupyterhub-traefik-proxy

  3. Install traefik

How-To enable TraefikFileProviderProxy#

You can enable JupyterHub to work with TraefikFileProviderProxy in jupyterhub_config.py, using the proxy_class configuration option.

You can choose to:

  • use the traefik_file entrypoint, new in JupyterHub 1.0, e.g.:

    c.JupyterHub.proxy_class = "traefik_file"
    
  • use the TraefikFileProviderProxy object, in which case, you have to import the module, e.g.:

    from jupyterhub_traefik_proxy.fileprovider import TraefikFileProviderProxy
    c.JupyterHub.proxy_class = TraefikFileProviderProxy
    
Traefik configuration#

Traefik’s configuration is divided into two parts:

  • The static configuration (loaded only at the beginning)

  • The dynamic configuration (can be hot-reloaded, without restarting the proxy), where the routing table will be updated continuously.

Traefik allows us to have one file for the static configuration file (traefik.toml or traefik.yaml) and one or several files for the routes, that traefik would watch.

Note

TraefikFileProviderProxy, uses two configuration files: one file for the routes (rules.toml or rules.yaml), and one for the static configuration (traefik.toml or traefik.yaml).

By default, Traefik will search for traefik.toml and rules.toml in the following places:

  • /etc/traefik/

  • $HOME/.traefik/

  • . the working directory

You can override this in TraefikFileProviderProxy, by modifying the static_config_file argument:

c.TraefikFileProviderProxy.static_config_file = "/path/to/static_config_filename.toml"

Similarly, you can override the dynamic configuration file by modifying the dynamic_config_file argument:

c.TraefikFileProviderProxy.dynamic_config_file = "/path/to/dynamic_config_filename.toml"

Note

  • When JupyterHub starts the proxy, it writes the static config once, then only edits the dynamic config file.

  • When JupyterHub does not start the proxy, the user is totally responsible for the static config and JupyterHub is responsible exclusively for the routes.

  • When JupyterHub does not start the proxy, the user should tell traefik to get its dynamic configuration from a directory. Then, one (or more) dynamic configuration file(s) can be managed externally, and dynamic_config_file will be managed by JupyterHub. This allows e.g., the administrator to configure traefik’s API outside of JupyterHub.

Externally managed TraefikFileProviderProxy#

When TraefikFileProviderProxy is externally managed, service managers like systemd or docker will be responsible for starting and stopping the proxy.

If TraefikFileProviderProxy is used as an externally managed service, then make sure you follow the steps enumerated below:

  1. Let JupyterHub know that the proxy being used is TraefikFileProviderProxy, using the proxy_class configuration option:

    c.JupyterHub.proxy_class = "traefik_file"
    
  2. Configure TraefikFileProviderProxy in jupyterhub_config.py

    JupyterHub configuration file, jupyterhub_config.py must specify at least:

    • That the proxy is externally managed

    • The traefik api credentials

    • The dynamic configuration file, if different from rules.toml or if this file is located in a place other than traefik’s default search directories (etc/traefik/, $HOME/.traefik/, the working directory). traefik must also be able to access the dynamic configuration file.

    Example configuration:

    # JupyterHub shouldn't start the proxy, it's already running
    c.TraefikFileProviderProxy.should_start = False
    
    # if not the default:
    c.TraefikFileProviderProxy.dynamic_config_file = "/path/to/somefile.toml"
    
    # traefik api credentials
    c.TraefikFileProviderProxy.traefik_api_username = "abc"
    c.TraefikFileProviderProxy.traefik_api_password = "xxx"
    
    # Validate the certificate on traefik's API? Default = True
    # c.TraefikFileProviderProxy.traefik_api_validate_cert = True
    
    # jupyterhub will configure traefik for itself, using this Host name
    # (and optional path) on the router rule:
    c.JupyterHub.bind_url = 'http://hub.contoso.com'
    
    # jupyterhub will also configure traefik's 'service' url, so this needs
    # to be accessible from traefik. By default, jupyterhub will bind to
    # 'localhost', but this will bind jupyterhub to its hostname
    # (only necessary when traefik is in a different machine or container)
    c.JupyterHub.hub_bind_url = 'http://:8000'
    
  3. Ensure traefik.toml / traefik.yaml

    The static configuration file, traefik.toml (or traefik.yaml) must configure at least:

    • The default entrypoint

    • The api entrypoint (and authenticate it in a user-managed dynamic configuration file)

    • The websockets protocol

    • The dynamic configuration directory to watch (make sure this configuration directory exists, even if empty before the proxy is launched)

    • Check tests/config_files/traefik.toml for an example.

Example setup#

This is an example setup for using JupyterHub and TraefikFileProviderProxy managed by another service than JupyterHub.

  1. Configure the proxy through the JupyterHub configuration file, jupyterhub_config.py, e.g.:

    
    # mark the proxy as externally managed
    c.TraefikFileProviderProxy.should_start = False
    
    # traefik api endpoint login password
    c.TraefikFileProviderProxy.traefik_api_password = "admin"
    
    # traefik api endpoint login username
    c.TraefikFileProviderProxy.traefik_api_username = "api_admin"
    
    # traefik's dynamic configuration file, which will be managed by JupyterHub
    c.TraefikFileProviderProxy.dynamic_config_file = "/var/run/traefik/rules.toml"
    
    # configure JupyterHub to use TraefikFileProviderProxy
    c.JupyterHub.proxy_class = "traefik_file"
    
  2. Create a traefik static configuration file, traefik.toml, e.g.:

    # enable the api
    [api]
    
    # the public port where traefik accepts http requests
    [entryPoints.http]
    address = ":8000"
    
    # the port on localhost where the traefik api should be found
    [entryPoints.auth_api]
    address = "localhost:8099"
    
    # the dynamic configuration directory
    # This must match the directory provided in Step 1. above.
    [providers.file]
    directory = "/var/run/traefik"
    watch = true
    
  3. Start traefik with the configuration specified above, e.g.:

    $ traefik --configfile traefik.toml
    

Using TraefikEtcdProxy#

Etcd is a distributed key-value store. This and TraefikConsulProxy is the choice to use when using jupyterhub-traefik-proxy in a distributed setup, such as a Kubernetes cluster, e.g. with multiple traefik-proxy instances.

How-To install TraefikEtcdProxy#
  1. Install jupyterhub

  2. Install jupyterhub-traefik-proxy

  3. Install traefik

  4. Install etcd

How-To enable TraefikEtcdProxy#

You can enable JupyterHub to work with TraefikEtcdProxy in jupyterhub_config.py, using the proxy_class configuration option.

You can choose to:

  • use the traefik_etcd entrypoint, new in JupyterHub 1.0, e.g.:

    c.JupyterHub.proxy_class = "traefik_etcd"
    
  • use the TraefikEtcdProxy object, in which case, you have to import the module, e.g.:

    from jupyterhub_traefik_proxy import TraefikEtcdProxy
    c.JupyterHub.proxy_class = TraefikEtcdProxy
    
Etcd configuration#
  1. Depending on the value of the should_start proxy flag, you can choose whether or not TraefikEtcdProxy willl be externally managed.

    • When should_start is set to True, TraefikEtcdProxy will auto-generate its static configuration (using the override values or the defaults) and store it in traefik.toml file. The traefik process will then be launched using this file.

    • When should_start is set to False, prior to starting the traefik process, you must create a toml file with the desired traefik static configuration and pass it to traefik. Keep in mind that in order for the routes to be stored in etcd, this toml file must specify etcd as the provider.

  2. TraefikEtcdProxy searches in the etcd key-value store the keys starting with the kv_traefik_prefix prefix in order to build its static configuration.

    Similarly, the dynamic configuration is built by searching the kv_jupyterhub_prefix.

    Note

    If you want to change or add traefik’s static configuration options, you can add them to etcd under this prefix and traefik will pick them up.

    • The default values of this configurations options are:

      kv_traefik_prefix = "/traefik/"
      kv_jupyterhub_prefix = "/jupyterhub/"
      
    • You can override the default values of the prefixes by passing their desired values through jupyterhub_config.py e.g.:

      c.TraefikEtcdProxy.kv_traefik_prefix = "/some_static_config_prefix/"
      c.TraefikEtcdProxy.kv_jupyterhub_prefix = "/some_dynamic_config_prefix/"
      
  3. By default, TraefikEtcdProxy assumes etcd accepts client requests on the official default etcd port 2379 for client requests.

    c.TraefikEtcdProxy.etcd_url = "http://127.0.0.1:2379"
    

    If the etcd cluster is deployed differently than using the etcd defaults, then you must pass the etcd url to the proxy using the etcd_url option in jupyterhub_config.py:

    c.TraefikEtcdProxy.etcd_url = "scheme://hostname:port"
    

Note

  1. TraefikEtcdProxy does not manage the etcd cluster and assumes it is up and running before the proxy itself starts.

    However, based on how etcd is configured and started, TraefikEtcdProxy needs to be told about some etcd configuration details, such as:

    • etcd address where it accepts client requests

      c.TraefikEtcdProxy.etcd_url="scheme://hostname:port"
      
    • etcd credentials (if etcd has authentication enabled)

      c.TraefikEtcdProxy.etcd_username="abc"
      c.TraefikEtcdProxy.etcd_password="123"
      
  2. Etcd has two API versions: the API V3 and the API V2. Traefik suggests using Etcd API V3, because the API V2 won’t be supported in the future.

    Checkout the etcd documentation to find out more about possible etcd configuration options.

Externally managed TraefikEtcdProxy#

If TraefikEtcdProxy is used as an externally managed service, then make sure you follow the steps enumerated below:

  1. Let JupyterHub know that the proxy being used is TraefikEtcdProxy, using the proxy_class configuration option:

    c.JupyterHub.proxy_class = "traefik_etcd"
    
  2. Configure TraefikEtcdProxy in jupyterhub_config.py

    JupyterHub configuration file, jupyterhub_config.py must specify at least:

    • That the proxy is externally managed

    • The traefik api credentials

    • The etcd credentials (if etcd authentication is enabled)

    Example configuration:

    # JupyterHub shouldn't start the proxy, it's already running
    c.TraefikEtcdProxy.should_start = False
    
    # if not the default:
    c.TraefikEtcdProxy.etcd_url = "http://etcd-host:2379"
    
    # traefik api credentials
    c.TraefikEtcdProxy.traefik_api_username = "abc"
    c.TraefikEtcdProxy.traefik_api_password = "123"
    
    # etcd credentials
    c.TraefikEtcdProxy.etcd_username = "def"
    c.TraefikEtcdProxy.etcd_password = "456"
    
  3. Create a toml file with traefik’s desired static configuration

    Before starting the traefik process, you must create a toml file with the desired traefik static configuration and pass it to traefik when you launch the process. Keep in mind that in order for the routes to be stored in etcd, this toml file must specify etcd as the provider/

    • Keep in mind that the static configuration must configure at least:

      • The default entrypoint

      • The api entrypoint

      • The etcd provider

    • Example:

      [api]
      
      [entryPoints.http]
      address = "127.0.0.1:8000"
      
      [entryPoints.auth_api]
      address = "127.0.0.1:8099"
      
      [providers.etcd]
      endpoints = [ "127.0.0.1:2379",]
      rootKey = "traefik"
      

    Note

    If you choose to enable the authentication on etcd, you can use this toml file to pass the credentials to traefik, e.g.:

      ```toml
      [providers.etcd]
      username = "root"
      password = "admin"
      ...
      ```
    
Example setup#

This is an example setup for using JupyterHub and TraefikEtcdProxy managed by another service than JupyterHub.

  1. Configure the proxy through the JupyterHub configuration file, jupyterhub_config.py, e.g.:

    # mark the proxy as externally managed
    c.TraefikEtcdProxy.should_start = False
    
    # traefik api endpoint login password
    c.TraefikEtcdProxy.traefik_api_password = "abc"
    
    # traefik api endpoint login username
    c.TraefikEtcdProxy.traefik_api_username = "123"
    
    # etcd url where it accepts client requests
    c.TraefikEtcdProxy.etcd_url = "http://127.0.0.1:2379"
    
    # configure JupyterHub to use TraefikEtcdProxy
    c.JupyterHub.proxy_class = "traefik_etcd"
    

    Note

    If you intend to enable authentication on etcd, add the etcd credentials to jupyterhub_config.py:

     # etcd username
     c.TraefikEtcdProxy.etcd_username = "def"
     # etcd password
     c.TraefikEtcdProxy.etcd_password = "456"
    
  2. Start a single-note etcd cluster on the default port on localhost. e.g.:

    $ etcd
    

    Note

    If you intend to enable authentication on etcd checkout this guide.

  3. Create a traefik static configuration file, traefik.toml, e.g:.

    # enable the api
    [api]
    
    # the public port where traefik accepts http requests
    [entryPoints.http]
    address = ":8000"
    
    # the port on localhost where the traefik api should be found
    [entryPoints.auth_api]
    address = "localhost:8099"
    
    [providers.etcd]
    # the etcd username (if auth is enabled)
    username = "def"
    # the etcd password (if auth is enabled)
    password = "456"
    # the etcd address
    endpoints = ["127.0.0.1:2379"]
    # the prefix to use for the static configuration
    rootKey = "traefik"
    
  4. Start traefik with the configuration specified above, e.g.:

    $ traefik -c traefik.toml
    

Using TraefikConsulProxy#

Warning

While it works today (2023), there does not appear to be a maintained Python client for the consul API. As such, using jupyterhub-traefik-proxy with consul is deprecated in jupyterhub-traefik-proxy 1.0. You can use etcd instead to achieve the same functionality (and slightly better performance).

Consul is a distributed key-value store. This and TraefikEtcdProxy is the choice to use when using jupyterhub-traefik-proxy in a distributed setup, such as a Kubernetes cluster, e.g. with multiple traefik-proxy instances.

How-To install TraefikConsulProxy#
  1. Install jupyterhub

  2. Install jupyterhub-traefik-proxy

  3. Install traefik

  4. Install consul

How-To enable TraefikConsulProxy#

You can enable JupyterHub to work with TraefikConsulProxy in jupyterhub_config.py, using the proxy_class configuration option.

You can choose to:

  • use the traefik_consul entrypoint, new in JupyterHub 1.0, e.g.:

    c.JupyterHub.proxy_class = "traefik_consul"
    
  • use the TraefikConsulProxy object, in which case, you have to import the module, e.g.:

    from jupyterhub_traefik_proxy import TraefikConsulProxy
    c.JupyterHub.proxy_class = TraefikConsulProxy
    
Consul configuration#
  1. Depending on the value of the should_start proxy flag, you can choose whether or not TraefikConsulProxy willl be externally managed.

    • When should_start is set to True, TraefikConsulProxy will auto-generate its static configuration (using the override values or the defaults) and store it in traefik.toml file. The traefik process will then be launched using this file.

    • When should_start is set to False, prior to starting the traefik process, you must create a toml file with the desired traefik static configuration and pass it to traefik. Keep in mind that in order for the routes to be stored in consul, this toml file must specify consul as the provider.

  2. TraefikConsulProxy searches in the consul key-value store the keys starting with the kv_traefik_prefix prefix in order to build its static configuration.

    Similarly, the dynamic configuration is built by searching the kv_jupyterhub_prefix.

    Note

    If you want to change or add traefik’s static configuration options, you can add them to consul under this prefix and traefik will pick them up.

    • The default values of this configurations options are:

      kv_traefik_prefix = "traefik/"
      kv_jupyterhub_prefix = "jupyterhub/"
      
    • You can override the default values of the prefixes by passing their desired values through jupyterhub_config.py e.g.:

      c.TraefikConsulProxy.kv_traefik_prefix="some_static_config_prefix/"
      c.TraefikConsulProxy.kv_jupyterhub_prefix="some_dynamic_config_prefix/"
      
  3. By default, TraefikConsulProxy assumes consul accepts client requests on the official default consul port 8500 for client requests.

    c.TraefikConsulProxy.consul_url = "http://127.0.0.1:8500"
    

    If the consul cluster is deployed differently than using the consul defaults, then you must pass the consul url to the proxy using the consul_url option in jupyterhub_config.py:

    c.TraefikConsulProxy.consul_url = "scheme://hostname:port"
    

    Note

    TraefikConsulProxy does not manage the consul cluster and assumes it is up and running before the proxy itself starts. However, based on how consul is configured and started, TraefikConsulProxy needs to be told about some consul configuration details, such as:

    • consul address where it accepts client requests

      c.TraefikConsulProxy.consul_url = "scheme://hostname:port"
      
    • consul credentials (if consul has acl enabled)

        c.TraefikConsulProxy.consul_password = "123"
      

    Checkout the consul documentation to find out more about possible consul configuration options.

Externally managed TraefikConsulProxy#

If TraefikConsulProxy is used as an externally managed service, then make sure you follow the steps enumerated below:

  1. Let JupyterHub know that the proxy being used is TraefikConsulProxy, using the proxy_class configuration option:

    c.JupyterHub.proxy_class = "traefik_consul"
    
  2. Configure TraefikConsulProxy in jupyterhub_config.py

    JupyterHub configuration file, jupyterhub_config.py must specify at least:

    • That the proxy is externally managed

    • The traefik api credentials

    • The consul credentials (if consul acl is enabled)

    Example configuration:

    # JupyterHub shouldn't start the proxy, it's already running
    c.TraefikConsulProxy.should_start = False
    
    # if not the default:
    c.TraefikConsulProxy.consul_url = "http://consul-host:2379"
    
    # traefik api credentials
    c.TraefikConsulProxy.traefik_api_username = "abc"
    c.TraefikConsulProxy.traefik_api_password = "123"
    
    # consul acl token
    c.TraefikConsulProxy.consul_password = "456"
    
  3. Create a toml file with traefik’s desired static configuration

    Before starting the traefik process, you must create a toml file with the desired traefik static configuration and pass it to traefik when you launch the process. Keep in mind that in order for the routes to be stored in consul, this toml file must specify consul as the provider/

    • Keep in mind that the static configuration must configure at least:

      • The default entrypoint

      • The api entrypoint

      • The consul provider

    Example:

    # enable the api
    [api]
    
    # the public port where traefik accepts http requests
    [entryPoints.http]
    address = ":8000"
    
    # the port on localhost where the traefik api should be found
    [entryPoints.auth_api]
    address = "localhost:8099"
    
    [providers.consul]
    # the consul username (if auth is enabled)
    username = "def"
    # the consul password (if auth is enabled)
    password = "456"
    # the consul address
    endpoints = ["127.0.0.1:8500"]
    # the prefix to use for the static configuration
    rootKey = "traefik"
    

    Note

    If you choose to enable consul Access Control Lists (ACLs) to secure the UI, API, CLI, service communications, and agent communications, you can use this toml file to pass the credentials to traefik, e.g.:

    [providers.consul]
    password = "admin"
    ...
    
Example setup#

This is an example setup for using JupyterHub and TraefikConsulProxy managed by another service than JupyterHub.

  1. Configure the proxy through the JupyterHub configuration file, jupyterhub_config.py, e.g.:

    # mark the proxy as externally managed
    c.TraefikConsulProxy.should_start = False
    
    # traefik api endpoint login password
    c.TraefikConsulProxy.traefik_api_password = "abc"
    
    # traefik api endpoint login username
    c.TraefikConsulProxy.traefik_api_username = "123"
    
    # consul url where it accepts client requests
    c.TraefikConsulProxy.consul_url = "http://127.0.0.1:8500"
    
    # configure JupyterHub to use TraefikConsulProxy
    c.JupyterHub.proxy_class = "traefik_consul"
    

    Note

    If you intend to enable consul acl, add the acl token to jupyterhub_config.py under consul_password:

    # consul token
    c.TraefikConsulProxy.consul_password = "456"
    
  2. Starts the agent in development mode on the default port on localhost. e.g.:

    $ consul agent -dev
    

    Note

    If you intend to enable consul acl, checkout this guide.

  3. Create a traefik static configuration file, traefik.toml, e.g:.

    # enable the api
    [api]
    
    # the public port where traefik accepts http requests
    [entryPoints.http]
    address = ":8000"
    
    # the port on localhost where the traefik api should be found
    [entryPoints.auth_api]
    address = "localhost:8099"
    
    [providers.consul]
    # the username (if auth is enabled)
    username = "def"
    # the password (if auth is enabled)
    password = "456"
    # the consul address
    endpoints = ["127.0.0.1:8500"]
    # the prefix to use for the static configuration
    rootKey = "traefik"
    
  4. Start traefik with the configuration specified above, e.g.:

    $ traefik -c traefik.toml
    

API Reference#

Proxy API#

Module: jupyterhub_traefik_proxy#

Traefik implementation of the JupyterHub proxy API

TraefikProxy#
class jupyterhub_traefik_proxy.proxy.TraefikProxy(**kwargs: Any)[source]#

JupyterHub Proxy implementation using traefik

async add_route(routespec, target, data)[source]#

Add a route to the proxy.

Subclasses must define this method

Parameters:
  • routespec (str) – A URL prefix ([host]/path/) for which this route will be matched, e.g. host.name/path/

  • target (str) – A full URL that will be the target of this route.

  • data (dict) – A JSONable dict that will be associated with this route, and will be returned when retrieving information about this route.

Will raise an appropriate Exception (FIXME: find what?) if the route could not be added.

The proxy implementation should also have a way to associate the fact that a route came from JupyterHub.

check_route_timeout c.TraefikProxy.check_route_timeout = Int(30)#

Timeout (in seconds) when waiting for traefik to register an updated route.

async check_routes(*args, **kwargs)[source]#

Check that all users are properly routed on the proxy.

concurrency c.TraefikProxy.concurrency = Int(10)#

The number of requests allowed to be concurrently outstanding to the proxy

Limiting this number avoids potential timeout errors by sending too many requests to update the proxy at once

async delete_route(routespec)[source]#

Delete a route with a given routespec if it exists.

enable_setup_dynamic_config c.TraefikProxy.enable_setup_dynamic_config = Bool(True)#

Whether to initialize the traefik dynamic configuration from JupyterHub configuration, when should_start is False (dynamic configuration is always applied when should_start is True).

Creates the traefik API router and TLS setup, if any. When True, only traefik static configuration must be managed by the external service (configuration of the endpoints and provider). The traefik api router should not already be configured via other dynamic configuration providers.

When False, initial dynamic configuration must be handled externally and match TraefikProxy configuration, such as traefik_api_url, traefik_api_username` and traefik_api_password. Choose this if the traefik api router is already configured via dynamic configuration elsewhere.

New in version 1.1.

extra_dynamic_config c.TraefikProxy.extra_dynamic_config = Dict()#

Extra dynamic configuration for treafik.

Merged with the default dynamic config during startup.

Always takes effect unless should_start and enable_setup_dynamic_config are both False.

extra_routes c.TraefikProxy.extra_routes = Dict()#

Additional routes to be maintained in the proxy.

A dictionary with a route specification as key, and a URL as target. The hub will ensure this route is present in the proxy.

If the hub is running in host based mode (with JupyterHub.subdomain_host set), the routespec must have a domain component (example.com/my-url/). If the hub is not running in host based mode, the routespec must not have a domain component (/my-url/).

Helpful when the hub is running in API-only mode.

extra_static_config c.TraefikProxy.extra_static_config = Dict()#

Extra static configuration for treafik.

Merged with the default static config before writing to .static_config_file.

Has no effect if Proxy.should_start is False.

async get_all_routes()[source]#

Fetch and return all the routes associated by JupyterHub from the proxy.

Subclasses must define this method

Should return a dictionary of routes, where the keys are routespecs and each value is a dict of the form:

{
  'routespec': the route specification ([host]/path/)
  'target': the target host URL (proto://host) for this route
  'data': the attached data dict for this route (as specified in add_route)
}
is_https Bool(False)#

Whether public_url specifies an https entrypoint

provider_name Unicode('')#

The provider name that Traefik expects, e.g. file, consul, etcd

should_start c.TraefikProxy.should_start = Bool(True)#

Should the Hub start the proxy

If True, the Hub will start the proxy and stop it. Set to False if the proxy is managed externally, such as by systemd, docker, or another service manager.

async start()[source]#

Start the proxy.

Will be called during startup if should_start is True.

Subclasses must define this method if the proxy is to be started by the Hub

static_config_file c.TraefikProxy.static_config_file = Unicode('traefik.toml')#

traefik’s static configuration file

async stop()[source]#

Stop the proxy.

Will be called during teardown if should_start is True.

Subclasses must define this method if the proxy is to be started by the Hub

toml_static_config_file c.TraefikProxy.toml_static_config_file = Unicode('')#

Deprecated. Use static_config_file

traefik_api_entrypoint c.TraefikProxy.traefik_api_entrypoint = Unicode('auth_api')#

The traefik entrypoint name to use for API access.

Separate from traefik_entrypoint, because this is usually only on localhost.

traefik_api_password c.TraefikProxy.traefik_api_password = Unicode('')#

The password for traefik api login

traefik_api_url c.TraefikProxy.traefik_api_url = Unicode('http://localhost:8099')#

traefik authenticated api endpoint url

traefik_api_username c.TraefikProxy.traefik_api_username = Unicode('')#

The username for traefik api login

traefik_api_validate_cert c.TraefikProxy.traefik_api_validate_cert = Bool(True)#

validate SSL certificate of traefik api endpoint

traefik_entrypoint c.TraefikProxy.traefik_entrypoint = Unicode('')#

The traefik entrypoint name to use.

By default, will be http if http or https if https.

If running traefik externally with your own specified entrypoint name, set this value.

traefik_env c.TraefikProxy.traefik_env = Dict()#

Environment variables to set for the traefik process.

Only has an effect when traefik is a subprocess (should_start=True).

traefik_log_level c.TraefikProxy.traefik_log_level = Unicode('')#

traefik’s log level

traefik_providers_throttle_duration c.TraefikProxy.traefik_providers_throttle_duration = Unicode('0s')#

throttle traefik reloads of configuration.

When traefik sees a change in configuration, it will wait this long before applying the next one. This affects how long adding a user to the proxy will take.

See https://doc.traefik.io/traefik/providers/overview/#providersprovidersthrottleduration

validate_routespec(routespec)[source]#

Override jupyterhub’s default Proxy.validate_routespec method, as traefik can set router rule’s on both Host and PathPrefix rules combined.

TraefikFileProviderProxy#
class jupyterhub_traefik_proxy.fileprovider.TraefikFileProviderProxy(**kwargs: Any)[source]#

JupyterHub Proxy implementation using traefik and toml or yaml config file

check_route_timeout c.TraefikFileProviderProxy.check_route_timeout = Int(30)#

Timeout (in seconds) when waiting for traefik to register an updated route.

concurrency c.TraefikFileProviderProxy.concurrency = Int(10)#

The number of requests allowed to be concurrently outstanding to the proxy

Limiting this number avoids potential timeout errors by sending too many requests to update the proxy at once

dynamic_config_file c.TraefikFileProviderProxy.dynamic_config_file = Unicode('rules.toml')#

traefik’s dynamic configuration file

enable_setup_dynamic_config c.TraefikFileProviderProxy.enable_setup_dynamic_config = Bool(True)#

Whether to initialize the traefik dynamic configuration from JupyterHub configuration, when should_start is False (dynamic configuration is always applied when should_start is True).

Creates the traefik API router and TLS setup, if any. When True, only traefik static configuration must be managed by the external service (configuration of the endpoints and provider). The traefik api router should not already be configured via other dynamic configuration providers.

When False, initial dynamic configuration must be handled externally and match TraefikProxy configuration, such as traefik_api_url, traefik_api_username` and traefik_api_password. Choose this if the traefik api router is already configured via dynamic configuration elsewhere.

New in version 1.1.

extra_dynamic_config c.TraefikFileProviderProxy.extra_dynamic_config = Dict()#

Extra dynamic configuration for treafik.

Merged with the default dynamic config during startup.

Always takes effect unless should_start and enable_setup_dynamic_config are both False.

extra_routes c.TraefikFileProviderProxy.extra_routes = Dict()#

Additional routes to be maintained in the proxy.

A dictionary with a route specification as key, and a URL as target. The hub will ensure this route is present in the proxy.

If the hub is running in host based mode (with JupyterHub.subdomain_host set), the routespec must have a domain component (example.com/my-url/). If the hub is not running in host based mode, the routespec must not have a domain component (/my-url/).

Helpful when the hub is running in API-only mode.

extra_static_config c.TraefikFileProviderProxy.extra_static_config = Dict()#

Extra static configuration for treafik.

Merged with the default static config before writing to .static_config_file.

Has no effect if Proxy.should_start is False.

async get_route(routespec)[source]#

Return the route info for a given routespec.

Parameters:

routespec (str) – A URI that was used to add this route, e.g. host.tld/path/

Returns:

dict with the following keys:

'routespec': The normalized route specification passed in to add_route
    ([host]/path/)
'target': The target host for this route (proto://host)
'data': The arbitrary data dict that was passed in by JupyterHub when adding this
        route.

None: if there are no routes matching the given routespec

Return type:

result (dict)

should_start c.TraefikFileProviderProxy.should_start = Bool(True)#

Should the Hub start the proxy

If True, the Hub will start the proxy and stop it. Set to False if the proxy is managed externally, such as by systemd, docker, or another service manager.

static_config_file c.TraefikFileProviderProxy.static_config_file = Unicode('traefik.toml')#

traefik’s static configuration file

toml_static_config_file c.TraefikFileProviderProxy.toml_static_config_file = Unicode('')#

Deprecated. Use static_config_file

traefik_api_entrypoint c.TraefikFileProviderProxy.traefik_api_entrypoint = Unicode('auth_api')#

The traefik entrypoint name to use for API access.

Separate from traefik_entrypoint, because this is usually only on localhost.

traefik_api_password c.TraefikFileProviderProxy.traefik_api_password = Unicode('')#

The password for traefik api login

traefik_api_url c.TraefikFileProviderProxy.traefik_api_url = Unicode('http://localhost:8099')#

traefik authenticated api endpoint url

traefik_api_username c.TraefikFileProviderProxy.traefik_api_username = Unicode('')#

The username for traefik api login

traefik_api_validate_cert c.TraefikFileProviderProxy.traefik_api_validate_cert = Bool(True)#

validate SSL certificate of traefik api endpoint

traefik_entrypoint c.TraefikFileProviderProxy.traefik_entrypoint = Unicode('')#

The traefik entrypoint name to use.

By default, will be http if http or https if https.

If running traefik externally with your own specified entrypoint name, set this value.

traefik_env c.TraefikFileProviderProxy.traefik_env = Dict()#

Environment variables to set for the traefik process.

Only has an effect when traefik is a subprocess (should_start=True).

traefik_log_level c.TraefikFileProviderProxy.traefik_log_level = Unicode('')#

traefik’s log level

traefik_providers_throttle_duration c.TraefikFileProviderProxy.traefik_providers_throttle_duration = Unicode('0s')#

throttle traefik reloads of configuration.

When traefik sees a change in configuration, it will wait this long before applying the next one. This affects how long adding a user to the proxy will take.

See https://doc.traefik.io/traefik/providers/overview/#providersprovidersthrottleduration

TKvProxy#
class jupyterhub_traefik_proxy.kv_proxy.TKvProxy(**kwargs: Any)[source]#

JupyterHub Proxy implementation using traefik and a key-value store.

Custom proxy implementations based on traefik and a key-value store can sublass TKvProxy.

check_route_timeout c.TKvProxy.check_route_timeout = Int(30)#

Timeout (in seconds) when waiting for traefik to register an updated route.

concurrency c.TKvProxy.concurrency = Int(10)#

The number of requests allowed to be concurrently outstanding to the proxy

Limiting this number avoids potential timeout errors by sending too many requests to update the proxy at once

enable_setup_dynamic_config c.TKvProxy.enable_setup_dynamic_config = Bool(True)#

Whether to initialize the traefik dynamic configuration from JupyterHub configuration, when should_start is False (dynamic configuration is always applied when should_start is True).

Creates the traefik API router and TLS setup, if any. When True, only traefik static configuration must be managed by the external service (configuration of the endpoints and provider). The traefik api router should not already be configured via other dynamic configuration providers.

When False, initial dynamic configuration must be handled externally and match TraefikProxy configuration, such as traefik_api_url, traefik_api_username` and traefik_api_password. Choose this if the traefik api router is already configured via dynamic configuration elsewhere.

New in version 1.1.

extra_dynamic_config c.TKvProxy.extra_dynamic_config = Dict()#

Extra dynamic configuration for treafik.

Merged with the default dynamic config during startup.

Always takes effect unless should_start and enable_setup_dynamic_config are both False.

extra_routes c.TKvProxy.extra_routes = Dict()#

Additional routes to be maintained in the proxy.

A dictionary with a route specification as key, and a URL as target. The hub will ensure this route is present in the proxy.

If the hub is running in host based mode (with JupyterHub.subdomain_host set), the routespec must have a domain component (example.com/my-url/). If the hub is not running in host based mode, the routespec must not have a domain component (/my-url/).

Helpful when the hub is running in API-only mode.

extra_static_config c.TKvProxy.extra_static_config = Dict()#

Extra static configuration for treafik.

Merged with the default static config before writing to .static_config_file.

Has no effect if Proxy.should_start is False.

flatten_dict_for_kv(data, prefix='')[source]#

Flatten a dictionary of data for storage in the KV store, prefixing each key with prefix and joining each key with TKvProxy.kv_separator.

If the final value is a list, then the provided bottom-level key shall be appended with an incrementing numeric number, in the style that is used by traefik’s KV store, e.g.

flatten_dict_for_kv({
    'x' : {
        'y' : {
            'z': 'a'
        }
    }, {
        'foo': 'bar'
    },
    'baz': [ 'a', 'b', 'c' ]
})
Returns:

The flattened dictionary

Return type:

dict

e.g.

{
     'traefik/x/y/z' : 'a',
     'traefik/x/foo': 'bar',
     'traefik/baz/0': 'a',
     'traefik/baz/1': 'b',
     'traefik/baz/2': 'c',
}

Inspired by this answer on StackOverflow

async get_route(routespec)[source]#

Return the route info for a given routespec.

Parameters:

routespec (str) – A URI that was used to add this route, e.g. host.tld/path/

Returns:

dict with the following keys:

'routespec': The normalized route specification passed in to add_route
    ([host]/path/)
'target': The target host for this route (proto://host)
'data': The arbitrary data dict that was passed in by JupyterHub when adding this
        route.

None: if there are no routes matching the given routespec

Return type:

result (dict)

kv_jupyterhub_prefix c.TKvProxy.kv_jupyterhub_prefix = KVStorePrefix('jupyterhub')#

The key value store key prefix for traefik dynamic configuration

kv_separator c.TKvProxy.kv_separator = Unicode('/')#

The separator used for the path in the KV store

kv_traefik_prefix c.TKvProxy.kv_traefik_prefix = KVStorePrefix('traefik')#

The key value store key prefix for traefik static configuration

should_start c.TKvProxy.should_start = Bool(True)#

Should the Hub start the proxy

If True, the Hub will start the proxy and stop it. Set to False if the proxy is managed externally, such as by systemd, docker, or another service manager.

static_config_file c.TKvProxy.static_config_file = Unicode('traefik.toml')#

traefik’s static configuration file

toml_static_config_file c.TKvProxy.toml_static_config_file = Unicode('')#

Deprecated. Use static_config_file

traefik_api_entrypoint c.TKvProxy.traefik_api_entrypoint = Unicode('auth_api')#

The traefik entrypoint name to use for API access.

Separate from traefik_entrypoint, because this is usually only on localhost.

traefik_api_password c.TKvProxy.traefik_api_password = Unicode('')#

The password for traefik api login

traefik_api_url c.TKvProxy.traefik_api_url = Unicode('http://localhost:8099')#

traefik authenticated api endpoint url

traefik_api_username c.TKvProxy.traefik_api_username = Unicode('')#

The username for traefik api login

traefik_api_validate_cert c.TKvProxy.traefik_api_validate_cert = Bool(True)#

validate SSL certificate of traefik api endpoint

traefik_entrypoint c.TKvProxy.traefik_entrypoint = Unicode('')#

The traefik entrypoint name to use.

By default, will be http if http or https if https.

If running traefik externally with your own specified entrypoint name, set this value.

traefik_env c.TKvProxy.traefik_env = Dict()#

Environment variables to set for the traefik process.

Only has an effect when traefik is a subprocess (should_start=True).

traefik_log_level c.TKvProxy.traefik_log_level = Unicode('')#

traefik’s log level

traefik_providers_throttle_duration c.TKvProxy.traefik_providers_throttle_duration = Unicode('0s')#

throttle traefik reloads of configuration.

When traefik sees a change in configuration, it will wait this long before applying the next one. This affects how long adding a user to the proxy will take.

See https://doc.traefik.io/traefik/providers/overview/#providersprovidersthrottleduration

unflatten_dict_from_kv(kv_list, root_key='')[source]#

Reconstruct tree dict from list of key/value pairs

This is the inverse of flatten_dict_for_kv, not including str coercion.

Args:

kv_list (list):

list of (key, value) pairs. keys and values should all be strings.

root_key (str, optional):

The key representing the root of the tree, if not the root of the key-value store.

Returns:

tree (dict):

The reconstructed dictionary. All values will still be strings, even those that originated as numbers or booleans.

TraefikEtcdProxy#
class jupyterhub_traefik_proxy.etcd.TraefikEtcdProxy(**kwargs: Any)[source]#

JupyterHub Proxy implementation using traefik and etcd

check_route_timeout c.TraefikEtcdProxy.check_route_timeout = Int(30)#

Timeout (in seconds) when waiting for traefik to register an updated route.

concurrency c.TraefikEtcdProxy.concurrency = Int(10)#

The number of requests allowed to be concurrently outstanding to the proxy

Limiting this number avoids potential timeout errors by sending too many requests to update the proxy at once

enable_setup_dynamic_config c.TraefikEtcdProxy.enable_setup_dynamic_config = Bool(True)#

Whether to initialize the traefik dynamic configuration from JupyterHub configuration, when should_start is False (dynamic configuration is always applied when should_start is True).

Creates the traefik API router and TLS setup, if any. When True, only traefik static configuration must be managed by the external service (configuration of the endpoints and provider). The traefik api router should not already be configured via other dynamic configuration providers.

When False, initial dynamic configuration must be handled externally and match TraefikProxy configuration, such as traefik_api_url, traefik_api_username` and traefik_api_password. Choose this if the traefik api router is already configured via dynamic configuration elsewhere.

New in version 1.1.

etcd_client_kwargs c.TraefikEtcdProxy.etcd_client_kwargs = Dict()#

Extra keyword arguments to pass to the etcd Python client constructor

etcd_password c.TraefikEtcdProxy.etcd_password = Unicode('')#

Password for accessing etcd.

etcd_url c.TraefikEtcdProxy.etcd_url = Unicode('http://127.0.0.1:2379')#

URL for the etcd endpoint.

etcd_username c.TraefikEtcdProxy.etcd_username = Unicode('')#

Username for accessing etcd.

extra_dynamic_config c.TraefikEtcdProxy.extra_dynamic_config = Dict()#

Extra dynamic configuration for treafik.

Merged with the default dynamic config during startup.

Always takes effect unless should_start and enable_setup_dynamic_config are both False.

extra_routes c.TraefikEtcdProxy.extra_routes = Dict()#

Additional routes to be maintained in the proxy.

A dictionary with a route specification as key, and a URL as target. The hub will ensure this route is present in the proxy.

If the hub is running in host based mode (with JupyterHub.subdomain_host set), the routespec must have a domain component (example.com/my-url/). If the hub is not running in host based mode, the routespec must not have a domain component (/my-url/).

Helpful when the hub is running in API-only mode.

extra_static_config c.TraefikEtcdProxy.extra_static_config = Dict()#

Extra static configuration for treafik.

Merged with the default static config before writing to .static_config_file.

Has no effect if Proxy.should_start is False.

kv_jupyterhub_prefix c.TraefikEtcdProxy.kv_jupyterhub_prefix = KVStorePrefix('jupyterhub')#

The key value store key prefix for traefik dynamic configuration

kv_password c.TraefikEtcdProxy.kv_password = Unicode('DEPRECATED')#

No help string is provided.

kv_separator c.TraefikEtcdProxy.kv_separator = Unicode('/')#

The separator used for the path in the KV store

kv_traefik_prefix c.TraefikEtcdProxy.kv_traefik_prefix = KVStorePrefix('traefik')#

The key value store key prefix for traefik static configuration

kv_url c.TraefikEtcdProxy.kv_url = Unicode('DEPRECATED')#

No help string is provided.

kv_username c.TraefikEtcdProxy.kv_username = Unicode('DEPRECATED')#

No help string is provided.

should_start c.TraefikEtcdProxy.should_start = Bool(True)#

Should the Hub start the proxy

If True, the Hub will start the proxy and stop it. Set to False if the proxy is managed externally, such as by systemd, docker, or another service manager.

static_config_file c.TraefikEtcdProxy.static_config_file = Unicode('traefik.toml')#

traefik’s static configuration file

toml_static_config_file c.TraefikEtcdProxy.toml_static_config_file = Unicode('')#

Deprecated. Use static_config_file

traefik_api_entrypoint c.TraefikEtcdProxy.traefik_api_entrypoint = Unicode('auth_api')#

The traefik entrypoint name to use for API access.

Separate from traefik_entrypoint, because this is usually only on localhost.

traefik_api_password c.TraefikEtcdProxy.traefik_api_password = Unicode('')#

The password for traefik api login

traefik_api_url c.TraefikEtcdProxy.traefik_api_url = Unicode('http://localhost:8099')#

traefik authenticated api endpoint url

traefik_api_username c.TraefikEtcdProxy.traefik_api_username = Unicode('')#

The username for traefik api login

traefik_api_validate_cert c.TraefikEtcdProxy.traefik_api_validate_cert = Bool(True)#

validate SSL certificate of traefik api endpoint

traefik_entrypoint c.TraefikEtcdProxy.traefik_entrypoint = Unicode('')#

The traefik entrypoint name to use.

By default, will be http if http or https if https.

If running traefik externally with your own specified entrypoint name, set this value.

traefik_env c.TraefikEtcdProxy.traefik_env = Dict()#

Environment variables to set for the traefik process.

Only has an effect when traefik is a subprocess (should_start=True).

traefik_log_level c.TraefikEtcdProxy.traefik_log_level = Unicode('')#

traefik’s log level

traefik_providers_throttle_duration c.TraefikEtcdProxy.traefik_providers_throttle_duration = Unicode('0s')#

throttle traefik reloads of configuration.

When traefik sees a change in configuration, it will wait this long before applying the next one. This affects how long adding a user to the proxy will take.

See https://doc.traefik.io/traefik/providers/overview/#providersprovidersthrottleduration

TraefikConsulProxy#
class jupyterhub_traefik_proxy.consul.TraefikConsulProxy(**kwargs: Any)[source]#

JupyterHub Proxy implementation using traefik and Consul

check_route_timeout c.TraefikConsulProxy.check_route_timeout = Int(30)#

Timeout (in seconds) when waiting for traefik to register an updated route.

concurrency c.TraefikConsulProxy.concurrency = Int(10)#

The number of requests allowed to be concurrently outstanding to the proxy

Limiting this number avoids potential timeout errors by sending too many requests to update the proxy at once

consul_client_kwargs c.TraefikConsulProxy.consul_client_kwargs = Dict()#

Extra consul client constructor arguments

consul_password c.TraefikConsulProxy.consul_password = Unicode('')#

Password or token for accessing consul.

consul_url c.TraefikConsulProxy.consul_url = Unicode('http://127.0.0.1:8500')#

URL for the consul endpoint.

consul_username c.TraefikConsulProxy.consul_username = Unicode('')#

Usrname for accessing consul.

enable_setup_dynamic_config c.TraefikConsulProxy.enable_setup_dynamic_config = Bool(True)#

Whether to initialize the traefik dynamic configuration from JupyterHub configuration, when should_start is False (dynamic configuration is always applied when should_start is True).

Creates the traefik API router and TLS setup, if any. When True, only traefik static configuration must be managed by the external service (configuration of the endpoints and provider). The traefik api router should not already be configured via other dynamic configuration providers.

When False, initial dynamic configuration must be handled externally and match TraefikProxy configuration, such as traefik_api_url, traefik_api_username` and traefik_api_password. Choose this if the traefik api router is already configured via dynamic configuration elsewhere.

New in version 1.1.

extra_dynamic_config c.TraefikConsulProxy.extra_dynamic_config = Dict()#

Extra dynamic configuration for treafik.

Merged with the default dynamic config during startup.

Always takes effect unless should_start and enable_setup_dynamic_config are both False.

extra_routes c.TraefikConsulProxy.extra_routes = Dict()#

Additional routes to be maintained in the proxy.

A dictionary with a route specification as key, and a URL as target. The hub will ensure this route is present in the proxy.

If the hub is running in host based mode (with JupyterHub.subdomain_host set), the routespec must have a domain component (example.com/my-url/). If the hub is not running in host based mode, the routespec must not have a domain component (/my-url/).

Helpful when the hub is running in API-only mode.

extra_static_config c.TraefikConsulProxy.extra_static_config = Dict()#

Extra static configuration for treafik.

Merged with the default static config before writing to .static_config_file.

Has no effect if Proxy.should_start is False.

kv_jupyterhub_prefix c.TraefikConsulProxy.kv_jupyterhub_prefix = KVStorePrefix('jupyterhub')#

The key value store key prefix for traefik dynamic configuration

kv_password c.TraefikConsulProxy.kv_password = Unicode('DEPRECATED')#

No help string is provided.

kv_separator c.TraefikConsulProxy.kv_separator = Unicode('/')#

The separator used for the path in the KV store

kv_traefik_prefix c.TraefikConsulProxy.kv_traefik_prefix = KVStorePrefix('traefik')#

The key value store key prefix for traefik static configuration

kv_url c.TraefikConsulProxy.kv_url = Unicode('DEPRECATED')#

No help string is provided.

kv_username c.TraefikConsulProxy.kv_username = Unicode('DEPRECATED')#

No help string is provided.

should_start c.TraefikConsulProxy.should_start = Bool(True)#

Should the Hub start the proxy

If True, the Hub will start the proxy and stop it. Set to False if the proxy is managed externally, such as by systemd, docker, or another service manager.

static_config_file c.TraefikConsulProxy.static_config_file = Unicode('traefik.toml')#

traefik’s static configuration file

toml_static_config_file c.TraefikConsulProxy.toml_static_config_file = Unicode('')#

Deprecated. Use static_config_file

traefik_api_entrypoint c.TraefikConsulProxy.traefik_api_entrypoint = Unicode('auth_api')#

The traefik entrypoint name to use for API access.

Separate from traefik_entrypoint, because this is usually only on localhost.

traefik_api_password c.TraefikConsulProxy.traefik_api_password = Unicode('')#

The password for traefik api login

traefik_api_url c.TraefikConsulProxy.traefik_api_url = Unicode('http://localhost:8099')#

traefik authenticated api endpoint url

traefik_api_username c.TraefikConsulProxy.traefik_api_username = Unicode('')#

The username for traefik api login

traefik_api_validate_cert c.TraefikConsulProxy.traefik_api_validate_cert = Bool(True)#

validate SSL certificate of traefik api endpoint

traefik_entrypoint c.TraefikConsulProxy.traefik_entrypoint = Unicode('')#

The traefik entrypoint name to use.

By default, will be http if http or https if https.

If running traefik externally with your own specified entrypoint name, set this value.

traefik_env c.TraefikConsulProxy.traefik_env = Dict()#

Environment variables to set for the traefik process.

Only has an effect when traefik is a subprocess (should_start=True).

traefik_log_level c.TraefikConsulProxy.traefik_log_level = Unicode('')#

traefik’s log level

traefik_providers_throttle_duration c.TraefikConsulProxy.traefik_providers_throttle_duration = Unicode('0s')#

throttle traefik reloads of configuration.

When traefik sees a change in configuration, it will wait this long before applying the next one. This affects how long adding a user to the proxy will take.

See https://doc.traefik.io/traefik/providers/overview/#providersprovidersthrottleduration

Changelog#

For detailed changes from the prior release, click on the version number and its link will bring up a GitHub listing of changes. Use git log on the command line for details.

1.1.0 - 2023-06-06#

(full changelog)

New features added#
  • add TraefikProxy.enable_setup_dynamic_config for opt-out of dynamic config setup #210 (@minrk, @alexleach)

Contributors to this release#

The following people contributed discussions, new ideas, code and documentation contributions, and review. See our definition of contributors.

(GitHub contributors page for this release)

@alexleach (activity) | @consideRatio (activity) | @minrk (activity)

1.0.1 - 2023-05-25#

1.0.1 fixes some packing issues in the 1.0 release. No changes to actual behavior.

(full changelog)

Maintenance and upkeep improvements#
  • ensure requirements.txt is included in the sdist, exclude performance and importable tests #207 (@bollwyvl, @minrk)

Contributors to this release#

The following people contributed discussions, new ideas, code and documentation contributions, and review. See our definition of contributors.

(GitHub contributors page for this release)

@bollwyvl (activity) | @minrk (activity)

1.0.0 - 2023-05-16#

1.0.0 is a big release for jupyterhub-traefik-proxy! It updates support for traefik to version 2.x (current default: 2.10.1). Traefik versions < 2.0 are no longer supported. If you have custom traefik configuration, make sure to checkout traefik’s v1 to v2 migration guide, since your configuration may need updating.

A major consequence of the v2 updates is that the performance of adding and removing routes when there are a large number already defined is now greatly improved, and no longer grows significantly with the number of routes.

Major changes:

  • Traefik v2 is required. Traefik v1 is not supported.

  • TraefikTomlProxy is deprecated in favor of TraefikFileProviderProxy, which supports both toml and yaml. Replace traefik_toml with traefik_file in your configuration.

  • python3 -m jupyterhub_traefik_proxy.install will now only install traefik, not any key-value-store providers. You can follow your KV store’s own installation instructions.

  • python3 -m jupyterhub_traefik_proxy.install --traefik-version x.y.z now supports fetching any published traefik version on any architecture, instead of a few preset versions.

Performance and responsiveness is also greatly improved.

(full changelog)

API and Breaking Changes#
New features added#
Enhancements made#
Bugs fixed#
Maintenance and upkeep improvements#
Documentation improvements#
Contributors to this release#

The following people contributed discussions, new ideas, code and documentation contributions, and review. See our definition of contributors.

(GitHub contributors page for this release)

@alexleach (activity) | @consideRatio (activity) | @dependabot (activity) | @devnull-mr (activity) | @dolfinus (activity) | @GeorgianaElena (activity) | @manics (activity) | @maulikjs (activity) | @minrk (activity) | @pre-commit-ci (activity) | @rcthomas (activity) | @twalcari (activity)

0.3.0 2021-10-18#
Enhancements made#
Bugs fixed#
  • Fix handling default server routes in TraefikTomlProxy #131 (@dolfinus)

  • Make etcd3 & python-consul2 soft dependencies #127 (@yuvipanda)

Continuous integration#
Contributors to this release#

(GitHub contributors page for this release)

@alexleach | @consideRatio | @dolfinus | @GeorgianaElena | @yuvipanda

0.2.0 2021-02-24#
Bugs fixed#
  • fix Escape character _ cannot be a safe character #109 #110 (@mofanke)

Maintenance and upkeep improvements#
Other merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@consideRatio | @GeorgianaElena | @manics | @minrk | @mofanke

0.1.6 2020-05-16#
Merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@consideRatio | @devnull-mr | @GeorgianaElena | @mofanke

0.1.5 2020-03-31#
Merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@consideRatio | @devnull-mr | @GeorgianaElena | @jtpio | @manics | @muxator | @yuvipanda

0.1.4 2019-09-20#
Merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@GeorgianaElena | @minrk

0.1.3 2019-02-26#
  • Load initial routing table from disk in TraefikTomlProxy when resuming from a previous session.

Merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@GeorgianaElena | @minrk

0.1.2 2019-02-22#
  • Fix possible race in atomic_writing with TraefikTomlProxy

0.1.1 2019-02-22#
  • make proxytest reusable with any Proxy implementation

  • improve documentation

  • improve logging and error handling

  • make check_route_timeout configurable

Merged PRs#
Contributors to this release#

(GitHub contributors page for this release)

@GeorgianaElena | @minrk

0.1.0#

First release!

Implementation details#

Traefik API#

traefik-proxy uses the Traefik API to monitor routes and configurations.

Because of security concerns, in traefik-proxy implementation, traefik api endpoint isn’t exposed on the public http endpoint. Instead, it runs on a dedicated authenticated endpoint that’s on localhost by default.

The port on which traefik-proxy’s api will run, as well as the username and password used for authenticating, can be passed to the proxy through jupyterhub_config.py, e.g.:

c.TraefikFileProviderProxy.traefik_api_url = "http://127.0.0.1:8099"
c.TraefikFileProviderProxy.traefik_api_password = "admin"
c.TraefikFileProviderProxy.traefik_api_username = "admin"

Check out TraefikProxy’s API Reference for more configuration options.

Class structure#

A JupyterHub Proxy implementation must implement these methods:

  • start / stop (starting and stopping the proxy)

  • add_route

  • delete_route

  • get_all_routes

  • get_route (a default implementation is provided by the base class, based on get_all_routes)

Additionally, for traefik we need to set up the separate “static config” (API access and where routes will be stored) and “dynamic config” (the routing table itself, which changes as servers start and stop). Where and how dynamic_config is stored is the ~only difference between TraefikProxy subclasses.

Setting up traefik configuration is in these methods:

  • _setup_traefik_static_config - must be extended by subclasses to add any provider-specific configuration to self.static_config

  • _setup_traefik_dynamic_config - usually not modified

TraefikProxy is organized into three levels:

First, is TraefikProxy. This class is responsible for everything traefik-specific. It implements talking to the traefik API, and computing what dynamic configuration is needed for each step. This base class provides implementations of start, stop, add_route, delete_route, and get_all_routes.

TraefikProxy subclasses must implement how dynamic config is stored, and set the appropriate static config to tell traefik how to load the dynamic config. Specifically, the generic methods:

  • _setup_traefik_static_config should extend self.static_config to configure the traefik configuration discovery provider

  • _apply_dynamic_config stores a given dynamic config dictionary in the appropriate config store

  • _delete_dynamic_config removes keys from the dynamic config store

  • _get_jupyterhub_dynamic_config reads the whole jupyterhub part (not read by traefik itself)

We have two classes at this level:

  • TraefikFileProviderProxy, which stores dynamic config in a toml or yaml file, and

  • TKvProxy - another base class, which implements the above, based on a generic notion of a key-value store

TKvProxy is an adapter layer, implementing all of the above methods, based on a few basic actions on key-value stores:

  • _kv_atomic_set should take a flat dictionary of key paths and string values, and store them.

  • _kv_atomic_delete should delete a number of keys in a single transaction

  • _kv_get_tree should recursively read everything in the key-value store under a prefix (returning the flattened dictionary)

TKvProxy is responsible for translating between key-value-friendly “flat” dictionaries and the ‘true’ nested dictionary format of the configuration (i.e. the nested dictionary {"a": {"b": 5}} will be flattened to {"a/b": "5"}).

Finally, we have our specific key-value store implementations: TraefikEtcdProxy and TraefikConsulProxy. These classes only need to implement:

  1. configuration necessary to connect to the key-value provider

  2. _setup_traefik_static_config to tell traefik how to talk to the same key-value provider

  3. the above three _kv_ methods for reading, writing, and deleting keys

Testing jupyterhub-traefik-proxy#

You can then run the all the test suite from the traefik-proxy directory with:

$ pytest -v ./tests

Or you can run a specific test with:

$ pytest -v ./tests/<test-file-name>

Indices and tables#