T-CREATOR

Dify のバージョン管理・運用アップデートの注意点

Dify のバージョン管理・運用アップデートの注意点

AI 開発プラットフォーム Dify の運用において、適切なバージョン管理とアップデート戦略は安定したサービス提供の要となります。本記事では、Dify のバージョン管理における課題と解決策を運用フェーズ別に詳しく解説いたします。

実際のエラーコードやトラブル事例を交えながら、初心者から上級者まで活用できる実践的な内容をお届けしますので、ぜひ最後までお読みください。

背景

Dify のバージョン管理が重要な理由

急速な機能拡張とアップデート頻度

Dify は活発に開発が進められているオープンソースプロジェクトです。月に複数回のアップデートがリリースされ、新機能の追加やバグ修正が継続的に行われています。

アップデート種別頻度影響範囲対応必要度
メジャーアップデート3-6 ヶ月破壊的変更あり
マイナーアップデート2-4 週間新機能追加
パッチアップデート1-2 週間バグ修正・セキュリティ

ビジネス継続性への影響

企業環境で Dify を運用する場合、アップデートの失敗は以下のような深刻な影響をもたらす可能性があります:

typescript// ビジネス影響の例
interface BusinessImpact {
  serviceDowntime: {
    duration: string;
    affectedUsers: number;
    revenueImpact: number;
  };
  dataIntegrity: {
    dataLoss: boolean;
    corruptedRecords: number;
    recoveryTime: string;
  };
  userExperience: {
    featureRegression: boolean;
    performanceDegradation: number;
    customerComplaints: number;
  };
}

const typicalFailureImpact: BusinessImpact = {
  serviceDowntime: {
    duration: '2-8 hours',
    affectedUsers: 1000,
    revenueImpact: 50000,
  },
  dataIntegrity: {
    dataLoss: false,
    corruptedRecords: 0,
    recoveryTime: '30 minutes',
  },
  userExperience: {
    featureRegression: true,
    performanceDegradation: 30,
    customerComplaints: 25,
  },
};

技術的な複雑性

Dify のアーキテクチャは複数のコンポーネントで構成されており、それぞれが異なるバージョン管理を必要とします:

yaml# Dify システム構成の例
services:
  dify-web:
    image: langgenius/dify-web:latest
    depends_on:
      - dify-api

  dify-api:
    image: langgenius/dify-api:latest
    depends_on:
      - db
      - redis
      - sandbox

  dify-worker:
    image: langgenius/dify-api:latest
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine

  redis:
    image: redis:6-alpine

  sandbox:
    image: langgenius/dify-sandbox:latest

課題

アップデート時に発生する典型的な問題

1. データベーススキーマの非互換性

bash# よくあるエラー例:データベースマイグレーション失敗
ERROR: Migration failed at version 0.6.8
Code: MIGRATION_SCHEMA_ERROR
Details: Column 'app_model_config' already exists in table 'apps'

# 関連エラー
FATAL: database "dify" does not exist
ERROR: relation "alembic_version" does not exist
ERROR: duplicate key value violates unique constraint "apps_pkey"

このエラーは、データベースの状態とアプリケーションが期待するスキーマ間に不整合がある場合に発生します。

2. 依存関係の競合

python# Python 依存関係の競合例
ERROR: pip's dependency resolver does not currently handle duplicate versions
Package versions in conflict:
- openai==1.3.5 (required by dify-api==0.6.8)
- openai==0.28.0 (required by langchain==0.0.325)

# Node.js 依存関係の競合
npm ERR! peer dep missing: react@>=16.8.0, required by @dify/ui-components@1.2.0
npm ERR! peer dep missing: next@>=13.0.0, required by @dify/web@0.6.8

3. 設定ファイルの互換性問題

bash# 設定ファイル関連のエラー
ERROR: Invalid configuration format
Code: CONFIG_PARSE_ERROR
File: /app/.env
Line: 42: VECTOR_STORE_TYPE=weaviate

# 新バージョンで必要な設定が不足
WARNING: Missing required environment variable 'INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH'
ERROR: Configuration validation failed for MAIL_SMTP_* settings

4. API 互換性の破綻

javascript// API レスポンス形式の変更による問題
// v0.6.7 以前
{
  "data": {
    "conversation_id": "uuid",
    "message": "response text"
  }
}

// v0.6.8 以降
{
  "conversation_id": "uuid",
  "answer": "response text",  // フィールド名変更
  "metadata": {               // 新しい構造
    "usage": {...}
  }
}

// 結果として発生するエラー
TypeError: Cannot read property 'message' of undefined
Code: API_RESPONSE_FORMAT_CHANGED

5. パフォーマンス劣化

bash# メモリ使用量の増加
WARNING: Memory usage exceeded 2GB (previous: 1.2GB)
OOMKilled: Container 'dify-api' was killed due to memory limit

# データベース接続プールの問題
ERROR: FATAL: sorry, too many clients already
Code: CONNECTION_POOL_EXHAUSTED
Active connections: 105/100

運用上の課題

チームワークフローとの乖離

