【问题标题】:Bokeh Flask Deployment on Google Cloud在 Google Cloud 上部署 Bokeh Flask
【发布时间】:2019-08-06 18:09:54
【问题描述】:

我正在尝试通过 Flask 将我的 Bokeh Dashboard 部署到 Google Cloud。我正在尝试将 Bokeh Dashboard 嵌入到 Flask 网站中,但无法将其从 localhost 中移除以正确部署它。我一直在寻找一个规范的例子,但还没有看到一些简单的东西,以便我可以推断出一个更复杂的系统。

我的 git hub 存储库的当前文件结构类似于,

app.yaml
requirements.txt
bokeh-sliders.py
hello.py
templates/
     hello.html

我的 bokeh-sliders.py 文件是

import numpy as np

from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider, TextInput
from bokeh.plotting import figure

# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))


# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
              tools="crosshair,pan,reset,save,wheel_zoom",
              x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])

plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)


# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1)


# Set up callbacks
def update_title(attrname, old, new):
    plot.title.text = text.value

text.on_change('value', update_title)

def update_data(attrname, old, new):

    # Get the current slider values
    a = amplitude.value
    b = offset.value
    w = phase.value
    k = freq.value

    # Generate the new curve
    x = np.linspace(0, 4*np.pi, N)
    y = a*np.sin(k*x + w) + b

    source.data = dict(x=x, y=y)

for w in [offset, amplitude, phase, freq]:
    w.on_change('value', update_data)


# Set up layouts and add to document
inputs = widgetbox(text, offset, amplitude, phase, freq)

curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"

我的 hello.py 文件是,

from flask import Flask, flash, redirect, render_template, request, session, abort
from bokeh.embed import server_document

app = Flask(__name__)

@app.route("/")
def hello():
    script=server_document("http://localhost:5006/bokeh-sliders")
    print(script)
    return render_template('hello.html',bokS=script)

if __name__ == "__main__":
    app.run()

我的嵌套 hello.html 文件是,

<html>
<head>
    <title>Website</title>
