カピバラ好きなエンジニアブログ

興味ある技術とか検証した内容を赴くままに書いていきます。カピバラの可愛さこそ至高。

DifyのストレージをS3に変更する(CFn利用)

前回はCFnを利用してEC2とDifyのコミュニティ版をデプロイしましたが、デフォルトだとDify上でファイルをアップロードするとEBSに格納されてしまうため、利用を続けているとEBS容量が膨らんでしまいます。 そこで、DifyのストレージにAmazon S3を利用するように修正して、EBSにファイルが格納されないようにしてみます。

目次

実施作業

準備

今回は以下の手順で実行します。

  • CFnテンプレートの修正
  • CloudFormationサービスのコンソールからデプロイ


前提

手順を開始する時点の前提条件は以下です。
このあたりは前回と同じです。

  • VPC/Subnetは構築済み
  • EC2はPublic Subnet上に構築
  • Difyはユーザーデータを利用してインストール・デプロイする
  • Docker Composeのインストールもユーザーデータを利用する
  • EC2に設定するSecurity Groupは作成済み(インバウンドは自宅IPからのHTTP接続のみ許可)


CFnテンプレートの修正

CFnテンプレートをAIに修正させ、細かいところを手修正したCFnテンプレートが以下になります。
以下の内容が記載された「dify_cfn_template_s3.yaml」を作成します。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Dify Application Deployment on EC2 Instance with S3 Storage'

Parameters:
  InstanceName:
    Type: String
    Default: 'Dify-Server'
    Description: 'Name tag for the EC2 instance'
  
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: 'EC2 Key Pair for SSH access'
    ConstraintDescription: 'Must be the name of an existing EC2 KeyPair'
  
  VPCId:
    Type: AWS::EC2::VPC::Id
    Description: 'VPC ID for the EC2 instance'
  
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: 'Public subnet ID for the EC2 instance'
  
  SecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
    Description: 'Security Group ID for the EC2 instance'
    Default: ''
    AllowedPattern: '^(|sg-[0-9a-fA-F]{8,17})$'
    ConstraintDescription: 'Must be a valid Security Group ID or left blank to create a new one'

Resources:
  # S3 Bucket for Dify Storage
  DifyS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Sub 'dify-storage-${AWS::AccountId}-${AWS::Region}'

  DifyEC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-DifyEC2Role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
      Policies:
        - PolicyName: DifyS3AccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                Resource: !GetAtt DifyS3Bucket.Arn
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:DeleteObject
                Resource: !Sub '${DifyS3Bucket.Arn}/*'

  DifyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref DifyEC2Role

  DifyLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/dify/${InstanceName}'
      RetentionInDays: 30

  DifyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64}}'
      InstanceType: t3.medium
      KeyName: !Ref KeyPairName
      IamInstanceProfile: !Ref DifyInstanceProfile
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroupId
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp3
            VolumeSize: 20
            Iops: 3000
            Encrypted: true
            DeleteOnTermination: true
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash

          # システムアップデート
          dnf update -y

          # CloudWatch エージェントのインストール
          dnf install -y amazon-cloudwatch-agent

          # Dockerのインストール
          dnf install -y docker
          systemctl start docker
          systemctl enable docker

          # ec2-userをdockerグループに追加
          usermod -a -G docker ec2-user

          # Gitのインストール(Amazon Linux 2023には通常含まれているが念のため)
          dnf install -y git

          # Docker Compose(V2)のインストール
          DOCKER_COMPOSE_VERSION="v2.39.2"
          mkdir -p /usr/local/lib/docker/cli-plugins
          curl -SL "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-x86_64" -o /usr/local/lib/docker/cli-plugins/docker-compose
          chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

          # Difyのクローンとセットアップ
          cd /home/ec2-user
          git clone https://github.com/langgenius/dify.git --branch 1.8.1
          cd dify/docker
          cp .env.example .env

          # .envファイルのS3設定を更新
          sed -i 's/^STORAGE_TYPE=.*/STORAGE_TYPE=s3/' .env
          echo "" >> .env
          echo "# S3 Configuration" >> .env
          echo "S3_ENDPOINT=https://s3.ap-northeast-1.amazonaws.com" >> .env
          echo "S3_REGION=ap-northeast-1" >> .env
          echo "S3_BUCKET_NAME=${DifyS3Bucket}" >> .env
          echo "S3_ACCESS_KEY=" >> .env
          echo "S3_SECRET_KEY=" >> .env
          echo "S3_USE_AWS_MANAGED_IAM=true" >> .env

          # ファイルの所有権をec2-userに変更
          chown -R ec2-user:ec2-user /home/ec2-user/dify

          # CloudWatch エージェント設定ファイルの作成
          cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << 'EOF'
          {
            "agent": {
              "run_as_user": "root"
            },
            "logs": {
              "logs_collected": {
                "files": {
                  "collect_list": [
                    {
                      "file_path": "/var/log/cloud-init-output.log",
                      "log_group_name": "/dify/${InstanceName}",
                      "log_stream_name": "{instance_id}/cloud-init",
                      "timezone": "UTC"
                    }
                  ]
                }
              }
            }
          }
          EOF

          # Docker Composeでサービス起動
          sudo -u ec2-user docker compose up -d

          # CloudWatch エージェント起動
          /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
            -a fetch-config \
            -m ec2 \
            -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
            -s

          # ログファイルに完了メッセージを出力
          echo "Dify installation completed at $(date)" >> /var/log/cloud-init-output.log
          
      Tags:
        - Key: Name
          Value: !Ref InstanceName

