
AWS ECS/Bedrock 素振り
下記記事をなぞってみる
Bedrock有効化
リクエストを出す。会社や利用目的を書かされるが適当に記載する。まとめて複数のモデルに対してリクエストをしておけばOK。

数分でアクセス権が付与された

DynamoDBテーブル作成
bedrock_simple_chat テーブルを作成。
パーティションキーは session_id とする。
キャパシティーの設定では「オンデマンド」を指定。


メモ
最初は session_id というカラムをパーティションキーにしていたが、これだとStreamlitからDynamoDBに繋ぎ込んだ際にカラムを認識できなかった。
DynamoDBで SessionId と指定して、 Streamlit 側で session_id と指定するとうまく紐づいた。
ECRレジストリ作成
bedrock-simple-chat というリポジトリを作成

アプリ開発
VSCodeで開発。
# rye を使ってPython環境を整備
rye init --script bedrock_simple_chat
# ディレクトリ移動
cd bedrock_simple_chat
# セットアップ
rye sync
rye add boto3==1.34.87
rye add streamlit==1.33.0
rye add langchain==0.2.0
rye add langchain-aws==0.1.4
rye add langchain-community==0.2.0
rye sync
# プロジェクト動作確認
rye run bedrock-simple-chat # アンダーバーで初期化したはずだが、勝手にハイフンになるので注意
main.py
# Pyhton外部モジュールのインポート
import streamlit as st
from langchain_aws import ChatBedrock
from langchain_community.chat_message_histories import DynamoDBChatMessageHistory
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# セッションIDを定義
if "session_id" not in st.session_state:
st.session_state.session_id = "session_id"
# セッションに会話履歴を定義
if "history" not in st.session_state:
st.session_state.history = DynamoDBChatMessageHistory(
table_name="bsc_db", session_id=st.session_state.session_id
)
# セッションにLangChainの処理チェーンを定義
if "chain" not in st.session_state:
# プロンプトを定義
prompt = ChatPromptTemplate.from_messages(
[
("system", "絵文字入りでフレンドリーに会話してください"),
MessagesPlaceholder(variable_name="messages"),
MessagesPlaceholder(variable_name="human_message"),
]
)
# チャット用LLMを定義
chat = ChatBedrock(
model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
model_kwargs={"max_tokens": 4000}, streaming=True,
)
# チェーンを定義
st.session_state.chain = prompt | chat
# タイトルを画面表示
st.title("Bedrockと話そう!")
# 履歴クリアボタンを画面表示
if st.button("履歴クリア"):
st.session_state.history.clear()
# メッセージを画面表示
for message in st.session_state.history.messages:
with st.chat_message(message.type):
st.markdown(message.content)
# チャット入力欄を定義
if prompt := st.chat_input("何でも話してね!"):
# ユーザーの入力をメッセージに追加
with st.chat_message("user"):
st.markdown(prompt)
# モデルの呼び出しと結果の画面表示
with st.chat_message("assistant"):
response = st.write_stream(
st.session_state.chain.stream(
{
"messages": st.session_state.history.messages,
"human_message": [HumanMessage(content=prompt)],
},
config={"configurable": {"session_id": st.session_state.session_id}},
)
)
# 会話を履歴に追加
st.session_state.history.add_user_message(prompt)
st.session_state.history.add_ai_message(response)
streamlit への PATH を通しておく
# 作業ディレクトリ内の、streamlitが存在するパスをPATHに追加
export PATH=$PATH:/home/XXXXXXXX/dev/bedrock_simple_chat/.venv/bin/
動作確認
streamlit run main.py
これだけで動いてしまった…
Bedrock(Claude)に接続もできている。実行ユーザーのAWSアクセスキーを用いて繋ぎ込みを行っているのか。

