Commit d44996b8 authored by Daniele Venzano's avatar Daniele Venzano

Implement hacky/experimental traefik support for reverse proxy

parent 47cab492
......@@ -207,12 +207,14 @@ class APIEndpoint:
endpoints = []
for service in
for port in service.description['ports']:
port_key = str(port['port_number']) + "/" + port['protocol']
backend_port =,, internal_name=port_key)
if backend_port is not None and backend_port.external_ip is not None:
endpoint = port['url_template'].format(**{"ip_port": backend_port.external_ip + ":" + str(backend_port.external_port)})
endpoints.append((port['name'], endpoint))
for port in service.ports:
if port.external_ip is not None:
endpoint = port.url_template.format(**{"ip_port": port.external_ip + ":" + str(port.external_port)})
if zoe_lib.config.get_conf().traefik_zk_ips is None:
endpoint_ext = None
endpoint_ext = '/{}/{}'.format(zoe_lib.config.get_conf().traefik_base_url, port.proxy_key())
endpoints.append((, endpoint, endpoint_ext))
return services_info, endpoints
......@@ -64,7 +64,11 @@
{% if endpoints|length > 0 %}
{% for endp in endpoints|sort %}
{% if endp[2] != None %}
<li><a href="{{ endp[2] }}" target="_blank">{{ endp[0] }}</a> (<a href="{{ endp[1] }}">internal link</a>)</li>
{% else %}
<li><a href="{{ endp[1] }}" target="_blank">{{ endp[0] }}</a> ({{ endp[1] }})</li>
{% endif %}
{% endfor %}
{% else %}
......@@ -99,6 +99,8 @@ def load_configuration(test_conf=None):
argparser.add_argument('--proxy-path', help='Proxy base path', default='')
argparser.add_argument('--reverse-proxy-path', help='Base path in case Zoe is behind a reverse proxy under a path', default='')
argparser.add_argument('--websocket_base', help='Base URL for websocket connections, you need to change it only when running Zoe behind a reverse proxy', default='ws://{{ server_address }}')
argparser.add_argument('--traefik-zk-ips', help='ZooKeeper address storing dynamic configuration for træfik', default=None)
argparser.add_argument('--traefik-base-url', help='Base path used in reverse proxy URLs generated for træfik', default='/zoe/proxy/')
# Scheduler
argparser.add_argument('--scheduler-class', help='Scheduler class to use for scheduling ZApps', choices=['ZoeElasticScheduler'], default='ZoeElasticScheduler')
......@@ -21,7 +21,13 @@ import functools
import psycopg2
from kazoo.client import KazooClient
except ImportError:
KazooClient = None
from zoe_lib.state.base import BaseRecord, BaseTable
import zoe_lib.config
log = logging.getLogger(__name__)
......@@ -110,11 +116,36 @@ class Execution(BaseRecord):
self._status = self.RUNNING_STATUS
self.time_start = datetime.datetime.utcnow()
self.sql_manager.executions.update(, status=self._status, time_start=self.time_start)
# This hackish code is here to support dynamic reverse proxying of web interfaces running in Zoe executions (e.g. jupyter)
# The idea is to use Træfik to do the reverse proxying, configured to use zookeeper to store dynamic configuration
# Zoe updates ZooKeeper whenever an execution runs or is terminated and Træfik craetes or deletes the route automatically
if zoe_lib.config.get_conf().traefik_zk_ips is not None:
zk = KazooClient(hosts=zoe_lib.config.get_conf().traefik_zk_ips)
for service in
for port in service.ports:
endpoint = port.url_template.format(**{"ip_port": port.external_ip + ":" + str(port.external_port)})
traefik_name = 'zoe_exec_{}_{}'.format(,
zk.create('/traefik/backends/{}/servers/server/url'.format(traefik_name), endpoint, makepath=True)
zk.create('/traefik/frontends/{}/routes/path/rule'.format(traefik_name), 'PathPrefix:{}/{}'.format(zoe_lib.config.get_conf().traefik_base_url, port.proxy_key()), makepath=True)
zk.create('/traefik/frontends/{}/backend'.format(traefik_name), traefik_name, makepath=True)
def set_cleaning_up(self):
"""The services of the execution are being terminated."""
self._status = self.CLEANING_UP_STATUS
self.sql_manager.executions.update(, status=self._status)
# See comment in method above
if zoe_lib.config.get_conf().traefik_zk_ips is not None:
zk = KazooClient(hosts=zoe_lib.config.get_conf().traefik_zk_ips)
zk.delete('/traefik/backends/zoe_exec_{}', recursive=True)
zk.delete('/traefik/frontends/zoetest', recursive=True)
def set_terminated(self, reason=None):
"""The execution is not running."""
......@@ -15,9 +15,11 @@
"""Interface to PostgresQL for Zoe state."""
import hashlib
import logging
from zoe_lib.state.base import BaseRecord, BaseTable
import zoe_lib.config
log = logging.getLogger(__name__)
......@@ -62,6 +64,10 @@ class Port(BaseRecord):
self.external_port = None
self.external_ip = None
def proxy_key(self):
"""Return unique identifier that can be used ti generate proxy URLs."""
return hashlib.sha256("{}:{}".format(, zoe_lib.config.get_conf().cookie_secret)).hexdigest()
class PortTable(BaseTable):
"""Abstraction for the port table in the database."""
......@@ -43,6 +43,9 @@ def gen_environment(execution: Execution, service: Service, env_subst_dict: Dict
env_list.append(('ZOE_GID', get_conf().fs_group_id))
env_list.append(('ZOE_USER', execution.owner.username))
if get_conf().traefik_zk_ips is not None:
for port in service.ports:
env_list.append(('REVERSE_PROXY_PATH_{}'.format(port.internal_number), '/{}/{}'.format(get_conf().traefik_base_url, port.proxy_key())))
wk_vol = ZoeFSWorkspace().get(execution.owner)
env_list.append(('ZOE_WORKSPACE', wk_vol.mount_point))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment