さくらのクラウドとTerraformを活用したDIVER OSINT CTFにおけるインフラ基盤

はじめに

こんにちは。DIVER OSINT CTF インフラチームのy-chanとxryuseixです。DIVER OSINT CTF 2026では、インフラの大部分にパートナーであるさくらインターネットさんの「さくらのクラウド」を利用しました。本記事では、DIVER OSINT CTFのインフラ基盤と、その運用について紹介します。

インフラ環境におけるIaC

DIVER OSINT CTF・SWIMMER OSINT CTFでは継続して新しい試みを行っており、運営を円滑に行いつつCTFのユーザー体験も向上させるために、スコアサーバ(CTFd)のプラグインを独自に構築したり、サーバー負荷について検証を行ったりしながらCTFを運用しています。そのため、ステージング環境やサーバー負荷のモニタリングを行う環境の構築が必要になります。

今回は最も強力な構成の本番環境に加え、最低限の構成で構築されたステージング環境とモニタリング環境の計3台のサーバーが稼働していました。

前回のDIVER OSINT CTF・SWIMMER OSINT CTFのインフラは主に手動でデプロイを行っていましたが、さくらのクラウドのようなサーバーのカスタム構成を組みやすい環境では、構成の指定を複数台分管理するのは少し面倒です。

そんな中、さくらのクラウドでは公式のTerraformライブラリが提供されています(Terraform Registry)。今回はこのTerraformライブラリを活用し、デプロイ全体で全面的にTerraformとさくらのクラウドの機能を活用しました。

さくらのクラウドにはVMのディスクイメージを「アーカイブ」する機能があります。このアーカイブを使うことで、様々な操作を行ったVMの状態を手軽にコピーすることができます。

今回の場合、まず最初にTerraformからUbuntuでステージング環境を構築し、Docker・CTFdの導入や初期設定、プラグインの導入・検証などを行いました。その状態をアーカイブしておき、本番環境をそのアーカイブイメージから起動することで、ステージング環境で試した構成と全く同じものを再現することができます。これにより、低スペックなステージング環境で動作確認済みのものを、高スペックな本番環境にそのままデプロイすることができるので、非常に便利かつ再現性を保てます。

このアーカイブイメージの指定も、さくらのクラウドのUI上で確認可能なIDをTerraformで指定するだけでよく、非常に簡単に行えます。

アーカイブの利用にあたっては少々お金はかかりますが、人間が手動で2回デプロイするよりも確実性・再現性が高く、時間の短縮やミスなどによる精神的な負担も減らせることは大いにメリットとなります。

モニタリングとsakuracloud_exporter

今まではサーバーのモニタリングと死活監視にMackerelを活用していました。MackerelではAPM機能があり、OpenTelemetryによるテレメトリを受信できます。しかし、以前に開催したSWIMMER OSINT CTFでAPM機能を活用したモニタリングを行った際、CTFdからのテレメトリ数が膨大になってしまい、Mackerel側のレートリミットに引っかかってしまいました。また、MackerelではAPM機能によるモニタリング結果とシステムメトリックなどを並べて一画面で見ることが今のところは難しいようでした。

そこで、今回はMackerelには死活監視のみを任せ、モニタリングはカスタム性の高いPrometheus/Grafanaを用いて行う形に移行しました。基本的なシステムメトリックはPrometheusのnode exporterで取得しつつ、node exporterで取れない通信帯域のメトリックについて、さくらのクラウドが公式に提供しているsakuracloud_exporter から得られる情報を活用しました。

また、CTFdへの負荷状況をモニタリングするため、CTFdにFlask/redis/sqlalchemyのInstrumentorを導入し、PrometheusとつなげるためにOpenTelemetry Collectorも導入しました。その他、CTFの参加者数や問題の正解比率・提出数などをモニタリングする意味も含めてCTFd Exporterを活用しました。

最終的にGrafanaでモニタリングしていた情報

監視面では、初動の最大数のアクセスを受け流せたため、あとはネットワーク面やCTFdのパフォーマンスの監視が主となりました。特にさくらのクラウドでは帯域幅が100Mbpsであるため、これを超えていないかをsakuracloud_exporter経由で監視していました。重いファイルをCTFd側に置かないようにするなどの対策もあり、さくらのクラウドの制約内で問題なくCTFを乗り越えられました。

スコアサーバ(CTFd)におけるIaC

続いては、スコアサーバのIaCについて説明します。DIVER OSINT CTFのスコアサーバはCTFdで運用されています。CTFdのGitHubリポジトリは以下です。

https://github.com/ctfd/ctfd

CTFdはサーバを立てたあと、CTF参加者に公開する前に以下のような項目を設定する必要があります。

  • イベント名
  • 開催日時
  • トップページ・ルールページ(HTML or Markdown)
  • 参加登録時のアンケート(CTFd Custom Fields)
  • …
参加登録時のアンケートの設定画面
ルールページの設定画面

また、各問題には設定項目が20個近くあり、DIVER OSINT CTFは50問近くの問題を提供しているため、運営は1000以上の項目を設定する必要があります。

