部署场景#

为了使您的应用程序成为一个用户友好的服务,您必须部署您的工作。本小节探讨了部署的各个方面。

独立的Bokeh服务器#

您可以让Bokeh服务器在网络上运行,以便用户直接与您的应用程序进行交互。对于本地网络部署来说,这可能是一个简单的解决方案,前提是运行服务器的硬件能力符合您的应用程序需求和预期的用户数量。

然而,如果您有身份验证、扩展或正常运行时间要求,您将需要考虑更复杂的部署配置。

将Bokeh服务器集成到其他Web服务中#

Bokeh服务器经常用于创建嵌入在更大的父应用程序中的图表和仪表板。例如,在金融环境中,这可能涉及将基于Bokeh的趋势线(用于绘制账户余额随时间变化的图表)集成到基于网络的交易平台中。在供应链环境中,Bokeh视图可以集成到现有的库存管理系统中,以交互式监控商店的物品供应情况。

为了满足这个用例,bokeh.embed 模块提供了 server_document()server_session() 方法。有关它们的使用和示例的详细讨论,请参阅 Bokeh 应用程序。简而言之,这些方法返回一个 HTML 脚本标签的文本,该标签从 Bokeh 服务器加载视图,并将视图添加到放置该脚本标签的任何页面的 DOM 中。

如果您希望集成的父服务不是基于Python的,您仍然可以通过server_document / server_session方法与Bokeh集成。然而,您需要通过调用一个小的、长期运行的Python脚本来实现这一点,该脚本通过任何标准形式的IPC返回这些方法的文本内容。

为了让您的父应用程序显示嵌入的Bokeh视图,您必须配置父应用程序以允许跨源请求到您的Bokeh服务器实例。这是通过将Bokeh服务器的公共主机名和端口号添加到父服务的内容安全策略(CSP)的script-srcconnect-src指令中来实现的,适用于HTTP(S)和WS(S)协议。配置CSP的确切步骤将取决于父服务所使用的工具包或框架,因此请参考该软件的具体文档。作为最后的手段,CSP头也可以通过反向代理进行覆盖。

警告

在父服务通过HTTPS运行时,不要通过HTTP运行Bokeh服务器,反之亦然。Bokeh的加载器代码通过window.location.protocol确定在客户端加载资源的协议,因此如果父服务的协议与Bokeh服务器实例的协议不匹配,加载器脚本对Bokeh服务器的请求将失败。

如果你的父应用程序是基于Python的,并且你不介意将你的Bokeh服务器应用程序紧密集成到父应用程序的代码库中,Bokeh还支持通过父应用程序启动的线程运行其底层的Tornado Web服务器。实际示例链接在Bokeh服务器API下。这种方法仍然需要使用server_documentserver_session,但可能会简化CSP配置以及Bokeh服务器应用程序的部署。

SSH隧道#

要在访问受限的主机上运行Bokeh服务器的独立实例,请使用SSH“隧道”连接到服务器。

在最简单的场景中,用户从另一个位置访问Bokeh服务器,例如没有中间机器的笔记本电脑。

像往常一样在远程主机上运行服务器。

bokeh serve

接下来,在本地机器上发出以下命令以建立到远程主机的SSH隧道:

ssh -NfL localhost:5006:localhost:5006 user@remote.host

user替换为远程主机上的用户名,将remote.host替换为托管Bokeh服务器的系统的主机名或IP地址。远程系统可能会提示您输入登录凭据。连接后,您将能够导航到localhost:5006,就像Bokeh服务器在本地机器上运行一样。

一个稍微复杂一些的场景涉及服务器和本地机器之间的网关。在这种情况下,必须从服务器到网关建立一个反向隧道,同时另一个隧道连接网关和本地机器。

在运行Bokeh服务器的远程主机上发出以下命令:

nohup bokeh server &
ssh -NfR 5006:localhost:5006 user@gateway.host

user替换为你在网关上的用户名,并将gateway.host替换为网关的主机名或IP地址。网关可能会提示你输入登录凭据。

要在本地机器和网关之间设置隧道,请在本地机器上运行以下命令:

ssh -NfL localhost:5006:localhost:5006 user@gateway.host

再次,将user替换为你在网关上的用户名,并将gateway.host替换为网关的主机名或IP地址。

