AWSLab

CloudFormationでAWS上にDify環境を自動構築する【Docker導入から起動まで一括】

スポンサーラベル
CloudFormationが作る構成。テンプレートからIAMロール、セキュリティグループ、EC2を作成し、EC2上でDocker Composeにより計15個のコンテナが動く AWS

当サイトはアフィリエイト広告を利用しています。

社内で生成AIを使ったアプリを作りたいとなったとき、選択肢のひとつになるのがDifyです。オープンソースで、セルフホストできるため、社内データを外部のサービスに預けずに済むのが大きな利点です。

ただ、Difyは15個ものコンテナで構成されていて、公式の手順どおりに進めても環境ごとに引っかかる箇所が出てきます。そこで本記事では、EC2の起動からDocker・Docker Composeの導入、Difyの起動までをCloudFormationのテンプレート1本で完結させる構成を紹介します。作っては壊せるようにしておくと、検証も後片付けも楽になります。

なお本記事の手順は、東京リージョンで実際にデプロイして動作を確認しています。所要時間はスタック作成開始からCREATE_COMPLETEまで約7分でした。

1. どういう構成を作るのか

CloudFormationが作る構成。テンプレートからIAMロール、セキュリティグループ、EC2を作成し、EC2上でDocker Composeにより計15個のコンテナが動く

パブリックサブネットにEC2を1台立て、その上でDocker ComposeによりDifyを動かすシンプルな構成です。検証用途を想定しているため、RDSやElastiCacheには分離していません。

インスタンスタイプはt3.large(8GB)にしました。実際に起動した直後のメモリ使用量は約2.6GBだったので、t3.medium(4GB)でも動きますが余裕はほとんどありません。ナレッジを登録して使い込むことを考えると、8GBは見ておいたほうが安全です。

設計上のポイントは2つあります。

SSHの鍵を使わない:22番ポートを開けず、AWS Systems Manager Session Manager経由でログインします。鍵ファイルの管理が不要になり、鍵の紛失や共有といった悩みがなくなります。

Webの公開範囲を絞る:80番は自分のグローバルIPからのみ許可します。Difyは初回アクセスした人が管理者になる仕様なので、構築直後に全開放しておくのは危険です。

2. なぜCloudFormationにするのか

手順書として「EC2を立てて、Dockerを入れて、docker composeを叩いて…」と書くこともできますが、次の点でテンプレート化したほうが結果的に早くなります。

  • 検証で作って壊してを繰り返しても、毎回同じ環境が手に入る
  • 消し忘れによる課金を防げる。スタックを削除すれば関連リソースがまとめて消える
  • インスタンスタイプや許可IPをパラメータにしておけば、本番と検証で使い回せる

3. テンプレート

以下が全体です。長いですが、やっていることは「IAMロール」「セキュリティグループ」「EC2とUserData」の3つだけです。

AWSTemplateFormatVersion: "2010-09-09"
Description: Dify single instance with Docker Compose

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Type: AWS::EC2::Subnet::Id
  AllowedCidr:
    Type: String
    Description: 管理画面へのアクセスを許可するCIDR
    AllowedPattern: ^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$
  InstanceType:
    Type: String
    Default: t3.large
    AllowedValues: [t3.medium, t3.large, t3.xlarge]
  VolumeSize:
    Type: Number
    Default: 30
    MinValue: 20
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

Resources:
  # Session Manager でログインするためのロール
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref InstanceRole

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Dify web UI
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref AllowedCidr

  Instance:
    Type: AWS::EC2::Instance
    CreationPolicy:
      # UserData が完走するまで CREATE_COMPLETE にしない
      ResourceSignal:
        Timeout: PT25M
        Count: 1
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: !Ref InstanceType
      IamInstanceProfile: !Ref InstanceProfile
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref VolumeSize
            VolumeType: gp3
            DeleteOnTermination: true
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -xe
          exec > >(tee -a /var/log/dify-setup.log) 2>&1

          dnf install -y aws-cfn-bootstrap docker git
          systemctl enable --now docker
          usermod -aG docker ec2-user

          # Docker Compose v2 は AL2023 の標準リポジトリに無いので直接配置する
          DOCKER_CONFIG=/usr/libexec/docker/cli-plugins
          mkdir -p $DOCKER_CONFIG
          curl -sSL https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 \
            -o $DOCKER_CONFIG/docker-compose
          chmod +x $DOCKER_CONFIG/docker-compose

          # Dify を取得して起動する
          git clone --depth 1 https://github.com/langgenius/dify.git /opt/dify
          cd /opt/dify/docker
          cp .env.example .env
          docker compose up -d

          # 応答するまで待ってから完了を通知する
          for i in $(seq 1 60); do
            if curl -fsS http://localhost/ >/dev/null 2>&1; then
              break
            fi
            sleep 10
          done

          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} \
            --resource Instance --region ${AWS::Region}

Outputs:
  DifyUrl:
    Value: !Sub http://${Instance.PublicIp}/install
  SessionManagerCommand:
    Value: !Sub aws ssm start-session --target ${Instance} --region ${AWS::Region}

4. テンプレートの要点

4.1 AMIをハードコードしない

LatestAmiIdはSSMのパブリックパラメータを参照しています。AMI IDはリージョンごとに違ううえ、更新されるたびに変わります。ここを直書きすると、他のリージョンで動かなくなったり、古いAMIを使い続けることになります。

LatestAmiId:
  Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
  Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

この書き方にしておくと、デプロイのたびに最新のAmazon Linux 2023が使われます。

4.2 Docker Compose v2の導入