bash# 開発チームと運用チームの連携不足による問題
# 開発環境: v0.6.9-dev
# ステージング環境: v0.6.7
# 本番環境: v0.6.6

# 結果として発生する問題
ERROR: Feature 'advanced_prompt_template' not available in production
Code: FEATURE_VERSION_MISMATCH
Required version: >=0.6.8
Current version: 0.6.6

ダウンタイムの長期化

多くの組織で、アップデート作業が想定時間を大幅に超過する事例が報告されています:

作業内容予定時間実際の時間主な遅延要因
データベースマイグレーション30 分2 時間大量データの移行処理
依存関係の更新15 分1 時間パッケージ競合の解決
設定ファイルの調整10 分45 分新しい必須設定の調査
動作確認テスト20 分1.5 時間予期しない機能劣化の発見

解決策

事前準備とバックアップ戦略

包括的なバックアップ計画

アップデート前の確実なバックアップは、安全な運用の基盤となります:

bash#!/bin/bash
# Dify 完全バックアップスクリプト

echo "🔄 Dify バックアップを開始します..."

# 1. データベースバックアップ
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backup/dify_${BACKUP_DATE}"
mkdir -p ${BACKUP_DIR}

echo "📊 データベースをバックアップ中..."
docker exec dify-db pg_dump -U postgres dify > ${BACKUP_DIR}/database.sql
if [ $? -eq 0 ]; then
    echo "✅ データベースバックアップ完了"
else
    echo "❌ データベースバックアップ失敗"
    exit 1
fi

# 2. 設定ファイルのバックアップ
echo "📋 設定ファイルをバックアップ中..."
cp -r /opt/dify/.env* ${BACKUP_DIR}/
cp -r /opt/dify/docker-compose.yml ${BACKUP_DIR}/
cp -r /opt/dify/nginx/ ${BACKUP_DIR}/

# 3. アップロードファイルのバックアップ
echo "📁 アップロードファイルをバックアップ中..."
tar -czf ${BACKUP_DIR}/storage.tar.gz /opt/dify/storage/

# 4. バックアップの整合性チェック
echo "🔍 バックアップの整合性を確認中..."
if [ -f "${BACKUP_DIR}/database.sql" ] && [ -s "${BACKUP_DIR}/database.sql" ]; then
    echo "✅ データベースバックアップ確認完了"
else
    echo "❌ データベースバックアップが不完全です"
    exit 1
fi

# 5. バックアップ情報の記録
cat > ${BACKUP_DIR}/backup_info.txt << EOF
Backup Date: ${BACKUP_DATE}
Dify Version: $(docker exec dify-api cat /app/version.txt 2>/dev/null || echo "Unknown")
Database Size: $(du -sh ${BACKUP_DIR}/database.sql | cut -f1)
Storage Size: $(du -sh ${BACKUP_DIR}/storage.tar.gz | cut -f1)
System Info: $(uname -a)
EOF

echo "🎉 バックアップが完了しました: ${BACKUP_DIR}"

自動バックアップの設定

bash# crontab 設定例
# 毎日午前2時に自動バックアップ実行
0 2 * * * /opt/dify/scripts/backup.sh >> /var/log/dify-backup.log 2>&1

# 週次でバックアップの古いファイルを削除(30日保持)
0 3 * * 0 find /backup -name "dify_*" -mtime +30 -exec rm -rf {} \;

段階的アップデート手法

Blue-Green デプロイメントの実装

yaml# docker-compose.blue-green.yml
version: '3.8'

services:
  # Blue環境(現在のバージョン)
  dify-web-blue:
    image: langgenius/dify-web:0.6.7
    container_name: dify-web-blue
    ports:
      - '3000:3000'
    networks:
      - dify-blue
    labels:
      - 'environment=blue'
      - 'version=0.6.7'

  dify-api-blue:
    image: langgenius/dify-api:0.6.7
    container_name: dify-api-blue
    networks:
      - dify-blue
    depends_on:
      - db-blue

  # Green環境(新しいバージョン)
  dify-web-green:
    image: langgenius/dify-web:0.6.8
    container_name: dify-web-green
    ports:
      - '3001:3000'
    networks:
      - dify-green
    labels:
      - 'environment=green'
      - 'version=0.6.8'

  dify-api-green:
    image: langgenius/dify-api:0.6.8
    container_name: dify-api-green
    networks:
      - dify-green
    depends_on:
      - db-green

  # ロードバランサー
  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    networks:
      - dify-blue
      - dify-green

networks:
  dify-blue:
    driver: bridge
  dify-green:
    driver: bridge

段階的切り替えスクリプト

bash#!/bin/bash
# Blue-Green 切り替えスクリプト

TARGET_ENV=${1:-green}
HEALTH_CHECK_URL="http://localhost:3001/health"

echo "🔄 ${TARGET_ENV} 環境への切り替えを開始します..."

# 1. Green環境のヘルスチェック
echo "🏥 ${TARGET_ENV} 環境のヘルスチェック中..."
for i in {1..30}; do
    if curl -s ${HEALTH_CHECK_URL} | grep -q "healthy"; then
        echo "✅ ${TARGET_ENV} 環境が正常に動作しています"
        break
    else
        echo "⏳ ${TARGET_ENV} 環境の起動を待機中... (${i}/30)"
        sleep 10
    fi

    if [ $i -eq 30 ]; then
        echo "❌ ${TARGET_ENV} 環境のヘルスチェックが失敗しました"
        exit 1
    fi
