実践ガイド:EdgeOneとHAIの連携活用

はじめに

現代のデジタル時代において、セキュリティと高速化はネットワークサービスの基盤となっています。EdgeOneは、テンセントクラウドが提供するエッジセキュリティ加速プラットフォームであり、グローバルに展開されたノードと強力なセキュリティ保護機能を通じて、ユーザーに安定した効率的なネットワーク体験を提供します。一方、HAI(HyperApplicationInventor)はテンセントクラウドが提供する高性能アプリケーションサービスで、使いやすいグラフィカルインターフェースと豊富なモデルライブラリを通じて、AIアプリケーション開発を容易にしています。本記事では、EdgeOneとHAIの連携がどのようにユーザーに安全かつ効率的なAIアプリケーション開発環境を提供するかを探ります。

EdgeOne:グローバル加速とセキュリティ保護

EdgeOneは、グローバルに分散したネットワークインフラを活用して、ユーザーに高速なアクセスを提供します。同時に、多層的なセキュリティ保護機能を備えており、DDoS攻撃、Web攻撃、BOT攻撃などの脅威からユーザーのサービスを守ります。

主要機能

  • DDoS保護:Anycastアーキテクチャを用いて25以上のクリーンセンタをグローバルに展開し、ネットワーク、トランスポート、アプリケーションレイヤーのDDoS攻撃を迅速に検出・除去します。
  • Web攻撃保護:広範な攻撃シグネチャライブラリを持ち、主要なセキュリティ脅威をカバーし、様々なWeb攻撃を阻止し、ゼロデイ脆弱性を防御します。AIを活用して検出精度を向上させ、誤報を減少させます。
  • スマートCC識別:長年の経験を活かし、ネットワークトラフィックと複数のパラメータを分析して自動的に攻撃源を識別・特定し、CC攻撃に対抗します。
  • BOT保護:プロトコル、IP、セッション特性を分析してBOTを識別し、データ分析を通じてモデルを構築し、悪意のあるクローラー攻撃を防止し、正常なクローラーへの誤検知を減少させます。
  • 攻撃追跡:異常イベントをキャプチャーし分析し、攻撃情報を抽出し、監視ページで攻撃詳細を表示し、ユーザーが防御戦略を調整するのを助けます。
  • 24時間監視:テンセントセキュリティチームが24/7でリアルタイム監視し、脅威を積極的に発見し対応し、セキュリティインシデントに迅速に応答します。

HAI:AIアプリケーションの迅速開発

HAIは、視覚的インタラクティブインターフェースを提供し、JupyterLab、WebUI等多种の計算リソース接続方法をサポートし、プログラミングの深い知識がないユーザーでもAIアプリケーション開発を簡単に行えます。この「手を動かせば開発できる」という設計コンセプトは、AI技術の応用のハードルを大幅に下げています。学術加速をサポートし、ルート自動最適化を通じて、主要な学術リソースプラットフォームのアクセスとダウンロード速度を大幅に向上させます。同時に、HAIはStableDiffusion、ChatGLMなどの人気モデルをプリインストールしており、ユーザーは数分で独自の大規模言語モデル、AI絵画などのアプリケーション環境を構築できます。

連携開発の概要

EdgeOneとHAIを連携させることで、コスト効率の高いAIアプリケーションデプロイメントを実現できます。HAIの高性能サーバーは時間単位で課金されるため、サーバーを停止してコストを節約できますが、再起動時にパブリックIPが変更され、アクセスパスが変わってしまいます。この問題をEdgeOneのエッジ関数で解決できます。

準備環境

HAI高性能サーバー

サーバーの作成手順は省略し、公式チュートリアルを参照してください。ここでは、モデルのダウンロード/アップロード、WEB UIを通じたAI絵画機能のデモンストレーション、Nginxのダウンロードと構成、APIインターフェースサービスの起動を重点的に説明します。

モデルのダウンロード/アップロード

モデルをダウンロードする際、JupyterLabページでのアップロードは速度が遅いため、COSオブジェクトストレージを選択しました。まず、Civitaiからモデルをダウンロードします。

モデルをローカルにダウンロードした後、COSオブジェクトストレージサービスを作成します。ストレージバケットを作成する際、地域はHAI絵画サーバーと同じである必要があります。そうしないと、余分なコストが発生します。

ストレージバケットに入り、ページの「アップロードファイル」をクリックし、ダウンロードしたモデルファイルを選択します。アップロード中はブラウザを操作しないでください。リフレッシュやブラウザを閉じると、すべての作業が無駄になります。

