StreamlitのWebアプリをGoogleCloudに構築する
詳しく調べてないけどDockerとかを使ってできるらしいけど、まずはDockerを使わずに環境構築をしたかったので、VMインスタンスを生成して、一つ一つ設定していってみた。
構築したシステムは以下
至ってシンプルなもの
苦労したところ
Streamlitはnginxの設定が一捻りしないといけないみたいでStackOverFlowなどを漁ってChatGPT活用しながら解決
ChatGPT、Claude、Geminiだけでは解決できなかった。
以下はローカルPCで作成したStreamlitで作ったWebアプリをGoogleCloudで実行するまでの手順
手順
Google Cloudプロジェクトの作成
Google Cloud Consoleにログインし、新しいプロジェクトを作成します。
Streamlitサーバ構築 streamlit-server
VMインスタンスの作成
Compute Engine > VMインスタンスに移動し、「インスタンスを作成」ボタンをクリックします。
インスタンスの名前(今回streamlit-serverとした)
リージョン、ゾーンを選択します。
マシンタイプ:プロジェクトの規模に応じて適切なマシンタイプを選択します(例:e2-medium)。
ブートディスク:デフォルトのUbuntu 20.04 LTSを選択します。
streamlit-serverにPython 3.12.4をインストールする
依存関係のインストール
最初に、Pythonをビルドするために必要な依存関係をインストールします。
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
Python 3.12のインストール
次に、Python 3.12をインストールします。
sudo apt install -y python3.12 python3.12-venv python3.12-dev
Python 3.12をデフォルトのPythonバージョンとして使用
インストール後、デフォルトのPythonバージョンとして使用するように設定します。
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 2
れにより、デフォルトのpython3コマンドがPython 3.12を指すようになります。
pipのインストールとアップグレード
Python 3.12にpipをインストールし、必要に応じてアップグレードします。
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3.12 get-pip.py
python3.12 -m pip install --upgrade pip
Python仮想環境の作成
Python 3.12を使用して仮想環境を作成し、Streamlitをインストールします。
python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Webサーバ(Nginx)を作成
1.ナビゲーションメニューからCompute Engineに移動:
2.左上のナビゲーションメニュー(≡)をクリックし、「Compute Engine」 > 「VMインスタンス」を選択します。
インスタンスの作成:
3.「インスタンスを作成」をクリックします。
インスタンスの設定:
名前:nginx-server
リージョンとゾーン:デフォルト
マシンタイプ:e2-medium(必要に応じて変更)
ブートディスク:Ubuntu 20.04 LTSを選択します。
ファイアウォール設定:
「HTTPトラフィックを許可する」と「HTTPSトラフィックを許可する」のチェックボックスをオンにします。
作成:
4.「作成」をクリックして、インスタンスの作成を開始します。
Webサーバ(Nginx)のセットアップ
SSHでWebサーバに接続:
nginx-serverインスタンスの「SSH」ボタンをクリックして、SSHで接続します。
Nginxのインストール:
sudo apt update
sudo apt install nginx
Nginxの設定ファイルの編集:
デフォルトのNginx設定ファイルを編集します
sudo vim /etc/nginx/sites-available/default
server {
listen 80;
server_name ここにnginxの外部IPアドレス;
# HTTPからHTTPSへのリダイレクト
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name ここにnginxの外部IPアドレス;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
location / {
proxy_pass http://ここにstreamlit-serverの内部IPアドレス:8501; # streamlit-serverの内部IPアドレスを使用
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
location /_stcore/stream {
proxy_pass http://ここにstreamlit-serverの内部IPアドレス:8501/_stcore/stream;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
Nginxの設定をテストして再起動
設定が正しいかをテストし、Nginxを再起動します。
sudo nginx -t
sudo systemctl restart nginx
これで、NginxがStreamlitアプリケーションへのリクエストをプロキシするように設定されました。
ファイアーウォールの設定
各VMインスタンスのネットワーク設定に移動し、それぞれのポートを開放する必要があります。具体的には、nginx-serverとstreamlit-serverの両方で適切なポートを開放します。
手順5:ファイアウォールの設定
Google Cloud Consoleでファイアウォールルールを設定する
Google Cloud Consoleにログインします。
ナビゲーションメニュー(左上の≡)から「VPCネットワーク」 > 「ファイアウォール」を選択します。
**「ファイアウォールルールを作成」**をクリックします。Nginx用のファイアウォールルールを作成
ファイアウォールルールの名前を入力します(例:allow-http-https)。
ネットワーク:default(通常はデフォルトのVPCネットワークを使用)。
ターゲットタグ:任意のタグを設定します(例:http-server)。
!!!!ソースIP範囲:0.0.0.0/0(すべてのIPアドレスからのアクセスを許可)。 ここは適宜、変える。!!!!
プロトコルとポート:
指定されたプロトコルとポートを選択し、tcp:80,443を入力します(HTTPとHTTPSトラフィックを許可)。Streamlit用のファイアウォールルールを作成
ファイアウォールルールの名前を入力します(例:allow-streamlit)。
ネットワーク:default。
ターゲットタグ:任意のタグを設定します(例:streamlit-server)。
!!!!ソースIP範囲:0.0.0.0/0(すべてのIPアドレスからのアクセスを許可)。 ここは適宜、変えること!!!!
プロトコルとポート:
指定されたプロトコルとポートを選択し、tcp:8501を入力します(Streamlitのデフォルトポート)。タグの適用
次に、作成したファイアウォールルールを適用するために、各インスタンスに適切なタグを設定します。
Compute Engine > VMインスタンスに移動します。
nginx-serverをクリックして、詳細ページを開きます。
「編集」ボタンをクリックします。
ネットワークタグのセクションで、先ほど設定したターゲットタグ(例:http-server)を追加します。
**「保存」**をクリックします。
同様に、streamlit-serverにもターゲットタグ(例:streamlit-server)を設定して保存します。
Streamlitを起動する
nohup streamlit run main.py &
ブラウザでNginxの外部IPアドレスをいれて接続する
詰まったところ
うまくリダイレクトがいかなかったnginxの設定例
server {
listen 80;
server_name YOUR_EXTERNAL_IP;
location / {
# HTTPからHTTPSへのリダイレクト
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name YOUR_EXTERNAL_IP;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
location / {
proxy_pass http://******:8501; # streamlit-serverの内部IPアドレスを使用
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
動作したものと何が違うのか?
動作したnginxの設定では
proxy_http_versionとWebSocket設定:
動作した設定には、proxy_http_version 1.1やWebSocket用のヘッダー(UpgradeとConnection)の設定が含まれています。これにより、WebSocket接続が正しく処理されます。
/stcore/streamの設定:
動作した設定には、/stcore/streamに対する特定の設定が含まれています。StreamlitはWebSocketを使用するため、これらの設定が必要です。
proxy_read_timeout:
動作した設定では、proxy_read_timeout 86400が追加されています。これにより、長時間の接続が維持されます。
enableCORSとenableXsrfProtectionの設定
これらの設定は、Streamlitアプリケーションのサーバー設定をカスタマイズするためのものです。
enableCORS = false:
Cross-Origin Resource Sharing (CORS)を無効にします。これにより、異なるオリジン(ドメイン、プロトコル、ポート)からのリクエストが許可されるようになります。デプロイされたアプリケーションが異なるオリジンからアクセスされる場合、この設定は重要です。
enableXsrfProtection = false:
XSRF (Cross-Site Request Forgery)保護を無効にします。これはセキュリティ上のリスクを伴いますが、特定の条件下では必要な場合があります。
これらの設定を無効にすることで、StreamlitアプリケーションがNginxのリバースプロキシ設定を通して正しく動作するようになります。特に、異なるオリジンからのリクエストを受け入れる場合や、セッション管理に関連する問題を回避する場合に有効です。
まとめ
動作した設定には、WebSocket接続と長時間接続のための追加設定が含まれており、これが動作の鍵となっています。また、Streamlitの設定でCORSとXSRF保護を無効にすることで、リバースプロキシ環境での動作が改善されました。
その他
練習で作ったものなのでドメインとかは取得しなかったので以下を実施
ドメイン名を使用せずにHTTPS通信を行う場合は自己署名証明書を使用することになります。自己署名証明書を使用すると、ブラウザに「保護されていない通信」と警告が表示されますが、技術的にはHTTPS通信を実現できます。
自己署名証明書を使用したHTTPS設定手順
手順:ディレクトリの作成と証明書の生成
/etc/nginx/sslディレクトリを作成
sudo mkdir -p /etc/nginx/ssl
2.自己署名証明書の生成
NginxのVMインスタンスで自己署名証明書を生成します。
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx-selfsigned.key -out /etc/nginx/ssl/nginx-selfsigned.crt
入力例
Country Name (2 letter code) [AU]: JP
State or Province Name (full name) [Some-State]: Tokyo
Locality Name (eg, city) [Default City]: Tokyo
Organization Name (eg, company) [Default Company Ltd]: MyCompany
Organizational Unit Name (eg, section) []: IT
Common Name (e.g. server FQDN or YOUR name) []: 35.238.112.153
Email Address []: email@example.com
これらの情報を入力した後、自己署名証明書が生成
他、今回使ったコマンド
手順1: Nginxが動作しているか確認
まず、Nginxが正しく動作しているか確認します。
sudo systemctl status nginx
手順2: ssコマンドを使用してリッスンポートを確認
ssコマンドを使用して、Nginxがどのポートでリッスンしているか確認します。
sudo ss -tuln | grep nginx
または、特定のポート(80および443)でリッスンしているか確認します。
sudo ss -tuln | grep 80
sudo ss -tuln | grep 443
手順3: 出力結果を確認
期待される出力は次のようになります:
ポート80でリッスンしているか確認
sudo ss -tuln | grep 80
出力例:
tcp LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 128 [::]:80 [::]:*
ポート443でリッスンしているか確認
sudo ss -tuln | grep 443
出力例:
tcp LISTEN 0 128 0.0.0.0:443 0.0.0.0:*
tcp LISTEN 0 128 [::]:443 [::]:*
手順4: リッスンしていない場合の対処
もしNginxが意図したポートでリッスンしていない場合、Nginxの設定ファイルを再確認し、修正する必要があります。
Nginx設定詳細
proxy_http_version 1.1;
HTTP/1.1を使用することを指定しています。
HTTP/1.1はWebSocketをサポートしており、Streamlitはリアルタイムの更新にWebSocketを使用します。
proxy_set_header Upgrade $http_upgrade;
クライアントがWebSocketへのアップグレードを要求した場合、この要求をバックエンドサーバー(Streamlit)に転送します。
WebSocketの接続確立に必要です。
proxy_set_header Connection "upgrade";
接続をアップグレードする必要があることをバックエンドサーバーに伝えます。
これもWebSocket接続の確立に必須です。
これらの設定が重要な理由:
リアルタイム更新:
Streamlitはリアルタイムで更新を行うためにWebSocketを使用します。
これらの設定がないと、WebSocket接続が確立できず、アプリケーションの動的な部分が機能しない可能性があります。
長時間接続:
WebSocketは長時間の接続を維持します。これらの設定により、NginxがWebSocket接続を適切に処理できます。
双方向通信:
WebSocketは双方向通信を可能にします。これにより、サーバーからクライアントへのプッシュ通知などが可能になります。
効率的なデータ転送:
WebSocketは一度接続が確立されると、HTTPリクエストのオーバーヘッドなしでデータを転送できます。
ブラウザの互換性:
最新のブラウザはWebSocketをサポートしており、これらの設定によりブラウザとサーバー間の互換性が確保されます。
これらの設定がない場合、Streamlitアプリケーションは正常に動作しないか、機能が制限される可能性があります。特に、リアルタイムの更新やインタラクティブな要素が正しく機能しない可能性があります。
したがって、これらの設定は、StreamlitアプリケーションをNginxの背後で正しく動作させるために不可欠です。
HTTP/1.1とWebSocketについて
HTTP/1.1を指定しない場合:
もしproxy_http_version 1.1;を指定しない場合、デフォルトでHTTP/1.0が使用されます。HTTP/1.0には以下の制限があります:
持続的接続(keep-alive)をサポートしていません。
ホストヘッダーがありません。
WebSocketをサポートしていません。
WebSocketとは:
WebSocketは、クライアント(通常はブラウザ)とサーバー間で全二重通信を可能にするプロトコルです。主な特徴は:
双方向通信:サーバーからクライアントへのプッシュ通知が可能。
リアルタイム性:低遅延でデータを送受信できる。
効率的:一度接続が確立されると、追加のHTTPリクエストは不要。
WebSocketをサポートすることで起こること:
リアルタイム更新:ページ全体をリロードせずに、部分的な更新が可能。
インタラクティブ性の向上:ユーザーの操作に即座に反応できる。
サーバープッシュ:サーバーから自発的にデータを送信できる。
効率的なデータ転送:HTTPヘッダーのオーバーヘッドが減少。
WebSocketをサポートしない場合:
ポーリング:定期的にサーバーに新しい情報を問い合わせる必要がある。
高いレイテンシ:リアルタイム性が低下する。
サーバー負荷の増加:頻繁なHTTPリクエストによりサーバーの負荷が高まる。
ユーザーエクスペリエンスの低下:更新に遅延が生じ、スムーズさが失われる。
Streamlitの場合、WebSocketは特に重要です。Streamlitは動的なデータ更新、インタラクティブなウィジェット、リアルタイムのグラフ更新などの機能に大きく依存しています。WebSocketをサポートしないと、これらの機能が正常に動作しない、または非常に遅くなる可能性があります。
例えば、Streamlitアプリでスライダーを動かすと、グラフがリアルタイムで更新されますが、これはWebSocketを使用して実現されています。WebSocketがない場合、この操作はかなり遅くなるか、完全に機能しなくなる可能性があります。
したがって、StreamlitアプリケーションでNginxを使用する場合、WebSocketのサポートは非常に重要です。これにより、アプリケーションの反応性、効率性、ユーザーエクスペリエンスが大幅に向上します。
HTTPのバージョンとWebSocketの歴史について
HTTPのバージョンとWebSocketの歴史について、詳しく説明させていただきます。
HTTPのバージョン:
HTTP/0.9 (1991年): 非常にシンプルで、GETメソッドのみをサポート。
HTTP/1.0 (1996年): ヘッダー、複数のメソッド、マルチメディアオブジェクトの処理を導入。
HTTP/1.1 (1997年): 持続的接続、パイプライニング、キャッシュ制御メカニズムを追加。
HTTP/2 (2015年): 多重化、ヘッダー圧縮、サーバープッシュなどを導入し、パフォーマンスを大幅に向上。
HTTP/3 (2022年): UDPベースのQUICプロトコルを使用し、さらなる高速化と信頼性を実現。
WebSocketの誕生と経緯:
WebSocketは比較的新しい技術で、2011年にRFC 6455として標準化されました。その誕生の背景には以下のような要因があります:
リアルタイム通信の需要: チャットアプリ、オンラインゲーム、リアルタイム更新など、即時性の高いウェブアプリケーションの需要が増加。
HTTPの制限: 従来のHTTPは要求-応答モデルに基づいており、サーバーからクライアントへのプッシュ通知が困難だった。
ポーリングの非効率性: リアルタイム性を模倣するために使用されていた長いポーリングやCometなどの技術は、リソースを大量に消費。
HTML5の発展: HTML5の一部として、より強力なウェブアプリケーションを可能にする技術が求められた。
WebSocketの特徴:
全二重通信:クライアントとサーバーが同時にデータを送受信可能。
低オーバーヘッド:一度接続が確立されると、小さいヘッダーでデータを送信可能。
プロトコル非依存:テキストデータやバイナリデータを送信可能。
クロスドメイン通信:適切に設定すれば、異なるドメイン間での通信が可能。
WebSocketとHTTPの関係:
WebSocketはHTTP上に構築されており、最初の接続はHTTPを使用して行われます。その後、接続がWebSocketにアップグレードされます。これにより、既存のウェブインフラストラクチャとの互換性を維持しつつ、新しい機能を提供しています。
HTTP/2とWebSocket:
HTTP/2は多重化やサーバープッシュなどの機能を導入し、WebSocketの一部の利点を提供しています。しかし、WebSocketは依然として双方向のリアルタイム通信に最適な選択肢の一つです。
HTTP/2や3をnginxで設定できるのか?
HTTP/2の場合:
NginxはHTTP/2をサポートしていますが、proxy_http_version ディレクティブではHTTP/2を直接指定することはできません。代わりに、以下のように設定します:
listen 443 ssl http2;
これはサーバーブロック内で設定し、クライアントとNginx間でHTTP/2を使用することを指定します。
しかし、proxy_http_version は依然として以下のように設定します:
proxy_http_version 1.1;
これは、Nginxとバックエンドサーバー(この場合はStreamlit)間の通信プロトコルを指定します。
HTTP/3の場合:
HTTP/3は比較的新しく、現時点(2024年7月現在)でのNginxのメインラインバージョンではまだ完全にはサポートされていません。HTTP/3サポートは実験的な段階にあり、特別なビルドやモジュールが必要です。
したがって、proxy_http_version 3.0 のような設定は現在のNginxでは使用できません。
なぜHTTP/1.1を使い続けるのか:
WebSocketとの互換性が主な理由です。WebSocketプロトコルはHTTP/1.1上に構築されており、HTTP/1.1を使用することで最も安定した動作が保証されます。
また、多くのバックエンドサーバー(Streamlitを含む)はHTTP/1.1を期待しており、HTTP/2やHTTP/3を使用すると予期せぬ問題が発生する可能性があります。
最適な設定:
StreamlitアプリケーションのためのNginx設定では、以下のようなアプローチが推奨されます:
クライアントとNginx間でHTTP/2を使用(可能な場合):
nginx
NginxとStreamlit間でHTTP/1.1を使用:
nginx
WebSocketサポートのための設定を維持:
nginx
このアプローチにより、クライアント側では最新のHTTPプロトコルの利点を活かしつつ、バックエンド側では安定性と互換性を確保できます。
将来的にはHTTP/3やWebSocketの代替技術がさらに普及し、設定方法が変わる可能性がありますが、現時点ではこの方法が最も安全で効果的です