done

# 2. Nginx設定の更新
echo "🔧 ロードバランサー設定を更新中..."
if [ "${TARGET_ENV}" = "green" ]; then
    sed -i 's/dify-web-blue:3000/dify-web-green:3000/g' /opt/dify/nginx/nginx.conf
    sed -i 's/dify-api-blue:5001/dify-api-green:5001/g' /opt/dify/nginx/nginx.conf
else
    sed -i 's/dify-web-green:3000/dify-web-blue:3000/g' /opt/dify/nginx/nginx.conf
    sed -i 's/dify-api-green:5001/dify-api-blue:5001/g' /opt/dify/nginx/nginx.conf
fi

# 3. Nginx設定のリロード
docker exec nginx nginx -s reload
if [ $? -eq 0 ]; then
    echo "✅ ロードバランサー設定更新完了"
else
    echo "❌ ロードバランサー設定更新失敗"
    exit 1
fi

# 4. 切り替え完了確認
sleep 5
if curl -s http://localhost/health | grep -q "healthy"; then
    echo "🎉 ${TARGET_ENV} 環境への切り替えが完了しました"
else
    echo "❌ 切り替え後のヘルスチェックが失敗しました"
    exit 1
fi

互換性チェックとテスト戦略

自動化されたテストスイート

python# test_compatibility.py - 互換性テストスイート
import requests
import json
import pytest
from typing import Dict, Any

class DifyCompatibilityTester:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.api_key = api_key
        self.headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }

    def test_api_endpoints(self) -> Dict[str, Any]:
        """API エンドポイントの互換性テスト"""
        test_results = {}

        # 1. ヘルスチェック
        try:
            response = requests.get(f"{self.base_url}/health")
            test_results['health_check'] = {
                'status': 'PASS' if response.status_code == 200 else 'FAIL',
                'response_time': response.elapsed.total_seconds(),
                'details': response.json() if response.status_code == 200 else response.text
            }
        except Exception as e:
            test_results['health_check'] = {
                'status': 'ERROR',
                'error': str(e)
            }

        # 2. 認証テスト
        try:
            response = requests.get(
                f"{self.base_url}/console/api/setup",
                headers=self.headers
            )
            test_results['authentication'] = {
                'status': 'PASS' if response.status_code in [200, 401] else 'FAIL',
                'details': 'Auth endpoint accessible'
            }
        except Exception as e:
            test_results['authentication'] = {
                'status': 'ERROR',
                'error': str(e)
            }

        # 3. チャット API テスト
        try:
            test_payload = {
                "inputs": {},
                "query": "Hello, this is a compatibility test",
                "response_mode": "blocking",
                "user": "compatibility-test"
            }

            response = requests.post(
                f"{self.base_url}/v1/chat-messages",
                headers=self.headers,
                json=test_payload
            )

            test_results['chat_api'] = {
                'status': 'PASS' if response.status_code == 200 else 'FAIL',
                'response_format_valid': self._validate_chat_response(response.json() if response.status_code == 200 else {}),
                'details': response.json() if response.status_code == 200 else response.text
            }
        except Exception as e:
            test_results['chat_api'] = {
                'status': 'ERROR',
                'error': str(e)
            }

        return test_results

    def _validate_chat_response(self, response: Dict) -> bool:
        """チャット API レスポンス形式の検証"""
        required_fields = ['answer', 'conversation_id', 'message_id']
        return all(field in response for field in required_fields)

    def test_database_schema(self) -> Dict[str, Any]:
        """データベーススキーマの互換性テスト"""
        # データベース接続テスト用のエンドポイント呼び出し
        try:
            response = requests.get(
                f"{self.base_url}/console/api/apps",
                headers=self.headers
            )

            return {
                'status': 'PASS' if response.status_code == 200 else 'FAIL',
                'details': 'Database schema compatible' if response.status_code == 200 else 'Schema incompatibility detected'
            }
        except Exception as e:
            return {
                'status': 'ERROR',
                'error': str(e)
            }

# テスト実行スクリプト
if __name__ == "__main__":
    tester = DifyCompatibilityTester(
        base_url="http://localhost:5001",
        api_key="your-api-key"
    )

    results = tester.test_api_endpoints()
    schema_results = tester.test_database_schema()

    print("🧪 互換性テスト結果:")
    for test_name, result in results.items():
        status_emoji = "✅" if result['status'] == 'PASS' else "❌" if result['status'] == 'FAIL' else "⚠️"
        print(f"{status_emoji} {test_name}: {result['status']}")

        if result['status'] == 'ERROR':
            print(f"   エラー詳細: {result['error']}")

    print(f"\n📊 データベーススキーマテスト: {schema_results['status']}")

CI/CD パイプラインでの自動テスト