しばらく待ってから、モデルファイルの詳細をクリックし、基本情報ページに入ります。ここで、権限を「パブリック読み取り」に設定し、HAIサーバーがモデルファイルをダウンロードできるようにしてください。その後、一時リンクをコピーします。

HAIサーバーに入り、ターミナルで以下のコマンドを使用してモデルファイルをモデルフォルダーにダウンロードします。

cd stable-diffusion-webui/models/Stable-diffusion
wget 一時リンクをここに貼り付け

ダウンロードが完了するのを待ちます。

WEB UIによるAI絵画デモンストレーション

WEB UIページに移動し、モデルファイルを選択し、魔法のようなプロンプトを使用して試してみます。もちろん、プロンプトが良いほど、生成結果も良いでしょう。不明な点がある場合は、ダウンロードしたモデルの付属チュートリアルを参照してください。これで基本的には問題ありません。次に、HAIがサイトサービスを提供する方法を見てみましょう。

Nginxサイト構成

JupyterLabのターミナルで以下のコマンドを使用します:

  1. Nginxをダウンロード:コマンド apt install nginx を使用してインストールします。
  2. Nginxを起動:コマンド service nginx start を使用してサービスを起動します。

IPアドレスでブラウザにアクセスし、以下のインターフェースが表示されれば、インストールは成功です。

次に、Nginxの構成ファイルを編集します:

vim /etc/nginx/sites-available/default

ファイルに以下の構成を追加します:

# /imagesパスのアクセスルールを構成
location /images/  {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Expose-Headers' 'strict-origin-when-cross-origin';
            alias   /root/stable-diffusion-webui/outputs/star/images/;  # 静的ページ
            expires 1d;
            }
location /sdapi/  {
      # CORSヘッダーを追加
     add_header 'Access-Control-Allow-Origin' '*' always;
     add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
     add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
    proxy_pass  http://127.0.0.1:7862/sdapi/;
    if ($request_method = "OPTIONS"){
            return 200;
        }
    }

ここでは/imagesパスのアクセスルールとAPIサービスのプロキシを構成しています。なぜプロキシを構成する必要があるのでしょうか?これは、絵画のAPIサービスがクロスドメインアクセスをサポートしていないためです。そのため、リバースプロキシを追加しました。私たちのリクエストはPOST非単純リクエストであるため、ブラウザはOPTIONSのプリフライトリクエストも送信します。これも処理しました。クロスドメインエラーを避けるためです。

変更後、service nginx reloadコマンドを使用してNginxの構成を再読み込みします。構成した/imagesパスに対応するファイルが表示されます。このパスは自由に構成できますが、必ず画像を含めるようにしてください。画像を自分で管理することも、プログラムで生成することもできます。デモンストレーションのため、AI絵画で生成したいくつかの画像を単純にコピーしました。

Nginxを通じて画像にアクセスし、正常にアクセスできることを確認します。

ブラウザに以下のアドレスを入力してアクセスします:http://あなたのIP/images/1.png

APIインターフェースサービスの起動

Nginxの起動問題を解決するために、起動コマンドをAPI起動サービスインターフェースに直接書き込み、サーバー起動時にNginxが正しく構成されるようにします。

import subprocess

def start_services():
    # Nginxを起動
    nginx_result = subprocess.run(['service', 'nginx', 'start'], capture_output=True, text=True)
    
    # 結果をチェック
    if nginx_result.returncode == 0:
        print("Nginx起動成功")
    else:
        print("Nginx起動エラー")
        print("エラーメッセージ:", nginx_result.stderr)
    
    # AIサービスを起動
    ai_service_result = subprocess.run(['python', 'launch.py', '--nowebui', '--xformers', '--opt-split-attention', '--listen', '--port', '7862'], capture_output=True, text=True)
    
    if ai_service_result.returncode == 0:
        print("AIサービス起動成功")
    else:
        print("AIサービス起動エラー")
        print("エラーメッセージ:", ai_service_result.stderr)

# 関数を呼び出してサービスを起動
start_services()

このコードは、NginxとAIサービスを同時に起動する関数を定義しています。サーバーを再起動するたびに、サービスが自動的に起動し、安定性と信頼性を確保します。

EdgeOneエッジ関数