これを人力でやると設定ミスが発生してしまったり、後から問題を更新したいときにスムーズに更新できなかったりするので、これらのCTFd上で行うすべての設定をIaCにしました。

問題やconfigなど、各種のデプロイに使用したツールは以下の通りです。

以下ではそれぞれのデプロイについて説明します。

問題のデプロイ

DIVERではCTFd/ctfcliをforkして改造したdiver-osint-ctf/ctfcliを使用して問題のデプロイをしています。元のctfcliにdiver-osint-ctf/geo_challengesや手動採点問題(リポジトリ非公開)のような、特殊な問題をデプロイできるような機能が追加されています。

diver-osint-ctf/ctfcliでは、あらかじめdiver-osint-ctf/ctfd-config-generatorを用いて以下のフォーマットでファイルを作っておきます(READMEより抜粋)。

ジャンル名
└── 問題名
    ├── build           # 問題サーバで実行されるファイルを配置してください(optional)
    ├── challenge.yml   # CTFdの設定を書いてください
    ├── flag.txt        # Flagを書いてください。
    ├── public          # 配布用ファイルを配置してください(optional)
    ├── solver          # ソルバを配置してください(optional)
    └── writeup
        └── README.md   # 作問者Writeupを配置してください

各問題の詳細(問題文や配点、フラグなど)は、以下のようなフォーマットでchallenge.ymlに記述しています。

name: "sample_chall"
author: "diver"
category: "sample"
description: |
  sample_**description**

  [sample](<https://example.com>)

flags:
  - "flag{sample_flag}"
tags:
  - easy
files:
  - public/sample_file.txt
requirements:
  - "welcome"
value: 500
attempts: 5
type: dynamic
extra:
  initial: 500
  decay: 100
  minimum: 100
image: null
host: null
state: visible
version: "0.1"

このようにyamlで記載することで、デプロイ前にlinterを実行し、設定ミス等を事前に発見できます。linterとしては以下の3つを実行しています。作問者は設定ファイルの記載方法よりも問題の内容に時間を割いてもらいたいので、このように複数の観点から多段階でチェックすることが非常に重要となります。

  • ctfcli lint機能
  • diver-osint-ctf/clilint (ctfcli lintにはない独自のチェックルールをたくさん実装しています)
  • LLMによる設定ミスや誤字脱字のチェック

これらの設定ファイルを書いてからlintを通した後、ctfcliのchallenge install機能やchallenge sync機能でデプロイ・問題の更新をしています。

ページのデプロイ

ページとは、DIVER OSINT CTFで言うところのRulesページやAbout Flagページを指します。ctfcliにはMarkdownやHTMLをデプロイする機能がありますが、画像はあらかじめ手動で登録する必要があります。また、CTFd上で保存した画像はファイル名がランダムな値なので予測できず、「Markdownでページを作成」→「画像を手動アップロード(この時にパスをコピー)」→「Markdownの画像のパスを変更」をしないといけないのが大変でした。

そこでdiver-osint-ctf/ctfcli-configでは、画像のアップロードとファイルパスの変換を自動で行うようにしました。変換後、そのMarkdownをctfcliでデプロイするという流れにしています。

CTFdのconfigのデプロイ

configとは、管理者ページで設定する項目(CTFの名前、ロゴ、開催時間、チーム/個人設定など)です。

これらの設定の一部はdiver-osint-ctf/ctfcliでもできますが、すべては出来なさそうだったので、DIVERではdiver-osint-ctf/ctfcli-configで設定ファイルを生成→diver-osint-ctf/ctfcliでデプロイという流れで運用するようにしています。

参加登録時のアンケートのデプロイ

多くのCTFでは終了後にアンケートを取っており、その結果に基づいて今後の開催方針を決定しています。しかし、このアンケートの収集率があまり良くなく、参加者の属性情報等は十分に集まりませんでした。そこで、DIVER OSINT CTFでは参加登録時にいくつかのアンケートの回答を必須としていました。

ただ、この項目はctfcliでデプロイできなかったため、diver-osint-ctf/ctfcli-configがCTFd APIを経由してデプロイするようにしています。

プラグインのデプロイ

DIVER OSINT CTFはたくさんのCTFdプラグインを作成し、導入しています。

これらのプラグインの中には事前にapt packageをインストールしないといけないものがあったり、インストール後に設定が必要なものがあったりします。例えば、ctfd-certificateはインストール後にイベントのロゴなどを設定します。そこで、

  1. SSHでサーバ上にデプロイし、かつ依存関係をインストール
  2. インストール後にCTFd APIを用いてプラグインの設定を流し込む

をする必要がありました。これらをdiver-osint-ctf/ctfcli-configが行うようにしました。

おわりに

DIVER OSINT CTFではインフラの設定をほぼすべてIaCで構築し、常に再現可能な状態にしています。これからもDIVER OSINT CTFのご参加・拡散をよろしくお願いします。

また、今回最大6000リクエスト/分を捌いたインフラ環境をご提供いただいたさくらインターネットさんに深い感謝を申し上げます。

DIVER OSINT CTFにおけるリクエストの処理状況