yaml# .github/workflows/dify-compatibility-test.yml
name: Dify Compatibility Test

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  compatibility-test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: difyai123456
          POSTGRES_DB: dify
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:6-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest requests

      - name: Start Dify services
        run: |
          docker-compose -f docker-compose.test.yml up -d
          sleep 60  # サービス起動待機

      - name: Run compatibility tests
        run: |
          python test_compatibility.py
          pytest tests/compatibility/ -v

      - name: Generate test report
        if: always()
        run: |
          pytest tests/compatibility/ --html=compatibility-report.html --self-contained-html

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: compatibility-test-results
          path: compatibility-report.html

ダウンタイム最小化のテクニック

ローリングアップデート戦略

bash#!/bin/bash
# ローリングアップデートスクリプト

NEW_VERSION=${1:-latest}
SERVICES=("dify-worker" "dify-api" "dify-web")
HEALTH_CHECK_RETRIES=30

echo "🔄 Dify v${NEW_VERSION} へのローリングアップデートを開始します..."

# 各サービスを順次更新
for service in "${SERVICES[@]}"; do
    echo "🔧 ${service} を更新中..."

    # 1. 新しいバージョンのイメージをプル
    docker pull langgenius/${service}:${NEW_VERSION}

    # 2. サービスの更新(1つずつ)
    docker-compose up -d --no-deps ${service}

    # 3. ヘルスチェック
    echo "🏥 ${service} のヘルスチェック中..."
    for i in $(seq 1 ${HEALTH_CHECK_RETRIES}); do
        if docker-compose exec ${service} curl -s http://localhost:5001/health | grep -q "healthy"; then
            echo "✅ ${service} が正常に起動しました"
            break
        else
            echo "⏳ ${service} の起動を待機中... (${i}/${HEALTH_CHECK_RETRIES})"
            sleep 10
        fi

        if [ $i -eq ${HEALTH_CHECK_RETRIES} ]; then
            echo "❌ ${service} のヘルスチェックが失敗しました"
            echo "🔙 ロールバックを実行します..."
            ./rollback.sh ${service}
            exit 1
        fi
    done

    # 4. 短時間の待機(接続の安定化)
    echo "⏳ 接続安定化のため待機中..."
    sleep 30
done

echo "🎉 ローリングアップデートが完了しました!"

ホットスワップ機能の実装

python# hot_swap.py - 無停止でのコンポーネント交換
import docker
import time
import logging
from typing import List, Dict

