はじめに
お疲れ様です。矢儀 @yuki_ink です。
この記事では、Difyのストレージ領域としてAmazon S3を利用する方法についてご紹介します。
S3を利用したいと思った背景の話になりますが、先日EC2でDifyを構築しました。
EC2のストレージ領域はEBS。
RAG目的でサイズが大きなドキュメントをアップロードすると、ストレージの空き容量がゴリゴリ削られていきます。
EBSは拡張も面倒だし、お値段もそれなりだし、、
あ~~~ S3使いたい~~~!!!
ということで、以下の対応を行いました。
前提
以下の記事で構築した環境で作業を行います。
※Difyの初期構築の手順は以下の記事を参照してください。
EC2(RHEL9)での操作を想定しますが、ローカルPC / Windows など異なる環境でも、対応手順の大きな流れは変わりません。
DifyでS3を使いたいという方には、本記事は何かの参考になると思います。
やったこと
- S3バケットの作成
- IAMユーザ(アクセスキー)の作成
- Difyが稼働するサーバでの環境変数の設定
1. S3バケットの作成
Dify用にS3バケットを用意します。
今回はすべてデフォルト値で作成しました。
自環境のセキュリティ要件などに従って設定してください。
2. IAMユーザ(アクセスキー)の作成
DifyからS3バケットにアクセスするためのIAMユーザを作成し、アクセスキーとシークレットアクセスキーを取得します。
アクセスキーの作成方法は以下の記事を参考にしてください。
IAMポリシーについては、今回は AmazonS3FullAccess
をアタッチしました。
自環境のセキュリティ要件などに従って設定してください。
また、S3を作成する際にKMSの暗号化を有効化した場合、KMSキーの利用に必要な権限もあわせて付与します。
当初、IAMユーザ(アクセスキー)ではなくIAMロールで制御したいと思っていたんですが、現状のDifyの標準機能ではIAMロールは対応しておらず、アクセスキーの利用が必要となるようです。
# IAMロールでやろうとしたときに出たエラー
api-1 | botocore.exceptions.ClientError: An error occurred (AuthorizationHeaderMalformed) when calling the PutObject operation: The authorization header is malformed; a non-empty Access Key (AKID) mustbe provided in the credential.
3. Difyが稼働するサーバでの環境変数の設定
Difyが稼働するサーバに接続し、以下の環境変数を設定します。
環境変数名 | 値 |
---|---|
STORAGE_TYPE | s3 |
STORAGE_LOCAL_PATH | storage |
S3_ENDPOINT | https://s3.(リージョン名).amazonaws.com (例)https://s3.us-west-2.amazonaws.com |
S3_BUCKET_NAME | ※1. S3バケットの作成 で作成したバケットの名前(例)dify-s3-bucket |
S3_ACCESS_KEY | ※2. IAMユーザ(アクセスキー)の作成 で取得したアクセスキーの値 |
S3_SECRET_KEY | ※2. IAMユーザ(アクセスキー)の作成 で取得したシークレットアクセスキーの値 |
S3_REGION | ※S3バケットを作成したリージョン (例)us-west-2 |
各環境変数についての詳細は公式ドキュメント「環境変数の説明」をご確認ください。
Linux環境では、以下のコマンドで設定できます。
ついでに、/etc/profile
にも書いておきましょう。
export STORAGE_TYPE='s3'
export STORAGE_LOCAL_PATH='storage'
export S3_ENDPOINT='https://s3.us-west-2.amazonaws.com'
export S3_BUCKET_NAME='dify-s3-bucket'
export S3_ACCESS_KEY='xxxxxxxxxxxxx'
export S3_SECRET_KEY='yyyyyyyyyyyyyyyyyyyyyyyyyyyy'
export S3_REGION='us-west-2'
ここまで来たら、サービスを再起動します。
cd dify/docker
#-d オプションをつけて、バックグラウンドで起動
docker compose up -d
dify/docker/docker-compose.yaml
の中身を見てみると、以下のような記述があり、ホストの環境変数を利用しているのが分かります。
S3_ENDPOINT: ${S3_ENDPOINT:-}
S3_BUCKET_NAME: ${S3_BUCKET_NAME:-}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-}
S3_SECRET_KEY: ${S3_SECRET_KEY:-}
S3_REGION: ${S3_REGION:-us-east-1}
動作確認
Difyにログインし、ナレッジの作成
からドキュメントをアップロードします。
今回はQiita CLIの README
を利用します。
簡単なRAGワークフローでテストしてみます。
大丈夫そう!!
S3バケットを確認すると、upload_files/
というフォルダが作成されており、配下にそれらしきファイルが格納されていました。
終わりに
以上、DifyとS3を連携させ、スケーラブルかつ安価なストレージを利用できるようになりました。
これでDifyに大きめのドキュメントをアップロードするときの罪悪感が軽くなりますw
Difyに関しては、AWS公式からもブログが出るほどAWSとの相性もよく、yamlファイルをいじらなくてもある程度のことはできそうです。
今後もDifyとAWSサービスの連携について、色々検証できたらと思います。
最後までお目通しいただき、ありがとうございました。
Tips (2024/12/3追記)
ある日突然、こんな事象が発生しました。
- Difyを開き、作成済みのチャットボットでプロンプトを入力すると、「Internal Server Error」というエラーメッセージが表示される。
※以前は使えていたものでもNG - ワークフロー選択画面で何か編集しようとしても、同様に「Internal Server Error」というエラーメッセージが表示される。
- Difyの設定画面でモデル一覧が読み込めない。
docker logs docker-api-1
コマンドでログを確認すると、こんなエラーが発生していました。
2024-12-03 08:38:18,900.900 ERROR [Dummy-12] [app.py:838] - Exception on /console/api/workspaces/current/model-providers [GET]
Traceback (most recent call last):
File "/app/api/extensions/storage/s3_storage.py", line 48, in load_once
data = client.get_object(Bucket=self.bucket_name, Key=filename)["Body"].read()
File "/app/api/.venv/lib/python3.10/site-packages/botocore/client.py", line 565, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/app/api/.venv/lib/python3.10/site-packages/botocore/client.py", line 1017, in _make_api_call
raise error_class(parsed_response, operation_name)
botocore.errorfactory.NoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/app/api/libs/rsa.py", line 55, in get_decrypt_decoding
private_key = storage.load(filepath)
File "/app/api/extensions/ext_storage.py", line 43, in load
return self.load_once(filename)
File "/app/api/extensions/ext_storage.py", line 46, in load_once
return self.storage_runner.load_once(filename)
File "/app/api/extensions/storage/s3_storage.py", line 51, in load_once
raise FileNotFoundError("File not found")
FileNotFoundError: File not found
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/app/api/.venv/lib/python3.10/site-packages/flask/app.py", line 880, in full_dispatch_request
rv = self.dispatch_request()
File "/app/api/.venv/lib/python3.10/site-packages/flask/app.py", line 865, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "/app/api/.venv/lib/python3.10/site-packages/flask_restful/__init__.py", line 489, in wrapper
resp = resource(*args, **kwargs)
File "/app/api/.venv/lib/python3.10/site-packages/flask/views.py", line 110, in view
return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
File "/app/api/.venv/lib/python3.10/site-packages/flask_restful/__init__.py", line 604, in dispatch_request
resp = meth(*args, **kwargs)
File "/app/api/controllers/console/setup.py", line 65, in decorated
return view(*args, **kwargs)
File "/app/api/libs/login.py", line 93, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
File "/app/api/controllers/console/wraps.py", line 22, in decorated
return view(*args, **kwargs)
File "/app/api/controllers/console/workspace/model_providers.py", line 38, in get
provider_list = model_provider_service.get_provider_list(tenant_id=tenant_id, model_type=args.get("model_type"))
File "/app/api/services/model_provider_service.py", line 46, in get_provider_list
provider_configurations = self.provider_manager.get_configurations(tenant_id)
File "/app/api/core/provider_manager.py", line 134, in get_configurations
custom_configuration = self._to_custom_configuration(
File "/app/api/core/provider_manager.py", line 640, in _to_custom_configuration
self.decoding_rsa_key, self.decoding_cipher_rsa = encrypter.get_decrypt_decoding(tenant_id)
File "/app/api/core/helper/encrypter.py", line 34, in get_decrypt_decoding
return rsa.get_decrypt_decoding(tenant_id)
File "/app/api/libs/rsa.py", line 57, in get_decrypt_decoding
raise PrivkeyNotFoundError("Private key not found, tenant_id: {tenant_id}".format(tenant_id=tenant_id))
libs.rsa.PrivkeyNotFoundError: Private key not found, tenant_id: 5bc686ba-491a-433c-bf79-7be863327732
2. ローカル展開ログで「ファイルが見つかりません」というエラーを修正する方法は? を参考にして、 docker exec -it docker-api-1 flask reset-encrypt-key-pair
コマンドを実行。
一応復旧しました、、
心当たりのある作業がないのですが、いったい何だったのか、、
もしかしたらこっちだったかも(未検証)