paint-brush
この Web IDE は、ラップトップを溶かしてしまうことなくクラウドでコードを実行します@oleksiijko
新しい歴史

この Web IDE は、ラップトップを溶かしてしまうことなくクラウドでコードを実行します

Oleksii Bondar12m2025/02/21
Read on Terminal Reader

長すぎる; 読むには

このプロジェクトは、機能を独立したサービスに分割できるマイクロサービス アーキテクチャの原則に基づいて構築されています。各コンポーネントは高度に専門化されたタスクを担当し、システムの柔軟性、スケーラビリティ、フォールト トレランスを保証します。このプロジェクトは Go プログラミング言語に基づいています。
featured image - この Web IDE は、ラップトップを溶かしてしまうことなくクラウドでコードを実行します
Oleksii Bondar HackerNoon profile picture
0-item
1-item

クラウドコンピューティングとマイクロサービスアーキテクチャの急速な発展の文脈では、セキュリティ、スケーラビリティ、高パフォーマンスを保証しながら、さまざまなプログラミング言語のコードを動的に実行する機能を提供する必要性が高まっています。この記事では、分離された環境でコード実行を実装するプロジェクトについて説明し、最新のWEB IDEに選択されたアーキテクチャソリューションの利点について説明します。このシステムは、行く、使用GRPC とは効果的なサービス間連携のため、レディスメッセージブローカーとしてドッカー実行環境を分離する。ウェブソケットサーバーは結果をリアルタイムで表示するために使用されます。

システムの主要コンポーネントがどのように構成されているか、代替ソリューションとどのように異なるか、これらのテクノロジを選択するとなぜ高いパフォーマンスとセキュリティを実現できるのかを詳しく説明します。


1. アーキテクチャの概要と主要コンポーネント

このプロジェクトは、機能を独立したサービスに分割できるマイクロサービス アーキテクチャの原則に基づいて構築されています。各コンポーネントは高度に専門化されたタスクを担当し、システムの柔軟性、スケーラビリティ、フォールト トレランスを保証します。