EdgeOneを構成する際、まず無ドメインサイトの構成を完了してください。無ドメインアクセスの構成は公式ドキュメントを参照してください。次に、エッジ関数を作成して対応するキャッシュ処理を実装します。エッジ関数を使用して、HAIサーバーのパブリックIPの変化に対応し、システムの安定性と信頼性を確保します。

画像処理関数

絵画アプリケーションの重要な機能の1つは、高品質な画像表示機能を提供することです。毕竟、絵画の視覚効果はユーザーを引きつける鍵です。画像表示機能が欠如すると、このアプリケーションは挿絵のない画集のようになり、魅力が乏しく、ユーザーの興味を引きつけるのが難しくなります。

async function imageHandler(event) {
    const cache = caches.default;
    const requestUrl = new URL(event.request.url);
    
    // 画像リクエストかどうかをチェック
    if (!requestUrl.pathname.match(/\.(jpg|jpeg|png|gif)$/)) {
        return new Response('Error: 画像URLではありません', { status: 400 });
    }
    
    // キャッシュキーを作成
    const cacheKey = new Request(requestUrl.searchParams.get('server') + requestUrl.pathname);
    
    try {
        // キャッシュから応答を取得
        let response = await cache.match(cacheKey);
        
        // キャッシュが存在しない場合、リモートリソースを取得してキャッシュ
        if (!response) {
            response = await fetchRemoteImage(event, cacheKey);
        }
        
        // キャッシュヒットのヘッダーを追加
        response.headers.append('x-edge-cache-status', 'hit');
        return response;
        
    } catch (error) {
        // キャッシュエラーの場合、リモートリソースを再取得
        await cache.delete(cacheKey);
        return await fetchRemoteImage(event, cacheKey);
    }
}

async function fetchRemoteImage(event, request) {
    const serverUrl = new URL(event.request.url);
    const remoteServer = serverUrl.searchParams.get('server');
    const remoteRequest = new Request(remoteServer + serverUrl.pathname);
    
    let response = await fetch(remoteRequest);
    
    // キャッシュコントロールヘッダーを追加
    response.headers.append('Cache-Control', 's-maxage=600');
    event.waitUntil(cache.put(remoteRequest, response.clone()));
    
    // キャッシュミスのヘッダーを追加
    response.headers.append('x-edge-cache-status', 'miss');
    return response;
}

addEventListener('fetch', event => {
    event.respondWith(imageHandler(event));
});

このコードは、ネットワークリクエストを処理し、リソースのキャッシュ戦略を管理し、ユーザーリクエストに迅速に応答しながら不要なネットワークトラフィックを減少させます。

静的Webページ関数

このエッジ関数はユーザーインタラクションを処理するために特別に設計されています。