Outputs:
  InstanceId:
    Description: 'EC2 Instance ID'
    Value: !Ref DifyEC2Instance
    Export:
      Name: !Sub '${AWS::StackName}-InstanceId'
  
  PublicIP:
    Description: 'Public IP address of the EC2 instance'
    Value: !GetAtt DifyEC2Instance.PublicIp
    Export:
      Name: !Sub '${AWS::StackName}-PublicIP'
  
  DifyURL:
    Description: 'URL to access Dify application'
    Value: !Sub 'http://${DifyEC2Instance.PublicIp}'
    Export:
      Name: !Sub '${AWS::StackName}-DifyURL'
  
  SSHCommand:
    Description: 'SSH command to connect to the instance'
    Value: !Sub 'ssh -i <your-key.pem> ec2-user@${DifyEC2Instance.PublicIp}'

  S3BucketName:
    Description: 'S3 Bucket name for Dify storage'
    Value: !Ref DifyS3Bucket
    Export:
      Name: !Sub '${AWS::StackName}-S3BucketName'


CloudFormationサービスのコンソールからデプロイ

前回と同じ要領でCloudFormationのスタックを作成します。

テンプレートに設定したEC2を構築するために必要なパラメータを指定して実行します。

  • Stack name:Dify-stack
  • InstanceName:Dify-Server
  • KeyPairName:既存のキーペアを指定する
  • SecurityGroupId:既存のSGを指定する
  • SubnetId:既存のPublic Subnetを指定する
  • VPCId:既存のVPCを指定する

数分経過後、CREATE_COMPLETE が表示されて正常終了しました。


Difyアクセス&ファイルアップロード

CFnスタックのOutputsタブから http://your_ip_address/install にアクセスしてDify画面が確認できました。

ログイン後、ナレッジ→ナレッジベースを作成を押下します。

適当なファイルをアップロードしてナレッジベースを作成します。


S3出力確認

CFnスタックのOutputsに表示されているS3バケット名をコピペして、S3コンソールから対象のバケットを表示します。

自動でパスが作成されており、upload_filsパスにはアップロードしたファイルが(名前は変わってますが)格納されていることが確認できました。


感想及び所感

ナレッジベースにアップロードするファイルはサイズが大きくなりがちだと思うので、S3を使えるとコストや容量の面で助かることは多そうです。

この記事がどなたかの参考になれば幸いです。