Amazon Linux 2023の標準リポジトリにはDocker Composeのプラグインが入っていませんdnf install docker-compose-pluginは通らないので、バイナリを直接配置します。

DOCKER_CONFIG=/usr/libexec/docker/cli-plugins
mkdir -p $DOCKER_CONFIG
curl -sSL https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 \
  -o $DOCKER_CONFIG/docker-compose
chmod +x $DOCKER_CONFIG/docker-compose

/usr/libexec/docker/cli-pluginsに置くことで、docker compose(ハイフンなし)のサブコマンドとして認識されます。

4.3 cfn-signalで「動いている」ことまで保証する

ここが今回いちばん伝えたい部分です。

デプロイから完了までの流れ。deployの後にUserDataが走り、docker compose upと疎通確認を経て、cfn-signalで通知して初めてCREATE_COMPLETEになる

UserDataは非同期に実行されるため、何もしないとEC2が起動した時点でスタックはCREATE_COMPLETEになります。しかしDifyのコンテナはまだイメージを取得している最中で、URLを開いてもつながりません。

CreationPolicycfn-signalを組み合わせると、UserDataが最後まで通ったことを確認してからスタックを完了扱いにできます。

CreationPolicy:
  ResourceSignal:
    Timeout: PT25M
    Count: 1

さらにUserDataの末尾で、実際に80番が応答するまで待ってから通知しています。これで「スタックはできたのにアプリが動いていない」という状態を避けられます

タイムアウトは余裕を見て25分にしていますが、実測ではEC2の作成開始からCREATE_COMPLETEまで約4分15秒、スタック全体でも約7分で完了しました。大半はコンテナイメージの取得時間です。

5. デプロイする

自分のグローバルIPを調べてから実行します。

# 自分のグローバルIPを確認
curl -s https://checkip.amazonaws.com

# デプロイ(VPC IDとサブネットIDは自分の環境のものに置き換える)
aws cloudformation deploy \
  --template-file dify-cloudformation.yaml \
  --stack-name dify \
  --capabilities CAPABILITY_IAM \
  --region ap-northeast-1 \
  --parameter-overrides \
    VpcId=vpc-xxxxxxxx \
    SubnetId=subnet-xxxxxxxx \
    AllowedCidr=203.0.113.10/32 \
    InstanceType=t3.large

CAPABILITY_IAMは、テンプレートがIAMロールを作るため必要です。付けずに実行すると弾かれます。

6. 動作確認

デプロイが完了したら、URLを取り出してブラウザで開きます。

aws cloudformation describe-stacks --stack-name dify \
  --region ap-northeast-1 \
  --query "Stacks[0].Outputs" --output table

DifyUrlに表示されたURL(http://<パブリックIP>/install)が初期設定画面です。最初にアクセスした人が管理者になるので、デプロイしたら早めに初期設定を済ませてください。

なお、EC2を停止して再度起動するとパブリックIPが変わります。検証を中断して再開する運用をするなら、Elastic IPの割り当てを検討してください。

コンテナの起動状況も確認しておきます。実際に動いていたのは以下の15個でした。

agent_backend      Up 2 minutes
agent_ssrf_proxy   Up 3 minutes
api                Up 2 minutes (healthy)
api_websocket      Up 2 minutes
db_postgres        Up 3 minutes (healthy)
local_sandbox      Up 2 minutes (healthy)
nginx              Up 2 minutes
plugin_daemon      Up 2 minutes
redis              Up 2 minutes (healthy)
sandbox            Up 3 minutes (healthy)
ssrf_proxy         Up 2 minutes
weaviate           Up 3 minutes
web                Up 2 minutes
worker             Up 2 minutes
worker_beat        Up 2 minutes

構築がうまくいかない場合は、Session Managerでログインしてログを見ます。

# SSH鍵は不要
aws ssm start-session --target i-xxxxxxxxxxxx --region ap-northeast-1

# UserData の実行ログ
sudo tail -100 /var/log/dify-setup.log

# コンテナの状態
cd /opt/dify/docker && sudo docker compose ps

7. 後片付け

検証が終わったらスタックごと削除します。EC2もEBSもセキュリティグループもまとめて消えるので、消し忘れによる課金を防げます。

aws cloudformation delete-stack --stack-name dify --region ap-northeast-1
aws cloudformation wait stack-delete-complete --stack-name dify --region ap-northeast-1

8. まとめ

Difyのような複数コンテナで動くアプリは、手順書で追いかけるより、テンプレートにまとめてしまったほうが確実です。要点を整理しておきます。

  • AMIはSSMパブリックパラメータで参照し、直書きしない
  • Amazon Linux 2023にはDocker Composeプラグインが無いので、バイナリを直接配置する
  • CreationPolicycfn-signalで、アプリが起動したことまで確認してから完了扱いにする
  • SSH鍵ではなくSession Managerを使い、22番ポートを開けない
  • Webの公開範囲は自分のIPに絞る。Difyは先着で管理者が決まる

なお本記事の構成は検証用です。本番運用するのであれば、データベースをRDSに、ファイルストレージをS3に分離し、ALB+ACMでHTTPS化する構成を検討してください。

DifyがどういうツールなのかについてはDifyとは何か——セルフホストで始める生成AIアプリ開発で整理しています。DockerのインストールそのものについてはAlmaLinuxにDockerとDocker Composeをインストールする方法でも解説しています。

次回は、ここで構築した環境のモデルプロバイダーとしてAWS Bedrockを使えるようにします。まずはAWS側の準備(Anthropicモデルのユースケース申請と推論プロファイル)から解説します。

あわせて読みたい

コメント

タイトルとURLをコピーしました