streamlitのLLMアプリをEC2+ALB+Route53で公開する方法


ChatGPTやClaudeのWebサイトは[React](https://ja.react.dev/)で作られているが、そこまでしたくなくサクッと作って、サクッと公開したいとき、[streamlit](https://streamlit.io/)は非常に楽。
ただ、streamlitは静的ビルドができず、S3+CloudFrontでは公開できない。今回は、プライベートVPC内のEC2+ALBで公開する方法を紹介する。
※パブリックVPC内であれば、EC2+Elastic IPだけでも公開できます。


## Streamlitの簡易LLMアプリケーション作成

streamlitのインストール
```
pip3 install streamlit
```

下記にAmazon BedrockのClaude3のWebアプリを立ち上げるstreamlitのプログラムを載せる。

:::note warn
BedrockのClaudeを使用するには、Bedrockのコンソール画面->モデルアクセスからモデルアクセスをリクエストする必要がある。
:::

:::note warn
下記コマンドでBedrockを使用権限のあるAWSユーザーで認証しておく必要がある。
```
aws configure
```
:::


```Python
import boto3
import json
import streamlit as st

# Bedrockのレスポンスを待つ関数
@st.cache_data
def get_bedrock_response(input_text):
    bedrock = boto3.client(service_name='bedrock-runtime')
    body = json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 2000,
        "temperature": 0.1,
        "top_p": 0.9,
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": input_text,
                    }
                ]
            }
        ]
    })
    modelId = 'anthropic.claude-3-sonnet-20240229-v1:0'  # claude-3-sonnetモデルのモデルIDを指定
    accept = 'application/json'
    contentType = 'application/json'

    response = bedrock.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
    response_body = json.loads(response.get('body').read())
    response_text = response_body.get('content')[0]['text']

    return response_text

def main():
    st.title("Anthropic claude-3-sonnet")
    input_text = st.text_input("このテキストをBedrockに送信します")
    send_button = st.button("送信")

    if send_button:
        # レスポンスを待つ処理を別の関数に分ける
        result = st.info("処理中...")
        try:
            response_text = get_bedrock_response(input_text)
            result.empty()
            st.write(response_text)
        except Exception as e:
            result.error(f"エラーが発生しました: {e}")

if __name__ == "__main__":
    main()
```



## EC2の設定
Nginxをインストールして起動する。

```
sudo apt-get install nginx
sudo systemctl status nginx.service
```

設定を変更する。
```
sudo emacs /etc/nginx/nginx.conf
```

```
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;
	
	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
        location /app {
            proxy_pass http://localhost:8000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
```

Nginxを再起動する。
```
sudo systemctl restart nginx.service
```

```
streamlit run app.py --server.baseUrlPath=/app --server.port 8000 --server.enableCORS=false --server.enableXsrfProtection=false
```

バックグラウンド実行の場合は下記コマンド
```
nohup streamlit run app.py --server.baseUrlPath=/app --server.port 8000 --server.enableCORS=false --server.enableXsrfProtection=false &
```


## ALBの設定

1. ロードバランサーのコンソール画面でALBを作成する。
2. ターゲットグループとして、ポート80でEC2インスタンスを登録する。
3. リスナーとルールで、HTTPS:443をSSL/TLS 証明書を付けてターゲットグループに転送するよう設定する。


## Route53の設定

Route53で、ドメインを作成し、AレコードでALBへエイリアスをする。

これで作成してドメインでHTTPSによるアクセスが可能になる。