主なコンポーネント:


  • gRPC はサービス間通信に使用されます。次の理由から、マイクロサービス間でデータを転送するのに最適です。
    • バイナリ プロトコル (プロトコル バッファー): 高速かつコンパクトなデータ転送を保証します。
    • 厳密な型指定: データの転送と処理におけるエラーを回避するのに役立ちます。
    • 低レイテンシ: これは、サービス間の内部呼び出し (たとえば、gRPC サーバーと Redis キュー間) にとって重要です。
  • WebSocket サーバー: クライアントとの双方向通信を提供し、実行結果をリアルタイムで送信します。結果を含むキューをサブスクライブし、データをクライアントに転送して、コンパイルおよび実行ログを即座に表示します。
  • ワーカー: キューからタスクを取得し、一時的な作業環境を作成し、分離された Docker コンテナー内でコードを検証および実行し、実行結果をキューに公開する独立したサービス。
  • Redis: gRPC サーバーからワーカーにタスクを転送し、ワーカーから WebSocket サーバーに結果を転送するためのメッセージ ブローカーとして使用されます。Redis の利点は、高速性、Pub/Sub サポート、およびスケーリングの容易さです。
  • 内部モジュール:
    • コンパイラと Docker ランナー: ストリーム ログを使用して Docker コマンドを実行し、コンパイルと実行のプロセスをリアルタイムで監視できるようにするモジュールです。
    • 言語ランナー: さまざまな言語 (C、C++、C#、Python、JavaScript、TypeScript) のコードの検証、コンパイル、実行のロジックを組み合わせます。各ランナーは単一のインターフェースを実装するため、新しい言語の機能拡張が簡単になります。


以下の図は、gRPC、Redis、WebSocket を使用してクライアントからワーカー プロセスへ、およびその逆のデータ フローを示しています。


2. 技術と選択の根拠

行く

Goの利点:

  • パフォーマンスとスケーラビリティ: Go は実行速度が速く、これは多数の並列リクエストを処理する場合に特に重要です。

  • 組み込みの同時実行サポート: ゴルーチンとチャネルのメカニズムにより、複雑なマルチスレッド パターンを使用せずにコンポーネント間の非同期の相互作用を実装できます。


GRPC とは

gRPC の利点:

  • 効率的なデータ転送: バイナリ転送プロトコル (プロトコル バッファー) により、gRPC は低レイテンシと低ネットワーク負荷を実現します。
  • 強力な型付け: これにより、マイクロサービス間のデータの誤った解釈に関連するエラーの数が削減されます。
  • 双方向ストリーミングのサポート: これは、ログや実行結果をリアルタイムで交換する場合に特に便利です。

比較: REST API とは異なり、gRPC はサービス間のより効率的で信頼性の高い通信を提供します。これは、高度な同時実行システムにとって重要です。


レディス

なぜRedisなのか?

  • 高いパフォーマンス: Redis は 1 秒あたり多数の操作を処理できるため、タスク キューや結果キューに最適です。

  • Pub/Sub およびリストのサポート: キューとサブスクリプション メカニズムの実装が簡単なため、サービス間の非同期のやり取りを簡単に整理できます。

  • 他のメッセージ ブローカーとの比較: RabbitMQ や Kafka とは異なり、Redis は必要な構成が少なく、リアルタイム システムに十分なパフォーマンスを提供します。


ドッカー

Docker の役割:

  • 環境の分離: Docker コンテナを使用すると、完全に分離された環境でコードを実行できるため、実行の安全性が向上し、メイン システムとの競合のリスクが軽減されます。

  • 管理性と一貫性: Docker を使用すると、ホスト システムに関係なく、コードをコンパイルおよび実行するための同じ環境が提供されます。

  • 比較: ホスト上で直接コードを実行するとセキュリティ上のリスクが生じ、依存関係の競合が発生する可能性がありますが、Docker を使用するとこれらの問題を解決できます。


ウェブソケット

  • リアルタイム: クライアントとの永続的な接続により、データ (ログ、実行結果) を即座に転送できます。
  • ユーザー エクスペリエンスの向上: WebSocket を使用すると、IDE はコードの結果を動的に表示できます。


3. マイクロサービスアーキテクチャの利点

このプロジェクトではマイクロサービス アプローチを採用しており、次のような大きな利点がいくつかあります。


  • 独立したスケーリング: 各サービス (gRPC サーバー、Worker、WebSocket サーバー、Redis) は、負荷に応じて個別にスケーリングできます。これにより、リソースを効率的に使用し、リクエスト数の増加に迅速に対応できます。
  • フォールト トレランス: システムを独立したモジュールに分割すると、1 つのマイクロサービスに障害が発生してもシステム全体に障害が発生することはありません。これにより、全体的な安定性が向上し、エラーからの回復が簡単になります。
  • 開発と展開の柔軟性: マイクロサービスは独立して開発および展開されるため、新機能やアップデートの導入が簡単になります。また、特定のサービスごとに最適なテクノロジーを使用することもできます。
  • 統合の容易さ: 明確に定義されたインターフェース (gRPC 経由など) により、既存のアーキテクチャに大きな変更を加えずに新しいサービスを簡単に接続できます。
  • 分離とセキュリティ: 各マイクロサービスは独自のコンテナ内で実行できるため、安全でないコードの実行に伴うリスクが最小限に抑えられ、追加の保護層が提供されます。


4. 建築アプローチの比較分析

リモート コード実行用の最新の WEB IDE を構築する場合、さまざまなアーキテクチャ ソリューションが比較されることがよくあります。次の 2 つのアプローチを検討してみましょう。


アプローチ A: マイクロサービス アーキテクチャ (gRPC + Redis + Docker)


  • 遅延: 40 ミリ秒
  • スループット: 90 ユニット
  • セキュリティ: 85 ユニット
  • 拡張性: 90 ユニット


特徴:
このアプローチは、高速で信頼性の高いサービス間通信、コード実行の高度な分離、コンテナ化による柔軟なスケーリングを実現します。応答性とセキュリティが重要な最新の WEB IDE に最適です。


アプローチ B: 従来のモノリシック アーキテクチャ (HTTP REST + 集中実行)


  • 遅延: 70 ミリ秒
  • スループット: 65 ユニット
  • セキュリティ: 60 ユニット
  • 拡張性: 70 ユニット


特徴:
初期のバージョンの Web IDE でよく使用されていたモノリシック ソリューションは、HTTP REST と集中コード実行に基づいています。このようなシステムでは、スケーリングの問題、レイテンシの増加、および他のユーザーのコードを実行する際のセキュリティの確保の難しさなどが発生します。


注: WEB IDE 開発の最新の状況では、HTTP REST と集中実行のアプローチは、必要な柔軟性とスケーラビリティを提供しないため、マイクロサービス アーキテクチャの利点よりも劣ります。


比較指標の視覚化

グラフは、マイクロサービス アーキテクチャ (アプローチ A) がモノリシック ソリューション (アプローチ B) と比較して、レイテンシが低く、スループットが高く、セキュリティとスケーラビリティが優れていることを明確に示しています。


5. Dockerアーキテクチャ:分離とスケーラビリティ

システムのセキュリティと安定性の重要な要素の 1 つは、Docker の使用です。当社のソリューションでは、すべてのサービスが個別のコンテナにデプロイされるため、次のことが保証されます。


  • 実行環境の分離: 各サービス (gRPC サーバー、Worker、WebSocket サーバー) とメッセージ ブローカー (Redis) はそれぞれ独自のコンテナーで実行されるため、安全でないコードがメイン システムに影響を与えるリスクが最小限に抑えられます。同時に、ユーザーがブラウザーで実行するコード (たとえば、WEB IDE 経由) は、タスクごとに別の Docker コンテナーで作成され、実行されます。このアプローチにより、潜在的に安全でないコードやエラーのあるコードがメイン インフラストラクチャの動作に影響を与えることがなくなります。
  • 環境の一貫性: Docker を使用すると、開発環境、テスト環境、運用環境で設定が同じままになるため、デバッグが大幅に簡素化され、コード実行の予測可能性が確保されます。
  • スケーラビリティの柔軟性: 各コンポーネントは個別に拡張できるため、変化する負荷に効果的に適応できます。たとえば、リクエストの数が増えると、追加のワーカー コンテナーを起動できます。各ワーカー コンテナーは、ユーザー コードを実行するための個別のコンテナーを作成します。

このスキームでは、Worker は Redis からタスクを受信するだけでなく、リクエストごとに個別のコンテナ (コンテナ: コード実行) を作成し、ユーザー コードを個別に実行します。


6. コードの小さなセクション

以下は、システムの仕組みを示すコードの主要セクションの縮小バージョンです。

  1. グローバル ランナー レジストリを使用して実行する言語を決定します。
  2. RunInDockerStreaming 関数を使用してユーザー コードを実行するために Docker コンテナーを起動します。



1. ランナー登録による言語検出

システムはグローバル レジストリを使用し、各言語に独自のランナーがあります。これにより、ランナー インターフェイスを実装して登録するだけで、新しい言語のサポートを簡単に追加できます。


 package languages import ( "errors" "sync" ) var ( registry = make(map[string]Runner) registryMu sync.RWMutex ) type Runner interface { Validate(projectDir string) error Compile(ctx context.Context, projectDir string) (<-chan string, error) Run(ctx context.Context, projectDir string) (<-chan string, error) } func Register(language string, runner Runner) { registryMu.Lock() defer registryMu.Unlock() registry[language] = runner } func GetRunner(language string) (Runner, error) { registryMu.RLock() defer registryMu.RUnlock() if runner, exists := registry[language]; exists { return runner, nil } return nil, errors.New("unsupported language") }


新しい言語を登録する例:


 func init() { languages.Register("python", NewGenericRunner("python")) languages.Register("javascript", NewGenericRunner("javascript")) }


したがって、リクエストを受信すると、システムは以下を呼び出します。


 runner, err := languages.GetRunner(req.Language)


コードを実行するための対応するランナーを受け取ります。


2. コードを実行するためにDockerコンテナを起動する

各ユーザー コード要求ごとに、個別の Docker コンテナーが作成されます。これは、ランナー メソッド内 (たとえば、Run) で実行されます。コンテナーを実行するための主なロジックは、RunInDockerStreaming 関数にあります。


 package compiler import ( "bufio" "fmt" "io" "log" "os/exec" "time" ) func RunInDockerStreaming(image, dir, cmdStr string, logCh chan < -string) error { timeout: = 50 * time.Second cmd: = exec.Command("docker", "run", "--memory=256m", "--cpus=0.5", "--network=none", "-v", fmt.Sprintf("%s:/app", dir), "-w", "/app", image, "sh", "-c", cmdStr) cmd.Stdin = nil stdoutPipe, err: = cmd.StdoutPipe() if err != nil { return fmt.Errorf("error getting stdout: %v", err) } stderrPipe, err: = cmd.StderrPipe() if err != nil { return fmt.Errorf("error getting stderr: %v", err) } if err: = cmd.Start();err != nil { return fmt.Errorf("Error starting command: %v", err) } // Reading logs from the container go func() { reader: = bufio.NewReader(io.MultiReader(stdoutPipe, stderrPipe)) for { line, isPrefix, err: = reader.ReadLine() if err != nil { if err != io.EOF { logCh < -fmt.Sprintf("[Error reading logs: %v]", err) } break } msg: = string(line) for isPrefix { more, morePrefix, err: = reader.ReadLine() if err != nil { break } msg += string(more) isPrefix = morePrefix } logCh < -msg } close(logCh) }() doneCh: = make(chan error, 1) go func() { doneCh < -cmd.Wait() }() select { case err: = < -doneCh: return err case <-time.After(timeout): if cmd.Process != nil { cmd.Process.Kill() } return fmt.Errorf("Execution timed out") } }


この関数は、docker run コマンドを生成します。


  • image は、特定の言語 (ランナー構成によって定義) 用に選択された Docker イメージです。
  • dir は、このリクエスト用に作成されたコードを含むディレクトリです。
  • cmdStr は、コードをコンパイルまたは実行するためのコマンドです。


したがって、ランナーの Run メソッドを呼び出すと、次のことが起こります。


  • RunInDockerStreaming 関数は、コードが実行される Docker コンテナを起動します。
  • 実行ログは logCh チャネルにストリーミングされ、実行プロセスに関する情報をリアルタイムで送信できます。


3. 統合実行プロセス

コード実行のメインロジックの最小化されたフラグメント (executor.ExecuteCode):


 func ExecuteCode(ctx context.Context, req CodeRequest, logCh chan string) CodeResponse { // Create a temporary directory and write files projectDir, err: = util.CreateTempProjectDir() if err != nil { return CodeResponse { "", fmt.Sprintf("Error: %v", err) } } defer os.RemoveAll(projectDir) for fileName, content: = range req.Files { util.WriteFileRecursive(filepath.Join(projectDir, fileName), [] byte(content)) } // Get a runner for the selected language runner, err: = languages.GetRunner(req.Language) if err != nil { return CodeResponse { "", err.Error() } } if err: = runner.Validate(projectDir); err != nil { return CodeResponse { "", fmt.Sprintf("Validation error: %v", err) } } // Compile (if needed) and run code in Docker container compileCh, _: = runner.Compile(ctx, projectDir) for msg: = range compileCh { logCh < -"[Compilation]: " + msg } runCh, _: = runner.Run(ctx, projectDir) var output string for msg: = range runCh​​ { logCh < -"[Run]: " + msg output += msg + "\n" } return CodeResponse { Output: output } }


この最小限の例では、


  • 言語の検出は languages.GetRunner(req.Language) の呼び出しによって行われ、これにより新しい言語のサポートを簡単に追加できます。
  • Docker コンテナの起動は、RunInDockerStreaming を使用してコードを個別に実行する Compile/Run メソッド内に実装されています。


これらの重要なフラグメントは、システムが拡張性(新しい言語の簡単な追加)をサポートし、リクエストごとに個別の Docker コンテナーを作成することで分離を提供する方法を示しています。このアプローチにより、プラットフォームのセキュリティ、安定性、スケーラビリティが向上します。これは、最新の WEB IDE にとって特に重要です。

7. 結論

この記事では、gRPC + Redis + Docker スタックを使用したマイクロサービス アーキテクチャ上に構築されたリモート コード実行プラットフォームについて説明します。このアプローチにより、次のことが可能になります。


  • 効率的なサービス間通信により、レイテンシが短縮され、高いスループットが確保されます。
  • ユーザー要求ごとに個別のコンテナが作成され、コード実行が個別の Docker コンテナに分離されるため、セキュリティが確保されます。
  • マイクロサービスの独立したスケーリングにより、システムを柔軟にスケーリングします。
  • WebSocket を介して結果をリアルタイムで配信します。これは、最新の WEB IDE にとって特に重要です。


比較分析により、マイクロサービス アーキテクチャは、すべての主要な指標において従来のモノリシック ソリューションを大幅に上回るパフォーマンスを発揮することが示されています。このアプローチの利点は実際のデータによって確認されており、高性能でフォールト トレラントなシステムを作成するための魅力的なソリューションとなっています。



著者: オレクシイ・ボンダル
日付: 2025–02–07