<style>
@import url(http://fonts.googleapis.com/css?family=Amatic+SC:700);
body{
    text-align: center;
}
h1{
    font-family: 'Amatic SC', cursive;
    font-weight: normal;
    color: #8ac640;
    font-size: 2.5em;
}
</style>

</head>
<body>
 <p>Flask embedding Bokeh test</p>
{{ bokS|indent(4)|safe }}

</body>
</html>

我知道这可能是 MWE 的一些代码,但这是我迄今为止所做的。还有一个app.yaml文件和一个requirements.txt文件但是不知道有没有必要回答这个问题。

如果我运行python hello.py,那么我可以查看交互式文档,但如果我尝试部署应用程序,则会收到 502 Bad Gateway 错误。关于通过server_document 正确部署散景仪表板,我是否遗漏了什么?

编辑:

我使用bokeh serve bokeh-sliders.py --allow-websocket-origin=*时的shell响应是,

(hello_world) brycechudomelka@cloudshell:~/mlcdashboard (mlcdashboard)$ bokeh serve bokeh-sliders.py --allow-websocket-origin=*
2019-08-06 12:53:31,636 Starting Bokeh server version 1.3.2 (running on Tornado 6.0.3)
2019-08-06 12:53:31,638 Host wildcard '*' will allow connections originating from multiple (or possibly all) hostnames or IPs. Use non-wildcard values to restrict access explicitly
2019-08-06 12:53:31,642 Bokeh app running at: http://localhost:5006/bokeh-sliders
2019-08-06 12:53:31,643 Starting Bokeh server with process id: 421
2019-08-06 12:55:38,205 302 GET /?authuser=0 (127.0.0.1) 1.20ms
2019-08-06 12:55:38,694 200 GET /bokeh-sliders (127.0.0.1) 332.15ms
2019-08-06 12:55:39,369 404 GET /favicon.ico (127.0.0.1) 0.91ms
2019-08-06 12:55:39,796 101 GET /bokeh-sliders/ws?bokeh-protocol-version=1.0&bokeh-session-id=70lK44usV1edkGRZmGWWpKMVn3DxhOsUlM5xSqqw6p5p (127.0.0.1) 1.38ms
2019-08-06 12:55:39,797 WebSocket connection opened
2019-08-06 12:55:39,798 ServerConnection created

因此,Bokeh Serve 可以工作,但未嵌入,因此无法部署。

【问题讨论】:

  • 您是否将使用--allow-websocket-origin 嵌入Bokeh 应用程序的域列入白名单? GCP 是否配置为代理/转发 websocket?当浏览器尝试连接时,Bokeh 服务器的控制台输出是什么?尝试连接时浏览器中的 JS 控制台输出是什么?
  • 我将此作为参考,gist.github.com/Wildcarde/6841f00f0a0cd52ade09964a0fdb5684,并使用--allow-websocket-origin=*
  • 另外,我编辑了我的帖子以包含您询问的信息。
  • 这些是真实部署尝试的日志吗?我问的是在实际部署中会发生什么。究竟是什么/在哪里报告 502?即前面是否有 nginx 代理或类似的代理?它的日志显示什么?这似乎是配置问题,而不是代码问题,但这里没有足够的信息来推测
  • 我刚刚说过:nginx 日志。我不使用 GCP,所以我无法帮助您弄清楚如何获取它们。但是您看到的实际 502 来自 nginx,因此找到整体答案意味着了解 nginx 为何返回 502。

标签: python flask google-cloud-platform bokeh


【解决方案1】:

我推断您正在尝试在 App Engine 中部署此应用程序。

请参考之前的question 了解更多关于在 App Engine 上使用 Bokeh 和 Numpy 的限制。

另请参阅此文档以了解有关 App 引擎标准中的 limitations on using sockets 的更多信息。

此文档可详细了解 App Engine Flex 的 beta 功能以允许 Websockets connections

总之,由于持久连接的限制,在 App Engine 上部署需要套接字连接的应用程序可能会很棘手。您最好使用 Compute Engine 实例来为您的应用程序提供服务(或 Kubernetes,具体取决于您的用例,这里有一个关于如何创建 custom interactive dashboards with Bokeh and BigQuery 的示例)

【讨论】:

  • 感谢您的回复。我已经阅读了 Bokeh + BigQuery 教程,但它不起作用。我按照教程进行操作,但似乎无法正常工作。
  • 不能正常工作是什么意思?你卡在某个步骤了吗?您是否看到任何错误消息?
【解决方案2】:

为了让您部署到 Google Cloud Platform,您必须创建一个自定义 flex 环境。这意味着必须包含 Dockerfile。这是一个 Git 存储库的示例,它将

这是 Dockerfile。

FROM continuumio/miniconda
ENV BK_VERSION=1.4.0 ENV PY_VERSION=3.7 ENV NUM_PROCS=4 ENV BOKEH_RESOURCES=cdn
RUN apt-get install git bash    
RUN git clone https://github.com/BryceWayne/CMCDashboard.git
RUN cd CMCDashboard 
RUN conda install --yes --quiet python=${PY_VERSION} pyyaml jinja2 bokeh=${BK_VERSION} numpy "nodejs>=8.8" pandas requests scikit-learn matplotlib lxml
RUN conda install -c anaconda lxml
RUN conda clean -ay
EXPOSE 8080
CMD bokeh serve --port 8080 \
    --allow-websocket-origin="*" \
    --num-procs=${NUM_PROCS} \
    CMCDashboard/dashboard.py

这里是 app.yaml 文件。

runtime: custom
env: flex

而且dashboard和上面是一样的,所以我们可以参考sliders.py。

import numpy as np

from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider, TextInput
from bokeh.plotting import figure

# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))


# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
              tools="crosshair,pan,reset,save,wheel_zoom",
              x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])

plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)


# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1)


# Set up callbacks
def update_title(attrname, old, new):
    plot.title.text = text.value

text.on_change('value', update_title)

def update_data(attrname, old, new):

    # Get the current slider values
    a = amplitude.value
    b = offset.value
    w = phase.value
    k = freq.value

    # Generate the new curve
    x = np.linspace(0, 4*np.pi, N)
    y = a*np.sin(k*x + w) + b

    source.data = dict(x=x, y=y)

for w in [offset, amplitude, phase, freq]:
    w.on_change('value', update_data)


# Set up layouts and add to document
inputs = widgetbox(text, offset, amplitude, phase, freq)

curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"

【讨论】:

    猜你喜欢
    • 2023-03-03
    • 2021-03-16
    • 2019-04-24
    • 1970-01-01
    • 1970-01-01
    • 2020-11-14
    • 2021-07-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多