您现在应该能够通过导航到localhost:5006从本地机器访问Bokeh服务器。您甚至可以从在本地机器上运行的Jupyter笔记本设置客户端连接。

注意

我们打算扩展这一部分,为其他工具和配置提供更多指导。如果您有其他网络部署场景的经验,并希望在此贡献您的知识,请联系我们 https://discourse.bokeh.org

SSL终止#

您可以配置Bokeh服务器以终止SSL连接并直接提供安全的HTTPS和WSS会话。为此,您需要提供--ssl-certfile参数,其值为包含证书以及建立证书真实性所需的任意数量的CA证书的单个PEM文件的路径。

bokeh serve --ssl-certfile /path/to/cert.pem

你也可以通过设置环境变量 BOKEH_SSL_CERTFILE 来提供证书文件的路径。

如果私钥是单独存储的,您可以通过设置--ssl-keyfile命令行参数或设置BOKEH_SSL_KEYFILE环境变量来提供其位置。如果私钥需要密码,请通过设置BOKEH_SSL_PASSWORD环境变量来提供密码。

或者,您可能希望在代理后面运行一个Bokeh服务器,并让代理终止SSL连接。详情请参阅下一小节。

基本反向代理设置#

为了向公众互联网提供网络应用程序,您可能希望将应用程序托管在内部网络上,并通过某些专用的HTTP服务器代理连接。本小节提供了如何配置一些常见反向代理的指导。

Nginx#

一个非常常见的HTTP和反向代理服务器是Nginx。这里是一个server配置段的示例:

