[3行まとめ]
・Bedrock + EKS + S3でRAG構成の生成AIサービス環境を構築
・CloudWatch Application Signalsを有効化して監視
・本記事では環境構築手順までを記載
はじめに
tsuzuki(@melktzk)です。本記事が初投稿です。
本記事ではAWSを活用して生成AIサービスを提供する環境を構築しつつ、2024年6月に正式リリースされたAmazon CloudWatch Application Signalsを利用してどのような監視ができるのか、実際に確認してみた内容を紹介していきます。
生成AIサービスはAWSが公開しているmulti-tenant-chatbot-using-rag-with-amazon-bedrockを利用したチャットボットWebアプリケーションを構築します。構築の際に実行環境の違いなどに依存した要注意ポイントがいくつかあったので、まずは環境構築編として生成AIサービス環境の構築とCloudWatch Application Signalsを有効化するところまでの手順について紹介します。
構成
生成AIサービス環境
構築する生成AIサービス環境のインフラレイヤは、
- コンテナ化されたアプリケーションをEKSのクラスター上で管理
- ユーザからのチャット入力情報に対して、S3に格納したドキュメントをベースにBedrockで回答を生成するRAG構成
- デフォルトではテナントA向けにはSagemakerについてのQAのCSVデータ、テナントB向けにはAmazon EMRについてのQAのCSVデータが保管されている
- チャットの履歴をDynamoDBに保存
- マルチテナントなアプリとしてCognitoでユーザ管理
- NLBで各テナントのコンテナへルーティング
のような構成になっています。全体のイメージは以下の図のとおりです。
上記図のとおり、本環境ではCloud9を利用して環境構築及びEKS操作などを実施しています。新規作成したAWSアカウントなど環境によってはCloud9が利用不可であることご注意ください。
また、マルチテナントやRAGとしてのふるまいを実現するためのコンテナ構成・アプリケーションレイヤの概要としては、
- SaaS Amazon EKS Reference Architecture を基にした、Istio Service Meshによるマルチテナントのルーティング
- テナントごとにKubernetes Namespace(名前空間)が分離
- チャットボットのPodは、チャット入力インターフェースの"Chatbot"アプリコンテナと、BedrockへのAPIなど実行する"RAG-API"アプリコンテナ(サイドカー)の2つのコンテナで構成
- BedrockのEmbeddingsモデル(デフォルトではamazon.titan-embed-text-v1)を使用して、S3のテキストデータをベクトル埋め込み変換
- Facebook AI Similarity Search (FAISS)を使用して、インデックス作成とユーザ入力情報の類似性検索の実行
- 扱うデータサイズが小さいデモユースケース向けな環境として、作成したインデックス(バイナリ形式)をコンテナアプリ初期実行時にインメモリにロードする構成で応答速度を向上
- Bedrockのテキスト生成モデル(デフォルトではanthropic.claude-instant-v1)を使用して、ユーザ入力情報+類似性検索結果の内容からユーザへの応答生成
のような構成になっています。以下の図は、ユーザのアクセス・チャット入力から応答生成までのフローイメージです。
画像出典元:https://aws.amazon.com/jp/blogs/containers/build-a-multi-tenant-chatbot-with-rag-using-amazon-bedrock-and-amazon-eks/
詳細はAWS公式ブログ記事 (Build a multi-tenant chatbot with RAG using Amazon Bedrock and Amazon EKS)で紹介されているのでそちらを参照ください。
監視環境
前述の生成AIサービス環境に対してCloudWatch Application Signalsを有効化して、SLO管理機能や自動計測などについて確認していきます。
上記図のとおり、CloudWatch Application Signalsは監視対象からメトリクスやトレース情報を収集して、サービスマップの表示やSLOの作成・管理が可能になります。また、CloudWatch AlarmやCloudWatch Synthetics, CloudWatch RUMなどの機能と連携することも可能ですが、今回の構築では対象外としています。
クライアント環境
本環境では、以下の図のとおりクライアントからのアクセスがサブドメインで振り分けされるドメイン駆動型ルーティングになっています。
画像出典元:https://aws.amazon.com/jp/blogs/containers/build-a-multi-tenant-chatbot-with-rag-using-amazon-bedrock-and-amazon-eks/
ただし、あくまでデモユースケース用のサンプルコードであるため、デフォルト構成ではAmazon Route 53によるDNS設定などは含まれていません。したがって、独自ドメインの取得・登録を実施してサンプルコードに記載されているデフォルトドメイン(*.example.com)を修正・環境構築するか、デフォルトドメインのままアクセスするクライアントのローカルhosts情報を書き換えるなどの対応が必要になります。
また、デフォルト構成ではNLBがパブリック公開されてしまう状態となっていました。
上記より本環境では、下記図のように別途VPC上にクライアント用のインスタンス (Windows)を構築してそのhostsを修正してアクセスさせるようにしました。さらにNLBにSecurity GroupでIP制限するように修正、クライアント側のNAT GW経由でのみアクセス可能とするように修正しています。
NLBのIP制限について、先ほど記載したように本環境のNLBはIstioで制御されており、後から個別にセキュリティグループ設定を追加・修正しても自動で元の状態に戻ってしまうため、その修正方法についても今回記載していきます。
環境構築
基本的に本環境はmulti-tenant-chatbot-using-rag-with-amazon-bedrockで用意されている各スクリプトと手順に従って構築していきます。注意すべきポイントを先にまとめつつ、構築手順について記載していきます。
ポイントまとめ
- Cloud9サイズ
- スクリプト/fileごとの修正
-
deploy-els.sh
- AZ設定、インスタンスサイズ
- EKSバージョン
-
setup.sh
- kubectlバージョン
- Istioバージョン
-
configure-istio.sh
- Envoyバージョン
-
proxy-protocol-envoy-filter.yaml
- Istioバージョンアップに伴う修正
-
deploy-els.sh
- アクセスエントリ
- 必要に応じて実施
- 環境削除や構築の再実行
手順
1. Bedrock有効化
本環境では東京リージョンに構築していくため、東京リージョンで利用可能なモデルを有効化します。
デフォルトで必要なのはanthropic.claude-instant-v1
とamazon.titan-embed-text-v1
だけですが、有効化しただけでは料金特にかからないのでここではすべてのモデルを有効化しています。
有効化するモデルのプロバイダーによっては追加で情報入力する必要があるので適宜入力します。
内容確認してリクエスト実行後、数分程度でアクセス権が付与されます。
2. Cloud9環境構築
スクリプトや各種コマンド実行していく環境としてCloud9を構築していきます。
Cloud9を利用できない環境では、SageMaker StudioやEC2利用など代替案を検討する必要がありますが、本記事では対象外としています。
ここではインスタンスタイプt3a.small
、プラットフォームAmazon Linux 2
を選択、他はデフォルト設定で立ち上げています。
デフォルト状態のCloud9だと、アプリのイメージビルドの際にストレージ容量が足りずにエラーとなる場合があります。AWS公式手順を参考にストレージサイズを変更していきます。Cloud9のIDEを開き、以下のスクリプトを作成します。
Cloud9リサイズ用スクリプト
#!/bin/bash
# Specify the desired volume size in GiB as a command line argument. If not specified, default to 20 GiB.
SIZE=${1:-20}
# Get the ID of the environment host Amazon EC2 instance.
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
INSTANCEID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id 2> /dev/null)
REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/placement/region 2> /dev/null)
# Get the ID of the Amazon EBS volume associated with the instance.
VOLUMEID=$(aws ec2 describe-instances \
--instance-id $INSTANCEID \
--query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \
--output text \
--region $REGION)
# Resize the EBS volume.
aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE
# Wait for the resize to finish.
while [ \
"$(aws ec2 describe-volumes-modifications \
--volume-id $VOLUMEID \
--filters Name=modification-state,Values="optimizing","completed" \
--query "length(VolumesModifications)"\
--output text)" != "1" ]; do
sleep 1
done
# Check if we're on an NVMe filesystem
if [[ -e "/dev/xvda" && $(readlink -f /dev/xvda) = "/dev/xvda" ]]
then
# Rewrite the partition table so that the partition takes up all the space that it can.
sudo growpart /dev/xvda 1
# Expand the size of the file system.
# Check if we're on AL2 or AL2023
STR=$(cat /etc/os-release)
SUBAL2="VERSION_ID=\"2\""
SUBAL2023="VERSION_ID=\"2023\""
if [[ "$STR" == *"$SUBAL2"* || "$STR" == *"$SUBAL2023"* ]]
then
sudo xfs_growfs -d /
else
sudo resize2fs /dev/xvda1
fi
else
# Rewrite the partition table so that the partition takes up all the space that it can.
sudo growpart /dev/nvme0n1 1
# Expand the size of the file system.
# Check if we're on AL2 or AL2023
STR=$(cat /etc/os-release)
SUBAL2="VERSION_ID=\"2\""
SUBAL2023="VERSION_ID=\"2023\""
if [[ "$STR" == *"$SUBAL2"* || "$STR" == *"$SUBAL2023"* ]]
then
sudo xfs_growfs -d /
else
sudo resize2fs /dev/nvme0n1p1
fi
fi
スクリプトを実行してストレージサイズを変更します。ここでは30GiBとしています。
$ chmod +x resize.sh
$
$ ./resize.sh 30
$ df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 954M 0 954M 0% /dev
tmpfs 963M 0 963M 0% /dev/shm
tmpfs 963M 456K 962M 1% /run
tmpfs 963M 0 963M 0% /sys/fs/cgroup
/dev/nvme0n1p1 30G 6.9G 24G 23% /
tmpfs 193M 0 193M 0% /run/user/1000
次に、各種EKSのコマンドなど実行可能にするために、Cloud9のインスタンスのロールを修正します。IAMロール管理画面に移動し、EC2用のロールを作成していきます。
許可ポリシーではAdministratorAccess
を選択します。
ロール名をCloud9AdminRole
とし、必要に応じてタグを追加してロール作成します。
ロール作成が完了したら、Cloud9のデフォルトロールの無効化と作成したロールのアタッチを実施していきます。
Cloud9のIDEに移動し、画面右上の歯車アイコンから設定画面を開きます。
設定画面のAWS Settings
のCredentials
の項目を左にスライドしてAWS Managed Temporary Credentials
をオフにします。これでデフォルトのロールの無効化ができます。
次にEC2インスタンスの管理画面に移動して、Cloud9のインスタンスを右クリックし、メニューからSecurity
>IAMロールの変更
をクリックします。
作成したロールCloud9AdminRole
を選択して更新します。
3. スクリプト取得
Cloud9のIDEに移動して、ターミナルからGitクローンを実施してコードを取得します。
$ git clone https://github.com/aws-samples/multi-tenant-chatbot-using-rag-with-amazon-bedrock.git
Cloning into 'multi-tenant-chatbot-using-rag-with-amazon-bedrock'...
remote: Enumerating objects: 79, done.
remote: Counting objects: 100% (79/79), done.
remote: Compressing objects: 100% (69/69), done.
remote: Total 79 (delta 15), reused 60 (delta 7), pack-reused 0 (from 0)
Receiving objects: 100% (79/79), 93.87 KiB | 5.21 MiB/s, done.
Resolving deltas: 100% (15/15), done.
$
$ cd multi-tenant-chatbot-using-rag-with-amazon-bedrock
$
4. スクリプト修正
取得した各ファイルを修正していきます。
①deploy-eks.sh
EKSクラスターをデプロイするスクリプトdeploy-eks.sh
の、以下の箇所を修正します。
- EKSバージョン
- EKSクラスターのAZ設定、インスタンスサイズ設定
デフォルトではEKSのバージョンが1.27
となっており、標準サポート外となってしまっているため変更します。ここでは、1.30
としています。
- export EKS_CLUSTER_VERSION="1.27"
+ export EKS_CLUSTER_VERSION="1.30"
クラスターのAZを指定する箇所で、アカウント依存でAZのbが利用できない場合があるので削除します。
- availabilityZones: ["${AWS_REGION}a", "${AWS_REGION}b", "${AWS_REGION}c"]
+ availabilityZones: ["${AWS_REGION}a", "${AWS_REGION}c"]
ノードとしてデプロイするEC2インスタンスのインスタンスタイプが指定されている箇所で、東京リージョンではAZ依存でt3a
が利用できない場合があるのでこれも削除します。
- instanceTypes: ["t3a.medium", "t3.medium"]
+ instanceTypes: ["t3.medium"]
修正後のdeploy-eks.sh全体は以下のとおり
#!/usr/bin/bash
source ~/.bash_profile
test -n "$EKS_CLUSTER_NAME" && echo EKS_CLUSTER_NAME is "$EKS_CLUSTER_NAME" || echo EKS_CLUSTER_NAME is not set
export EKS_CLUSTER_VERSION="1.30"
echo "Deploying Cluster ${EKS_CLUSTER_NAME} with EKS ${EKS_CLUSTER_VERSION}"
cat << EOF > ${YAML_PATH}/cluster-config.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ${EKS_CLUSTER_NAME}
region: ${AWS_REGION}
version: "${EKS_CLUSTER_VERSION}"
iam:
withOIDC: true
serviceAccounts:
- metadata:
name: aws-load-balancer-controller
namespace: kube-system
wellKnownPolicies:
awsLoadBalancerController: true
availabilityZones: ["${AWS_REGION}a", "${AWS_REGION}c"]
managedNodeGroups:
- name: nodegroup
desiredCapacity: 3
instanceTypes: ["t3.medium"]
volumeEncrypted: true
ssh:
allow: false
cloudWatch:
clusterLogging:
enableTypes: ["*"]
secretsEncryption:
keyARN: ${MASTER_ARN}
EOF
eksctl create cluster -f ${YAML_PATH}/cluster-config.yaml
aws eks update-kubeconfig --name=${EKS_CLUSTER_NAME}
# Associate an OIDC provider with the EKS Cluster
echo "Associating an OIDC provider with the EKS Cluster"
eksctl utils associate-iam-oidc-provider \
--region=${AWS_REGION} \
--cluster=${EKS_CLUSTER_NAME} \
--approve
export OIDC_PROVIDER=$(aws eks describe-cluster \
--name ${EKS_CLUSTER_NAME} \
--query "cluster.identity.oidc.issuer" \
--output text)
echo "Installing AWS Load Balancer Controller"
kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller/crds?ref=master"
helm repo add eks https://aws.github.io/eks-charts
helm repo update
# Setting AWS Load Balancer Controller Version
export VPC_ID=$(aws eks describe-cluster \
--name ${EKS_CLUSTER_NAME} \
--query "cluster.resourcesVpcConfig.vpcId" \
--output text)
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=${EKS_CLUSTER_NAME} \
--set serviceAccount.create=false \
--set region=${AWS_REGION} \
--set vpcId=${VPC_ID} \
--set serviceAccount.name=aws-load-balancer-controller
kubectl -n kube-system rollout status deployment aws-load-balancer-controller
export OIDC_PROVIDER=$(aws eks describe-cluster \
--name ${EKS_CLUSTER_NAME} \
--query "cluster.identity.oidc.issuer" \
--output text)
export OIDC_ID=$(echo $OIDC_PROVIDER | awk -F/ '{print $NF}')
echo "Creating S3 Access Role in IAM"
export S3_ACCESS_ROLE=${EKS_CLUSTER_NAME}-s3-access-role-${RANDOM_STRING}
export ENVOY_IRSA=$(
envsubst < iam/s3-access-role-trust-policy.json | \
xargs -0 -I {} aws iam create-role \
--role-name ${S3_ACCESS_ROLE} \
--assume-role-policy-document {} \
--query 'Role.Arn' \
--output text
)
echo "Attaching S3 Bucket policy to S3 Access Role"
aws iam attach-role-policy \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/s3-envoy-config-access-policy-${RANDOM_STRING} \
--role-name ${S3_ACCESS_ROLE}
echo ${ENVOY_IRSA}
echo "export ENVOY_IRSA=${ENVOY_IRSA}" \
| tee -a ~/.bash_profile
TENANTS="tenanta tenantb"
for t in $TENANTS
do
export NAMESPACE="${t}-ns"
export SA_NAME="${t}-sa"
echo "Creating DynamoDB / Bedrock Access Role in IAM"
export CHATBOT_ACCESS_ROLE=${EKS_CLUSTER_NAME}-${t}-chatbot-access-role-${RANDOM_STRING}
export CHATBOT_IRSA=$(
envsubst < iam/chatbot-access-role-trust-policy.json | \
xargs -0 -I {} aws iam create-role \
--role-name ${CHATBOT_ACCESS_ROLE} \
--assume-role-policy-document {} \
--query 'Role.Arn' \
--output text
)
echo "Attaching S3 Bucket and DynamoDB policy to Chatbot Access Role"
aws iam attach-role-policy \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/s3-contextual-data-access-policy-${t}-${RANDOM_STRING} \
--role-name ${CHATBOT_ACCESS_ROLE}
aws iam attach-role-policy \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/dynamodb-access-policy-${t}-${RANDOM_STRING} \
--role-name ${CHATBOT_ACCESS_ROLE}
done
②setup.sh
初期設定用スクリプトsetup.sh
の、以下の箇所を修正します。
- kubectlバージョン
- Istioバージョン
利用するBedrockのモデルを変更したい場合もここでsetup.shの修正が必要です。本記事では対象外としていますが、環境構築後に変更したくなった場合はsetup.shとは別のアプリケーション用コードを修正してコンテナ再デプロイなどの作業が必要になるため注意してください。
①deploy-eks.shで変更したEKSバージョンに合わせて、kubectlバージョンも修正します。EKSバージョンとの対応はkubectl と eksctl のセットアップを参照。
- export KUBECTL_VERSION="1.27.1/2023-04-19"
+ export KUBECTL_VERSION="1.30.0/2024-05-12"
同様に①deploy-eks.shで変更したEKSバージョンに合わせて、Istioのバージョンも修正します。EKSバージョンとの対応はSupport status of Istio releasesを参照。
- export ISTIO_VERSION="1.18.3"
+ export ISTIO_VERSION="1.22.3"
修正後のsetup.shの全体は以下のとおり
#!/usr/bin/env bash
. ~/.bash_profile
export TEXT2TEXT_MODEL_ID=anthropic.claude-instant-v1
export EMBEDDING_MODEL_ID=amazon.titan-embed-text-v1
export BEDROCK_SERVICE=bedrock-runtime
echo "export TEXT2TEXT_MODEL_ID=${TEXT2TEXT_MODEL_ID}" \
| tee -a ~/.bash_profile
echo "export EMBEDDING_MODEL_ID=${EMBEDDING_MODEL_ID}" \
| tee -a ~/.bash_profile
echo "export BEDROCK_SERVICE=${BEDROCK_SERVICE}" \
| tee -a ~/.bash_profile
export KUBECTL_VERSION="1.30.0/2024-05-12"
if [ "x${KUBECTL_VERSION}" == "x" ]
then
echo "################"
echo "Please specify a version for kubectl"
echo "################"
exit
fi
export EKS_CLUSTER_NAME="multitenant-chatapp"
if [ "x${EKS_CLUSTER_NAME}" == "x" ]
then
echo "################"
echo "Please specify a name for the EKS Cluster"
echo "################"
exit
fi
echo "export EKS_CLUSTER_NAME=${EKS_CLUSTER_NAME}" | tee -a ~/.bash_profile
export ISTIO_VERSION="1.22.3"
if [ "x${ISTIO_VERSION}" == "x" ]
then
echo "################"
echo "Please specify a version for Istio"
echo "################"
exit
fi
echo "export ISTIO_VERSION=${ISTIO_VERSION}" \
| tee -a ~/.bash_profile
echo "Installing helper tools"
sudo yum -q -y install jq bash-completion
sudo amazon-linux-extras install -q -y python3.8 2>/dev/null >/dev/null
python3.8 -m pip install -q -q --user botocore
python3.8 -m pip install -q -q --user boto3
echo "Uninstalling AWS CLI 1.x"
sudo pip uninstall awscli -y
echo "Installing AWS CLI 2.x"
curl --silent --no-progress-meter \
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
-o "awscliv2.zip"
unzip -qq awscliv2.zip
sudo ./aws/install --update
PATH=/usr/local/bin:$PATH
/usr/local/bin/aws --version
rm -rf aws awscliv2.zip
CLOUD9_EC2_ROLE="Cloud9AdminRole"
AWS=$(which aws)
echo "---------------------------"
${AWS} sts get-caller-identity --query Arn | \
grep ${CLOUD9_EC2_ROLE} -q && echo "IAM role valid. You can continue setting up the EKS Cluster." || \
echo "IAM role NOT valid. Do not proceed with creating the EKS Cluster or you won't be able to authenticate.
Ensure you assigned the role to your EC2 instance as detailed in the README.md"
echo "---------------------------"
export AWS_REGION=$(curl --silent --no-progress-meter \
http://169.254.169.254/latest/dynamic/instance-identity/document \
| jq -r '.region')
export AWS_DEFAULT_REGION=$AWS_REGION
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export RANDOM_STRING=$(cat /dev/urandom \
| tr -dc '[:alpha:]' \
| fold -w ${1:-20} | head -n 1 \
| cut -c 1-8 \
| tr '[:upper:]' '[:lower:]')
echo "export RANDOM_STRING=${RANDOM_STRING}" | tee -a ~/.bash_profile
echo "Installing kubectl"
sudo curl --silent --no-progress-meter --location -o /usr/local/bin/kubectl \
https://s3.us-west-2.amazonaws.com/amazon-eks/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
sudo chmod +x /usr/local/bin/kubectl
kubectl version --client=true
echo "Installing bash completion for kubectl"
kubectl completion bash >> ~/.bash_completion
. /etc/profile.d/bash_completion.sh
. ~/.bash_completion
echo "Installing eksctl"
curl --silent --no-progress-meter \
--location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" \
| tar xz -C /tmp
sudo mv -v /tmp/eksctl /usr/local/bin
echo "eksctl Version: $(eksctl version)"
echo "Installing bash completion for eksctl"
eksctl completion bash >> ~/.bash_completion
. /etc/profile.d/bash_completion.sh
. ~/.bash_completion
test -n "$AWS_REGION" && echo AWS_REGION is "$AWS_REGION" || echo AWS_REGION is not set
echo "export ACCOUNT_ID=${ACCOUNT_ID}" | tee -a ~/.bash_profile
echo "export AWS_REGION=${AWS_REGION}" | tee -a ~/.bash_profile
echo "export AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}" | tee -a ~/.bash_profile
aws configure set default.region ${AWS_REGION}
aws configure get default.region
export YAML_PATH=yaml
echo "export YAML_PATH=yaml" | tee -a ~/.bash_profile
[ -d ${YAML_PATH} ] || mkdir ${YAML_PATH}
export ENVOY_CONFIG_BUCKET="envoy-config-${RANDOM_STRING}"
aws s3 mb s3://${ENVOY_CONFIG_BUCKET}
if [[ $? -eq 0 ]]
then
echo "export ENVOY_CONFIG_BUCKET=${ENVOY_CONFIG_BUCKET}" \
| tee -a ~/.bash_profile
fi
# Creating S3 Bucket Policy for Envoy Dynamic Configuration Files
echo "Creating S3 Bucket Policy for Envoy Dynamic Configuration Files"
envsubst < iam/s3-envoy-config-access-policy.json | \
xargs -0 -I {} aws iam create-policy \
--policy-name s3-envoy-config-access-policy-${RANDOM_STRING} \
--policy-document {}
# Creating DynamoDB and Bedrock access policy for chatbot app
TENANTS="tenanta tenantb"
for t in $TENANTS
do
export TENANT=${t}
echo "Creating Contextual Data S3 Bucket for ${t}"
aws s3 mb s3://contextual-data-${t}-${RANDOM_STRING}
if [ "${t}" == "tenanta" ]
then
aws s3 cp data/Amazon_SageMaker_FAQs.csv s3://contextual-data-${t}-${RANDOM_STRING}
elif [ "${t}" == "tenantb" ]
then
aws s3 cp data/Amazon_EMR_FAQs.csv s3://contextual-data-${t}-${RANDOM_STRING}
fi
echo "S3 access policy for ${t}"
envsubst < iam/s3-contextual-data-access-policy.json | \
xargs -0 -I {} aws iam create-policy \
--policy-name s3-contextual-data-access-policy-${t}-${RANDOM_STRING} \
--policy-document {}
echo "DynamoDB and Bedrock access policy for ${t} chatbot app"
envsubst < iam/dynamodb-access-policy.json | \
xargs -0 -I {} aws iam create-policy \
--policy-name dynamodb-access-policy-${t}-${RANDOM_STRING} \
--policy-document {}
done
# Ingest Data to FAISS Index
source ~/.bash_profile
pip3.8 install -q -q --user -r data_ingestion_to_vectordb/requirements.txt
python3.8 data_ingestion_to_vectordb/data_ingestion_to_vectordb.py
echo "Creating Chatbot ECR Repository"
export ECR_REPO_CHATBOT=$(aws ecr create-repository \
--repository-name ${EKS_CLUSTER_NAME}-${RANDOM_STRING}-chatbot \
--encryption-configuration encryptionType=KMS)
export REPO_URI_CHATBOT=$(echo ${ECR_REPO_CHATBOT}|jq -r '.repository.repositoryUri')
export REPO_CHATBOT=$(echo ${ECR_REPO_CHATBOT}|jq -r '.repository.repositoryName')
echo "Creating rag-api ECR Repository"
export ECR_REPO_RAGAPI=$(aws ecr create-repository \
--repository-name ${EKS_CLUSTER_NAME}-${RANDOM_STRING}-rag-api \
--encryption-configuration encryptionType=KMS)
export REPO_URI_RAGAPI=$(echo ${ECR_REPO_RAGAPI}|jq -r '.repository.repositoryUri')
export REPO_RAGAPI=$(echo ${ECR_REPO_RAGAPI}|jq -r '.repository.repositoryName')
echo "export ECR_REPO_CHATBOT=${REPO_CHATBOT}" | tee -a ~/.bash_profile
echo "export REPO_URI_CHATBOT=${REPO_URI_CHATBOT}" | tee -a ~/.bash_profile
echo "export ECR_REPO_RAGAPI=${REPO_RAGAPI}" | tee -a ~/.bash_profile
echo "export REPO_URI_RAGAPI=${REPO_URI_RAGAPI}" | tee -a ~/.bash_profile
echo "Building Chatbot and RAG-API Images"
sh image-build/build-chatbot-image.sh
docker rmi -f $(docker images -a -q) &> /dev/null
sh image-build/build-rag-api-image.sh
docker rmi -f $(docker images -a -q) &> /dev/null
echo "Installing helm"
curl --no-progress-meter \
-sSL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm version --template='Version: {{.Version}}'; echo
rm -vf ${HOME}/.aws/credentials
echo "Generating a new key"
ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa <<< y
export EC2_KEY_NAME=${EKS_CLUSTER_NAME}-${RANDOM_STRING}
aws ec2 import-key-pair --key-name ${EC2_KEY_NAME} --public-key-material fileb://~/.ssh/id_rsa.pub
echo "export EC2_KEY_NAME=${EC2_KEY_NAME}" | tee -a ~/.bash_profile
echo "Creating KMS Key and Alias"
export KMS_KEY_ALIAS=${EKS_CLUSTER_NAME}-${RANDOM_STRING}
aws kms create-alias --alias-name alias/${KMS_KEY_ALIAS} \
--target-key-id $(aws kms create-key --query KeyMetadata.Arn --output text)
echo "export KMS_KEY_ALIAS=${KMS_KEY_ALIAS}" | tee -a ~/.bash_profile
export MASTER_ARN=$(aws kms describe-key --key-id alias/${KMS_KEY_ALIAS} \
--query KeyMetadata.Arn --output text)
echo "export MASTER_ARN=${MASTER_ARN}" | tee -a ~/.bash_profile
③configure-istio.sh
Istioの設定スクリプトconfigure-istio.sh
の、以下の箇所を修正します。
- Envoyバージョン
②setup.shで変更したIstioのバージョンに合わせてEnvoyのDockerイメージバージョン設定を修正します。対応するバージョンはSupported Envoy Versionsとenvoyproxy/envoyを参照してください。
- image: envoyproxy/envoy:v1.27.0
+ image: envoyproxy/envoy:v1.31-latest
修正後のconfigure-istio.shの全体は以下のとおり
#!/usr/bin/bash
. ~/.bash_profile
# Directory for generated certs
mkdir certs
echo "Creating Root CA Cert and Key"
openssl req -x509 -sha256 -nodes -days 365 \
-newkey rsa:2048 \
-subj '/O=Cluster 1 CA/CN=ca.example.com' \
-keyout certs/ca_example_com.key \
-out certs/ca_example_com.crt
echo "Creating Cert and Key for Istio Ingress Gateway"
openssl req \
-newkey rsa:2048 -nodes \
-subj "/O=Cluster 1/CN=*.example.com" \
-keyout certs/example_com.key \
-out certs/example_com.csr
openssl x509 -req -days 365 \
-set_serial 0 \
-CA certs/ca_example_com.crt \
-CAkey certs/ca_example_com.key \
-in certs/example_com.csr \
-out certs/example_com.crt
echo "Creating TLS secret for Istio Ingress Gateway"
kubectl create -n istio-ingress secret generic credentials \
--from-file=tls.key=certs/example_com.key \
--from-file=tls.crt=certs/example_com.crt
echo "Creating namespaces"
kubectl create namespace llm-demo-gateway-ns
kubectl create namespace envoy-reverse-proxy-ns
kubectl create namespace tenanta-oidc-proxy-ns
kubectl create namespace tenantb-oidc-proxy-ns
kubectl create namespace tenanta-ns
kubectl create namespace tenantb-ns
echo "Enabling sidecar injection in namespaces"
kubectl label namespace envoy-reverse-proxy-ns istio-injection=enabled
kubectl label namespace tenanta-oidc-proxy-ns istio-injection=enabled
kubectl label namespace tenantb-oidc-proxy-ns istio-injection=enabled
kubectl label namespace tenanta-ns istio-injection=enabled
kubectl label namespace tenantb-ns istio-injection=enabled
kubectl get namespace -L istio-injection
echo "Applying STRICT mTLS Policy on all application namespaces"
cat << EOF > ${YAML_PATH}/strictmtls.yaml
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: strict-mtls
spec:
mtls:
mode: STRICT
EOF
kubectl -n tenanta-ns apply -f ${YAML_PATH}/strictmtls.yaml
kubectl -n tenantb-ns apply -f ${YAML_PATH}/strictmtls.yaml
kubectl -n envoy-reverse-proxy-ns apply -f ${YAML_PATH}/strictmtls.yaml
kubectl -n tenanta-ns get PeerAuthentication
kubectl -n tenantb-ns get PeerAuthentication
kubectl -n envoy-reverse-proxy-ns get PeerAuthentication
rm -rf ${YAML_PATH}/strictmtls.yaml
echo "Deploying Istio Gateway resource"
cat << EOF > ${YAML_PATH}/llm-demo-gateway.yaml
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: llm-demo-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: credentials
minProtocolVersion: TLSV1_2
maxProtocolVersion: TLSV1_3
hosts:
- 'tenanta-ns/*'
- 'tenantb-ns/*'
EOF
kubectl -n llm-demo-gateway-ns apply -f ${YAML_PATH}/llm-demo-gateway.yaml
rm -rf ${YAML_PATH}/llm-demo-gateway.yaml
# Copying Envoy Dynamic Config files to S3 bucket
echo "Copying Envoy Dynamic Config files to S3 bucket"
aws s3 cp envoy-config/envoy.yaml s3://${ENVOY_CONFIG_BUCKET}
aws s3 cp envoy-config/envoy-lds.yaml s3://${ENVOY_CONFIG_BUCKET}
aws s3 cp envoy-config/envoy-cds.yaml s3://${ENVOY_CONFIG_BUCKET}
echo "Deploying Envoy Reverse Proxy"
export DOLLAR='$'
cat << EOF > ${YAML_PATH}/envoy-reverse-proxy.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-s3-access-role-${RANDOM_STRING}
name: envoy-reverse-proxy-sa
---
apiVersion: v1
kind: Service
metadata:
name: envoy-reverse-proxy
labels:
app: envoy-reverse-proxy
spec:
selector:
app: envoy-reverse-proxy
ports:
- port: 80
name: http
targetPort: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: envoy-reverse-proxy
labels:
app: envoy-reverse-proxy
spec:
replicas: 2
selector:
matchLabels:
app: envoy-reverse-proxy
minReadySeconds: 60
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app: envoy-reverse-proxy
annotations:
eks.amazonaws.com/skip-containers: "envoy-reverse-proxy"
spec:
serviceAccountName: envoy-reverse-proxy-sa
initContainers:
- name: envoy-reverse-proxy-bootstrap
image: public.ecr.aws/aws-cli/aws-cli:2.13.6
volumeMounts:
- name: envoy-config-volume
mountPath: /config/envoy
command: ["/bin/sh", "-c"]
args:
- aws s3 cp s3://${DOLLAR}{ENVOY_CONFIG_S3_BUCKET}/envoy.yaml /config/envoy;
aws s3 cp s3://${DOLLAR}{ENVOY_CONFIG_S3_BUCKET}/envoy-lds.yaml /config/envoy;
aws s3 cp s3://${DOLLAR}{ENVOY_CONFIG_S3_BUCKET}/envoy-cds.yaml /config/envoy;
env:
- name: ENVOY_CONFIG_S3_BUCKET
value: ${ENVOY_CONFIG_BUCKET}
containers:
- name: envoy-reverse-proxy
image: envoyproxy/envoy:v1.31-latest
args: ["-c", "/config/envoy/envoy.yaml"]
imagePullPolicy: Always
ports:
- containerPort: 8000
volumeMounts:
- name: envoy-config-volume
mountPath: /config/envoy
volumes:
- name: envoy-config-volume
emptyDir: {}
EOF
kubectl -n envoy-reverse-proxy-ns apply -f ${YAML_PATH}/envoy-reverse-proxy.yaml
rm -rf ${YAML_PATH}/envoy-reverse-proxy.yaml
echo "Adding Istio External Authorization Provider"
cat << EOF > ${YAML_PATH}/auth-provider.yaml
---
apiVersion: v1
data:
mesh: |-
accessLogFile: /dev/stdout
defaultConfig:
discoveryAddress: istiod.istio-system.svc:15012
proxyMetadata: {}
tracing:
zipkin:
address: zipkin.istio-system:9411
enablePrometheusMerge: true
rootNamespace: istio-system
trustDomain: cluster.local
extensionProviders:
- name: rev-proxy
envoyExtAuthzHttp:
service: envoy-reverse-proxy.envoy-reverse-proxy-ns.svc.cluster.local
port: "80"
timeout: 1.5s
includeHeadersInCheck: ["authorization", "cookie"]
headersToUpstreamOnAllow: ["authorization", "path", "x-auth-request-user", "x-auth-request-email"]
headersToDownstreamOnDeny: ["content-type", "set-cookie"]
EOF
kubectl -n istio-system patch configmap istio --patch "$(cat ${YAML_PATH}/auth-provider.yaml)"
kubectl rollout restart deployment/istiod -n istio-system
rm -rf ${YAML_PATH}/auth-provider.yaml
echo "Configuring AuthorizationPolicy on Istio Ingress Gateway"
kubectl apply -f ${YAML_PATH}/chatbot-auth-policy.yaml
rm -rf ${YAML_PATH}/chatbot-auth-policy.yaml
④proxy-protocol-envoy-filter.yaml
Istio Ingress Gatewayの設定istio-proxy-v2-config/proxy-protocol-envoy-filter.yaml
の、以下を修正します。
- Istioバージョンアップに伴う修正
Istioの1.18.xから1.19.xへのバージョンアップで下位互換性のない変更がされています。そのため、Istio 1.18.xが前提の元設定ではNLBとの疎通にエラーが発生します。本エラーに対応するためにistioのissues#39868とEnvoy Filterの情報を参考に、以下のように修正します。
- listener_filters:
- - name: envoy.filters.listener.proxy_protocol
- - name: envoy.filters.listener.tls_inspector
+ listener_filters:
+ - name: envoy.filters.listener.proxy_protocol
+ typed_config:
+ '@type': type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol
+ - name: envoy.filters.listener.tls_inspector
+ typed_config:
+ '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
修正後のproxy-protocol-envoy-filter.yamlの全体は以下のとおり
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: proxy-protocol
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: LISTENER
patch:
operation: MERGE
value:
listener_filters:
- name: envoy.filters.listener.proxy_protocol
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol
- name: envoy.filters.listener.tls_inspector
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
以上で設定の修正は完了です。
5. setupスクリプトの実行
ここからは、各スクリプトを実行して実際に環境構築を実施していきます。Cloud9のIDEのターミナルで以下のように実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x setup.sh
$ ./setup.sh
ここでは、環境変数の定義や依存関係ライブラリの取得、各リソース用ポリシー作成、S3バケット作成、ECRリポジトリ作成、アプリ用のイメージビルドなど実施されます。
setup.shの実行ログ例(アカウントIDなどはマスク)
$ ./setup.sh
export TEXT2TEXT_MODEL_ID=anthropic.claude-instant-v1
export EMBEDDING_MODEL_ID=amazon.titan-embed-text-v1
export BEDROCK_SERVICE=bedrock-runtime
export EKS_CLUSTER_NAME=multitenant-chatapp
export ISTIO_VERSION=1.22.3
Installing helper tools
Package jq-1.5-1.amzn2.0.2.x86_64 already installed and latest version
Package 1:bash-completion-2.1-6.amzn2.noarch already installed and latest version
Uninstalling AWS CLI 1.x
WARNING: Skipping awscli as it is not installed.
Installing AWS CLI 2.x
You can now run: /usr/local/bin/aws --version
aws-cli/2.18.3 Python/3.12.6 Linux/5.10.226-214.879.amzn2.x86_64 exe/x86_64.amzn.2
---------------------------
IAM role valid. You can continue setting up the EKS Cluster.
---------------------------
export RANDOM_STRING=XXXXXXXXX
Installing kubectl
Client Version: v1.30.0-eks-036c24b
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Installing bash completion for kubectl
Installing eksctl
‘/tmp/eksctl’ -> ‘/usr/local/bin/eksctl’
eksctl Version: 0.191.0
Installing bash completion for eksctl
AWS_REGION is ap-northeast-1
export ACCOUNT_ID=XXXXXXXXXXX
export AWS_REGION=ap-northeast-1
export AWS_DEFAULT_REGION=ap-northeast-1
ap-northeast-1
export YAML_PATH=yaml
make_bucket: envoy-config-XXXXXXXXX
export ENVOY_CONFIG_BUCKET=envoy-config-XXXXXXXXX
Creating S3 Bucket Policy for Envoy Dynamic Configuration Files
{
"Policy": {
"PolicyName": "s3-envoy-config-access-policy-XXXXXXXXX",
"PolicyId": "ANPAW3MECN2KS3CVJ6QYV",
"Arn": "arn:aws:iam::XXXXXXXXXXX:policy/s3-envoy-config-access-policy-XXXXXXXXX",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-10-10T03:42:35+00:00",
"UpdateDate": "2024-10-10T03:42:35+00:00"
}
}
Creating Contextual Data S3 Bucket for tenanta
make_bucket: contextual-data-tenanta-XXXXXXXXX
upload: data/Amazon_SageMaker_FAQs.csv to s3://contextual-data-tenanta-XXXXXXXXX/Amazon_SageMaker_FAQs.csv
S3 access policy for tenanta
{
"Policy": {
"PolicyName": "s3-contextual-data-access-policy-tenanta-XXXXXXXXX",
"PolicyId": "ANPAW3MECN2KROO6LLHW5",
"Arn": "arn:aws:iam::XXXXXXXXXXX:policy/s3-contextual-data-access-policy-tenanta-XXXXXXXXX",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-10-10T03:42:40+00:00",
"UpdateDate": "2024-10-10T03:42:40+00:00"
}
}
DynamoDB and Bedrock access policy for tenanta chatbot app
{
"Policy": {
"PolicyName": "dynamodb-access-policy-tenanta-XXXXXXXXX",
"PolicyId": "ANPAW3MECN2K7BF3377KO",
"Arn": "arn:aws:iam::XXXXXXXXXXX:policy/dynamodb-access-policy-tenanta-XXXXXXXXX",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-10-10T03:42:42+00:00",
"UpdateDate": "2024-10-10T03:42:42+00:00"
}
}
Creating Contextual Data S3 Bucket for tenantb
make_bucket: contextual-data-tenantb-XXXXXXXXX
upload: data/Amazon_EMR_FAQs.csv to s3://contextual-data-tenantb-XXXXXXXXX/Amazon_EMR_FAQs.csv
S3 access policy for tenantb
{
"Policy": {
"PolicyName": "s3-contextual-data-access-policy-tenantb-XXXXXXXXX",
"PolicyId": "ANPAW3MECN2KQGH66AYF7",
"Arn": "arn:aws:iam::XXXXXXXXXXX:policy/s3-contextual-data-access-policy-tenantb-XXXXXXXXX",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-10-10T03:42:47+00:00",
"UpdateDate": "2024-10-10T03:42:47+00:00"
}
}
DynamoDB and Bedrock access policy for tenantb chatbot app
{
"Policy": {
"PolicyName": "dynamodb-access-policy-tenantb-XXXXXXXXX",
"PolicyId": "ANPAW3MECN2K4OFTGGLJ2",
"Arn": "arn:aws:iam::XXXXXXXXXXX:policy/dynamodb-access-policy-tenantb-XXXXXXXXX",
"Path": "/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"CreateDate": "2024-10-10T03:42:49+00:00",
"UpdateDate": "2024-10-10T03:42:49+00:00"
}
}
documents:loaded:size=153
Documents:after split and chunking size=154
vector_db:created=<langchain.vectorstores.faiss.FAISS object at 0x7f7b28df9b20>::
S3 Bucket: $contextual-data-tenanta-XXXXXXXXX
documents:loaded:size=260
Documents:after split and chunking size=265
vector_db:created=<langchain.vectorstores.faiss.FAISS object at 0x7f7b2b764970>::
S3 Bucket: $contextual-data-tenantb-XXXXXXXXX
Creating Chatbot ECR Repository
Creating rag-api ECR Repository
export ECR_REPO_CHATBOT=multitenant-chatapp-XXXXXXXXX-chatbot
export REPO_URI_CHATBOT=XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-chatbot
export ECR_REPO_RAGAPI=multitenant-chatapp-XXXXXXXXX-rag-api
export REPO_URI_RAGAPI=XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-rag-api
Building Chatbot and RAG-API Images
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
{
"imageIds": [],
"failures": [
{
"imageId": {
"imageDigest": "None"
},
"failureCode": "InvalidImageDigest",
"failureReason": "Invalid request parameters: image digest should satisfy the regex '[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+'"
}
]
}
[+] Building 1.1s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile-app 0.0s
=> => transferring dockerfile: 1.34kB 0.0s
=> [internal] load metadata for public.ecr.aws/docker/library/python:3.11.4-slim 1.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 10.66kB 0.0s
=> [installer-image 1/5] FROM public.ecr.aws/docker/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf83b0ba3dc247013e2f43e1b5a045ad4901 0.0s
=> CACHED [stage-1 2/5] RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && DEBIAN_FRONTEND=noninteractive apt-get -qq upgrade - 0.0s
=> CACHED [stage-1 3/5] WORKDIR /home/streamlit/app 0.0s
=> CACHED [installer-image 2/5] WORKDIR /app 0.0s
=> CACHED [installer-image 3/5] RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && DEBIAN_FRONTEND=noninteractive apt-get -qq i 0.0s
=> CACHED [installer-image 4/5] ADD app/* ./ 0.0s
=> CACHED [installer-image 5/5] RUN pip install --user --upgrade -q -q pip && pip install --user -q -q -r requirements.txt 0.0s
=> CACHED [stage-1 4/5] COPY --chown=streamlit:streamlit --from=installer-image /root/.local /home/streamlit/.local/ 0.0s
=> CACHED [stage-1 5/5] COPY --chown=streamlit:streamlit app/*.py /home/streamlit/app/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:6d652c08d9893c9f6d7c70720d838bf73c51217e02b5a110486db89141e880d4 0.0s
=> => naming to XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-chatbot:latest 0.0s
The push refers to repository [XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-chatbot]
3df3f95fe208: Pushed
8e79ce1f95ac: Pushed
d53056c1cb0a: Pushed
90adf92c730f: Pushed
84aede4a88e1: Pushed
3e5ac6427acb: Pushed
c01ec0304ec8: Pushed
5b60283f3630: Pushed
511780f88f80: Pushed
latest: digest: sha256:b1ee3d2543c5e9248a49ace700a44bf909fff15793eeb288c05e76467b79b976 size: 2210
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
{
"imageIds": [],
"failures": [
{
"imageId": {
"imageDigest": "None"
},
"failureCode": "InvalidImageDigest",
"failureReason": "Invalid request parameters: image digest should satisfy the regex '[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+'"
}
]
}
[+] Building 0.5s (14/14) FINISHED docker:default
=> [internal] load build definition from Dockerfile-api 0.1s
=> => transferring dockerfile: 1.38kB 0.0s
=> [internal] load metadata for public.ecr.aws/docker/library/python:3.11.4-slim 0.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 8.28kB 0.0s
=> [installer-image 1/5] FROM public.ecr.aws/docker/library/python:3.11.4-slim@sha256:17d62d681d9ecef20aae6c6605e9cf83b0ba3dc247013e2f43e1b5a045ad4901 0.0s
=> CACHED [stage-1 2/5] RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && DEBIAN_FRONTEND=noninteractive apt-get -qq upgrade - 0.0s
=> CACHED [stage-1 3/5] WORKDIR /home/ragapi/app 0.0s
=> CACHED [installer-image 2/5] WORKDIR /app 0.0s
=> CACHED [installer-image 3/5] RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update -y 2>/dev/null >/dev/null && DEBIAN_FRONTEND=noninteractive apt-get -qq i 0.0s
=> CACHED [installer-image 4/5] ADD api/requirements.txt ./ 0.0s
=> CACHED [installer-image 5/5] RUN pip install --upgrade -q -q pip && pip install --user --upgrade -q -q pip && pip install --user -q -q -r requirements.txt & 0.0s
=> CACHED [stage-1 4/5] COPY --chown=ragapi:ragapi --from=installer-image /root/.local /home/ragapi/.local/ 0.0s
=> CACHED [stage-1 5/5] COPY --chown=ragapi:ragapi api/app /home/ragapi/app/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:cecec0cd243eba12af73f0102f9a517c876e94dc3d87e9e1738daaf5ca19f12a 0.0s
=> => naming to XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-rag-api:latest 0.0s
The push refers to repository [XXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/multitenant-chatapp-XXXXXXXXX-rag-api]
add10cf807e1: Pushed
7c9245b7838b: Pushed
b9b0d28a5952: Pushed
1b15e06f6043: Pushed
84aede4a88e1: Pushed
3e5ac6427acb: Pushed
c01ec0304ec8: Pushed
5b60283f3630: Pushed
511780f88f80: Pushed
latest: digest: sha256:53c434e46783d96c43d4848d9feadf9631a5be22dc1afe027a3671f20358cf9b size: 2209
Installing helm
Helm v3.16.1 is already latest
Version: v3.16.1
Generating a new key
Generating public/private rsa key pair.
Your identification has been saved in /home/ec2-user/.ssh/id_rsa.
Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:XXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ec2-user@ip-172-31-25-176.ap-northeast-1.compute.internal
The key's randomart image is:
+---[RSA 2048]----+
+----[SHA256]-----+
{
"KeyFingerprint": "XXXXXXXXXXXXXXXXXXXX",
"KeyName": "multitenant-chatapp-XXXXXXXXX",
"KeyPairId": "key-XXXXXXXXXXXXXXX"
}
export EC2_KEY_NAME=multitenant-chatapp-XXXXXXXXX
Creating KMS Key and Alias
export KMS_KEY_ALIAS=multitenant-chatapp-XXXXXXXXX
export MASTER_ARN=arn:aws:kms:ap-northeast-1:XXXXXXXXXXX:key/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6. DynamoDBテーブル作成
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x create-dynamodb-table.sh
$ ./create-dynamodb-table.sh
ここでは、チャット履歴用テーブルなどが作成されます。
create-dynamodb-table.shの実行ログ例
$ ./create-dynamodb-table.sh
Creating DynamoDB table Sessions_tenanta_XXXXXXX
Creating DynamoDB table ChatHistory_tenanta_XXXXXXX
Creating DynamoDB table Sessions_tenantb_XXXXXXX
Creating DynamoDB table ChatHistory_tenantb_XXXXXXX
7. EKSクラスター作成
以下のとおり実行します。このスクリプトの実行には完了まで30分程度かかることがあります。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x deploy-eks.sh
$ ./deploy-eks.sh
ここでは、EKSクラスターの構築やロールの作成、OIDCプロバイダーのクラスターとの関連付け、クラスターへのAWS Load Balancer Controllerのデプロイなどが実行されます。
deploy-eks.shの実行ログ例
$ ./deploy-eks.sh
EKS_CLUSTER_NAME is multitenant-chatapp
Deploying Cluster multitenant-chatapp with EKS 1.30
2024-10-10 03:50:08 [ℹ] eksctl version 0.191.0
2024-10-10 03:50:08 [ℹ] using region ap-northeast-1
2024-10-10 03:50:09 [ℹ] subnets for ap-northeast-1a - public:192.168.0.0/19 private:192.168.64.0/19
2024-10-10 03:50:09 [ℹ] subnets for ap-northeast-1c - public:192.168.32.0/19 private:192.168.96.0/19
2024-10-10 03:50:09 [ℹ] nodegroup "nodegroup" will use "" [AmazonLinux2023/1.30]
2024-10-10 03:50:09 [ℹ] using Kubernetes version 1.30
2024-10-10 03:50:09 [ℹ] creating EKS cluster "multitenant-chatapp" in "ap-northeast-1" region with managed nodes
2024-10-10 03:50:09 [ℹ] 1 nodegroup (nodegroup) was included (based on the include/exclude rules)
2024-10-10 03:50:09 [ℹ] will create a CloudFormation stack for cluster itself and 0 nodegroup stack(s)
2024-10-10 03:50:09 [ℹ] will create a CloudFormation stack for cluster itself and 1 managed nodegroup stack(s)
2024-10-10 03:50:09 [ℹ] if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-1 --cluster=multitenant-chatapp'
2024-10-10 03:50:09 [ℹ] Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "multitenant-chatapp" in "ap-northeast-1"
2024-10-10 03:50:09 [ℹ] configuring CloudWatch logging for cluster "multitenant-chatapp" in "ap-northeast-1" (enabled types: api, audit, authenticator, controllerManager, scheduler & no types disabled)
2024-10-10 03:50:09 [ℹ] default addons vpc-cni, kube-proxy, coredns were not specified, will install them as EKS addons
2024-10-10 03:50:09 [ℹ]
2 sequential tasks: { create cluster control plane "multitenant-chatapp",
2 sequential sub-tasks: {
5 sequential sub-tasks: {
1 task: { create addons },
wait for control plane to become ready,
associate IAM OIDC provider,
2 sequential sub-tasks: {
create IAM role for serviceaccount "kube-system/aws-load-balancer-controller",
create serviceaccount "kube-system/aws-load-balancer-controller",
},
update VPC CNI to use IRSA if required,
},
create managed nodegroup "nodegroup",
}
}
2024-10-10 03:50:09 [ℹ] building cluster stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:50:09 [ℹ] deploying stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:50:39 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:51:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:52:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:53:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:54:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:55:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:56:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:57:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:58:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:59:09 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-cluster"
2024-10-10 03:59:11 [!] recommended policies were found for "vpc-cni" addon, but since OIDC is disabled on the cluster, eksctl cannot configure the requested permissions; the recommended way to provide IAM permissions for "vpc-cni" addon is via pod identity associations; after addon creation is completed, add all recommended policies to the config file, under `addon.PodIdentityAssociations`, and run `eksctl update addon`
2024-10-10 03:59:11 [ℹ] creating addon
2024-10-10 03:59:12 [ℹ] successfully created addon
2024-10-10 03:59:12 [ℹ] creating addon
2024-10-10 03:59:12 [ℹ] successfully created addon
2024-10-10 03:59:13 [ℹ] creating addon
2024-10-10 03:59:13 [ℹ] successfully created addon
2024-10-10 04:01:14 [ℹ] building iamserviceaccount stack "eksctl-multitenant-chatapp-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-10-10 04:01:14 [ℹ] deploying stack "eksctl-multitenant-chatapp-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-10-10 04:01:14 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-10-10 04:01:44 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-10-10 04:02:40 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2024-10-10 04:02:40 [ℹ] created serviceaccount "kube-system/aws-load-balancer-controller"
2024-10-10 04:02:41 [ℹ] deploying stack "eksctl-multitenant-chatapp-addon-vpc-cni"
2024-10-10 04:02:41 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-addon-vpc-cni"
2024-10-10 04:03:11 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-addon-vpc-cni"
2024-10-10 04:03:11 [ℹ] updating addon
2024-10-10 04:03:22 [ℹ] addon "vpc-cni" active
2024-10-10 04:03:22 [ℹ] building managed nodegroup stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:03:22 [ℹ] deploying stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:03:22 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:03:52 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:04:23 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:05:44 [ℹ] waiting for CloudFormation stack "eksctl-multitenant-chatapp-nodegroup-nodegroup"
2024-10-10 04:05:44 [ℹ] waiting for the control plane to become ready
2024-10-10 04:05:45 [✔] saved kubeconfig as "/home/ec2-user/.kube/config"
2024-10-10 04:05:45 [ℹ] no tasks
2024-10-10 04:05:45 [✔] all EKS cluster resources for "multitenant-chatapp" have been created
2024-10-10 04:05:45 [✔] created 0 nodegroup(s) in cluster "multitenant-chatapp"
2024-10-10 04:05:45 [ℹ] nodegroup "nodegroup" has 3 node(s)
2024-10-10 04:05:45 [ℹ] node "ip-192-168-0-181.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [ℹ] node "ip-192-168-38-32.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [ℹ] node "ip-192-168-44-200.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [ℹ] waiting for at least 3 node(s) to become ready in "nodegroup"
2024-10-10 04:05:45 [ℹ] nodegroup "nodegroup" has 3 node(s)
2024-10-10 04:05:45 [ℹ] node "ip-192-168-0-181.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [ℹ] node "ip-192-168-38-32.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [ℹ] node "ip-192-168-44-200.ap-northeast-1.compute.internal" is ready
2024-10-10 04:05:45 [✔] created 1 managed nodegroup(s) in cluster "multitenant-chatapp"
2024-10-10 04:05:46 [ℹ] kubectl command should work with "/home/ec2-user/.kube/config", try 'kubectl get nodes'
2024-10-10 04:05:46 [✔] EKS cluster "multitenant-chatapp" in "ap-northeast-1" region is ready
Updated context arn:aws:eks:ap-northeast-1:XXXXXXXXXXX:cluster/multitenant-chatapp in /home/ec2-user/.kube/config
Associating an OIDC provider with the EKS Cluster
2024-10-10 04:05:48 [ℹ] IAM Open ID Connect provider is already associated with cluster "multitenant-chatapp" in "ap-northeast-1"
Installing AWS Load Balancer Controller
customresourcedefinition.apiextensions.k8s.io/ingressclassparams.elbv2.k8s.aws created
customresourcedefinition.apiextensions.k8s.io/targetgroupbindings.elbv2.k8s.aws created
eks already exists with the same configuration, skipping
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "eks" chart repository
...Successfully got an update from the "oauth2-proxy" chart repository
...Successfully got an update from the "istio" chart repository
Update Complete. ⎈Happy Helming!⎈
NAME: aws-load-balancer-controller
LAST DEPLOYED: Thu Oct 10 04:05:55 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!
Waiting for deployment "aws-load-balancer-controller" rollout to finish: 0 of 2 updated replicas are available...
deployment "aws-load-balancer-controller" successfully rolled out
Creating S3 Access Role in IAM
Attaching S3 Bucket policy to S3 Access Role
arn:aws:iam::XXXXXXXXXXX:role/multitenant-chatapp-s3-access-role-XXXXXXX
export ENVOY_IRSA=arn:aws:iam::XXXXXXXXXXX:role/multitenant-chatapp-s3-access-role-XXXXXXX
Creating DynamoDB / Bedrock Access Role in IAM
Attaching S3 Bucket and DynamoDB policy to Chatbot Access Role
Creating DynamoDB / Bedrock Access Role in IAM
Attaching S3 Bucket and DynamoDB policy to Chatbot Access Role
8. Istio Service Meshのデプロイ
7. EKSクラスター作成の完了後、Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x deploy-istio.sh
$ ./deploy-istio.sh
ここでは、Istio Ingress GatewayのデプロイなどIstio Service Meshの構築が実行されます。
deploy-istio.shの実行ログ例
master-role:~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock (main) $ ./deploy-istio.sh
Installing Istio with Ingress Gateway (NLB)
istio already exists with the same configuration, skipping
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "oauth2-proxy" chart repository
...Successfully got an update from the "eks" chart repository
...Successfully got an update from the "istio" chart repository
Update Complete. ⎈Happy Helming!⎈
namespace/istio-system created
namespace/istio-ingress created
NAME: istio-base
LAST DEPLOYED: Thu Oct 10 04:27:31 2024
NAMESPACE: istio-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Istio base successfully installed!
To learn more about the release, try:
$ helm status istio-base -n istio-system
$ helm get all istio-base -n istio-system
NAME: istiod
LAST DEPLOYED: Thu Oct 10 04:27:33 2024
NAMESPACE: istio-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
istiod successfully installed!
To learn more about the release, try:
$ helm status istiod -n istio-system
$ helm get all istiod -n istio-system
Next steps:
* Deploy a Gateway: https://istio.io/latest/docs/setup/additional-setup/gateway/
* Try out our tasks to get started on common configurations:
* https://istio.io/latest/docs/tasks/traffic-management
* https://istio.io/latest/docs/tasks/security/
* https://istio.io/latest/docs/tasks/policy-enforcement/
* Review the list of actively supported releases, CVE publications and our hardening guide:
* https://istio.io/latest/docs/releases/supported-releases/
* https://istio.io/latest/news/security/
* https://istio.io/latest/docs/ops/best-practices/security/
For further documentation see https://istio.io website
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-base istio-system 1 2024-10-10 04:27:31.844081791 +0000 UTC deployed base-1.22.3 1.22.3
istiod istio-system 1 2024-10-10 04:27:33.398846873 +0000 UTC deployed istiod-1.22.3 1.22.3
Creating Istio Ingress Gateway, associating an internet-facing NLB instance
with Proxy v2 protocol and cross-AZ loadbalancing enabled
Error: INSTALLATION FAILED: context deadline exceeded
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
istio-ingressgateway istio-ingress 1 2024-10-10 04:27:47.107675455 +0000 UTC failed gateway-1.22.3 1.22.3
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istiod ClusterIP 10.100.8.164 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 5m15s
NAME READY STATUS RESTARTS AGE
istiod-d56968787-kdxbz 1/1 Running 0 5m16s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.100.133.141 <pending> 15021:32338/TCP,80:32063/TCP,443:31846/TCP 5m4s
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-6b644dc8cb-9tdwc 1/1 Running 0 5m5s
Status of Load Balancer multitenant-chatapp-nlb: "active"
Enabling Proxy v2 protocol processing on Istio Ingress Gateway
error: error parsing istio-proxy-v2-config/proxy-protocol-envoy-filter.yaml: error converting YAML to JSON: yaml: line 15: did not find expected '-' indicator
Warning: EnvoyFilter exposes internal implementation details that may change at any time. Prefer other APIs if possible, and exercise extreme caution, especially around upgrades.
envoyfilter.networking.istio.io/ingressgateway-settings created
上記は④proxy-protocol-envoy-filter.yamlのYaml修正時にインデント等を誤っていたためエラーが出た例。Yaml修正して直接下記の様にコマンド再実行して無事完了。
$ kubectl -n istio-ingress apply -f istio-proxy-v2-config/proxy-protocol-envoy-filtamlya
Warning: EnvoyFilter exposes internal implementation details that may change at any time. Prefer other APIs if possible, and exercise extreme caution, especially around upgrades.
envoyfilter.networking.istio.io/proxy-protocol created
9. Cognitoユーザープールの作成
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。ここではtenantaとtenantbの2つのテナント用のCognitoユーザープールを作成します。スクリプト実行時に、各テストユーザーに設定されるパスワードが要求されるのでそれぞれ入力が必要です。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x deploy-userpools.sh
$ ./deploy-userpools.sh
ここでは、ユーザープールの作成に加えて、次の手順でデプロイされるテナントごとのプロキシの設定ファイルの生成なども実行されます。
deploy-userpools.shの実行ログ例
Creating User Pool for tenanta
Adding Custom Attributes to User Pool
Creating User Pool Client for tenanta
Setting User Pool Client Properties for tenanta
Creating User Pool Domain for tenanta
Creating user1@tenanta.com in tenanta_chatbot_example_com_XXXXXXX
Enter a Password for user1@tenanta.com: #パスワード設定が要求されるので入力
Setting User Custom Attributes for user1@tenanta.com
Creating AuthN Policy for tenanta
Creating AuthZ Policy for tenanta
Creating oauth2-proxy Configuration for tenanta
Creating User Pool for tenantb
Adding Custom Attributes to User Pool
Creating User Pool Client for tenantb
Setting User Pool Client Properties for tenantb
Creating User Pool Domain for tenantb
Creating user1@tenantb.com in tenantb_chatbot_example_com_XXXXXXX
Enter a Password for user1@tenantb.com: #パスワード設定が要求されるので入力
Setting User Custom Attributes for user1@tenantb.com
Creating AuthN Policy for tenantb
Creating AuthZ Policy for tenantb
Creating oauth2-proxy Configuration for tenantb
Creating AuthorizationPolicy on Istio Ingress Gateway
10. Istio Ingress Gatewayの設定
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x configure-istio.sh
$ ./configure-istio.sh
ここでは、以下が実施されます。
- 自己署名のルートCA証明書とキーの生成
- ルートCA署名のIstio Ingress Gateway証明書生成
- Istio Ingress Gatewayのクレデンシャル作成
- Gateway、Envoy リバース プロキシ、OIDCプロキシ、テナントのKubernetes名前空間の作成
- Istio Gatewayリソースのデプロイ
- Envoyリバースプロキシのデプロイ
- oauth2-proxyのデプロイ
- Istio外部認証プロバイダー定義の追加
configure-istio.shの実行ログ例
$ ./configure-istio.sh
Creating Root CA Cert and Key
Generating a 2048 bit RSA private key
............................................................................................................+++
....................+++
writing new private key to 'certs/ca_example_com.key'
-----
Creating Cert and Key for Istio Ingress Gateway
Generating a 2048 bit RSA private key
....+++
..................+++
writing new private key to 'certs/example_com.key'
-----
Signature ok
subject=/O=Cluster 1/CN=*.example.com
Getting CA Private Key
Creating TLS secret for Istio Ingress Gateway
secret/credentials created
Creating namespaces
namespace/llm-demo-gateway-ns created
namespace/envoy-reverse-proxy-ns created
namespace/tenanta-oidc-proxy-ns created
namespace/tenantb-oidc-proxy-ns created
namespace/tenanta-ns created
namespace/tenantb-ns created
Enabling sidecar injection in namespaces
namespace/envoy-reverse-proxy-ns labeled
namespace/tenanta-oidc-proxy-ns labeled
namespace/tenantb-oidc-proxy-ns labeled
namespace/tenanta-ns labeled
namespace/tenantb-ns labeled
NAME STATUS AGE ISTIO-INJECTION
default Active 54m
envoy-reverse-proxy-ns Active 13s enabled
istio-ingress Active 22m
istio-system Active 22m
kube-node-lease Active 54m
kube-public Active 54m
kube-system Active 54m
llm-demo-gateway-ns Active 14s
tenanta-ns Active 9s enabled
tenanta-oidc-proxy-ns Active 11s enabled
tenantb-ns Active 8s enabled
tenantb-oidc-proxy-ns Active 10s enabled
Applying STRICT mTLS Policy on all application namespaces
peerauthentication.security.istio.io/strict-mtls created
peerauthentication.security.istio.io/strict-mtls created
peerauthentication.security.istio.io/strict-mtls created
NAME MODE AGE
strict-mtls STRICT 4s
NAME MODE AGE
strict-mtls STRICT 3s
NAME MODE AGE
strict-mtls STRICT 4s
Deploying Istio Gateway resource
gateway.networking.istio.io/llm-demo-gateway created
Copying Envoy Dynamic Config files to S3 bucket
upload: envoy-config/envoy.yaml to s3://envoy-config-XXXXXXX/envoy.yaml
upload: envoy-config/envoy-lds.yaml to s3://envoy-config-XXXXXXX/envoy-lds.yaml
upload: envoy-config/envoy-cds.yaml to s3://envoy-config-XXXXXXX/envoy-cds.yaml
Deploying Envoy Reverse Proxy
serviceaccount/envoy-reverse-proxy-sa created
service/envoy-reverse-proxy created
deployment.apps/envoy-reverse-proxy created
Adding Istio External Authorization Provider
configmap/istio patched
deployment.apps/istiod restarted
Configuring AuthorizationPolicy on Istio Ingress Gateway
authorizationpolicy.security.istio.io/cluster1-auth-policy created
11. アプリケーションのデプロイ
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x deploy-tenant-services.sh
$ ./deploy-tenant-services.sh
ここでは、各テナント用のアプリコンテナのデプロイが実行されます。
deploy-tenant-services.shの実行ログ例
$ ./deploy-tenant-services.sh
oauth2-proxy already exists with the same configuration, skipping
Deploying tenanta services ...
-> Deploying chatbot service
serviceaccount/tenanta-sa created
deployment.apps/chatbot created
service/chatbot created
Applying Frontend Authentication Policy for tenanta
requestauthentication.security.istio.io/frontend-jwt-auth created
Applying Frontend Authorization Policy for tenanta
authorizationpolicy.security.istio.io/frontend-authz-pol created
-> Deploying VirtualService to expose chatbot via Ingress Gateway
virtualservice.networking.istio.io/chatbot created
Deploying OIDC Proxy for tenanta
NAME: oauth2-proxy
LAST DEPLOYED: Thu Oct 10 04:52:35 2024
NAMESPACE: tenanta-oidc-proxy-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
To verify that oauth2-proxy has started, run:
kubectl --namespace=tenanta-oidc-proxy-ns get pods -l "app=oauth2-proxy"
Deploying tenantb services ...
-> Deploying chatbot service
serviceaccount/tenantb-sa created
deployment.apps/chatbot created
service/chatbot created
Applying Frontend Authentication Policy for tenantb
requestauthentication.security.istio.io/frontend-jwt-auth created
Applying Frontend Authorization Policy for tenantb
authorizationpolicy.security.istio.io/frontend-authz-pol created
-> Deploying VirtualService to expose chatbot via Ingress Gateway
virtualservice.networking.istio.io/chatbot created
Deploying OIDC Proxy for tenantb
NAME: oauth2-proxy
LAST DEPLOYED: Thu Oct 10 04:52:47 2024
NAMESPACE: tenantb-oidc-proxy-ns
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
To verify that oauth2-proxy has started, run:
kubectl --namespace=tenantb-oidc-proxy-ns get pods -l "app=oauth2-proxy"
ここまでで、生成AIサービス環境構築のための基本的な手順は完了です。
12. hosts情報の出力
次に、アプリケーションにアクセスするための情報を出力させます。
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のとおり実行します。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x hosts-file-entry.sh
$ ./hosts-file-entry.sh
ここで出力されたhosts情報を利用してクライアントからアプリケーションにアクセスします。後程使うので出力された内容をコピーしておきます。
hosts-file-entry.shの実行ログ例
$ ./hosts-file-entry.sh
Status of Load Balancer multitenant-chatapp-nlb: active
You can update your hosts file with the following entries:
---------------------------------------------------------
XX.XXX.XXX.XXX tenanta.example.com
XX.XXX.XXX.XXX tenantb.example.com
XX.XXX.XX.XX tenanta.example.com
XX.XXX.XX.XX tenantb.example.com
13. アクセスエントリ
クラスター構築後、AWS管理コンソールから対象EKSクラスターを確認しようとしても、以下の様に権限がなくてアクセスできない状態となっています。アクセスエントリの作成
から設定していきます。
アクセスポリシーをアクセスエントリに関連付けるを参考に設定を進めていきます。
追加するポリシーはAmazonEKSClusterAdminPolicy
としています。
IAMプリンシパルARN
が現在コンソールにアクセスしているロールと一致することを確認し実行します。
設定完了後、EKSクラスター内のリソースが閲覧可能になっていることを確認します。
14. IP制限
本手順は構築するアプリケーションへのアクセスにIP制限が必要な場合に参考にしてください。
ロードバランサーをデプロイするマニフェストに設定を追加・再デプロイすることで、永続的な設定としてIP制限を行います。まず、ロードバランサーを構築するサービスを特定します。
Cloud9のIDEで新しくターミナルウィンドウを開き、以下のようにコマンド実行して確認していきます。
$ kubectl get namespace
NAME STATUS AGE
amazon-cloudwatch Active 67d
default Active 67d
envoy-reverse-proxy-ns Active 67d
istio-ingress Active 67d
istio-system Active 67d
kube-node-lease Active 67d
kube-public Active 67d
kube-system Active 67d
llm-demo-gateway-ns Active 67d
tenanta-ns Active 67d
tenanta-oidc-proxy-ns Active 67d
tenantb-ns Active 67d
tenantb-oidc-proxy-ns Active 67d
名前空間の一覧を表示させ、Istioに関連しそうなものを探します。次に以下の様にサービス一覧を取得して対象サービスを探します。
$ kubectl -n istio-ingress get svc
istio-ingressgateway LoadBalancer XX.XXX.XXX.XXX multitenant-chatapp-nlb-XXXXXXXXXXXXXXXXX.elb.ap-northeast-1.amazonaws.com 15021:32338/TCP,80:32063/TCP,443:31846/TCP 36d
istio-ingressgateway
がロードバランサーを構成しているサービスであることが確認できました。本手順では構築時のマニフェストが作成されていないので、ここではAWS管理コンソールのEKS > クラスター > multitenant-chatapp > Service > istio-ingress/istio-ingressgateway
からコピーして作成します。
rawビューで全体を表示・コピーし、Cloud9のIDEでマニフェスト用のJsonファイルを新規で作成します。Jsonファイル作成したら、"Amazon EKS の LoadBalancer タイプのサービスの CIDR IP アドレスを制限する方法を教えてください。"を参考に、以下2つを追加します。
// ... 略
"annotations": {
"meta.helm.sh/release-name": "istio-ingressgateway",
"meta.helm.sh/release-namespace": "istio-ingress",
// ... 略
+ "service.beta.kubernetes.io/aws-load-balancer-target-group-attributes": "preserve_client_ip.enabled=true"
},
// ... 略
// ... 略
"spec": {
// ... 略
+ "loadBalancerSourceRanges": "<クライアントIPアドレス>/32"
}
}
作成したマニフェストで以下のとおり更新実行してIP制限完了です。
$ kubectl apply -f <作成したJsonファイル名>
更新実施後、対象ロードランサーのセキュリティグループ設定が更新されていることが確認できます。
15. 削除スクリプト
生成AIサービス環境全体を削除する場合は以下の様にして削除実施します。また、環境構築時に設定ミスでエラー発生して再構築したくなったような場合も、各構築用スクリプトは基本的に冪等性を持っていないため、いったん削除スクリプトで全体削除してからやり直すことをお勧めします。
$ cd ~/environment/multi-tenant-chatbot-using-rag-with-amazon-bedrock
$ chmod +x cleanup.sh
$ ./cleanup.sh
VPC削除に失敗する場合は、直接依存関係のあるリソースを削除して、スタックの再削除で対応します。
動作確認
監視設定を有効化する前に、構築した生成AIアプリにアクセスできるか確認していきます。
クライアント環境の構築手順については本記事では省略しますが、本環境と同様にEC2インスタンスをクライアントとして構築する場合のポイントを記載しておきます。
- クライアント構築するVPCとEKSクラスターのVPCは分ける
- それぞれのルーティングや削除スクリプトに影響でないように
- プライベートサブネット内へのRDP環境構築はEC2 Instance Connect Endpointの記事など参照
- Windows Server利用の場合はブラウザアクセスできるようにWindows サーバーで Internet Explorer ESC をオフにする方法を参照
生成AIアプリにアクセスするために、12. hosts情報の出力で保存しておいたドメイン情報をクライアント(Windows)ローカルのhostsに追加します。
クライアントでブラウザを開き、以下どちらかのURLにアクセスします。
https://tenanta.example.com/
https://tenantb.example.com/
ここではテナントAのURLにアクセスします。自己署名証明書関連で警告が出ますが画面下の"Continuity to tenanta.example.com (unsafe)"
からアクセスします。
Cognitoの認証画面に遷移するので、テナントA用のユーザuser1@tenanta.com
と9. Cognitoユーザープールの作成で設定したパスワードでサインインします。
生成AIアプリ画面が表示されました。
適当に質問してみます。SageMaker QA(テナントA向けのドキュメント)と関係のない内容でもある程度回答してくれるようですね。
監視有効化
独自のサービスを使用してAmazon EKSクラスターでApplication Signalsを有効にするを参考にApplication Signalsを有効化していきます。
ワーカーノードの権限設定
EKSクラスターのワーカーノードの情報を各サービスに送信できるように、ワーカーノードのIAMロールに以下のポリシーを追加します。
--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
--policy-arn arn:aws:iam::aws:policy/AWSXRayWriteOnlyAccess
ワーカーノードのEC2インスタンスにアタッチされているロールをEC2管理画面等から特定し、上記ポリシーをアタッチします。
アドオン追加
EKSクラスターにCloudWatch Application Signals用のアドオンをインストールしていきます。
対象EKSクラスターのアドオン追加設定画面から、Amazon CloudWatch Observability EKS
アドオンを選択します。
設定したEKSバージョンに合わせてバージョンを設定し、IAMロールは未設定
のままで追加実行します。未設定
だと対象インスタンスにアタッチされているロールをそのまま使う設定となっています。
設定数分後にアドオンが有効化が完了します。
CloudWatch Application Signals有効化
CloudWatch管理画面に移動し、Application Signalsを有効にする
を実行します。
プラットフォームをEKS選択、EKSクラスターは構築したmultitenant-chatapp
を選択します。
セットアップオプションはコンソールを選択し、監視対象サービスを選んでいきます。
各テナントごとのサービスを選択し、対応するアプリの言語を選択していきます。
今回のアプリはPythonで実装されているため、Pythonを選択して設定完了します。
以上で各設定手順は完了です。
可視化確認
監視設定完了して数分後、再度クライアントから生成AIへのアクセス・チャット実行など実施すると、Application Signalsのサービス画面に以下の様にサービスが検出されます。
サービスマップも確認すると、それっぽいのが出ていますね。チャット履歴を保存するためのDynamoDBとのやりとり部分は可視化されるようです。
ただし、ここで紹介されているようなBedrockとのやりとり部分などは可視化されませんでした。このあたりについては、次回記事で深堀していきたいと思います。
SLO管理機能についても確認してみます。SLO作成画面から生成AIアプリであるchatbotサービスを選択します。
監視対象とするオペレーションでは、本環境ではクライアントからのアクセス(Get)については設定できそうです。BedrockへのAPI実行などはどうやらInternalOperation
にくくられてしまっているようですね。
監視条件としては、可用性やレイテンシが選択可能で、それぞれの達成目標率や警告の閾値なども設定できます。
SLO作成すると、直近の状況が可視化されます。
アプリケーション側の実装に手を入れなくても、CloudWatch Application Signalsを有効化するだけでこれら可視化・SLO管理ができることが確認できました。
おわりに
本記事では、AWSを活用して生成AIサービス環境(multi-tenant-chatbot-using-rag-with-amazon-bedrock)の構築手順をメインにまとめつつ、
CloudWatch Application Signalsでどのよう監視が可能かを確認してみました。
単純にCloudWatch Application Signalsを有効化するだけでは、あまり細かい粒度での可視化はできませんでしたが、かなりお手軽にEKSクラスター環境におけるサービス全体のレイテンシやエラー状況を可視化・SLO管理が可能となることが確認できました。
今回はあくまでEKSベースの環境であり、あまりゼロベースでこれからEKS環境を利用していく、というユースケースは少ないかもしれませんが何かしらの参考となれば幸いです。
次回はchatbotのアプリケーションコードにも手を入れつつ、もっと生成AIサービスの監視として詳細な監視ができないかどうかの深堀をしていきたいと思います。