const aiDrawingPage = `

<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI絵画生成</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .main-content {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
        }
        .drawing-area, .control-panel {
            flex: 1;
            min-width: 300px;
        }
        .image-display {
            width: 100%;
            height: 400px;
            background-color: #eee;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        textarea, select, input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 12px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
        }
        button:hover {
            background-color: #45a049;
        }
        .gallery {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 20px;
        }
        .gallery img {
            width: calc(33.33% - 10px);
            height: 150px;
            object-fit: cover;
            border-radius: 4px;
        }
        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
            }
            .gallery img {
                width: calc(50% - 10px);
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>AI絵画生成ツール</h1>
        </div>
        <div class="main-content">
            <div class="drawing-area">
                <div class="image-display" id="imageContainer">
                    <p>生成された画像がここに表示されます</p>
                </div>
            </div>
            <div class="control-panel">
                <div class="form-group">
                    <textarea id="prompt" placeholder="プロンプトを入力してください..." rows="4"></textarea>
                </div>
                <div class="form-group">
                    <textarea id="negativePrompt" placeholder="ネガティブプロンプトを入力してください..." rows="4"></textarea>
                </div>
                <div class="form-group">
                    <select id="resolution">
                        <option value="512:512">512x512</option>
                        <option value="768:768">768x768</option>
                        <option value="768:1024">768x1024</option>
                        <option value="1024:768">1024x768</option>
                    </select>
                </div>
                <div class="form-group">
                    <select id="style">
                        <option value="anime">アニメスタイル</option>
                        <option value="realistic">リアリスティック</option>
                        <option value="oil">油絵</option>
                    </select>
                </div>
                <div class="form-group">
                    <input type="password" id="apiKey" placeholder="アクセスキー">
                </div>
                <div class="form-group">
                    <button id="generateBtn">生成</button>
                </div>
            </div>
        </div>
        <div class="gallery" id="imageGallery">
            
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const generateBtn = document.getElementById('generateBtn');
            const imageContainer = document.getElementById('imageContainer');
            const imageGallery = document.getElementById('imageGallery');
            
            // 以前の画像を表示
            displayPreviousImages();
            
            generateBtn.addEventListener('click', function() {
                const prompt = document.getElementById('prompt').value;
                const negativePrompt = document.getElementById('negativePrompt').value;
                const resolution = document.getElementById('resolution').value;
                const style = document.getElementById('style').value;
                const apiKey = document.getElementById('apiKey').value;
                
                if (!prompt) {
                    alert('プロンプトを入力してください');
                    return;
                }
                
                generateImage(prompt, negativePrompt, resolution, style, apiKey);
            });
            
            function generateImage(prompt, negativePrompt, resolution, style, apiKey) {
                const [width, height] = resolution.split(':').map(Number);
                const data = {
                    prompt: prompt,
                    negative_prompt: negativePrompt,
                    width: width,
                    height: height,
                    style: style,
                    steps: 28,
                    guidance_scale: 7.5
                };
                
                fetch('https://your-edge-function-url/generate', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${apiKey}`
                    },
                    body: JSON.stringify(data)
                })
                .then(response => response.json())
                .then(result => {
                    if (result.image) {
                        displayGeneratedImage(result.image);
                        addToGallery(result.image);
                    } else {
                        alert('画像生成に失敗しました');
                    }
                })
                .catch(error => {
                    console.error('Error:', error);
                    alert('サーバーエラーが発生しました');
                });
            }
            
            function displayGeneratedImage(imageData) {
                imageContainer.innerHTML = `<img src="${imageData}" alt="生成された画像" style="max-width: 100%; max-height: 100%;">`;
            }
            
            function addToGallery(imageData) {
                const img = document.createElement('img');
                img.src = imageData;
                img.alt = '生成された画像';
                imageGallery.appendChild(img);
            }
            
            function displayPreviousImages() {
                // 以前の画像を表示するロジック
                const previousImages = [
                    'https://example.com/image1.jpg',
                    'https://example.com/image2.jpg',
                    'https://example.com/image3.jpg'
                ];
                
                previousImages.forEach(imgSrc => {
                    const img = document.createElement('img');
                    img.src = imgSrc;
                    img.alt = '以前の画像';
                    imageGallery.appendChild(img);
                });
            }
        });
    </script>
</body>
</html>`;

async function servePage(request) {
    return new Response(aiDrawingPage, {
        headers: {
            'Content-Type': 'text/html; charset=UTF-8',
            'Cache-Control': 'no-cache'
        }
    });
}

addEventListener('fetch', event => {
    event.respondWith(servePage(event.request));
});

このコードは、AI絵画アプリケーションのユーザーインターフェースを定義し、それと対話するJavaScriptコードを含みます。主な機能と構成要素は以下の通りです:

  • HTML構造:ページには、絵画結果の表示とテキスト記述の入力のための2つの主要部分をレイアウトするコンテナが含まれています。絵画結果部分には、AI生成画像を表示するための画像コンテナが含まれています。テキスト記述部分には、AI絵画を指導するための正の記述と負の記述を入力するための2つのテキストエリアが含まれています。AI絵画プロセスをトリガーするためのボタンがあります。
  • JavaScriptロジック:ページの読み込みが完了すると、以前の画像を表示する関数が呼び出されます。generateボタンのクリックイベントをリッスンし、ユーザー入力の記述と選択されたパラメータを収集し、AI絵画リクエストを構築し、サーバーに送信します。XMLHttpRequestオブジェクトを使用してPOSTリクエストを指定されたサーバーアドレスに送信し、応答を処理し、ページ上の画像コンテナを動的に更新して生成された画像を表示します。

最終的な効果

IPアドレスを分離し、最小限の変更量を確保しました。再起動が必要な場合は、IPアドレスを更新して再デプロイすれば、再度アクセスできます。

生成された画像の品質はまだ向上の余地があります。幅高比が正方形でない画像については、少し幅を広くまたは高くすることで、生成された画像がより観賞性を持つことができます。これらの問題はすべてデバッグを通じて解決できます。総じて、EdgeOneとHAIの連携は段階的な成果を収め、一区切りとします。

タグ: EdgeOne HAI AI開発 クラウドサービス セキュリティ

7月19日 00:14 投稿