server {
    listen 80 default_server;
    server_name _;

    access_log  /tmp/bokeh.access.log;
    error_log   /tmp/bokeh.error.log debug;

    location / {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

上述的 server 块设置了 Nginx 以将传入的连接代理到端口 80 上的 127.0.0.1 到端口 5100 上的 127.0.0.1。要在此配置中工作,您需要使用一些命令行选项来配置 Bokeh 服务器。特别是,使用 --port 让 Bokeh 服务器监听端口 5100。

bokeh serve myapp.py --port 5100

上面的基本服务器块没有为静态资源(如Bokeh JS和CSS文件)配置任何特殊处理。这意味着Bokeh服务器直接提供这些文件。

虽然这是一个可行的选项,但它要求Bokeh服务器做额外的工作,这些工作最好由Nginx来处理。要使用Nginx提供静态资源,请将以下子块添加到上面的代码中,并将/path/to/bokeh/server/static替换为您的静态资源路径:

location /static {
    alias /path/to/bokeh/server/static;
}

确保运行Nginx的账户有权限访问Bokeh资源。或者,您可以在部署期间将资源复制到全局静态目录。

为了跨进程传递cookies和头部信息,Bokeh可能会将这些信息包含在JSON网络令牌中,并通过WebSocket发送。在某些情况下,这个令牌可能会变得非常大,导致Nginx丢弃请求。你可能需要通过覆盖Nginx的默认设置large_client_header_buffers来解决这个问题:

large_client_header_buffers 4 24k;

Apache#

另一个常见的HTTP服务器和代理是Apache。以下是一个在Apache后面运行的Bokeh服务器的配置示例:

<VirtualHost *:80>
    ServerName localhost

    CustomLog "/path/to/logs/access_log" combined
    ErrorLog "/path/to/logs/error_log"

    ProxyPreserveHost On
    ProxyPass /myapp/ws ws://127.0.0.1:5100/myapp/ws
    ProxyPassReverse /myapp/ws ws://127.0.0.1:5100/myapp/ws

    ProxyPass /myapp http://127.0.0.1:5100/myapp
    ProxyPassReverse /myapp http://127.0.0.1:5100/myapp

    <Directory />
        Require all granted
        Options -Indexes
    </Directory>

    Alias /static /path/to/bokeh/server/static
    <Directory /path/to/bokeh/server/static>
        # directives to effect the static directory
        Options +Indexes
    </Directory>

</VirtualHost>

上述配置将/static别名为Bokeh静态资源目录的位置。然而,也可以(可能更可取)将静态资源复制到您为Apache配置的任何标准静态文件位置,作为部署的一部分。

您可能还需要为上述配置启用一些模块:

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
apache2ctl restart

根据您的系统,您可能需要使用sudo来运行上述命令。

和之前一样,使用以下命令运行Bokeh服务器:

bokeh serve myapp.py --port 5100

使用代理的Unix套接字#

在某些情况下,您可能希望使用Unix套接字而不是WebSocket将代理的Bokeh服务器连接到代理。您可以将Bokeh服务器绑定到Unix套接字,并使用Nginx或Apache代理到Unix域套接字。

注意

在Windows上不支持绑定到Unix套接字。

bokeh serve --unix-socket /path/to/socket.sock

一个Nginx配置可能看起来像这个例子:

upstream myserver {
    server unix:/path/to/socket.sock;
}

server {
    listen 80 default_server;
    server_name _;

    access_log  /tmp/bokeh.access.log;
    error_log   /tmp/bokeh.error.log debug;

    location / {
        proxy_pass http://myserver;
    }

}

请注意,Bokeh服务器的网络选项,如websocket来源和SSL配置,与Unix套接字不兼容。这些限制需要由代理在前端强制执行。

如果有多个用户共享主机,您可以限制套接字上的文件权限以限制对代理服务器的访问。

使用Nginx和SSL进行反向代理#

要在SSL终止的Nginx代理后面部署Bokeh服务器,您需要进行一些额外的自定义配置。特别是,您必须使用--use-xheaders标志来配置Bokeh服务器。

bokeh serve myapp.py --port 5100 --use-xheaders

--use-xheaders 标志使 Bokeh 在所有请求中使用 X-Real-IpX-Forwarded-ForX-SchemeX-Forwarded-Proto 头信息来覆盖远程 IP 和 URI 方案/协议,当这些头信息可用时。

您还需要自定义Nginx。特别是,您必须配置Nginx以发送X-Forwarded-Proto标头并使用SSL终止。可选地,您可能希望将所有HTTP流量重定向到HTTPS。

此配置的完整详细信息,例如如何以及在哪里安装SSL证书和密钥,因平台而异,以下仅是一个参考的nginx.conf设置:

# redirect HTTP traffic to HTTPS (optional)
server {
    listen      80;
    server_name foo.com;
    return      301 https://$server_name$request_uri;
}

server {
    listen      443 default_server;
    server_name foo.com;

    # adds Strict-Transport-Security to prevent man-in-the-middle attacks
    add_header Strict-Transport-Security "max-age=31536000";

    ssl on;

    # SSL installation details vary by platform
    ssl_certificate /etc/ssl/certs/my-ssl-bundle.crt;
    ssl_certificate_key /etc/ssl/private/my_ssl.key;

    # enables all versions of TLS, but not the deprecated SSLv2 or v3
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # disables all weak ciphers
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

此配置将把所有传入的HTTPS连接代理到内部运行的Bokeh服务器上,地址为http://127.0.0.1:5100

负载均衡#

Bokeh服务器在设计上是可扩展的。如果您需要更多的容量,您可以简单地运行额外的服务器。在这种情况下,您通常希望在所有Bokeh服务器实例后面运行一个负载均衡器,以便新连接可以分配到各个服务器之间。

../../../_images/bokeh_serve_scale.svg

Bokeh 服务器是水平可扩展的。要增加更多容量,您可以在负载均衡器后面运行更多服务器。#

你可以根据需要运行任意数量的Bokeh服务器。以下示例基于在三台不同端口上运行的三台Bokeh服务器的设置:

bokeh serve myapp.py --port 5100
bokeh serve myapp.py --port 5101
bokeh serve myapp.py --port 5102

以下部分基于此设置提出了基本配置。有关更详细的信息,请参阅 Nginx负载均衡器文档Apache代理均衡器模块 文档。例如,有不同的策略可用于定义如何将传入连接 分配到服务器实例之间。

Nginx#

首先,你需要在Nginx配置中添加一个upstream节。 这通常放在server节的上方,看起来像下面这样:

upstream myapp {
    least_conn;            # Use the least-connected strategy
    server 127.0.0.1:5100;
    server 127.0.0.1:5101;
    server 127.0.0.1:5102;
}

其余的配置使用名称 myapp 来引用上述的 upstream 部分,该部分列出了三个 Bokeh 服务器实例的内部连接信息。

接下来,在Bokeh服务器的location部分中,将proxy_pass值更改为引用上面的upstream部分。下面的代码使用了proxy_pass http://myapp;

server {

    location / {
        proxy_pass http://myapp;

        # all other settings unchanged
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
    }

}

Apache#

首先,确保你已经启用了proxy_balancerrewrite模块。

为http和websocket协议添加负载均衡器:

<Proxy "balancer://myapp_http">
    BalancerMember "http://127.0.0.1:5100/myapp"
    BalancerMember "http://127.0.0.1:5101/myapp"
    BalancerMember "http://127.0.0.1:5102/myapp"
    ProxySet lbmethod=bybusyness
</Proxy>

<Proxy "balancer://myapp_ws">
    BalancerMember "ws://127.0.0.1:5100/myapp"
    BalancerMember "ws://127.0.0.1:5101/myapp"
    BalancerMember "ws://127.0.0.1:5102/myapp"
    ProxySet lbmethod=bybusyness
</Proxy>

bybusyness 负载均衡方法确保将传入连接分配给当时活动连接最少的实例。它应该比其他可用算法(如byrequests)产生更好的结果。您可能需要启用mod_lbmethod_bybusyness

最后,您可以将websocket和http请求代理到相应的负载均衡器:

RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /myapp(.*)    balancer://myapp_ws$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /myapp(.*)    balancer://myapp_http$1 [P,L]

认证#

Bokeh 服务器本身没有任何认证或授权的功能。但是,你可以配置 Bokeh 服务器使用一个“认证提供者”,该提供者可以接入 Tornado 的底层功能。有关背景信息,请参阅 Tornado 文档中的 认证与安全。本节的其余部分假设你对这些材料有一定的了解。

认证模块#

您可以配置Bokeh服务器,仅允许经过身份验证的用户连接。为此,请在命令行上提供实现必要功能的模块路径。

bokeh serve --auth-module=/path/to/auth.py

或者,您可以将BOKEH_AUTH_MODULE环境变量设置为此路径。

模块必须包含以下两个函数中的一个,该函数返回当前用户(或None):

def get_user(request_handler):
    pass

async def get_user_async(request_handler):
    pass

该模块将函数传递给Tornado的RequestHandler,它可以检查cookies或请求头以确定认证用户。如果没有认证用户,这些函数应返回None

此外,模块必须通过包含以下内容之一来指定未认证用户的重定向位置:

  • 一个模块属性 login_url 和(可选的)一个 LoginHandler

  • 一个用于 get_login_url 的函数定义

login_url = "..."

class LoginHandler(RequestHandler):
    pass

def get_login_url(request_handler):
    pass

如果模块提供了一个相对的login_url,它还可以提供一个可选的LoginHandler类,Bokeh服务器将自动将其纳入。

get_login_url 函数在登录 URL 必须根据请求、cookies 或其他因素而变化的情况下非常有用。您还可以在定义 get_url_function 时指定一个 LoginHandler

要定义一个用于用户注销的端点,你也可以使用可选的 logout_urlLogoutHandler 参数,类似于登录选项。

如果您不提供身份验证模块,配置将不需要任何身份验证即可访问Bokeh服务器端点。

警告

配置执行认证模块的内容。

安全Cookies#

如果你想在你的认证模块中使用Tornado的set_secure_cookieget_secure_cookie函数,你必须设置一个cookie密钥。为此,请使用BOKEH_COOKIE_SECRET环境变量。

export BOKEH_COOKIE_SECRET=<cookie secret value>

值应该是一个长的、随机的字节序列。

安全#

默认情况下,Bokeh服务器将接受任何具有允许的WebSocket来源的传入连接。如果您指定了一个会话ID,并且服务器上已经存在具有该ID的会话,服务器将连接到该会话。否则,服务器将自动创建并使用一个新的会话。

如果您在一个大型组织内部或向更广泛的互联网部署嵌入式Bokeh应用程序,您可能希望限制谁可以启动会话,以及从何处启动。Bokeh允许您管理会话创建权限。

WebSocket 来源#

当Bokeh服务器接收到HTTP请求时,它会立即返回一个脚本,该脚本会启动WebSocket连接。所有后续的通信都通过WebSocket进行。

为了减少跨站滥用的风险,Bokeh服务器只会从明确允许的来源发起WebSocket连接。对于不在允许列表中的Origin头部的请求,将生成HTTP 403错误响应。

默认情况下,只允许 localhost:5006,使得以下两个调用相同:

bokeh serve --show myapp.py

bokeh serve --show --allow-websocket-origin=localhost:5006 myapp.py

这两个操作都会打开您的默认浏览器,访问默认的应用程序URL localhost:5006,并且由于localhost:5006在允许的 WebSocket来源列表中,Bokeh服务器会创建并显示一个新的会话。

当你使用server_document()server_session()将Bokeh服务器嵌入到另一个网页中时,请求Bokeh服务器的Origin头 是托管你Bokeh内容的页面的URL。

例如,如果用户导航到您的页面 https://acme.com/products, 浏览器报告的来源头将是 acme.com。在这种情况下, 您通常会限制 Bokeh 服务器仅处理来自 acme.com 页面的请求,防止其他页面在您不知情的情况下嵌入您的 Bokeh 应用。

你可以通过如下方式设置--allow-websocket-origin命令行参数来实现:

bokeh serve --show --allow-websocket-origin=acme.com myapp.py

这将阻止其他站点在他们的页面中嵌入您的Bokeh应用程序,因为查看这些页面的用户的请求将报告与acme.com不同的来源,导致Bokeh服务器拒绝它们。

警告

请记住,这只能防止其他网页在您不知情的情况下嵌入您的Bokeh应用程序。

如果您需要允许多个来源,您可以在命令行中传递多个--allow-websocket-origin实例。

您还可以配置Bokeh服务器以允许所有连接,无论来源如何:

bokeh serve --show --allow-websocket-origin='*' myapp.py

此选项仅适用于测试、实验和本地笔记本使用。

签名的会话ID#

默认情况下,Bokeh服务器会自动为所有来自允许的WebSocket来源的新请求创建新会话,即使您没有提供会话ID。

当将Bokeh应用程序嵌入到另一个Web应用程序(如Flask或Django)中时,请确保只有您的Web应用程序能够生成对Bokeh服务器的有效请求,您可以配置Bokeh服务器仅使用加密签名的会话ID创建会话。

首先,使用 bokeh secret 命令创建一个用于签名会话ID的密钥。

export BOKEH_SECRET_KEY=`bokeh secret`

然后在启动Bokeh服务器时将BOKEH_SIGN_SESSIONS设置为yes。 此时,您通常还希望设置允许的WebSocket来源。

BOKEH_SIGN_SESSIONS=yes bokeh serve --allow-websocket-origin=acme.com myapp.py

然后,在您的Web应用程序中,明确使用generate_session_id提供签名的会话ID:

from bokeh.util.token import generate_session_id

script = server_session(url='http://localhost:5006/bkapp',
                        session_id=generate_session_id())
return render_template("embed.html", script=script, template="Flask")

确保为Bokeh服务器和Web应用程序进程(如Flask、Django或您正在使用的任何其他工具)设置相同的BOKEH_SECRET_KEY环境变量。

注意

签名的会话ID用作访问令牌。与任何令牌系统一样,安全性基于保持令牌的机密性。您还应该在终止SSL连接的代理后面运行Bokeh服务器,或者配置Bokeh服务器直接终止SSL。这使您能够安全地将会话ID传输到客户端浏览器。

XSRF cookies#

Bokeh 服务器可以使用 Tornado 的跨站请求伪造保护功能。要启用此功能,请使用 --enable-xsrf-cookies 选项或将环境变量 BOKEH_XSRF_COOKIES 设置为 yes

通过此设置,您需要正确配置所有自定义和登录处理程序上的PUT、POST和DELETE操作,以便它们能够正常工作。通常,这意味着将以下代码添加到所有HTML表单提交模板中:

{% module xsrf_form_html() %}

有关完整详情,请参阅Tornado文档中的XSRF Cookies

扩展服务器#

你可以使用num-procs选项来分叉多个服务器进程。例如,运行以下命令来分叉3个进程:

bokeh serve --num-procs 3

请注意,分叉操作发生在底层的Tornado服务器中。有关更多信息,请参阅Tornado文档