部署场景#
为了将您的应用程序变成用户友好的服务,您需要部署您的工作。本节探讨了部署的各个方面。
独立 Bokeh 服务器#
您可以让 Bokeh 服务器在网络上运行,以便用户直接与您的应用程序交互。对于本地网络部署来说,这可能是一个简单的解决方案,前提是运行服务器的硬件功能能够满足您的应用程序需求和预期的用户数量。
但是,如果您有身份验证、扩展或正常运行时间的要求,则需要考虑更复杂的部署配置。
将 Bokeh 服务器集成到其他 Web 服务中#
Bokeh 服务器经常用于创建嵌入在更大父应用程序中的绘图和仪表板。例如,在金融领域,这可能涉及将基于 Bokeh 的趋势线(绘制一段时间内的账户余额)集成到基于 Web 的交易平台中。在供应链环境中,可以将 Bokeh 视图集成到现有的库存管理系统中,以交互方式监控商店的物品供应。
为了满足此用例,bokeh.embed
模块提供了 server_document()
和 server_session()
方法。有关其用法的详细讨论以及示例,请参阅 Bokeh 应用程序。简而言之,这些方法返回 HTML 脚本标签的文本,该标签从 Bokeh 服务器加载视图,并将视图添加到放置脚本标签的任何页面的 DOM 中。
如果要与之集成的父服务不是基于 Python 的,您仍然可以通过 server_document
/ server_session
方法与 Bokeh 集成。但是,您需要通过调用一个小型、长期存在的 Python 脚本(通过任何标准的 IPC 形式返回这些方法的文本内容)来实现这一点。
要允许您的父应用程序显示嵌入的 Bokeh 视图,您必须将父应用程序配置为允许对 Bokeh 服务器实例进行跨源请求。这可以通过将 Bokeh 服务器的公共主机名和端口号添加到父服务的 script-src
和 connect-src
指令中来实现,方法是使用 HTTP(S) 和 WS(S) 协议。配置 CSP 的确切过程将取决于父服务构建所使用的工具包或框架,因此请参考该软件的具体文档。作为最后的手段,也可以通过反向代理覆盖 CSP 标头。
警告
在通过 HTTPS 运行父服务的同时,不要通过 HTTP 运行 Bokeh 服务器,反之亦然。Bokeh 的加载程序代码通过 window.location.protocol
在客户端确定加载资源的协议,因此,如果父服务的协议与 Bokeh 服务器实例的协议不匹配,则加载程序脚本对 Bokeh 服务器的请求将失败。
如果您的父应用程序是基于 Python 的,并且您不介意将 Bokeh 服务器应用程序紧密集成到父应用程序的代码库中,Bokeh 还支持通过父应用程序启动的线程运行其底层的 Tornado Web 服务器。实际示例链接在 Bokeh 服务器 API 下。此方法仍然需要使用 server_document
或 server_session
,但也可以简化 CSP 配置以及 Bokeh 服务器应用程序的部署。
SSH 隧道#
要在访问受限的主机上运行 Bokeh 服务器的独立实例,请使用 SSH “隧道”连接到服务器。
在最简单的场景中,用户从另一个位置(例如没有中间机器的笔记本电脑)访问 Bokeh 服务器。
照常在 **远程主机** 上运行服务器。
bokeh serve
接下来,在 **本地机器** 上发出以下命令以建立到远程主机的 SSH 隧道
ssh -NfL localhost:5006:localhost:5006 [email protected]
将 user 替换为远程主机上的用户名,将 remote.host 替换为主机 Bokeh 服务器的系统的主机名或 IP 地址。远程系统可能会提示您输入登录凭据。连接后,您将能够导航到 localhost:5006
,就像 Bokeh 服务器在本地机器上运行一样。
稍微复杂一点的场景涉及服务器和本地机器之间的网关。在这种情况下,必须从服务器到网关建立一个反向隧道,另一个隧道连接网关和本地机器。
在将运行 Bokeh 服务器的 **远程主机** 上发出以下命令
nohup bokeh server &
ssh -NfR 5006:localhost:5006 [email protected]
将 user 替换为网关上的用户名,将 gateway.host 替换为网关的主机名或 IP 地址。网关可能会提示您输入登录凭据。
要设置本地机器和网关之间的隧道,请在 **本地机器** 上运行以下命令
ssh -NfL localhost:5006:localhost:5006 [email protected]
同样,将 user 替换为网关上的用户名,将 gateway.host 替换为网关的主机名或 IP 地址。
您现在应该能够通过导航到 localhost:5006
从本地机器访问 Bokeh 服务器。您甚至可以设置从在本地机器上运行的 Jupyter Notebook 建立的客户端连接。
注意
我们打算扩展本节,为其他工具和配置提供更多指导。如果您有其他 Web 部署场景的经验并希望在此处贡献您的知识,请通过 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 连接。有关详细信息,请参阅下一节。
基本反向代理设置#
要向通用互联网提供 Web 应用程序,您可能希望将您的应用程序托管在内部网络上,并通过某些专用的 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 资源。或者,您可以在部署期间将资源复制到全局静态目录。
为了跨进程传递 cookie 和标头,Bokeh 可能会将这些信息包含在 JSON Web 令牌中,并通过 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-Ip
、X-Forwarded-For
、X-Scheme
和 X-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 连接到 foo.com
代理到在 http://127.0.0.1:5100
上内部运行的 Bokeh 服务器。
负载均衡#
Bokeh 服务器在设计上是可扩展的。如果您需要更多容量,只需运行更多服务器即可。在这种情况下,您通常希望在负载均衡器后面运行所有 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_balancer
和 rewrite
模块。
为 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
,该函数可以检查 cookie 或请求标头以确定经过身份验证的用户。如果没有经过身份验证的用户,这些函数应返回 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 必须根据请求、cookie 或其他因素而变化的情况下很有用。在定义 get_url_function
时,您也可以指定 LoginHandler
。
要定义注销用户的端点,您还可以使用可选的 logout_url
和 LogoutHandler
参数,类似于登录选项。
如果您不提供身份验证模块,则配置将不需要任何身份验证即可访问 Bokeh 服务器端点。
警告
配置将执行身份验证模块的内容。
安全性#
默认情况下,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#
默认情况下,即使您没有提供会话 ID,Bokeh 服务器也会自动为来自允许的 WebSocket 来源的所有新请求创建新会话。
在将 Bokeh 应用程序嵌入另一个 Web 应用程序(如 Flask 或 Django)时,请确保**只有**您的 Web 应用程序能够生成对 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='https://127.0.0.1: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 传输到客户端浏览器。
扩展服务器#
您可以使用num-procs选项分叉多个服务器进程。例如,运行以下命令以分叉 3 个进程
bokeh serve --num-procs 3
请注意,分叉操作发生在底层的 Tornado 服务器中。有关更多信息,请参阅Tornado 文档。