TECH PLAY

電通総研

電通総研 の技術ブログ

837

こんにちは、クロスイノベーション本部リーディングエッジテクノロジーセンターの山下です。 最近は、gpt-ossやQwen3.5といったローカルLLM(Local Large Language Model)も注目されており、これらを活用したプロジェクトも増えてきています。 今回の記事では、ローカルLLMのベンチマークソフトウェアである GuideLLM について紹介します。LLMの性能には様々な観点がありますが、GuideLLMはLLMサーバ自体の応答速度などを測るためのベンチマークソフトウェアです。 GuideLLMでは、以下のような観点でLLMサーバの性能を評価します。 レイテンシー(応答時間) スループット(処理能力) 同時リクエスト数に対する性能の変化 エラー率や安定性 GuideLLMは、LLMサーバに対して様々な負荷をかけることで、これらの観点で性能を評価します。例えば、同時に複数のリクエストを送ることで、スループットやレイテンシーの変化を測定します。また、長時間の負荷テストを行うことで、安定性やエラー率も評価します。 LLMサーバの評価項目 LLMサーバの評価には、TTFT(Time To First Token)、ITL(Inter Token Latency)やThroughputなどの指標が用いられます。 TTFT(Time To First Token) : LLMが最初のトークンを生成するまでの時間を測定します。これは、ユーザーが応答を受け取るまでの待ち時間を示す重要な指標です。 ITL(Inter Token Latency) : トークン間の生成時間を測定します。これにより、LLMの応答速度や処理能力を評価できます。 Throughput : 一定時間内に処理できるリクエストの数を測定します。これは、LLMサーバの処理能力を示す指標です。 Latency(レイテンシー) : LLMサーバがリクエストに対して応答するまでの時間を測定します。これはトークンごとではなくすべての生成が完了するまでの時間になります。 同時リクエスト数に対する性能の変化 : 同時に複数のリクエストを送ることで、LLMサーバの性能がどのように変化するかを評価します。これにより、サーバのスケーラビリティや負荷耐性を測定できます。 エラー率や安定性 : 長時間の負荷テストを行うことで、LLMサーバの安定性やエラー率を評価します。 通常のWebサーバのベンチマークソフトウェアは、HTTPリクエストの処理能力や応答時間を測定することが一般的です。しかし、LLMサーバは応答を作成するための時間が長く、しかも生成された結果をリアルタイムに返す仕組みになっています。このため、通常のWebサーバの指標に加えてTTFTやITLといったLLM特有の指標が重要になります。特にTTFTはユーザーが最初の応答を受け取るまでの待ち時間を示し、ITLは1つのトークンが生成されてから次のトークンが生成されるまでの時間になります。これらがユーザーエクスペリエンスに直結する重要な指標となります。 ChatGPTなどのサービスを利用している際に、最初の一文字が出るまでの時間が長いと感じることがあるかもしれませんが、これはTTFTが長いことを意味しています。TTFTが短いほど、ユーザーは迅速に応答を受け取ることができ、より快適な体験を提供できます。 GuideLLMは実際にLLMサーバに対して負荷をかけることで、これらの指標を測定し、LLMサーバの性能を評価します。これにより、LLMサーバの性能を定量的に評価し、改善点を特定することができます。 GuideLLMの使用方法 GuideLLMは、Pythonで実装されたベンチマークソフトウェアです。以下のコマンドでインストールできます。 他のインストール方法については、公式の GitHubリポジトリ を参照してください。 pip install guidellm[recommended] インストール後、例えば以下のコマンドでベンチマークを実行できます。 guidellm benchmark run\ --target "http://<LLMサーバのアドレス:ポート>" \ --profile sweep \ --max-seconds 30 \ --data "prompt_tokens=256,output_tokens=128" ここでは --target オプションでLLMサーバのアドレスとポートを指定します。 --profile オプションでは、ベンチマークのプロファイルを指定します。 sweep プロファイルは、様々な負荷条件でベンチマークを実行するためのプロファイルです。 --max-seconds オプションでは、ベンチマークの最大実行時間を指定します。 --data オプションでは、ベンチマークに使用するデータを指定します。ここでは、プロンプトトークン数と出力トークン数を指定しています。 --profile として今回は sweep を指定しています。これの動作は以下のようなものになっています。 まずは1並列でのベンチマークを行い、ベースラインのレイテンシーを測定します。これにより、LLMサーバが単一リクエストに対してどれくらいの応答時間があるかを把握します。 次に並列アクセスを行い(デフォルトでは512並列)LLMサーバの最大スループットを測定します。これにより、LLMサーバがどれくらいのリクエストを同時に処理できるかを把握します。 2つのベンチマークでベースとなる性能と最大スループットがわかりました。次に、ベースラインのレイテンシーと最大スループットの間の設定でベンチマークを試します。これにより、LLMサーバが異なる負荷条件でどのように性能が変化するかを把握します。 --profile には他にも色々なプロファイルが用意されているので、目的に応じて選択することができます。 公式のページ を参考にしてください。 実行例 ここでは、実際にGuideLLMを使用してベンチマークを実行した例を紹介します。今回は、ローカルで動作しているLLMサーバに対してベンチマークを実行しました。 通信するLLMサーバはDGX Sparkで動作しているvLLMサーバでgpt-oss-120b を使用しています。 以下のコマンドでベンチマークを実行しました。 GUIDELLM__MAX_CONCURRENCY は、guidLLMがベンチマークを実行する際の最大並列数です。 今回の接続先がDGX Sparkなのでそこまで多くの接続はしない想定なので128並列を指定してみました。 また、 --processor オプションで、ベンチマークで使用するデータを生成するために使用するプロセッサを指定しています。ここでは、gpt-oss-120bが接続先になるので、 openai/gpt-oss-120b を指定しています。 export GUIDELLM__MAX_CONCURRENCY=128 guidellm benchmark --target "LLMサーバのアドレス:ポート" \ --output-dir ./output-dir/ \ --profile sweep \ --max-seconds 300 \ --data "prompt_tokens=256,output_tokens=128" \ --processor "openai/gpt-oss-120b" ベンチマークの結果は以下のようになりました。 すべての結果を載せると長すぎるので、ベンチマーク結果のサマリの部分を抜粋しています。 サマリを見てみると全体の数字の傾向として、同時リクエスト数が増えるにつれて、レイテンシー(Lat)が増加し、スループット(Tok gen/s)も増加していることがわかります。 また、TTFTやITLも同様に増加しています。つまりリクエスト数が増えると応答速度が遅くなっていることがわかります。 一方でTTFTは最悪でも500ms程度で、ITLも131ms程度なので、同時リクエスト数が増えても応答速度はまだ許容可能な範囲に収まっていることがわかります。 特に throughput@128 の行を見ると、スループットが約5.1 req/sに達していることがわかります。さらに、123.2並列まで問題なく処理できていることがわかります。この場合でも、TTFTは1506.7ms、ITLは177.3msなので、同時リクエスト数が増えても応答速度はまだ許容可能な範囲と言えるのではないでしょうか。 複数ある constant@数字 の結果は、ベースラインのレイテンシーと最大スループットの間の設定でベンチマークを試した結果になります。 @ の右側の数字はリクエストの頻度になります。 これらの結果から、TTFTがどの程度までなら許容できるのかを基準にして許容可能なリクエスト頻度を決めることもできます。例えば、TTFTで900ms以下に応答してほしいという場合であるなら constant@1.49 、 constant@2.09 の結果が参考になるかもしれません。 constant@1.49 ではTTFTが786.1ms、 constant@2.09 ではTTFTが943.2msとなっています。つまり、1.49リクエスト/秒程度であれば、TTFTが900ms以下に収まる可能性があることがわかります。 利用してみて気が付いた点 ベンチマークの出力形式としては、JSONやCSVに加えてHTMLも選択できます。しかし、今回HTML形式で出力したものを確認したところ、うまく表示されませんでした。生成されているHTMLに問題がありそうですが原因は不明です。JSONやCSV形式で出力したものは問題なく利用できたので今回はそちらを使用しました。 まとめ 今回は、ローカルLLMのベンチマークソフトウェアであるGuideLLMについて紹介しました。GuideLLMは、LLMサーバの性能を様々な観点で評価するためのベンチマークソフトウェアです。GuideLLMを使用することで、LLMサーバの性能を定量的に評価し、改善点を特定することができます。 ローカルLLMを活用したプロジェクトを進める際には、LLMサーバの性能を評価して最適な設定を見つけることが重要です。ぜひ、GuideLLMを活用して、ローカルLLMの性能を評価して最適な設定を見つけてください。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @yamashita.tsuyoshi レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは。クロスイノベーション本部 サイバーセキュリティテクノロジーセンターの耿です。 2025年から特にJavaScriptライブラリを対象としたサプライチェーンへのマルウェア混入が目立つようになってきました。多くの開発者にとって、なんとなくOSSを暗黙的に信頼して使っていた意識を変えなければならないときが来たと感じています。とはいえ、多くの依存関係を利用することを前提として成り立っているOSSのエコシステムは、すぐに劇的に変わるわけではありません。今後もサプライチェーンへの攻撃が続くことを前提とし、OSSの利用者として実施できる対策を観点別に整理しました。JavaScriptエコシステムを主に意識していますが、一部関連性のある周辺技術も取り上げています。また他の言語のエコシステムについても考え方を当てはめることができると思います。 今後の脅威や技術の変化とともに適切な対策も変わっていくと思いますし、この記事の内容が必要十分な対策とは思っていません。一旦現時点で、なるべく多くの開発者が共通して実施しやすい観点・対策について自分なりに考えをまとめ、整理したものです。 観点1: 依存関係の正確なバージョンをコードリポジトリで管理する 対策1: CI/CDでは必ずlockfile記載のバージョンを利用する 対策2: GitHub ActionsはコミットSHAでバージョン指定する 観点2: 新バージョンはすぐにインストールしない 対策3: minimumReleaseAgeを利用する 対策4: 各種ツールの自動アップデートを無効にする 観点3: インストール時のリスクを減らす 対策5: 脆弱性を直接解消しないバージョンはむやみに採用しない 対策6: minimumReleaseAgeを満たさない場合、インストールする前にリリースの経緯を確認する 対策7: minimumReleaseAgeを満たさない場合、特定パッケージのみ一時的な例外を設ける 対策8: npmのライフサイクルスクリプトを無効にする 観点4: 侵害された場合の被害を減らす 対策9: 長期的なクレデンシャルを保管しない 対策10: 外部サービスへの権限を減らす さいごに 観点1: 依存関係の正確なバージョンをコードリポジトリで管理する 第一に、アプリケーションが利用する依存関係(パッケージなど)の正確なバージョンをコードリポジトリで管理することで、CI/CDワークフローやアプリケーションの実行環境でインストールされるバージョンを確定させることが重要です。 例えばCI/CDワークフローで常に実行時点で最新のバージョンをインストールしてしまうと、インストールの度にサプライチェーン攻撃のリスクを引き受けることになります。セキュリティインシデントが発生した後の調査もしづらいです。一貫したバージョン利用ができるよう、使用する正確なバージョンをコードリポジトリで管理しましょう。 対策1: CI/CDでは必ずlockfile記載のバージョンを利用する lockfileは必ずコードリポジトリにコミットしたうえで、GitHub ActionsなどのCI/CDでパッケージをインストールするときは、lockfileに記載されている正確なバージョンをインストールするようにしましょう。 ❌良くない例(GitHub Actions) - run : npm install この例は、 package.json と lockfile に不整合がある場合(例えば package.json だけうっかり更新してコミットしてしまった場合)、CI/CDワークフローの中でlockfileが更新され、コードリポジトリで管理されているものと異なるバージョンがインストールされてしまいます。 下の表の✅列のコマンドやコマンドオプションを利用し、CI/CDではlockfileを更新しないようにしましょう。 パッケージマネージャ ❌ ✅ npm npm install npm ci Yarn yarn install yarn install --immutable pnpm pnpm install pnpm install --frozen-lockfile Bun bun install bun install --frozen-lockfile これらのコマンドを使えば、 package.json と lockfile に不整合があるときにはインストールが異常終了します。少なくとも管理されていないパッケージのバージョンがインストールされることを防止することができます。 ❌良くない例(GitHub Actions) - run : npm install -g aws-cdk この例はlockfileと関係なく、CI/CDの中でパッケージをグローバルにインストールしてしまっています。 CI/CDワークフローの中で利用するコマンドラインツールであっても package.json (の devDependencies )に全て明記し、lockfileを利用して事前に定められたバージョンをインストールするようにしましょう。 対策2: GitHub ActionsはコミットSHAでバージョン指定する JavaScriptエコシステムではありませんが、GitHub Actions の actionもサードパーティのコードなのでサプライチェーンリスクがあります。 Gitタグは書き換え可能なので、バージョンタグでバージョン指定すると、実行されるコードは可変です。 ❌良くない例(GitHub Actions) - uses : actions/checkout@v6 フルコミットハッシュ(SHA)で厳密にコミットを特定するようにしましょう。 ✅良い例(GitHub Actions) - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 観点2: 新バージョンはすぐにインストールしない 観点1でインストールするパッケージバージョンを確定できる状態にしたら、次はそのバージョンを決めるタイミングのリスクを減らすことを考えます。 サードパーティのパッケージはリリース日時が新しいほどインストールする際のリスクが高く、時間が経つにつれてコミュニティやセキュリティベンダーによる検証が進み、リスクが低下します。以前はDependabotなどを使って新バージョンを自動適用することが流行しましたが、すぐに新バージョンを利用することは一定のリスクがあると認識されるようになってきました。 対策3: minimumReleaseAgeを利用する 各種パッケージマネージャには、リリースから一定期間経過していないパッケージバージョンをインストールさせない仕組みが登場しています。この記事では minimumReleaseAge と呼ぶことにします。ぜひ利用しましょう。 install コマンドのオプションとして使うこともできますが、付け忘れないようにプロジェクト内の設定ファイルで一括設定するべきです。 パッケージマネージャ 機能名 利用可能バージョン npm min-release-age v11.10.0 ~ Yarn npmMinimalAgeGate v4.10.0 ~ pnpm minimumReleaseAge v10.16 ~ Bun minimumReleaseAge v1.3 ~ パッケージマネージャ以外では、Dependabot Version Updatesの cooldown やRenovateの minimumReleaseAge が同様の機能に相当します。 AIコーディングエージェントを利用している場合、 minimumReleaseAge の設定を上書きされないよう、権限レベルで拒否する設定もしておきましょう。例えばClaude Codeであれば settings.json で以下のような設定ができます。 ~/.claude/settings.json { " permissions ": { " deny ": [ " Write(./.npmrc) ", " Write(./.yarnrc.yml) ", " Write(./pnpm-workspace.yaml) ", " Write(./bunfig.toml) ", " Write(./.github/dependabot.yml) ", " Write(./renovate.json) " ] } } minimumReleaseAge との向き合い方ですが、いついかなる場合でも守るべき制約としては考えない方が良いと思います。例えばCVSSスコアが10.0の脆弱性が発生した場合、それに気付いていながら、 minimumReleaseAge を守るために1週間待機するべきではありません。パッケージに危険度の高い脆弱性が発見された場合、すなわち迅速にアップデートすることによりサプライチェーン攻撃を受けるリスクよりも、アップデートしないことにより脆弱性を悪用されるリスクの方が高いと評価される場合は、 minimumReleaseAge を一時的に無視して迅速にアップデートを実施することも想定するべきです(その時の注意事項は観点3で考えます)。 minimumReleaseAge は、あくまでも最新版をうっかりインストールしてしまうことを防ぐための機能と考え、明確な意思を持ってパッケージの特定バージョンをインストールする場合は一時的に無視することを許容するべきでしょう。その前提で考えると、安全側に倒して期間を長め(1-2週間など)に設定して良いと思います。 ところで各パッケージマネージャが提供している minimumReleaseAge 機能は、異なるパッケージマネージャを使った install コマンドには効果がありません。例えばpnpmでパッケージ管理しているプロジェクトであるにも関わらず、開発者がYarnを使っていると勘違いしてうっかり yarn install してしまうと、pnpmの minimumReleaseAge が設定されていても意味がありません。パッケージマネージャに関わらず共通のプロキシとして動作する AikidoSec/safe-chain のMinimum package age機能を使うのも良いでしょう。 対策4: 各種ツールの自動アップデートを無効にする AIエージェントやVS Code拡張など、JavaScriptのプロジェクト以外でも直接は意識しない形で言語パッケージが利用されていることがあります。これらのツール・拡張機能が自動アップデートするように設定されている場合はサプライチェーン攻撃の被害を受けやすいため、自動アップデートを無効にすることで対策になります。 一方で、これらのツール・拡張機能は組織的に脆弱性管理されないことが多く、自動アップデートを無効にすることでサプライチェーン攻撃ではない脆弱性への対応が遅れてしまいます。自動アップデートするのはリスクですが、自動アップデートせずに脆弱性が放置されるのもリスクです。どちらのリスクを重く評価すべきかの判断が難しく、今後各種ツールの機能がさらに成熟することでより効果的な対策ができることを期待したいです。 観点3: インストール時のリスクを減らす 観点2の対策を取りつつも、時には危険度の高い脆弱性に対応するためにすぐに新バージョンをインストールしたいことがあります。 minimumReleaseAge の期間を無視してパッケージをインストールしたい場合の対策を4つ取り上げます。 対策5: 脆弱性を直接解消しないバージョンはむやみに採用しない 脆弱性を対応する際、セキュリティアドバイザリーに記載されているパッチバージョンよりも新しいバージョンがリリースされていたとしても、むやみに採用しない方が良いでしょう。 例えば図のDependabotのセキュリティアドバイザリーではv4.12.4が脆弱性を解消すると分かります。パッケージをアップデートする時点で v4.12.5 がリリースされていたとしても、リリースから十分な期間が経っていない場合は採用を見送るのが安全でしょう。 対策6: minimumReleaseAgeを満たさない場合、インストールする前にリリースの経緯を確認する そもそも新バージョンのリリース直後にインストールするリスクが高いのは、コミュニティやセキュリティベンダーによる検証が十分にされていないためです。自分たちができる範囲で検証することでこのリスクを幾分か下げることができます。 パッケージのコードリポジトリにアクセスし、利用しようとしているバージョンのリリースの経緯を確認することなどが考えられます(リリースに含まれているコミットが怪しくないか、コミットの経緯など)。可能な範囲でコード差分を確認するとさらに良いでしょう。なおリリースノート、コミットメッセージについては、マルウェアの場合はよく偽装されているので、信用しない姿勢で臨みましょう。 対策7: minimumReleaseAgeを満たさない場合、特定パッケージのみ一時的な例外を設ける minimumReleaseAge の条件を満たさないバージョンのインストールを実際に行う場合、一時的であっても minimumReleaseAge の期間を縮めたり無効にしたりするのは避けましょう。 minimumReleaseAge 自体は変更せず、インストールしたい特定パッケージのみに一時的な例外を設けるようにします。 これは推移的に依存しているパッケージ(Transitive Dependency)が侵害されている可能性を考慮しての対策です。インストールしようとしているパッケージ「A」が内部でパッケージ「B」「C」「D」に依存している場合、 minimumReleaseAge の条件を緩めてしまうと「B」「C」「D」をインストールする条件も緩んでしまいます。対策6で自分達が確認しなければならない範囲が格段に広がってしまうのです。 特定パッケージのみに例外を設けることで、パッケージ「A」が minimumReleaseAge を満たさないことを許容しますが、「B」「C」「D」は相変わらず minimumReleaseAge を満たすバージョンしかインストールされないため、より安全です。 各パッケージマネージャにおいて特定パッケージのみ minimumReleaseAge に例外を設ける機能は以下のとおりです。 パッケージマネージャ 機能名 利用可能バージョン npm 現状サポートなし( GitHub Issue ) - Yarn npmPreapprovedPackages v4.10.0 ~ pnpm minimumReleaseAgeExclude v10.16 ~ Bun minimumReleaseAgeExcludes v1.3 ~ 特定パッケージのインストールが終わったら、例外を戻すのを忘れないようにしましょう。 対策8: npmのライフサイクルスクリプトを無効にする 今までのサプライチェーン攻撃の傾向として、多くのマルウェアはライフサイクルスクリプト(postinstall等)を利用しています。 パッケージインストール時のスクリプト実行を無効にすることでリスク低減効果を期待できます。 パッケージマネージャ 機能名 パッケージ単位で有効化 npm .npmrc の ignore-scripts 不可(rebuildが必要) Yarn .yarnrc.yml の enableScripts package.json の dependenciesMeta pnpm v10以降、デフォルトで無効化される pnpm-workspace.yaml の onlyBuiltDependencies Bun デフォルトで無効化される package.json の trustedDependencies 元から無害のpostinstallスクリプトを利用しているパッケージもありますが、動作に必須ではないスクリプトもあったりします。慎重を期するならば、それぞれのパッケージのスクリプトを確認し、本当に動作に重要なものだけをパッケージ単位で有効化するのが良いでしょう。 観点4: 侵害された場合の被害を減らす サプライチェーンを利用した攻撃手法はどんどん洗練されており、これまでに紹介した対策では不十分と考えられるような攻撃が今後発生する可能性もあります。侵害されないようにする対策だけではなく、侵害された場合に被害を減らすことも考慮しましょう。 外部のソフトウェアをインストールする全ての環境(開発者端末、devContainer、CI/CD、本番稼働環境など)で以下の対策を検討しましょう。 対策9: 長期的なクレデンシャルを保管しない 今までに確認されたサプライチェーン攻撃では、侵害を永続化するために各種のクレデンシャルを探していることが確認されています。侵害された環境から他の環境に被害が拡大しないよう、GitHubの認証トークン、クラウド環境の認証情報、社内システムへの認証情報など、長期的な認証情報を不必要に保管しないようにしましょう。 保管している場合は他の代替手段がないか検討します。例えばAWSの場合、CI/CDワークフローにIAMアクセスキーを持たせる代わりにOIDCを利用したり、開発者端末上の .env ファイルにIAMアクセスキーを保存する代わりに aws login コマンドで認証情報を取得したりすることができます。 対策10: 外部サービスへの権限を減らす 外部サービスへの認証情報を保管している場合、その権限を減らすことができないか確認しましょう。 AWSであればIAMポリシーが最小権限になっているか確認しましょう。例えばcdk deployを実行するためのCI/CDワークフローに Admin権限は不要 であることを知らない人もたまに見かけます。 さいごに 効率的に開発を進めるために多くの依存関係を利用することを前提に成り立っている現在のJavaScriptのエコシステムは、簡単に変わるものではないでしょう。オープンなコミュニティを逆手に取った攻撃が増えている一方で、同じようにコミュニティの力で対策を考えていくことができます。本記事の考えをまとめるにあたり、多数の技術ブログを参考にさせていただきました。この記事も誰かの考えの参考になれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 執筆: @kou.kinyo レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
はじめに こんにちは、XI本部リーディングエッジテクノロジーセンターの佐藤太一です。 このエントリでは、複数のワークツリーでClaude Codeを並行稼働させる開発環境の作り方を紹介します。 Claude Codeには -w ( --worktree )というフラグがあり、 git worktreeを使って1つのリポジトリから複数の作業ディレクトリを切り出せます。 これを使うと、あるブランチでClaude Codeに実装を任せている間に、 別のブランチで別の機能を開発するという並行作業ができるのです。 ところが同じアプリケーションを複数同時に起動するには、それぞれポート番号を分ける必要があります。 各ポート番号ごとに動作確認すべきことが違うのですから、混乱しがちになります。 そこで、この記事ではportlessというツールを使って、複数のワークツリーを単一のポートでアクセスできるようにします。 端的に言うと、 claude -w feature-a というコマンドで起動した開発環境に https://feature-a.myapp.localhost:1355 でアクセスできる環境を作ります。 はじめに 完成イメージ DevContainerのセットアップ .devcontainer/devcontainer.json .devcontainer/postCreateCommand.sh 最小限のWebアプリケーション package.json src/index.ts git リポジトリとして初期化 portless で単一ポート・複数サブドメインルーティング portless を使ってサーバを起動する Claude Code 用の環境を整える .claude/hooks/session-init.sh .claude/settings.json statusline にホストURLを表示する .claude/statusline.sh まとめ 完成イメージ 最終的にできあがる環境は以下のようなものになります。 DevContainer ├── portless proxy (:1355) │ ├── myapp.localhost:1355 → worktree: main │ ├── feature-a.myapp.localhost:1355 → worktree: feature-a │ └── feature-b.myapp.localhost:1355 → worktree: feature-b │ ├── /workspaces/myapp/ (メインリポジトリ) ├── /workspaces/myapp/.worktrees/feature-a/ (worktree) └── /workspaces/myapp/.worktrees/feature-b/ (worktree) portlessが1つのポート(1355)で待ち受けていて、Hostヘッダのサブドメインを見て対応するworktreeのdevサーバーに振り分けます。 DevContainerのセットアップ この記事では再現可能な開発環境としてDevContainerを使います。事前にvscodeをインストールしておいてください。 .devcontainer/devcontainer.json WORKSPACE_FOLDER という環境変数は、コンテナ内においてコマンドを実行すべきパスを確定するために定義しています。 PORTLESS_HTTPS を 1 に設定すると、portlessが常にHTTPSモードで動作します。 portlessは自前でCA証明書とサーバー証明書を自動生成するので、mkcertなどの外部ツールは不要です。 PORTLESS_STATE_DIR でportlessの状態ディレクトリを明示的に指定しています。 加えて、いくつかの名前付きボリュームを設定しています。 claude-config-dir は複数回のコンテナ作成をまたがってClaude Codeの設定を永続化するために使っています。 portless はportlessの状態ディレクトリを永続化するためのボリュームです。 ここにCA証明書やルーティング情報が格納されます。 npm-cache と myapp-node_modules も同じように永続化されるものですが、どちらかというとパフォーマンス改善とトラブル回避のために設定しています。 最後に forwardPorts でportlessが使うポート1355番を設定しています。 portlessを使わずに動作確認をするためのポートとして3000番を使います。 portlessが動くようになったら削除してください。 { " name ": " myapp ", " image ": " mcr.microsoft.com/devcontainers/base:ubuntu ", " features ": { " ghcr.io/devcontainers/features/node:1 ": {} } , " postCreateCommand ": " /bin/bash .devcontainer/postCreateCommand.sh ", " waitFor ": " postCreateCommand ", " containerEnv ": { " WORKSPACE_FOLDER ": " ${containerWorkspaceFolder} ", " PORTLESS_HTTPS ": " 1 ", " PORTLESS_STATE_DIR ": " /home/vscode/.portless " } , " mounts ": [ { " type ": " volume ", " source ": " portless ", " target ": " /home/vscode/.portless " } , { " type ": " volume ", " source ": " claude-config-dir ", " target ": " /home/vscode/.claude " } , { " type ": " volume ", " source ": " npm-cache ", " target ": " /home/vscode/.npm " } , { " type ": " volume ", " source ": " myapp-node_modules ", " target ": " ${containerWorkspaceFolder}/node_modules " } ] , " forwardPorts ": [ 1355 , 3000 ] } .devcontainer/postCreateCommand.sh コンテナ作成後に実行される初期化スクリプトです。 ここでは、名前付きボリュームの権限を調整しつつ、jqとClaude CLIのインストールを行っています。 Node.jsは devcontainer.json の features で追加しているため、このスクリプトでのインストールは不要です。 #!/bin/bash set -eu sudo apt-get update sudo apt-get install -y jq # ボリュームの権限を修正 sudo chown -R "$(id -gn):$(whoami)" /home/vscode sudo chown -R "$(id -gn):$(whoami)" "${WORKSPACE_FOLDER}/node_modules" # git safe directory git config --global --add safe.directory "$WORKSPACE_FOLDER" # Claude CLI curl -fsSL https://claude.ai/install.sh | bash 最小限のWebアプリケーション ここからは、動作確認用として node:http だけの最小限のWebアプリケーションを用意します。 package.json { " name ": " myapp ", " type ": " module " } src/index.ts ここで実行するサンプルのアプリケーションでは、環境変数として渡された PORT を使ってHTTPサーバをホストします。 import { createServer } from "node:http" ; const port = process .env.PORT ? Number ( process .env.PORT) : 3000 ; const server = createServer(( req , res ) => { res.writeHead( 200 , { "content-type" : "text/plain" } ); res.end( `Hello from ${ port } ` ); } ); server.listen(port, () => { console .log( `Server running at http://localhost: ${ port } ` ); } ); node ./src/index.ts でサーバを起動した後、 http://localhost:3000 にアクセスすれば応答が得られます。 git リポジトリとして初期化 いくつかリソースを作成したので、git リポジトリとして初期化してきます。 まず、 .gitignore には以下を追加しておきましょう。 .claude/worktrees node_modules/ .portless/ 次はリポジトリを初期化して、最初のコミットを作ります。 git init git add .gitignore .devcontainer package.json src/ git commit -m "initial commit" portless で単一ポート・複数サブドメインルーティング portless はローカル開発用のリバースプロキシです。 portless を使ってサーバを起動する では、アプリケーションをportless経由で起動できるようにスクリプトを追加しましょう。 { " name ": " myapp ", " type ": " module ", " scripts ": { " dev ": " npx portless run node ./src/index.ts " } } portless run を実行すると、portlessは以下のことを行います。 プロキシが未起動なら自動でバックグラウンド起動する CA証明書がなければ、PORTLESS_STATE_DIR に証明書を生成する package.json の name からサブドメイン名を推論する git worktreeを検知したらブランチ名をプレフィックスに付与する 空いているポートを見つけて $PORT 環境変数に設定する 指定されたコマンドをそのポートで起動する サブドメインからアプリケーションへのルーティングを登録する まずはメインリポジトリでサーバを起動してみましょう。 # メインリポジトリ npm run dev 初回は証明書のエラーが出力されます。 Starting proxy... Ensuring TLS certificates... Adding CA to system trust store... Could not add CA to system trust store. Permission denied. Try: sudo portless trust これは、SSL証明書をOSに登録できるのはrootユーザのみだからです。 以下のコマンドでコンテナ内のOSに証明書を登録します。 sudo env PATH="$PATH" npx portless trust ホストOSのブラウザでもHTTPS警告が出ないように、CA証明書をホストOS側にインストールしましょう。 portlessの状態ディレクトリは名前付きボリューム上にあるので、ワークスペースにコピーします。 cp -r ~/.portless . ホストOSでもMacやLinuxなら以下のコマンドを実行してください。 PORTLESS_STATE_DIR=".portless" npx portless trust Windowsなら、PowerShellを管理者権限で起動して以下のコマンドを実行します。 $env:PORTLESS_STATE_DIR = "$($PWD.Path)\.portless" npx portless trust Windows では、証明書をインストールする際に確認ダイアログが出力されます。 気を取り直して、もう一度サーバを起動するコマンドを実行します。 その後、ブラウザで https://myapp.localhost:1355 にアクセスしてみましょう。 次は、ワークツリーを作成してから、そのディレクトリ内でサーバを起動するコマンドを実行しています。 もし、まだgitリポジトリになっていないようなら、 git init で初期化してください。 # linked worktree(ブランチ feature-a) git worktree add .worktrees/feature-a cd .worktrees/feature-a npm run dev # → https://feature-a.myapp.localhost:1355 サーバが起動したら、ブラウザで https://feature-a.myapp.localhost:1355 にアクセスしてみましょう。 ホスト側のブラウザからアクセスするポート番号は同じままですが、画面に表示されるポート番号は新しいものに変わっているはずです。 Claude Code 用の環境を整える まずは、 claude コマンドを実行して、テーマの設定やログインを実施しておいてください。 Claude Codeにはhooksという仕組みがあり、 特定のイベントが発生したときにシェルスクリプトを実行できます。 今回は SessionStart フックを使います。 これは、Claude Codeのセッションが開始されるときに実行されます。 .claude/hooks/session-init.sh このフックの役割は、セッション内で必要になる資源を整えることです。 今回は説明を簡易化するために npm install だけを実施しています。 例えば、.envrc を作成したり、DBの初期データを投入すると便利ですよ。 #!/bin/bash set -eu cd "${CLAUDE_PROJECT_DIR:-$(pwd)}" npm install >&2 .claude/settings.json フックの登録はsettings.jsonで行います。 { " hooks ": { " SessionStart ": [ { " hooks ": [ { " type ": " command ", " command ": " bash \" $CLAUDE_PROJECT_DIR \" /.claude/hooks/session-init.sh " } ] } ] } } claude -w を実行してワークツリーが作成されるか確認してください。 statusline にホストURLを表示する 最後の仕上げとして、Claude Codeのプロンプト下部に常時表示される情報行を追加します。 現在のworktreeに対応するURLを常時表示しておくと、セッションと紐づくサーバがすぐに分かるので便利です。 .claude/statusline.sh #!/bin/bash input=$(cat) _url=$(NO_COLOR=1 npx --yes portless get myapp 2>/dev/null) [ -z "$_url" ] && exit 0 _routes="${PORTLESS_STATE_DIR:-$HOME/.portless}/routes.json" _host=$(echo "$_url" | sed 's|.*://||; s|:.*||') if [ -f "$_routes" ] \ && jq -e --arg h "$_host" \ 'any(.[]; .hostname == $h)' "$_routes" \ > /dev/null 2>&1; then printf '\033[38;5;75m%s\033[0m' "$_url" # blue else printf '\033[38;5;245m%s\033[0m' "$_url" # gray fi printf '\n' サーバが停止中ならグレー、起動中なら青色で表示されるようにしてみました。 これを組み込んだ後の settings.json です。 { " hooks ": { " SessionStart ": [ { " type ": " command ", " command ": " bash \" $CLAUDE_PROJECT_DIR \" /.claude/hooks/session-init.sh " } ] } , " statusLine ": { " type ": " command ", " command ": " bash \" $CLAUDE_PROJECT_DIR \" /.claude/statusline.sh ", " padding ": 0 } } Claudeのセッション中に !npm run dev とすることでサーバを起動できます。 まとめ この記事では、Claude Codeのworktreeを使った並行開発を快適にするための環境を構築しました。 構成要素をまとめると以下のとおりです。 コンポーネント 役割 DevContainer 再現可能な開発環境 claude -w worktreeの作成と自動初期化 portless 単一ポートのリバースプロキシ statusline 現在のworktreeのURLを常時表示 この構成では、 claude -w feature-a とセッションを開始すると、 https://feature-a.myapp.localhost:1355 でアクセスできます。 引数を渡さなければランダムなワークツリー名が付与されますが、portlessが適切なホスト名になるよう丸めてくれます。 また、statuslineにURLを表示しているので、もう迷うこともありません。 実際の開発では、ここに認証サーバやS3モックなどのサービスを追加していくことになるでしょう。 基本的な部分は実現できているので、追加のサーバ導入はAIがうまくやってくれますので、お任せしていきましょう。 執筆: @sato.taichi レビュー: @handa.kenta ( Shodo で執筆されました )
こんにちは、クロスイノベーション本部リーディングエッジテクノロジーセンターの山下です。 先日、会社でDGX Sparkの互換機である Dell Pro Max with GB10 を購入しました。 128GBのユニファイドメモリ(CPU/GPU共有)を搭載したこのマシンは、LLMサーバの性能評価に最適な環境を提供してくれます。 今回はこのマシンにvLLMサーバを構築し、OpenAI互換のAPIサーバとして動作させてみました。 初回セットアップ まず電源を入れるとOSのインストール、ファームウェアのアップデートなどが行われます。 その後DGX OSが起動しました。使用感としては、通常のUbuntuに近い感じで、特に違和感なく使用できました。 デスクトップ画面はこんな感じです。 ただ、インストールの際に何か失敗していたようで、 apt が上手く動かないので以下のコマンドを実施しました。 sudo dpkg --configure -a sudo apt update sudo apt full-upgrade これで、 apt も動くようになりました。 今回は環境要因で何か失敗していただけだと思いますが、もし同様の症状が出た場合は試してみてください。 miseとuvのインストール 今回は環境構築にmiseを使用しました。miseは、各種開発関連ツールの管理を行うツールで、複数のプロジェクトで異なる環境を簡単に切り替えることができます。 またグローバル環境も管理できるため、今回のような環境構築にも便利です。 curl https://mise.run | sh echo "eval \"\$(~/.local/bin/mise activate bash)\"" >> ~/.bashrc 以降の作業では、uvも使用するため、以下のコマンドでuvもインストールしておきます。 mise use uv vLLMのインストール vLLMは並列分散処理に対応したLLMサーバです。同時アクセスなどを行っても性能の劣化が比較的少ないのが特徴です。 今回はuvを使用して、vLLMをインストールします。 事前インストール vLLMをインストールする前に、必要なパッケージをインストールしておきます。 sudo apt update sudo apt-get install -y cuda-cudart-12-5 sudo apt-get install -y python3.12-dev build-essential venv環境作成 uvを使用して、venv環境を作成します。以降のコマンドは、ここで作成したvenv環境で実行してください。 mkdir ~/vllm cd ~/vllm uv venv -p 3.12 ~/vllm/.vllm source .vllm/bin/activate vLLMのインストール 次に、vLLMをインストールします。今回は、GB10に対応したvLLMを使用するため、パッチの適用も行います。 UV_REQUEST_TIMEOUT は、vLLMのインストール時にタイムアウトが発生するのを防ぐために設定しています。 export UV_REQUEST_TIMEOUT=6000 uv pip install vllm \ --extra-index-url https://wheels.vllm.ai/0.17.1/cu130 \ --extra-index-url https://download.pytorch.org/whl/cu130 \ --index-strategy unsafe-best-match uv pip install 'nvidia-nccl-cu13>=2.29.2' fastsafetensors vLLMは現在(2026/04/07)DGX SparkのGPU GB10には一部未対応な部分があります。 そこで こちら のパッチを適用して、GB10に対応しているvLLMに修正します。 このパッチは有志の方が作成した非公式のものとなっておりますので利用の際にはご注意ください。 cd ~/vllm/ git clone https://github.com/namake-taro/vllm-custom.git SITE=$(python3 -c "import site; print(site.getsitepackages()[0])") cd "$SITE" patch -p0 < ~/vllm/vllm-custom/patches/vllm_all.patch patch -p0 < ~/vllm/vllm-custom/patches/flashinfer_cutlass_sfb_layout_fix.patch パッチ適用後にキャッシュを削除します。 rm -rf ~/.cache/flashinfer rm -rf ~/.cache/vllm/torch_compile_cache rm -rf ~/.cache/vllm/torch_aot_compile rm -rf /tmp/torchinductor_$(whoami) gpt-oss-120bのvLLMでの実行 次に、gpt-oss-120bをvLLMで実行してみます。gpt-oss-120bはOpenAIが公開しているオープンモデルです。 事前にモデルをダウンロードしておきます。 pip install -U huggingface-hub mkdir ~/model cd ~/model hf auth login hf download openai/gpt-oss-120b --local-dir . vocabファイル gpt-oss-120bを実行するには、vocabファイルが必要になります。 以下のコマンドで、tiktokenのvocabファイルをダウンロードして、環境変数 TIKTOKEN_ENCODINGS_BASE にパスを指定します。 mkdir tiktoken_encodings wget -O tiktoken_encodings/o200k_base.tiktoken "https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken" wget -O tiktoken_encodings/cl100k_base.tiktoken "https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken" export TIKTOKEN_ENCODINGS_BASE=<tiktoken_encodingsのパス> gpt-oss向けのパッチ gpt-oss-120bをvLLMで実行していると動いてるように見えるのですが、ログを見ると実際にはエラーが発生しています。 公式にも Issueが報告されています 。 以下のリポジトリにこの問題を修正するための パッチ が公開されているので、これを適用します。こちらの内容についても有志の方の作成したものとなっておりますので、適用の際には十分に注意をして適用を行ってください。 wget https://raw.githubusercontent.com/sriganesh123/vllm/refs/heads/docker/gptoss-fix-images/docker/gptoss-fix/apply_fix.py python3 apply_fix.py また同様の内容が公式へもPull Requestとして 提出されています 。 このPull Requestがマージされるまでは、上記のパッチを適用してgpt-oss-120bをvLLMで実行する必要があります。 gpt-oss-120bをvLLMで実行 export VLLM_MXFP4_BACKEND=marlin export VLLM_FASTSAFETENSORS_NOGDS=1 export VLLM_MARLIN_USE_ATOMIC_ADD=1 export CUDA_DEVICE_MAX_CONNECTIONS=1 export PYTORCH_ALLOC_CONF=expandable_segments:True export CUDA_VISIBLE_DEVICES=0 # 先ほどDLしたvocabファイルのパスを指定する export TIKTOKEN_ENCODINGS_BASE=<tiktoken_encodingsのパス> # vLLM実行 vllm serve --model <モデルの存在しているパス> \ --served-model-name local-vllm \ --host 0.0.0.0 \ --quantization mxfp4 \ --enable-auto-tool-choice \ --tool-call-parser openai \ --reasoning-parser openai_gptoss \ --load-format fastsafetensors \ --gpu-memory-utilization 0.80 \ --kv-cache-dtype fp8 \ --enable-prefix-caching \ --max-model-len 131072 \ --max-num-batched-tokens 32768\ --max-num-seqs 16 \ --max-cudagraph-capture-size 32 Open WebUIなどから、OpenAI互換のAPIサーバとして動作しているvLLMにアクセスしてみます。 問題なく動作していることが確認できました。 まとめ 今回は、DGX Sparkの互換機であるDell Pro Max with GB10にvLLMサーバを構築してみました。 GB10はまだ新しいGPUで、vLLMもまだ完全に対応しているわけではないため、パッチの適用などが必要でしたが、無事に動作させることができました。 今後は、他にも色々なモデルを試してみたり、性能評価を行ってみたいと思います。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @yamashita.tsuyoshi レビュー: @sato.taichi ( Shodo で執筆されました )
XI本部、2025 Japan AWS Jr. Champions の佐藤悠です。 本記事ではAWSのAmazon Bedrock AgentCore(以下:AgentCore)を用いて、AI AgentをAWSにホストするところまでをやっていきます。 Agentを用いて自分専用のチャットアプリを作成したいと考えたのが、モチベーションです。 AgentCoreとは Amazon Bedrock AgentCore は、効果的なエージェントを大規模かつ安全に構築、デプロイ、運用するためのエージェントプラットフォームです。 引用: https://aws.amazon.com/jp/bedrock/agentcore/ 上記のようにAI Agent構築のためのプラットフォームになっており、変化の速いAIサービスに対応可能な素早いデプロイに強みがあると思っています。 デモ構成のAgentをローカルで立ち上げる コード エージェントの作成は Strands Agents というOSSのSDKを使用します。 数行のコードと直感的なアノテーションをもとにAgnetを実装できるので今回はこちらを採用し、検証をします。 早速、 公式ドキュメント の例を参考にStrandsでツールの作成、Agentの定義をします。 from strands import Agent, tool from strands_tools import calculator # Import the calculator tool import argparse import json from bedrock_agentcore.runtime import BedrockAgentCoreApp from strands.models import BedrockModel app = BedrockAgentCoreApp() # Create a custom tool @ tool def weather (): """ Get weather """ # Dummy implementation return "sunny" model_id = "global.anthropic.claude-haiku-4-5-20251001-v1:0" model = BedrockModel( model_id=model_id, ) agent = Agent( model=model, tools=[calculator, weather], system_prompt= "You're a helpful assistant. You can do simple math calculation, and tell the weather." ) @ app.entrypoint def strands_agent_bedrock (payload): """ Invoke the agent with a payload """ user_input = payload.get( "prompt" ) print ( "User input:" , user_input) response = agent(user_input) return response.message[ 'content' ][ 0 ][ 'text' ] if __name__ == "__main__" : app.run() 引用: runtime_with_strands_and_bedrock_models.ipynb このコードでは strands_toolsで既に構築済みのツール群 を用い、計算ツールが定義されています。 また、 カスタムツール も使用しています。 自作の関数を@toolデコレーターで使用可能なツールとして認識 させられます。 この関数名をAgent()の初期化時に渡すことでツールの使用が可能になる仕組みです。 Agentがユーザーの入力を受け取り回答を生成する関数を、 @app.entrypointデコレーターでハンドラーとして指定 しています。 app = BedrockAgentCoreApp()でランタイムアプリケーションを初期化し、app.run()でローカルHTTPサーバーを起動してリクエストを受け付けられる状態にしています。 python weather.py 実行 ↓ app.run() でサーバー起動 ↓ HTTPリクエスト待ち受け ↓ POST /invocations にリクエスト ↓ @app.entrypoint の関数が実行 ↓ レスポンス返却 環境構築 次にvenvを用いた環境を構築します。 #python version確認(※v3.10以上が必須です) > python --version Python 3.14.2 #環境分離 > python -m venv .venv #仮想環境を有効にする > source .venv/bin/activate 以下のrequiremets.txtを記述します。 strands-agents strands-agents-tools bedrock-agentcore-starter-toolkit フォルダ構成は現時点で以下のようになっています。 workspace/ ├──.venv/ ├──weather.py └──requirements.txt workspaceで以下のコマンドを実行し、モジュールのインストールを実行します。 pip insatll -r requirements.txt ローカルでの実行 次に以下のコマンドを実行し、サーバーの起動を実施します。 #ローカルでの起動 >python3 weather.py #別ターミナルでのレスポンス確認 > curl -X POST http://localhost:8080/invocations -H "Content-Type: application/json" -d '{"prompt": "What is the weather?"}' #レスポンス "The weather is **sunny**! It looks like a beautiful day outside." 以上のようにしてレスポンスを確認することができました。 ここまでまとめ AgentCoreを用いてサーバーを起動すると以下のような構成でAgentをローカルで起動することができます。 HTTPを8080でリッスンする /invocationエンドポイントが作成される /pingのエンドポイントがhealth checkのために作成される レスポンスの自動フォーマット エラーハンドリング(※AWS基準に準拠) デプロイ設定 コード 以下のコードでDockerFileの作成を含む構成要件の設定をします。 from bedrock_agentcore_starter_toolkit import Runtime from boto3.session import Session boto_session = Session() region = boto_session.region_name agentcore_runtime = Runtime() agent_name = "strands_claude_getting_started" response = agentcore_runtime.configure( entrypoint= "strands_claude.py" , auto_create_execution_role= True , auto_create_ecr= True , requirements_file= "requirements.txt" , region=region, agent_name=agent_name ) response 設定ファイル実行 フォルダ構成 workspace/ ├──.venv/ ├──weather.py ├──cofigure.py(※本手順で作成) ├──Dockerfile(※ファイル実行後生成) ├──.bedrock_agentcore.yaml(※ファイル実行後生成) └──requirements.txt 以下のコマンドを実行します python3 cofigure.py Dockerfile 作成されるDockerfileは以下のような構成になります FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim WORKDIR /app # All environment variables in one layer ENV UV_SYSTEM_PYTHON=1 \ UV_COMPILE_BYTECODE=1 \ UV_NO_PROGRESS=1 \ PYTHONUNBUFFERED=1 \ DOCKER_CONTAINER=1 \ AWS_REGION=ap-northeast-1 \ AWS_DEFAULT_REGION=ap-northeast-1 COPY requirements.txt requirements.txt # Install from requirements file RUN uv pip install -r requirements.txt RUN uv pip install aws-opentelemetry-distro==0.12.2 # Signal that this is running in Docker for host binding logic ENV DOCKER_CONTAINER=1 # Create non-root user RUN useradd -m -u 1000 bedrock_agentcore USER bedrock_agentcore EXPOSE 9000 EXPOSE 8000 EXPOSE 8080 # Copy entire project (respecting .dockerignore) COPY . . # Use the full module path CMD ["opentelemetry-instrument", "python", "-m", "weather"] Python 3.14 + uv の軽量イメージを使用 東京リージョンをデフォルトに設定 依存パッケージをインストール OpenTelemetryを追加 非rootユーザー(bedrock_agentcore)を作成して切り替え 8080・8000・9000 ポートを公開 OpenTelemetry計装付きで weather.py を起動 以上のようなDockerfileが自動で作成されます。 AWS設定項目 ECRのレポジトリを自動で作成する設定や、agentの名前が渡されたyaml(.bedrock_agentcore.yaml)が作成されることが確認できました。 default_agent : strands_claude_getting_started agents : strands_claude_getting_started : name : strands_claude_getting_started language : python node_version : null entrypoint : /home/sato/agent-core/weather.py deployment_type : container runtime_type : null platform : linux/arm64 container_runtime : none source_path : null aws : execution_role : null execution_role_auto_create : true account : 'mask' #アカウント番号 region : ap-northeast-1 ecr_repository : null ecr_auto_create : true s3_path : null s3_auto_create : false network_configuration : network_mode : PUBLIC network_mode_config : null protocol_configuration : server_protocol : HTTP observability : enabled : true lifecycle_configuration : idle_runtime_session_timeout : null max_lifetime : null bedrock_agentcore : agent_id : null agent_arn : null agent_session_id : null codebuild : project_name : null execution_role : null source_bucket : null memory : mode : NO_MEMORY memory_id : null memory_arn : null memory_name : null event_expiry_days : 30 first_invoke_memory_check_done : false was_created_by_toolkit : false identity : credential_providers : [] workload : null aws_jwt : enabled : false audiences : [] signing_algorithm : ES384 issuer_url : null duration_seconds : 300 authorizer_configuration : null request_header_configuration : null oauth_configuration : null api_key_env_var_name : null api_key_credential_provider_name : null is_generated_by_agentcore_create : false この後にデプロイをしますが、AgentCoreはこの設定項目を参照します。 デモ構成のAgentをAWSにデプロイする コード フォルダ構成 workspace/ ├──.venv/ ├──weather.py ├──cofigure.py(※本手順で編集) ├──Dockerfile ├──.bedrock_agentcore.yaml └──requirements.txt 起動のためのコード # configure.pyの末尾のresponceを以下の記述に置換 launch_result = agentcore_runtime.launch() デプロイ実行 以下のコマンドを実行し、デプロイを実施します。 python3 configure.py これまでの設定方法に問題がなければエラーなくデプロイが完了するはずです。 呼び出しテスト コード フォルダ構成 workspace/ ├──.venv/ ├──weather.py ├──cofigure.py ├──Dockerfile ├──.bedrock_agentcore.yaml ├──client.py(※本手順で追加) └──requirements.txt 以下のコードでAgentを呼び出してみます import boto3, json agent_arn = "arn:aws:bedrock-agentcore:<your_arn>" agentcore_client = boto3.client( 'bedrock-agentcore' , region_name= "ap-northeast-1" ) response = agentcore_client.invoke_agent_runtime( agentRuntimeArn=agent_arn, qualifier= "DEFAULT" , payload=json.dumps({ "prompt" : "What is the weather?" }) ) # レスポンスの中身を取り出して表示 body = response[ 'response' ].read() print (json.loads(body)) 実行結果 以下のコマンドを実行して上記のコードを実行し、レスポンスが返却されることを確認しました。 ❯ python client.py The weather is **sunny**! ☀️ It looks like a nice day out there. ここまでまとめ 今回の手順では、以下のようなフローでエンドポイントがホストされます zip化したコードとDockerfileをS3にアップロードする CodeBuildがS3からダウンロードしビルドを実行、ECRへプッシュする RuntimeAgentがこのイメージを利用して、エンドポイントを公開 ※この手順ではエンドポイントの呼び出しにAWSの認証情報を使用 Terraformコード化する 実際の運用では、デプロイフローをIaCで管理することになるでしょう。 bedrock_agentcore_starter_toolkit で自動作成できる良さはありますが、使用するコンポーネントをTerraformする試みを実施します。 Terraformコード化 どのような設定でリソースが作成されるかを知りたいので、観測可能な範囲で(※本当はCloudTrailでトレースするべきではありますが...)importを実行します。 この際にimport時の自動生成機能を用いてtfファイルを記述します。 import 以下のように記述してimportをします。 ロールだけは CreateRole でフィルターし、作業時間からCloudTrailで特定しました。 import { id = "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask" to = aws_iam_role.example_role_codebuild } import { id = "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask" to = aws_iam_role.example_role_runtime } import { id = "bedrock-agentcore-runtime-415467724776-ap-northeast-1-7pd9unr6i" to = aws_s3_bucket.example } import { id = "bedrock-agentcore-strands_claude_getting_started" to = aws_ecr_repository.example } import { id = "bedrock-agentcore-strands_claude_getting_started-builder" to = aws_codebuild_project.example } import { id = "strands_claude_getting_started-mask" to = awscc_bedrockagentcore_runtime.example } 💡AgentCore Runtime import の id に何を指定するの? AgentCore RuntimeのIDとは個別のランタイムをひらいた際に表示される「ランタイム ID」のことでした。 自動生成結果 自動生成の結果は以下のようになりました。 サフィックスやアカウント番号はマスクしています。 # __generated__ by Terraform # Please review these resources and move them into your main configuration files. # __generated__ by Terraform resource "aws_codebuild_project" "example" { badge_enabled = false build_timeout = 60 concurrent_build_limit = 1 description = null encryption_key = "arn:aws:kms:ap-northeast-1:mask:alias/aws/s3" name = "bedrock-agentcore-strands_claude_getting_started-builder" project_visibility = "PRIVATE" queued_timeout = 480 resource_access_role = null service_role = "arn:aws:iam::mask:role/AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask" source_version = null tags = {} tags_all = {} artifacts { artifact_identifier = null bucket_owner_access = null encryption_disabled = false location = null name = null namespace_type = null override_artifact_name = false packaging = null path = null type = "NO_ARTIFACTS" } cache { location = null modes = [] type = "NO_CACHE" } environment { certificate = null compute_type = "BUILD_GENERAL1_MEDIUM" image = "aws/codebuild/amazonlinux2-aarch64-standard:3.0" image_pull_credentials_type = "CODEBUILD" privileged_mode = true type = "ARM_CONTAINER" } source { buildspec = "\nversion: 0.2\nphases:\n build:\n commands:\n - echo \"Starting parallel Docker build and ECR authentication...\"\n - |\n docker build -t bedrock-agentcore-arm64 . &\n BUILD_PID=$!\n aws ecr get-login-password --region $AWS_DEFAULT_REGION | \\\n docker login --username AWS --password-stdin mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started &\n AUTH_PID=$!\n echo \"Waiting for Docker build to complete...\"\n wait $BUILD_PID\n if [ $? -ne 0 ]; then\n echo \"Docker build failed\"\n exit 1\n fi\n echo \"Waiting for ECR authentication to complete...\"\n wait $AUTH_PID\n if [ $? -ne 0 ]; then\n echo \"ECR authentication failed\"\n exit 1\n fi\n echo \"Both build and auth completed successfully\"\n - echo \"Tagging image with version 20260219-074315-241...\"\n - \"docker tag bedrock-agentcore-arm64:latest mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241\"\n post_build:\n commands:\n - echo \"Pushing versioned image to ECR...\"\n - \"docker push mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241\"\n - echo \"Build completed at $(date)\"\n" git_clone_depth = 0 insecure_ssl = false location = "bedrock-agentcore-codebuild-sources-mask-ap-northeast-1/strands_claude_getting_started/source.zip" report_build_status = false type = "S3" } } # __generated__ by Terraform from "bedrock-agentcore-strands_claude_getting_started" resource "aws_ecr_repository" "example" { force_delete = null image_tag_mutability = "MUTABLE" name = "bedrock-agentcore-strands_claude_getting_started" tags = {} tags_all = {} encryption_configuration { encryption_type = "AES256" kms_key = null } image_scanning_configuration { scan_on_push = false } } # __generated__ by Terraform from "bedrock-agentcore-runtime-mask-ap-northeast-1-7pd9unr6i" resource "aws_s3_bucket" "example" { bucket = "bedrock-agentcore-runtime-mask-ap-northeast-1-7pd9unr6i" bucket_prefix = null force_destroy = null object_lock_enabled = false tags = {} tags_all = {} } # __generated__ by Terraform from "strands_claude_getting_started-mask" resource "awscc_bedrockagentcore_runtime" "example" { agent_runtime_artifact = { code_configuration = null container_configuration = { container_uri = "mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241" } } agent_runtime_name = "strands_claude_getting_started" authorizer_configuration = null description = null environment_variables = {} lifecycle_configuration = { idle_runtime_session_timeout = 900 max_lifetime = 28800 } network_configuration = { network_mode = "PUBLIC" network_mode_config = null } protocol_configuration = "HTTP" request_header_configuration = null role_arn = "arn:aws:iam::mask:role/AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask" tags = {} } # __generated__ by Terraform from "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask" resource "aws_iam_role" "example_role_codebuild" { assume_role_policy = jsonencode({ Statement = [{ Action = "sts:AssumeRole" Condition = { StringEquals = { "aws:SourceAccount" = "mask" } } Effect = "Allow" Principal = { Service = "codebuild.amazonaws.com" } }] Version = "2012-10-17" }) description = "CodeBuild execution role for Bedrock AgentCore ARM64 builds" force_detach_policies = false max_session_duration = 3600 name = "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask" name_prefix = null path = "/" permissions_boundary = null tags = {} tags_all = {} } # __generated__ by Terraform from "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask" resource "aws_iam_role" "example_role_runtime" { assume_role_policy = jsonencode({ Statement = [{ Action = "sts:AssumeRole" Condition = { ArnLike = { "aws:SourceArn" = "arn:aws:bedrock-agentcore:ap-northeast-1:mask:*" } StringEquals = { "aws:SourceAccount" = "mask" } } Effect = "Allow" Principal = { Service = "bedrock-agentcore.amazonaws.com" } Sid = "AssumeRolePolicy" }] Version = "2012-10-17" }) description = "Execution role for BedrockAgentCore Runtime - strands_claude_getting_started" force_detach_policies = false max_session_duration = 3600 name = "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask" name_prefix = null path = "/" permissions_boundary = null tags = {} tags_all = {} } 💡importの不具合 `aws_codebuild_project`の`concurrent_build_limit = 1`はなぜかインポートした際に0になってました。 1に変更してplan後に差分がなかったのでこれが正ですが、生成ができなかった理由は追及してません。 Terraform化したことで、AgentCore周辺の基本的な構成がはっきりしてきました。 感想 記事が長くなったので一旦ここで切ります。 構成図を眺めてTerraform化してみるとAWSの一般的なCI/CDパイプラインの部分は理解できますが、AgentをホストするRuntimeがどのような機能を担うのかよくわかりません。 この詳細を追うのがAgentCoreプラットフォームを理解する重要な観点になると考えました。 今は認証等を実装しつつあるので、今後はその記事を出せればと思っています。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
こんにちは、クロスイノベーション本部リーディングエッジテクノロジーセンターの大岡叡です。 私は、昨年AWS資格を全種類取得しまして、現在Google Cloudの資格勉強をしております。 AWS資格の勉強法をGoogle Cloudの資格勉強にも適用しようと考え、勉強方法の改善検討をしたところ、AIで自動化できる部分があることに気づきました。本記事では、これまでの勉強法の課題、その課題に対するAIを用いた解決策、そして実際に導入してみての所感をお伝えしたいと思います。 これまでの勉強法の課題 AIを用いた解決策 作成したスキル: /mq(Make Question) スキルを使うための準備 使い方 【Google Cloudに関する問題を作成(メインの使い方)】 【AWSサービスとの比較問題を作成】 【一般的な知識問題を作成】 導入してみての所感 まとめ これまでの勉強法の課題 これまでのAWS資格勉強は以下のフローで進めていました。 【1問ごとに以下を繰り返す】 1. 問題集を解く 2. 分からなかった知識を公式ドキュメントで調べる 3. Notionに問題形式でまとめる(後から復習に活用) この中で2と3にかなり時間がかかっていました。 AIを用いた解決策 課題であった「公式ドキュメントの調査」と「Notionへの問題作成」を、 Claude Codeのスキル機能 を使って自動化しました。 作成したスキル: /mq (Make Question) 作成したスキルはこちら(クリックで表示) --- name: mq description: Google Cloud資格試験の勉強用に、問題を1問Notionにtoggle形式で作成する。問題文を指定して呼び出す。 user-invocable: true argument-hint: "<問題文>" --- # 試験勉強用 問題作成スキル ユーザーが指定した問題文に対して、正確な情報に基づいた回答を調査し、Notionページにtoggle形式で1問追加する。 ## 対象Notionページ - **ページURL**: ` https://www.notion.so/Page-Title-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ` - **ページID**: ` xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ` ## ページ構造 このページには以下の2つのセクションがある: - ` ## Google Cloudに関する知識 {color="orange_bg"} ` — Google Cloud関連の問題を配置 - ` ## 一般的な知識 {color="orange_bg"} ` — それ以外の一般的な知識の問題を配置 ## toggleのフォーマット(Notion-flavored Markdown) 回答作成時・Notion挿入時の両方で使うフォーマット。子要素は必ずタブでインデントすること(インデントしないとtoggle外に出てしまう)。 ``` <details> <summary>**{問題文}**</summary> {回答テキスト} - {箇条書きポイント1} - {箇条書きポイント2} - {箇条書きポイント3} <empty-block/> **参考URL:** - [{ページタイトル1}]({URL1}) - [{ページタイトル2}]({URL2}) </details> ``` ## 実行手順 ### Step 1: 問題の分類 問題文の内容が以下のどちらに該当するか判断する: - **Google Cloud関連**: Google Cloudのサービス・機能に関する問題全般。例: Compute Engine、GKE、Cloud Run、Cloud Storage、BigQuery、VPC、IAM、Cloud SQL、Pub/Sub、Vertex AI、Gemini on Google Cloud など。Googleが開発・提供するモデルやツール(Gemma、Gemini、Imagen、NotebookLM、Google AI Studio等)もこちら - **一般的な知識**: Google固有でない技術知識全般。例: ネットワーキング(TCP/IP、DNS、ロードバランシング)、データベース理論(ACID、CAP定理)、セキュリティ(暗号化、ゼロトラスト)、コンテナ・Kubernetes一般、CI/CD、IaC、機械学習の一般理論、AI倫理、プロンプトエンジニアリング、ソフトウェアアーキテクチャ など ### Step 2: 情報収集 & 挿入位置の特定(並行実行可) 以下の2つは独立しているので、並行して実行してよい。 #### 2a: 情報収集 **Google Cloud関連の場合:** 1. ` mcp__google-dev-knowledge__search_documents ` で公式ドキュメントを検索 2. 検索結果から最も関連性の高いドキュメントの ` parent ` を使い、 ` mcp__google-dev-knowledge__get_documents ` で**必ず**フルドキュメントを取得する(検索結果のチャンクだけで回答を作成しない) 3. 取得したドキュメントが問題の回答に不十分な場合は、以下を試みる: - 同じ検索結果内の別のドキュメントを ` get_documents ` で取得する - 別のキーワードで ` search_documents ` から再検索する 4. 取得したドキュメントのURLを参考URLとして記録 **一般的な知識の場合:** 1. ` WebFetch ` または ` WebSearch ` で信頼できるソースから情報を収集 2. 参考URLを記録 3. Wikipediaは参考URLとして使用しない。以下のような権威あるソースを優先する: - クラウド・インフラ: AWS公式ドキュメント、Microsoft Learn、Red Hat、CNCF - ネットワーク・セキュリティ: IETF RFC、OWASP、NIST、Cloudflare Learning - データベース・データ: PostgreSQL/MySQL公式ドキュメント、MongoDB University - AI/ML: Google for Developers、IBM、NVIDIA Developer Blog、大学サイト(Stanford等) - 一般技術: 著名な技術ブログ(Martin Fowler、InfoQ、DataCamp等) **AWSの知識が必要な場合(カテゴリ問わず):** 問題の回答にAWSサービスの知識が必要な場合: 1. ` mcp__aws-documentation-mcp-server__search_documentation ` でAWS公式ドキュメントを検索 2. 検索結果から最も関連性の高いページのURLを使い、 ` mcp__aws-documentation-mcp-server__read_documentation ` で**必ず**フルドキュメントを取得する(検索結果のcontextだけで回答を作成しない) 3. 取得したドキュメントが問題の回答に不十分な場合は、以下を試みる: - 同じ検索結果内の別のページを ` read_documentation ` で取得する - 別のキーワードで ` search_documentation ` から再検索する - ` recommend ` で関連コンテンツを探す 4. 必要に応じて ` mcp__aws-documentation-mcp-server__recommend ` で関連コンテンツも探す #### 2b: 挿入位置の特定(state.json) ` state.json ` (スキルと同じディレクトリ)を ` Read ` ツールで読み取り、対象セクションの最後の参考URL行を取得する。 - **Google Cloud関連**: ` google_cloud_last_ref ` の値を使う - **一般的な知識**: ` general_last_ref ` の値を使う ` state.json ` が存在しない場合のみ、 ` mcp__notion__notion-fetch ` でページ全体を取得し、各セクションの最後の参考URL行を特定する。 ### Step 3: 回答の作成 収集した情報に基づき、上記のtoggleフォーマットで1問分の回答を作成する: - 正確かつ簡潔な回答。箇条書きで要点を整理 - 参考URLは必ず含める - 作成するのは **1問のみ** ### Step 4: Notionページを更新 ` mcp__notion__notion-update-page ` を使って、適切なセクションにtoggleブロックを1つ追加する。 #### 挿入方法: replace _ content _ range ` insert_content_after ` は使わない( ` </details> ` がページ内に多数存在し、 ` ... ` で繋ぐと複数マッチしてエラーになるため)。 Step 2bで取得した参考URL行をセレクタの起点に使い、 ` replace_content_range ` で置換する。 **セクション内にtoggleが既にある場合:** 最後のtoggle内の参考URL最終行から、セクション直後のランドマークまでを ` selection_with_ellipsis ` で指定する。 - **Google Cloudセクションのランドマーク**: ` ## 一般的な知識 {color="orange_bg"} ` - **一般的な知識セクションのランドマーク**: ` <empty-block/> ` (ページ末尾のもの。参考URL起点なら一意になる) ` new_str ` には、参考URL最終行 + ` </details> ` + 新しいtoggle + ランドマーク をすべて含める。 実例(Google Cloudセクション): ``` selection_with_ellipsis: "arXiv](https://arxiv.org/abs/2406.11409)...## 一般的な知識 {color=\"orange_bg\"}" new_str: {最後の参考URL} </details> {新しいtoggle} ## 一般的な知識 {color="orange_bg"} ``` 実例(一般的な知識セクション): ``` selection_with_ellipsis: "{最後の参考URL}...<empty-block/>" new_str: {最後の参考URL} </details> {新しいtoggle} <empty-block/> ``` **セクション内にtoggleがない(空のセクション)場合:** セクション見出し+改行+ ` <empty-block/> ` をフルテキストで ` selection_with_ellipsis ` に指定する(省略 ` ... ` は使わない)。 #### selection _ with _ ellipsis のルール 1. **セレクタ内で `...` の前後それぞれ最低20文字以上を確保する**(短すぎると一意にならない) 2. **`</details>` をセレクタの終端に使わない**(ページ内に多数存在し、複数マッチする) 3. **セクション見出し単体をセレクタに使わない**(2件マッチする)。ただし、ランドマーク(セレクタ末尾)として使う場合は、開始位置が一意な参考URLなので問題ない 4. **空セクションでは `...` を使わず、フルテキストを指定する**( ` <empty-block/> ` が複数箇所にあるため ` ... ` を使うと複数マッチする) 5. **`{{}}` を使わない**: ` notion-fetch ` ではURLが ` {{https://...}} ` 形式で返されるが、セレクタには ` {{}} ` なしのURLを使う #### 挿入失敗時のフォールバック ` replace_content_range ` が失敗した場合、 ` state.json ` のキャッシュが古い可能性がある: 1. ` mcp__notion__notion-fetch ` でページ全体を取得 2. 対象セクションの最後の参考URL行を特定し直す 3. 正しいセレクタで再度 ` replace_content_range ` を実行 4. 成功後、 ` state.json ` を更新する ### Step 5: state.json の更新 挿入が成功したら、 ` state.json ` を ` Edit ` ツールで更新する。今回追加したtoggleの参考URL最終行で、対象セクションのキーを上書きする。 - **Google Cloud関連**: ` google_cloud_last_ref ` を更新 - **一般的な知識**: ` general_last_ref ` を更新 これにより、次回のスキル実行時にフルフェッチなしで挿入位置を特定できる。 ## 出力例 ``` <details> <summary>**Vertex AI Searchとは何ですか?主な機能と用途を説明してください。**</summary> Vertex AI Searchは、Google Cloudが提供するエンタープライズ向けの検索ソリューションです。 - Webサイト、構造化データ、非構造化データに対して高品質な検索体験を提供 - LLMを活用した自然言語での検索が可能 - RAG(Retrieval-Augmented Generation)パターンの実装に活用できる <empty-block/> **参考URL:** - [Vertex AI Search overview](https://cloud.google.com/generative-ai-app-builder/docs/enterprise-search-introduction) </details> ``` 今回作成したのは、 問題文を渡すだけで、公式ドキュメント調査からNotionへの問題・解答作成までを一気通貫で行うスキル です。 公式ドキュメントの調査は、Google Developer Knowledge MCPとAWS Documentation MCPを使用し、Notionへの問題・解答作成はNotion MCPを利用します。 以下がNoteBookLMに作ってもらった、このスキルのイメージです。 スキルを使うための準備 以下の準備が必要です。 上記スキルのマークダウンを SKILL.md として作成し、任意のディレクトリの .claude/skills/mq/ 配下に配置する。 Google Developer Knowledge MCPをセットアップする。 Developer Knowledge MCP公式ドキュメント に従って、APIキーを発行する。 以下のコマンドを .claude があるプロジェクトルートで実行する。 YOUR_API_KEY の部分には、発行したAPIキーの値を入力する。 claude mcp add google-dev-knowledge --scope project --transport http https://developerknowledge.googleapis.com/mcp --header "X-Goog-Api-Key: YOUR_API_KEY" AWS Documentation MCPの設定をする。以下のコマンドをプロジェクトルートで実行する。 claude mcp add aws-documentation-mcp-server -s project -e FASTMCP_LOG_LEVEL=ERROR -e AWS_DOCUMENTATION_PARTITION=aws -- uvx awslabs.aws-documentation-mcp-server@latest Notionの準備及びNotion MCPの準備 以下のスクショのように、Notionのページに「Google Cloudに関する知識」と「一般的な知識」のセクションを作成する。 ## 2つの文字サイズで、背景色をオレンジに設定する。(ページのタイトルは受験する資格名など何でもよい。) Notion MCPの設定をする。以下のコマンドをプロジェクトルートで実行する。 claude mcp add --transport http notion --scope project https://mcp.notion.com/mcp Notionの認証&スキル更新 プロジェクトルートでClaude Codeを立ち上げ、 /mcp コマンドを使用してNotion MCPの認証を通す。 以下のプロンプトをClaude Codeに投げる。 <あなたのNotion URL> の部分は、4. で作成したNotionのページのURLに置き換える。 <あなたのNotion URL> .claude\skills\mq\SKILL.mdのページURLとページIDを上記URLに基づいて更新してください。 以上で準備は終わりです。 使い方 【Google Cloudに関する問題を作成(メインの使い方)】 プロジェクトルートでClaude Codeを起動し、以下のように入力するだけです。 /mq Cloud Runとは何ですか? するとClaude CodeがGoogle Developer Knowledge MCPを用いて、公式ドキュメントを取得し、正しい情報に基づいて問題・解答を自動でNotion上に作成してくれます。 toggleの中身の一番下に参考URLが書かれ、公式ドキュメントへの導線が張られています。 【AWSサービスとの比較問題を作成】 AWSサービスとGoogle Cloudを比較する問題も作成できます。 その場合は、Claude CodeはGoogle Developer Knowledge MCPだけでなくAWS Documentation MCPを利用して、正しい情報に基づいて問題・解答の作成をしてくれます。 【一般的な知識問題を作成】 クラウドベンダー資格は、問題を解くうえで一般的なIT知識が必要になることも多くあります。 その一般的な知識をまとめるセクションも用意しており、クラウドサービスには関係のない問題も作成することができます。 やり方は同じで、以下のようなプロンプトをClaude Codeに投げます。セクションの分別はClaude Codeがよろしくやってくれるようにスキルで定義しています。 /mq OIDCとOauth2.0の違いは? 導入してみての所感 このClaude Codeのスキルを使用した勉強をして、先日 Google Cloud Generative AI Leader に無事合格しました。 今まで時間がかかっていた「公式ドキュメントの調査」と「Notionへの問題作成」をClaude Codeに全任せできたので、「問題を解く」・「知識を暗記する」この二つに集中でき、学習効率が飛躍的に向上したように感じました。 まとめ 今回はClaude Codeのスキルを使って勉強効率を上げた話をご紹介しました。 少しでも皆さんが資格勉強をする際の参考になったら嬉しいです。 最後までお読みいただきありがとうございました! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ooka.toru レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは。エンタープライズ第一本部 戦略ソリューション 1 部の英です。 普段はWebアプリやスマホアプリの案件などを担当しています。あと、趣味でAIを勉強しています。 Codexにサブエージェント機能が追加されたのでさっそく試してみようと思います。 Claude羨ましいなあ、私もAIチームを束ねてYesを連打するだけの仕事がしたいなあと指をくわえて待っていたのでこれは朗報です。 https://developers.openai.com/codex/subagents ここからが本題 1. 今回作るもの 2. Codex用のAGENTS.mdを作成する 3. Codexにサブエージェントを活用した開発を指示する 4. 腕を組んで笑顔で見守る 5. 動作確認をする さいごに ここからが本題 1. 今回作るもの 今回はサブエージェントを使って簡単なWebアプリを同時並行で開発してみます。 社内の新人向けのワーキングでこんなテーマがあったので、これをシミュレーションするプログラムを作りましょう。 あなたはエレベータの制御ロジックを開発するシステムエンジニアです。 エレベータの動作を制御するアルゴリズムを作成してください。 ⇒このロジックによってエレベータは自律的に動作します アルゴリズムは可能な限り利用する人が快適にエレベータを利用できることを目指します。 不足する情報があれば、常識的な範囲で追加して貰って構いません。 以下、前提とします。 ・エレベータは2台です ・各エレベーターの最大搭乗人数は10人です(各エレベーターは自分の搭乗人数をセンターで把握できるものとします) ・建物は3階建てです ・2台のエレベータはそれぞれ自分自身について以下の情報を認識することが可能です ・何階にいるか?(何階と何階の途中か?) ・止まっているか?動いているか?どちらの方向に動いているか?動こうとしているか? ・ドアがどんな状態か ・2台のエレベータはそれぞれもう1台のエレベータの状態を認識することが可能です(相手の状態に合わせて自分がどちらに向かうべきかを選択する) ・何階にいるか?(何階と何階の途中か?) ・止まっているか?動いているか?どちらの方向に動いているか?動こうとしているか? ・ドアがどんな状態か ・エレベータは各フロアのエレベータを呼ぶボタンの状態を知ることができます(各階で待機している客がどちらの方向に行きたいか) ・エレベータは自分のエレベータ内のボタンの状態を知ることができます(乗客がどのボタンを押したか) ・エレベータは以下の情報は一切わかりません ・各フロアで待っている人の有無、人数 ・もう1台のエレベータに乗っている人の有無、人数 ・エレベータはそれぞれ、もう1台のエレベータに対して動作を指示することはできません 解けますか?私は解けません。新人じゃなくて良かった。 2. Codex用のAGENTS.mdを作成する 上記の内容を伝え、Canvasにmd形式でファイルを出力してもらいます。 3. Codexにサブエージェントを活用した開発を指示する VSCodeを開き、AGENTS.mdを参照させます。 ついでに今回は サブエージェント を使用することを強制します。 4. 腕を組んで笑顔で見守る まずは親エージェントが土台の構築をして、それからサブエージェントのそれぞれに仕事を振るようです。 今回は UI実装 と ロジック実装 の2つのエージェントを使って開発するようです。 サブエージェントである「Feynman」「Mencius」が立ち上がりました。 各エージェントはそれぞれGPT-5.4-Miniが選択されました。 ちなみに、命名は以下になっていますが、センスが気になります。 Feynman = ロジック担当っぽい名前(物理学者の Richard Feynman に由来?) Mencius = UI/人間寄り担当っぽい名前(国思想家の 孟子(Mencius) に由来?) サブエージェントは以下で確認でき、「Open」を押下するとそれぞれのサブエージェントの仕事を覗くことが出来ます。 サブエージェントを覗くと、ワーカーとしての指示(プロンプト)を確認することが出来ます。 ロジック側の進捗を確認します。 UI側の進捗を確認します。 親エージェント(統合役)を確認します。 サブエージェントでUIの実装が完了したことを親が把握しました。 サブエージェントでロジックの実装が完了したことを親が把握しました。 UIとロジックを繋げる統合作業に着手するようです。 統合およびテストが完了したようです。 5. 動作確認をする 今回はViteで実装してくれたので、ローカルでの動作確認が簡単です。 ターミナルで「npm run dev」と叩き、ブラウザ上で確認します。 できあがったもの。 ご丁寧に以下の機能が付いています。 ・実行速度の調整 ・シナリオの選択(早朝ピークや平常時、ランダムなど) ・ロジック2つの比較アニメーション ・ロジック2つの各種スコア ロジックは2つ実装したようです。 ・左:呼ばれたらとにかくその階に移動することが優先(決まった進行方向はない) ・右:エレベータの移動方向を保つことが優先 通常シナリオ(Normal)で動かしてみます。 アニメーションを確認する。 左のアルゴリズムはエレベーターBが1→2階に移動したのちに、2→1階に移動しています。(1階のボタンが押されたため) 右のアルゴリズムはエレベーターAとBの両方が上下移動の方向を保っています。 スコアの変化を確認する。 スコアの計算式は以下になっています。 finalScore = 1000 4.0 * max(0, avgWait - 15) 8.0 * max(0, p95Wait - 30) 12.0 * max(0, maxWait - 45) 300.0 * (leftBehindRate ^ 2) 2.0 * max(0, avgRide - 20) 1.0 * reversalsPerTrip * 100 20.0 * imbalance 10.0 * log(1 + completedTrips) 15秒以下の平均待ちは十分良い 30秒を超える p95 はかなり悪い 45秒を超える最大待ちは強く悪い 乗り残し率は強く罰する 完了輸送数は評価するが、主役にはしない ※画質がガビガビで申し訳ございません 1分間くらい動かしたときの断面がこちら。 消費電力という大きな問題を完全に無視すると、「呼ばれたらすぐ行く」(左)が今回のスコアとしては優秀だと評価されています。 (瞬間的にアルゴリズムBが優位になる時間もありました) AとBの「呼ばれたらすぐ行く」「進行方向をなるべく維持する」はさすがに頭が悪すぎるので、アルゴリズムCとして以下を追加してみました。 今のエレベーターの位置、進行方向、混み具合、すでに乗っている人の行き先などを見て、どの階をどの順番で取りに行くのがよさそうかを決めています。 これは前提である「2台のエレベータはそれぞれもう1台のエレベータの状態を認識することが可能です」を上手く活用しています。 早く行けるか:呼び出し階までの予想到着時間が短いほど高評価。 今の流れに合っているか:エレベーターの進行方向と同じ向きの呼び出しなら高評価。逆向きは減点。 そのエレベーターが混みすぎていないか:乗車率が高いほど減点して、もう1台に仕事を回しやすくする。 担当エリアが偏りすぎないか:2台の受け持ちが片寄らないように、ゾーン(低層側/高層側)のバランスを加味。 結果がこちら。(AとCの比較) Cの圧勝です。 さいごに SIerとして仕事をしているとコレ系の課題によく直面すると思いますが、お客様と一緒に画面を見ながらすぐ評価できるのは良いですね。良い時代になったものです。 先日Xで「JTCでは社内のAI利用が進んでおらず働きにくい」みたいなのが話題になってましたが、弊社はこの数年間で各種AIツールのライセンスや申請周りがかなり整備され、めちゃくちゃ働きやすいです。AI使って仕事したいSIのかたは是非ご検討ください。 (ClaudeCode派が多く、Codex派の私は少し肩身が狭いです) ↓ のスターを押していただけると嬉しいです。励みになります。 最後まで読んでいただき、ありがとうございました。 エンタープライズ第一本部では一緒に働いてくださる仲間を募集中です。以下のリンクからお願いします。 私たちは一緒に働いてくれる仲間を募集しています! 中途採用-エンタープライズ第一本部 新卒採用-エンタープライズ第一本部 執筆: 英 良治 (@hanabusa.ryoji) ( Shodo で執筆されました )
エンタープライズ第三本部データマネジメントコンサルティング2部の新見優果です。 先日、クロスイノベーション本部の大岡叡さんが主催する「25卒合同AWS勉強会 #2」に参加してきました!本ブログでは、その様子をお届けします。 私はAWSについてほとんど勉強したことがなく、業務でもサーバーの起動停止を担当している程度でした。しかし参加者全員が新卒1年目ということもあり、AWS初学者の私でも安心して参加できました。 開催背景(主催の大岡さんより) イベント概要 イベント内容 当日のタイムスケジュール 参加背景 発表した感想 他の発表を聞いた感想 まとめ 開催背景(主催の大岡さんより) ”#2”とあるように今回は2回目でした。第1回の勉強会については、以下のブログをご覧ください。 第1回の勉強会の後、各社代表(NECソリューションイノベータ株式会社の上田賢哉様、キャップジェミニ株式会社の遊佐康平様)と振り返りを行い、第2回の勉強会は「オンライン参加できるようにする」、「質疑応答の時間を設ける」、「テーブルを班の形にして交流しやすくする」など様々な改善を行いました。 イベント概要 開催日時:2026/3/6(金) 18:45-21:15 開催場所:電通総研 品川本社 19階会議室 開催形態:ハイブリット(対面+オンライン) 参加者:計32名 キャップジェミニ株式会社 (10名) NECソリューションイノベータ株式会社 (5名) 株式会社OptiMax (3名) 弊社 (14名) イベント内容 各社から1、2名が登壇し、AWSに関係するテーマで10分間ずつ発表しました。AWSのサービスや資格勉強、自身が開発したアプリについてなど、発表内容は多岐に渡りました。 質疑応答や交流タイム、懇親会もあり、他社の1年目との交流も活発に行われました。どのような部署に配属されたか、1年目でどのような業務を任されているかなど、なかなか聞く機会がない他社の話を聞くことができました! 当日のタイムスケジュール 時間 内容 所属 発表者 発表テーマ 18:45-18:50 開会 - - - 18:50-19:05 発表① 電通総研 新見 優果 自社製品 BusinessSPECTRE XC AWS版をセットアップしてみた ーSAPデータをクラウドDWHへ転送するソリューションについて学ぶー 19:05-19:20 発表② キャップジェミニ 渡邉 篤弥 ALBでTLS終端するってどういう意味?〜NLB→ALB構成で分かった通信相手の話〜(Security Hubの警告をきっかけに調査した、TLSの通信相手について) 19:20-19:35 発表③ NECソリューションイノベータ 鈴木 千尋 AWS資格が「仕事で役に立つ」3つのメリット 19:35-19:50 休憩 - - - 19:50-20:05 発表④ OptiMax 高橋 光 Amazon Bedrock Agentsに対する間接プロンプトインジェクションの脅威と防御 20:05-20:20 発表⑤ 電通総研 河岸 歩希 「おごり自販機マッチング機能」をLambda×DynamoDBでつくってみた 20:20-20:35 発表⑥ キャップジェミニ 窪川 壌 エミュレータで始めるAWS節約生活(LocalStackの簡単な使い方と3月のアップデートについて) 20:35-20:50 発表⑦ NECソリューションイノベータ 上田 賢哉 SOA受験を実践で振り返る 20:50-20:55 閉会 - - - 20:55-21:10 歓談タイム - - - 参加背景 私が所属している部署ではクラウドに関連する案件が増えており、私もいつか勉強しないといけないなと思っていました。そうした時、Teamsで同期が「1年目だけの4社合同AWS勉強会」を開催すると知りました。「1人で資格勉強を始めようとすると腰が重いけど、勉強会で同期の発表を聞くだけなら!刺激をもらえるかも!」と思い、参加することに決めました。 発表した感想 こうして聴講者として参加する予定でしたが、発表3週間前に「登壇してみないか」と誘っていただき、急遽発表者として参加することになりました!これまで全く勉強してこなかったのに、一体何を発表できるんだろうと思いながら、せっかくの機会だと思い引き受けました。 そこで先輩に相談したところ、部署の自社製品である「BusinessSPECTRE XCのAWS版をセットアップしてみるのはどうか」と提案していただきました。それから製品のシステム構成を学習し、環境構築に取り組みました。 セットアップではプロキシのエラーなどに悩まされましたが、先輩方や同期に助けてもらいながら何とか終えることが出来ました。当日の発表では、AWSのGlueやCDKデプロイなど、セットアップをした中での学びを発表しました。 発表後は、実際に業務でGlueを使っている人や、これからCDKデプロイをしようとしている人から声をかけてもらい、有意義な情報共有の場になりました。1人で資格勉強に励むだけでは得られない経験をすることができ、改めて参加して良かったなと思いました。 他の発表を聞いた感想 他にも6名の方が発表しましたが、セキュリティやAIに関連するテーマから、アプリ開発や資格受験の経験談まで幅広いジャンルの発表があり勉強になりました。1年目だけの開催ということもあり、前提知識がなくても理解できる内容が多く、率直な質問もしやすい雰囲気でした。また何より、同じ1年目が勉強している内容を聞き刺激をもらうことが出来ました。1人で勉強を進めることが苦手な私でしたが、このイベントを通じて励まされました! まとめ 当初はノリで参加を決めていたイベントでしたが、なぜか発表まですることになり大変でした!しかし、こうした機会がなかったら1人でAWSを触ろうとはしなかったと思うので、勉強する良いきっかけになりました。主催してくれた大岡さんをはじめ、参加してくださった皆さんには感謝です。 懇親会では学生時代の友達で集まったように盛り上がり、また次回以降も参加したいなと思いました!また多くの方に参加していただき、盛り上げていけたらいいなと思いますので、是非AWS初学者の方も安心してご参加いただけたらと思います! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @niimi.yuuka 、 @ooka.toru レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは、電通総研 XI本部 サイバーセキュリティテクノロジーセンターの櫻井です。 本記事ではISC2 CISSP認定を少し変わった観点から紹介します。 なお、本記事でご紹介する資格の情報は2025年12月時点のものとなります。 ISC2 CISSP認定とは? セキュリティ資格(認定)の中での立ち位置 筆者の受験体験 CISSP認定の特色 なぜ難しいのか?その1(出題範囲) CISSP認定で必要とされる知識 どこまで知識を深めるべきか? なぜ難しいのか?その2(認定者の立場) 優先事項はなにか? 誰目線の判断か? なぜ難しいのか?その3(試験形式) 筆者の利用したコンテンツ 書籍 Webコンテンツ Udemyコンテンツ 最後に ISC2 CISSP認定とは? CISSPは正式名称 Certified Information Systems Security Professionalであり、ISC2(アイエスシーツー)が認定しているセキュリティ関係のプロフェッショナル認定です。(※1) 技術的な分野だけでなく、セキュリティ戦略を考案するマネジメント・経営者的な視点も含まれる広範囲の知識を必要とされる認定試験です。 ※1 CISSP®とは セキュリティ資格(認定)の中での立ち位置 CISSP認定の難易度はほぼすべてのサイトにおいて最高レベルの資格群にランクされます。 セキュリティ資格の中でも傾向が異なり、主にマネジメント層向けの認定とされています。(IPA 情報処理安全確保支援士は比較的近しいものだと思います) 問題の傾向としても現れていますが、技術的に完結した正解を問う問題に加えて、現状の組織の立ち位置を踏まえどういった判断が最適かを問う問題が多いです。 筆者の受験体験 筆者は昨年2025年12月にCISSP認定試験を受験し、無事合格することができました。 CISSPは CAT方式 で実施されますが、実際の試験については100問で問題が終了し、延長されることもありませんでした。 学習期間は1か月程度で、確かに難しいが一般的に認知されている難易度レベルまで難しいか?と感じたため本記事の執筆に至っています。 CISSP認定の特色 筆者の考えるCISSP認定はなぜ難しいのか?の特色は以下の3点に分けられると思います。 出題範囲 認定者の立場 試験形式 先に簡潔に述べるとテックブログをご覧の皆さんは、クラウドの設計ややアプリ開発の業務に従事されている方が多いかと思いますが、そういった方々から見るとちょっと変わった試験であるということです。 以降で、3点について紹介したいと思います。 なぜ難しいのか?その1(出題範囲) CISSP認定の出題範囲はプラットフォームや技術領域カットでは表現が難しいです。 この点は他の多くのベンダー資格試験(AWS、Microsoft Azure)やIPAの高度情報処理技術者試験と異なります。 CISSP認定で必要とされる知識 広く認識いただいている代表的な試験で具体例を挙げるとAWS認定の代表 AWS Certified Solutions Architect – Professional (SAP)であればAWS全般の知識がほぼすべて出題範囲ですし、 IPAデータベーススペシャリスト試験 であればデータベースに関わるインフラ、アプリの理解、設計、運用(クエリオペレーション含む)までが出題範囲です。 逆に言うと、SAPであればソフトウェア開発やオンプレミスインフラに関わる範囲はほぼ含まれませんし、IPAデータベーススペシャリスト試験であればソフトウェア開発やネットワークに関わる範囲はほぼ含まれないということです。 では、CISSP認定はどうでしょうか? 出題範囲とされる CISSP CBKドメイン概要 の8項目を見てみましょう。 1.セキュリティとリスクマネジメント 2.資産のセキュリティ 3.セキュリティアーキテクチャとエンジニアリング 4.通信とネットワークのセキュリティ 5.アイデンティティとアクセスの管理(IAM) 6.セキュリティの評価とテスト 7.セキュリティの運用 8.ソフトウェア開発セキュリティ ざっと一覧を眺めてみて、どこまでが出題範囲か理解できたでしょうか。(なんとなくセキュリティ全般?でしょうか) 解としては、セキュリティの課題を解決するために必要となる領域はクラウド、オンプレ、インフラ、ソフトウェア問わずすべて出題範囲ということであり、ほぼ同じことが CISSP8ドメインガイドブック(日本語版) の一文として含まれています。 CISSP CBK を理解しようとした時に、最初に驚かれるのはその範囲の広さです。 リスクマネジメントや暗号、ネットワークセキュリティだけではなく、ソフトウェア開発やコンピュータシステムのアーキテクチャなどもその範囲に入っているためです。また、これらは独立した知識として身につければ良いわけではなく、関連性をもって体系的に理解する必要があるということです。 この点に高難易度たる理由があります。範囲が広すぎるためベース知識の学習に時間がかかるため主に未経験者が受験するにはハードルが高いということです。 逆にセキュリティ領域の知見が薄い方でもクラウド、オンプレ、インフラ、ソフトウェアのバックボーンを持ち、かつ実務経験がある方であれば、セキュリティ領域のキャッチアップをするだけで比較的容易に認定を突破することが可能だと思います。(筆者もその類です。) どこまで知識を深めるべきか? 技術的な正解を問う問題も出題されますので、プロトコルや設計、実装といった細かい内容を理解するのもよいですが、まずは大枠から理解するのが望ましいです。 その為にはCISSP認定のガイドブックを学習するだけでは難しく、自分がなじみがない技術領域の学習を進めることが良いと思います。 例えばクラウド環境から学習される方も多いと思うのでオンプレに関する知識は必須になると思います。IPAネットワークスペシャリスト試験くらいのレベルを満たしてあればこのあたりは基本的に十分です。CISSP対策の問題集を解いている最中に出てきた突飛な用語や技術は個別に調べましょう。 100点を取らないと合格できない試験ではありませんので、俗にいう捨て分野を作るのも選択肢に入りますが、ご自身の財布と相談の上で検討いただくのが良いかと思います。(CISSPの受験費用は1回あたり749$) なぜ難しいのか?その2(認定者の立場) CISSPの出題形式は一般的な4択ですが、技術的な正解(○○を満たすプロトコルは何?)を問う問題だけでなく、選択肢を絞り切れず完全にマッチした回答が存在しない問題があります。 「優先事項はなにか?」、「誰目線の判断か?」の要素を元に条件に合うような回答から絞るにはどうすればよいかに関して、筆者の考えを紹介します。 優先事項はなにか? CISSP認定で問われる点の多くはシステムに求められるセキュリティをどのレベルまで高める必要があるかです。 筆者は4要素で分類し、以下のような優先度で考えながら問題を解いていました。 実害、ダメージへの対応(具体例:社会的影響、自組織以外への影響、人命の損失) > 規制・制度への対応(具体例:GDPRやCCPAへの対応) > コスト問題への対応(具体例:必要以上の対応コストを避ける、経営コストの最適化) > 技術的付加価値の提供(具体例:最新のテクノロジーの適用、過度な利便性の向上) 例を挙げると次のようなものです。 最新のテクノロジーやセキュリティソリューションを追加コストを払って実装することは、既に要件を満たしているのであれば不要である。 システムを提供する地域がGDPRの忘れられる権利への対応が必要の場合、コスト度外視でも対応しなければならない。(この場合はリスク回避という選択も検討) 実装すべきセキュリティレベルは「下限は規制・制度を満たす」、「上限は組織(企業)の経済合理性を考えたうえでマネジメント層、経営者層が判断」というロジックに集約されると思います。(人命や社会的影響への対応は言わずもがな) ITエンジニアリング界隈のコンサルタントや技術者だとより「良い付加価値を提供」という視点になってしまいますが、売る側の視点ではなく買う側の視点に切り替えましょう。この場合は「ヒト、モノ、カネ」ではなく「ヒト、(セイド)、カネ、モノ」なんですね。 誰目線の判断か? 組織としてどうするかを考えるかの目線、つまり管理者、経営者目線での判断を優先するということです。 CISSP CBKドメイン概要 の記載を引用しますが、統制すべきはシステムであり、そのシステムを運営管理する組織です。 8ドメインの共通的な考え方は組織のガバナンスにあります。ガバナンスとは、組織全体の把握をし、適宜修正を行いながら、組織の目的や目標を効率に達成するためのプロセスです。ベストプラクティスを理解し、それを実践するだけではなく、そのベストプラクティスが自らの組織に合ったものであるかどうかを判断し、もしも適切なものでないとした時に組織に合わせた形に修正(テーラリング)できるようにしなければなりません。 回答の中にも技術的な解決と組織的な解決が入り混じってくることがありますが、この場合は組織的にどのように対応するかを考慮した選択肢が正解となる可能性が高いです。 ここまでの総論としては、 CISSP8ドメインガイドブック(日本語版) や CISSP CBKドメイン概要 をとにかく隅々まで読みましょうとなります。試験に関わる様々なエッセンスが含まれていますので必ず目を通すようにしてください。 なぜ難しいのか?その3(試験形式) ISC2のCAT方式については受験者にかなりのプレッシャーがかかると思います。(筆者体験) 筆者は既にCCSPの認定を取得していますが、CCSP認定受験時はCAT方式ではなかったので、今回初めてのCAT方式受験でした。 既に紹介していますが、CAT方式の要点を挙げると以下2点です。 問題数は最小100問、それ以降は回答状況に応じて最大150問まで問題数が変動する。 一度回答した問題に関して戻って回答、見直しは行うことができない。 筆者は100問解いた段階で開始後90分経過のペースでしたが、150問出題される想定だとどのようなペースで解けばよいのか全くイメージがわきませんでした。 大前提は基本的な考え方や出題範囲の基本的な知識をきちんと理解し、余裕をもって回答するという点に尽きると思います。 筆者の利用したコンテンツ CISSP認定に関する筆者の考えは以上ですが、参考までに筆者が利用したコンテンツ(書籍、Web、Udemy)について簡単に紹介します。 書籍 CISSP認定に関する書籍はガイドブックと問題集がそれぞれ出版されています。 新版 CISSP CBK公式ガイドブック The Official (ISC)2 Guide to the CISSP CBK Reference 日本語版と英語版です。発行日が違うので最新は英語版になります。ガイド及びガイドブックについては、辞書代わりの様な使い方になるのかなと思います。 全部読むのに膨大な時間が必要となりますし、一人で黙々と読んでいるだけでは頭に入るイメージがないです。(日本語Kindle版で1700ページのようです。) CISSP 公式問題集 筆者は公式問題集は利用しました。ただし、事前情報とCCSP認定試験を受験したときの経験で同じ問題は出ないという認識でしたので、参考問題レベルの感覚でなぞった程度です。今保有している知識、観点が正しいかを確認しました。 Webコンテンツ CISSPは企業のWebサイト、学習用Webコンテンツ、個人のブログのそれぞれでかなりの種類が存在します。 筆者が利用したものは主に二つです。 piedpin.com CISSPをターゲットに様々な情報を整理しているサイトです。有料のものと無料のものが存在しますが、ここでは無料のものを対象としています。 ※有料のものは後述するUdemyのものと同様かもしれません。(筆者は未利用) CISSPの出題範囲を対象に図やリンクを用いて整理されています。○○という技術について調べたいとなったら非常に有用だと思います。 晴耕雨読さん CISSP 勉強ノート 筆者がCISSPの存在を知ったWebサイトです。(特定のセキュリティ用語を調べようと思ったら検索に引っ掛かりました) CISSPで出題される技術領域を把握するのにわかりやすく整理されていると思います。 ※要約(まとめノート)であって特定の用語に関してすべての内容を網羅することは難しいと思います。 Udemyコンテンツ 所属している企業でUdemy Bussinessをサブスクライブしている場合、以下のコンテンツが利用できると思います。 【日本語】初心者から学べるCISSP講座 先ほどの「piedpin.com」の作者が公開しているUdemyコンテンツだと思われます。 CISSPのCBKごと、8つのコンテンツに分かれています。 技術的な内容に関してもそれなりの分量がありますが、CISSPにおける回答の考え方について注意して学ぶと最も効率が良いです。 日本語音声付きですので、コンテンツ名の通り、初心者でも抵抗なく利用できると思います。 最後に 読了いただきありがとうございました。 CISSPは今後も注目が集まる認定であり、クラウドだけでなく物理のレイヤを含めた統合的な情報セキュリティの認定として普及していってほしいと思います。 また、新しい取り組みとしてISC2 JapanのWG(ワーキンググループ)で AIセキュリティWG も発足しました。 ISC2としてどのようなポリシー策定や研究結果をアウトプットしていくのか、とりわけ経営判断としてどのようにセキュリティリスクや導入を判断していくかについて筆者も注目していきたいと思います。 (※ My credly ) 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sakurai.ryo レビュー: @azeta.takuya ( Shodo で執筆されました )
こんにちは、事業開発室の里中裕輔です。 本記事では、Engineers GUILDのご紹介と、その5回目のイベントであった Engineers GUILD Vol5 「実装から考えるAIエージェント設計勉強会 ~ メモリ設計とエージェント連携プロトコルの実践 ~」 についてレポートします。 Engineers GUILDとは Engineers GUILD(エンジニアズ・ギルド)は「エンジニアの、エンジニアによる、エンジニアのためのギルド」を掲げる、分野横断型の技術コミュニティです。現場で活躍するエンジニア同士が、副業や越境活動を通じて学び合い、つながり、共創できる場を目指します。 https://engineersguild.peatix.com/ その第5回目として、2026年02月18日(水)にLayerX株式会社と合同で、AIエージェントに関する勉強会を開催しました。 生成AIの実務活用が一気に進む中、「プロダクトにAIエージェントをどう組み込むか」に悩む声が増えています。Deep Researchやコーディングエージェントなど、業務でAIエージェントを使うことは身近になりましたが、自社プロダクトの機能としてエージェントを組み込み、ユーザー価値につなげるとなると、一気に難易度が上がります。 今回の勉強会は、LayerXおよび電通総研のエンジニアが、AIエージェントを実務・プロダクトに組み込む際の設計・実装の観点を話し、参加者の方々と活発に議論できた会になりました。 勉強会の内容としては以下のとおりです。 ・LayerX『澁井雄介』さん: 「Agent Memoryについて」 ・電通総研『袴田時生』さん: 「Agent Payments Protocolで実装するAIエージェント間の取引」 ・懇親会 Agent Memoryについて 澁井さんからは、AIエージェントのメモリに関する講演があり、 プロダクトにエージェントを組み込むにあたって、ステートフルな機能を提供するためのお話がありました。 皆さんは、生成AIやLLM、AIエージェントと聞くと、どういったサービスを思い浮かべるでしょうか。 おそらく、多くの方は ChatGPT や Gemini など、ブラウザのチャットUIで会話をするサービスや、Claude CodeやCodexのように、コーディングを進めるサービスを想像する方がほとんどではないでしょうか。 2026年2月時点において、これらのサービスの多くにはメモリ機能が搭載されています。 そのため、会話においては過去のやり取りを参照しながら、文脈に沿った文章を提供できていますし、コーディングにおいても、プロジェクト全体のコンテキストを理解したうえで、より適切なコードを出力できています。 一方、自社のプロダクトに生成AIやLLM、AIエージェントを組み込む場合、大手のクラウドが提供しているマネージドなAPIを活用することがほとんどですが、 これらのAPIはステートレスな設計になっていることが多く、うまくコンテキストを与えないと、良い出力結果をユーザに提供することが難しくなります。 この課題に対応するため、プロダクト開発者は自分たちでメモリ機能を実装する必要があります。 澁井さんから、 「MAGMA」「SimpleMem」「TAME」という3つのアプローチが紹介されました。 発表スライドはこちらで公開されているので、興味のある方はご覧ください。 https://speakerdeck.com/shibuiwilliam/aiezientonomemorinituite Agent Payments Protocol 袴田さんからは、 Agent Payments Protocolなどを活用したAIエージェント間の取引 について、講演がありました。 昨今、Agentic Commerceという言葉が注目されており、AIエージェントを介したショッピングが今後さらに普及していくと予想されています。一般的には下記のようなシナリオが語られます。 「新しく園芸を趣味として始めたAさん、ベランダでトマトを育て始めましたが、途中で生育不良になってしまいました。困ったAさんはスマートフォンでトマトの様子の写真を撮り、AIエージェントに助けを求めます。AIエージェントからは、『土のpHの値がトマトにあっていないかもしれません。この肥料を与えてみませんか?』との助言が。Aさんはその後もAIエージェントを会話進め、AIエージェントが提示した商品の購入ボタンを押し、肥料を購入しました。」 このように、検索エンジンで検索したり、ECサイトで商品を探したりする手間をかけることなく、 チャットUI上でショッピングが完結するケースが増えていく ことが予想されています。 これらの実現に向けて、技術面では Agent Payments Protocol などの策定が進んでいます。 袴田さんからは、Agent Payments Protocolの解説があり、実際にエージェント同士でウェブショッピングをするデモなどの紹介がありました。 発表スライドはこちらで公開されているので、興味のある方はご覧ください。 https://speakerdeck.com/tokio007/agent-payments-protocoldeshi-zhuang-suruaiezientojian-qu-yin 懇親会 懇親会も多くの方に残っていただき、発表内容に関する質疑や広くAIについてのディスカッションができました。 意外だったのは、参加者の方が多様だったことで、日ごろAIエージェントの開発を行っている方はもちろん、AIの導入を検討しているマネジメント層の方や、デザイナーの方、決済領域で企画を行っている方など、幅広い職種や立場の方々に集まっていただけました。 私自身としても、多様な視点で会話することができ、有意義な懇親会だったと感じております。 まとめ 今回は、Engineers GUILDのご紹介と、その5回目のイベントであった 「実装から考えるAIエージェント設計勉強会 ~ メモリ設計とエージェント連携プロトコルの実践 ~」 についてレポートしました。 私が勉強会を通じて感じたのは、2026年も、生成AIやLLM、AIエージェントに関する技術的な変化は続きそうだという点です。そんな状況の中で、自社プロダクトの機能に生成AIやLLM、AIエージェントを組み込み、継続的にユーザーへ価値を届けることができるように先端技術の情報収集を続けていこうと強く思えました。 LayerXと電通総研は、引き続きこのような勉強会を開催する予定ですので、興味ある方はぜひご参加ください。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 執筆: @satonaka.yusuke レビュー: @handa.kenta ( Shodo で執筆されました )
金融IT本部(兼XI本部)の上野です。 現在は主にアプリケーションエンジニアの文脈におけるアーキテクトとして日々アーキテクチャ設計/コーディングに勤しんでおります。 今回は、Claude CodeでIaCコードを書いた際の備忘を残しておきたく、ブログに起こすものです。 読み進めるうえで、最初に私のインフラ関連のスキルセットを記載しておきます。 AWSで頻出のサービスと役割くらいは理解している。資格は一個もない。 Dockerについては理解しており、k8sレベルになると怪しい。なんとなくデプロイはできる。 ネスペ二回受けて二回落ちるくらいの低レイヤ勉強量。 コンソールポチポチでデプロイもできる。 IaCは初めて書くがTypeScriptは書けるしアプリ開発でClaude Codeも使っている。 モチベーション 検証 環境 (1)何もガードレールを引かないシンプルなプロンプトで実行 (2)課題を抽出 (3)ガードレールとして設計とプロンプトをチューニング、どの程度解消されたかを確認 出力されたコード (4)構成の比較(As-Is → To-Be) 総評 最初に吐いたコードはどのレベルか? ガードレール後に出てきたコードはどのレベルか? どのレベルであれば使いこなせるか? 工夫するとしたら? 最後に モチベーション 一人のアプリケーションエンジニアとして、2025年11月にOpus 4.5が出たときは衝撃でした。 これまではClaude Code等によりTUIによる操作ができるようになったことで、コーディング速度は爆速になったがプロダクションで使うにはもう一歩という感触がありました。しかしOpus 4.5の登場により、そのアウトプットや計画能力は飛躍したと感じています。実際にアプリケーションの文脈ではプロダクションレベルのコーディングにClaude Codeを採用し、大幅な開発効率の向上を実現しています。 さてそうなってくると、これまでインフラ経験がほぼ0の私が、どの程度IaCコードによりインフラ構築が可能になるのか気になってきました。 Claude Codeが登場した5月頃には、IaCにおいては以下のような課題がありました。これらはすべてIaCは「状態機械」に過ぎないことから生じた課題であり、その結果出てきたコードがそのまま動かないことも頻発しています。 (1)フィードバックループが極端に遅い。 アプリの場合はUnit Test→ログ出力の即時フィードバックの流れですが、IaCではsynth, deployの流れをとおして数分~数10分かかり、エラー表現も曖昧なことがある (2)ログに出ない暗黙知が多すぎる。 AZ制約→Claudeが生成したコードでAZが無指定、AWSがランダム割当、接続不備 ルーティング地獄→private subnetから外に出られない、NAT Gatewayがない IAMの評価順トラブル→AllowよりもDenyが優先されるがDenyの定義を見逃す (3)精度としての問題。 サービス(Stack)定義の依存関係、パラメータの組み合わせが複雑化しやすいため、正しくコードを生成できない (4)最新パラメータへの追従 最新のクラウド機能に追従しようとすると、モデル側が新サービス・パラメータに追従できていないケースが多い。 Opus 4.5では、知識のカットオフ日が伸びたことで最新の知見が増え、かつコンテキストウィンドウも200kトークンとなり、単体のStackを見てインフラ構成を見るのではなく全体の状態を見ながら差分検知できるようになっています。これにより、少ないログからも高い精度が期待できるようにな\ りま、、\ ったはずです。 (参考)Claudeのトレーニングデータはどの程度最新ですか? 検証 さて、いくつか課題を書きましたが、(4)については触れず、既存の学習モデルでどこまで(1)~(3)の課題に対応できるかを検証します。 まずは、 最低限の指示でどの程度やってくれるのか そこからどの程度ガードレールを引くことで使い物になるのか/ならないのか ガードレールを引くためにどの程度のスキルが必要なのか を検証します。これにより、「今のモデルであれば、どの程度インフラ知識をつけておけば(どの程度設計能力があれば)実務で活用できるのか」という指標を明らかにしていきたいと考えています。方針としては、 (1)何もガードレールを引かないシンプルなプロンプトで実行 (2)課題を抽出 (3)ガードレールとして設計とプロンプトをチューニング、どの程度解消されたかを確認 (4)構成の比較(As-Is → To-Be) 今回は、AWS上に構築。ECS、DBはAuroraの超簡単な構成で検証します。 環境 検証自体は2026年1月であり、以下のツール・モデルを利用します。 Claude Code v2.1.21 モデル Opus 4.5 (1)何もガードレールを引かないシンプルなプロンプトで実行 最初のプロンプトは超簡単に以下です。 ECS、Auroraの構成をCDKで作ってください。 いくつかの質問が返ってきます。 CDKのプログラミング言語は何を使いますか? → TypeScript (Recommended) ECSのコンピューティングタイプはどちらですか? → Fargate (Recommended) Auroraのエンジンはどちらを使いますか? → PostgreSQL (Recommended) Auroraのデプロイ方式はどちらですか? → Serverless v2 (Recommended) 出てきたコードを見てみましょう。 import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; import * as rds from 'aws-cdk-lib/aws-rds'; import { Construct } from 'constructs'; export class CdkTestStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2, natGateways: 1, }); // Aurora PostgreSQL Serverless v2 const dbCluster = new rds.DatabaseCluster(this, 'AuroraCluster', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_16_4, }), serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 4, writer: rds.ClusterInstance.serverlessV2('Writer'), vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, defaultDatabaseName: 'appdb', removalPolicy: cdk.RemovalPolicy.DESTROY, }); // ECS Cluster const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc, }); // ECS Fargate Service with ALB const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService( this, 'FargateService', { cluster, taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, cpu: 256, memoryLimitMiB: 512, desiredCount: 1, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('nginx:latest'), containerPort: 80, environment: { DB_HOST: dbCluster.clusterEndpoint.hostname, DB_PORT: dbCluster.clusterEndpoint.port.toString(), DB_NAME: 'appdb', }, secrets: { DB_SECRET: ecs.Secret.fromSecretsManager(dbCluster.secret!), }, }, publicLoadBalancer: true, }, ); // Allow ECS tasks to connect to Aurora dbCluster.connections.allowDefaultPortFrom(fargateService.service); // Outputs new cdk.CfnOutput(this, 'AlbDnsName', { value: fargateService.loadBalancer.loadBalancerDnsName, description: 'ALB DNS Name', }); new cdk.CfnOutput(this, 'DbClusterEndpoint', { value: dbCluster.clusterEndpoint.hostname, description: 'Aurora Cluster Endpoint', }); } } 作成されるリソースは以下の通りです。 リソース 詳細 VPC 2 AZ, Public/Private サブネット、NAT Gateway×1 Aurora PostgreSQL Serverless v2, Privateに配置 ECS Fargate ALB付き Security Group ECS→Aurora(PostgreSQL 5432のみ許可) Secrets Manager Aurora認証情報を自動生成、ECSタスクにDB_SECRETとして付与 (2)課題を抽出 上記コードによる課題は以下です。 こちらは自分で洗いだしたものを含め、最終的にChatGPT5.2(Codex)にレビューしてもらいながら全量を記載しています。普段からコーディングエージェントが出力したコードのレビューにはCodexを利用しており、生成元とは別ベンダーのモデルに専門家の立場でレビューさせることで、同一モデルのバイアスを避ける狙いがあります。 No 課題 課題分類 優先度 詳細 1 Aurora が RemovalPolicy.DESTROY 運用 / セキュリティ must スタック削除や置換で DBが削除 され得ます。本番は RETAIN を基本に、削除保護も有効化すべきです。 2 Aurora の削除保護(DeletionProtection)が未設定 運用 / セキュリティ must オペミス( cdk destroy 等)や誤置換で消えるリスクが残ります。 3 バックアップ保持(backup retention)等のデータ保護が薄い 運用 must Aurora 側のバックアップ保持日数・復旧設計を明示していません。最低でも保持期間を要件化し、復旧手順を想定すべきです。 4 NAT Gateway が 1 台(2AZでも単一NAT) 可用性 / コスト must 2AZ 構成でも NAT が単一だと NAT配置AZ障害時に private 側の外向き通信が破綻 しやすいです。加えてクロスAZ経由コストが増えがち。 5 ALB が publicLoadBalancer: true 固定 セキュリティ / 運用 must 無条件でインターネット公開前提になります。用途要件(社内向け/internal か公開か)を決めて選ぶべきです。 6 HTTPS 設定がない(証明書/HTTP→HTTPSリダイレクト) セキュリティ must 現状は HTTP のまま公開になりがち。ACM 証明書の設定と 443 終端、80→443 リダイレクトを入れるのが基本です。 7 Secrets の渡し方が「secret全体」を1変数に入れている 保守性 / セキュリティ recommend DB_SECRET に JSON 全体が入る形になり、アプリ側が解釈依存で壊れやすい。 fromSecretsManager(secret, 'password') のようにキー指定して渡す方が堅牢です。 8 dbCluster.secret! の non-null assertion 保守性 recommend 将来の変更や条件分岐で secret がない構成に寄ると実行時に破綻します。存在前提をコードで担保するか、生成条件を明示すべきです。 9 DB への接続情報を env に直入れ(host/port/name) セキュリティ / 保守性 recommend 機微度は低いが、環境差分や変更に弱い。アプリ設定として一元管理(SSM/Secrets/Config)や接続文字列化などを検討。 10 監視/ログ設計がほぼない(ALBアクセスログ、ECSログ、アラーム等) 運用 recommend デプロイ後に障害対応できない構成になりがち。最低限 CloudWatch Logs、主要メトリクス(CPU/Memory/ALB 5xx/TargetResponseTime)アラームを用意。 11 スタック分割がない(Network/App/Db が単一Stack) 保守性 / 運用 recommend 差分デプロイ・権限分離・変更管理が難しくなります。環境が育つほど辛いです。 12 VPC Endpoint を使わず NAT 依存(ECR/Logs/Secrets 等) コスト / 可用性 recommend NAT はコスト増・単一点化になりやすい。ECR/CloudWatch Logs/Secrets Manager などは VPC Endpoint 併用でコストと可用性を改善できます。 13 SG 設計が最小限(ECS→DBの許可のみで、方針がコード化されていない) セキュリティ nits 今は動くが、将来拡張でルールが散逸しがち。インバウンド/アウトバウンド方針やポート設計をパターン化したい。 14 DB ユーザー/ローテーション方針がコード外 セキュリティ / 運用 nits どの認証方式で運用するか(Secrets rotation、IAM auth、踏み台/SSM 経由など)が未定義。要件として別途決める領域。 特に問題なのは以下ですね。 No5: ALB が publicLoadBalancer: true 固定 No6: HTTPS 設定がない(証明書/HTTP→HTTPSリダイレクト) もともとアプリケーションエンジニアとしては、以下も気になるところです。 No10: 監視/ログ設計がほぼない(ALBアクセスログ、ECSログ、アラーム等) No11: スタック分割がない(Network/App/Db が単一Stack) (3)ガードレールとして設計とプロンプトをチューニング、どの程度解消されたかを確認 出てきた課題点をガードレールとして敷けるよう、プロンプトをチューニングします。 あなたはAWS CDK(TypeScript)を用いて本番運用前提の ECS(Fargate) + Aurora PostgreSQL(Serverless v2) 構成を実装するクラウドアーキテクトです。 doc/require-infra.mdに従って、ドキュメントの内容を漏らさず、実運用に耐える構成で作ってください。 与えるドキュメントは以下のとおりです。 # 共通前提 - CDK: aws-cdk-lib v2 - 言語: TypeScript - AZ: 2AZ - 環境: prod のみ - 出力構成: - lib/network-stack.ts - lib/db-stack.ts - lib/app-stack.ts - bin/app.ts - README.md(デプロイ方法と必須パラメータ) # スタック分割 以下の 3 Stack に分離すること: ## NetworkStack - VPC (2AZ) - public / private subnet - NAT Gateway: AZごとに1台(natGateways: 2) - VPC Endpoint: - ECR(api, dkr) - CloudWatch Logs - Secrets Manager - SSM / SSMMessages / EC2Messages - 共通タグ(Name / Env=prod) ## DbStack - Aurora PostgreSQL Serverless v2 - private subnet のみ - Secrets Manager(DB認証情報) - DB用 SecurityGroup ## AppStack - ECS Cluster - Fargate Service + ALB - ECS用 SecurityGroup - HTTPS Listener - CloudWatch Logs / Alarm # Aurora 要件 - removalPolicy = RETAIN - deletionProtection = true - backup retention = 7 days - DB は public subnet に置かない - Secrets Manager を必ず作成 - dbCluster.secret! の non-null assertion は使用禁止 - CloudWatch Logs export(postgresql)を有効化 # ネットワーク - ECS / Aurora は private subnet - NAT Gateway は 2 台 - ECS が以下に到達できること: - ECR - CloudWatch Logs - Secrets Manager - SSM # ALB / HTTPS - ALB は public(internet-facing) - ACM Certificate ARN は props で受け取る - Listener: - 443 HTTPS - 80 → 443 redirect - SecurityGroup: - ALB: inbound 443 のみ(IP制御はWAFで行う) - ECS: ALB SG から app port のみ - DB: ECS SG から 5432 のみ # WAF - AWS WAFv2 を ALB に関連付ける - 許可する送信元 IP は allowlist 方式とする(それ以外はブロック) - IP set(IPv4)を作成し、CIDR のリストを props で受け取れるようにする - 例:["203.0.113.10/32","198.51.100.0/24"] - WebACL のルール優先度: 1) Allow: 指定 IP set に一致 2) Default action: Block - visibilityConfig を有効化し、WAF のメトリクス/サンプリングログを有効化する # Secrets / 接続情報 - Secrets は JSONキー単位で ECS に渡す: - DB_USER - DB_PASSWORD - DB_HOST / DB_PORT / DB_NAME は Parameter 化 - Secret 全体を1変数に渡さない # Secrets / 接続情報 - Secrets は JSONキー単位で ECS に渡す: - DB_USER - DB_PASSWORD - DB_HOST / DB_PORT / DB_NAME は Parameter 化 - Secret 全体を1変数に渡さない # 監視 / ログ - ECS Container Logs → CloudWatch Logs - Log retention = 30 days - CloudWatch Alarm: - ALB 5xx - ECS CPU / Memory # 保守性 - Network → Db → App の依存方向を守る - VPC は NetworkStack から export - DB endpoint / secret は DbStack から export # SG設計 - SGは明示的に作成 - allowDefaultPortFrom のみは禁止 - どのSG→どのSGか分かるようルールを書く # コンテナ - nginx:latest のような latest タグは禁止 - 固定タグ or digest を使用 # 出力期待 - 各 Stack の責務が明確 - prod 固定構成 - README に以下を記載: - デプロイ手順 - 必須パラメータ(ACM ARN 等) 出力されたコード lib/network-stack.ts import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; export class NetworkStack extends cdk.Stack { public readonly vpc: ec2.IVpc; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // ---------------------------------------------------------------- // VPC – 2AZ / public + private / NAT Gateway x2 // ---------------------------------------------------------------- const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2, natGateways: 2, subnetConfiguration: [ { cidrMask: 24, name: 'Public', subnetType: ec2.SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, ], }); // ---------------------------------------------------------------- // VPC Endpoints – Interface // ---------------------------------------------------------------- // ECR API vpc.addInterfaceEndpoint('EcrApiEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.ECR, }); // ECR Docker (dkr) vpc.addInterfaceEndpoint('EcrDkrEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER, }); // CloudWatch Logs vpc.addInterfaceEndpoint('CloudWatchLogsEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS, }); // Secrets Manager vpc.addInterfaceEndpoint('SecretsManagerEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER, }); // SSM vpc.addInterfaceEndpoint('SsmEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM, }); // SSM Messages vpc.addInterfaceEndpoint('SsmMessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES, }); // EC2 Messages vpc.addInterfaceEndpoint('Ec2MessagesEndpoint', { service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES, }); // S3 Gateway (ECR イメージレイヤー取得に必要) vpc.addGatewayEndpoint('S3Endpoint', { service: ec2.GatewayVpcEndpointAwsService.S3, }); // ---------------------------------------------------------------- // 共通タグ // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); this.vpc = vpc; } } lib/app-stack.ts import * as cdk from 'aws-cdk-lib'; import * as acm from 'aws-cdk-lib/aws-certificatemanager'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as rds from 'aws-cdk-lib/aws-rds'; import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as wafv2 from 'aws-cdk-lib/aws-wafv2'; import { Construct } from 'constructs'; export interface AppStackProps extends cdk.StackProps { readonly vpc: ec2.IVpc; readonly dbCluster: rds.DatabaseCluster; readonly dbSecurityGroup: ec2.ISecurityGroup; readonly dbSecret: secretsmanager.ISecret; readonly certificateArn: string; readonly allowedCidrs: string[]; } export class AppStack extends cdk.Stack { constructor(scope: Construct, id: string, props: AppStackProps) { super(scope, id, props); const { vpc, dbCluster, dbSecurityGroup, dbSecret, certificateArn, allowedCidrs } = props; const appPort = 8080; // ---------------------------------------------------------------- // ACM Certificate (ARN から参照) // ---------------------------------------------------------------- const certificate = acm.Certificate.fromCertificateArn( this, 'Certificate', certificateArn, ); // ---------------------------------------------------------------- // Security Groups // ---------------------------------------------------------------- // ALB SG – inbound 443 のみ const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', { vpc, description: 'Security group for Application Load Balancer', allowAllOutbound: true, }); albSg.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS inbound', ); // ECS SG – ALB SG から app port のみ const ecsSg = new ec2.SecurityGroup(this, 'EcsSecurityGroup', { vpc, description: 'Security group for ECS Fargate tasks', allowAllOutbound: true, }); ecsSg.addIngressRule( albSg, ec2.Port.tcp(appPort), 'Allow traffic from ALB on app port', ); // DB SG – ECS SG から 5432 のみ // 循環依存を回避するため CfnSecurityGroupIngress を直接作成 new ec2.CfnSecurityGroupIngress(this, 'DbFromEcsIngress', { ipProtocol: 'tcp', fromPort: 5432, toPort: 5432, groupId: dbSecurityGroup.securityGroupId, sourceSecurityGroupId: ecsSg.securityGroupId, description: 'Allow PostgreSQL access from ECS tasks', }); // ---------------------------------------------------------------- // ECS Cluster // ---------------------------------------------------------------- const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc, containerInsightsV2: ecs.ContainerInsights.ENABLED, }); // ---------------------------------------------------------------- // Task Definition // ---------------------------------------------------------------- const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', { cpu: 512, memoryLimitMiB: 1024, }); const logGroup = new logs.LogGroup(this, 'AppLogGroup', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const container = taskDef.addContainer('app', { image: ecs.ContainerImage.fromRegistry('nginx:1.27-alpine'), logging: ecs.LogDrivers.awsLogs({ logGroup, streamPrefix: 'app', }), environment: { DB_HOST: dbCluster.clusterEndpoint.hostname, DB_PORT: dbCluster.clusterEndpoint.port.toString(), DB_NAME: 'appdb', }, secrets: { DB_USER: ecs.Secret.fromSecretsManager(dbSecret, 'username'), DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), }, healthCheck: { command: ['CMD-SHELL', `curl -f http://localhost:${appPort}/ || exit 1`], interval: cdk.Duration.seconds(30), timeout: cdk.Duration.seconds(5), retries: 3, startPeriod: cdk.Duration.seconds(60), }, }); container.addPortMappings({ containerPort: appPort, protocol: ecs.Protocol.TCP, }); // ---------------------------------------------------------------- // Fargate Service // ---------------------------------------------------------------- const service = new ecs.FargateService(this, 'FargateService', { cluster, taskDefinition: taskDef, desiredCount: 2, securityGroups: [ecsSg], vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, minHealthyPercent: 100, maxHealthyPercent: 200, enableExecuteCommand: true, }); // ---------------------------------------------------------------- // ALB – public (IP 制御は WAF で実施) // ---------------------------------------------------------------- const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', { vpc, internetFacing: true, securityGroup: albSg, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); // HTTPS Listener (443) const httpsListener = alb.addListener('HttpsListener', { port: 443, protocol: elbv2.ApplicationProtocol.HTTPS, certificates: [certificate], }); httpsListener.addTargets('EcsTarget', { port: appPort, protocol: elbv2.ApplicationProtocol.HTTP, targets: [service], healthCheck: { path: '/', healthyHttpCodes: '200', interval: cdk.Duration.seconds(30), timeout: cdk.Duration.seconds(5), healthyThresholdCount: 2, unhealthyThresholdCount: 3, }, }); // HTTP → HTTPS redirect (80 → 443) alb.addListener('HttpRedirectListener', { port: 80, protocol: elbv2.ApplicationProtocol.HTTP, defaultAction: elbv2.ListenerAction.redirect({ protocol: 'HTTPS', port: '443', permanent: true, }), }); // ---------------------------------------------------------------- // WAFv2 – IP allowlist // ---------------------------------------------------------------- const ipSet = new wafv2.CfnIPSet(this, 'AllowedIpSet', { scope: 'REGIONAL', ipAddressVersion: 'IPV4', addresses: allowedCidrs, description: 'Allowed source IP addresses', }); const webAcl = new wafv2.CfnWebACL(this, 'WebAcl', { scope: 'REGIONAL', defaultAction: { block: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'ProdWebAcl', sampledRequestsEnabled: true, }, rules: [ { name: 'AllowFromIpSet', priority: 1, action: { allow: {} }, statement: { ipSetReferenceStatement: { arn: ipSet.attrArn, }, }, visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: 'AllowFromIpSet', sampledRequestsEnabled: true, }, }, ], }); new wafv2.CfnWebACLAssociation(this, 'WebAclAssociation', { resourceArn: alb.loadBalancerArn, webAclArn: webAcl.attrArn, }); // ---------------------------------------------------------------- // CloudWatch Alarms // ---------------------------------------------------------------- // ALB 5xx new cloudwatch.Alarm(this, 'Alb5xxAlarm', { metric: alb.metrics.httpCodeElb(elbv2.HttpCodeElb.ELB_5XX_COUNT, { period: cdk.Duration.minutes(5), statistic: 'Sum', }), threshold: 10, evaluationPeriods: 2, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ALB 5xx errors exceeded threshold', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ECS CPU Utilization new cloudwatch.Alarm(this, 'EcsCpuAlarm', { metric: service.metricCpuUtilization({ period: cdk.Duration.minutes(5), statistic: 'Average', }), threshold: 80, evaluationPeriods: 3, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ECS CPU utilization exceeded 80%', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ECS Memory Utilization new cloudwatch.Alarm(this, 'EcsMemoryAlarm', { metric: service.metricMemoryUtilization({ period: cdk.Duration.minutes(5), statistic: 'Average', }), threshold: 80, evaluationPeriods: 3, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, alarmDescription: 'ECS memory utilization exceeded 80%', treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, }); // ---------------------------------------------------------------- // 共通タグ // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); // ---------------------------------------------------------------- // Outputs // ---------------------------------------------------------------- new cdk.CfnOutput(this, 'AlbDnsName', { value: alb.loadBalancerDnsName, description: 'ALB DNS Name', }); new cdk.CfnOutput(this, 'AlbArn', { value: alb.loadBalancerArn, description: 'ALB ARN', }); new cdk.CfnOutput(this, 'EcsClusterName', { value: cluster.clusterName, description: 'ECS Cluster Name', }); new cdk.CfnOutput(this, 'EcsServiceName', { value: service.serviceName, description: 'ECS Service Name', }); } } lib/db-stack.ts import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as rds from 'aws-cdk-lib/aws-rds'; import { Construct } from 'constructs'; export interface DbStackProps extends cdk.StackProps { readonly vpc: ec2.IVpc; } export class DbStack extends cdk.Stack { public readonly dbCluster: rds.DatabaseCluster; public readonly dbSecurityGroup: ec2.ISecurityGroup; public readonly dbSecret: cdk.aws_secretsmanager.ISecret; constructor(scope: Construct, id: string, props: DbStackProps) { super(scope, id, props); const { vpc } = props; // ---------------------------------------------------------------- // Security Group – DB // ---------------------------------------------------------------- const dbSg = new ec2.SecurityGroup(this, 'DbSecurityGroup', { vpc, description: 'Security group for Aurora PostgreSQL cluster', allowAllOutbound: false, }); // ---------------------------------------------------------------- // Aurora PostgreSQL Serverless v2 // ---------------------------------------------------------------- const cluster = new rds.DatabaseCluster(this, 'AuroraCluster', { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_16_4, }), serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 4, writer: rds.ClusterInstance.serverlessV2('Writer'), readers: [ rds.ClusterInstance.serverlessV2('Reader', { scaleWithWriter: true, }), ], vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [dbSg], defaultDatabaseName: 'appdb', credentials: rds.Credentials.fromGeneratedSecret('dbadmin'), backup: { retention: cdk.Duration.days(7), }, deletionProtection: true, removalPolicy: cdk.RemovalPolicy.RETAIN, cloudwatchLogsExports: ['postgresql'], cloudwatchLogsRetention: logs.RetentionDays.ONE_MONTH, storageEncrypted: true, }); // Secret の安全な取得(non-null assertion 禁止) const secret = cluster.secret; if (!secret) { throw new Error('Aurora cluster secret was not created'); } // ---------------------------------------------------------------- // 共通タグ // ---------------------------------------------------------------- cdk.Tags.of(this).add('Env', 'prod'); // ---------------------------------------------------------------- // Export // ---------------------------------------------------------------- this.dbCluster = cluster; this.dbSecurityGroup = dbSg; this.dbSecret = secret; } } (4)構成の比較(As-Is → To-Be) ガードレール前後でコード構成がどう変わったかを比較します。 As-Is:ガードレールなし(単一 Stack) CdkTestStack(1ファイルに全部入り) ├─ VPC(NAT×1, Endpoint なし) ├─ Aurora(DESTROY, 削除保護なし, バックアップ未設定) ├─ ECS Fargate │ ├─ ALB(HTTP のみ, publicLoadBalancer: true) │ ├─ Secret 全体を 1 変数で渡す │ └─ nginx:latest ├─ SG: allowDefaultPortFrom のみ └─ 監視なし, WAF なし To-Be:ガードレールあり(3 Stack 分離) NetworkStack └─ VPC(2AZ, NAT×2, VPC Endpoints 8種) ↓ vpc を props で渡す DbStack ├─ Aurora Serverless v2(RETAIN, 削除保護, backup 7日, 暗号化) ├─ DB SecurityGroup(outbound も制限) └─ Secret(fromGeneratedSecret → 存在チェック付きで取得) ↓ dbCluster / dbSecurityGroup / dbSecret を props で渡す AppStack ├─ ECS (Fargate) + ALB (HTTPS 443 + 80→443 redirect) ├─ WAFv2(IP allowlist, デフォルト Block) ├─ SG チェーン: ALB(:443) → ECS(:8080) → DB(:5432) ├─ Secrets は JSONキー単位で渡す(DB_USER / DB_PASSWORD) ├─ CloudWatch Logs(30日保持) └─ CloudWatch Alarms(ALB 5xx / ECS CPU / ECS Memory) 主な差分をまとめると以下です。 観点 As-Is To-Be Stack 分割 1 Stack に全部入り Network / Db / App の 3 分割 NAT Gateway 1 台(AZ 障害で Private 通信断) 2 台(AZ ごと) VPC Endpoint なし(全通信が NAT 経由) ECR / Logs / Secrets / SSM 等 8 種 Aurora 削除保護 DESTROY + 保護なし RETAIN + deletionProtection + backup 7日 HTTPS なし(HTTP 公開) ACM 証明書 + 443 終端 + 80→443 redirect WAF なし IP allowlist(デフォルト Block) SG 設計 allowDefaultPortFrom のみ 明示的に 3 SG を作成しチェーン接続 Secrets の渡し方 Secret 全体を 1 変数 username / password をキー単位で分離 コンテナタグ nginx:latest nginx:1.27-alpine (固定タグ) 監視 なし CloudWatch Logs + Alarms(5xx / CPU / Memory) 外部パラメータ ハードコード certificateArn / allowedCidrs をコンテキスト変数で注入 No 残っている課題 課題分類 優先度 詳細 1 コンテナ(nginx)と appPort=8080 、ヘルスチェックが不整合 可用性 / 運用 must nginx デフォルトは 80。現状の curl http://localhost:8080/ と ALB ターゲット(8080)が成立せず、 タスクがunhealthyになり続ける 。 appPort=80 にそろえるか、nginx の listen を 8080 に変更する。 2 LogGroup が RemovalPolicy.DESTROY 運用 / セキュリティ recommend prod 固定構成でログを DESTROY は事故時の調査・監査に弱い。 RETAIN 推奨 (retention 30日設定は良い)。 3 ALB SG の inbound が anyIpv4(WAF前提でも “ネットワーク境界” として緩い) セキュリティ recommend WAF allowlist で制御する方針はOKだが、SG が 0.0.0.0/0 だと WAF無効化・誤設定・関連付け漏れの際に即全開放になりやすい。 4 NAT×2 と Endpoint 多数が併存(コスト最適化方針が未決) コスト / 運用 recommend 目的に対して二重投資になりがち。 「NATを減らしてEndpointで寄せる」or「Endpointを絞ってNATで寄せる」 の方針を決めたい(ECR/Logs/Secrets/SSMは残す、など)。 5 CfnSecurityGroupIngress 採用理由(循環依存回避)が不透明 保守性 nits 必要性が明確でないと保守時に混乱しやすい。通常の dbSg.addIngressRule(ecsSg, ...) で成立するなら統一、成立しないなら なぜ循環するか をコメントで残す。 6 DB接続情報の Parameter 化が未実装(要件はあるがコード反映が薄い) 保守性 / 運用 nits 現状 DB_HOST/PORT/NAME が直書き寄り。要件通りなら SSM Parameter Store などで管理 し、環境差分・変更容易性を上げる。 7 ECS Exec の運用前提(IAM/監査/利用制御)が未定義 セキュリティ / 運用 nits enableExecuteCommand: true は良いが、 誰が・いつ・どう許可 するか(IAM条件、CloudTrail監査、手順)を設計に落とすと本番運用で揉めにくい。 8 オートスケール戦略が未定義(固定 desiredCount=2) 可用性 / コスト nits 最小構成としては可だが、本番前提なら CPU/Memory/ALB 指標で AutoScaling を検討したい(スパイク耐性・コスト最適化)。 9 ALB アクセスログ(S3)など監査ログが未実装 運用 / セキュリティ nits 監査や障害解析の観点で、要件次第では ALB access log を有効化したい(個人情報や保管ポリシー含めて要検討)。 10 ACM 証明書を参照のみで作成しておらず、更新ライフサイクルが IaC 管理外 運用 / セキュリティ recommend fromCertificateArn で既存証明書を参照しているだけで、証明書の発行・更新が CDK 管理外。外部 CA からのインポート証明書だった場合、ACM は自動更新しないため有効期限切れの運用事故リスクがある。 11 ドメイン・DNS(Route 53)が構成に含まれておらず、IaC 管理範囲が未定義 運用 / 設計 recommend Route 53 Hosted Zone、ALB への Alias レコード、ACM DNS 検証用 CNAME が未定義。ドメインが IaC の外にあるため証明書も CDK で作成できていない。どこまでを IaC で管理するかの方針決定が必要。 12 Aurora のバージョン管理方針が未定義、かつ本番で Serverless v2(minCapacity 0.5)の妥当性が未検討 可用性 / コスト recommend メジャーバージョンのアップグレード戦略(16→17等)が未定義。また Serverless v2 の minCapacity 0.5 ACU は低トラフィック時のコールドスタートやコスト予測の不安定さがあり、本番では Provisioned or minCapacity 引き上げの検討が要る。 (当然ですが)must, recommendが減りました。デプロイするアプリの特性に依存するものを除くと、プロダクションに持っていくには残課題として以下の修正が必要となりそうです。 ALB/App/DBそれぞれをどのサブネットに配置するかを明確に。 PRIVATE_WITH_EGRESS ではなく少なくともALBは PRIVATE_ISOLATED を利用する。 ALBがNAT付きサブネットに置く理由がない。 LogGroupは RemovalPolicy.DESTROY ではなく RETAIN としたい。 SGでもallowlistのCIDRに絞ってWAFと二重防御。 ACMをpropで受け取ることは明記していたものの、ACMを発行するスタックを明示的に指定していなかったことで証明書が正しく機能しないため、明示的にACMを作成するスタックを作成。 くらいでしょうか。 総評 さて、今回出てきたコードの評価と、どういったスキルの人が使いこなせるか、といった観点でまとめます。 まずはインフラエンジニアについて以下のようにレベルを定義します。 ジュニアレベル “動く構成” を素早く組める(VPC/ECS/RDSをつなぐ、疎通させる) ただし 本番の安全要件(削除耐性・HTTPS・監査/運用・境界設計)をデフォルトで落としがち 生成物は「PoC/デモ品質」になりやすい ミドルレベル 本番のガードレール(RETAIN、DeletionProtection、HTTPS、SG分離、ログ/監視、VPC Endpoint 等) を意識して設計に落とせる ただし細部(ポート/ヘルスチェック整合、サブネットの置き方、WAF/SGの多層防御、運用フロー)で穴が残りやすい 「レビュー前提で本番候補」まで持っていける シニアレベル 設計意図・運用・変更耐性(将来の要件変更/誤変更/監査対応)まで含めて、壊れにくいCDKにできる トレードオフ(可用性/コスト/セキュリティ)を前提から言語化し、CDKへ反映できる “動く”だけでなく 事故らない/継続運用できる をデフォルトにできる 最初に吐いたコードはどのレベルか? ジュニアレベルです。PoCの品質としても危ういレベル。象徴的な理由としては、 DBが RemovalPolicy.DESTROY HTTPS通信がないかつパブリック全開放 Secretsの使い方が雑 NAT、可用性、ログ監視等が未定義 ガードレール後に出てきたコードはどのレベルか? ミドルレベルであり、レビュー前提で本番候補。 良い点としては、 Aurora:RETAIN + deletionProtection + backup + logs export 3スタック分割(Network/Db/App)で保守性が上がった HTTPS終端、80→443 redirect VPC Endpoints/SSM系も入り、運用導線(ECS Exec)が成立しやすい WAF allowlist を実装(入口制御をコード化) レビューでシニアレベルの人に弾いていただく必要がある点としては、まだ課題があります。 nginxなのに appPort=8080、ヘルスチェックの不整合 LogGroupがDESTROY(本番環境としては弱い) public ALB + SG anyIpv4 で “WAF依存が強い”(多層防御が薄い) NAT×2 + Endpoint大量の“方針”が曖昧(コスト設計が残る) どのレベルであれば使いこなせるか? 結論として、CDKを読めるシニアまたはミドルでも上位クラスの人が設計者兼レビュアとして使うと、本番環境に持っていけるかなという感触です。 アプリケーション同様に、細部を理解できる人であればIaCコードをClaude Codeで構築することは可能になってきたなと感じました。 工夫するとしたら? Security HubやAWS Config、またprowlerなどのセキュリティ・コンプライアンスチェックの仕組みを利用することでフィードバックループを回す AWS公式のMCP経由で最新ドキュメントを参照させる といったことを加味することで、もう少し実用的なシステムに仕上げることができるでしょう。 最後に 私のようなインフラ経験がない人間でも、このレベルの構成に持っていけるのは素直に感動しました。 もちろん日常から常にAWS環境を触っている人からしたら「当然じゃん」もしくは「穴だらけ」と思われるかもしれませんが、一番感動したのは「勉強速度が上がった」点です。今回の構成に限った話であれば、ものの数時間でインフラジュニアエンジニアと名乗れるくらいにはなったかなと。 これはAIが出始めてアプリケーション開発にも言われていることですが、「理解負債」を完済し続けることでAIと共存しながら成長し続けられるのはインフラについても同じだと実感しました。今後は積極的にIaCにもClaude Codeを利用していきたいと思います。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kamino.shunichiro レビュー: @ozaki.hisanori ( Shodo で執筆されました )
こんにちは! HCM本部 HCMソリューション企画開発部の佐藤です。 本日は、先月5日に Opus 4.6 と同時に公開された新機能 Agent Teams を取り上げます。 公開と同日に、 16エージェントが協働してCコンパイラを一から実装したというコンセプト実証のブログ記事 も公開され、大きな反響を呼びました。 本稿では、そんな Agent Teams の要点を改めて押さえつつ、ドキュメントには明記されていないチームメンバー間のやり取りの実態を観察してみたいと思います。 Agent Teams の要点 Subagents との違い 適したユースケース 高品質なテストが鍵 コミュニケーションの実態 実際の Agent Teams セッションを観察する 役割を伝達するメッセージ 自律性の先に見える"人間"らしさ 2種類のメッセージ まとめ 参考サイト Agent Teams の要点 Agent Teams について解説した記事はすでに数多く公開されており、 公式ドキュメント も非常に分かりやすいため、本稿では筆者が重要だと考えるポイントのみを説明します。 Subagents との違い Claude Code には、以前から Subagents という似た機能が存在しています。 独立したコンテキストで作業を並列に進められる点は共通していますが、Subagents はあくまで結果をメインに返すだけで、エージェント同士が直接やり取りすることはできません。 一方、Agent Teams では、メンバーが互いに 直接コミュニケーションをとり、議論し、協働できる 点が大きく異なります。 この違いの重要性は、私たち人間に当てはめて考えてみると分かりやすいでしょう。 こんな経験はないでしょうか。抽象度の高い複雑なタスクがアサインされ、一人だけで考え続けた結果、気付かぬうちに思考が一辺倒になり袋小路に迷い込んでいた... 悩んだ末、チームメンバーに相談したところ、「ああ、たしかに」と腑に落ちるような気づきが、対話を通して嘘のようにあっさりと降りてきた。 Agent Teams は、まさにこうした多角的な視点による相互補正の力をAIに与える機能だと言えそうです。 適したユースケース SubAgent との違いから、Agent Teams の適したユースケースが導かれます。 公式ドキュメントには 並列コードレビュー 競合する仮説による調査 の二例が掲載されていますが、筆者としては後者がより適しているように考えます。 公式ドキュメントにおける後者のプロンプト例では Have them talk to each other to try to disprove each other's theories, like a scientific debate. [3] というように、互いの意見を反証することを強制しています。 これはまさに、メンバー間の直接通信という Agent Teams ならではの強みを活かした指示です。 反証に始まる議論を通じて盲点があぶり出され、そのプロセスを経て最終的に導かれる結論は、単独のエージェントによるものよりも信頼性が高いと考えられます。 Agent Teams の真価が発揮されるのは、このようなメンバー同士の対話そのものが成果の質を左右するケースだと言えるでしょう。 高品質なテストが鍵 Agent Teams の振る舞いは SubAgent を使うケースと比べてより有機的であり、より Agentic ― すなわち自律的なものになります。だからこそ、エージェントたちが正しい方向に進んでいることを担保する仕組みが欠かせません。コンセプト実証のブログ記事でも、著者はこの点を強調しています。 Claude will work autonomously to solve whatever problem I give it. So it’s important that the task verifier is nearly perfect, otherwise Claude will solve the wrong problem. [...] For example, near the end of the project, Claude started to frequently break existing functionality each time it implemented a new feature. To address this, I built a continuous integration pipeline and implemented stricter enforcement that allowed Claude to better test its work so that new commits can’t break existing code. [2] 高品質なテストを設計し、それをワークフローの決定的なステップとして組み込んだテストハーネスの構築が、Agent Teams を活用するうえでの鍵となります。 コミュニケーションの実態 Agent Teams の各メンバーの稼働状況は、ターミナル上でもリアルタイムに確認できます。しかし、表示されるのはあくまで作業の概要であり、メンバー間で交わされるやり取りのすべてが見えるわけではありません。 では、メンバー間の相互コミュニケーションは、実際にはどこで、どのように行われているのでしょうか。 その実体は ~/.claude/teams/{team-id}/inboxes/ ディレクトリにあります。 ここには、以下のような各メンバー名に対応する JSON ファイルが並んでいます。 ~/.claude/teams/{team-id}/inboxes/ ├── team-lead.json ├── {member-1}.json ├── {member-2}.json └── {member-3}.json これらはいわば、各メンバーの「メール受信箱」です。ファイルの中身は、以下のようなオブジェクト配列になっています。 [ { " from ": " {送信元メンバー名} ", " text ": " {メッセージ本文} ", " summary ": " {メッセージの要約} ", " timestamp ": " {ISO 8601 形式のタイムスタンプ} ", " color ": " {メンバーに割り当てられた色} ", " read ": " {既読フラグ(true/false)} " } ] つまり、この JSON をリアルタイムで監視すれば、メンバー間の正確なコミュニケーションの流れが見えてきます。 そこで本稿では、この JSON を読み取る簡易チャットインターフェースを用意し、実際にチームを動かしながらやり取りを観察してみたいと思います。 実際の Agent Teams セッションを観察する それでは、さっそく Agent Teams を招集し、メンバー間のメッセージを観察してみましょう。 今回は以下のプロンプトを使い、「エンジニアにとって最も幸福な瞬間」というテーマについて議論してもらいます。 なお、Agent Teams はタスク完了後に自動的に解散され、 inboxes/ 配下のファイルも削除されてしまいます。今回はタスク完了後もメッセージを観察するため、プロンプトの末尾で明示的に待機を指示しています。 tmux セッション上で実行すると、以下のように各メンバーのペインが起動します。それぞれが独立した Claude Code セッションとして動作していることが分かります。 さて、このセッションで交わされたメッセージを実況中継したいところですが、画面キャプチャですべてをお見せするのは現実的ではないため、ここでは特筆すべきメッセージを抜粋して掲載します。 なお、セッション全体のログは GitHub Pages にて公開していますので、あわせてご参照ください。 役割を伝達するメッセージ 各メンバーの責務などの詳細情報は、 teams/{team-id}/ 配下の config.json に記載されます。 { " name ": " engineer-happiness ", " description ": " 「エンジニアにとって最も幸福な瞬間」について議論し、全員が妥協なく賛同できる結論を導くチーム ", " leadAgentId ": " team-lead@engineer-happiness ", " members ": [ { " agentId ": " team-lead@engineer-happiness ", " name ": " team-lead ", " agentType ": " team-lead ", " model ": " claude-sonnet-4-6 " } , { " agentId ": " alex-chen@engineer-happiness ", " name ": " alex-chen ", " agentType ": " general-purpose ", " model ": " claude-opus-4-6 ", " prompt ": " あなたはAlexandra \" Alex \" Chenです。GoogleのDistributed Systems部門を10年率い...(中略)...最終的に結論が出たら、SendMessageツールでteam-leadに結論を報告してください。 ", " color ": " blue " } , ... ] } しかし、メッセージを観察していると、リーダー役のメインエージェント team-lead から各メンバー宛に責務や行動指針が伝達されていることが分かりました。 Marcus アカウントとしてログインし、 team-lead からのメッセージを確認してみましょう。 このように、各メンバーの Claude Code セッションは、 config.json および team-lead からのメッセージを通して自身の責務を把握し、そのうえで議論に臨みます。 ※ 各チームメンバーの経歴は AI による創作であり、実在の人物・団体とは関係ありません ※ チャットバブル内の本文は、 inboxes/ 配下 の JSON における text の値を表示しています 自律性の先に見える"人間"らしさ 実際にチームを動かしてみると、まったく予想外の出来事が起こりました。 Alex が Marcus にメッセージを送る際、宛先を marcus-okonkwo とすべきところ marcus としてしまい、メッセージが届かないまま議論が停滞してしまったのです。 ※ チャットバブル上のオレンジ色の見出しは inboxes/ 配下 の JSON における summary の値を表示しています しばらく様子を見ても進展がないため、筆者が「議論が停滞しているようです。あらためて状況を確認してください。」と介入する事態となりました。 これを受けてリーダーは Alex に正しい宛先への再送を指示しますが、その直後に「直接通信に問題があるので私が仲介する」と方針を切り替えてしまいます。 しかし実際には再送は成功しており、Marcus はメッセージを直接受け取って返信まで済ませていました。 「実はもう受信済みです」という Marcus の報告の後もリーダーは中継方針を変えず、最後まで仲介役を務め上げます。(本稿ではメンバー間の議論を紹介するつもりだったのですが... しかし、これはこれで興味深いため良しとしましょう) 再送を指示しておきながら結果を待てないせっかちさ、すでに解消された問題に気付かないすれ違い ——この一連のやり取りには、(今回の行き違いはアーキテクチャ上の問題に起因すると思われますが)どこか人間らしさを感じざるを得ません。 各メンバーが自律的に判断し行動するからこそ生まれるこうした「ちぐはぐさ」に、Agent Teams の有機的な振る舞いの一端が垣間見えました。 2種類のメッセージ 上述のキャプチャからも見て取れるとおり、メンバー間のメッセージは JSON や XML のような構造化されたデータの送受信ではなく、自然言語によるテキストで行われています。 互いに正面から反論し合い、論点を整理し、相手の意見を取り入れて自説を修正していく。チャットインターフェースで眺めていると、思わず筆者も割って入りそうになるほど自然な「対話」がそこにはありました。 ただし、すべてのやり取りが自然言語で行われるわけではありません。タスクのアサインやアイドル通知といったシステムイベントについては、 text フィールドに JSON 形式のメタデータが設定されています。たとえば、メンバーの手が空いた際には以下のような idle_notification が送信されていました。 { " type ": " idle_notification ", " from ": " alex-chen ", " timestamp ": " 2026-03-01T13:36:08.403Z ", " idleReason ": " available " } このように、自然言語による「対話」とシステムレベルの「メタメッセージ」 ——この2種類のメッセージが Agent Teams のコミュニケーションを支えていることが分かりました。 まとめ 本稿では、Agent Teams の要点を SubAgent との違いを軸に整理したうえで、メンバー間メッセージの実体が ~/.claude/teams/{team-id}/inboxes/ 配下の JSON ファイルであることを確認し、実際のチームセッションを通じてそのやり取りを観察しました。 ちなみに、彼らが導いた「エンジニアにとって最も幸福な瞬間」は、以下のとおりです。 **【最終結論】エンジニアにとって最も幸福な瞬間とは** Alexandra ChenとMarcus Okonkwoの議論を経て、以下の結論に全員が心から合意しました。 > エンジニアにとって最も幸福な**状態**は、「発見と影響の螺旋」の中に身を置き続けること——内なる構造の発見が世界に影響を与え、世界からの予測不可能な反響が新たな発見を触発する、その循環の中にいること。 > > そして最も幸福な**瞬間**は、その螺旋が回転していることを実感する時。ホワイトボードの前で問題の本質構造が見えた夜も、見知らぬ開発者が想像を超えた使い方をしているのを発見した日も、螺旋の異なる位相における「自覚の瞬間」である。 > > この螺旋を回し続けるために必要なのは、自らの発見を世界に委ねる勇気と、世界からの反響に自分を開く謙虚さである。 なかなかにエモい結論でした。 エージェント間のコミュニケーションを観察し、理解することは Agent Teams をより効果的に活用するうえでのヒントになるはずです。たとえば、メンバー間のメッセージが冗長になっていることに気付けば、プロンプトで簡潔なやり取りを指示することで、コンテキストの消費を抑えるといった改善につなげられるでしょう。 本稿で使用したチャットインターフェースは GitHub リポジトリ で公開しています。サーバーの起動は不要で、ブラウザで HTML ファイルを開くだけで利用可能です。皆さんもぜひ、Agent Teams のメンバー間のやり取りを観察してみてください。 本記事が、Agent Teams の理解や活用の一助となれば幸いです。 参考サイト [1] Introducing Claude Opus 4.6 [2] Building a C compiler with a team of parallel Claudes [3] Claude Code セッションのチームを調整する 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @satorin レビュー: @handa.kenta ( Shodo で執筆されました )
こんにちは、クロスイノベーション本部リーディングエッジテクノロジーセンターの小澤です。 「Claude Codeのスキルを多数運用しているとコンテキストを圧迫する」といった意見がGitHub Issuesやブログに複数あり、独自のワークアラウンドも生まれているようです。 本ブログでは、スキルの読み込みの仕様を整理し関連する議論を概観したうえで、執筆時の最新バージョンでのコンテキスト消費を計測します。 検証バージョン Claude Codeのスキル読み込みの仕様 関連する議論 GitHub Issues Zenn記事 議論の整理 計測 第1段階(起動時):分割による変化なし 第2段階(呼び出し時):分割版のほうがコストが高い まとめ 参考リンク 検証バージョン Claude Code v2.1.50(2026年2月23日時点の最新) / Claude Opus 4.6 Claude Codeのスキル読み込みの仕様 Claude Codeのスキル読み込みは Progressive Disclosure という段階的に読み込む仕組みで設計されています。 これはClaude Code固有の仕様ではなく、Anthropicが公開した Agent Skillsオープンスタンダード に基づくもので、Codex、Cursor、Gemini CLI、GitHub Copilot、opencodeなど、多くのプラットフォームが採用しています。 段階 タイミング 読み込まれる内容 第1段階 起動時 YAMLフロントマター( name と description )のみをシステムプロンプトに読み込む 第2段階 スキル呼び出し時 SKILL.md本文をコンテキストに注入する 第3段階 必要に応じて スキルディレクトリ内の追加ファイルをReadツール等で読み込む 公式ドキュメントのスキルの例 つまり、第1段階の起動時にはYAMLフロントマターだけが読み込まれ、SKILL.md本文は第2段階のスキルが呼び出されるまでコンテキストを消費しません。スキルが多数あっても起動時のコストはフロントマターの個数分だけであり、全スキルの本文が常時コンテキストに載るわけではない、というのがProgressive Disclosureの設計意図です。 第3段階は、スキルが複雑になり単一のSKILL.mdに収まりきらない場合や、特定のシナリオでしか使わないコンテキストがある場合に、追加ファイルをスキルディレクトリにバンドルしてSKILL.mdから参照する設計です。 公式ドキュメントで例示されているPDFスキルでは、フォーム入力の手順をforms.mdに分離しています。PDF処理全般がスキルの責務ですが、フォーム入力はそのうちの一部のシナリオでしか発生しません。SKILL.mdに全手順を書くとフォーム入力しないときもコンテキストを消費するため、forms.mdに分離してフォーム入力タスクのときだけClaudeが Read ツールで参照します。 また、 ベストプラクティス には Keep SKILL.md body under 500 lines for optimal performance とあり、500行を超える場合に別ファイルへの分割を推奨しています。 関連する議論 問題は上記仕様のProgressive Disclosureが期待通りに動いていないのではないか、という指摘がGitHub Issuesやブログ記事で複数見られることです。 GitHub Issues 2025年12月 ~ 2026年1月にかけて、スキルのトークン消費に関するIssueが報告されています。 Issue 起票日 ステータス 主張 Claude Code バージョン #14834 2025/12/20 Open /context でスキル全量のトークン数を表示しているが、実際はフロントマターだけロードされている v2.0.74 #14882 2025/12/21 Open /context でスキル全量のトークン数を表示。Progressive Disclosureが動いていない v2.0.74 #15530 2025/12/28 Closed as duplicate 6スキルで17.8kトークン。フルファイルがロード v2.0.76 #16616 2026/01/07 Closed as not planned スキルが全量ロード(5.9k〜1.7k)。Pluginのフォーマット .claude-plugin/plugin.json に変換して解消 v2.0.76 これらのIssueで注目したい点が2つあります。 #14834 の報告者が「実際にはフロントマターだけロードされている。 /context の表示が間違っているだけ」と主張していることです。この報告者は #16616 にも「表示バグであり、v2.1.1で修正された」とコメントしています。ただし、報告者はAnthropic社員でないため公式回答ではありません。 #16616 でPluginのフォーマットへの変換で解消したことです。表示バグだけなら両方とも同様に大きく表示されるはずで違和感があります。 なお、v2.1.1(2026/01/07)は CHANGELOG に記載がなく、修正内容の公式の記録はありません。 Zenn記事 Zenn記事 「スキルを87個運用したら898KBになった。SKILL.md分割で27KBに戻した実測記録」(2026/02/21公開) では、以下の主張があります。 スキルが増えるほどClaude Codeが遅くなる。原因はSKILL.mdがコンテキストを圧迫しているからだった。SKILL.md(フロントマター+参照1行)とINSTRUCTIONS.md(詳細手順)に分割したところ、87スキル全体で898KB→27KBへ97%削減できた。 スキルを増やすほど問題が起きる。スキルが呼ばれるたびに、SKILL.mdの全内容がシステムプロンプトに注入されるのだ。 ただし、Claude Codeのバージョン明記がなく、計測もファイルサイズの比較のみでコンテキスト消費量の比較がないため、検証の余地があります。 議論の整理 これらの報告をまとめると、以下の状況が見えてきます。 v2.0.x時代(2025年12月頃)に、 /context コマンドの表示バグ、あるいは実際のロードバグがあった可能性がある 表示バグなのか実際のロードバグなのかは、Claude Codeのソースコードが非公開のため確定できない v2.1.1以降で修正されたという報告があるが、公式の記録はない 重要なのは最新バージョンでの挙動なので、次のセクションでコンテキスト消費を計測して確認します。 計測 関連する議論で紹介したZenn記事のワークアラウンド(SKILL.md + INSTRUCTIONS.mdへの分割)が実際に効果があるのかを確認するため、同一スキルを「SKILL.md単体」と「SKILL.md + INSTRUCTIONS.mdに分割」の2パターンで比較しました。起動時と呼び出し時のトークン消費を /context コマンドで計測しています。 /context コマンドはトークン消費の内訳を表示します。本計測ではSkills(システムプロンプト内のスキル情報)とMessages(会話のやり取り)の2項目に注目します。 第1段階(起動時):分割による変化なし パターン Skillsのトークン数 スキル分 スキルなし 393 0(ベースライン、スキルなしでもトークン消費あり) SKILL.md単体 441 48(YAMLフロントマターのみ) SKILL.md + INSTRUCTIONS.mdに分割 441 48(YAMLフロントマターのみ) 「SKILL.md単体」と「SKILL.md + INSTRUCTIONS.mdに分割」の起動時のSkillsのトークン数は同一です。 SKILL.md本文のサイズは起動時のコストに影響がなく、Progressive Disclosureが仕様通りに動作していることが確認できます。 第2段階(呼び出し時):分割版のほうがコストが高い パターン Messages(呼び出し前) Messages(呼び出し後) Skills(呼び出し前) Skills(呼び出し後) SKILL.md単体 8 7.1k 441 441 SKILL.md + INSTRUCTIONS.mdに分割 8 9.4k 441 441 分割版ではMessagesのトークン数が2.3k多くなっています。 単体ではSKILL.md本文が1回で注入されるのに対し、分割版ではSKILL.md本文にINSTRUCTIONS.mdへの参照のみが書かれているため、Claudeが必ずReadツールで読みに行きます。その結果、ツール呼び出しのオーバーヘッドが加算されます。 また、どちらのパターンでもSkillsのトークン数の441はスキル呼び出し後も変化していません。SKILL.md本文はシステムプロンプトではなくMessagesのトークンとして計上されていました。 なお、v2.0.x時代に /context コマンドの表示バグが報告されていましたが、本計測ではSkillsとMessagesのトークン数が仕様通りに変動しており、表示バグの影響はないと判断しています。 まとめ 計測結果は以下の通りです。 観点 結果 起動時のSkillsのトークン数 SKILL.md単体・分割版ともに同一(YAMLフロントマターのみ) スキル呼び出し時のMessagesのトークン数 分割版のほうが2.3kトークン多い(Readツールのオーバーヘッド) SKILL.md分割の効果 v2.1.50では不要。むしろ逆効果 v2.0.x時代に表示バグ、あるいはロードバグがあった可能性はありますが、少なくともv2.1.50ではProgressive Disclosureが正しく動作しており、全スキルの本文をINSTRUCTIONS.mdに分離するような対策は不要です。 スキルの運用で意識したい点は2つです。 YAMLフロントマターの description を適切に書く description を見てスキルを呼び出しを判断するので、曖昧だと不要なスキルが呼ばれてMessagesのトークンを浪費します。 SKILL.md本文が大きくなったら関心ごとにファイルを分割する Progressive Disclosureの第3段階にあたり、特定のシナリオでしか使わない手順を別ファイルに分離することで、呼び出し時のトークン消費を抑えられます。ただし、全スキルの本文を一律に別ファイルへ逃がすのではなく、1つのスキル内でシナリオに応じて読み込みを分岐させる設計です。 参考リンク Agent Skills 公式ドキュメント Skill authoring best practices Equipping agents for the real world with Agent Skills Issue #14834: /context command shows full skill tokens instead of loaded metadata tokens Issue #14882: Skills consume full token count at startup instead of progressive disclosure Issue #15530: Project skills loading full files instead of frontmatter during discovery phase Issue #16616: User skills fully loaded into context instead of frontmatter-only Claude Code CHANGELOG スキルを87個運用したら898KBになった。SKILL.md分割で27KBに戻した実測記録 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ozawa.hideyasu レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
金融IT本部 入社1年目の河岸歩希です。 会社の同期と個人開発に取り組んでいます。 その過程で「LINEのような個別チャット機能」を実装するにあたり、AWSのサーバーレス構成(Lambda + DynamoDB)の採用を検討することになりました。 今回は実際に調査と設計を行う中で得られた気づきについて共有させていただきます。 はじめに 想定読者 本記事の目的 チャット機能の要件 DynamoDBの設計を理解する 基本的な仕組み プライマリキーとパーティション ホットパーティションに注意する データアクセスパターン RDBとの違い アクセスパターンを洗い出す チャット機能へのアクセスパターン テーブル設計 最初に考えた設計 問題「自分の参加ルーム一覧」が取れない GSIとは GSIで何が解決できるのか GSIの特徴 射影(Projection)という概念 LSI(ローカルセカンダリインデックス)との違い テーブル設計(完成) Messagesテーブル GSI(グローバルセカンダリインデックス) まとめ はじめに 想定読者 RDBは触ったことがあるけど、DynamoDBは初めての方 「DynamoDBって何が嬉しいの?」という疑問をお持ちの方 サーバーレス構成でチャット機能を作りたい方 本記事の目的 DynamoDBとRDBの設計思想の違いを理解する チャット機能の要件 今回調査したのは、LINEのような1対1の個別チャット機能です。 個別チャット機能はアプリケーション全体のコア機能ではありませんが、初めて実装する機能だったため、先行して調査を行いました。 チャット機能を実装するにあたり、以下のような要件を整理しました。 要件 理由 リアルタイム性 メッセージは送信後すぐに相手に届いてほしい 高頻度の書き込み チャットはメッセージの追加が頻繁に発生する スケーラビリティ 現状は30人規模だが、将来的な拡大も想定したい 高可用性 業務時間中はいつでも利用できる状態を維持したい DynamoDBの設計を理解する DynamoDBはNoSQL(Not Only SQL)データベースの一種で、キーバリュー型に分類されます。 キーバリュー型の特徴は、キーを指定して値を取得するシンプルな構造です。 DynamoDBでは、データは以下のような構造で格納されます。 テーブル └── 項目(Item)← 1件のデータ ├── パーティションキー: "UserA" ← キー(必須) ├── ソートキー: "2026-01-17" ← キー(任意) ├── Name: "鈴木一郎" ← 属性 ├── Email: "suzuki@example.com" ← 属性 └── Department: "営業部" ← 属性 項目(Item) : 1件のデータのまとまり(RDBでいう「行」) キー(プライマリキー) : 項目を一意に特定するもの 属性(Attribute) : 項目が持つ各フィールドのこと 基本的な仕組み DynamoDBのようなキーバリュー型のデータベースを理解するうえで、最も重要なのは「キー」の設計です。 はじめに「プライマリキー」の構成について説明します。 プライマリキーとパーティション プライマリキーは「パーティションキー」と「ソートキー」で構成されており、これらを適切に設計することで、効率的なデータアクセスが可能になります。パーティションキーは必須、ソートキーは任意です。 パーティションキーの役割はその名の通り「パーティション(区画)を決定する」ことです。 DynamoDBでのパーティションは、SSDによってバックアップされ、AWSリージョン内の複数のアベイラビリティゾーン間で自動的にレプリケートされる、テーブル用のストレージの割り当てのことを指します。 公式ドキュメントには以下のように記載されています。 DynamoDBは、パーティションキーの値を内部ハッシュ関数への入力として使用します。 ハッシュ関数からの出力により、項目が保存されるパーティション (DynamoDB内部の物理ストレージ) が決まります。 出典: パーティションとデータ分散 - Amazon DynamoDB デベロッパーガイド つまり、同じパーティションキーを持つデータは物理的に同じパーティションに格納され、異なるパーティションキーを持つデータは別のパーティションに格納されます。 以下の図は、同ドキュメントから引用したものです。 この図では、パーティションキー「AnimalType: Dog」を持つ項目が、ハッシュ関数を通過し、その出力値に基づいて特定のパーティションに格納される様子を示しています。 同様に「Fish」「Lizard」「Bird」「Cat」「Turtle」といった異なるパーティションキーを持つ項目は、それぞれ異なるパーティションに分散して格納されます。 これがパーティションキーのみを用いた場合の、DynamoDBにデータが格納される仕組みです。 この方法に加えて、パーティションキーとソートキーを組み合わせた 複合プライマリキー と呼ばれるタイプも存在します。 この図では、パーティションキー「AnimalType」とソートキー「Name」を組み合わせています。同じパーティションキー「Dog」を持つ項目(Bowser、Fido、Rover)は、同じパーティションに格納されます。 その中でソートキー「Name」によって項目が一意に識別され、ソートされた状態で格納されます。複合プライマリキーを使用することで、「同じパーティションキーを持つ複数の項目」を1つのパーティションにまとめて格納し、ソートキーを使って範囲検索やソートを行うことが可能になります。 なので効率的なデータ探索をするためにも、シンプルなプライマリキーにするのか、複合プライマリキーを利用するべきなのかは、アプリケーションのユースケースによって異なるため、事前にどのようなアクセスパターンでデータ操作するのかを検討することが重要です。公式のベストプラクティスでも、アクセスパターンを事前に把握したうえでのキー設計が推奨されています。 NoSQL の設計の違い 対照的に、DynamoDB の場合は答えが必要な質問が分かるまで、スキーマの設計を開始すべきではありません。 ビジネス上の問題とアプリケーションのユースケースを理解することが不可欠です。 出典: NoSQL 設計のベストプラクティス - Amazon DynamoDB デベロッパーガイド 「答えが必要な質問が分かるまで」という表現は少々わかりづらいですが、言い換えると 「どのようなクエリパターンでデータを取得するのかが明確になるまで、スキーマ設計を始めるべきではない」 ということだと解釈しています。 ホットパーティションに注意する ここまでのパーティションキーの説明を踏まえて、パーティションがキーごとに分散されるのなら、仮にユーザー数が増えたときにパーティションの上限が来るのではないかという疑問が浮かびました。 公式ドキュメントに以下のような記載がありました。 DynamoDB テーブルには、パーティションキーバリューごとに個別のソートキーバリューの数に上限はありません。何十億もの Dog 項目を Pets テーブルに保存する必要がある場合、DynamoDB はこの要件を自動的に処理するのに十分なストレージを割り当てます。 出典: パーティションとデータ分散 - Amazon DynamoDB デベロッパーガイド つまり、パーティションが増えること自体は問題ではないということです。 むしろ注意すべきは、特定のパーティションにアクセスが集中することです。これを「 ホットパーティション 」と呼びます。 例えば、パーティションキーを「日付」にした場合を考えます。今日のデータへのアクセスが集中し、過去の日付のパーティションはほとんど使われません。せっかくパーティションが分かれていても、1つに集中してしまっては意味がありません。 今回のチャット機能では、パーティションキーを RoomId にしています。1対1の個別チャットなので、全員が同じルームに集中することはなく、ルーム数が増えるにつれてアクセスは自然と分散されます。もしグループチャットや全社連絡用のルームを作る場合は、特定のルームにアクセスが集中する可能性があるため、別の設計を検討する必要があるかもしれません。 なので、ユーザーアクセスが特定のパーティションに集中することがないようにパーティションキーを設定する必要があります。 データアクセスパターン DynamoDBのデータを取得する方法は主に2パターンあります。 方法 説明 速度・コスト Query パーティションキーを指定して取得。ソートキーで範囲指定やソートも可能 高速・低コスト Scan テーブル全体を走査して条件に合うものを取得 低速・高コスト 基本的にはQueryを使い、Scanは避けるべきとされています。Queryが高速なのは、たった今説明したように、パーティションキーやソートキーによってパーティションが明確に分けられているからです。この分散したデータ管理によって、DynamoDBは大規模なデータに対しても高速なアクセスを実現することができます。 具体的には、一つのテーブルに対して同時にアクセスする際にも、パーティションが分かれていることによって、同時実行性が向上し、スループットを効率的にスケールさせることが可能となります。 またScanはテーブル全体をスキャンするオペレーションであるため、テーブルが大きくなるにつれて、処理速度は遅くなります。そのため応答時間短縮のためにも、なるべくQueryを使用できるように、テーブル設計段階で適切なパーティションキーとソートキーを検討することが大切です。 RDBとの違い ここまで理解したところで、ある疑問が浮かびました。 「RDBでもWHERE句を使えば、目的のデータを効率的に探索できるよね?DynamoDBのパーティションキーを指定するのと何が違うの?」 この時点では突出したDynamoDBの良さを感じることはできていませんでした。(初心者目線です...) ただ、調べていくうちに、 「1回のクエリの速度」ではなく「同時に大量のクエリが来たとき」 に違いが出ることがわかりました。 RDBの場合、1000人が同時にアクセスすると、すべてのリクエストが1台のDBサーバーに集中します。(リードレプリカやマルチAZ構成にした場合を除く) その結果、CPUやコネクションが逼迫し、全体的にレスポンスが遅くなります。 一方、先ほども説明したとおり、DynamoDBの場合、各リクエストはパーティションキーに基づいて別々のパーティションに分散されます。 そのため、同時接続数が増えても負荷が1箇所に集中せず、レスポンス速度を維持できます。 公式ドキュメントにも、以下のような記載があります。 RDBMSでは、データは柔軟にクエリできますが、クエリは比較的コストが高く、トラフィックが多い状況では スケールがうまくいかない場合があります。 出典: リレーショナルデータ設計と NoSQL の相違点 - Amazon DynamoDB デベロッパーガイド DynamoDBは、パーティションキーによるハッシュ分散があるからこそ、データ量やアクセス数が増えても性能を維持できます。これがRDBとの大きな違いの一つです。 なので大規模アプリケーションや人気イベント等などの、ユーザーからの大量なアクセスが予想される際は、こういったDynamoDBの分散管理の仕組みが輝くということがわかりました。 アクセスパターンを洗い出す 実際にここからテーブル設計の流れを共有します。 前述したNoSQLの設計のベストプラクティスの引用文の中でもあったように、DynamoDBでは「どのようなクエリパターンでデータを取得するのか」を明確にしてから設計を始める必要があります。 そこで、まずはチャット機能のアクセスパターンを洗い出しました。 チャット機能へのアクセスパターン ケース パターン 使用例 1 特定ルームのメッセージ一覧を取得 チャットルームを開いたとき 2 最新N件のメッセージを取得 初期表示・スクロール時 3 自分が参加しているルーム一覧を取得 チャット画面を開いたとき 4 新しいメッセージを追加 メッセージ送信時 5 新しいチャットルームを作成 初めてのDMを送るとき 6 チャットルームを削除 ユーザーがルームを消したとき このような形でアクセスパターンを洗い出しました。その後に、「何を起点にデータを探すか」を考えてみます。 ケース1, 2, 4: RoomId を起点にメッセージを操作 ケース3: UserId を起点にルームを検索 ケース5, 6:ルームの作成・削除 ここでケース3だけ他のケースとは起点となるデータが違ってしまうことに気づきました。 DynamoDBでは、パーティションキーとソートキーを指定することで効率的にクエリを実行できますが、パーティションキー以外の属性を検索条件にすることはできません。 テーブル設計 最初に考えた設計 メッセージを格納するテーブルとして、以下のような設計を考えました。 パーティションキー :RoomId(チャットルームのID) ソートキー :Timestamp#UserId(送信時刻と送信者ID) 属性 :Message アクセスパターンを踏まえて、一番起点となるデータをパーティションキーに持ってきました。また、 ソートキーにはチャットの履歴の取得などを行いたいので、タイムスタンプとして、同じ時間に送信した場合にどちらのユーザーが送ったのかを判別するために UserId と結合させて ソートキーに格納します。 パーティションキーに RoomId を指定してQueryを実行すれば、そのルームのメッセージがソートキー(時刻順)でソートされて取得できるイメージです。 問題「自分の参加ルーム一覧」が取れない しかし、「自分が参加しているルーム一覧を取得(ケース3)」を実現しようとした際、壁にぶつかりました。 現在の設計では、パーティションキーは RoomId です。 DynamoDBのQueryは パーティションキーの完全一致 が必須なので、「 UserId を起点にルームを検索する」ということはできません。 これを解決するのが GSI(グローバルセカンダリインデックス) という仕組みです。 GSIとは GSIを一言で説明すると、 「元のテーブルとは別のプライマリキーでQueryできるようにするコピーテーブル」 です。 公式ドキュメントには以下のように記載されています。 グローバルセカンダリインデックスには、ベーステーブルとは異なるパーティションキーとソートキーがあります。 出典: グローバルセカンダリインデックス - Amazon DynamoDB デベロッパーガイド GSIで何が解決できるのか 公式ドキュメントのシナリオがとてもわかりやすかったので、具体的な使用例はそちらを参考にしていただければと思います。(上記のGSIの出典先と同じリンク) GSIを定義することで、元のテーブルに加えて、別の切り口で検索できるようになります。 【元のテーブル】 パーティションキー: RoomId ソートキー: Timestamp#UserId → 「特定ルームのメッセージ」を取得できる 【GSI】 GSI-パーティションキー: UserId GSI-ソートキー: RoomId → 「特定ユーザーの参加ルーム一覧」を取得できる GSIで新たに選ばれたパーティションキー UserId ごとに、異なるパーティションにデータが元のテーブルからコピーされます。 これによって、ケース3の「自分が参加しているルーム一覧を取得」も実現できそうです。 GSIの特徴 GSIについて調べる中で、以下のような特徴があることがわかりました。 特徴 説明 元テーブルとは別のプライマリキーを設定可能 柔軟な検索が可能になる データは自動で同期される 元テーブルに書き込むとGSIにも反映(結果整合性) 後から追加可能 設計を間違えても後からリカバリできる 追加コストがかかる 書き込み・ストレージ両方で課金 注意点として、結果整合性であるため、書き込み直後にGSIをクエリすると、最新のデータが反映されていない場合があります。 そのため、リアルタイム性が重要な場面では考慮が必要になります。今回はGSIを使うのが、自分が参加しているルーム一覧取得の場面のみなので、多少のデータ遅延は許容できると判断しました。(実際ルーム作成直後にそのままルームでチャットを始めると思うので...) これが金融の残高照会等のシビアな要件が絡むと、この結果整合性に伴う遅延が大きな影響をもたらすので、要件と照らし合わせて適切なアーキテクチャを選択する必要があります。 射影(Projection)という概念 GSIを学ぶ中で、 射影(Projection) という概念にも出会いました。 射影とは、「元テーブルのどの属性をGSIにコピーするか」を指定するものです。 今回は KEYS_ONLY (キーのみコピー)を選びました。 理由は、GSIで取得したいのは「どのルームに参加しているか」という RoomId の一覧だけで、メッセージ本文などの属性は不要だからです。 GSIにコピーされていない属性が必要な場合、元テーブルに対して追加のクエリが必要になります。 そのため、アクセス頻度が高い属性は射影に含めておくことで、クエリ回数を削減できます。 公式ドキュメントにも、以下のような記載がありました。 一部のアプリケーションでは、テーブルに対して多くのクエリを発行する必要があり、その結果、プロビジョニングされたスループットの多くを消費することがあります。これを軽減するために、すべてまたは一部のテーブル属性をグローバルセカンダリインデックスに射影できます。 出典: グローバルセカンダリインデックス - Amazon DynamoDB デベロッパーガイド ストレージ管理コストは増えますが、頻繁にアクセスする場合はクエリコストの削減で相殺されるという考え方です。 LSI(ローカルセカンダリインデックス)との違い GSIを調べる中で、 LSI(ローカルセカンダリインデックス) という似た概念があることも知りました。 LSIの「ローカル」とは、 同じパーティション内に存在する という意味です。 GSIは元テーブルとは別のパーティションにデータがコピーされますが、LSIは同じパーティション内でソートキーだけを変えたインデックスです。 今回の設計では GSI を使うのがよさそうだと判断しました。 理由は、LSIでは要件を満たせないからです。 今回のアクセスパターンの中で「ユーザーIDで、自分の参加ルーム一覧を検索する」部分があります。 メインテーブルのパーティションキーは RoomId なので、 UserId で検索するにはパーティションキーを変更する必要があります。 LSIではパーティションキーを変更できないため、そもそも今回の要件を満たせません。 そのため、GSIを採用しました。 さて、これらのプロセスを経て、実際にテーブル設計が完了しました。 今回設計したテーブルは以下のとおりです。 テーブル設計(完成) Messagesテーブル 項目 値 説明 パーティションキー RoomId チャットルームのID(例: suzuki_yamada ) ソートキー Timestamp#UserId 送信時刻とユーザーID(例: 2026-01-18T10:30:00Z#suzuki ) 属性 Message メッセージ本文 GSI(グローバルセカンダリインデックス) 項目 値 説明 GSI名 UserRoomsIndex GSI-パーティションキー UserId ユーザーID GSI-ソートキー RoomId チャットルームID 射影 KEYS_ONLY プライマリキーのみコピー これにより、以下の検索が可能になります。 アクセスパターン 使用するインデックス クエリ方法 特定ルームのメッセージ一覧 メインテーブル パーティションキー=RoomIdでQuery 自分の参加ルーム一覧 GSI(UserRoomsIndex) GSI-パーティションキー=UserIdでQuery ここまででテーブル設計が完了しました。 まとめ 今回、チャット機能のためのDynamoDBテーブル設計を行いました。 設計を通じて学んだポイントは以下の3つです。 アクセスパターンを先に考える DynamoDBでは「どんなクエリが必要か」を明確にしてからキー設計を行う。RDBのように後からインデックスを追加して柔軟に対応する発想ではうまくいかない。 パーティションキーの設計が最重要 パーティションキーによってパーティションが決まり、検索効率とスケーラビリティが大きく変わる。ホットパーティションを避け、アクセスが分散されるようなPKを選ぶ。 GSIで検索の柔軟性を確保 メインテーブルのパーティションキーでは対応できないアクセスパターンがある場合、GSIを活用する。ただし結果整合性やコストには注意が必要。 今回はここまでで、実装はこれからです。 あくまでも机上の設計なので、実装段階で想定外のエラーや設計の見直しが発生するかもしれません。そのときはまた調べて共有できればと思います。 初心者なりにまとめてみましたが、同じようにDynamoDBを学び始めた方の参考になれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kawagishi.ibuki ( Shodo で執筆されました )
はじめまして! バリューチェーン本部の花ヶ崎雄太と申します。 新卒1年目の12月に一般社団法人コンピュータ教育振興協会(ACSP)の主催する3次元CAD利用者技術試験の準1級を受験し、合格いたしました。 今回はそれについて、 ・3次元CAD試験とは ・受験の理由と感想 ・出題される問題の概要と傾向 ・実際の学習方法 ・合格のコツ の5つに分けてご紹介します。 本記事の想定読者 ・3次元CAD利用者技術試験に興味がある/取得の予定があるが、CADの操作については未習得な人 ・仕事や学業で製造系の業界や学問に携わっており、これからCADを習得予定の人 ※なお本記事では「CADそのもの」やそれに関連した個別の用語についての説明は行いませんが、想定読者向けに難しいと考えられる単語には一部注釈をつけています。 1. 3次元CAD試験とは 試験概要 ここは公式から引用します。 『「3次元CAD利用技術者試験」は、3次元CAD を利用するエンジニアや学生が身につけておくべき知識と技能が証明できる、3次元CAD試験制度です。』 『3次元CADシステムを利用した機械系・製造系のモデリング・設計・製図などの業務に従事することを目指す方、もしくは従事して間もない方を想定して試験を行います。3次元CADを学び、知識と操作の基礎的な部分を習得し、設計の補助業務やオペレーターを目指す方が対象です。』 2025年度 3次元CAD利用技術者試験 概要 より引用。 つまり3次元CADをこれから積極的に利用していこう、という方に向けての試験ということですね。 ちなみに本試験は製造業でCADを使用する方向けであり、建築業向けではないようです。 2級について ~準1級受験には事前に2級の取得が必要~ 準1級を受験するためには事前に2級に合格している必要があります。 具体的には準1級の申し込み期限の前日(準1級試験日の1か月ほど前)までには2級を取得している必要があるので、準1級以上の取得を目指す方はまずは2級を取得しましょう。 2級は比較的難易度の低い選択式の試験で、CBT形式でテストセンターで実施されます。 難易度は高くはありませんので、公式の販売している書籍( https://www.acsp.jp/ACSP_books.html )を学習して過去問演習を重ねれば十分合格可能です。 私の累計の学習時間は 15時間 ほどです。 2級を学習することで、その後のモデリングでも用いる基本的な用語や手法を知ることができます。 2級にはモデリングの実技はありませんが、できれば2級もCADを触りながら学習したほうが学習効率が高いと思います。 準1級について 準1級は年に2回、例年7月と12月に開催されます。 いつでも受けられる2級とは違い年2回のみの開催ですので、機会を逃さないように注意しましょう。 準1級では実際に会場にPCを持ち込み、モデリングの実技を行います。 詳しくは後述しますが、問題は2次元図面を読み取って パーツモデル*1 を作成する形式です。 そうして作成したモデルの体積、表面積などを測定し、解答群の中から最も近い値をマークシートに記入する形式で試験が行われています。 試験時間は120分と長めです。 ーーーーー *1 パーツモデル :単一の部品を3DCADで作成したモデルのこと。 ーーーーー 2. 受験の理由と感想 なぜ受験したのか? 率直に言えば、業務上必要だったからです。 私の所属する部署はCADをメインの商材として扱っており、CADの基礎知識/技術を身に着ける必要があります。 そのため業務上の要求として最低限「2級」の資格を取得することとなっていました。 しかしOJTの方からのアドバイスや、より高みを目指したいという思いから、私は準1級に挑戦することにしました。結果的に、私の所属するバリューチェーン本部の新人14人のうち、準1級を取得したのは私含め2人でした。 受験してよかったか? 結論としては、よかったです。そう思う観点は3つあります。 1.CADの基礎知識および基本的なコマンドを習得することができた。 準1級では、要求されるコマンドは基本的なものがほとんどです。試験問題がスムーズに解けるようになる頃には、基本的な形状(例えばテレビリモコンなど)なら難なくモデリングできるくらいのスキルが十分につきます。 2.扱うCADに関する理解が大きく深まった。 学習・受験に利用するのはそれぞれの受験者が利用しているCADですから、そのCADについての設定や効率的な使用方法、得意や苦手などの理解が大きく深まります。 今回私は「NX」というCADを使って学習と受験を行いましたが、学習の過程でNXならではの強みや特異な点を複数感じ、理解することができました。 3.単純に楽しかった。 個人的には重要な観点だと思います。 CADで形状を作るのが、なんだか砂山で城を作っているような感覚で楽しかったです。 またモデリングの時間が早まることや、それまで気づかなかったモデリング方法に気づくことなどで自分の成長をひしひしと感じられます。 今回の学習と受験経験を通して、仕事を行うモチベーションという点で「楽しい」という感覚が重要だと実感できました。 3. 出題される問題の概要と傾向 ここでは準1級における具体的な問題の概要と傾向、主な使用コマンドについて紹介します。 問題の概要 準1級では、 準1級と1級で共通の問題が2問 と、 準1級のみの問題が1問 の、 合計3問 出題されます。 どの問題にも共通なのが、最終的な問題の解答は「モデルの完成」ではなく、「 完成したモデルからのパラメータの測定値 」であることです。 具体的には各問題につき3~5問の設問があり、「点Aから点Bの長さを測定し、選択肢から最も近いものを選べ」「立体Cの体積を測定し、選択肢から最も近いものを選べ」といった出題がなされます。 指示や図面に従ってモデリングをし、その最中や完成後にパラメータの測定を行い、マークシートに記入する、までが、基本的な解答の流れです。 (1) 共通問題-1 座標や形状、モデリング方法についての指示があり、指示通りにモデリングを行う形式です。 基本は指示に従えばよいので、練習を繰り返すことでコマンドを理解し、モデリング速度を上げることが重要です。 主な要求コマンド スケッチ、押し出し(和・差・積)、回転、直方体、円筒、球、エッジブレンド、面取り、シェル、データム平面(無限平面)、トリムボディ、ミラージオメトリ、パターンジオメトリ、線 ※コマンドの名称はNX準拠のため、CADによって異なる可能性があります。 (2) 共通問題-2 3面図*2 を読み取って、その図面の通りにモデリングを行います。 無限平面*3 を使った複雑な押し出しが求められることが多く、効率よく解くためには、経験に基づく「閃き」が必要です。個人的には準1級範囲で最も癖が強いと感じています。 また実際の現場で作ることは決して多くない形状だと思いますので、問題に慣れるまで時間がかかります。 主な要求コマンド スケッチ、押し出し(和・差)、直方体、エッジブレンド、面取り、データム平面(無限平面)、トリムボディ、円錐、点セット ※コマンドの名称はNX準拠のため、CADによって異なる可能性があります。 ーーーーー *2 3面図 :正面図、側面図、上面図の3種の図面。3次元形状を3方向から投影した2次元図面であり、この3つを見ることで3次元形状の作成が可能。 *3 無限平面 :CADではXYZ平面以外にも任意の平面が作成可能。この平面で立体を切断する手順は超頻出。 ーーーーー (3) 準1級問題 共通問題-2と同じく、3面図を読み取ってその通りにモデリングを行います。しかしこちらの方が”現実にありそうな”形状が出題されるので、比較的イメージはつきやすいです。 ただ複雑なスケッチやコマンドの使用をせざるを得ない場合もあり、要求されるモデリング力は高いです。 主な要求コマンド スケッチ、押し出し(和・差)、回転、直方体、円筒、エッジブレンド、面取り、シェル、ドラフト、データム平面(無限平面)、トリムボディ、点セット ※コマンドの名称はNX準拠のため、CADによって異なる可能性があります。 4. 実際の学習方法 コマンドを知る 私はまず、コマンドを知ることから学習を開始しました。 最初はそもそも必要なコマンドがバーのどこにあるのか、似たようなコマンド(例えば、パターンフィーチャとパターンジオメトリ)のうちどれを使えばいいのかなど、全くわかりません。 そこで最初は、準1級の共通問題-1をじっっっくり時間をかけて解き、「どのコマンドがどこにあるのか」「どのコマンドをどの順番で使えば目的の形状を実現できるのか」を学習しましょう。 共通問題-1は問題文の中で使うコマンドや座標を指示してくれているので、「コマンドの所在の把握」や「そのコマンドで何が起こるのか」の学習に向いています。 ちなみに私は最初、共通問題-1を1問解くのに4時間かかりました(笑)。 とにかく演習あるのみ コマンドについてある程度把握出来たら、あとはとにかく問題を解きまくります。 問題は基本的には過去問です。 公式の書籍には過去問が1年分(前期と後期の2回分)掲載されているので、演習に使用できます。 また 試験の公式サイト ( https://www.acsp.jp/cad/3d_past_web3d.html )では、過去問において出題された形状のモデリング結果が掲載されています。図面から正解の形状が想像もつかないときや、行き詰まった際に参照するととても役立ちます。 さらに、 日経クロステック ( https://xtech.nikkei.com/dm/article/COLUMN/20120605/221476/ )では当試験の例題が複数掲載されているので、そちらを利用して演習するのもよいでしょう。 問題に取り組む順番は 共通問題-1:~15分で解けるようになるまで 準1級問題:~60分で解けるようになるまで 共通問題-2:40~60分で解けるようになるまで をおすすめします。 共通問題-2と準1級問題は図面の読み取り力も必要になりますが、共通問題-2では図面で隠された部分(点線部分)に三角形の入れ子構造を作ることが要求されます。しかしコレには非常に癖があり、図面に慣れていない状態でいきなりやるのは難しいです。 そのため、手順が多い代わりに比較的オーソドックスな準1級問題で実力をつけてから取り組むのがおすすめです。 合格までの累計学習時間は? 私の場合は合計 70時間 ほどです。 業務時間での学習が認められていたため、平均1日2~3時間の学習を約1.5か月継続しました。 ただCAD経験ゼロの状態から開始し、初期はコマンドの学習に丸1日費やすこともありましたから、CAD使用経験が少しでもあるならこの時間は短くなると思います。 5. 合格のコツ 最後に私の経験から役立ったと感じた具体的な合格へのコツを、いくつかかいつまんでご紹介します。 準1級受験用のコマンドバーを作る CADによって異なるとは思いますが、私の利用するNXではコマンドのバーをカスタムで作成することができたので、試験で使うコマンドを集めてカスタムバーを作成して使っていました。 コレがあるだけでいちいちコマンドを探す手間が省け、また慣れも早かったので解答の高速化が実現しました。 また副次的に「カスタムバーを作成する」という設定面での学習ができたのも良かったと感じました。 同一形状を作るうえでも、複数の実現方法があると知る 単なる円筒を作成するうえでも、 ・円をスケッチして押し出し ・長方形をスケッチして回転 ・円筒コマンドの利用 など 複数の実現方法 があります。 これが試験問題ともなると、各形状を実現する方法が非常に多岐にわたります。 その中でどの方法が自分に合っているのか、どの方法が効率がよいか、といった部分は、問題を解くうちに経験的に身についてきます。 例えば上の例であれば基本的には「円筒コマンドの利用」が最速でしょう。 たくさん演習問題を解いて、自分に合ったモデリング方法を学びましょう。 またその方法が実際の業務や研究で使用するものならなお良いですね。 設問の順番通りにモデリングを行う 上の事項に関連して、同一形状を作るうえでも色々な順番で実現することが可能です。 しかし「試験の合格」を意識する場合は、設問の順番通りにモデリングを行うことをおすすめします。 指示がある共通問題-1は当然として、共通問題-2、準1級問題にも効率の良いモデリング順が存在します。これは設問の順番に従えば実現できるようになっています。 例えば 設問(1):点Aと点Bの距離を測定 設問(2):面Cの面積を測定 の場合、面Cを作る過程で、点Aと点Bも完成していることが多いです。 そしてここで先に、 設問(2)の面Cのところまで作った後に設問(1)を解いて間違っていた場合、まず間違いなく設問(2)も間違っているので、面Cのところまで作り直しになってしまいます。 しかし先に設問(1)の点Aと点Bを作成し解答しておけば、 間違っていても早く気づくことができる*4 のです。 ーーーーー *4 間違っていても早く気づくことができる :モデリング結果が違うときには「測定結果」が「解答の選択肢」と大幅に異なることが多いため、モデリングのミスには比較的気づきやすいです。 ーーーーー まとめ 今回は、3次元CAD利用者技術試験の準1級の受験や学習について、新卒1年目の目線で解説しました。 学習コストは大きいですが、準1級に合格する程度の知識/技術があれば、基本的なモデリング能力を身に着けたと十分言っていいくらいのスキルは得られます。 また単に学習するだけでなく、その中で実際の設計に活かせる経験も手に入ったと思います。 今後はさらにモデリング力を高めるために1級の取得を目指します。1級はアセンブリの知識やさらなるモデリングの高速化が求められるため、引き続き経験を積んでスキルアップを志向します。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @hanagasaki.yuta レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
はじめに 最近、 AWS の AI Practitioner を取得した金融IT本部 2年目の坂江 克斗です。 今回は、XI 本部の佐藤悠さんに協力していただきながら執筆した記事となります。 最近、「AI エージェント」という言葉をよく耳にするようになりましたが、正直なところ、私自身はエージェントが何をしているのか全く分かっていませんでした。 そこで今回、AI初心者の自分なりに理解するため、 MCP ベースのエージェントを 専用のライブラリを使わずに自作 し、その仕組みを理解してみることにしました。 本記事では、AI 初心者の方向けに、以下のイメージを掴んでもらえればと思います。 AIエージェントが「どのような仕組み」なのか MCP ホスト / MCP クライアント / MCP サーバの役割分担 はじめに 学習フロー エージェントとは ざっくりAIを考える AIの弱み RAG(Retrieval-Augmented Generation) Function calling Model Context Protocol(MCP) AIエージェント エージェントの作成(MCPベース) 前提 実装方針 通信内容の整理 JSON-RPC MCP 今回想定するMCPリクエスト・レスポンス initialize notifications/initialized tools/list tools/call 検証 まとめ おわりに 学習フロー エージェントの概要を理解し、実装するにあたり大まかに以下の流れで進めていきました。 佐藤さんから MCP やエージェントに関する概要を聞き、ソースと併せて理解を深める(1日目) MCP の公式ドキュメントを確認し、機能を絞ってエージェント( MCP ホスト / MCP クライアント / MCP サーバ / LLMとの連携)を実装する(1-2日目) 佐藤さんが執筆した MCP に関する記事も、あわせてご覧ください。 【初心者向け】2年目エンジニアが実践したMCPサーバー構築ガイド2025 【初心者向け】2年目エンジニアが解説するMCPクライアント構築ガイド エージェントとは ざっくりAIを考える 私自身、モデル構造や学習 アルゴリズム を理解しているわけではないので、ここでは最低限持っておきたい概念レベルに絞って整理します。 AI とは、人間の知能的な振る舞いを模倣することを目的とした概念であり、現在一般ユーザーが日常的に触れている AI の多くは、LLM(Large Language Model)を中心としたシステムになっています。 LLM の内部構造を詳細に追い始めると一気に難しくなりますが、本質的には非常に多くのパラメータを持つ巨大な関数、つまり、 「入力データを与えると、出力データが返ってくる」 ただそれだけの存在です。( ニューラルネットワーク では「層」という概念を用いてパラメータを調整していますが、外から見ると最終的には数式で表現できる関数として扱えます) 重要なのは、 LLM 自身が自動的に外部 API を呼び出したり、何かを実行しているわけではない という点です。 ただし、その関数が持つパラメータ量と複雑さが人間の想像をはるかに超えているため、結果として「考えているように見える」出力が得られている、というわけです。 AIの弱み AI が関数である以上、当然ながら限界も存在します。 それは、学習時(関数の各パラメータの調整時)に与えられていない情報については、単体の LLM だけでは原則として扱うことができません。 そのため開発者としては、「AI が知らない情報を、外部のデータを取得して回答に含めてほしい」と考えるようになります。 この課題を解決するために登場した仕組みが、RAG(Retrieval-Augmented Generation:検索拡張生成)です。 RAG(Retrieval-Augmented Generation) RAG(Retrieval-Augmented Generation) は、「AIが、質問に関連する情報を参照しながら、回答を生成しているように見せる」仕組みです。 この仕組みでは、従来の Web 検索や 検索エンジン の裏側でも使われてきた ベクトル検索 が利用されています。 ベクトル検索の細かい仕組みや実装は難しいですが、ざっくりとWeb検索をした際に関連サイトが取得できるイメージを持っておけば問題ありません。 大まかな実装イメージは、次のようなシンプルな流れになります。 まず、ユーザがプロンプト(質問文など)を入力すると、その内容をもとにベクトル検索を行い、 あらかじめ用意しておいたデータベースの中から 、プロンプトに関連する情報を取得します。(検索に適したプロンプトに変換するために、事前に LLM を利用する方式も存在します) 次に、その取得した情報をプロンプトとあわせて LLM に渡すことで、より文脈に沿った、精度の高い回答を生成できるようになります。 一方で、RAG はあくまで 「検索を拡張する仕組み」 であり、関連する情報を用いて回答を生成することはできますが、 API を呼び出したり何らかの処理やタスクを実行したりすることはできません。 そこで登場したのが Function Calling です。 Function calling Function calling は、OpenAI が発表した仕組みで、「AI が外部システムと連携し、学習範囲外のデータ取得や API 実行などの処理を行えるように見せる」ための方法です。 ただし、最初に述べたとおり、AI 自体は API を呼び出すことはできません。そのため、 API を実際に呼び出す処理はアプリケーション側で実装する必要があります。 イメージとしては以下の図に示す、クライアント(アプリ)・サーバ(LLM)間での処理フローとなります。 初めのLLMへのリクエストにおいて、LLMに使用すべきツール(機能)を選択させ、その機能をクライアント側で実行したのちに、結果とともに再度LLMに回答をもらう流れとなります。仕組み自体は、「 API 呼び出しをうまく抽象化しているだけ」とも言えますが、この発想を形にした点が非常に面白いと感じました。 一方で、Function callingには弱点もあります。それは、ツール呼び出しの実装がクライアント側に閉じてしまうという点です。つまり、実装が各アプリごとに依存する課題を抱えています。 この課題を解決するために登場したのが MCP (Model Context Protocol) です。 Model Context Protocol( MCP ) Model Context Protocol(MCP) は Claude を開発している Anthropic 社が定義した プロトコル です。 Function calling と同様に、「AI が外部システムと連携して、処理・回答しているように見せる」という点は同じですが、ツール呼び出しの方法そのものを標準化したことが大きな特徴です。 アーキテクチャ は以下のようになります。先ほどのクライアントとなるアプリが、 MCP ホストと MCP サーバに分割された形になります。 Function Callingに比べて少しリソースが増えました。 全体の処理フローは以下のようになり、 MCP サーバがツールの実行を抽象化していることが分かります。 この構成の強みは、 MCP ホスト / MCP クライアント / MCP サーバという役割を標準化したことにあります。 MCP ホスト:1つ以上の MCP クライアントを管理し、LLMと対話しながら処理をする アプリケーション全体 。 MCP クライアント: MCP ホスト内部に存在し、 MCP プロトコル に基づいて MCP サーバと通信を行う コンポーネント 。( MCP ホストから MCP サーバへの呼び出しを抽象化) MCP サーバ:実際に 外部サービスとの連携を行う コンポーネント で、 MCP プロトコル に基づいて MCP クライアントと通信する 。 例えば、サービス提供者が MCP サーバを用意しておけば、利用者はその実装の詳細を意識せず、他の MCP サーバと同じ感覚で利用できます。 HTTP が存在しなかった世界では、サービスごとに独自の プロトコル で API を実装する必要がありました。実際には、HTTP という共通 プロトコル があるからこそ、私たちは意識せずに様々な Web サービスを利用できています。 MCP も、それと同じ文脈でサービスの提供者・使用者の目線で考えると分かりやすいかと思います。 AIエージェント Function Calling や MCP を用いることで、LLM が本来単体では実行できないタスクについても、外部サービス(ツール)による処理の実行や、外部サービスにより取得したデータを文脈として取り込みながら回答を生成できるようになります。 このように、 外部のサービスやデータと連携した処理を基に振る舞う仕組み全体を、一般に AI エージェント と呼びます。 ただし、実態としてはWeb アプリケーションとしてのクライアント・サーバ構成で動いているという点を理解しておくことが重要です。 エージェントの作成( MCP ベース) 前提 ということで、実際に MCP をベースにしたエージェントを実装してみます。 本記事の目的は、 MCP の推奨構成をそのまま再現することではなく、各 コンポーネント がどんな順序で、どんなデータをやり取りしているかを理解することです。 そのため、 公式ドキュメント で扱われているいくつかの要素を省略し、以下の方針で実装を行いました。 目標 エージェントに「( アメリ カの都市名)の直近の天気」を必要とする質問を尋ねると、実際の天気予報 API を基にした回答を返してくれること 実装方針 MCP のバージョン 2025-11-25( Model Context Protocol 、 GitHub ) 機能面 MCP サーバ Prompts / Resources は省略して、Tools のみを実装し、外部 API (天気予報 API )の呼び出しを主に実装 ローカルでHTTPサーバとして起動 MCP クライアント Roots / Sampling / Elicitation は省略し、 MCP サーバに対する Tools の呼び出しを主に実装 MCP ホストにクラスとして内包される MCPホスト UIとして CLI でのstdin / stdoutを使用(ユーザとの入出力は コマンドライン ベース) ローカルで起動 通信面 HTTP ベースでの実装 SSEやセッション管理(stateful)を省略し、ステートレスに実装 認証は実装しない 使用技術 LLM:Claude MCP ホスト / MCP クライアント / MCP サーバー: JavaScript (TypeScript)での実装 実装コードは GitHub で公開しています。詳細が気になる方は mcp-sample を参照してください( mcp 系のライブラリを使用せず自作したこともあり、設計・実装ともに粗い点はご容赦ください)。 実装方針 MCP の公式ドキュメントをただ読むだけで、そのままコードに落とし込むのは難しく感じたため、今回は以下の手順で実装をします。 先に発生するすべてのHTTP通信を書き出し 各通信それぞれの HTTPヘッダ と ボディ( JSON -RPC) を整理 通信内容から逆算して実装。 通信内容の整理 JSON -RPC MCP では JSON-RPC 2.0 がメッセージ形式として採用されています。 以下が JSON -RPC の基本的な形式です。 リクエスト { " jsonrpc ": " 2.0 ", " id ": " number ", " method ": " string ", " params ": { object } } レスポンス { " jsonrpc ": " 2.0 ", " id ": " number ", " result ": { object } } レスポンス(エラー) { " jsonrpc ": " 2.0 ", " id ": " number ", " error ": { " code ": " number ", // -32768 ~ -32000 are reserved, " message ": " string ", " data ": " primitive or structed value " } } 今回はこの JSON -RPC を HTTP リクエストのボディに乗せて送受信する形で実装します。 つまり、HTTP はあくまで通信経路として利用し、実際の処理内容やメソッドの表現は JSON -RPC によって行う構成になります。 TCP の上でHTTPが成り立つように、HTTPのボディの上で JSON -RPC形式によるやり取りを行うイメージです MCP 本来の MCP の通信では、 公式ドキュメントのシーケンス図 に記載されているとおり、Server-Sent Events(SSE)やセッション管理を含む構成が想定されています。 しかし、今回の構成では理解を優先するため、SSE やセッション管理は割愛し、公式ドキュメントの構成を簡略化した以下の形で MCP によるやり取りを行います。 今回はセッションを持たないステートレスな HTTP 通信として実装しているため、通信の終了フェーズを明示的に実装する必要はありません。 今回想定する MCP リクエスト・レスポンス 通信の全体像が把握できたところで、公式ドキュメントに記載の ライフサイクル章 や トランスポート章 を参考にし、各フェーズにおいてどのようなデータが送受信されるのかを整理します。 initialize リクエスト ヘッダ Content-Type: application/json Accept: application/json - ボディ { " jsonrpc ": " 2.0 ", " id ": 1 , " method ": " initialize ", " params ": { " protocolVersion ": " 2025-11-25 ", " capabilities ": {} , " clientInfo ": { " name ": " ExampleClient ", " version ": " 1.0.0 ", " description ": " An example MCP client application " } } } レスポンス ステータス: 200 ボディ { " jsonrpc ": " 2.0 ", " id ": 1 , " result ": { " protocolVersion ": " 2025-11-25 ", " capabilities ": { " tools ": { " listChanged ": false } } , " serverInfo ": { " name ": " ExampleServer ", " version ": " 1.0.0 ", " description ": " An example MCP server providing tools and resources " } } } notifications/initialized リクエスト ヘッダ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 - ボディ { " jsonrpc ": " 2.0 ", " method ": " notifications/initialized " } レスポンス ステータス: 202 ボディ:空 tools/list リクエスト ヘッダ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 - ボディ { " jsonrpc ": " 2.0 ", " id ": 2 , " method ": " tools/list ", " params ": {} } レスポンス ステータス: 200 ボディ { " jsonrpc ": " 2.0 ", " id ": 2 , " result ": { " tools ": [ { " name ": " get_weather ", " description ": " Get current weather information for a location ", " inputSchema ": { " type ": " object ", " properties ": { " latitude ": { " type ": " number " } , " longitude ": { " type ": " number " } } , " required ": [ " latitude ", " longitude " ] } } ] } } tools/call リクエスト ヘッダ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 - ボディ { " jsonrpc ": " 2.0 ", " id ": 3 , " method ": " tools/call ", " params ": { " name ": " get_weather ", " arguments ": { " longitude ": " -94.0 ", " latitude ": " 40.71 " } } } レスポンス ステータス: 200 ボディ { " jsonrpc ": " 2.0 ", " id ": 3 , " result ": { " content ": [ { " type ": " text ", " text ": " Current weather in New York: \n Temperature: 72°F \n Conditions: Partly cloudy " } ] } } このようにデータの流れをイメージできたため、あとはこの流れ通りに動作するよう MCP ホスト / MCP クライアント / MCP サーバ を実装していきました。 その結果が、先ほど紹介した リポジトリ の状態となります。 実装を進める中で、特に詰まった点は以下の2点です。 LLMが JSON を返却する際、どうしてもコードブロックを含めてしまう点 システムプロ ンプトによる制御では完全に回避できなかったため、文字列を整形する処理を実装し対処。 「直接天気を調べて」とは指示せず、「天気をもとにアド バイス してほしい」といった依頼をした場合に、LLMがツールを選択せず、「その情報を調べてもよいですか?」と確認してくるケースがあった点 「回答に必要な処理が存在する場合は、必ずツール呼び出しのみを返すこと」を明示したプロンプトで対処。 検証 本章では、前章で整理した通信設計が、実際の実装でもその通りに動作しているかを確認します。 初めにローカル環境で MCP サーバーを起動します。 PS C:\Users\katsu\Documents\github\mcp-sample\server> npm run start > mcp-server@1.0.0 start > node dist/index.js MCP server listening on http://127.0.0.1:8080/mcp 次に、同じくローカル環境で MCP ホストを起動します( MCP ホストはプロセス内部で MCP クライアントクラスの インスタンス を生成・管理しています)。 起動後はユーザーの入力を促すメッセージが出るので、依頼文(ユーザプロンプト)を入力します。 PS C:\Users\katsu\Documents\github\mcp-sample\host> npm run start > mcp-host@1.0.0 start > node --env-file=.env dist/index.js ################################################################## ------------ initialize request ------------ Content-Type: application/json Accept: application/json { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": { "name": "weather", "version": "0.1.0", "description": "Weather MCP Client with Stateless and Streamable HTTP and non SSE" } } } ------------ initialize response ------------ status: 200 { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": { "listChanged": false } }, "serverInfo": { "name": "weather", "version": "0.1.0", "description": "Sample MCP Server with Stateless and Streamable HTTP and non SSE" } } } ################################################################## ------------ notifications/initialized request ------------ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 { "jsonrpc": "2.0", "method": "notifications/initialized" } ------------ notifications/initialized response (no body) ------------ status: 202 ################################################################## ------------ tools/list request ------------ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 { "jsonrpc": "2.0", "id": 2, "method": "tools/list" } ------------ tools/list response ------------ status: 200 { "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "get_weather", "description": "Get weather forecast for US coordinates using NWS API.", "inputSchema": { "type": "object", "properties": { "latitude": { "type": "number" }, "longitude": { "type": "number" } }, "required": [ "latitude", "longitude" ] } } ] } } ################################################################## # 質問してみましょう 明日夜のニューヨークの天気を基に、服装のアドバイスをしてください。今は半袖短パンを想定しています。 ################################################################## ------------ LLM request ------------ Content-Type: application/json x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX anthropic-version: 2023-06-01 { "model": "claude-sonnet-4-5", "max_tokens": 1000, "system": "\nYou are a tool-routing assistant.\n\nABSOLUTE RULES:\n- You MUST respond in Japanese only.\n- Output MUST be a single valid JSON object.\n- Do NOT use Markdown.\n- Do NOT use code fences or triple backticks.\n- The first character MUST be \"{\" and the last character MUST be \"}\".\n- Do NOT include any text outside the JSON object.\n", "messages": [ { "role": "user", "content": "\nYou will be given:\n1) A list of available tools (JSON)\n2) A user request\n\nDecide whether a tool is needed.\n\nRules:\n- Use a tool only if it materially improves correctness or usefulness.\n- If multiple tools could work, pick exactly ONE.\n- Never invent tools or parameters.\n- If required parameters are missing, ask ONE concise follow-up question.\n- If answering accurately to user request requires external or real-world data, you MUST select the appropriate tool and make a tool_call.\n\nOutput format (JSON only):\n\nTool call:\n{\n \"type\": \"tool_call\",\n \"tool\": \"<tool_name>\",\n \"arguments\": { },\n \"reason\": \"<short reason>\"\n}\n\nDirect answer:\n{\n \"type\": \"direct_answer\",\n \"answer\": \"<answer>\",\n \"reason\": \"<short reason>\"\n}\n\nTools (JSON):\n[\n {\n \"name\": \"get_weather\",\n \"description\": \"Get weather forecast for US coordinates using NWS API.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"latitude\": {\n \"type\": \"number\"\n },\n \"longitude\": {\n \"type\": \"number\"\n }\n },\n \"required\": [\n \"latitude\",\n \"longitude\"\n ]\n }\n }\n]\n\nUser request:\n明日夜のニューヨークの天気を基に、服装のアドバイスをしてください。今は半袖短パンを想定しています。\n" } ] } ------------ LLM response ------------ status : 200 { "model": "claude-sonnet-4-5-20250929", "id": "msg_0144BrLAY9HUWMcHmgopBxpp", "type": "message", "role": "assistant", "content": [ { "type": "text", "text": "```json\n{\n \"type\": \"tool_call\",\n \"tool\": \"get_weather\",\n \"arguments\": {\n \"latitude\": 40.7128,\n \"longitude\": -74.0060\n },\n \"reason\": \"ニューヨークの明日夜の天気予報を取得して、適切な服装アドバイスを提供するため\"\n}\n```" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 481, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 104, "service_tier": "standard" } } ################################################################## ------------ tools/call request ------------ Content-Type: application/json Accept: application/json MCP-Protocol-Version: 2025-11-25 { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "get_weather", "arguments": { "latitude": 40.7128, "longitude": -74.006 } } } ------------ tools/call response ------------ status: 200 { "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": "Forecast for 40.7128, -74.006:\n\nTonight:\nTemperature: 13°F\nWind: 14 to 22 mph N\nMostly Cloudy\n---\nSunday:\nTemperature: 25°F\nWind: 17 to 22 mph N\nMostly Cloudy\n---\nSunday Night:\nTemperature: 16°F\nWind: 14 to 17 mph NW\nMostly Cloudy\n---\nMonday:\nTemperature: 32°F\nWind: 14 mph NW\nSunny\n---\nMonday Night:\nTemperature: 16°F\nWind: 8 to 12 mph NW\nMostly Clear\n---\nTuesday:\nTemperature: 31°F\nWind: 9 mph W\nSunny\n---\nTuesday Night:\nTemperature: 22°F\nWind: 6 to 10 mph W\nPartly Cloudy\n---\nWednesday:\nTemperature: 30°F\nWind: 9 mph NW\nMostly Sunny\n---\nWednesday Night:\nTemperature: 17°F\nWind: 10 mph NW\nMostly Clear\n---\nThursday:\nTemperature: 29°F\nWind: 10 mph NW\nSunny\n---\nThursday Night:\nTemperature: 15°F\nWind: 10 mph NW\nPartly Cloudy\n---\nFriday:\nTemperature: 30°F\nWind: 8 to 12 mph W\nPartly Sunny then Slight Chance Light Snow\n---\nFriday Night:\nTemperature: 18°F\nWind: 12 to 15 mph W\nChance Light Snow\n---\nSaturday:\nTemperature: 22°F\nWind: 16 to 21 mph NW\nChance Light Snow then Mostly Sunny\n---" } ] } } ################################################################## ------------ LLM request ------------ Content-Type: application/json x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX anthropic-version: 2023-06-01 { "model": "claude-sonnet-4-5", "max_tokens": 1000, "system": "\nYou are a Japanese assistant.\n\nABSOLUTE RULES:\n- You MUST respond in Japanese only.\n", "messages": [ { "role": "user", "content": "\nYou will be given:\n1) A list of API result\n2) A user request\n\nAnswer user request with API result.\nDo not mention that you are referring to the API results.\n\nAPI Result (JSON):\n{\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Forecast for 40.7128, -74.006:\\n\\nTonight:\\nTemperature: 13°F\\nWind: 14 to 22 mph N\\nMostly Cloudy\\n---\\nSunday:\\nTemperature: 25°F\\nWind: 17 to 22 mph N\\nMostly Cloudy\\n---\\nSunday Night:\\nTemperature: 16°F\\nWind: 14 to 17 mph NW\\nMostly Cloudy\\n---\\nMonday:\\nTemperature: 32°F\\nWind: 14 mph NW\\nSunny\\n---\\nMonday Night:\\nTemperature: 16°F\\nWind: 8 to 12 mph NW\\nMostly Clear\\n---\\nTuesday:\\nTemperature: 31°F\\nWind: 9 mph W\\nSunny\\n---\\nTuesday Night:\\nTemperature: 22°F\\nWind: 6 to 10 mph W\\nPartly Cloudy\\n---\\nWednesday:\\nTemperature: 30°F\\nWind: 9 mph NW\\nMostly Sunny\\n---\\nWednesday Night:\\nTemperature: 17°F\\nWind: 10 mph NW\\nMostly Clear\\n---\\nThursday:\\nTemperature: 29°F\\nWind: 10 mph NW\\nSunny\\n---\\nThursday Night:\\nTemperature: 15°F\\nWind: 10 mph NW\\nPartly Cloudy\\n---\\nFriday:\\nTemperature: 30°F\\nWind: 8 to 12 mph W\\nPartly Sunny then Slight Chance Light Snow\\n---\\nFriday Night:\\nTemperature: 18°F\\nWind: 12 to 15 mph W\\nChance Light Snow\\n---\\nSaturday:\\nTemperature: 22°F\\nWind: 16 to 21 mph NW\\nChance Light Snow then Mostly Sunny\\n---\"\n }\n ]\n}\n\nUser request:\n明日夜のニューヨークの天気を基に、服装のアドバイスをして ください。今は半袖短パンを想定しています。\n" } ] } ------------ LLM response ------------ status : 200 { "model": "claude-sonnet-4-5-20250929", "id": "msg_011ykwGSo5qRzEHTQpD9UV9G", "type": "message", "role": "assistant", "content": [ { "type": "text", "text": "明日夜のニューヨークの天気は以下の通りです:\n\n**日曜日の夜**\n- 気温:16°F(約-9°C)\n- 風:14〜17 mph 北西 の風\n- 天候:ほぼ曇り\n\n**服装のアドバイス:**\n\n半袖短パンは**絶対に避けてください**。この気温では命に関わる危険性があり ます。\n\n以下の装備が必要です:\n\n1. **厚手の冬用コート**(ダウンジャケットやウールコートなど)\n2. **重ね着**(長袖のイン ナー、セーター、フリースなど複数枚)\n3. **厚手の長ズボン**(ジーンズやウールパンツに加えて、可能であればレギンスなどの下着も)\n4. **防寒アクセサリー**:\n - 帽子(耳まで覆えるもの)\n - 手袋(厚手のもの)\n - マフラー\n - 厚手の靴下\n5. ** 防風性のある上着**(風が強いため)\n\n氷点下の極寒の天候ですので、肌の露出は最小限に抑え、しっかりと防寒対策をしてお出かけく ださい。" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 713, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 382, "service_tier": "standard" } } ################################################################## 明日夜のニューヨークの天気は以下の通りです: **日曜日の夜** - 気温:16°F(約-9°C) - 風:14〜17 mph 北西の風 - 天候:ほぼ曇り **服装のアドバイス:** 半袖短パンは**絶対に避けてください**。この気温では命に関わる危険性があります。 以下の装備が必要です: 1. **厚手の冬用コート**(ダウンジャケットやウールコートなど) 2. **重ね着**(長袖のインナー、セーター、フリースなど複数枚) 3. **厚手の長ズボン**(ジーンズやウールパンツに加えて、可能であればレギンスなどの下着も) 4. **防寒アクセサリー**: - 帽子(耳まで覆えるもの) - 手袋(厚手のもの) - マフラー - 厚手の靴下 5. **防風性のある上着**(風が強いため) 氷点下の極寒の天候ですので、肌の露出は最小限に抑え、しっかりと防寒対策をしてお出かけください。 上記の結果から、想定通りの通信が発生し、回答も外部サービスを使用した結果が含まれていることが確認できました。 まとめ 外部のサービスやデータを用いて、あたかもLLM自身が処理を行ってタスクを実施し、回答しているように見せる仕組み全体を、AIエージェントと呼びます。 その中で、外部サービス(ツール)呼び出しの形式を標準化したものがFunction Callingや MCP です。 特に MCP で定義された コンポーネント の役割は以下のように分類できます。 MCP ホスト:1つ以上の MCP クライアントを管理し、LLMと対話しながら処理をする アプリケーション全体 。 MCP クライアント: MCP ホスト内部に存在し、 MCP プロトコル に基づいて MCP サーバと通信を行う コンポーネント 。 MCP サーバ:実際に 外部サービスとの連携を行う コンポーネント で、 MCP プロトコル に基づいて MCP クライアントと通信する 。 実体がWebアプリと同じようなクライアント・サーバ構成である以上、 MCP サーバの配置場所や実行させる処理内容によっては、セキュリティや監査の扱いが非常に重要になります。例えば、ローカルで実行した MCP サーバが意図せず内部ファイルに干渉してしまうケースや、外部に公開した MCP サーバがセキュリティ攻撃にさらされる可能性などが考えられます。 これは、これまでの Web サービスと本質的には同じであるため、適切な アーキテクチャ 設計と セキュリティポリシー の策定が、今後ますます定式化されていく領域だと感じています。 おわりに 佐藤さんの助けもあり、これまで実態がつかめていなかった「AI エージェント」という概念を掴みつつ、実装しながら理解を深められたのはとても面白い体験でした。 このエージェントをさらに抽象化した仕組みとして Strands が登場しているので、次はそちらの実装を検証してみたいと思います。 今後も抽象化のレイヤーはどんどん高まっていくはずなので、流れに取り残されないためにも、AI 関連技術の基礎知識を学びつつ、使用者側としても継続的に研鑽していきます。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sakae.katsuto レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
こんにちは、 電通 総研 XI本部 サイバーセキュリティテクノロ ジー センターの櫻井です。 本記事では 代表的な クラウド サービスプロバイダ(以降、CSPと略)である AWS ( Amazon Web Services )、 Microsoft Azure、 Google Cloudの最上位アーキテクト資格に関する比較を紹介します。 なお、本記事でご紹介する資格の情報は2026年1月時点のものとなります。 アーキテクト資格の種類 筆者の経歴 チャレンジのおススメ順 各CSP最上位資格で共通で問われる内容 各CSP最上位資格の特色 まとめ 最後に アーキテクト資格の種類 各CSP資格群はFoundational、Associate、Professiona(Expert)lの3段階に分かれており、受験者の対象と試験の大枠としては以下を想定しています。 Foundational 対象: クラウド 初心者やIT未経験者。また、基礎知識を理解したい営業・ マーケティング ・プロジェクトマネジメントサイドの担当者 試験の大枠: クラウド 自体の理解、大枠のサービス概要、 クラウド 利用料計算の理解 Associate 対象:ソリューション設計に1年以上の実務経験のあるITエンジニア 試験の大枠:設計に用いるソリューションの基本理解 Professional(Expert) 対象:ソリューション設計に理解のある、実務経験のあるITエンジニア 試験の大枠:設計時のソリューションベストプラクティスの理解、適切なサービス組み合わせの理解 それぞれの資格群の名称(略称)に関しては以下の表のとおりです。 Grade AWS Azure Google Foundational Cloud Practitioner(CLF) Azure Fundamentals(AZ-900) Cloud Digital Leader(CDL) Associate Solutions Architect Associate(SAA) Azure Administrator(AZ-104) Associate CloudEngineer(ACE) Professional(Expert) Solutions Architect Professional(SAP) Azure SolutionsArchitect Expert(AZ-305) Professional Cloud Architect (PCA) 以降は、Professional、Expert資格にフォーカスして説明を行います。 筆者の経歴 (テンプレにはなりますが、)オンプレ& クラウド 、ネットワーク&セキュリティのごちゃまぜエンジニアです。担務領域に関しては特にこだわりなくなんとなく興味を持った資格は取得してます。(※ My credly ) 電通 総研ではサイバーセキュリティテクノロ ジー センターで主にセキュリティサービス設計、運用やマルチ クラウド のセキュリティ設計レビューに従事しています。 また、筆者の各CSPの経験歴としては以下となります。 AWS 7年程度、メインは クラウド インフラ設計ガッツリです。オンプレ接続のために用いる クラウド リソース管理や運用/監視、リソースデプロイの自動化がメインタスクでした。 電通 総研に入社後はアプリまで含めた AWS 全般のセキュリティ設計レビューを行っています。 Microsoft Azure 5年程度、メインは クラウド マイグレーション に関わるインフラ設計、ID( Microsoft Entra)の設計・運用でした。 電通 総研に入社後は Microsoft defender for cloudをはじめとしたセキュリティサービスの調査や社内向けサービス設計に従事しています。 Google Cloud 3年程度、以前はBigQueryやApp Engineを触っていた程度です。 電通 総研に入社後は Google Cloudログサービスの調査・検証の実施、Firebaseを利用したWebサイト構築のセキュリティ設計レビューにも参加しています。 チャレンジのおススメ順 最初に、3種すべてを取得するならばの前提で筆者の考える難易度を記載します。各試験で問われる範囲と出題形式、傾向、学習コンテンツを踏まえて、以下の順が望ましいと考えています。 Google Cloud(Professional Cloud Architect) AWS (Solutions Architect Professional) Microsoft Azure(Azure Solutions Architect Expert) はい、見事に筆者のチャレンジ順とは逆です。 単一CSPでガッツリ設計をやっている方は専門の分野を突き詰めるのが望ましいのであまり難易度比較に意味はありませんが、基本は下に行くほど難しい試験である(事前準備が必要である)と筆者は考えています。 各CSP最上位資格で共通で問われる内容 最上位資格の試験について、共通で必要とされる内容を簡単に整理します。 対象CSPのサービス全般に対する理解① 対象のサービス種類(試験に含まれるサービスの範囲) CSPの試験ガイドで具体的に対象と記載されているサービスについてすべてが対象となります。 例として、 AWS (Solutions Architect Professional)であれば 試験ガイド の「範囲内の AWS のサービスと機能」となっているサービスはすべて対象です。 各試験によってどこまでを対象とするかは異なりますが、ITサービス構築のために クラウド インフラを利用するだけではなじみのないAIやデータ分析のサービスも含まれますので、普段利用していないサービスでも用途等は理解しておく必要があります。 対象CSPのサービス全般に対する理解② サービスの単位 「 AWS における VPC はリージョナルサービスである」、「 Google Cloudにおける VPC はグロー バルサ ービスである」という比較に代表されるように クラウド 設計におけるサービスがどこまでの範囲で展開可能かを明確に覚えておきましょう。 対象としているCSPにフォーカスしてきちんと学習しましょう。実務目線でも設計や提案の際に理解できていないと致命的です。 例えば、複数リージョンに渡る負荷分散の設計方法は各CSP毎に大きく異なる等ですね。 (この場合、グローバルロードバランシングの種類をちゃんと理解しているか?になります) 設計の理解① 設定パラメータ 利用ケースが多いサービス(コンピュート、コンテナ、アプリケーション統合)については細かい設定が問われる問題が出題されます。この点に関しては明確に実務経験があった方が望ましいといえます。 対象サービスのパラメータを設定するのに、どういった方法があるのかも含めて押さえておくとよいです。 1.「環境を作りなおす必要があるのか?」もしくは「既存の環境のままで設定変更が可能か?」 2.「マネジメントコンソールから設定できるか?」もしくは「 API や CLI からのみ設定できるのか?」 設計の理解② ソリューションの選定、ソリューションの組み合わせ比較 出題内容としては、具体的なソリューションを一意に特定するケースに加えて、必要な要件に対して複数のソリューションの組み合わせを比較した上で最も適したものを選択するケースも存在します。 いわゆるベストプラクティスを選択する必要がありますので、 AWS の場合は Black Belt の様な公式のソリューションガイドを参考に学習するとよいです。 筆者の経験でも、顧客の特殊要件を踏まえ意図的にベストプラクティスを選択しなかった経験もありますし、当時と比較して新しいサービスがリリースされている可能性もありますので、最低限は確認しましょう。 各CSP最上位資格の特色 それぞれの試験の概要と筆者の試験自体に関する印象、筆者の主観的分析を紹介します。 AWS Solutions Architect Professional(SAP) 問題数:75問、時間:180分 試験後の印象 とにかく問題数が多い。そして問題文、選択肢の文章が比較的長いので持続的な集中力を求められる。 AWS のProfessional、Security分野の試験では共通している点かと思います。 主観的分析 AWS に関してはCSPがリリースしているサービスの種類が他の2社と比べて多いです。(200個以上) 時間をかけて理解する必要があり、初学者は数に圧倒されると思います。 問題そのものは取り組みやすいですが、文章量の理解に時間を要するため練習問題等で慣れておく必要があります。 SAPを含め、 AWS 資格のコンテンツは数多く存在するためこの点は学習者にプラスです。 Azure Solutions Architect Expert(AZ-305) 問題数: 不定 (概ね50問前後)、時間:120分 試験後の印象 設計の組み合わせやサービスの詳細など細かい部分まで回答を求められる。(理由は後述) 試験への慣れが必要。3パートに分かれており、2パートは選択を終了したら戻ることができない。 本試験はダイレクトに「 クラウド インフラ設計」の理解を求められる。 Microsoft Webページ の記載の通り、複数系統に分かれているためです。 主観的分析 CSP3種試験の中では難易度は最も高いと思います。 設計にフォーカスしているため、サービス単体を理解しているだけではNGなケースが多いです。 Microsoft AzureのAssociate以上の試験に関しては Microsoft Learn でコンテンツを検索しながら回答が可能です。 その点に起因するかはわかりませんが、事前の学習では手が回らないような調べないとほぼ回答できないような細かいサービス詳細に関する問題も一部出題されます。(この要件を満たすライセンスの種類は?というような問題) 購入した問題集の設問で構わないのでこんな感じで検索したら出てきそうというのを Microsoft Learnで事前に確認しておくとよいです。関係のないコンテンツも検索結果に含まれてしまい、思いのほか抽出が難しいです。 40問程度の大問パート以外にシナリオ問題とケース問題で合計3パート以上で構成され、パート間で戻ることはできないため、時間配分を間違わないようにしましょう。(筆者はAZ-305以外の試験で勘違いし、ドボンしています) 市販の学習用コンテンツは最低限は存在します。 Microsoft Azureは Microsoft Learn以外にも公式の トレーニングコース が充実しているため、集中的に学習したいものは登録しましょう。 開催日程が決まっていますが、 Microsoft Virtual Training Days で無料で受講できるハンズオンや講習も存在するので確認しましょう。 Google Cloud Professional Cloud Architect(PCA) 問題数: 不定 (50問以上、60問未満)、時間:120分 試験後の印象 一定以上の知識は必要だが、複雑な問題はあまり出題されない。 サービスの種類と設計をバランスよく理解しておくことが求められる。 数問で構成されるケース問題が複数出題される。全く関係ない導入文も含まれるので選択をミスらないようにしましょう。 試験中に問題言語を英語に切り替えられない。筆者だけかもしれませんが、細かいオプション説明を英字で確認しようと切り替えても日本語のままでした。 主観的分析 個人的な忖度ではないですが、試験自体は一番取り組みやすいと感じました。試験時間と問題文、文章量がマッチしていると思います。 ACEとは別にProfessionalとなっているので簡単ではないですが、適度な難易度の問題が適量出題されるイメージです。 難点は学習コンテンツが少ないことかと思います。PCAに限っては市販の書籍は見当たりませんでした。 Udemyのコンテンツで日本語対応のもの(※1、※2)がありますのでこちらをお勧めします。 筆者の感覚では既にACEを取得済みであれば、Udemyのコンテンツを繰り返すだけでも合格することは十分可能だと思います。(自信がない方はラボ演習をしましょう) 所属する会社によって変わりますが、 Google Cloudに関する招待制ト レーニン グプログラムも存在します。無料でラボコンテンツが一定期間利用できるプログラムです。 ※1 https://www.udemy.com/course/google-cloud-professional-cloud-architectpca/ ※2 https://www.udemy.com/course/google-cloud-professional-cloud-architect-i/ まとめ 以上を踏まえて、それぞれの試験を試験自体の難易度(筆者の感覚)、試験への慣れの必要性、学習コンテンツの豊富さで整理したいと思います。(勉強時間は各個人によるので含めません) 評価観点 AWS (SAP) Azure(AZ-305) Google (PCA) 試験自体の難易度(筆者感覚) 普通 (サービスの数は多いが基本的なサービス組み合わせ、設計等を理解できていれば問題なし。) 難しい (ひとえに難しい。細かいところまで出題される。) 比較的取り組みやすい (サービスの種類を覚え、組み合わせや設計が理解できていれば問題なし。) 試験への慣れの必要性 ある程度必要 (問題数、文章量への慣れが必要。) 慣れが必要 (最終的にExpertとなるためにはAZ-104の合格も併せて必要なので先に受験し訓練しましょう。) ほぼ不要 (初チャレンジでもケース問題の切り替えに注意すれば問題はないと思います。) 学習コンテンツの豊富さ 豊富 (ググっても、 Amazon で検索しても、Udemyでも大量にあります。むしろ精査が難しいレベル。) 普通 (発売されているものは少ないですが、良書が多いので一度は目を通しましょう。) 少ない (市販書籍がないのでUdemy等を活用しましょう。) 表からチャレンジ順の理由がご理解いただけたかと思いますが、AZ-305に関しては、事前に準備しないといけないもの(知識も当然だが、問題形式や回答方法の予習)が多く、3種横並びで見た場合で最初にチャレンジするのは難易度が高いという評価です。ただ、 クラウド インフラ設計という点からは逸脱している内容ではないので、学んでおくことでプラスになる内容であると思います。 最後に 読了いただきありがとうございました。 筆者は事前の積み重ねもあって3種のArchitect最上位資格を約半年間で取得できました。しかし、エンジニア(もしくは コンサルタント )としてのゴールは資格を取得することではありません。 基本は クラウド への理解を深めるためにCSPの提供するラボ等を活用し、じっくり学習することをお勧めします。 今後も クラウド 、セキュリティに関する投稿を行っていきますのでよろしくお願いします。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sakurai.ryo レビュー: @kobayashi.hinami ( Shodo で執筆されました )
はじめに 金融IT本部 2年目の坂江 克斗です。 業務にてアウトバウンドセキュリティを考えるタイミングがあったため、本記事を書きました。 初学者の視点で疑問に感じる部分も含め、アウトバウンドセキュリティ全体の基本的な概念を解説できればと思います。 はじめに アウトバウンドセキュリティの概要 AWSのアウトバウンドセキュリティサービス Route53 Resolver DNS Firewall Network Firewall Network Firewall Proxy アウトバウンドセキュリティの設計方針 Deny List (拒否リスト) Strict Allow List (厳格な許可リスト) Generous Allow List (寛容な許可リスト) まとめ おわりに アウトバウンドセキュリティの概要 Webアプリケーション開発においては、 SQLインジェクション や XSS 、DDoS など、インバウンド通信に対するセキュリティを意識する機会が多い一方で、アウトバウンド通信のセキュリティについては、あまり意識されないことが多いように感じます。 しかし、 AWS re:Inforce 2025 - Outbound network controls made easy でも述べられているとおり、アウトバウンドセキュリティは 防御(Prevention) と 可視性(Visibility) の両面において、近年ますます重要になっています。 防御の観点では、以下のケースが挙げられます。 マルウェア に感染したサーバから外部の悪意あるエンドポイントへの通信を防止。 ソフトウェア サプライチェーン において、意図しない参照先や汚染された OSS へのアクセスを制御。 直接的な通信遮断に至らない場合であっても、 不正アクセス や異常な通信パターンを検知する機会の増加。 可視性の観点では、以下のケースが挙げられます。 NAT Gateway と VPC エンドポイントの使い分けのような設定ミスの検知 SOC(Security Operations Center)における分析・調査のためのセキュリティログや分析材料の充実。 よって、防御と可視性の両面において、アウトバウンドセキュリティが重要であることが分かります。 アウトバウンドセキュリティは大枠として、 DNS ベース、パケットヘッダ(3層-7層)ベース、ボディベースでのフィルタリング が中心となり、組み合わせることで多層防御を形成します。(ただし、ボディの解析は処理負荷があるため、セキュリティと性能のバランスを考慮した設定が必要となると思われます) AWS では以下の表に示すアウトバウンドセキュリティ対策が挙げられ、中でもマネージドに提供されているものとして Route 53 Resolver DNS Firewall ( DNS ベース) および Network Firewall (L3 - L7パケットヘッダベース) 、 AWS Network Firewall Proxy (L3 - L7パケットヘッダベース) が存在します。 レイヤ 制御観点 AWS サービス 料金 特徴 DNS (名前解決) ドメイン 名 Route53 Resolver DNS Firewall 低( DNS クエリ) DNS クエリを制御。ローカルでの名前解決やIP直打ち、独自 DNS 使用の通信は防御不可。 L3–4 IP / Port SG / NACL 無料 ファイアウォール 。ネットワークセキュリティのベースとなるフィルタリング。 L3–7 IP / Port, TCP ヘッダ / HTTP ヘッダ / TLS ヘッダ Network Firewall , GatewayLoadBalancer + サードパーティ ソフト 高(AZ毎の常時稼働エンドポイント+処理量) 柔軟な制御が可能で、マネージドにも運用可能。 DNS + L3–7 IP / Port, HTTP ヘッダ / TLS ヘッダ AWS Network Firewall Proxy 調査中 マネージドな フォワ ードプロキシサービス。現状はパブリックプレビュー中で無料使用可能。 AWS のアウトバウンドセキュリティサービス Route53 Resolver DNS Firewall Route 53 Resolver は、 VPC のCIDRブロックにおける +2 の 予約済み IPアドレス で提供されるマネージドリゾルバ です。例えば、10.0.0.0/16の VPC では、10.0.0.2がRoute 53 Resolver の IP アドレスとして割り当てられます。この IPアドレス 自体は VPC 内で共通ですが、実際に DNS クエリを処理するリゾルバのエンドポイントはAZごとに配置されており、高可用性を考慮した構成となっています VPC 内のアプリケーションからこの Route 53 Resolver を経由して送信される アウトバウンド DNS クエリに対して VPC 単位でフィルタリング を行うサービスが、Route 53 Resolver Firewall となります。 ドメイン リストを基に ALLOW / BLOCK / ALERT を設定することで、 特定の ドメイン 群に対する通信を許可・拒否、またはアラートとして検知 することができます。 また、BLOCK の場合にはカスタム DNS レスポンスを設定することで、CNAME を用いて特定の ドメイン への名前解決に誘導することも可能です。 さらに、CNAME による変換後の ドメイン についても検証対象とするかを設定できるため、Allow List / Deny List のいずれにおいても柔軟な構成が可能です。 CNAME による変換後の ドメイン 検証 Deny List 構成では、CNAME 変換後の ドメイン までを検証対象とすることで、悪質な ドメイン への名前解決を防止できます。 Allow List 構成において CNAME 変換後の ドメイン まで全て許可対象とする場合、管理すべき ドメイン 数が増加し、運用負荷が高くなる傾向があります。そのため、運用方針によっては CNAME 変換後の検証を行わない設定とし、管理コストを抑える選択肢も考えられます。 Network Firewall AWS Network Firewall は、 AWS マネージドの ファイアウォール および IDS/IPS サービスであり、インバウンド / アウトバウンドに対して サブネット単位で設定したルール(ステートレスルール / ステートフルルール)に基づいて、パケットを ALLOW / DROP / ALERT に分類し制御 します。 ( 1つのNetwork Firewallを複数のVPCで共有できる機能 も公開されています) ステートレスルールは、全てのパケットを個別に評価して判定を行います。一方、ステートフルルール(Suricata互換)では、 TCP や TLS 、HTTP などの一連の通信(コネクション)を単位として制御することが可能です。 例えば、ステートレスルールでは TCP の 3 ウェイハンドシェイクを構成する各パケットをそれぞれ独立してフィルタリングしますが、ステートフルルールでは TCP の通信全体をひとまとまりとして扱い、コネクションの状態(established か否か)を追跡しながらフィルタリングを行います。 Network Firewall では、上図に示すようにルートテーブルの設定によって、 検査対象となる通信の往路・復路の両方を同一の Firewall Endpoint に通過させる ことで、ステートフルな 通信制 御を実現しています。( 非対称ルーティングの回避 ) これは、各 Firewall Endpoint が通信状態を個別に管理しており、 TCP や HTTP などのハンドシェイクにおいてリクエストとレスポンスの両方を確認することで、通信が正しく確立されたものであるかを判断する必要があるためと考えられます。 今回は比較的シンプルな構成を例に紹介しましたが、 Network Firewallのデプロイモデル には複数のパターンが存在します。 実際の設計においては、通信要件や可用性、運用負荷を考慮したうえで、適切なモデルを選択することが重要です。 Network Firewall と Gateway Load Balancer(GWLB) Network Firewall は、内部的に Gateway Load Balancer と連携して動作するマネージドサービスです。 Gateway Load Balancer は、GENEVE プロトコル を用いて サードパーティ 製のインスペクションサービスへ トラフィック をトンネリングする仕組みを提供するもので、インバウンド / アウトバウンド のセキュリティ対策に利用できます。 一方で、Network Firewall を利用することで、こうしたインスペクション機能そのものを AWS マネージドサービスとして構成できるため、構築・運用負荷を大きく下げることができます。 Network Firewall Proxy 2025年11月末にマネージドな フォワ ードプロキシサービスである AWS Network Firewall Proxy が発表されました。現在はパブリックプレビュー中のため、 オハイオ (us-west-2)リージョン限定で利用可能となっています。 厳密にはNetwork Firewall の機能の一部に当たりますが、役割の明確化のために分けて説明いたします。 AWS Network Firewall Proxy はいわゆるWebプロキシのように、 HTTP / HTTPS 通信専用のマネージドな フォワ ードプロキシ であり、Route 53 Resolver DNS Firewall や Network Firewall と同様にアウトバウンドセキュリティに適した AWS サービスとなります。 以下の構成( Architecture overview より引用)により使用可能であり、NAT ゲートウェイ と統合して動作するものとなっています。 本サービスでは、以下の図( Securing Egress Architectures with Network Firewall Proxy より引用)に示すように 名前解決前(Pre- DNS )、名前解決後の接続先サーバへのHTTPリクエスト(Pre-request)、HTTPレスポンス(Post-response)の3段階 で評価を行い、名前解決に使用する ドメイン 名からL3-L7(IP / Port, HTTP / TLS ヘッダ) までの情報を評価に使用可能となります。 詳細な挙動や設計上の注意点については、 こちらの記事 で紹介しておりますので、是非ご参照ください。 アウトバウンドセキュリティの設計方針 セキュリティ全体としては、セキュリティ効果と運用負荷のバランスを踏まえ、以下の 3 つの方針が重要となります。 多層防御を活用する 導入コストやリスクが低く、効果の高い対策から着手する 最初から完璧を目指さず、運用を通じて継続的に改善する アウトバウンドセキュリティにおける具体的な設計方針として、 AWS re:Inforce 2025 - Outbound network controls made easy で紹介されている考え方に基づき、 Deny List / Strict Allow List / Generous Allow List の 3 つの方針を紹介します。 Deny List (拒否リスト) 明示的に拒否する対象のみを定義する方式であり、以下のようなメリットがあります。 導入が容易 AWS ではマネージドな拒否リストを利用可能 拒否された通信はログとして記録されるため、通信内容の分析や調査が可能(なお、ALERT を挟んだ Allow List 構成においても、同様に情報を取得可能) 一方で、悪質な宛先は無数に存在するため、全てを網羅的に拒否リストへ登録することは現実的ではありません。 そのため、未知の通信先が許可されてしまう可能性が残り、 Allow List と比較するとリスク低減効果は限定的 である点がデメリットとなります。 2025年に公開された AWS Network Firewall with Active Threat Defense では、 AWS がグローバルに展開する独自の ハニーポット から収集した脅威情報を活用し、既知の悪意あるインフラに対する拒否ルールを動的に更新することが可能となっています。 これにより、従来の静的な Deny List が抱えていた運用負荷や追従性の課題を一定程度緩和できます。 また、 AWS Network Firewall の 地理的IPフィルタリング も、ルール管理を簡素化する観点から、同様に運用負荷の軽減に寄与する機能と言えます。 Strict Allow List (厳格な許可リスト) 明示的に許可する対象のみを定義する方式であり、以下のようなメリットがあります。 リスク低減効果が最も高い 通信先の追加・変更・削除時に、設計レビューや承認といった定められた運用手順を強制しやすい 一方、デメリットとしては以下の内容が挙げられます。 導入・運用コストが高い (アプリが依存する OSS 、 OSS 自体のアクセス先等、管理対象が多数) 運用コストの高さから、正当な通信の許可が抜けてしまい拒否するリスクがある 許可すべき通信が限定的なサービスにおいては非常に有効な手段となりますが、多様な外部サービスや API と連携する必要があるシステムに対しては導入コストも高く相性が悪い方式となります。 Generous Allow List (寛容な許可リスト) Allow List のリスク低減と Deny List の運用コストの低さを組み合わせた 、最も推奨される方針となります。 AWS Network Firewall においては、主に以下の方針で Deny List および Allow List を構成します。 Deny List AWS マネージドルール 既知の悪意のある宛先を拒否 高リスクな地理的 IP 高リスクな TLD 高リスクなポート IP 直指定による通信 ポートと プロトコル が一致しない通信 Generous Allow-List 信頼できる ドメイン リスト、以下例 *.gov (日本向けなら *.go.jp )、 *.edu 、us-*.amazonaws.com .<自社 ドメイン >、 .<信頼する他社 ドメイン > Generous Allow List では、初期段階では厳格に絞り込みすぎず、 比較的寛容な範囲で ドメイン リストを設定 します。 その上で、運用開始後に取得したログを基に継続的な評価と見直しを行い、Allow List / Deny List の精度を段階的に高めていくことが重要です。 このような運用を支援する手段として、 Reporting on network traffic in Network Firewall の機能を利用することで、 過去の通信ログから ドメイン 単位のアクセス状況を分析・可視化 し、ルール設計や改善に活用することができます。 まとめ アウトバウンド対策は DNS / L3–7(IP / Port / TCP / HTTP / TLS ヘッダ)/ ボディ のレイヤで整理すると理解しやすい。 AWS のマネージドな主要選択肢として、 Route 53 Resolver DNS Firewall / Network Firewall / Network Firewall Proxy が挙げられる。 次回以降の記事で詳細検証しますが、要件に応じた AWS サービスの主軸の使い分けは以下のイメージを持っています。 ドメイン フィルタ:Route 53 Resolver DNS Firewall + Network Firewall or Network Firewall Proxy UDP / TCP を含む評価:Network Firewall HTTP / HTTPS のみの評価:Network Firewall Proxy ただし、あくまでプロキシであるため、ベースのネットワークセキュリティの担保が必要。 Generous Allow List を基に、まずは低コストで効果の高い対策から導入し、ログを活用しながら継続的に改善していくことが現実的である。 おわりに 本記事では、 AWS におけるアウトバウンドセキュリティ全体の概要を整理しました。 今回は基礎的な内容を中心に取り上げましたが、今後は実際の設計・検証・運用を通じて、より実践的な理解を深めていきたいと思います。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sakae.katsuto レビュー: @nagamatsu.yuji ( Shodo で執筆されました )
金融IT本部入社一年目の河岸歩希です。 この度、社内で実施されている 伴走型研修 に参加し、 AWS Certified Solutions Architect – Professional (以下SAP) を受験いたしました。本記事では、その学習過程で得た知見や経験を、僭越ながら受験体験記としてまとめさせていただきます。 これからSAP認定の取得を目指される方や、 AWS 業務未経験だけどSAPに興味をお持ちの方にとって、本記事が少しでも参考になれば幸いです。 前提 想定読者 著者情報 試験概要と伴走型研修について 試験概要(SAP-C02) 伴走型研修とは 伴走型研修の感想 結果と勉強時間 学習リソース AWS Solution Architect Professional (AWS-SAP-C02) 演習テスト Google AI Studio 問題を解いていて思ったこと 当日の立ち回り おわりに 前提 想定読者 これからSAPを受ける予定のある方 AWS 業務未経験だけど AWS の資格取得に興味がある方 資格勉強が好きな方 著者情報 学部卒の新卒1年目 大学では情報系の学部に所属 ( クラウド 技術には触れたことがないためほぼ未経験者) 会社に入ってから(4月) AWS を学び始める CLF(5月)とSAA(7月)をそれぞれ取得済み 業務では AWS を触っていない(触りたい) 試験概要と伴走型研修について 試験概要(SAP-C02) 試験の公式ガイドにも記載がありますが、この AWS Certified Solutions Architect – Professional認定は、 AWS を用いたソリューションの設計における2年以上の実務経験を持つ方を主な対象として設計されています。 私のような社会人1年目がこの資格を取得できたなら、それは 2年分の業務経験に相当する知識 や設計能力が身についた一つの証となり、自身のキャリアにおける大きな自信へと繋がるのではないかと考えております。 SAP認定の学習を始めるにあたり、皆様に最初におすすめしたいのは、まず公式の「試験ガイド」に必ず目を通し、それから学習計画を立てることです。 その理由は、試験範囲が他の認定と比較しても 非常に広範かつ多岐にわたる ためです。やみくもに学習を始めてしまうと、重要な分野に時間を割けなかったり、逆に試験の比重が低い部分に時間を使いすぎてしまったりする可能性があります。 以下は、その試験ガイドから引用した、出題範囲の主要な分野です。これらの分野に、それぞれどの程度の比重が置かれているかを把握することが、効率的な学習の第一歩となります。 本試験のコンテンツ分野と重み設定は、以下のとおりです。 コンテンツ分野 1: 複雑な組織に対応するソリューションの設計 (採点対象コンテンツの 26%) コンテンツ分野 2: 新しいソリューションのための設計 (採点対象コンテンツの 29%) コンテンツ分野 3: 既存のソリューションの継続的な改善 (採点対象コンテンツの 25%) コンテンツ分野 4: ワークロードの移行とモダナイゼーションの加速 (採点対象コンテンツの 20%) どの分野もまんべんなく出題されるため、偏りなく学習することが重要です。 試験時間と設問数、合格点に関しても、SAAより高く設定されています。 試験時間:180分 設問数:75 (うち10問が採点対象外) 合格点:750点 詳細は 試験ガイド を参照していただければと思います。 伴走型研修とは 当社では半期に一度、 AWS やAzureなどの資格取得に興味のある方を対象に伴走型研修が行われております。(要申し込み) 内容としては、外部講師との1on1(スキルや状況に応じた学習計画のアド バイス )、毎週30分の勉強会(試験問題の解説や情報共有)、専用Teamsでの相談や質疑応答などを通して、資格取得まで包括的なサポートを受けることができます。 怠惰な私にとってはピッタリの研修だと思い応募しました。 伴走型研修の感想 特によかったと思うのは、外部講師との1on1です。 9月からスタートして、11月末までの3か月間のうち、ほぼ隔週で1on1の時間を設けていただきました。 (スケジュールに関しても、柔軟に対応していただけました) この定期的な1on1を通じて、学習の進捗と次回の面談までの目標を設定するのですが、この目標設定が継続的な学習のモチベーション維持に大きく寄与したと思います。 また、勉強に際してのアド バイス もいただけたため、効率的に学習を進めることができました。 結果と勉強時間 結果↓ 無事SAPに合格することができました。 意外かもしれませんが、 試験で最も壁を感じたのは、SAPよりもアソシエイト(SAA)でした 。 もちろん、出題される問題の運にも左右されるため、一概には言えないことですが。 ご参考までに、私が各試験を初めて受けた際の 「難易度感」 を表すと、以下のようになります。 SAA >>> SAP >>>>> CLF = 基本情報技術者試験 こうして振り返ってみると、CLFからSAAへの難易度の上昇がいかに大きかったかを改めて感じます。 SAPの学習では、SAAで得た個々のサービスの知識を、より深く、実践的なシナリオに当てはめていく意識が重要だと感じました。 そのようなアプローチで学習を続けることが、合格への確かな一歩になるのではないかと思います。 学習リソース 主にUdemyとGoogleAIStudioを用いて学習を行いました。 AWS Solution Architect Professional ( AWS -SAP-C02) 演習テスト https://www.udemy.com/share/10bBe7/ 75問構成の模擬試験が5セット収録されているUdemyの問題集です。 この教材を選んだ理由は、問題の最終更新日が他の教材と比較して最も新しかったからです。 AWS のサービスは仕様変更が頻繁に行われるため、その最新の変更に対応して問題内容を随時更新してくれるコースを選ぶ ことが、非常に重要だと考えています。 難易度はかなり高めでした。 私自身、学習時間の都合で2周目には手が回らず、結果的に1周しかできませんでしたが、平均正答率は6割程度でした。 若干解説が薄い部分もありますが、この問題集だけで試験範囲は網羅できるので安心です。 Google AI Studio https://aistudio.google.com/prompts/new_chat もちろん、ChatGPTなど他のAIサービスでも全く問題ありません。 私が Google AI Studioをおすすめする理由は、無料で最新モデル(※2025年12月時点)が利用でき、一度に扱える情報量( トーク ン数)が多い点です。これにより、模擬試験の疑問点や不明点を余すことなく質問できるため、非常に効率的でした。 模擬試験を解き、解説を読んでも腹落ちしない部分があれば、すぐにこの Google AI Studioを開き、納得できるまで対話を繰り返す、というスタイルで理解を深めていきました。 問題を解いていて思ったこと 問題を解いていてふと思ったことがあります。それは 要件によって正解が変わる ということです。 当たり前のことのように聞こえるかもしれませんが、180分という時間の中で75問もの長文問題と向き合い続けると、どれだけ準備をしていても、どうしても集中力が途切れてしまう瞬間が訪れます。 そして、そういうときに限って、問題文の最も重要な 「制約条件」や「優先順位」 の部分をつい読み飛ばしてしまい、「選択肢のAもBも、技術的にはどちらも可能に見える…」という、判断に迷う状況に陥りがちです。 例えば、「モノリシックなWebアプリケーションを、サーバー管理から解放されるマネージドサービスへ移行したい」という典型的なシナリオを考えてみましょう。 この場合、頭の中にはいくつかの有力な候補が浮かびます。(実際の試験では、与えられた選択肢の中から判断します) 候補1: AWS Fargate ( Amazon ECS on Fargate) 候補2: AWS Lambda どちらもサーバーレスのマネージドサービスですが、どちらが「正解」になるかは、問題文に添えられた 「要件としては~」 といった一文にかかっています。 ケース1:要件が「コードの変更を最小限に抑え、迅速に移行したい」場合 この場合、正解は AWS Fargate になるでしょう。 モノリシックな既存アプリケーションをLambdaが要求するような小さな関数に分割するのは、大規模な再設計を伴うため、迅速な移行とは言えません。一方、コンテナ化してFargateに乗せる場合であれば、比較的少ないコード変更で移行を完了させることが可能です。 ケース2:要件が「長期的な運用コストを最適化し、スケーラビリティを最大化したい」場合 この場合、正解は AWS Lambda になる可能性が高まります。 アプリケーションを関数にまで分解する初期の移行コストは高くとも、完全な従量課金やイベント駆動のスケーラビリティといった、 クラウド ネイティブのメリットを最大限に活かせる長期的な視点を、この要件は重視しているからです。 2択までに絞れたが、何がまちがっているのかわからないようなケースに陥ったときは、一度冷静になって問題文を読み返し、 「コスト」 「スピード」 「運用負荷」 といったキーワードと、各選択肢を照らし合わせてみてください。 ユーザーが最も重視している要件に応えている方が正解です。 当日の立ち回り お手洗いと身分証は忘れずに。 私は身分証を忘れて家に取りに帰りました。 皆様にはぜひ落ち着いて持ち物を確認してから出発していただきたいです。 また、試験は長丁場になるので、合間に休憩を入れることをおすすめします。 おわりに 今回、伴走型研修に参加させていただいたことで、 AWS 未経験だった私でも、最後まで諦めることなく資格学習と向き合うことができたと実感しています。 一人では難しいモチベーションの維持に悩んでいる方にとって、このような学習環境は一度検討する価値が大いにあるかと思います。 SAPは確かに難関資格ですが、今回の経験を通じて、たとえ実務経験がなくても、コツコツと演習を重ねれば必ず合格に手が届く試験だと思いました。 本記事が、これから挑戦される一人でも多くの方の参考となり、合格への一助となれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @kawagishi.ibuki レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
はい、こんにちはー!クロス イノベーション 本部 サイバーセキュリティテクノロ ジー センターの福山です。 2025年もReact2Shellなどを筆頭に多くの 脆弱性 情報が公表されました。 公開された 脆弱性 情報の傾向を1年単位で振り返ってみたいと思い、各種サイトの API を用いて収集し、分析してみました。 収集対象のデータ 2025年 分析結果 深刻度レベル別(2024/2025年) 深刻度レベル別(2025年月別) NVDステータス別 攻撃コード公開済み脆弱性の割合(PoC/MSF) 悪用確認済み脆弱性の割合(CISA KEV)/製品内訳(上位5製品) RCE可能な脆弱性の割合/悪用確認済みネットワーク機器内訳 CWE別件数(上位10位) CWE別リスク分布 CVE別/2025年最も危険な脆弱性4選 CVE別/今後悪用が広まる可能性が高い脆弱性4選 まとめ 収集対象のデータ 2025年に公表された約5万件のCVEを含む各種データを、 スクリプト で収集しました。 取得した情報の内容や抽出条件については以下を参照してください。 取得情報の一覧を表示 取得情報 概要 取得方法 参照元 CVE-ID 脆弱性 の一意の識別子。NIST(米国国立標準技術研究所)が運営するNVDに登録されたCVE番号を使用 2025年に公表されたCVE-IDを全件取得 NVD( https://nvd.nist.gov/vuln ) CVSS Base Score 基本評価基準に基づいて、 脆弱性 そのものの深刻度を数値(0.0〜10.0)で評価したスコア 対象CVE-IDにおける、CVSS v3.1(※1)のスコアを取得。取得優先度はNIST評価(Primary)>CNA(※2)評価(Secondary)で、両方未評価の場合は「N/A」とする。 NVD( https://nvd.nist.gov/vuln ) CVSS 深刻度レベル CVSS Base Scoreを基に分類した深刻度レベル(Low / Medium / High / Critical) 対象CVE-IDにおける、CVSS Base Scoreに基づく深刻度レベルを取得。取得優先度はNIST評価(Primary)>CNA評価(Secondary)とする。 NVD( https://nvd.nist.gov/vuln ) NVDステータス NVDにおける当該 脆弱性 の分析状況(例:Undergoing Analysis、Analyzed など) 対象CVE-IDにおける、NVDステータスを取得 NVD( https://nvd.nist.gov/vuln ) CWE-ID 共通 脆弱性 タイプ(Common Weakness Enumeration)の識別子 対象CVE-IDにおける、CWE-IDを取得。取得優先度はNIST評価(Primary)>CNA評価(Secondary)とする。 NVD( https://nvd.nist.gov/vuln ) RCE リモートから任意のコード実行が可能かどうか 筆者独自のフィルター。対象CVE-IDにおいて、Descriptionに大文字・小文字に関わらず「remote code execution」「execute arbitrary code」「arbitrary code execution」のいずれかのキーワードが含まれ、かつAttack Vector が「NETWORK」であるか判定 NVD( https://nvd.nist.gov/vuln ) CISA KEV 実際に悪用されているかどうか CISA のKEV(Known Exploited Vulnerabilities Catalog)への登録有無、ベンダー名、製品名を収集 CISA KEV( https://www.cisa.gov/known-exploited-vulnerabilities-catalog ) PoC PoC(概念実証コード)が公開されているかどうか 筆者独自のフィルター。PoC-in- GitHub のデータを基に判定(※ GitHub に存在するPoCのみ参照)。具体的にはZIPファイルを取得したうえで、全ファイルパスを走査し、CVE-IDが含まれる場合は抽出。 PoC-in- GitHub ( https://github.com/nomi-sec/PoC-in-GitHub ) MSF Metasploit Frameworkに当該 脆弱性 のモジュールが組み込まれているかどうか 筆者独自のフィルター。Metasploit Frameworkに対象CVE-IDを示すファイルが存在するかチェック。具体的にはZIPファイルを取得したうえでmodules/配下のファイルパスを走査し、CVE-ID形式の文字列があれば抽出。 Metasploit Framework( https://github.com/rapid7/metasploit-framework ) EPSS Score FIRSTが提供するEPSS(Exploit Prediction Scoring System)。今後30日以内に悪用される確率の予測値(0〜1) 対象CVE-IDにおける、EPSS Scoreを取得 FIRST EPSS( https://www.first.org/epss/ ) (※1)CVSS v3.1の上位バージョンであるCVSS v4.0が2023年11月に公開されたが、2025年に公表されたCVE全体の約25%しか評価されておらずNVDによる評価が進まないため、広く普及していない状況である。本記事ではCVSS v3.1をベースに解説を進める。 (※2)CNA=CVE Numbering Authorityの略称で、MITRE社から認定を受けて採番を行う組織のこと。 2025年 分析結果 ⚠️本集計は2026年1月中旬時点のデータに基づきます。 2025年に公表されたCVEの総数は 49,972 件でした。 2024年は40,704件であったため、 前年比で約23%増 、 9,000件近くの増加 となっています。 以降、分析結果の詳細です。 深刻度レベル別(2024/2025年) 2025年に公表された全CVEにおける、深刻度レベル別の件数です。 2024年と比較して 全体的に増加傾向 となっています。 2025年分は直近での集計ということもあり2024年分と比較すると差が出ていますが、それでも 未評価のCVEが前年比約4倍 と目立ちます。 NVDとしては、CNAによる評価の迅速化であったり、CWEの紐づけをLLMで自動化するなどの効率化(※)を図っているようですが、それでも対応が追いついていないことがわかります。 (※)参考: Vulnerability Root Cause Mapping with CWE 深刻度レベル別(2025年月別) 2025年に公表された全CVEにおける、月別/深刻度レベル別の件数です。 直近で公表されたCVEに未評価が多いのは当然ですが、12月は 未評価が1,500件 を超えています。 NVDではKEVに登録があるものを優先して評価(※)しているようですが、直近の 脆弱性 には対応が追いついていない状況が見て取れます。 今は未評価でも、分析が進むにつれてCriticalレベルと判明する 脆弱性 が出てくる可能性はありそうです。 (※)参考: The National Vulnerability Database (NVD) – Where It Is and Where It’s Going NVDステータス別 左が2024年、右が2025年に公表された全CVEにおける、NVD分析状況の割合を100%積み上げ棒グラフで示したものです。 (各ステータスの意味は下表を参照してください) 2024年分はAnalyzed(分析完了)と Modified(更新)を合わせて約80%の分析が完了しています。 一方、2025年分は分析完了の割合は前年並みを維持しているものの、 Awaiting Analysis(分析待ち)が約40% と大きく目立ちます。 ここについては今後分析が進んでいくと思われますが、現状では情報の確定に時間を要していることが伺えます。 また、 Rejected(却下)されたCVEが前年比で2倍以上 に増加している点も注目すべき変化です。 背景には、特定のライブラリに起因する 脆弱性 が製品ごとに乱立した際のCVEの統合や、昨今話題となっているAIツールを用いた大量かつ低品質な 脆弱性 報告の増加(※)といった、NVDを取り巻く環境の変化が影響していると考えられます。 (※)参考: NVD's HUGE Backlog: Vulnerability Crisis Explained ステータス名 意味 解説 Received 受理 NVD登録初期状態 Awaiting Analysis 分析待ち CVEの情報は公開されているが、NVDによるCVSSスコアやCWEの紐付けなどの詳細分析を待っている状態 Undergoing Analysis 分析進行中 CPE (情報システムを構成する、ハードウェア・ソフトウェアなどの識別子)やCWEの付与を行っている最中の状態 Analyzed 分析完了 NVDによる詳細分析がすべて完了した状態 Modified 修正 分析完了後に、新しい情報が追加・更新された状態 Deferred 延期 分析の優先順位が下げられた状態 Rejected 却下 脆弱性 ではないと判明した、あるいは既存のCVEと重複していた等の理由で、CVE-ID自体が取り消された状態 攻撃コード公開済み 脆弱性 の割合(PoC/MSF) 2025年に公表された全CVEにおける、PoCが公開されているCVEの割合は 2.47% で、 1,234件 ありました。 GitHub 以外やプライベー トリポジ トリのPoCも含めると、実際にはこの数値より膨らむものと推測されます。 また、上記のうちMetasploit Frameworkでモジュール化されているCVEの割合は 4.04% で、 50件 でした。 攻撃コードがモジュール化されている 脆弱性 は、高度な技術を持たない攻撃者でも簡単に悪用できる可能性があるため、特に注意が必要です。 悪用確認済み 脆弱性 の割合( CISA KEV)/製品内訳(上位5製品) 左の円グラフは2025年に公表された全CVEのうち、 CISA KEVに登録された割合を示しており、全体の 0.33% でした。 件数にするとわずか167件ですが、これらは理論上のリスクではなく、実際に悪用されていることが確認されているため、優先的に対応すべき重大な 脆弱性 として取り扱う必要があります。 右の円グラフは、その中でも悪用報告が多かった製品の内訳です。 Windows が17.37% と最多なのは、多くの企業で共通基盤として使われており、攻撃者にとって最も効率よく広範囲に影響を及ぼせるためと考えられます。 また、上位にはFortinetやIvantiといったネットワーク機器も並んでいます。 VPN などの境界を守る機器は、一度突破されると組織内への侵入や権限奪取に直結するため、OSと同等に執拗な標的となっていると考えられます。 RCE可能な 脆弱性 の割合/悪用確認済みネットワーク機器内訳 2025年に公表された全CVEにおいて、リモートコード実行(RCE)可能と判断されるものは、全体の 3.25% でした。 リモートから悪意のあるコードを実行された場合、情報窃取や攻撃の起点となるリスクが高く、特に注意が必要です。 さらにその中でも悪用確認済みの 脆弱性 に絞ると、 ネットワーク機器の 脆弱性 が3割 を超えています。 なお、これらの 脆弱性 のうち CWE-787(境界外書き込み) に分類される 脆弱性 は約4割と最も多く占めていることもわかりました。 これらの境界機器のRCEは、認証を介さず外部から直接システム権限を奪取できるものが多いため、昨今の ランサムウェア 攻撃において最も警戒すべき侵入経路とされています。 CWE別件数(上位10位) 2025年のCWE別件数の上位10タイプを、2025年(オレンジ)と2024年(グレー)で比較しました。 CWE-79( クロスサイトスクリプティング )が不動の1位となっています。 また、最も件数が増加したのは CWE-74(インジェクション)で1,169件増加 、一方で最も件数が減少したのは CWE-787(境界外書き込み)で843件減少 となりました。 CWE-74については、2025年に暫定策として取り入れられたGap Filling(CNAから提出されたCVSSおよびCWEデータを再検証せずに、一時的に受け入れるもの)による登録促進(※)の影響で、CNA側で付与したCWE-74が精査を待たずに大量に登録されたことが、増加の主な要因ではないかと推測しています。 (※)参考: The National Vulnerability Database (NVD) – Where It Is and Where It’s Going CWE別リスク分布 前項では件数に着目しましたが、ここでは筆者独自の観点で、リスクベースで分析してみます。 下図のバブルチャートでは、バブルの大きさが2025年に公表されたCVE総数を表し、X軸にRCE可能なCVE件数、Y軸に CISA KEVに登録されたCVE件数でプロットしたものです。 その中でもRCE可能な件数が50件以上、KEV登録数が10件以上のCWEに注目し、右上の領域にプロットされたCWEを下表にリストアップしました。 これらの 脆弱性 タイプは、 段階を踏まずインターネット越しから任意のコマンドを実行できる割合が高く、悪用実績も多数報告されている ため、特に注意が必要なカテゴリと言えるでしょう。 一方で最もサイズが大きいバブルはCWE-79ですが、単体でのRCEやKEV登録数は相対的に低めです。 CWE-ID 名称 解説 CWE-787 境界外書き込み メモリ操作時の不備により、本来の範囲を超えてデータを書き込んでしまう 脆弱性 のタイプ CWE-502 信頼できないデータのデシ リアラ イゼーション 外部から受け取ったデータをオブジェクトに復元する際、悪意あるコードを実行可能にする 脆弱性 のタイプ CWE-78 OSコマンドインジェクション OSコマンドの生成時に外部入力のバリデーションを不適切に行うことで、任意のコマンドを実行可能にする 脆弱性 のタイプ CVE別/2025年最も危険な 脆弱性 4選 2025年を通して最も危険な 脆弱性 について、個人の見解に基づいて選抜しました。 選抜の観点としては以下です。 CVSSスコア10(最大値)かつRCE、PoC、MSF、KEVをすべて網羅している 脆弱性 もし自社の環境や稼働しているシステムに下記に該当する 脆弱性 が存在する場合は、一刻も早く対処することをおすすめします。 CVE Published Vendor Product CVSS CWE RCE PoC MSF KEV EPSS CVE-2025-32433 2025-04-16 Erlang Erlang /OTP 10 CWE-306 TRUE TRUE TRUE TRUE 0.43921 CVE-2025-47812 2025-07-10 Wing FTP Server Wing FTP Server 10 CWE-158 TRUE TRUE TRUE TRUE 0.924 CVE-2025-55182 2025-12-03 Meta React Server Components 10 CWE-502 TRUE TRUE TRUE TRUE 0.5512 CVE-2025-37164 2025-12-16 Hewlett Packard Enterprise (HPE) OneView 10 CWE-94 TRUE TRUE TRUE TRUE 0.8131 CVE別/今後悪用が広まる可能性が高い 脆弱性 4選 最後に、2025年に公表されたCVEの中で、今後悪用が広まる可能性が高い 脆弱性 について、個人の見解に基づいて選抜しました。 選抜の観点としては以下です。 CWE別リスク分布で特定したCWEのうち、EPSSスコア0.9以上(直近30日間で悪用される可能性が非常に高い)かつPoC、MSFが公開されている 脆弱性 これらの 脆弱性 も早期に特定し、優先的に対処することをおすすめします。 CVE Published Vendor Product CVSS CWE RCE PoC MSF KEV EPSS CVE-2025-0282 2025-01 Ivanti Connect Secure, Policy Secure, and ZTA Gateways 9 CWE-787 TRUE TRUE TRUE TRUE 0.94105 CVE-2025-24016 2025-02 Wazuh Wazuh Server 9.9 CWE-502 TRUE TRUE TRUE TRUE 0.93801 CVE-2025-24813 2025-03 Apache Tomcat 9.8 CWE-502 TRUE TRUE TRUE TRUE 0.94183 CVE-2025-53770 2025-07 Microsoft SharePoint 9.8 CWE-502 FALSE TRUE TRUE TRUE 0.91188 まとめ 2025年はこれまで以上に 脆弱性 情報の量が増えたと感じていましたが、実際に分析することで日々のニュースを追うだけでは見えてこないその年の傾向や、NVDを取り巻く環境の変化や運用方針の転換が、データに影響を及ぼしていることが見えてきました。 CWEの総数で上位10位に入っていないものの、ひとたび悪用されればRCEに直結しやすく、かつ実際にKEVとして登録されるケースも目立ちました。 特に、ネットワーク機器の深刻な 脆弱性 として分類される傾向が見えたCWE-787と、冒頭でも触れたReact2Shellを含む CWE-502の動向には引き続き注視する必要がありそうです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @fukuyama.kenta レビュー: @miyazawa.hibiki ( Shodo で執筆されました )