TECH PLAY

電通総研

電通総研 の技術ブログ

840

1. はじめに はじめまして!電通総研の武田嵩史(たかふみ)と申します。テックブログを執筆するのが初めてで怯えながらの投稿ですが、全2回に渡り本テーマについて発信できればと思います。簡単に私の自己紹介をしてから本内容に移りたいと思います。 自己紹介 改めまして武田嵩史と申します。普段はクラウドを用いたシステム開発や運用のプロジェクト・マネージャーをしております。 その中でも最近ではクラウドHPCの基盤を構築することが多く、様々なクラウドベンダー様とお仕事をさせていただいております。 ”クラウドHPC”?なんじゃそりゃ?という方は弊社が公開している以下記事をご覧いただければと思います! SDGsの達成にも貢献!?クラウドHPCについてもう一度考えてみる 電通総研のクラウドHPCサービスがもたらす革新とメリット 扱うクラウドはAWS, Azure, OCIの3つが主でして、それぞれのクラウドプロバイダーの強み・特徴を活かして最適な環境をお客様に提供することを得意としております。 モチベーション さて、そんな私がクラウドHPCの中でも最近特に注目しているのが、AWSの "hpc8a" インスタンスです。 AMD社の最新プロセッサであるTurin(EPYC 9R45)を搭載したインスタンスタイプでして、 ap-northeast-1(東京) リージョンで利用ができることが1つの特徴です。 このインスタンス、AWS公式のページを引用すると以下のパフォーマンスを発揮するとのこと。 前世代の Hpc7a インスタンスよりも 最大 40% 優れたパフォーマンス、42% 広いメモリ帯域幅、最大 25% 優れた料金パフォーマンス を実現します。 (引用元) 第 5 世代 AMD EPYC プロセッサを搭載した Amazon EC2 Hpc8a インスタンスの一般提供開始 HPCワークロードの特徴上、解析データサイズも大きかったり、多くのI/Oが発生したりするので、データとコンピューティングリソースは可能な限り近い場所に置く方がパフォーマンスを発揮できます。そのため、日本のお客様は日本で利用できる高性能なマシンはあればあるだけ嬉しいので、このインスタンスタイプに私自身もとても興味があり、実力はどんなものかと調査すべく今回のベンチマーク実施に至りました。 本題 本記事は以下の流れとなっております。 目次 はじめに ←終了 ベンチマーク概要 Ansys Fluentベンチマーク結果 Cradle MSC scFLOWベンチマーク結果 おわりに(次回予告) 2. ベンチマーク概要 2.1 ベンチマークに利用したインスタンスタイプ ベンチマークと言ってもhpc8aだけで解析実行しても仕方ありません。ということで、今回は以下のEC2インスタンスタイプを選定しベンチマークを実施しました。 Instance type Spec Cost $ (instance/h) Cost $ (core/h) hpc6id.32xlarge (Intel) CPU:Intel Xeon Ice Lake 64cores Memory:1024GB NIC:200Gbps EFA $5.70 $0.09 r8i.96xlarge (Intel) CPU:Intel Xeon 6 Granite Rapids-AP 192 cores Memory:3072GB NIC:100Gbps EFA $26.67 $0.14 c7a.48xlarge (AMD) CPU:AMD EPYC 9R14 Genoa 192 cores Memory:384GB NIC:50 Gbps EFA $9.85 $0.05 hpc7a.96xlarge (AMD) CPU:AMD EPYC 9R14 Genoa 192 cores Memory:768GB NIC:300 Gbps EFA $7.20 $0.04 m8a.48xlarge (AMD) CPU:AMD EPYC 9R45 Turin 192 cores Memory:768GB NIC:75 Gbps EFA $11.69 $0.06 c8a.48xlarge (AMD) CPU:AMD EPYC 9R45 Turin 192 cores Memory:384GB NIC:75 Gbps EFA $10.35 $0.06 hpc8a.48xlarge (AMD) CPU:AMD EPYC 9R45 Turin 192 cores Memory:768GB NIC:300 Gbps EFA $7.92 $0.04 c8g.48xlarge (AWS) CPU:AWS Graviton4 192 cores Memory:384GB NIC:50 Gbps EFA $7.63 $0.04 ※Costはオハイオリージョンでの単価です。 ※Cost $ (core/h), Cost $ (instance/h)においては小数点3桁を四捨五入した小数点以下2桁で表記しております。 ※一部ベンチマークについては結果がないインスタンスタイプもございます。 選定の基準としては3つの観点で行いました。 ” 1. 会社 " CPUの製造元として代表的なIntel, AMDを選定。また、AWSも独自でプロセッサを開発しているので、Gravitonもノミネートしました。 " 2. 世代 " 各CPUの世代が上がると性能も向上するのか、どの程度向上するのか、という指標をみるために複数世代を選定しました。 " 3. インスタンスの用途 " AWSは同じCPUを搭載していても、ワークロードによって最適化されたインスタンスタイプを準備しています。基本的にはコンピューティング最適化インスタンス(cシリーズ)とHPC特化インスタンス(hpcシリーズ)を選定していますが、メモリ最適化(rシリーズ)、汎用(mシリーズ)も選定しているものもあります。 2.2 ベンチマーク実施内容 ここではどのような条件、ソルバでベンチマークを行ったかをご説明いたします。 2.2.1 ベンチマーク実施ソルバ 今回ベンチマークを実施したソルバ・ベンチマークモデルは以下です。 Solver Version Benchmark Model Ansys Fluent 2025R2 f1_racecar_140m Fluent benchmark model Ansys LS-DYNA 2025R2 Car2Car LS-DYNA benchmark model Siemens STAR-CCM+ 2506.0001 Golf_140M [Original Model] Cradle MSC scFLOW 2025.1 Common Research Model この中で第1回となる今回は Ansys Fluent と Cradle MSC scFLOW のベンチマーク結果を皆様に共有いたします! 2.2.2 ベンチマーク実施環境情報 ベンチマークを実施した環境情報は以下のとおりです。 項目 内容 OS Red Hat Enterprise Linux 8.10 ストレージ Amazon FSx for Lustre (SSD / 250 MB/s-TiB ) *4.8 TiB ネットワーク EFA 有効化 ジョブスケジューラー Slurm MPI Intel MPI 2021.13 MPI (Arm プロセッサ利用時) Open MPI 4.1.7 また、これらの環境の準備にはAWS ParallelClusterを利用しており大変便利なサービスでしたので、こちらも別途ご紹介できればと思います。 (参考) AWS ParallelCluster 2.2.3 ベンチマーク評価項目 ベンチマークの評価項目は以下の3つの指標で評価を行いました。 1.実行時間 各ソルバーの実行時間を秒単位で計測。 2.コスト 各ベンチマークジョブを完走するのに必要となるインスタンスの費用を計算。ここでは起動/停止にかかる費用やデータの保管費用、通信にかかる費用は含んでおらず、あくまでベンチマークジョブを完走するのにかかったコンピューティングリソースのみの費用を算出しています。 3.コストパフォーマンス 「計算速度が速くても値段が高いと簡単には利用できない。」というのが実情だと思いますので、今回はコストパフォーマンスという指標も準備しました。コストパフォーマンスは以下式で定義します。 今回は簡単のために、Genoaプロセッサを搭載したコンピューティング最適化インスタンスタイプ "c7a.48xlarge"を"1.0"として正規化したスコアで評価しました。 3. Ansys Fluentベンチマーク結果 それではAnsys Fluentのベンチマーク結果がこちらです。Ansys Fluentでは256並列から2048並列までの並列数で実施しました。 3.1 実行時間 ソルバー実行の経過時間比較 同一並列解析数の場合、全ての場合でTurin (m8a, hpc8a) のインスタンス性能が良い結果となりました。 前世代とのパフォーマンス比較 全並列数で3割程度のパフォーマンス改善がみられました。AWS公式によると、 最大40%の優れたパフォーマンス とのことだったので、今回の結果はそれに近い結果が得られました。 Gravitonの計算スケール c8g (Graviton4) については、並列数が増加するにつれ、他インスタンスタイプとの経過時間の差は縮小傾向にあり、大規模並列計算において計算時間のスケールメリットが出ることが確認できました。 3.2 コスト コスト比較 hpc8aが全ての並列数で一番コストが低い結果でした。 Ansys Fluentの今回のベンチマークモデルにおいては512並列が一番コストに優れた並列数となりました。 前世代とのコスト比較 全並列数で2割程度のコスト改善がみられました。AWS公式によると、 最大 25% 優れた料金パフォーマンス とのことだったので、こちらも公式に近い結果が得られました。 3.3 コストパフォーマンス コストパフォーマンス比較 hpc8aが一番コストパフォーマンスに優れた結果になりました。 同CPUスペックで比較するとHPCインスタンスはコストパフォーマンスが高い結果がみられます。 c8g (Graviton4) は2048並列になると、c7aのコストパフォーマンスを上回る結果になりました。 以上がAnsys Fluentのベンチマーク結果となりますが、いかがでしたか。 実行時間・コスト・コストパフォーマンス全てにおいてhpc8aが良いパフォーマンスを示していたことがみてとれたのではないでしょうか。 4. MSC scFLOWベンチマーク結果 続いてはCradle MSC scFLOWのベンチマーク結果です。scFLOWでは128並列から1024並列までの並列数で実施しました。 scFLOWにおいては一部実施できていないインスタンスタイプがあることご了承ください。 4.1 実行時間 ソルバー実行の経過時間比較 同一並列解析数の場合、全ての場合でhpc8aの性能が良い結果となりました。 前世代とのパフォーマンス比較 こちらもAnsys Fluentと同様、全並列数で3割程度のパフォーマンス改善がみられました。 4.2 コスト コスト比較 hpc8aが全ての並列数で一番コストが低い結果でした。 前世代とのコスト比較 こちらもAnsys Fluentと同様、全並列数で2割程度のコスト改善がみられました。 4.3 コストパフォーマンス コストパフォーマンス比較 上記の結果からも分かるとおり、hpc8aが一番コストパフォーマンスに優れた結果になりました。 以上、scFLOWのベンチマーク結果でしたが、大きな方向性としてはAnsys Fluentと変わらず、実行時間・コスト・コストパフォーマンス全てにおいてhpc8aが良いパフォーマンスを残していました。 5. おわりに(次回予告) というところで今回はAnsys FluentとCradle MSC scFLOWにおけるhpc8aのベンチマーク結果をご紹介いたしました。 次回は衝突系ソルバのAnsys LS-DYNAと流体系ソルバSiemens STAR-CCM+のベンチマーク結果もご紹介いたしますので、そちらもお楽しみにお待ちいただけますと幸いです! 末筆になりましたが、本ベンチマーク実施にあたりご協力いただきました各社様、ご関係者の皆様に厚く御礼申し上げます。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @takeda.takafumi レビュー: @kobayashi.hinami ( Shodo で執筆されました )
はい、こんにちはー!クロスイノベーション本部 サイバーセキュリティテクノロジーセンターの福山です。 昨年あたりから、ソフトウェア脆弱性報告件数の増加やサプライチェーン攻撃のニュースが目立つようになりました。 そこで今回は脆弱性管理とソフトウェアサプライチェーンの現状を把握すべく、関連テーマにフォーカスしたセキュリティカンファレンス「VulnCon」にバーチャル参加しましたので、本稿にレポートとしてまとめます。 VulnConとは 1. NIST's National Vulnerability Database Update and the Vulnerability Enrichment Ecosystem 2. Vulnrichment Playground 3. Supply Chains and Malware Campaigns: Is CVE the Right Way to Name the Game? 4. How to Answer "What's Affected?" in Open Source 5. Identifying Exploited and Likely-to-Be-Exploited Vulnerabilities 6. Taming the Scanner Storm: How VEX Brings Context to Vulnerability Data 7. Contextual SBOMs: Unlocking Precise Vulnerability Management with Build-Time Content Intelligence 8. Lessons From NPM's Dark Side: Preventing the Next Shai-Hulud まとめ VulnConとは VulnConとはCVEプログラムやFIRSTが中心となって主催する「脆弱性管理に特化した国際カンファレンス」です。 第1回開催は2024年と比較的新しいカンファレンスであり、今年は米国アリゾナ州スコッツデールで開催されました。 https://www.first.org/conference/vulncon26/ VulnConは、脆弱性管理とサイバーセキュリティ分野の専門家が一堂に会し、意見交換などを行い、具体的な成果の創出を目指す場となっています。 今回、VulnCon 2026(現地時間:2026年4月13日~4月16日開催)をリモートで視聴(参加費用 $100)し、筆者が注目した8つのセッションを紹介します。 本記事では各セッション内容をもとに、脆弱性管理およびサプライチェーンセキュリティの観点から重要なポイントを整理しました。 なお、本記事は講演者の発言内容に基づく書き起こしであり、内容は「TLP:CLEAR」のみです。 1. NIST's National Vulnerability Database Update and the Vulnerability Enrichment Ecosystem 発表:NIST NVD NVDの役割 CVEに対してNVDは追加メタデータ(CPE・CVSS・CWE・参照タグ)を提供する「バウンダリーオブジェクト」として機能 CNAは2016年以降独自公開が可能になり、現在502のCNAが存在 スケールの問題:NVDが直面する現状 CVEの増加数が過去最大レベルに達しており、現行の手動プロセスでは追いつかない CPE割り当てに分析時間の半分以上を消費(LLMプロジェクトやGitHubベースのソフトウェアの急増が負荷を増大) 大規模な未処理バックログが蓄積 NVDの新しい優先度付けアプローチ(リスクベースへの転換) CISA KEVリスト掲載CVE:1営業日以内にエンリッチ(付加情報の付与) 米国連邦政府ソフトウェアに関連するCVE 大統領令14208号で定義されたクリティカルソフトウェアのCVE リクエストベースでの対応も受け付け(対応は可能な範囲で実施) CVSSスコアの方針変更 既にスコアを持つCVEには重複スコアを付与しない(Gap Fillingの正式化) アナリストが矛盾を発見した場合は再評価の可能性あり ※Gap Filling:2025年から暫定導入され、CNAから提出されたCVSSおよびCWEデータを再検証せずに、一時的に受け入れる方針のこと バックログの整理 「deferred」ステータスを「not scheduled」に改名 2026年3月1日以前の古いバックログは原則「not scheduled」に移行 修正されたCVEの再エンリッチは原則行わず(リクエストがあれば対応) 自動化への取り組み LLM/AIを活用した分析自動化を小チームで研究中:完成次第オープンソースとして公開予定 RPAによる反復作業の自動化も並行して検討 CPE管理のフェデレーション化(CNAや作業グループと協議中) 2. Vulnrichment Playground 発表: CISA、Tharros Labs Vulnrichmentとは CISAが連邦政府機関(FCEB)や重要インフラ保護のために行っている脆弱性分析を広く公開する取り組み CVEのADP(Authorized Data Publisher)コンテナとして提供、GitHubリポジトリからも取得可能 毎年約2,000件以上のCVD(協調的脆弱性開示)ケースを処理する中で得た大規模な分析知見が元になっている Vulnrichmentが提供するデータ SSVC(Stakeholder Specific Vulnerability Categorization):全新規CVEにスコアリング KEVフラグ:既知の脆弱性悪用カタログへの掲載有無 CVSS・CWE:CNA未提供分のバックフィル(CNAが既に提供している場合は上書きしない) 参照タグ:パッチ・アドバイザリ等の分類 2年目の最大の変化:CPEの廃止 当初CPEの付与も実験的に行っていたが、分析時間の2/3をCPE作成に費やしていることが判明 利用者からの「CPEよりSSVC・KEV・CWEのカバレッジを広げてほしい」という声を受けて廃止 CPE廃止によってリソースを再配分し、全新規CVEへの完全エンリッチメントが可能になった 「Playground」の本質:本番環境での実験 KEVは拘束力のある運用指令(BOD)に紐づいており変更コストが高い。Vulnrichmentは低リスクで実験できる場として設計されている 「追加するだけでなく、やめることも重要」という姿勢:CPE廃止がその実践例 Linuxカーネルから「上流カーネル脆弱性のCVSSスコアリングをやめてほしい」という要望(GitHub Issue #262)も公開の場で議論中 Pandoc CVEを使った実践例(CVE記録がどう変化するか) 当初:記述不明確、影響範囲(affected)が「N/A」、誤った参照リンク Vulnrichmentが同日中にCVSS・SSVCを追加 数ヶ月後:参照を修正、SSVCの技術的影響を誤入力→後日修正 実験的介入:CISAが直接Pandocの開発者と議論・検証を行ったうえで書き直す(通常スコープ外) 今後の実験候補:CVE間の関係性の表現 現在は記述文の最終行に「他のCVEとの関連」を自然言語で書くしかない 「CVE同士の関係性を機械可読な形で表現できないか」をVulnrichmentで実験したい(スキーマ変更の前段として) Pandocの例:外部ツールの呼び出し時に別のSSRF脆弱性が発生→関係性が人間にはわかるが機械には処理できない Supplier ADP(SADP)パイロットへの言及 CNA自身が「自社製品でこのCVEは影響なし」というVEX的な情報をADPコンテナとして追記できる仕組みのパイロット 例:Pandocを組み込んだ自社Webアプリが --sandbox など安全な方法でPandocを呼び出しており影響を受けない場合、(提供元がCNAであれば)その旨をADPコンテナとして「影響なし」と追記できる。 参考: https://github.com/CVEProject/sadp-pilot Vulnrichmentのデータ品質・透明性について CVEプログラムのレコード・NVD・Vulnrichmentの3つを信頼性で順位付けするのは難しい 「GitHub上で問題提起→議論→修正」という透明なプロセスが品質の担保 CVSSだけで脆弱性を優先度付けするのは不十分であり、SSVCのようなコンテキスト情報との組み合わせが重要 3. Supply Chains and Malware Campaigns: Is CVE the Right Way to Name the Game? パネルディスカッション: Telos Labs、VulnCheck、HeroDevs、GitHub 議論の前提 CVE CNA Rules 4.1.8 / 4.1.9:一般的な目的で故意に作成された悪意のあるコードはCVE対象外。ただし、トロイの木馬、バックドア、または類似のサプライチェーンの侵害など、悪意のある形に改変された製品は、脆弱性であると判断される可能性がある。 OSVやOpenSSFのmalicious-packagesリポジトリなど代替はあるが、エコシステム全体での標準化や運用の成熟はまだ発展途上 ケース1:XZ Utils(CVE-2024-3094)—バックドア埋め込み Red Hatがバックドアを悪意ある改変済み正規コードとしてCVEを発行 CVEがあったことで、既存の脆弱性管理パイプラインがすぐに対応できた CVEなしでは「どのXZ Utilsの問題か」の識別子がなく、情報伝達が混乱していた可能性 ケース2:tj-actions(CVE-2025-30066)—GitHubアクションの侵害 メンテナーのアカウントがロックアウトされた状態でMITREがCVEを発行 メンテナーが機能停止しても、他のCNA(この例ではMITRE)がCVE発行で通知を前に進められる可能性が示された CVEがあったことで既存ワークフローへの統合が即座に可能 ケース3:Shai-Hulud—npmワームキャンペーン npmのマルウェア削除処理がCVEなしで機能し、GHSAが公開されnpm auditで警告 CVEを使わなくても npm エコシステム(GHSA/npm audit)が機能した ただしCVEシステムにはキャンペーン全体を関連付けるメカニズムが存在しない(記述に書くしかない) ケース4:Trivy侵害(CVE-2026-33634) GitHubがTrivy向けCVEを発行、下流のLightLLMとTelnyxは個別アドバイザリを取得 パッケージマネージャーをまたがる侵害(Go+PyPI)でのCVE運用の課題を浮き彫りに CVEが最善の方法か? 「CVEは25年前の設計思想で動いており、今でも機能している。だが完璧ではない」 キャンペーン全体を一つのIDで追跡したい場合と、個別パッケージごとにIDが必要な場合は矛盾する CVEの増発は対応チームの負荷増大を招く(10件のCVEは1件の10倍の事務作業) 「IDが多すぎることは問題ではないが、それらを関連付けられないことが問題」(現行CVEにそのメカニズムなし) 4. How to Answer "What's Affected?" in Open Source 発表: Google CVEプログラムの課題 1999年は321件 → 現在は10分に1件ペース 構造化されていないテキストが多く、自動マッチングが困難 NVDのCPE付与の遅延により手動処理が必要な状況が続いている OSV(Open Source Vulnerability)スキーマの設計思想 機械可読な形式でオープンソースの脆弱性を記述 introduced (脆弱性の混入時点)・ fixed (修正が入った時点)・ last_affected (影響を受ける最終点)イベントで範囲を定義 OSVの設計がCVE5.0スキーマにおけるバージョン範囲導入に影響した バージョン範囲の複雑さ(LTSブランチ問題) 「1.xで導入→2.2.8で修正→3.0で再導入」のような複数ブランチは単純な線形バージョンでは表現しづらい コミットの差分(diff)をハッシュ化した patch-id を使うことで、チェリーピックされた修正を自動検出できる Gitは本質的な順序関係を持つため、エコシステム固有のバージョン比較ロジックに頼らずに済む 「影響あり」判定のルール 導入コミットから現在のコミットへ、修正コミットを経由しないパスが存在する場合に影響あり マージコミットの扱い:修正ブランチ取り込みでも履歴次第で安全性が変わる。判断には“introduced→当該commit”の経路にfixedを含むか、曖昧なら影響あり寄りに倒す。 introduced: 0 (最初のバージョンから脆弱)の際、Gitの複数ルート(subtreeなど)への対処も明示 データソースの種類 エコシステム直接提供(crates.io、GoなどはOSVフォーマットで直接提供、関数レベルの情報も含む) GitHub Security Advisory(GHSA)経由でMaven・npmの情報を補完 CVE/NVDからの変換:バージョンタグとGitコミットを照合して範囲を推定(ただし修正コミットではなくリリースコミットを辿りがちで限界もある) ここから学べること CNAとしてGitコミットハッシュでの脆弱性レポート提供を強く推奨 ツール(VEX・到達可能性解析・静的解析)と組み合わせて「自分のプロジェクトで実際に影響を受けるか」をさらに絞り込める 5. Identifying Exploited and Likely-to-Be-Exploited Vulnerabilities 発表: VulnCheck VulnCheckのCNA活動の3本柱 Report of Vulnerability Service:外部研究者から脆弱性報告を受け付け、CVE発行まで無償でサポート 脆弱性研究・CVD調整:サードパーティとの調整、監査、CVEアサイン Initial Accessチーム:エクスプロイト開発、検知アーティファクト作成(実際に脆弱なコンテナを本番インターネットに公開して攻撃トラフィックを確認) VulnCheck独自KEVの定義と実態 「公開情報として悪用報告があるもの」または「VulnCheckが観測したもの」すべてをKEVとして扱う(CISAより広い定義) 2025年の悪用確認済み脆弱性のうち約28%が開示日当日以前に悪用証拠あり AIの普及によりこのタイムラインがさらに短縮される懸念 CVE未割当の悪用脆弱性の発見:Shadow Serverとの連携 Shadow Serverのシンクホール(悪用検知シグネチャ)を分析→CVEが存在しない状態で長年悪用されていた脆弱性を約50件発見、CVEアサイン 実例:2016年から公開されていたD-LinkのDSLモデムの脆弱性→2025年まで未割当→CVEアサイン後にCISA KEVへ掲載 「CVEがない=リスクがない」ではなく、単に見えていないだけ Metasploitモジュールの監査 Metasploitに存在するエクスプロイトモジュールとCVEの紐付けを全件監査 数百件のモジュールにCVEが未割当→うち約200件はCNA管轄内でCVEアサインが可能 Moore's Lawの進化版:「カジュアルな攻撃者の能力向上速度≒AIの進化速度」になりつつある BlackHat/Bostonのチャットログ例:脅威アクターがMetasploitをインストールし多数のモジュールを活用 製品セキュリティアドバイザリの活用 Microsoft MSRCのような機械可読フォーマットで悪用インジケータを提供するベンダーが理想 実例:Notepad++が悪用→サプライチェーン侵害→VulnCheckがDIBSプロセスで調整しCVEアサイン→CISA KEVへ掲載 DIBSプロセス:重複CVE発行を防ぐ協調メカニズム CNAが集まり、クリティカルな脆弱性に対して「誰がCVEを発行するか」を短期間で合意する非公式プロセス CVE重複発行リスクを減らしながら、対応速度を確保する リポジトリは公開されており参加可能 研究者コミュニティとの関係構築が重要 Project Discoveryなど、繰り返し脆弱性を報告する研究者コミュニティとの信頼関係を構築→深夜にLinkedIn経由でゼロデイ報告が届く Versa Connectの認証バイパス脆弱性:報告当夜にCVEを発行し、翌日にはShadow ServerおよびSibolが実運用環境での悪用を確認→CISA KEV掲載 脆弱性の悪用に関する初期情報は、研究者コミュニティやフォーラム、Discordなどに常駐する「perpetually online groups」によって最初にサーフェスすることが多く、そうしたシグナルをいかに早くキャッチできるかがカギになる 研究者クレジットの重要性 VulnCheckのInitial Accessチームの研究者(Kale)に加え、外部の脆弱性研究チームであるwatchTowr Labs、Code Whiteが同一の脆弱性をほぼ同時に発見する「researcher collision」が発生 全報告者に適切なクレジットを付与することが信頼を構築するうえで重要 ベンダーはアドバイザリとCVEレコード両方にクレジットを記載すべき AIによる影響(Q&Aより) 高スキル研究者が使えば高品質な脆弱性報告の大量提出が可能に スキルの低い報告者からの「スロップ(雑な報告)」も増加 脅威アクターはまだ古い枯れた脆弱性を多用しており、AIによる新規開発加速の実害はまだ計測困難だが、注視中 6. Taming the Scanner Storm: How VEX Brings Context to Vulnerability Data 発表: NVIDIA 前置き この発表はスキャナーから得られる脆弱性データをどう扱うかという話 今回は特にコンテナイメージにフォーカス 一部のイメージを対象に始めたプロジェクトで、その後すべてのコンテナイメージにVEXを適用できるようになった ※VEXとは: https://www.vuls.biz/blog/articles/20241105a#Vulnerability-Exploitability-eXchange-VEX (上記FutureVuls Blogを参考) 問題:スキャン結果が多すぎて開発者が動けない 34,000のコンテナイメージを毎日スキャン結果を更新→約2,690万件の脆弱性発見、24,536件のユニークなCVE そのうち66%はベンダーパッチが未提供(上流修正はあるがベンダーが未取り込み) スキャナーは「ベンダーが影響なしとしているか」「本当に悪用可能か」「偽陽性か」を判定しない VEX(Vulnerability Exploitability eXchange ※)オーバーレイシステムの設計 2段階パイプライン: Ubuntu(GitHub公開のVEX)とRed Hat(CSAF系VEX)を取り込み、正規化してCycloneDXとしてDBに格納 スキャン完了時にAPIがスキャンレポートにVEX情報を上書きする ルールエンジンでリスク許容度に応じた自動VEX適用ポリシーを設定 VEXアクションの優先順位(ルール) upgrade_suggested → 自動反映しない。修正版があるのでアップグレードを促す vendor_VEX-not_affected → Critical/High含む全深刻度で自動反映可 fix_available → vendor_VEX-not_affected より後順位で適用 vendor_VEX-affected → ベンダーが影響をMedium以下とする等、条件付きで自動反映し、開発者の追加調査の負担を減らす no_vex_or_upgrade_found → 原則は自動で判定せず。ただし最近公開された場合などはトリアージ中として扱う 実績データ 修正しなかったfindingsのうち94%でVEX情報に基づく判定が可能(CVE 2020以降) 中低深刻度1,700万件のうち84%にVEX情報が適用 Critical/Highは修正可能なものが多く積極的に修正指示 -2,600万件→VEX適用後、開発者が真に対応すべき件数を大幅削減 ベンダーが「影響なし」と言っていても顧客スキャナーでは検出されるため、SLAの設計が必要な課題として残る 学んだこと ベンダーごとのVEXフォーマット・更新頻度が異なり正規化が必須 スキャナー側がVEX情報を取り込んでいないケースがあり、業界全体での標準化・取り込みが課題 Alpine等まだ対応していないディストリビューションのVEX情報の取り込みが今後の課題 7. Contextual SBOMs: Unlocking Precise Vulnerability Management with Build-Time Content Intelligence 発表: Red Hat SBOMについて SBOM(Software Bill of Materials)は、ビルド工程で使われ、最終的なソフトウェアコンポーネントに含まれる全アーティファクトを機械可読で棚卸ししたもの 脆弱性管理において重要なのは、ソフトウェア資産全体の中からCVEを迅速に特定でき、修正の優先順位付けにも役立つため 規制(例:EU Cyber Resilience Act)やフレームワーク(例:NIST SSDF)でSBOM要求が増え、SPDX/CycloneDXやツール群で成熟してきた一方、複雑で現実的なコンテナビルドでは「SBOMが約束すること」と「実際に提供できること」にギャップがある 標準SBOM(Analyzed SBOM)の根本的な限界 コンテナイメージは複数のベース・ビルダーイメージからなるマルチステージビルドで構成 標準SBOMはフラットな「contains」リストのみを提供し、「なぜそのアーティファクトがそこにあるか」が不明 「誰が修正責任者か」がわからない状態でCVEが報告されるため、複数チームが無駄な調査・再ビルドに時間を浪費 誤った順序での再ビルドによりCVEが修正されない事態が起こり得る Contextual SBOMのコアコンセプト ビルドステップを精密に監視し、全アーティファクトをビルドアクション・生成イメージにマッピング 使用するSPDXのRelationshipType: CONTAINS :コンテナイメージ内の標準的な包含関係 DESCENDANT_OF :ベースイメージとの親子関係(例:アプリイメージ→UBI Python) BUILD_TOOL_OF :マルチステージビルダーイメージとの関係 具体的な効果(ベースイメージの例) 脆弱なHTTPパッケージ検出時→Contextual SBOMから「UBI Pythonベースイメージに由来」と即座に判定 アプリケーション所有者ではなくベースイメージ所有者に通知 ベースイメージ修正後のCI/CDパイプラインの自動再ビルドを完全自動化 具体的な効果(ビルダーイメージの例) curl が脆弱→GoLang Builderイメージが起点と特定→アプリレイヤーにコピーされていない場合は最終アプリの再ビルド不要と判断可能 ソースコードの依存関係起因のCVE→ベースイメージ所有者・ビルダー所有者には通知不要 Contextual SBOM導入前後の比較 Before After アーティファクトの起源 不明 即座に特定可能 修正担当チーム 関係しそうなチームが巻き込まれ、担当特定に時間 担当チームを最初から特定でき、担当だけが動ける 再ビルド順序 試行錯誤・重複ビルド 最適な順序で自動実行 社内エスカレーション 全体アラートになりがち 「対応不要」と伝えられる 課題と呼びかけ アーティファクトの一意識別子(チェックサム・SPDXのPackage Verification Code)の一貫性が精度の前提 カスタムバイナリファイルなど一意識別子を持たないコンテンツでは起源特定が困難→「不確実状態」でフラグ付け SBOMツールエコシステム全体におけるContextual SBOM対応ツール開発への投資を呼びかけ Contextual SBOMの利用方法 Hermeto(※) 公開プロジェクトとして公開中、CI/CDパイプラインへの組み込み可能 ※Hermeto Project: https://github.com/hermetoproject 8. Lessons From NPM's Dark Side: Preventing the Next Shai-Hulud 発表: OpenSourceMalware.com 「偶発的脆弱性」vs「意図的マルウェア」の違い 偶発的:開発者のミスによるフロー、CVE対象、サードパーティライブラリが動いている場所で悪用、影響期間は長期間のケースがある 意図的:最初に感染するのは開発者PCやCI、影響期間は短命(axiosは3時間未満でレジストリから消えた)、シークレット窃取が主な目的 CVEの対処法(最新バージョンにアップグレード)が、マルウェアでは逆に感染経路になる なぜnpmが標的になるか postinstallなどのライフサイクルスクリプトがデフォルトで実行される(ダウンロード直後に感染) provenance(発行元証明)が必須ではなく、検証の習慣が根付いていない 長期有効トークン(Long-lived Access Token)が存在し、一度奪われると長期間悪用される さらにJavaScriptの推移的依存関係の多さも影響範囲の拡大を加速 2025年主要マルウェアキャンペーン(5件) eslint-config-prettier(7月): 3800万DL/週。タイポスクワッティングドメインのフィッシングでトークン窃取→Windows開発環境でRCE is(7月): 280万DL/週。「メンテナーの誤ったアクセス削除」を装うソーシャルエンジニアリング→クリプトウォレット・クラウドクレデンシャル窃取 NX(8月): GitHubアクションの未発見脆弱性を悪用→AIツール(Claude・Gemini・Amazon Q)の設定を書き換えてマルウェアの共犯者化→370社・20,000ファイル以上流出(第2波で6,700リポジトリ追加公開) Quix(10月): 26億DL/週。「2FA更新」を装うフィッシング→chalk・debugパッケージ含む28パッケージ侵害→$1,000相当の暗号通貨窃取 Shai-Hulud(9月・11月): 187パッケージ掌握→TruffleHogを武器化してシークレットをスキャン→感染したパッケージを自動的に再公開するワーム機能→528リポジトリ公開化→第2波で492パッケージ追加侵害(ホームディレクトリ削除機能付き)→26,000リポジトリからクレデンシャル流出 2026年:TeamPCPによるTrivy・axios侵害 Trivy: 長期有効PATを悪用して悪意あるリリースを公開→LightLLMなど下流ツールも汚染 axios: 北朝鮮系脅威アクターによる高度なソーシャルエンジニアリング(偽のSlack連絡・偽のTeamsアップデート誘導によるマルウェア実行) Shai-Hulud第2波の被害を受けた組織の分析 侵害された30,000パッケージについて、どれだけの組織がシークレットをローテーションしたかを調査(Entro Security社による分析) 被害を受けた組織は1,000を超える 一定数の組織で、Shai-Hulud第2波から72時間後も有効なクレデンシャルを保持していた 被害組織は技術系企業だけでなく不動産・医療・保険など幅広い業種に及ぶ 防御策:npmメンテナー向け ハードウェアキー(YubiKeyなど):ブラウザのオリジン不一致を拒否し、人的認証経路を保護 Trusted Publishing(GitHub OIDC):CI/CDパイプラインのトークンを保護(採用率:過去ATO被害者で13%、Shai-Hulud被害者でさらに低い10.8%) セッションベーストークン(2025年12月npm導入):長期トークンを廃止しExposure Window(露出期間)を縮小 防御策:利用者向け 開発者: バージョンピンニング、lockfile管理、自動アップデートの無効化、ライフサイクルスクリプトの無効化 プロダクトセキュリティ: 使用していない依存関係の削除、CIやAI IDE上でのマルウェアスキャン クラウドセキュリティ: シークレットローテーションの自動化・即時実行、異常なクレデンシャル使用の監視 インシデントレスポンス: 脅威インテリジェンスとのフィード統合、サプライチェーンのIRプレイブックの作成 Q & A パッケージ単位ではなく、リポジトリ単位でブロックするという考え方とは? マルウェアを配布するには、パッケージ単体だけでなく、それを公開するためのインフラ(例:GitHubリポジトリ)が必要になる そのため、個々の悪性パッケージを追いかけるのではなく、以下の対応の方が、運用上効率的でスケールしやすいケースがある 悪性だと判明したGitHubリポジトリ自体をブロックする そのリポジトリから配布される成果物を包括的に遮断する クールダウン期間とは何か?なぜ有効なのか? 新しいパッケージやバージョンを公開直後にすぐ使わず、一定時間待ってから利用する運用のこと 推奨される待機期間:2〜3日(理由:アカウント乗っ取りなどで公開された 悪性バージョンの多くは、その期間内に削除・検知されている) 攻撃を完全に防げるわけではないが、何もしない場合に比べてリスクを大きく下げられる実用的な防御策とされている クールダウン運用で問題になりがちな点は? 重大な CVE 対応など、即時アップデートが必要な例外ケースが必ず発生する そのため、クールダウンを前提とする場合は、次が不可欠である 技術的にクールダウンを回避できる仕組み 誰が・どの条件で例外を承認するかという事前合意 実際の運用では、「24時間以内に全社が即時アップデート必須」というケースは稀 理論上の緊急性と、現場で実際に対応できる現実にはギャップがある まとめ VulnCon 2026では、脆弱性管理を取り巻くエコシステムが大きな転換点にあることが示されました。 NVDやCVEといった基盤は引き続き重要である一方、npmパッケージのマルウェアキャンペーンの事例が示すように、サプライチェーン攻撃のスピードと複雑さは増しており、単一の制度や指標だけでは実運用に耐えない場面が増えています。 また脆弱性管理において、VEXやContextual SBOMによる影響範囲の文脈化に加え、VulnCheckの発表が示したような「実際に悪用されている/されやすいか」という視点を組み合わせる重要性が強調されていました。 本記事で紹介したセッションは、これらの変化と課題を理解するうえで特に参考になる内容が多かったと感じました。 ちなみにバーチャル参加の場合、TLP:CLEARかつ2日目以降のセッションしか視聴できませんでした。 ネットワーキングパーティやワークショップへの参加、TLP:GREEN以上のセッションを視聴したい場合は、現地参加をご検討ください。 https://www.first.org/events/calendar/2027#start:2027-03-30 CVE/FIRST VulnCon 2027 & Annual CNA Summit - Save the Date Scottsdale, US March 30, 2027–April 2, 2027 私たちは共に働いていただける仲間を募集しています! みなさまのご応募、お待ちしています! 株式会社電通総研 新卒採用サイト 株式会社電通総研 キャリア採用サイト 執筆: @fukuyama.kenta レビュー: @kobayashi.hinami ( Shodo で執筆されました )
こんにちは。クロスイノベーション本部、AIトランスフォーメーションセンターに所属している村本です。 今回は、電通総研が取り組む「OJTリーダー制度」の一環で、私がOJTリーダーとして新人の方(杉江さん)とどのように過ごしてきたかを紹介したいと思います。 なおこの記事は、OJTリーダーから見た振り返りであり、新人の杉江さんから見ると全く状況が違うかもしれません。そこで対となる記事として、新人の杉江さん目線での振り返りも投稿していただく予定です。 前提:電通総研の新人教育制度とAITCについて 新人教育の流れ 受け入れ先のAITCについて 受け入れるにあたって大事にしたこと 「新人さん」ではなく「新しいメンバー」をチームで受け入れる 新メンバーの疑問はチームにとって有意義だから、気軽に質問してね 日々のコミュニケーション:OJTリーダー以外との接点も作る 新人専用のTeamsチャネルを作り情報を集約 日報を投稿していただき、部全体からリアクションをもらえるように Teams開発部屋:リアルタイムコミュニケーションの場 開発チームメンバーとの1on1ラッシュ ゼロから機能リリースまで:半年間の実践振り返り 10月~11月:簡易生成AIアプリ開発を通じて基礎とチームの「お作法」を学ぶ 11月~1月:開発プロセスの様々なタスクを担って開発に慣れる 2月~3月:主要機能の開発リードとリリース体験 OJTリーダーとしての半年を振り返ってみて 新メンバーにとって働きやすいチームであることが大事 OJT期間はチームのプロセスをアップデートする期間 OJTリーダー1人で頑張りすぎない まとめ 前提:電通総研の新人教育制度とAITCについて まず初めに、前提として電通総研の新人教育制度について軽く触れます。 (実際に何をしたか気になる方はこの章を飛ばして読んでください) 新人教育の流れ www.dentsusoken.com 技術職で入社された方々は、入社後4~9月まで集合研修が行われます。そして、10月以降各部署に配属され、そこから半年間のOJT期間となります。この期間は、OJTリーダー(今回は私)と配属先部署(AITC)と人事部門が協力して新入社員をサポートします。(営業職で入社された方は7月時点で配属され、別の研修を受けます) 集合研修で基礎的なITスキルをじっくり学んでもらい、OJT期間で配属先に合わせたスキルを実践を通じて身につけていくイメージですね。 受け入れ先のAITCについて 私が所属しているAIトランスフォーメーションセンター(AITC)は、電通総研の中でもAIに特化したプロジェクトチームです。 aitc.dentsusoken.com AITCの中でもいくつかグループがあり、私と杉江さんはAIコアソリューショングループに所属しています。 ここでは、エンタープライズ向け生成AIソリューション「Know Narrator」の開発やAIにかかわる技術検証に取り組んでいます。 Know Narratorは自社開発のプロダクトなので、杉江さんは開発チームの一員として、プロダクト開発のスキルを身につけながら、製品の魅力向上に貢献していくことが大きな目標になります。 受け入れるにあたって大事にしたこと OJTリーダーとして、一番大事にしたのは「新人さん」じゃなくて「新しいメンバー」として受け入れることでした。 「新人さん」ではなく「新しいメンバー」をチームで受け入れる OJT期間において、最も大事なこと・目標は、新入社員の方が「チームの一員」として、「社会人」として、「開発者」として様々な経験を積むことです。 一方で、受け入れるチーム目線で見たときの目標はどうでしょうか? 「新入社員が戦力化すること」になりがち ではないでしょうか? しかし、この目標がすべてになったとき、 「戦力化するまでは負担なのか?」 という話になってしまいます。OJTリーダーとしても焦りますし、新人目線でもなんだか嫌ですよね。自分が負担と言われているようで。 これは非常にもったいないです。 もちろん戦力化は大きな目標の1つです。しかし、私は 「チームの暗黙知が通じないフレッシュな新メンバーが来た」と捉えて、チームのプロセスを見直すチャンス だと思うようにしました。 例えば: 新人が環境構築に時間がかかる → 環境構築するための情報が足りていない 新人がチームの動きをわかっていない→ コミュニケーションパスや担当が見えづらい 新人が開発に貢献できていない → アーキテクチャや開発プロセスが複雑すぎる こんな風に捉えるようにしました。 チーム開発は、時間が経つと暗黙知の塊になっていきます。定期的にプロセスの見直しやドキュメント整備はしますが、気づけばチームメンバー間の阿吽の呼吸に依存していることが多いんじゃないでしょうか? 新しく来た人には、この暗黙知や阿吽の呼吸は通用しません。そしてこれは、なにも“新入社員”だからではないんです。ベテランでも同じです。 ベテランの方の戦力化が早いのは、過去の経験や自分なりのやり方を持っていて、チームにフィットする能力が高いからかと。つまり、新入社員だろうがベテランだろうが、チームに慣れるまでに多かれ少なかれ時間はかかります。 だからこそ、「戦力化する=チームに慣れる」前に、チームとして新メンバーが働きやすい環境に変わっていけるよう、一緒に改善していく意識を大事にしました。 新メンバーの疑問はチームにとって有意義だから、気軽に質問してね この考え方は杉江さんにも伝えていました。 「あなたがOJT期間で『わからない』『コミュニケーションがとりづらい』と思うことは、チームの課題です。今後チームに入ってくるメンバーも思うことなので、気軽に言ってください。一緒に改善していきましょう」 (こんな綺麗な言い方をしたか定かではないですが、ニュアンスは伝えたはず…) こういった意識づけをすることで、少しでも質問のハードルを下げられたのならうれしいと考えていました。新人の頃は「気軽に質問してね」と言われてもなかなか気をつかうものですから。これも新人に限った話ではないとは思いますが。 「質問しやすい」環境というのは、結局「自分がチームに受け入れられている」と感じることが大事 だと思いますので、日々のコミュニケーションからチームに馴染んでいただけるよう意識しました。 日々のコミュニケーション:OJTリーダー以外との接点も作る 日々のコミュニケーションをどのようにとっていたかをまず紹介します。ここで大事にしたのは、OJTリーダーと新人の1対1の関係ではなく、「チーム全体がサポートできるよう新人と様々なメンバーとの接点を増やす」ことです。 新人専用のTeamsチャネルを作り情報を集約 【狙い】 新人に関する情報を一元化し、「ここを見ればわかる」状態にする 新人が気軽に質問でき、チームメンバーが気軽にサポートできる空間を作る 来期以降の新人もここを見れば参考にできる まず初めに、AITCに配属された新人にかかわるトピックを扱う専用Teamsチャネルを作成しました。このチャネルはAITCメンバーであればだれでも閲覧・コメント可能です。 実際に、「初めての顔合わせ時の服装」という新人にとってはハードルの高いトピックも、気軽にやり取りされていました。 以下のような形で、新人の方へ宣伝するメンバーも。 日報を投稿していただき、部全体からリアクションをもらえるように 【狙い】 新人が日報を通じて文章を書く力、取り組んだことを整理・アウトプットする力を身に着ける AITC全体で新人が取り組んでいることや困っていることを把握し、サポートできるようにする 配属後2か月間、上記の新人専用Teamsチャネル内での専用スレッド上に、YWTP形式(やったこと・わかったこと・できたこと・問題)の日報を書いていただきました。 日報はOJTリーダーが、新人の方が自分のこなしているタスクを理解できているか?問題や悩み事はなにか?を把握する1つの手段になります。またTeamsですので他のAITCメンバーも見ることができます。 下図は一例ですが、「オフィスのプリンタ設定がうまくいかない」というコメントに対して、OJTリーダーではないAITCのメンバーの方からマニュアルリンクが連携される様子です なお、日報自体は開発チームとしても導入している仕組みで、開発チームに入る杉江さんには1月以降も継続して日報を書いていただいています。 Teams開発部屋:リアルタイムコミュニケーションの場 【狙い】 開発チームの日常的なコミュニケーション環境に新人を組み込む わからないことがあったときにOJTリーダーだけでなく、その場にいるメンバーがサポートできる環境を作る こちらは以前別記事でも紹介した取り組みになります。 tech.dentsusoken.com 開発チームでは、「開発部屋」としてTeamsの会議ルームを作業時間中立ち上げっぱなしにしています(通常時マイクとカメラはオフで、自由に離席してよいです。詳細は上記記事にて)。 杉江さんにもこの開発部屋に参加していただき、質問や聞きたいことがあればここで聞いていただくようにしました。 こうすることで、私以外のメンバーも何をしているか認知しやすくなりますし、私が不在のときも、ほかのメンバーにサポートしていただくことができます。また、杉江さんにとっても周りがどういうことに取り組んでいるか知る機会につながったのではないかな?と思います。 実際、私が1週間ほど海外出張でサポートできない期間がありましたが、ほかの開発メンバーとコミュニケーションをとりながら作業を進めてくれていました。 開発チームメンバーとの1on1ラッシュ 【狙い】 開発チームのメンバーと新人の関係性を構築する スケジュール調整などの実務を経験していただく 電通総研では1on1文化があり、とくに開発チームでは毎回新メンバーに開発チーム全員との1on1ラッシュを行ってもらっています。 何度も言いますが、新人さんを受け入れるというのは何もOJTリーダーだけが行うのではありません。チーム全体で受け入れるのです。そのためにも相互に理解をしていくのは必要で、ざっくばらんに会話する機会を用意しています。 また、1on1のスケジュール調整を新人さんにリードしてもらい、社内のツールにも慣れつつスケジュール調整の練習をしていただきました。 ゼロから機能リリースまで:半年間の実践振り返り 半年間で具体的にどのようなことに取り組んできたか、ざっくりとした時系列で紹介します。 10月~11月:簡易生成AIアプリ開発を通じて基礎とチームの「お作法」を学ぶ 【主に取り組んだこと】 Know Narratorをユーザーとして触ってみて、リバースエンジニアリング的な手法で理解を深める 既存コードには触れず、1からリポジトリを作成して環境構築・実装・テストを行う 製品へ導入予定の技術領域の検証を進める 開発プロセスは本番チームを踏襲(タスク分解→実装→テスト→コードレビュー) 【狙い、身に着けてほしいスキル】 完全に0から自力で簡易な生成AIアプリを作り「動くものができた」という体験を積み重ねる。 Know Narrator開発のお作法をこちらでも採用して、お作法に慣れていただく タスクの進め方や困ったときの相談など、OJTリーダーとの会話を通して慣れていただく 未知の技術を自分で調べて実装する経験をしていただく 【成果】 Pythonプロジェクトの環境構築(自動テスト・フォーマット含む)から生成AIアプリ作成までを完遂した 上記の過程で学んだ知見を、本番プロダクト(Know Narrator)のCI/CD改善にも還元した 検証過程で得られた技術情報をチームへ共有し、後の顧客向け機能開発の基礎となった 最初の2か月は1on1やチームの動きに慣れていただきながら、Know Narratorのコードベースとは別の場所で、0から簡単な生成AIアプリを作っていただきました。 Know Narratorのコードベースはすでに大規模になっており、製品開発未経験の方がいきなり触れるにはハードルが高いです。また、 既に開発が進んでいる製品側は環境周りやCI/CDが整備されすぎているため、「自分で環境を作り上げる経験」が積みにくい という懸念もありました。 そこで、私から以下のようなタスクをステップごとに渡しつつ、杉江さん自身でいろいろ調べながら実装していただきました。 StepをこなすたびにPRを上げてもらい、Teamsの専用スレッドで連絡をしていただく。そしてそのPRを私がレビューするという流れで進めていきました。これは開発チームの普段の開発スタイルそのものです。 AIツールの利用も任せていましたが、PRの内容や報告内容について私が質問したときに自分の言葉で答えられるようにはしてもらいました。「AIに聞いたら○○だったので...」ではなく、AIツールを利用して得られた知見を基に、自分の理解・言葉で話してもらう練習をしていただく形です。 一方、OJTリーダー目線・開発チーム目線でもこの進め方はメリットがありました。 杉江さんが出したPRを見るなかで新たな発見があり、その知見をそのまま製品のCI/CDに組み込みました。やはり既に環境(CI/CD)などが整っている製品側だと、改めて調べなおして改善する機会がなく、改善の優先度はどうしても落ちますからね 。そういう意味でも0から調べながら進めてもらえたのはよかったと思います。 最終的には、画像生成などができる簡単なチャットアプリを実装し、そこで調べていただいた内容や作ったアプリを開発チームに共有していただきました。また、共有していただいた技術要素の一部はその後、Know Narratorの機能開発に活かされ、実際にお客さまの手元に届いています。 11月~1月:開発プロセスの様々なタスクを担って開発に慣れる 【主に取り組んだこと】 Know Narratorの開発環境を構築、環境構築手順書の改善を行う リリース前のシナリオテスト実施と、バグ修正・リファクタリング 社内で開催されていたAI Coding学習会への参加 【狙い、身に着けてほしいスキル】 環境構築を通じて、手順書の「初見でわかりにくい点」を洗い出し改善する 実際の開発タスク(テスト・バグ修正)をこなし、チーム開発のフローに慣れる 既存コードのリファクタリングを通じて、コードベースの構造を理解する 【成果】 改善された環境構築手順書により、後に参画したパートナー企業のオンボーディング工数が削減された シナリオテストを通じてリリース前のバグを発見・修正し、品質担保に貢献した 既存メンバーが着手できていなかったコードのリファクタリングを完遂し、可読性と保守性を向上させた 開発のキャッチアップを終え、11月中旬からは実際のKnow Narrator開発に参加していただきました。 まずは環境構築からスタートです。 手順書はある程度整備されていましたが、それでも「初めて触る人」にしかわからないつまづきポイントはあるもの です。そこで、環境構築と並行して手順書の改善も依頼しました。 結果、ブラッシュアップされた手順書は、その後チームに参画した新メンバーの方にも共有され、環境構築のフォローをする時間を削減できました。「新メンバーの『わからない』はチームの資産になる」という良い実例だったと思います。 環境構築後は、シナリオテストを通じて製品理解を深めていただきました。テスト中に発見したバグは、起票から修正まで一貫して杉江さんに担当していただきました。 ソリューションの品質に、配属後2か月で貢献 できたのは素晴らしいです。 その後、機能開発へ進む予定でしたが、対象コードが複雑化していたため、まずはリファクタリングに取り組んでいただくことにしました。「最初の大きな実装タスクがリファクタリングで良いのか?」という迷いはありましたが、結果として 「コードの構造理解にとても役立った」 という感想を杉江さんからいただきました。(本音だったと思っています) このリファクタリングでは、既存メンバーが手を付けられていなかった技術的負債を解消できたため、チームとしても非常に助かる成果となりました。 2月~3月:主要機能の開発リードとリリース体験 【主に取り組んだこと】 継続してシナリオテストや様々な開発タスクに取り組んでいただく 主要機能の実装・テストをリードし、月次リリースにて顧客へ提供する 【狙い、身に着けてほしいスキル】 自分が実装した機能が製品として世に出る喜びと、そのためにすべきことを経験していただく 大きな機能開発を通して、設計や実装、テストはもちろん、自身のタスク整理や周辺とのコミュニケーションの経験を積む プロダクトオーナーやビジネスオーナーに対して、機能の開発状況や仕様について説明する経験をしていただく 【成果】 杉江さんが検証や仕様整理から取り組んだ機能が3月末のリリースに含まれた 開発チームの一員として様々なタスクをこなしチームに貢献した チーム外のステークホルダーに対し、実装された機能の紹介やデモなどを行った 2月からは、完全に1人の開発メンバーとして、様々な実装タスクに取り組んでいただくようになりました。OJTリーダーとして、杉江さんのタスクの調整や設計の指針を出すことはあっても、基本的には自律的に動いてもらえたと思います。 技術的にも、既存のコードのアーキテクチャを理解しての質問ができるようになっていた印象でした。また、「わからないことを聞く」ということも積極的に取り組めていたと思います。 そして、3月アップデート(Know Narratorは月次で機能追加などのアップデートを行っています)にて、 主要機能の1つを杉江さんの実装により提供 することができました。 「目標に対して何が足りないか?」「どこを修正すべきか?」といったタスク整理や、関係者とのコミュニケーションに取り組んでもらえたかと思います。 OJTリーダーとしての半年を振り返ってみて まずは、杉江さんが本当によく頑張ってくれたと感じています。 配属されて半年以内に顧客提供される機能の開発の実装を自律的に進めていただけたのは素晴らしい成果だと思います。進化の激しい生成AIにおいて、生成AIの理解を進めながらソフトウェア開発のスキルも着実に積んでいただけたのかなと。これからの活躍にも大いに期待しています。 新メンバーにとって働きやすいチームであることが大事 あらためて振り返ってみても、 「“新人”ではなく“新メンバー”として受け入れる」 というスタンスを大事にしたのはよかったなと思います。 杉江さんが感じた「疑問」や「やりづらさ」を共有してもらい、それをチームの課題として一緒に改善できたことは、私にとっても大きな収穫でした。 実際に杉江さんがどう感じていたかは彼自身の記事に譲りますが、少なくとも「やりづらいのは個人の責任ではなく、基本的にチームのプロセスの問題」というスタンスで接することは、心理的安全性の高いチーム作りにおいて大事であると私は思います。 もちろん個人の努力で解決すべきことがあるのはわかっています。お互いに指摘することも大事でしょう。しかし、信頼関係やリスペクトがなければ、新人・ベテラン問わず働きにくい職場になってしまいます。 私自身が「働きやすい」と思えるチームであり続けるためにも、このマインドはこれからも大事にしていきたいなと思っています。 OJT期間はチームのプロセスをアップデートする期間 今回のやり方がすべてのプロジェクトで通用するわけではありませんが、どんな状況であれ「今、新メンバーが来たらどう受け入れるか?」を常に意識しておくことは重要です。あくまで今回の私たちのやり方は、 2025年10月時点でのKnow NarratorチームとしてのOJT だったと思います。次のOJTでは、おそらく違うことに取り組んでもらうことになるでしょう。 このような環境だからと言って、OJTを「新人の育成=コストがかかるもの」と捉えるのは非常にもったいないですし、新人にとってはやりづらさを生む要因でしょう。 人の入れ替わりは起きるものだし、新人が来てくれるのはありがたいこと なのです。せっかく迎え入れるなら「チームのプロセスを見直す絶好の機会」と捉えて、チーム全体で成長していければと考えています。 OJTリーダー1人で頑張りすぎない OJTリーダーを担当すると、どうしても個人の業務負荷は増えます。だからこそ、「チーム全体で受け入れる」という文化・スタンスが重要です。 実際に、私もOJTリーダーとして研修などに参加しないといけないときは、ほかの方に自分のタスクを巻き取ってもらうなどサポートをいただきました。前述の通り、最初の2か月間は個別で演習課題のやり取りをしていたので、開発タスクはなるべく軽いものやレビューのみにするなど動き方も調整していました。このように、新人だけでなく、OJTリーダー側のサポートがあることは想像以上に大きかったなと感じています。 ただ、「チームのサポートが大事!」と思っていても、実際問題すぐにサポートをもらえる環境になっているのか?と言われると難しいものでしょう。人事部門からも各部署に対する説明などはしていただいていますが、現場レベルにその意識が落ちているか?は難しい課題だと思います。 OJTリーダーになったからには、自分が1人で抱え込まないためにも、周囲を巻き込むアクションが必要なのだろうと思いました。新人とチームの方々のコミュニケーションを促すこと、チームの方にOJTでどのようなことをしているかアピールすること。私はこのようなアクションをとりやすいチーム文化があったのが幸いしましたが、自分がチーム側の立場になったときもOJTペアの様子は意識していきたいですね。 上位者やチームがOJTに対して理解を深める。OJTリーダーはしっかり周りを巻き込みながら、新人も自分も働きやすい環境を整備する。このような心がけをこれからも積み重ねていきたいです。 まとめ 半年間のOJTを、OJTリーダー目線で振り返ってみました。 新人の方がめきめきと成長し、チームの戦力になっていく姿を間近で見られるのは、OJTリーダーならではの楽しさです。実際、前職でも1度OJTリーダーを担当しましたが、2回目の今回も非常に楽しかったです。 OJTリーダーの負担は確かにありますが、新人の方にはまったく関係のない話ですし、チームにとっても「新しい風」を取り込む重要なプロセスです。 自分がOJTリーダーになるにしろならないにしろ、チームに新メンバーがやってくる際は、「入ってきた方をどう受け入れ、どうトモに成長するか」に集中し、双方にとって有意義な期間にできるよう前向きにとらえていきたいですね。 最後までお読みいただきありがとうございました。杉江さん視点の記事も公開されますので、ぜひ併せてご覧ください!もしかしたら、全然違う感じ方をしているかもしれません!!! 私たちは共に働いていただける仲間を募集しています! みなさまのご応募、お待ちしています! 株式会社電通総研 新卒採用サイト 株式会社電通総研 キャリア採用サイト 執筆: @naoki.muramoto レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは、クロスイノベーション本部リーディングエッジテクノロジーセンターの山下です。 最近は、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 で執筆されました )