※上記コマンドを実行した環境・ユーザーについては確かに以下の通りなので、多分それを使っているんだろう。
aws cli コマンドがインストール済み
aws configure でアクセスキーが設定済み(権限も高め)
Dockerイメージ化
dockerはインストール済みであること。
参考にさせてもらった記事
https://sogo.dev/posts/2023/11/rye-with-docker
DockerFile
ARG PYTHON_VERSION=3.12.5aaaaaa
FROM python:${PYTHON_VERSION}-slim-bookworm
RUN echo "Using Python version: ${PYTHON_VERSION}"
RUN apt update && \
apt install --no-install-recommends -y curl && \
apt clean && \
rm -rf /var/lib/apt/lists/*
ARG USERNAME=ryeuser
RUN useradd ${USERNAME} --create-home
USER ${USERNAME}
WORKDIR /home/${USERNAME}/app
ENV RYE_HOME=/home/${USERNAME}/.rye
ENV PATH=${RYE_HOME}/shims:${PATH}
RUN curl -sSf https://rye.astral.sh/get | RYE_NO_AUTO_INSTALL=1 RYE_INSTALL_OPTION="--yes" bash
RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
--mount=type=bind,source=requirements.lock,target=requirements.lock \
--mount=type=bind,source=requirements-dev.lock,target=requirements-dev.lock \
--mount=type=bind,source=.python-version,target=.python-version \
--mount=type=bind,source=README.md,target=README.md \
rye sync --no-dev --no-lock
ENV PATH=.venv/bin/:${PATH}
COPY . .
CMD ["/bin/bash", "-c", ". .venv/bin/activate && streamlit run ./src/bedrock_simple_chat/main.py"]
.dockerignore
.venv
ビルド
docker build . -t bedrock-simple-chat:latest --build-arg PYTHON_VERSION=$(cat .python-version)
コンテナ起動
http://localhost:8501 でアクセスできるようになるはず。
docker run -it -p 8501:8501 bedrock-simple-chat
起動したコンテナ内でターミナルを使ってコマンドを叩きたければ👇️
docker run -it bedrock-simple-chat /bin/bash
ECRへプッシュ
# DockerでECRにログイン
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com
# コンテナへ「最新版」タグを付与
docker tag bedrock-simple-chat:latest 191846745499.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-simple-chat:latest
# コンテナをECRへプッシュ(登録)
docker push 191846745499.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-simple-chat:latest
インフラ周りの設定
VPC
コンテナとALBを配置するVPCを作成
ECSにおいてはAZが2つ必要らしい!

VPCエンドポイント作成
VPC内に含むことができないサービスのためにVPCエンドポイントを作成することでAWSネットワーク内で通信を完結できるようにする。
DynamoDB用のエンドポイント
エンドポイントを作成する。「タイプ」は「Gateway」を選択
ルートテーブルはVPC作成時に作成されたprivateサブネットのものを指定


VPCエンドポイントのGateway型とInterface型の違い
こちらを参考に
S3とDynamoDBのみ、Gateway型をサポートしていて、それ以外は概ねInterface型らしい。
すごく簡潔な解説はこちら
Gateway型
S3, DynamoDBに対してよりセキュアに(インターネット経由せずに)接続したいときに使う
VPC外からの接続ができない(VPCの中から、VPC外のS3やDynamoDBに繋ぐときに飲み使われる)
ルートテーブルにルートを追加
パブリックIPでの接続
課金なし
Interface型
VPC外から接続可能
プライベートIPで接続が可能
ENIを利用してVPC内部にサービスのエンドポイントが立ち上がります。そのため、ネットワークACLでローカルの通信のみに制限しても影響を受けることがありません。(?)
課金あり
そもそもルートてブルって…?
こちらを参照
→VPC内(サブネット内)の通信を振り分けるルールの一覧
外部からのアクセスに対し、VPC内のどのリソースとやり取りさせるかの割り振りを行う。
→ルートテーブルはサブネットに関連付けて利用します。
1つのサブネットに対して1つのルートテーブルが紐づけられる。
ECR, CloudWatch, Betrock用のエンドポイント
インターフェイズ型でエンドポイントを作成する。下記4種で作成
いずれも、privateサブネットを選択。セキュリティグループはデフォルト
com.amazonaws.ap-northeast-1.ecr.api(ECR用)
com.amazonaws.ap-northeast-1.ecr.dkr(ECR用)
com.amazonaws.ap-northeast-1.logs(CloudWatch用)
com.amazonaws.ap-northeast-1.bedrock-runtime(Bedrock用)
作成結果。計6種。

ALB用セキュリティグループ作成

コンテナ用IAMロール作成
ECSのタスクロール用のIAMロールを作成する。許可するポリシーは2つ
AmazonBedrockFullAccess
AmazonDynamoDBFullAccess


ECSのクラスター&サービス作成
ECSの用語・概念はこちらを参考に
・クラスター
タスクとサービスを実行する基盤です
・サービス
ECSクラスター内で、タスクを実行し管理します
・タスク
タスク定義に基づいてコンテナを起動します
タスク定義
前段作業で作成したロールを他スクロールとして指定


コンテナー
コンテナーのポートは8501を指定。コンテナーイメージはECR上のイメージURIを指定。

クラスター
デフォルトの内容で作成

こちらでも言及されている通り、クラスター作成処理を実行時、エラーになることがある(なった)。
メッセージに記載のリンクからCloudFormationスタックに飛び、「再試行」で今度はクラスター作成成功した。
サービス作成
作成したクラスターの中にサービスを作成する


ネットワーキングでは、作成したVPC、サブネット(プライベート2つ)
パブリックIPはオフ

ロードバランシングでは新規にALBを作成

ALB追加設定
クラスターのサービス作成時に新設したALBに追加設定を行う。
ALBはプライベートサブネットに配置されるので、パブリックサブネットに移動する。

ALBに、前段で作成したセキュリティグループ(自身のPCからのアクセス許可のために作成したもの)を追加する

接続テスト

やったー😄
AWS インフラ周り構成まとめ
理解が甘いと思っているので誤りもありそうだけど、ひとまず自分の理解を記載。
ECS利用の際はマルチAZが必須と書いてあったのでAZは2つ作った。
ALBもこれに合わせて(?)、両AZのパブリックサブネットに配置。
ECSのサービスデプロイ(と合わせて設定するネットワーキング)についてもプライベートサブネット2つを指定。
これって、2つのAZ上にECSサービスを配置して、両ECSサービスにインターネットからアクセスできるようにALBを2つ配置した、という理解。
間違ってる?ECSサービスやALB自体は1つずつだけど、ネットワークが2種類あるというだけ?
こういうレベルで自分は理解できていないんだな…というのがわかっただけでも良しとしよう。