class DifyHotSwap:
    def __init__(self):
        self.client = docker.from_env()
        self.logger = logging.getLogger(__name__)

    def swap_service(self, service_name: str, new_image: str) -> bool:
        """
        サービスの無停止交換
        """
        try:
            # 1. 現在のサービス情報を取得
            current_container = self.client.containers.get(f"dify-{service_name}")
            current_config = current_container.attrs['Config']

            # 2. 新しいコンテナを起動(別ポートで)
            temp_port = self._get_available_port()
            new_container = self.client.containers.run(
                image=new_image,
                name=f"dify-{service_name}-new",
                environment=current_config['Env'],
                ports={f'{current_config["ExposedPorts"].keys()[0]}': temp_port},
                detach=True,
                network_mode=current_container.attrs['NetworkSettings']['Networks'].keys()[0]
            )

            # 3. 新しいコンテナのヘルスチェック
            if not self._wait_for_healthy(new_container, timeout=180):
                new_container.stop()
                new_container.remove()
                return False

            # 4. ロードバランサーの設定を更新
            self._update_load_balancer(service_name, temp_port)

            # 5. 古いコンテナを停止
            current_container.stop()
            current_container.remove()

            # 6. 新しいコンテナを正式なポートに移動
            new_container.stop()
            final_container = self.client.containers.run(
                image=new_image,
                name=f"dify-{service_name}",
                environment=current_config['Env'],
                ports=current_container.attrs['NetworkSettings']['Ports'],
                detach=True,
                network_mode=current_container.attrs['NetworkSettings']['Networks'].keys()[0]
            )

            new_container.remove()

            self.logger.info(f"Service {service_name} successfully swapped to {new_image}")
            return True

        except Exception as e:
            self.logger.error(f"Hot swap failed for {service_name}: {str(e)}")
            return False

    def _wait_for_healthy(self, container, timeout: int = 60) -> bool:
        """コンテナのヘルスチェック待機"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            try:
                container.reload()
                if container.status == 'running':
                    # カスタムヘルスチェック
                    result = container.exec_run("curl -s http://localhost:5001/health")
                    if result.exit_code == 0 and b"healthy" in result.output:
                        return True
            except Exception:
                pass
            time.sleep(5)
        return False

    def _get_available_port(self) -> int:
        """利用可能なポート番号を取得"""
        import socket
        sock = socket.socket()
        sock.bind(('', 0))
        port = sock.getsockname()[1]
        sock.close()
        return port

    def _update_load_balancer(self, service_name: str, new_port: int):
        """ロードバランサー設定の更新"""
        # Nginx設定ファイルの更新実装
        pass

ロールバック計画と緊急対応

自動ロールバック機能

bash#!/bin/bash
# rollback.sh - 自動ロールバックスクリプト

SERVICE_NAME=${1:-all}
BACKUP_VERSION=${2:-$(cat /opt/dify/backup/last_version.txt)}
ROLLBACK_TIMEOUT=300

echo "🔙 Dify ロールバックを開始します..."
echo "📋 対象: ${SERVICE_NAME}, バージョン: ${BACKUP_VERSION}"

# ロールバック前のヘルスチェック
check_service_health() {
    local service=$1
    local max_attempts=10

    for i in $(seq 1 $max_attempts); do
        if docker-compose exec $service curl -s http://localhost:5001/health >/dev/null 2>&1; then
            return 0
        fi
        sleep 3
    done
    return 1
}

# 個別サービスのロールバック
rollback_service() {
    local service=$1
    echo "🔄 ${service} をロールバック中..."

    # 現在のコンテナを停止
    docker-compose stop $service

    # 前のバージョンのイメージでコンテナを起動
    docker-compose run -d --name ${service}-rollback \
        langgenius/dify-${service}:${BACKUP_VERSION}

    # ヘルスチェック
    if check_service_health ${service}-rollback; then
        echo "✅ ${service} のロールバックが成功しました"
        # 元のコンテナを削除し、新しいコンテナをリネーム
        docker rm -f dify-${service}
        docker rename ${service}-rollback dify-${service}
        return 0
    else
        echo "❌ ${service} のロールバックが失敗しました"
        docker rm -f ${service}-rollback
        return 1
    fi
}

# データベースのロールバック
rollback_database() {
    echo "🗄️ データベースをロールバック中..."

    # 現在のデータベースをバックアップ
    EMERGENCY_BACKUP="/backup/emergency_$(date +%Y%m%d_%H%M%S).sql"
    docker exec dify-db pg_dump -U postgres dify > $EMERGENCY_BACKUP

    # 前のバックアップからリストア
    LAST_BACKUP=$(find /backup -name "dify_*.sql" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)

    if [ -f "$LAST_BACKUP" ]; then
        echo "📥 バックアップからリストア中: $LAST_BACKUP"
        docker exec -i dify-db psql -U postgres -d dify < $LAST_BACKUP

        if [ $? -eq 0 ]; then
            echo "✅ データベースのロールバックが完了しました"
            return 0
        else
            echo "❌ データベースのロールバックが失敗しました"
            # 緊急バックアップからリストア
            docker exec -i dify-db psql -U postgres -d dify < $EMERGENCY_BACKUP
            return 1
        fi
    else
        echo "❌ 利用可能なデータベースバックアップが見つかりません"
        return 1
    fi
}

# メインロールバック処理
case $SERVICE_NAME in
    "all")
        echo "🔄 全サービスのロールバックを実行します..."

        # データベースロールバック
        if ! rollback_database; then
            echo "💥 データベースのロールバックが失敗しました。処理を中止します。"
            exit 1
        fi

        # 各サービスのロールバック
        SERVICES=("dify-api" "dify-worker" "dify-web")
        for service in "${SERVICES[@]}"; do
            if ! rollback_service $service; then
                echo "💥 ${service} のロールバックが失敗しました"
                exit 1
            fi
        done
        ;;
    "database")
        rollback_database
        ;;
    *)
        rollback_service $SERVICE_NAME
        ;;
esac

# 最終ヘルスチェック
echo "🏥 最終ヘルスチェックを実行中..."
sleep 30

if curl -s http://localhost/health | grep -q "healthy"; then
    echo "🎉 ロールバックが正常に完了しました!"

    # ロールバック履歴の記録
    cat >> /var/log/dify-rollback.log << EOF
$(date): Rollback completed successfully
Service: $SERVICE_NAME
Target Version: $BACKUP_VERSION
Operator: $(whoami)
---
EOF

else
    echo "💥 ロールバック後のシステムが不安定です。緊急対応が必要です。"

    # 緊急アラートの送信
    curl -X POST "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" \
        -H 'Content-type: application/json' \
        --data '{"text":"🚨 Dify緊急事態: ロールバック失敗により系全体が不安定な状態です。即座の対応が必要です。"}'

    exit 1
fi

障害対応手順書

markdown# Dify 緊急障害対応手順

## レベル 1: サービス部分停止

### 症状

- 一部機能が利用できない
- レスポンス時間の軽微な増加
- エラー率 < 5%

### 対応手順

1. 影響範囲の特定
2. ログの確認とエラー分析
3. 該当サービスの再起動
4. 復旧確認

### 実行コマンド例

```bash
# サービス状態確認
docker-compose ps
docker-compose logs dify-api --tail=100

# 個別サービス再起動
docker-compose restart dify-api
```

## レベル 2: サービス大規模停止

### 症状

- 主要機能が利用できない
- エラー率 > 5%
- ユーザーからの問い合わせ増加

### 対応手順

1. 即座にステータスページを更新
2. ロードバランサーでトラフィック制御
3. 問題のあるサービスを隔離
4. ロールバックの実行を検討

### 実行コマンド例

```bash
# 緊急ロールバック実行
./rollback.sh all

# トラフィック制御
docker-compose scale dify-api=0
```

## レベル 3: システム全停止

### 症状

- 全サービスにアクセス不可
- データベース接続エラー
- システム全体の機能停止

### 対応手順

1. 緊急事態宣言の発令
2. 関係者への即座の通知
3. バックアップからの完全復旧
4. インシデント報告書の作成

### 実行コマンド例

```bash
# 緊急停止
docker-compose down

# バックアップからの完全復旧
./emergency_restore.sh
```

具体例

実際のアップデート手順とスクリプト

v0.6.7 から v0.6.8 への実アップデート例

bash#!/bin/bash
# update_v0.6.7_to_v0.6.8.sh
# 実際の Dify v0.6.7 → v0.6.8 アップデート手順

set -e  # エラー時に即座に終了

CURRENT_VERSION="0.6.7"
TARGET_VERSION="0.6.8"
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)

echo "🚀 Dify v${CURRENT_VERSION} から v${TARGET_VERSION} へのアップデートを開始します"

# ステップ1: 事前確認
echo "📋 事前確認を実行中..."

# バージョン確認
RUNNING_VERSION=$(docker exec dify-api cat /app/version.txt 2>/dev/null || echo "unknown")
if [ "$RUNNING_VERSION" != "$CURRENT_VERSION" ]; then
    echo "⚠️  警告: 現在のバージョン($RUNNING_VERSION)が期待値($CURRENT_VERSION)と異なります"
    read -p "続行しますか? (y/N): " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 1
    fi
fi

# ディスク容量確認
AVAILABLE_SPACE=$(df / | awk 'NR==2 {print $4}')
if [ $AVAILABLE_SPACE -lt 2097152 ]; then  # 2GB
    echo "❌ ディスク容量が不足しています (利用可能: ${AVAILABLE_SPACE}KB)"
    exit 1
fi

# ステップ2: バックアップ実行
echo "💾 バックアップを実行中..."
./backup.sh
if [ $? -ne 0 ]; then
    echo "❌ バックアップが失敗しました"
    exit 1
fi

# ステップ3: メンテナンスモードの有効化
echo "🔧 メンテナンスモードを有効化中..."
cat > /opt/dify/nginx/maintenance.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>メンテナンス中</title>
    <meta charset="utf-8">
</head>
<body style="text-align:center; padding-top:100px; font-family:Arial;">
    <h1>🔧 システムメンテナンス中</h1>
    <p>現在、システムのアップデート作業を行っております。</p>
    <p>しばらくお待ちください。</p>
    <p>完了予定時刻: $(date -d '+1 hour' '+%H:%M')</p>
</body>
</html>
EOF

# Nginx設定を一時的に変更
cp /opt/dify/nginx/nginx.conf /opt/dify/nginx/nginx.conf.backup
cat > /opt/dify/nginx/nginx.conf << 'EOF'
server {
    listen 80;
    location / {
        return 503;
    }
    error_page 503 /maintenance.html;
    location = /maintenance.html {
        root /usr/share/nginx/html;
        internal;
    }
}
EOF

docker exec nginx nginx -s reload

# ステップ4: v0.6.8 特有の準備作業
echo "🔧 v0.6.8 固有の準備作業を実行中..."

# 新しい環境変数の追加
cat >> /opt/dify/.env << 'EOF'

# v0.6.8 新規追加設定
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=1000
MAIL_SMTP_SSL_KEYFILE=
MAIL_SMTP_SSL_CERTFILE=
CODE_EXECUTION_ENDPOINT=http://sandbox:8194
CODE_EXECUTION_API_KEY=dify-sandbox
CODE_EXECUTION_MAX_TIMEOUT=30
EOF

# データベースマイグレーション事前チェック
echo "🔍 データベースマイグレーション事前チェック中..."
docker exec dify-db psql -U postgres -d dify -c "SELECT version_num FROM alembic_version;" > /tmp/db_version.txt

# ステップ5: 段階的アップデート実行
echo "🔄 段階的アップデートを実行中..."

# 5-1: Worker の更新(最初に更新、影響範囲が限定的)
echo "👷 Worker を更新中..."
docker-compose stop dify-worker
docker pull langgenius/dify-api:${TARGET_VERSION}
sed -i "s/dify-api:${CURRENT_VERSION}/dify-api:${TARGET_VERSION}/g" docker-compose.yml
docker-compose up -d dify-worker

# Worker のヘルスチェック
for i in {1..30}; do
    if docker-compose exec dify-worker curl -s http://localhost:5001/health | grep -q "healthy"; then
        echo "✅ Worker が正常に起動しました"
        break
    else
        echo "⏳ Worker の起動を待機中... (${i}/30)"
        sleep 10
    fi

    if [ $i -eq 30 ]; then
        echo "❌ Worker の起動に失敗しました。ロールバックします。"
        ./rollback.sh dify-worker
        exit 1
    fi
done

# 5-2: API の更新
echo "🔌 API を更新中..."
docker-compose stop dify-api
docker-compose up -d dify-api

# API のヘルスチェック(より厳密)
API_HEALTHY=false
for i in {1..60}; do
    # ヘルスエンドポイントをチェック
    if curl -s http://localhost:5001/health | grep -q "healthy"; then
        # データベース接続もチェック
        if curl -s http://localhost:5001/console/api/setup | grep -q -E "(has_active_admin|setup_status)"; then
            echo "✅ API が正常に起動しました"
            API_HEALTHY=true
            break
        fi
    fi

    echo "⏳ API の起動を待機中... (${i}/60)"
    sleep 10
done

if [ "$API_HEALTHY" != "true" ]; then
    echo "❌ API の起動に失敗しました。ロールバックします。"
    ./rollback.sh all
    exit 1
fi

# 5-3: Web UI の更新
echo "🌐 Web UI を更新中..."
docker pull langgenius/dify-web:${TARGET_VERSION}
docker-compose stop dify-web
sed -i "s/dify-web:${CURRENT_VERSION}/dify-web:${TARGET_VERSION}/g" docker-compose.yml
docker-compose up -d dify-web

# Web UI のヘルスチェック
for i in {1..30}; do
    if curl -s http://localhost:3000/ | grep -q "Dify"; then
        echo "✅ Web UI が正常に起動しました"
        break
    else
        echo "⏳ Web UI の起動を待機中... (${i}/30)"
        sleep 10
    fi

    if [ $i -eq 30 ]; then
        echo "❌ Web UI の起動に失敗しました。ロールバックします。"
        ./rollback.sh all
        exit 1
    fi
done

# ステップ6: 統合テスト実行
echo "🧪 統合テストを実行中..."
python3 /opt/dify/scripts/test_compatibility.py --version ${TARGET_VERSION}

if [ $? -ne 0 ]; then
    echo "❌ 統合テストが失敗しました。ロールバックします。"
    ./rollback.sh all
    exit 1
fi

# ステップ7: メンテナンスモード解除
echo "🔓 メンテナンスモードを解除中..."
cp /opt/dify/nginx/nginx.conf.backup /opt/dify/nginx/nginx.conf
docker exec nginx nginx -s reload

# 最終確認
sleep 10
if curl -s http://localhost/ | grep -q "Dify"; then
    echo "🎉 Dify v${TARGET_VERSION} へのアップデートが正常に完了しました!"

    # バージョン情報を記録
    echo "${TARGET_VERSION}" > /opt/dify/backup/last_version.txt

    # 成功ログの記録
    cat >> /var/log/dify-update.log << EOF
$(date): Update completed successfully
From: $CURRENT_VERSION
To: $TARGET_VERSION
Duration: $(($(date +%s) - $START_TIME)) seconds
Operator: $(whoami)
---
EOF

    # 関係者への通知
    curl -X POST "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" \
        -H 'Content-type: application/json' \
        --data "{\"text\":\"✅ Dify が v${TARGET_VERSION} に正常にアップデートされました!\"}"

else
    echo "💥 アップデート後の最終確認が失敗しました。"
    exit 1
fi

アップデート後の検証スクリプト

python# post_update_verification.py
import requests
import json
import time
import sys
from datetime import datetime

class PostUpdateVerification:
    def __init__(self, base_url: str = "http://localhost"):
        self.base_url = base_url
        self.results = []

    def run_all_tests(self) -> bool:
        """全ての検証テストを実行"""
        tests = [
            ("基本接続テスト", self.test_basic_connectivity),
            ("認証システムテスト", self.test_authentication_system),
            ("チャット機能テスト", self.test_chat_functionality),
            ("ファイルアップロードテスト", self.test_file_upload),
            ("データベース整合性テスト", self.test_database_integrity),
            ("API レスポンス形式テスト", self.test_api_response_format),
            ("パフォーマンステスト", self.test_performance),
        ]

        print("🔍 アップデート後検証を開始します...\n")

        all_passed = True
        for test_name, test_func in tests:
            print(f"⏳ {test_name} 実行中...")
            try:
                result = test_func()
                if result:
                    print(f"✅ {test_name} - 成功")
                else:
                    print(f"❌ {test_name} - 失敗")
                    all_passed = False
                self.results.append((test_name, result))
            except Exception as e:
                print(f"💥 {test_name} - エラー: {str(e)}")
                self.results.append((test_name, False, str(e)))
                all_passed = False

            time.sleep(2)  # テスト間隔

        return all_passed

    def test_basic_connectivity(self) -> bool:
        """基本的な接続テスト"""
        try:
            # Web UI への接続
            response = requests.get(f"{self.base_url}", timeout=10)
            if response.status_code != 200:
                return False

            # API への接続
            response = requests.get(f"{self.base_url}:5001/health", timeout=10)
            if response.status_code != 200:
                return False

            health_data = response.json()
            return health_data.get('status') == 'healthy'

        except Exception:
            return False

    def test_authentication_system(self) -> bool:
        """認証システムの動作確認"""
        try:
            # セットアップ状態の確認
            response = requests.get(f"{self.base_url}:5001/console/api/setup", timeout=10)
            return response.status_code in [200, 401]  # 正常または認証要求
        except Exception:
            return False

    def test_chat_functionality(self) -> bool:
        """チャット機能の基本動作確認"""
        try:
            # テスト用のリクエスト
            test_data = {
                "inputs": {},
                "query": "Hello, this is a post-update test",
                "response_mode": "blocking",
                "user": "post-update-test"
            }

            # Note: 実際の実装では適切な認証が必要
            headers = {
                'Authorization': 'Bearer test-key',
                'Content-Type': 'application/json'
            }

            response = requests.post(
                f"{self.base_url}:5001/v1/chat-messages",
                json=test_data,
                headers=headers,
                timeout=30
            )

            # v0.6.8 の新しいレスポンス形式を確認
            if response.status_code == 200:
                data = response.json()
                required_fields = ['answer', 'conversation_id', 'message_id']
                return all(field in data for field in required_fields)

            return False
        except Exception:
            return False

    def test_file_upload(self) -> bool:
        """ファイルアップロード機能の確認"""
        try:
            # テスト用の小さなファイルを作成
            test_content = "This is a test file for post-update verification"

            files = {
                'file': ('test.txt', test_content, 'text/plain')
            }

            headers = {
                'Authorization': 'Bearer test-key'
            }

            response = requests.post(
                f"{self.base_url}:5001/files/upload",
                files=files,
                headers=headers,
                timeout=15
            )

            return response.status_code in [200, 201]
        except Exception:
            return False

    def test_database_integrity(self) -> bool:
        """データベースの整合性確認"""
        try:
            # アプリケーション一覧の取得でDB接続を確認
            headers = {
                'Authorization': 'Bearer test-key'
            }

            response = requests.get(
                f"{self.base_url}:5001/console/api/apps",
                headers=headers,
                timeout=15
            )

            return response.status_code in [200, 401]  # 正常または認証エラー
        except Exception:
            return False

    def test_api_response_format(self) -> bool:
        """API レスポンス形式の互換性確認"""
        try:
            # ヘルスチェックのレスポンス形式を確認
            response = requests.get(f"{self.base_url}:5001/health", timeout=10)
            if response.status_code == 200:
                data = response.json()
                # v0.6.8 で期待される形式
                expected_fields = ['status', 'version']
                return any(field in data for field in expected_fields)
            return False
        except Exception:
            return False

    def test_performance(self) -> bool:
        """基本的なパフォーマンス確認"""
        try:
            start_time = time.time()
            response = requests.get(f"{self.base_url}:5001/health", timeout=10)
            response_time = time.time() - start_time

            # レスポンス時間が5秒以内であることを確認
            return response.status_code == 200 and response_time < 5.0
        except Exception:
            return False

    def generate_report(self):
        """検証結果のレポート生成"""
        report = {
            'timestamp': datetime.now().isoformat(),
            'total_tests': len(self.results),
            'passed_tests': sum(1 for result in self.results if result[1]),
            'failed_tests': sum(1 for result in self.results if not result[1]),
            'details': self.results
        }

        with open('/var/log/dify-post-update-verification.json', 'w') as f:
            json.dump(report, f, indent=2)

        print(f"\n📊 検証結果:")
        print(f"  総テスト数: {report['total_tests']}")
        print(f"  成功: {report['passed_tests']}")
        print(f"  失敗: {report['failed_tests']}")
        print(f"  成功率: {(report['passed_tests'] / report['total_tests'] * 100):.1f}%")

if __name__ == "__main__":
    verifier = PostUpdateVerification()
    success = verifier.run_all_tests()
    verifier.generate_report()

    if success:
        print("\n🎉 すべての検証テストが成功しました!")
        sys.exit(0)
    else:
        print("\n💥 一部の検証テストが失敗しました。確認が必要です。")
        sys.exit(1)

まとめ

Dify のバージョン管理・運用アップデートは、適切な計画と実行により安全かつ効率的に行うことができます。

今回ご紹介した運用フェーズ別のアプローチを実践することで、ビジネス継続性を保ちながら最新の機能を活用できます:

重要な成功要因

要因重要度実装コストROI
事前バックアップ戦略最高非常に高
段階的アップデート
自動化テスト
ロールバック計画最高非常に高
監視・アラート

運用レベル別の推奨アプローチ

スタートアップ・小規模チーム

  1. 基本的なバックアップ戦略の確立
  2. 手動での Blue-Green デプロイ
  3. シンプルなヘルスチェックの実装

中規模企業

  1. 自動化された CI/CD パイプラインの構築
  2. 包括的なテストスイートの開発
  3. 監視・アラートシステムの導入

エンタープライズ

  1. 完全自動化されたゼロダウンタイム更新
  2. 高度な監視・分析システム
  3. 災害復旧・事業継続計画との統合

次のステップ

  1. 現在の運用レベルを評価し、適切なアプローチを選択する
  2. バックアップ戦略を最優先で実装する
  3. 段階的に自動化レベルを向上させる
  4. 定期的な障害対応訓練を実施する
  5. 継続的な改善プロセスを確立する

適切なバージョン管理により、Dify の持つ革新的な機能を安全に活用し、ビジネスの成長を加速させることができるでしょう。

皆様の組織での Dify 運用が、安定性と革新性を両立した成功事例となることを願っています。

関連リンク